Copyright 2018
[s-mailx.git] / quit.c
blob692d7b37979f06ec18288cdc68171d0ed9c84fc6
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Termination processing.
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)
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 #ifndef APPEND
117 if (res != NULL)
118 while ((c = getc(res)) != EOF)
119 putc(c, obuf);
120 #endif
121 srelax_hold();
122 for (p = 0, mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
123 if ((mp->m_flag & MPRESERVE) || !(mp->m_flag & MTOUCH)) {
124 ++p;
125 if (sendmp(mp, obuf, NULL, NULL, SEND_MBOX, NULL) < 0) {
126 n_perr(mailname, 0);
127 srelax_rele();
128 goto jerror;
130 srelax();
132 srelax_rele();
133 #ifdef APPEND
134 if (res != NULL)
135 while ((c = getc(res)) != EOF)
136 putc(c, obuf);
137 #endif
138 ftrunc(obuf);
140 if (ferror(obuf)) {
141 n_perr(mailname, 0);
142 jerror:
143 fseek(obuf, 0L, SEEK_SET);
144 goto jleave;
146 if (fseek(obuf, 0L, SEEK_SET) == -1)
147 goto jleave;
149 _alter(mailname);
150 if (p == 1)
151 fprintf(n_stdout, _("Held 1 message in %s\n"), displayname);
152 else
153 fprintf(n_stdout, _("Held %d messages in %s\n"), p, displayname);
154 rv = 0;
155 jleave:
156 NYD_LEAVE;
157 return rv;
160 static bool_t
161 edstop(void) /* TODO oh my god */
163 int gotcha, c;
164 struct message *mp;
165 FILE *obuf = NULL, *ibuf = NULL;
166 struct stat statb;
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+", NULL)) == 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 while ((c = getc(ibuf)) != EOF)
243 putc(c, obuf);
245 fflush(obuf);
246 if (ferror(obuf)) {
247 n_err(_("Failed to finalize %s\n"), n_shexp_quote_cp(mailname, FAL0));
248 goto jleave;
251 if(gotcha){
252 /* Non-system boxes are never removed except forced via POSIX mode */
253 #ifdef HAVE_FTRUNCATE
254 ftruncate(fileno(obuf), 0);
255 #else
256 int fd;
258 if((fd = open(mailname, (O_WRONLY | O_CREAT | n_O_NOXY_BITS | O_TRUNC),
259 0600)) != -1)
260 close(fd);
261 #endif
263 if(ok_blook(posix) && !ok_blook(keep) && n_path_rm(mailname))
264 fputs(_("removed\n"), n_stdout);
265 else
266 fputs(_("truncated\n"), n_stdout);
267 } else
268 fputs((ok_blook(bsdcompat) || ok_blook(bsdmsgs))
269 ? _("complete\n") : _("updated.\n"), n_stdout);
270 fflush(n_stdout);
272 rv = TRU1;
273 jleave:
274 if (obuf != NULL)
275 Fclose(obuf);
276 if (ibuf != NULL)
277 Fclose(ibuf);
278 if(!rv){
279 /* TODO The codebase aborted by jumping to the main loop here.
280 * TODO The OpenBSD mailx simply ignores this error.
281 * TODO For now we follow the latter unless we are interactive,
282 * TODO in which case we ask the user whether the error is to be
283 * TODO ignored or not. More of this around here in this file! */
284 rv = getapproval(_("Continue, possibly loosing changes"), TRU1);
286 j_leave:
287 NYD_LEAVE;
288 return rv;
291 FL bool_t
292 quit(bool_t hold_sigs_on)
294 int p, modify, anystat, c;
295 FILE *fbuf, *lckfp, *rbuf, *abuf;
296 struct message *mp;
297 struct stat minfo;
298 bool_t rv;
299 NYD_ENTER;
301 if(!hold_sigs_on)
302 hold_sigs();
304 rv = FAL0;
305 fbuf = lckfp = rbuf = NULL;
306 temporary_folder_hook_unroll();
308 /* If we are read only, we can't do anything, so just return quickly */
309 /* TODO yet we cannot return quickly if resources have to be released!
310 * TODO somewhen it'll be mailbox->quit() anyway, for now do it by hand
311 *if (mb.mb_perm == 0)
312 * goto jleave;*/
313 p = (mb.mb_perm == 0);
315 switch (mb.mb_type) {
316 case MB_FILE:
317 break;
318 case MB_MAILDIR:
319 rv = maildir_quit(TRU1);
320 goto jleave;
321 #ifdef HAVE_POP3
322 case MB_POP3:
323 rv = pop3_quit(TRU1);
324 goto jleave;
325 #endif
326 #ifdef HAVE_IMAP
327 case MB_IMAP:
328 case MB_CACHE:
329 rv = imap_quit(TRU1);
330 goto jleave;
331 #endif
332 case MB_VOID:
333 rv = TRU1;
334 /* FALLTHRU */
335 default:
336 goto jleave;
338 if (p) {
339 rv = TRU1;
340 goto jleave; /* TODO */
343 /* If editing (not reading system mail box), then do the work in edstop() */
344 if (n_pstate & n_PS_EDIT) {
345 rv = edstop();
346 goto jleave;
349 /* See if there any messages to save in mbox. If no, we
350 * can save copying mbox to /tmp and back.
352 * Check also to see if any files need to be preserved.
353 * Delete all untouched messages to keep them out of mbox.
354 * If all the messages are to be preserved, just exit with
355 * a message */
356 fbuf = n_fopen_any(mailname, "r+", NULL);
357 if (fbuf == NULL) {
358 if (n_err_no != n_ERR_NOENT)
359 jnewmail:
360 fprintf(n_stdout, _("Thou hast new mail.\n"));
361 rv = TRU1;
362 goto jleave;
365 if ((lckfp = n_dotlock(mailname, fileno(fbuf), FLT_WRITE, 0,0, UIZ_MAX)
366 ) == NULL) {
367 n_perr(_("Unable to (dot) lock mailbox"), 0);
368 Fclose(fbuf);
369 fbuf = NULL;
370 rv = getapproval(_("Continue, possibly loosing changes"), TRU1);
371 goto jleave;
374 rbuf = NULL;
375 if (!fstat(fileno(fbuf), &minfo) && minfo.st_size > mailsize) {
376 fprintf(n_stdout, _("New mail has arrived.\n"));
377 rbuf = Ftmp(NULL, "quit", OF_RDWR | OF_UNLINK | OF_REGISTER);
378 if (rbuf == NULL || fbuf == NULL)
379 goto jnewmail;
380 #ifdef APPEND
381 fseek(fbuf, (long)mailsize, SEEK_SET);
382 while ((c = getc(fbuf)) != EOF)
383 putc(c, rbuf);
384 #else
385 p = minfo.st_size - mailsize;
386 while (p-- > 0) {
387 c = getc(fbuf);
388 if (c == EOF)
389 goto jnewmail;
390 putc(c, rbuf);
392 #endif
393 fflush_rewind(rbuf);
396 anystat = holdbits();
397 modify = 0;
398 for (c = 0, p = 0, mp = message; PTRCMP(mp, <, message + msgCount); ++mp) {
399 if (mp->m_flag & MBOX)
400 c++;
401 if (mp->m_flag & MPRESERVE)
402 p++;
403 if (mp->m_flag & MODIFY)
404 modify++;
406 if (p == msgCount && !modify && !anystat) {
407 rv = TRU1;
408 if (p == 1)
409 fprintf(n_stdout, _("Held 1 message in %s\n"), displayname);
410 else if (p > 1)
411 fprintf(n_stdout, _("Held %d messages in %s\n"), p, displayname);
412 goto jleave;
415 if (c == 0) {
416 if (p != 0) {
417 if (writeback(rbuf, fbuf) >= 0)
418 rv = TRU1;
419 else
420 rv = getapproval(_("Continue, possibly loosing changes"), TRU1);
421 goto jleave;
423 goto jcream;
426 if (makembox() == STOP) {
427 rv = getapproval(_("Continue, possibly loosing changes"), TRU1);
428 goto jleave;
431 /* Now we are ready to copy back preserved files to the system mailbox, if
432 * any were requested */
433 if (p != 0) {
434 if (writeback(rbuf, fbuf) < 0)
435 rv = getapproval(_("Continue, possibly loosing changes"), TRU1);
436 goto jleave;
439 /* Finally, remove his file. If new mail has arrived, copy it back */
440 jcream:
441 if (rbuf != NULL) {
442 abuf = fbuf;
443 fseek(abuf, 0L, SEEK_SET);
444 while ((c = getc(rbuf)) != EOF)
445 putc(c, abuf);
446 ftrunc(abuf);
447 _alter(mailname);
448 rv = TRU1;
449 } else {
450 #ifdef HAVE_FTRUNCATE
451 ftruncate(fileno(fbuf), 0);
452 #else
453 int fd;
455 if((fd = open(mailname, (O_WRONLY | O_CREAT | n_O_NOXY_BITS | O_TRUNC),
456 0600)) != -1)
457 close(fd);
458 #endif
459 if(!ok_blook(keep))
460 n_path_rm(mailname);
461 rv = TRU1;
463 jleave:
464 if(rbuf != NULL)
465 Fclose(rbuf);
466 if (fbuf != NULL) {
467 Fclose(fbuf);
468 if (lckfp != NULL && lckfp != (FILE*)-1)
469 Pclose(lckfp, FAL0);
472 if(!hold_sigs_on)
473 rele_sigs();
474 NYD_LEAVE;
475 return rv;
478 FL int
479 holdbits(void)
481 struct message *mp;
482 int anystat, autohold, holdbit, nohold;
483 NYD_ENTER;
485 anystat = 0;
486 autohold = ok_blook(hold);
487 holdbit = autohold ? MPRESERVE : MBOX;
488 nohold = MBOX | MSAVED | MDELETED | MPRESERVE;
489 if (ok_blook(keepsave))
490 nohold &= ~MSAVED;
491 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp) {
492 if (mp->m_flag & MNEW) {
493 mp->m_flag &= ~MNEW;
494 mp->m_flag |= MSTATUS;
496 if (mp->m_flag & (MSTATUS | MFLAG | MUNFLAG | MANSWER | MUNANSWER |
497 MDRAFT | MUNDRAFT))
498 ++anystat;
499 if (!(mp->m_flag & MTOUCH))
500 mp->m_flag |= MPRESERVE;
501 if (!(mp->m_flag & nohold))
502 mp->m_flag |= holdbit;
504 NYD_LEAVE;
505 return anystat;
508 FL enum okay
509 makembox(void) /* TODO oh my god */
511 struct message *mp;
512 char *mbox, *tempQuit;
513 int mcount, c;
514 FILE *ibuf = NULL, *obuf, *abuf;
515 enum n_fopen_state fs;
516 enum okay rv = STOP;
517 NYD_ENTER;
519 mbox = _mboxname;
520 mcount = 0;
521 if (!ok_blook(append)) {
522 if ((obuf = Ftmp(&tempQuit, "makembox",
523 OF_WRONLY | OF_HOLDSIGS | OF_REGISTER)) == NULL) {
524 n_perr(_("temporary mail quit file"), 0);
525 goto jleave;
527 if ((ibuf = Fopen(tempQuit, "r")) == NULL)
528 n_perr(tempQuit, 0);
529 Ftmp_release(&tempQuit);
530 if (ibuf == NULL) {
531 Fclose(obuf);
532 goto jleave;
535 if ((abuf = n_fopen_any(mbox, "r", NULL)) != NULL) {
536 while ((c = getc(abuf)) != EOF)
537 putc(c, obuf);
538 Fclose(abuf);
540 if (ferror(obuf)) {
541 n_perr(_("temporary mail quit file"), 0);
542 Fclose(ibuf);
543 Fclose(obuf);
544 goto jleave;
546 Fclose(obuf);
548 if ((c = open(mbox, (O_WRONLY | O_CREAT | n_O_NOXY_BITS | O_TRUNC),
549 0666)) != -1)
550 close(c);
551 if ((obuf = n_fopen_any(mbox, "r+", &fs)) == NULL) {
552 n_perr(mbox, 0);
553 Fclose(ibuf);
554 goto jleave;
556 } else {
557 if ((obuf = n_fopen_any(mbox, "a", &fs)) == NULL) {
558 n_perr(mbox, 0);
559 goto jleave;
563 srelax_hold();
564 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp) {
565 if (mp->m_flag & MBOX) {
566 ++mcount;
567 #ifdef HAVE_IMAP
568 if((fs & n_PROTO_MASK) == n_PROTO_IMAP &&
569 !n_ignore_is_any(n_IGNORE_SAVE) && imap_thisaccount(mbox)){
570 if(imap_copy(mp, PTR2SIZE(mp - message + 1), mbox) == STOP)
571 goto jcopyerr;
572 }else
573 #endif
574 if (sendmp(mp, obuf, n_IGNORE_SAVE, NULL, SEND_MBOX, NULL) < 0) {
575 #ifdef HAVE_IMAP
576 jcopyerr:
577 #endif
578 n_perr(mbox, 0);
579 srelax_rele();
580 if (ibuf != NULL)
581 Fclose(ibuf);
582 Fclose(obuf);
583 goto jleave;
585 mp->m_flag |= MBOXED;
586 srelax();
589 srelax_rele();
591 /* Copy the user's old mbox contents back to the end of the stuff we just
592 * saved. If we are appending, this is unnecessary */
593 if (!ok_blook(append)) {
594 rewind(ibuf);
595 c = getc(ibuf);
596 while (c != EOF) {
597 putc(c, obuf);
598 if (ferror(obuf))
599 break;
600 c = getc(ibuf);
602 Fclose(ibuf);
603 fflush(obuf);
605 ftrunc(obuf);
606 if (ferror(obuf)) {
607 n_perr(mbox, 0);
608 Fclose(obuf);
609 goto jleave;
611 if (Fclose(obuf) != 0) {
612 #ifdef HAVE_IMAP
613 if((fs & n_PROTO_MASK) != n_PROTO_IMAP)
614 #endif
615 n_perr(mbox, 0);
616 goto jleave;
618 if (mcount == 1)
619 fprintf(n_stdout, _("Saved 1 message in mbox\n"));
620 else
621 fprintf(n_stdout, _("Saved %d messages in mbox\n"), mcount);
622 rv = OKAY;
623 jleave:
624 NYD_LEAVE;
625 return rv;
628 FL void
629 save_mbox_for_possible_quitstuff(void){ /* TODO try to get rid of that */
630 char const *cp;
631 NYD2_ENTER;
633 if((cp = fexpand("&", FEXP_NVAR)) == NULL)
634 cp = n_empty;
635 n_strscpy(_mboxname, cp, sizeof _mboxname);
636 NYD2_LEAVE;
639 FL int
640 savequitflags(void)
642 enum quitflags qf = 0;
643 size_t i;
644 NYD_ENTER;
646 for (i = 0; i < n_NELEM(_quitnames); ++i)
647 if (n_var_oklook(_quitnames[i].okey) != NULL)
648 qf |= _quitnames[i].flag;
649 NYD_LEAVE;
650 return qf;
653 FL void
654 restorequitflags(int qf)
656 size_t i;
657 NYD_ENTER;
659 for (i = 0; i < n_NELEM(_quitnames); ++i) {
660 char *x = n_var_oklook(_quitnames[i].okey);
661 if (qf & _quitnames[i].flag) {
662 if (x == NULL)
663 n_var_okset(_quitnames[i].okey, TRU1);
664 } else if (x != NULL)
665 n_var_okclear(_quitnames[i].okey);
667 NYD_LEAVE;
670 /* s-it-mode */