collect(): ~[FfMmUu]: should default to the "dot" (Andrew Gee)
[s-mailx.git] / quit.c
blob36132f7a83eb44c8b91c9eb634682a7d3f9a2680
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 loosing 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 case MB_MAILDIR:
326 rv = maildir_quit(TRU1);
327 goto jleave;
328 #ifdef HAVE_POP3
329 case MB_POP3:
330 rv = pop3_quit(TRU1);
331 goto jleave;
332 #endif
333 #ifdef HAVE_IMAP
334 case MB_IMAP:
335 case MB_CACHE:
336 rv = imap_quit(TRU1);
337 goto jleave;
338 #endif
339 case MB_VOID:
340 rv = TRU1;
341 /* FALLTHRU */
342 default:
343 goto jleave;
345 if (p) {
346 rv = TRU1;
347 goto jleave; /* TODO */
350 /* If editing (not reading system mail box), then do the work in edstop() */
351 if (n_pstate & n_PS_EDIT) {
352 rv = edstop();
353 goto jleave;
356 /* See if there any messages to save in mbox. If no, we
357 * can save copying mbox to /tmp and back.
359 * Check also to see if any files need to be preserved.
360 * Delete all untouched messages to keep them out of mbox.
361 * If all the messages are to be preserved, just exit with
362 * a message */
363 fbuf = n_fopen_any(mailname, "r+", NULL);
364 if (fbuf == NULL) {
365 if (n_err_no != n_ERR_NOENT)
366 jnewmail:
367 fprintf(n_stdout, _("Thou hast new mail.\n"));
368 rv = TRU1;
369 goto jleave;
372 if ((lckfp = n_dotlock(mailname, fileno(fbuf), FLT_WRITE, 0,0, UIZ_MAX)
373 ) == NULL) {
374 n_perr(_("Unable to (dot) lock mailbox"), 0);
375 Fclose(fbuf);
376 fbuf = NULL;
377 rv = getapproval(_("Continue, possibly loosing changes"), TRU1);
378 goto jleave;
381 rbuf = NULL;
382 if (!fstat(fileno(fbuf), &minfo) && minfo.st_size > mailsize) {
383 bool_t lastnl;
385 fprintf(n_stdout, _("New mail has arrived.\n"));
386 rbuf = Ftmp(NULL, "quit", OF_RDWR | OF_UNLINK | OF_REGISTER);
387 if (rbuf == NULL || fbuf == NULL)
388 goto jnewmail;
389 fseek(fbuf, (long)mailsize, SEEK_SET);
390 for(lastnl = FAL0; (c = getc(fbuf)) != EOF && putc(c, rbuf) != EOF;)
391 lastnl = (c == '\n') ? (lastnl ? TRU2 : TRU1) : FAL0;
392 if(lastnl != TRU2)
393 putc('\n', rbuf);
394 fflush_rewind(rbuf);
397 anystat = holdbits();
398 modify = 0;
399 for (c = 0, p = 0, mp = message; PTRCMP(mp, <, message + msgCount); ++mp) {
400 if (mp->m_flag & MBOX)
401 c++;
402 if (mp->m_flag & MPRESERVE)
403 p++;
404 if (mp->m_flag & MODIFY)
405 modify++;
407 if (p == msgCount && !modify && !anystat) {
408 rv = TRU1;
409 if (p == 1)
410 fprintf(n_stdout, _("Held 1 message in %s\n"), displayname);
411 else if (p > 1)
412 fprintf(n_stdout, _("Held %d messages in %s\n"), p, displayname);
413 goto jleave;
416 if (c == 0) {
417 if (p != 0) {
418 if (writeback(rbuf, fbuf) >= 0)
419 rv = TRU1;
420 else
421 rv = getapproval(_("Continue, possibly loosing changes"), TRU1);
422 goto jleave;
424 goto jcream;
427 if (makembox() == STOP) {
428 rv = getapproval(_("Continue, possibly loosing changes"), TRU1);
429 goto jleave;
432 /* Now we are ready to copy back preserved files to the system mailbox, if
433 * any were requested */
434 if (p != 0) {
435 if (writeback(rbuf, fbuf) < 0)
436 rv = getapproval(_("Continue, possibly loosing changes"), TRU1);
437 goto jleave;
440 /* Finally, remove his file. If new mail has arrived, copy it back */
441 jcream:
442 if (rbuf != NULL) {
443 abuf = fbuf;
444 fseek(abuf, 0L, SEEK_SET);
445 while ((c = getc(rbuf)) != EOF)
446 putc(c, abuf);
447 ftrunc(abuf);
448 _alter(mailname);
449 rv = TRU1;
450 } else {
451 #ifdef HAVE_FTRUNCATE
452 ftruncate(fileno(fbuf), 0);
453 #else
454 int fd;
456 if((fd = open(mailname, (O_WRONLY | O_CREAT | n_O_NOXY_BITS | O_TRUNC),
457 0600)) != -1)
458 close(fd);
459 #endif
460 if(!ok_blook(keep))
461 n_path_rm(mailname);
462 rv = TRU1;
464 jleave:
465 if(rbuf != NULL)
466 Fclose(rbuf);
467 if (fbuf != NULL) {
468 Fclose(fbuf);
469 if (lckfp != NULL && lckfp != (FILE*)-1)
470 Pclose(lckfp, FAL0);
473 if(!hold_sigs_on)
474 rele_sigs();
475 NYD_LEAVE;
476 return rv;
479 FL int
480 holdbits(void)
482 struct message *mp;
483 int anystat, autohold, holdbit, nohold;
484 NYD_ENTER;
486 anystat = 0;
487 autohold = ok_blook(hold);
488 holdbit = autohold ? MPRESERVE : MBOX;
489 nohold = MBOX | MSAVED | MDELETED | MPRESERVE;
490 if (ok_blook(keepsave))
491 nohold &= ~MSAVED;
492 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp) {
493 if (mp->m_flag & MNEW) {
494 mp->m_flag &= ~MNEW;
495 mp->m_flag |= MSTATUS;
497 if (mp->m_flag & (MSTATUS | MFLAG | MUNFLAG | MANSWER | MUNANSWER |
498 MDRAFT | MUNDRAFT))
499 ++anystat;
500 if (!(mp->m_flag & MTOUCH))
501 mp->m_flag |= MPRESERVE;
502 if (!(mp->m_flag & nohold))
503 mp->m_flag |= holdbit;
505 NYD_LEAVE;
506 return anystat;
509 FL enum okay
510 makembox(void) /* TODO oh my god (also error reporting) */
512 struct message *mp;
513 char *mbox, *tempQuit;
514 int mcount, c;
515 FILE *ibuf = NULL, *obuf, *abuf;
516 enum n_fopen_state fs;
517 enum okay rv = STOP;
518 NYD_ENTER;
520 mbox = _mboxname;
521 mcount = 0;
522 if (ok_blook(append)) {
523 if ((obuf = n_fopen_any(mbox, "a+", &fs)) == NULL) {
524 n_perr(mbox, 0);
525 goto jleave;
527 if((fs & n_PROTO_MASK) == n_PROTO_FILE)
528 n_folder_mbox_prepare_append(obuf, NULL);
529 } else {
530 if ((obuf = Ftmp(&tempQuit, "makembox",
531 OF_WRONLY | OF_HOLDSIGS | OF_REGISTER)) == NULL) {
532 n_perr(_("temporary mail quit file"), 0);
533 goto jleave;
535 if ((ibuf = Fopen(tempQuit, "r")) == NULL)
536 n_perr(tempQuit, 0);
537 Ftmp_release(&tempQuit);
538 if (ibuf == NULL) {
539 Fclose(obuf);
540 goto jleave;
543 if ((abuf = n_fopen_any(mbox, "r", &fs)) != NULL) {
544 bool_t lastnl;
546 for (lastnl = FAL0; (c = getc(abuf)) != EOF && putc(c, obuf) != EOF;)
547 lastnl = (c == '\n') ? (lastnl ? TRU2 : TRU1) : FAL0;
548 if(lastnl != TRU2 && (fs & n_PROTO_MASK) == n_PROTO_FILE)
549 putc('\n', obuf);
551 Fclose(abuf);
553 if (ferror(obuf)) {
554 n_perr(_("temporary mail quit file"), 0);
555 Fclose(ibuf);
556 Fclose(obuf);
557 goto jleave;
559 Fclose(obuf);
561 if ((c = open(mbox, (O_WRONLY | O_CREAT | n_O_NOXY_BITS | O_TRUNC),
562 0666)) != -1)
563 close(c);
564 if ((obuf = n_fopen_any(mbox, "r+", &fs)) == NULL) {
565 n_perr(mbox, 0);
566 Fclose(ibuf);
567 goto jleave;
571 srelax_hold();
572 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp) {
573 if (mp->m_flag & MBOX) {
574 ++mcount;
575 #ifdef HAVE_IMAP
576 if((fs & n_PROTO_MASK) == n_PROTO_IMAP &&
577 !n_ignore_is_any(n_IGNORE_SAVE) && imap_thisaccount(mbox)){
578 if(imap_copy(mp, PTR2SIZE(mp - message + 1), mbox) == STOP)
579 goto jcopyerr;
580 }else
581 #endif
582 if (sendmp(mp, obuf, n_IGNORE_SAVE, NULL, SEND_MBOX, NULL) < 0) {
583 #ifdef HAVE_IMAP
584 jcopyerr:
585 #endif
586 n_perr(mbox, 0);
587 srelax_rele();
588 if (ibuf != NULL)
589 Fclose(ibuf);
590 Fclose(obuf);
591 goto jleave;
593 mp->m_flag |= MBOXED;
594 srelax();
597 srelax_rele();
599 /* Copy the user's old mbox contents back to the end of the stuff we just
600 * saved. If we are appending, this is unnecessary */
601 if (!ok_blook(append)) {
602 bool_t lastnl;
604 rewind(ibuf);
605 for(lastnl = FAL0; (c = getc(ibuf)) != EOF && putc(c, obuf) != EOF;)
606 lastnl = (c == '\n') ? (lastnl ? TRU2 : TRU1) : FAL0;
607 if(lastnl != TRU2 && (fs & n_PROTO_MASK) == n_PROTO_FILE)
608 putc('\n', obuf);
609 Fclose(ibuf);
610 fflush(obuf);
612 ftrunc(obuf);
613 if (ferror(obuf)) {
614 n_perr(mbox, 0);
615 Fclose(obuf);
616 goto jleave;
618 if (Fclose(obuf) != 0) {
619 #ifdef HAVE_IMAP
620 if((fs & n_PROTO_MASK) != n_PROTO_IMAP)
621 #endif
622 n_perr(mbox, 0);
623 goto jleave;
625 if (mcount == 1)
626 fprintf(n_stdout, _("Saved 1 message in mbox\n"));
627 else
628 fprintf(n_stdout, _("Saved %d messages in mbox\n"), mcount);
629 rv = OKAY;
630 jleave:
631 NYD_LEAVE;
632 return rv;
635 FL void
636 save_mbox_for_possible_quitstuff(void){ /* TODO try to get rid of that */
637 char const *cp;
638 NYD2_ENTER;
640 if((cp = fexpand("&", FEXP_NVAR)) == NULL)
641 cp = n_empty;
642 n_strscpy(_mboxname, cp, sizeof _mboxname);
643 NYD2_LEAVE;
646 FL int
647 savequitflags(void)
649 enum quitflags qf = 0;
650 size_t i;
651 NYD_ENTER;
653 for (i = 0; i < n_NELEM(_quitnames); ++i)
654 if (n_var_oklook(_quitnames[i].okey) != NULL)
655 qf |= _quitnames[i].flag;
656 NYD_LEAVE;
657 return qf;
660 FL void
661 restorequitflags(int qf)
663 size_t i;
664 NYD_ENTER;
666 for (i = 0; i < n_NELEM(_quitnames); ++i) {
667 char *x = n_var_oklook(_quitnames[i].okey);
668 if (qf & _quitnames[i].flag) {
669 if (x == NULL)
670 n_var_okset(_quitnames[i].okey, TRU1);
671 } else if (x != NULL)
672 n_var_okclear(_quitnames[i].okey);
674 NYD_LEAVE;
677 /* s-it-mode */