getmsglist(): improve [0879986] a bit
[s-mailx.git] / quit.c
blob8f4f4648528da1be34729170b423f24da986c230
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 - 2016 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,
49 QUITFLAG_EMPTYBOX = 1<<4
52 struct quitnames {
53 enum quitflags flag;
54 enum okeys okey;
57 static struct quitnames const _quitnames[] = {
58 {QUITFLAG_HOLD, ok_b_hold},
59 {QUITFLAG_KEEP, ok_b_keep},
60 {QUITFLAG_KEEPSAVE, ok_b_keepsave},
61 {QUITFLAG_APPEND, ok_b_append},
62 {QUITFLAG_EMPTYBOX, ok_b_emptybox} /* TODO obsolete emptybox */
65 static char _mboxname[PATH_MAX]; /* Name of mbox */
67 /* Touch the indicated file */
68 static void _alter(char const *name);
70 /* Preserve all the appropriate messages back in the system mailbox, and print
71 * a nice message indicated how many were saved. On any error, just return -1.
72 * Else return 0. Incorporate the any new mail that we found */
73 static int writeback(FILE *res, FILE *obuf);
75 /* Terminate an editing session by attempting to write out the user's file from
76 * the temporary. Save any new stuff appended to the file */
77 static bool_t edstop(void);
79 /* Remove "mailname", unless *keep* says otherwise; force truncation, then */
80 static void _demail(void);
82 static void
83 _alter(char const *name) /* TODO error handling */
85 #ifdef HAVE_UTIMENSAT
86 struct timespec tsa[2];
87 #else
88 struct stat sb;
89 struct utimbuf utb;
90 #endif
91 NYD_ENTER;
93 #ifdef HAVE_UTIMENSAT
94 tsa[0].tv_sec = n_time_epoch() + 1;
95 tsa[0].tv_nsec = 0;
96 tsa[1].tv_nsec = UTIME_OMIT;
97 utimensat(AT_FDCWD, name, tsa, 0);
98 #else
99 if (!stat(name, &sb)) {
100 utb.actime = n_time_epoch() + 1;
101 utb.modtime = sb.st_mtime;
102 utime(name, &utb);
104 #endif
105 NYD_LEAVE;
108 static int
109 writeback(FILE *res, FILE *obuf)
111 struct message *mp;
112 int rv = -1, p, c;
113 NYD_ENTER;
115 if (fseek(obuf, 0L, SEEK_SET) == -1)
116 goto jleave;
118 #ifndef APPEND
119 if (res != NULL)
120 while ((c = getc(res)) != EOF)
121 putc(c, obuf);
122 #endif
123 srelax_hold();
124 for (p = 0, mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
125 if ((mp->m_flag & MPRESERVE) || !(mp->m_flag & MTOUCH)) {
126 ++p;
127 if (sendmp(mp, obuf, NULL, NULL, SEND_MBOX, NULL) < 0) {
128 n_perr(mailname, 0);
129 srelax_rele();
130 goto jerror;
132 srelax();
134 srelax_rele();
135 #ifdef APPEND
136 if (res != NULL)
137 while ((c = getc(res)) != EOF)
138 putc(c, obuf);
139 #endif
140 ftrunc(obuf);
142 if (ferror(obuf)) {
143 n_perr(mailname, 0);
144 jerror:
145 fseek(obuf, 0L, SEEK_SET);
146 goto jleave;
148 if (fseek(obuf, 0L, SEEK_SET) == -1)
149 goto jleave;
151 _alter(mailname);
152 if (p == 1)
153 printf(_("Held 1 message in %s\n"), displayname);
154 else
155 printf(_("Held %d messages in %s\n"), p, displayname);
156 rv = 0;
157 jleave:
158 if (res != NULL)
159 Fclose(res);
160 NYD_LEAVE;
161 return rv;
164 static bool_t
165 edstop(void) /* TODO oh my god */
167 int gotcha, c;
168 struct message *mp;
169 FILE *obuf = NULL, *ibuf = NULL;
170 struct stat statb;
171 bool_t rv;
172 NYD_ENTER;
174 rv = TRU1;
176 if (mb.mb_perm == 0)
177 goto j_leave;
179 hold_sigs();
181 for (mp = message, gotcha = 0; PTRCMP(mp, <, message + msgCount); ++mp) {
182 if (mp->m_flag & MNEW) {
183 mp->m_flag &= ~MNEW;
184 mp->m_flag |= MSTATUS;
186 if (mp->m_flag & (MODIFY | MDELETED | MSTATUS | MFLAG | MUNFLAG |
187 MANSWER | MUNANSWER | MDRAFT | MUNDRAFT))
188 ++gotcha;
190 if (!gotcha)
191 goto jleave;
193 rv = FAL0;
195 /* TODO This is too simple minded? We should regenerate an index file
196 * TODO to be able to truly tell whether *anything* has changed!
197 * TODO (Or better: only come here.. then! It is an *object method!* */
198 /* TODO Ignoring stat error is easy, huh? */
199 if (!stat(mailname, &statb) && statb.st_size > mailsize) {
200 if ((obuf = Ftmp(NULL, "edstop", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==
201 NULL) {
202 n_perr(_("tmpfile"), 0);
203 goto jleave;
205 if ((ibuf = Zopen(mailname, "r")) == NULL) {
206 n_perr(mailname, 0);
207 Fclose(obuf);
208 goto jleave;
211 n_file_lock(fileno(ibuf), FLT_READ, 0,0, UIZ_MAX); /* TODO ign. lock err*/
212 fseek(ibuf, (long)mailsize, SEEK_SET);
213 while ((c = getc(ibuf)) != EOF) /* xxx bytewise??? TODO ... I/O error? */
214 putc(c, obuf);
215 Fclose(ibuf);
216 ibuf = obuf;
217 fflush_rewind(obuf);
220 printf(_("%s "), n_shexp_quote_cp(displayname, FAL0));
221 fflush(stdout);
223 if ((obuf = Zopen(mailname, "r+")) == NULL) {
224 n_perr(mailname, 0);
225 goto jleave;
228 n_file_lock(fileno(obuf), FLT_WRITE, 0,0, UIZ_MAX); /* TODO ign. lock err! */
229 ftrunc(obuf);
231 srelax_hold();
232 c = 0;
233 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp) {
234 if (mp->m_flag & MDELETED)
235 continue;
236 ++c;
237 if (sendmp(mp, obuf, NULL, NULL, SEND_MBOX, NULL) < 0) {
238 n_perr(mailname, 0);
239 srelax_rele();
240 goto jleave;
242 srelax();
244 srelax_rele();
246 gotcha = (c == 0 && ibuf == NULL);
247 if (ibuf != NULL) {
248 while ((c = getc(ibuf)) != EOF)
249 putc(c, obuf);
251 fflush(obuf);
252 if (ferror(obuf)) {
253 n_perr(mailname, 0);
254 goto jleave;
256 Fclose(obuf);
258 if (gotcha && !ok_blook(keep) && !ok_blook(emptybox)/* TODO obsolete eb*/) {
259 bool_t rms;
261 if ((rms = n_path_rm(mailname)) == TRU1)
262 printf((ok_blook(bsdcompat) || ok_blook(bsdmsgs))
263 ? _("removed\n") : _("removed.\n"));
264 else {
265 int e = errno;
267 printf(_("removal error (ignored)\n"));
268 n_err(_("Error removing %s (ignored):"),
269 n_shexp_quote_cp(mailname, FAL0)); /* TODO */
270 n_perr(NULL, e); /* TODO */
272 } else
273 printf((ok_blook(bsdcompat) || ok_blook(bsdmsgs))
274 ? _("complete\n") : _("updated.\n"));
275 fflush(stdout);
277 rv = TRU1;
278 jleave:
279 if (ibuf != NULL)
280 Fclose(ibuf);
281 rele_sigs();
283 if(!rv){
284 /* TODO The codebase aborted by jumping to the main loop here.
285 * TODO The OpenBSD mailx simply ignores this error.
286 * TODO For now we follow the latter unless we are interactive,
287 * TODO in which case we ask the user whether the error is to be
288 * TODO ignored or not. More of this around here in this file! */
289 rv = getapproval(_("Continue, possibly loosing changes"), TRU1);
291 j_leave:
292 NYD_LEAVE;
293 return rv;
296 static void
297 _demail(void) /* TODO error handling */
299 NYD2_ENTER;
300 if (ok_blook(keep) || n_path_rm(mailname) <= FAL0) {
301 /* TODO demail(): try use f?truncate(2) instead?! */
302 int fd = open(mailname, (O_WRONLY | O_CREAT | n_O_NOFOLLOW | O_TRUNC),
303 0600);
304 if (fd >= 0)
305 close(fd);
307 NYD2_LEAVE;
310 FL bool_t
311 quit(void)
313 int p, modify, anystat, c;
314 FILE *fbuf = NULL, *lckfp = NULL, *rbuf, *abuf;
315 struct message *mp;
316 struct stat minfo;
317 bool_t rv;
318 NYD_ENTER;
320 rv = FAL0;
321 temporary_localopts_folder_hook_unroll();
323 /* If we are read only, we can't do anything, so just return quickly */
324 /* TODO yet we cannot return quickly if resources have to be released!
325 * TODO somewhen it'll be mailbox->quit() anyway, for now do it by hand
326 *if (mb.mb_perm == 0)
327 * goto jleave;*/
328 p = (mb.mb_perm == 0);
330 /* TODO lex.c:setfile() has just called hold_sigs(); before it called
331 * TODO us, but this causes uninterruptible hangs due to blocked sigs
332 * TODO anywhere except for MB_FILE (all others install their own
333 * TODO handlers, as it seems, properly); marked YYY */
334 switch (mb.mb_type) {
335 case MB_FILE:
336 break;
337 case MB_MAILDIR:
338 rele_sigs(); /* YYY */
339 rv = maildir_quit();
340 hold_sigs(); /* YYY */
341 goto jleave;
342 #ifdef HAVE_POP3
343 case MB_POP3:
344 rele_sigs(); /* YYY */
345 rv = pop3_quit();
346 hold_sigs(); /* YYY */
347 goto jleave;
348 #endif
349 case MB_VOID:
350 rv = TRU1;
351 /* FALLTHRU */
352 default:
353 goto jleave;
355 if (p) {
356 rv = TRU1;
357 goto jleave; /* TODO */
360 /* If editing (not reading system mail box), then do the work in edstop() */
361 if (pstate & PS_EDIT) {
362 rv = edstop();
363 goto jleave;
366 /* See if there any messages to save in mbox. If no, we
367 * can save copying mbox to /tmp and back.
369 * Check also to see if any files need to be preserved.
370 * Delete all untouched messages to keep them out of mbox.
371 * If all the messages are to be preserved, just exit with
372 * a message */
373 fbuf = Zopen(mailname, "r+");
374 if (fbuf == NULL) {
375 if (errno != ENOENT)
376 jnewmail:
377 printf(_("Thou hast new mail.\n"));
378 rv = TRU1;
379 goto jleave;
382 if ((lckfp = n_dotlock(mailname, fileno(fbuf), FLT_WRITE, 0,0, UIZ_MAX)
383 ) == NULL) {
384 n_perr(_("Unable to (dot) lock mailbox"), 0);
385 Fclose(fbuf);
386 fbuf = NULL;
387 rv = getapproval(_("Continue, possibly loosing changes"), TRU1);
388 goto jleave;
391 rbuf = NULL;
392 if (!fstat(fileno(fbuf), &minfo) && minfo.st_size > mailsize) {
393 printf(_("New mail has arrived.\n"));
394 rbuf = Ftmp(NULL, "quit", OF_RDWR | OF_UNLINK | OF_REGISTER);
395 if (rbuf == NULL || fbuf == NULL)
396 goto jnewmail;
397 #ifdef APPEND
398 fseek(fbuf, (long)mailsize, SEEK_SET);
399 while ((c = getc(fbuf)) != EOF)
400 putc(c, rbuf);
401 #else
402 p = minfo.st_size - mailsize;
403 while (p-- > 0) {
404 c = getc(fbuf);
405 if (c == EOF)
406 goto jnewmail;
407 putc(c, rbuf);
409 #endif
410 fflush_rewind(rbuf);
413 anystat = holdbits();
414 modify = 0;
415 for (c = 0, p = 0, mp = message; PTRCMP(mp, <, message + msgCount); ++mp) {
416 if (mp->m_flag & MBOX)
417 c++;
418 if (mp->m_flag & MPRESERVE)
419 p++;
420 if (mp->m_flag & MODIFY)
421 modify++;
423 if (p == msgCount && !modify && !anystat) {
424 rv = TRU1;
425 if (p == 1)
426 printf(_("Held 1 message in %s\n"), displayname);
427 else if (p > 1)
428 printf(_("Held %d messages in %s\n"), p, displayname);
429 goto jleave;
432 if (c == 0) {
433 if (p != 0) {
434 if (writeback(rbuf, fbuf) >= 0)
435 rv = TRU1;
436 else
437 rv = getapproval(_("Continue, possibly loosing changes"), TRU1);
438 goto jleave;
440 goto jcream;
443 if (makembox() == STOP) {
444 rv = getapproval(_("Continue, possibly loosing changes"), TRU1);
445 goto jleave;
448 /* Now we are ready to copy back preserved files to the system mailbox, if
449 * any were requested */
450 if (p != 0) {
451 if (writeback(rbuf, fbuf) < 0)
452 rv = getapproval(_("Continue, possibly loosing changes"), TRU1);
453 goto jleave;
456 /* Finally, remove his file. If new mail has arrived, copy it back */
457 jcream:
458 if (rbuf != NULL) {
459 abuf = fbuf;
460 fseek(abuf, 0L, SEEK_SET);
461 while ((c = getc(rbuf)) != EOF)
462 putc(c, abuf);
463 Fclose(rbuf);
464 ftrunc(abuf);
465 _alter(mailname);
466 rv = TRU1;
467 } else {
468 _demail();
469 rv = TRU1;
471 jleave:
472 if (fbuf != NULL) {
473 Fclose(fbuf);
474 if (lckfp != NULL && lckfp != (FILE*)-1)
475 Pclose(lckfp, FAL0);
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 */
514 struct message *mp;
515 char *mbox, *tempQuit;
516 int mcount, c;
517 FILE *ibuf = NULL, *obuf, *abuf;
518 enum okay rv = STOP;
519 NYD_ENTER;
521 mbox = _mboxname;
522 mcount = 0;
523 if (!ok_blook(append)) {
524 if ((obuf = Ftmp(&tempQuit, "makembox",
525 OF_WRONLY | OF_HOLDSIGS | OF_REGISTER)) == NULL) {
526 n_perr(_("temporary mail quit file"), 0);
527 goto jleave;
529 if ((ibuf = Fopen(tempQuit, "r")) == NULL)
530 n_perr(tempQuit, 0);
531 Ftmp_release(&tempQuit);
532 if (ibuf == NULL) {
533 Fclose(obuf);
534 goto jleave;
537 if ((abuf = Zopen(mbox, "r")) != NULL) {
538 while ((c = getc(abuf)) != EOF)
539 putc(c, obuf);
540 Fclose(abuf);
542 if (ferror(obuf)) {
543 n_perr(_("temporary mail quit file"), 0);
544 Fclose(ibuf);
545 Fclose(obuf);
546 goto jleave;
548 Fclose(obuf);
550 if ((c = open(mbox, (O_WRONLY | O_CREAT | n_O_NOFOLLOW | O_TRUNC), 0666)
551 ) != -1)
552 close(c);
553 if ((obuf = Zopen(mbox, "r+")) == NULL) {
554 n_perr(mbox, 0);
555 Fclose(ibuf);
556 goto jleave;
558 } else {
559 if ((obuf = Zopen(mbox, "a")) == NULL) {
560 n_perr(mbox, 0);
561 goto jleave;
565 srelax_hold();
566 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp) {
567 if (mp->m_flag & MBOX) {
568 ++mcount;
569 if (sendmp(mp, obuf, saveignore, NULL, SEND_MBOX, NULL) < 0) {
570 n_perr(mbox, 0);
571 srelax_rele();
572 if (ibuf != NULL)
573 Fclose(ibuf);
574 Fclose(obuf);
575 goto jleave;
577 mp->m_flag |= MBOXED;
578 srelax();
581 srelax_rele();
583 /* Copy the user's old mbox contents back to the end of the stuff we just
584 * saved. If we are appending, this is unnecessary */
585 if (!ok_blook(append)) {
586 rewind(ibuf);
587 c = getc(ibuf);
588 while (c != EOF) {
589 putc(c, obuf);
590 if (ferror(obuf))
591 break;
592 c = getc(ibuf);
594 Fclose(ibuf);
595 fflush(obuf);
597 ftrunc(obuf);
598 if (ferror(obuf)) {
599 n_perr(mbox, 0);
600 Fclose(obuf);
601 goto jleave;
603 if (Fclose(obuf) != 0) {
604 n_perr(mbox, 0);
605 goto jleave;
607 if (mcount == 1)
608 printf(_("Saved 1 message in mbox\n"));
609 else
610 printf(_("Saved %d messages in mbox\n"), mcount);
611 rv = OKAY;
612 jleave:
613 NYD_LEAVE;
614 return rv;
617 FL void
618 save_mbox_for_possible_quitstuff(void) /* TODO try to get rid of that */
620 char const *cp;
621 NYD_ENTER;
623 if ((cp = expand("&")) == NULL)
624 cp = "";
625 n_strscpy(_mboxname, cp, sizeof _mboxname);
626 NYD_LEAVE;
629 FL int
630 savequitflags(void)
632 enum quitflags qf = 0;
633 size_t i;
634 NYD_ENTER;
636 for (i = 0; i < NELEM(_quitnames); ++i)
637 if (n_var_oklook(_quitnames[i].okey) != NULL)
638 qf |= _quitnames[i].flag;
639 NYD_LEAVE;
640 return qf;
643 FL void
644 restorequitflags(int qf)
646 size_t i;
647 NYD_ENTER;
649 for (i = 0; i < NELEM(_quitnames); ++i) {
650 char *x = n_var_oklook(_quitnames[i].okey);
651 if (qf & _quitnames[i].flag) {
652 if (x == NULL)
653 n_var_okset(_quitnames[i].okey, TRU1);
654 } else if (x != NULL)
655 n_var_okclear(_quitnames[i].okey);
657 NYD_LEAVE;
660 /* s-it-mode */