Generalize and straigthen handling of the soft-exit-status *!*
[s-mailx.git] / quit.c
blobe154265c9060f11b660dd2e801da038c0523a0e7
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 - 2017 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 NYD_ENTER;
88 #ifdef HAVE_UTIMENSAT
89 tsa[0].tv_sec = n_time_epoch() + 1;
90 tsa[0].tv_nsec = 0;
91 tsa[1].tv_nsec = UTIME_OMIT;
92 utimensat(AT_FDCWD, name, tsa, 0);
93 #else
94 if (!stat(name, &sb)) {
95 utb.actime = n_time_epoch() + 1;
96 utb.modtime = sb.st_mtime;
97 utime(name, &utb);
99 #endif
100 NYD_LEAVE;
103 static int
104 writeback(FILE *res, FILE *obuf)
106 struct message *mp;
107 int rv = -1, p, c;
108 NYD_ENTER;
110 if (fseek(obuf, 0L, SEEK_SET) == -1)
111 goto jleave;
113 #ifndef APPEND
114 if (res != NULL)
115 while ((c = getc(res)) != EOF)
116 putc(c, obuf);
117 #endif
118 srelax_hold();
119 for (p = 0, mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
120 if ((mp->m_flag & MPRESERVE) || !(mp->m_flag & MTOUCH)) {
121 ++p;
122 if (sendmp(mp, obuf, NULL, NULL, SEND_MBOX, NULL) < 0) {
123 n_perr(mailname, 0);
124 srelax_rele();
125 goto jerror;
127 srelax();
129 srelax_rele();
130 #ifdef APPEND
131 if (res != NULL)
132 while ((c = getc(res)) != EOF)
133 putc(c, obuf);
134 #endif
135 ftrunc(obuf);
137 if (ferror(obuf)) {
138 n_perr(mailname, 0);
139 jerror:
140 fseek(obuf, 0L, SEEK_SET);
141 goto jleave;
143 if (fseek(obuf, 0L, SEEK_SET) == -1)
144 goto jleave;
146 _alter(mailname);
147 if (p == 1)
148 fprintf(n_stdout, _("Held 1 message in %s\n"), displayname);
149 else
150 fprintf(n_stdout, _("Held %d messages in %s\n"), p, displayname);
151 rv = 0;
152 jleave:
153 if (res != NULL)
154 Fclose(res);
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 bool_t rv;
167 NYD_ENTER;
169 rv = TRU1;
171 if (mb.mb_perm == 0)
172 goto j_leave;
174 for (mp = message, gotcha = 0; PTRCMP(mp, <, message + msgCount); ++mp) {
175 if (mp->m_flag & MNEW) {
176 mp->m_flag &= ~MNEW;
177 mp->m_flag |= MSTATUS;
179 if (mp->m_flag & (MODIFY | MDELETED | MSTATUS | MFLAG | MUNFLAG |
180 MANSWER | MUNANSWER | MDRAFT | MUNDRAFT))
181 ++gotcha;
183 if (!gotcha)
184 goto jleave;
186 rv = FAL0;
188 /* TODO This is too simple minded? We should regenerate an index file
189 * TODO to be able to truly tell whether *anything* has changed!
190 * TODO (Or better: only come here.. then! It is an *object method!* */
191 /* TODO Ignoring stat error is easy, huh? */
192 if (!stat(mailname, &statb) && statb.st_size > mailsize) {
193 if ((obuf = Ftmp(NULL, "edstop", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==
194 NULL) {
195 n_perr(_("tmpfile"), 0);
196 goto jleave;
198 if ((ibuf = Zopen(mailname, "r")) == NULL) {
199 n_perr(mailname, 0);
200 goto jleave;
203 n_file_lock(fileno(ibuf), FLT_READ, 0,0, UIZ_MAX); /* TODO ign. lock err*/
204 fseek(ibuf, (long)mailsize, SEEK_SET);
205 while ((c = getc(ibuf)) != EOF) /* xxx bytewise??? TODO ... I/O error? */
206 putc(c, obuf);
207 Fclose(ibuf);
208 ibuf = obuf;
209 fflush_rewind(obuf);
210 /*obuf = NULL;*/
213 fprintf(n_stdout, _("%s "), n_shexp_quote_cp(displayname, FAL0));
214 fflush(n_stdout);
216 if ((obuf = Zopen(mailname, "r+")) == NULL) {
217 int e = errno;
218 n_perr(n_shexp_quote_cp(mailname, FAL0), e);
219 goto jleave;
221 n_file_lock(fileno(obuf), FLT_WRITE, 0,0, UIZ_MAX); /* TODO ign. lock err! */
222 ftrunc(obuf);
224 srelax_hold();
225 c = 0;
226 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp) {
227 if (mp->m_flag & MDELETED)
228 continue;
229 ++c;
230 if (sendmp(mp, obuf, NULL, NULL, SEND_MBOX, NULL) < 0) {
231 srelax_rele();
232 n_err(_("Failed to finalize %s\n"), n_shexp_quote_cp(mailname, FAL0));
233 goto jleave;
235 srelax();
237 srelax_rele();
239 gotcha = (c == 0 && ibuf == NULL);
240 if (ibuf != NULL) {
241 while ((c = getc(ibuf)) != EOF)
242 putc(c, obuf);
244 fflush(obuf);
245 if (ferror(obuf)) {
246 n_err(_("Failed to finalize %s\n"), n_shexp_quote_cp(mailname, FAL0));
247 goto jleave;
250 if(gotcha){
251 /* Non-system boxes are never removed except forced via POSIX mode */
252 #ifdef HAVE_FTRUNCATE
253 ftruncate(fileno(obuf), 0);
254 #else
255 int fd;
257 if((fd = open(mailname, (O_WRONLY | O_CREAT | n_O_NOFOLLOW | O_TRUNC),
258 0600)) != -1)
259 close(fd);
260 #endif
262 if(ok_blook(posix) && !ok_blook(keep) && n_path_rm(mailname))
263 fputs(_("removed\n"), n_stdout);
264 else
265 fputs(_("truncated\n"), n_stdout);
266 } else
267 fputs((ok_blook(bsdcompat) || ok_blook(bsdmsgs))
268 ? _("complete\n") : _("updated.\n"), n_stdout);
269 fflush(n_stdout);
271 rv = TRU1;
272 jleave:
273 if (obuf != NULL)
274 Fclose(obuf);
275 if (ibuf != NULL)
276 Fclose(ibuf);
277 if(!rv){
278 /* TODO The codebase aborted by jumping to the main loop here.
279 * TODO The OpenBSD mailx simply ignores this error.
280 * TODO For now we follow the latter unless we are interactive,
281 * TODO in which case we ask the user whether the error is to be
282 * TODO ignored or not. More of this around here in this file! */
283 rv = getapproval(_("Continue, possibly loosing changes"), TRU1);
285 j_leave:
286 NYD_LEAVE;
287 return rv;
290 FL bool_t
291 quit(bool_t hold_sigs_on)
293 int p, modify, anystat, c;
294 FILE *fbuf = NULL, *lckfp = NULL, *rbuf, *abuf;
295 struct message *mp;
296 struct stat minfo;
297 bool_t rv;
298 NYD_ENTER;
300 if(!hold_sigs_on)
301 hold_sigs();
303 rv = FAL0;
304 temporary_folder_hook_unroll();
306 /* If we are read only, we can't do anything, so just return quickly */
307 /* TODO yet we cannot return quickly if resources have to be released!
308 * TODO somewhen it'll be mailbox->quit() anyway, for now do it by hand
309 *if (mb.mb_perm == 0)
310 * goto jleave;*/
311 p = (mb.mb_perm == 0);
313 switch (mb.mb_type) {
314 case MB_FILE:
315 break;
316 case MB_MAILDIR:
317 rv = maildir_quit(TRU1);
318 goto jleave;
319 #ifdef HAVE_POP3
320 case MB_POP3:
321 rv = pop3_quit(TRU1);
322 goto jleave;
323 #endif
324 case MB_VOID:
325 rv = TRU1;
326 /* FALLTHRU */
327 default:
328 goto jleave;
330 if (p) {
331 rv = TRU1;
332 goto jleave; /* TODO */
335 /* If editing (not reading system mail box), then do the work in edstop() */
336 if (n_pstate & n_PS_EDIT) {
337 rv = edstop();
338 goto jleave;
341 /* See if there any messages to save in mbox. If no, we
342 * can save copying mbox to /tmp and back.
344 * Check also to see if any files need to be preserved.
345 * Delete all untouched messages to keep them out of mbox.
346 * If all the messages are to be preserved, just exit with
347 * a message */
348 fbuf = Zopen(mailname, "r+");
349 if (fbuf == NULL) {
350 if (errno != ENOENT)
351 jnewmail:
352 fprintf(n_stdout, _("Thou hast new mail.\n"));
353 rv = TRU1;
354 goto jleave;
357 if ((lckfp = n_dotlock(mailname, fileno(fbuf), FLT_WRITE, 0,0, UIZ_MAX)
358 ) == NULL) {
359 n_perr(_("Unable to (dot) lock mailbox"), 0);
360 Fclose(fbuf);
361 fbuf = NULL;
362 rv = getapproval(_("Continue, possibly loosing changes"), TRU1);
363 goto jleave;
366 rbuf = NULL;
367 if (!fstat(fileno(fbuf), &minfo) && minfo.st_size > mailsize) {
368 fprintf(n_stdout, _("New mail has arrived.\n"));
369 rbuf = Ftmp(NULL, "quit", OF_RDWR | OF_UNLINK | OF_REGISTER);
370 if (rbuf == NULL || fbuf == NULL)
371 goto jnewmail;
372 #ifdef APPEND
373 fseek(fbuf, (long)mailsize, SEEK_SET);
374 while ((c = getc(fbuf)) != EOF)
375 putc(c, rbuf);
376 #else
377 p = minfo.st_size - mailsize;
378 while (p-- > 0) {
379 c = getc(fbuf);
380 if (c == EOF)
381 goto jnewmail;
382 putc(c, rbuf);
384 #endif
385 fflush_rewind(rbuf);
388 anystat = holdbits();
389 modify = 0;
390 for (c = 0, p = 0, mp = message; PTRCMP(mp, <, message + msgCount); ++mp) {
391 if (mp->m_flag & MBOX)
392 c++;
393 if (mp->m_flag & MPRESERVE)
394 p++;
395 if (mp->m_flag & MODIFY)
396 modify++;
398 if (p == msgCount && !modify && !anystat) {
399 rv = TRU1;
400 if (p == 1)
401 fprintf(n_stdout, _("Held 1 message in %s\n"), displayname);
402 else if (p > 1)
403 fprintf(n_stdout, _("Held %d messages in %s\n"), p, displayname);
404 goto jleave;
407 if (c == 0) {
408 if (p != 0) {
409 if (writeback(rbuf, fbuf) >= 0)
410 rv = TRU1;
411 else
412 rv = getapproval(_("Continue, possibly loosing changes"), TRU1);
413 goto jleave;
415 goto jcream;
418 if (makembox() == STOP) {
419 rv = getapproval(_("Continue, possibly loosing changes"), TRU1);
420 goto jleave;
423 /* Now we are ready to copy back preserved files to the system mailbox, if
424 * any were requested */
425 if (p != 0) {
426 if (writeback(rbuf, fbuf) < 0)
427 rv = getapproval(_("Continue, possibly loosing changes"), TRU1);
428 goto jleave;
431 /* Finally, remove his file. If new mail has arrived, copy it back */
432 jcream:
433 if (rbuf != NULL) {
434 abuf = fbuf;
435 fseek(abuf, 0L, SEEK_SET);
436 while ((c = getc(rbuf)) != EOF)
437 putc(c, abuf);
438 Fclose(rbuf);
439 ftrunc(abuf);
440 _alter(mailname);
441 rv = TRU1;
442 } else {
443 #ifdef HAVE_FTRUNCATE
444 ftruncate(fileno(fbuf), 0);
445 #else
446 int fd;
448 if((fd = open(mailname, (O_WRONLY | O_CREAT | n_O_NOFOLLOW | O_TRUNC),
449 0600)) != -1)
450 close(fd);
451 #endif
452 if(!ok_blook(keep))
453 n_path_rm(mailname);
454 rv = TRU1;
456 jleave:
457 if (fbuf != NULL) {
458 Fclose(fbuf);
459 if (lckfp != NULL && lckfp != (FILE*)-1)
460 Pclose(lckfp, FAL0);
462 if(!hold_sigs_on)
463 rele_sigs();
464 NYD_LEAVE;
465 return rv;
468 FL int
469 holdbits(void)
471 struct message *mp;
472 int anystat, autohold, holdbit, nohold;
473 NYD_ENTER;
475 anystat = 0;
476 autohold = ok_blook(hold);
477 holdbit = autohold ? MPRESERVE : MBOX;
478 nohold = MBOX | MSAVED | MDELETED | MPRESERVE;
479 if (ok_blook(keepsave))
480 nohold &= ~MSAVED;
481 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp) {
482 if (mp->m_flag & MNEW) {
483 mp->m_flag &= ~MNEW;
484 mp->m_flag |= MSTATUS;
486 if (mp->m_flag & (MSTATUS | MFLAG | MUNFLAG | MANSWER | MUNANSWER |
487 MDRAFT | MUNDRAFT))
488 ++anystat;
489 if (!(mp->m_flag & MTOUCH))
490 mp->m_flag |= MPRESERVE;
491 if (!(mp->m_flag & nohold))
492 mp->m_flag |= holdbit;
494 NYD_LEAVE;
495 return anystat;
498 FL enum okay
499 makembox(void) /* TODO oh my god */
501 struct message *mp;
502 char *mbox, *tempQuit;
503 int mcount, c;
504 FILE *ibuf = NULL, *obuf, *abuf;
505 enum okay rv = STOP;
506 NYD_ENTER;
508 mbox = _mboxname;
509 mcount = 0;
510 if (!ok_blook(append)) {
511 if ((obuf = Ftmp(&tempQuit, "makembox",
512 OF_WRONLY | OF_HOLDSIGS | OF_REGISTER)) == NULL) {
513 n_perr(_("temporary mail quit file"), 0);
514 goto jleave;
516 if ((ibuf = Fopen(tempQuit, "r")) == NULL)
517 n_perr(tempQuit, 0);
518 Ftmp_release(&tempQuit);
519 if (ibuf == NULL) {
520 Fclose(obuf);
521 goto jleave;
524 if ((abuf = Zopen(mbox, "r")) != NULL) {
525 while ((c = getc(abuf)) != EOF)
526 putc(c, obuf);
527 Fclose(abuf);
529 if (ferror(obuf)) {
530 n_perr(_("temporary mail quit file"), 0);
531 Fclose(ibuf);
532 Fclose(obuf);
533 goto jleave;
535 Fclose(obuf);
537 if ((c = open(mbox, (O_WRONLY | O_CREAT | n_O_NOFOLLOW | O_TRUNC), 0666)
538 ) != -1)
539 close(c);
540 if ((obuf = Zopen(mbox, "r+")) == NULL) {
541 n_perr(mbox, 0);
542 Fclose(ibuf);
543 goto jleave;
545 } else {
546 if ((obuf = Zopen(mbox, "a")) == NULL) {
547 n_perr(mbox, 0);
548 goto jleave;
552 srelax_hold();
553 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp) {
554 if (mp->m_flag & MBOX) {
555 ++mcount;
556 if (sendmp(mp, obuf, n_IGNORE_SAVE, NULL, SEND_MBOX, NULL) < 0) {
557 n_perr(mbox, 0);
558 srelax_rele();
559 if (ibuf != NULL)
560 Fclose(ibuf);
561 Fclose(obuf);
562 goto jleave;
564 mp->m_flag |= MBOXED;
565 srelax();
568 srelax_rele();
570 /* Copy the user's old mbox contents back to the end of the stuff we just
571 * saved. If we are appending, this is unnecessary */
572 if (!ok_blook(append)) {
573 rewind(ibuf);
574 c = getc(ibuf);
575 while (c != EOF) {
576 putc(c, obuf);
577 if (ferror(obuf))
578 break;
579 c = getc(ibuf);
581 Fclose(ibuf);
582 fflush(obuf);
584 ftrunc(obuf);
585 if (ferror(obuf)) {
586 n_perr(mbox, 0);
587 Fclose(obuf);
588 goto jleave;
590 if (Fclose(obuf) != 0) {
591 n_perr(mbox, 0);
592 goto jleave;
594 if (mcount == 1)
595 fprintf(n_stdout, _("Saved 1 message in mbox\n"));
596 else
597 fprintf(n_stdout, _("Saved %d messages in mbox\n"), mcount);
598 rv = OKAY;
599 jleave:
600 NYD_LEAVE;
601 return rv;
604 FL void
605 save_mbox_for_possible_quitstuff(void) /* TODO try to get rid of that */
607 char const *cp;
608 NYD_ENTER;
610 if ((cp = expand("&")) == NULL)
611 cp = n_empty;
612 n_strscpy(_mboxname, cp, sizeof _mboxname);
613 NYD_LEAVE;
616 FL int
617 savequitflags(void)
619 enum quitflags qf = 0;
620 size_t i;
621 NYD_ENTER;
623 for (i = 0; i < n_NELEM(_quitnames); ++i)
624 if (n_var_oklook(_quitnames[i].okey) != NULL)
625 qf |= _quitnames[i].flag;
626 NYD_LEAVE;
627 return qf;
630 FL void
631 restorequitflags(int qf)
633 size_t i;
634 NYD_ENTER;
636 for (i = 0; i < n_NELEM(_quitnames); ++i) {
637 char *x = n_var_oklook(_quitnames[i].okey);
638 if (qf & _quitnames[i].flag) {
639 if (x == NULL)
640 n_var_okset(_quitnames[i].okey, TRU1);
641 } else if (x != NULL)
642 n_var_okclear(_quitnames[i].okey);
644 NYD_LEAVE;
647 /* s-it-mode */