a_go_evaluate(): fix un/signed comparison
[s-mailx.git] / quit.c
blobd05a6db35c1125665c6ab80f7a437374c224bbbe
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Termination processing. TODO MBOX -> VFS; error handling: catastrophe!
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
6 */
7 /*
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
35 #undef n_FILE
36 #define n_FILE quit
38 #ifndef HAVE_AMALGAMATION
39 # include "nail.h"
40 #endif
42 #include <utime.h>
44 enum quitflags {
45 QUITFLAG_HOLD = 1<<0,
46 QUITFLAG_KEEP = 1<<1,
47 QUITFLAG_KEEPSAVE = 1<<2,
48 QUITFLAG_APPEND = 1<<3
51 struct quitnames {
52 enum quitflags flag;
53 enum okeys okey;
56 static struct quitnames const _quitnames[] = {
57 {QUITFLAG_HOLD, ok_b_hold},
58 {QUITFLAG_KEEP, ok_b_keep},
59 {QUITFLAG_KEEPSAVE, ok_b_keepsave},
60 {QUITFLAG_APPEND, ok_b_append}
63 static char _mboxname[PATH_MAX]; /* Name of mbox */
65 /* Touch the indicated file */
66 static void _alter(char const *name);
68 /* Preserve all the appropriate messages back in the system mailbox, and print
69 * a nice message indicated how many were saved. On any error, just return -1.
70 * Else return 0. Incorporate the any new mail that we found */
71 static int writeback(FILE *res, FILE *obuf);
73 /* Terminate an editing session by attempting to write out the user's file from
74 * the temporary. Save any new stuff appended to the file */
75 static bool_t edstop(void);
77 static void
78 _alter(char const *name) /* TODO error handling */
80 #ifdef HAVE_UTIMENSAT
81 struct timespec tsa[2];
82 #else
83 struct stat sb;
84 struct utimbuf utb;
85 #endif
86 struct n_timespec const *tsp;
87 NYD_ENTER;
89 tsp = n_time_now(TRU1); /* TODO -> eventloop */
91 #ifdef HAVE_UTIMENSAT
92 tsa[0].tv_sec = tsp->ts_sec + 1;
93 tsa[0].tv_nsec = tsp->ts_nsec;
94 tsa[1].tv_nsec = UTIME_OMIT;
95 utimensat(AT_FDCWD, name, tsa, 0);
96 #else
97 if (!stat(name, &sb)) {
98 utb.actime = tsp->ts_sec;
99 utb.modtime = sb.st_mtime;
100 utime(name, &utb);
102 #endif
103 NYD_LEAVE;
106 static int
107 writeback(FILE *res, FILE *obuf) /* TODO errors */
109 struct message *mp;
110 int rv = -1, p, c;
111 NYD_ENTER;
113 if (fseek(obuf, 0L, SEEK_SET) == -1)
114 goto jleave;
116 srelax_hold();
117 for (p = 0, mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
118 if ((mp->m_flag & MPRESERVE) || !(mp->m_flag & MTOUCH)) {
119 ++p;
120 if (sendmp(mp, obuf, NULL, NULL, SEND_MBOX, NULL) < 0) {
121 n_perr(mailname, 0);
122 srelax_rele();
123 goto jerror;
125 srelax();
127 srelax_rele();
129 if(res != NULL){
130 bool_t lastnl;
132 for(lastnl = FAL0; (c = getc(res)) != EOF && putc(c, obuf) != EOF;)
133 lastnl = (c == '\n') ? (lastnl ? TRU2 : TRU1) : FAL0;
134 if(lastnl != TRU2)
135 putc('\n', obuf);
137 ftrunc(obuf);
139 if (ferror(obuf)) {
140 n_perr(mailname, 0);
141 jerror:
142 fseek(obuf, 0L, SEEK_SET);
143 goto jleave;
145 if (fseek(obuf, 0L, SEEK_SET) == -1)
146 goto jleave;
148 _alter(mailname);
149 if (p == 1)
150 fprintf(n_stdout, _("Held 1 message in %s\n"), displayname);
151 else
152 fprintf(n_stdout, _("Held %d messages in %s\n"), p, displayname);
153 rv = 0;
154 jleave:
155 NYD_LEAVE;
156 return rv;
159 static bool_t
160 edstop(void) /* TODO oh my god */
162 int gotcha, c;
163 struct message *mp;
164 FILE *obuf = NULL, *ibuf = NULL;
165 struct stat statb;
166 enum n_fopen_state fs;
167 bool_t rv;
168 NYD_ENTER;
170 rv = TRU1;
172 if (mb.mb_perm == 0)
173 goto j_leave;
175 for (mp = message, gotcha = 0; PTRCMP(mp, <, message + msgCount); ++mp) {
176 if (mp->m_flag & MNEW) {
177 mp->m_flag &= ~MNEW;
178 mp->m_flag |= MSTATUS;
180 if (mp->m_flag & (MODIFY | MDELETED | MSTATUS | MFLAG | MUNFLAG |
181 MANSWER | MUNANSWER | MDRAFT | MUNDRAFT))
182 ++gotcha;
184 if (!gotcha)
185 goto jleave;
187 rv = FAL0;
189 /* TODO This is too simple minded? We should regenerate an index file
190 * TODO to be able to truly tell whether *anything* has changed!
191 * TODO (Or better: only come here.. then! It is an *object method!* */
192 /* TODO Ignoring stat error is easy, huh? */
193 if (!stat(mailname, &statb) && statb.st_size > mailsize) {
194 if ((obuf = Ftmp(NULL, "edstop", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==
195 NULL) {
196 n_perr(_("tmpfile"), 0);
197 goto jleave;
199 if ((ibuf = n_fopen_any(mailname, "r", NULL)) == NULL) {
200 n_perr(mailname, 0);
201 goto jleave;
204 n_file_lock(fileno(ibuf), FLT_READ, 0,0, UIZ_MAX); /* TODO ign. lock err*/
205 fseek(ibuf, (long)mailsize, SEEK_SET);
206 while ((c = getc(ibuf)) != EOF) /* xxx bytewise??? TODO ... I/O error? */
207 putc(c, obuf);
208 Fclose(ibuf);
209 ibuf = obuf;
210 fflush_rewind(obuf);
211 /*obuf = NULL;*/
214 fprintf(n_stdout, _("%s "), n_shexp_quote_cp(displayname, FAL0));
215 fflush(n_stdout);
217 if ((obuf = n_fopen_any(mailname, "r+", &fs)) == NULL) {
218 int e = n_err_no;
219 n_perr(n_shexp_quote_cp(mailname, FAL0), e);
220 goto jleave;
222 n_file_lock(fileno(obuf), FLT_WRITE, 0,0, UIZ_MAX); /* TODO ign. lock err! */
223 ftrunc(obuf);
225 srelax_hold();
226 c = 0;
227 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp) {
228 if (mp->m_flag & MDELETED)
229 continue;
230 ++c;
231 if (sendmp(mp, obuf, NULL, NULL, SEND_MBOX, NULL) < 0) {
232 srelax_rele();
233 n_err(_("Failed to finalize %s\n"), n_shexp_quote_cp(mailname, FAL0));
234 goto jleave;
236 srelax();
238 srelax_rele();
240 gotcha = (c == 0 && ibuf == NULL);
241 if (ibuf != NULL) {
242 bool_t lastnl;
244 for(lastnl = FAL0; (c = getc(ibuf)) != EOF && putc(c, obuf) != EOF;)
245 lastnl = (c == '\n') ? (lastnl ? TRU2 : TRU1) : FAL0;
246 if(lastnl != TRU2 && (fs & n_PROTO_MASK) == n_PROTO_FILE)
247 putc('\n', obuf);
249 /* May nonetheless be a broken MBOX TODO really: VFS, object KNOWS!! */
250 else if(!gotcha && (fs & n_PROTO_MASK) == n_PROTO_FILE)
251 n_folder_mbox_prepare_append(obuf, NULL);
252 fflush(obuf);
253 if (ferror(obuf)) {
254 n_err(_("Failed to finalize %s\n"), n_shexp_quote_cp(mailname, FAL0));
255 goto jleave;
258 if(gotcha){
259 /* Non-system boxes are never removed except forced via POSIX mode */
260 #ifdef HAVE_FTRUNCATE
261 ftruncate(fileno(obuf), 0);
262 #else
263 int fd;
265 if((fd = open(mailname, (O_WRONLY | O_CREAT | n_O_NOXY_BITS | O_TRUNC),
266 0600)) != -1)
267 close(fd);
268 #endif
270 if(ok_blook(posix) && !ok_blook(keep) && n_path_rm(mailname))
271 fputs(_("removed\n"), n_stdout);
272 else
273 fputs(_("truncated\n"), n_stdout);
274 } else
275 fputs((ok_blook(bsdcompat) || ok_blook(bsdmsgs))
276 ? _("complete\n") : _("updated.\n"), n_stdout);
277 fflush(n_stdout);
279 rv = TRU1;
280 jleave:
281 if (obuf != NULL)
282 Fclose(obuf);
283 if (ibuf != NULL)
284 Fclose(ibuf);
285 if(!rv){
286 /* TODO The codebase aborted by jumping to the main loop here.
287 * TODO The OpenBSD mailx simply ignores this error.
288 * TODO For now we follow the latter unless we are interactive,
289 * TODO in which case we ask the user whether the error is to be
290 * TODO ignored or not. More of this around here in this file! */
291 rv = getapproval(_("Continue, possibly losing changes"), TRU1);
293 j_leave:
294 NYD_LEAVE;
295 return rv;
298 FL bool_t
299 quit(bool_t hold_sigs_on)
301 int p, modify, anystat, c;
302 FILE *fbuf, *lckfp, *rbuf, *abuf;
303 struct message *mp;
304 struct stat minfo;
305 bool_t rv;
306 NYD_ENTER;
308 if(!hold_sigs_on)
309 hold_sigs();
311 rv = FAL0;
312 fbuf = lckfp = rbuf = NULL;
313 temporary_folder_hook_unroll();
315 /* If we are read only, we can't do anything, so just return quickly */
316 /* TODO yet we cannot return quickly if resources have to be released!
317 * TODO somewhen it'll be mailbox->quit() anyway, for now do it by hand
318 *if (mb.mb_perm == 0)
319 * goto jleave;*/
320 p = (mb.mb_perm == 0);
322 switch (mb.mb_type) {
323 case MB_FILE:
324 break;
325 #ifdef HAVE_MAILDIR
326 case MB_MAILDIR:
327 rv = maildir_quit(TRU1);
328 goto jleave;
329 #endif
330 #ifdef HAVE_POP3
331 case MB_POP3:
332 rv = pop3_quit(TRU1);
333 goto jleave;
334 #endif
335 #ifdef HAVE_IMAP
336 case MB_IMAP:
337 case MB_CACHE:
338 rv = imap_quit(TRU1);
339 goto jleave;
340 #endif
341 case MB_VOID:
342 rv = TRU1;
343 /* FALLTHRU */
344 default:
345 goto jleave;
347 if (p) {
348 rv = TRU1;
349 goto jleave; /* TODO */
352 /* If editing (not reading system mail box), then do the work in edstop() */
353 if (n_pstate & n_PS_EDIT) {
354 rv = edstop();
355 goto jleave;
358 /* See if there any messages to save in mbox. If no, we
359 * can save copying mbox to /tmp and back.
361 * Check also to see if any files need to be preserved.
362 * Delete all untouched messages to keep them out of mbox.
363 * If all the messages are to be preserved, just exit with
364 * a message */
365 fbuf = n_fopen_any(mailname, "r+", NULL);
366 if (fbuf == NULL) {
367 if (n_err_no != n_ERR_NOENT)
368 jnewmail:
369 fprintf(n_stdout, _("Thou hast new mail.\n"));
370 rv = TRU1;
371 goto jleave;
374 if ((lckfp = n_dotlock(mailname, fileno(fbuf), FLT_WRITE, 0,0, UIZ_MAX)
375 ) == NULL) {
376 n_perr(_("Unable to (dot) lock mailbox"), 0);
377 Fclose(fbuf);
378 fbuf = NULL;
379 rv = getapproval(_("Continue, possibly losing changes"), TRU1);
380 goto jleave;
383 rbuf = NULL;
384 if (!fstat(fileno(fbuf), &minfo) && minfo.st_size > mailsize) {
385 bool_t lastnl;
387 fprintf(n_stdout, _("New mail has arrived.\n"));
388 rbuf = Ftmp(NULL, "quit", OF_RDWR | OF_UNLINK | OF_REGISTER);
389 if (rbuf == NULL || fbuf == NULL)
390 goto jnewmail;
391 fseek(fbuf, (long)mailsize, SEEK_SET);
392 for(lastnl = FAL0; (c = getc(fbuf)) != EOF && putc(c, rbuf) != EOF;)
393 lastnl = (c == '\n') ? (lastnl ? TRU2 : TRU1) : FAL0;
394 if(lastnl != TRU2)
395 putc('\n', rbuf);
396 fflush_rewind(rbuf);
399 anystat = holdbits();
400 modify = 0;
401 for (c = 0, p = 0, mp = message; PTRCMP(mp, <, message + msgCount); ++mp) {
402 if (mp->m_flag & MBOX)
403 c++;
404 if (mp->m_flag & MPRESERVE)
405 p++;
406 if (mp->m_flag & MODIFY)
407 modify++;
409 if (p == msgCount && !modify && !anystat) {
410 rv = TRU1;
411 if (p == 1)
412 fprintf(n_stdout, _("Held 1 message in %s\n"), displayname);
413 else if (p > 1)
414 fprintf(n_stdout, _("Held %d messages in %s\n"), p, displayname);
415 goto jleave;
418 if (c == 0) {
419 if (p != 0) {
420 if (writeback(rbuf, fbuf) >= 0)
421 rv = TRU1;
422 else
423 rv = getapproval(_("Continue, possibly losing changes"), TRU1);
424 goto jleave;
426 goto jcream;
429 if (makembox() == STOP) {
430 rv = getapproval(_("Continue, possibly losing changes"), TRU1);
431 goto jleave;
434 /* Now we are ready to copy back preserved files to the system mailbox, if
435 * any were requested */
436 if (p != 0) {
437 if (writeback(rbuf, fbuf) < 0)
438 rv = getapproval(_("Continue, possibly losing changes"), TRU1);
439 goto jleave;
442 /* Finally, remove his file. If new mail has arrived, copy it back */
443 jcream:
444 if (rbuf != NULL) {
445 abuf = fbuf;
446 fseek(abuf, 0L, SEEK_SET);
447 while ((c = getc(rbuf)) != EOF)
448 putc(c, abuf);
449 ftrunc(abuf);
450 _alter(mailname);
451 rv = TRU1;
452 } else {
453 #ifdef HAVE_FTRUNCATE
454 ftruncate(fileno(fbuf), 0);
455 #else
456 int fd;
458 if((fd = open(mailname, (O_WRONLY | O_CREAT | n_O_NOXY_BITS | O_TRUNC),
459 0600)) != -1)
460 close(fd);
461 #endif
462 if(!ok_blook(keep))
463 n_path_rm(mailname);
464 rv = TRU1;
466 jleave:
467 if(rbuf != NULL)
468 Fclose(rbuf);
469 if (fbuf != NULL) {
470 Fclose(fbuf);
471 if (lckfp != NULL && lckfp != (FILE*)-1)
472 Pclose(lckfp, FAL0);
475 if(!hold_sigs_on)
476 rele_sigs();
477 NYD_LEAVE;
478 return rv;
481 FL int
482 holdbits(void)
484 struct message *mp;
485 int anystat, autohold, holdbit, nohold;
486 NYD_ENTER;
488 anystat = 0;
489 autohold = ok_blook(hold);
490 holdbit = autohold ? MPRESERVE : MBOX;
491 nohold = MBOX | MSAVED | MDELETED | MPRESERVE;
492 if (ok_blook(keepsave))
493 nohold &= ~MSAVED;
494 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp) {
495 if (mp->m_flag & MNEW) {
496 mp->m_flag &= ~MNEW;
497 mp->m_flag |= MSTATUS;
499 if (mp->m_flag & (MSTATUS | MFLAG | MUNFLAG | MANSWER | MUNANSWER |
500 MDRAFT | MUNDRAFT))
501 ++anystat;
502 if (!(mp->m_flag & MTOUCH))
503 mp->m_flag |= MPRESERVE;
504 if (!(mp->m_flag & nohold))
505 mp->m_flag |= holdbit;
507 NYD_LEAVE;
508 return anystat;
511 FL enum okay
512 makembox(void) /* TODO oh my god (also error reporting) */
514 struct message *mp;
515 char *mbox, *tempQuit;
516 int mcount, c;
517 FILE *ibuf = NULL, *obuf, *abuf;
518 enum n_fopen_state fs;
519 enum okay rv = STOP;
520 NYD_ENTER;
522 mbox = _mboxname;
523 mcount = 0;
524 if (ok_blook(append)) {
525 if ((obuf = n_fopen_any(mbox, "a+", &fs)) == NULL) {
526 n_perr(mbox, 0);
527 goto jleave;
529 if((fs & n_PROTO_MASK) == n_PROTO_FILE)
530 n_folder_mbox_prepare_append(obuf, NULL);
531 } else {
532 if ((obuf = Ftmp(&tempQuit, "makembox",
533 OF_WRONLY | OF_HOLDSIGS | OF_REGISTER)) == NULL) {
534 n_perr(_("temporary mail quit file"), 0);
535 goto jleave;
537 if ((ibuf = Fopen(tempQuit, "r")) == NULL)
538 n_perr(tempQuit, 0);
539 Ftmp_release(&tempQuit);
540 if (ibuf == NULL) {
541 Fclose(obuf);
542 goto jleave;
545 if ((abuf = n_fopen_any(mbox, "r", &fs)) != NULL) {
546 bool_t lastnl;
548 for (lastnl = FAL0; (c = getc(abuf)) != EOF && putc(c, obuf) != EOF;)
549 lastnl = (c == '\n') ? (lastnl ? TRU2 : TRU1) : FAL0;
550 if(lastnl != TRU2 && (fs & n_PROTO_MASK) == n_PROTO_FILE)
551 putc('\n', obuf);
553 Fclose(abuf);
555 if (ferror(obuf)) {
556 n_perr(_("temporary mail quit file"), 0);
557 Fclose(ibuf);
558 Fclose(obuf);
559 goto jleave;
561 Fclose(obuf);
563 if ((c = open(mbox, (O_WRONLY | O_CREAT | n_O_NOXY_BITS | O_TRUNC),
564 0666)) != -1)
565 close(c);
566 if ((obuf = n_fopen_any(mbox, "r+", &fs)) == NULL) {
567 n_perr(mbox, 0);
568 Fclose(ibuf);
569 goto jleave;
573 srelax_hold();
574 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp) {
575 if (mp->m_flag & MBOX) {
576 ++mcount;
577 #ifdef HAVE_IMAP
578 if((fs & n_PROTO_MASK) == n_PROTO_IMAP &&
579 !n_ignore_is_any(n_IGNORE_SAVE) && imap_thisaccount(mbox)){
580 if(imap_copy(mp, PTR2SIZE(mp - message + 1), mbox) == STOP)
581 goto jcopyerr;
582 }else
583 #endif
584 if (sendmp(mp, obuf, n_IGNORE_SAVE, NULL, SEND_MBOX, NULL) < 0) {
585 #ifdef HAVE_IMAP
586 jcopyerr:
587 #endif
588 n_perr(mbox, 0);
589 srelax_rele();
590 if (ibuf != NULL)
591 Fclose(ibuf);
592 Fclose(obuf);
593 goto jleave;
595 mp->m_flag |= MBOXED;
596 srelax();
599 srelax_rele();
601 /* Copy the user's old mbox contents back to the end of the stuff we just
602 * saved. If we are appending, this is unnecessary */
603 if (!ok_blook(append)) {
604 bool_t lastnl;
606 rewind(ibuf);
607 for(lastnl = FAL0; (c = getc(ibuf)) != EOF && putc(c, obuf) != EOF;)
608 lastnl = (c == '\n') ? (lastnl ? TRU2 : TRU1) : FAL0;
609 if(lastnl != TRU2 && (fs & n_PROTO_MASK) == n_PROTO_FILE)
610 putc('\n', obuf);
611 Fclose(ibuf);
612 fflush(obuf);
614 ftrunc(obuf);
615 if (ferror(obuf)) {
616 n_perr(mbox, 0);
617 Fclose(obuf);
618 goto jleave;
620 if (Fclose(obuf) != 0) {
621 #ifdef HAVE_IMAP
622 if((fs & n_PROTO_MASK) != n_PROTO_IMAP)
623 #endif
624 n_perr(mbox, 0);
625 goto jleave;
627 if (mcount == 1)
628 fprintf(n_stdout, _("Saved 1 message in mbox\n"));
629 else
630 fprintf(n_stdout, _("Saved %d messages in mbox\n"), mcount);
631 rv = OKAY;
632 jleave:
633 NYD_LEAVE;
634 return rv;
637 FL void
638 save_mbox_for_possible_quitstuff(void){ /* TODO try to get rid of that */
639 char const *cp;
640 NYD2_ENTER;
642 if((cp = fexpand("&", FEXP_NVAR)) == NULL)
643 cp = n_empty;
644 n_strscpy(_mboxname, cp, sizeof _mboxname);
645 NYD2_LEAVE;
648 FL int
649 savequitflags(void)
651 enum quitflags qf = 0;
652 size_t i;
653 NYD_ENTER;
655 for (i = 0; i < n_NELEM(_quitnames); ++i)
656 if (n_var_oklook(_quitnames[i].okey) != NULL)
657 qf |= _quitnames[i].flag;
658 NYD_LEAVE;
659 return qf;
662 FL void
663 restorequitflags(int qf)
665 size_t i;
666 NYD_ENTER;
668 for (i = 0; i < n_NELEM(_quitnames); ++i) {
669 char *x = n_var_oklook(_quitnames[i].okey);
670 if (qf & _quitnames[i].flag) {
671 if (x == NULL)
672 n_var_okset(_quitnames[i].okey, TRU1);
673 } else if (x != NULL)
674 n_var_okclear(_quitnames[i].okey);
676 NYD_LEAVE;
679 /* s-it-mode */