FIX "," message specification (since [1c4b8c9], v14.8.4)..
[s-mailx.git] / quit.c
blobca3f9a16401a7a7a01caebc7c1f7c92f7de4c24b
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 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
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 void 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)
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 void
165 edstop(void) /* TODO oh my god - and REMOVE that CRAPPY reset(0) jump!! */
167 int gotcha, c;
168 struct message *mp;
169 FILE *obuf = NULL, *ibuf = NULL;
170 struct stat statb;
171 bool_t doreset;
172 NYD_ENTER;
174 hold_sigs();
175 doreset = FAL0;
177 if (mb.mb_perm == 0)
178 goto jleave;
180 for (mp = message, gotcha = 0; PTRCMP(mp, <, message + msgCount); ++mp) {
181 if (mp->m_flag & MNEW) {
182 mp->m_flag &= ~MNEW;
183 mp->m_flag |= MSTATUS;
185 if (mp->m_flag & (MODIFY | MDELETED | MSTATUS | MFLAG | MUNFLAG |
186 MANSWER | MUNANSWER | MDRAFT | MUNDRAFT))
187 ++gotcha;
189 if (!gotcha)
190 goto jleave;
192 doreset = TRU1;
194 /* TODO This is too simple minded? We should regenerate an index file
195 * TODO to be able to truly tell wether *anything* has changed!
196 * TODO (Or better: only come here.. then! It is an *object method!* */
197 /* TODO Ignoring stat error is easy, huh? */
198 if (!stat(mailname, &statb) && statb.st_size > mailsize) {
199 if ((obuf = Ftmp(NULL, "edstop", OF_RDWR | OF_UNLINK | OF_REGISTER,
200 0600)) == NULL) {
201 n_perr(_("tmpfile"), 0);
202 goto jleave;
204 if ((ibuf = Zopen(mailname, "r")) == NULL) {
205 n_perr(mailname, 0);
206 Fclose(obuf);
207 goto jleave;
210 file_lock(fileno(ibuf), FLT_READ, 0,0, 1); /* TODO ignoring lock error! */
211 fseek(ibuf, (long)mailsize, SEEK_SET);
212 while ((c = getc(ibuf)) != EOF) /* xxx bytewise??? TODO ... I/O error? */
213 putc(c, obuf);
214 Fclose(ibuf);
215 ibuf = obuf;
216 fflush_rewind(obuf);
219 printf(_("\"%s\" "), displayname);
220 fflush(stdout);
221 if ((obuf = Zopen(mailname, "r+")) == NULL) {
222 n_perr(mailname, 0);
223 goto jleave;
226 file_lock(fileno(obuf), FLT_WRITE, 0,0, 1); /* TODO ignoring lock error! */
227 ftrunc(obuf);
229 srelax_hold();
230 c = 0;
231 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp) {
232 if (mp->m_flag & MDELETED)
233 continue;
234 ++c;
235 if (sendmp(mp, obuf, NULL, NULL, SEND_MBOX, NULL) < 0) {
236 n_perr(mailname, 0);
237 srelax_rele();
238 goto jleave;
240 srelax();
242 srelax_rele();
244 gotcha = (c == 0 && ibuf == NULL);
245 if (ibuf != NULL) {
246 while ((c = getc(ibuf)) != EOF)
247 putc(c, obuf);
249 fflush(obuf);
250 if (ferror(obuf)) {
251 n_perr(mailname, 0);
252 goto jleave;
254 Fclose(obuf);
256 doreset = FAL0;
258 if (gotcha && !ok_blook(keep) && !ok_blook(emptybox)/* TODO obsolete eb*/) {
259 rm(mailname);
260 printf((ok_blook(bsdcompat) || ok_blook(bsdmsgs))
261 ? _("removed\n") : _("removed.\n"));
262 } else
263 printf((ok_blook(bsdcompat) || ok_blook(bsdmsgs))
264 ? _("complete\n") : _("updated.\n"));
265 fflush(stdout);
266 jleave:
267 if (ibuf != NULL)
268 Fclose(ibuf);
269 rele_sigs();
270 NYD_LEAVE;
271 if (doreset)
272 reset(0);
275 static void
276 _demail(void)
278 NYD2_ENTER;
279 if (ok_blook(keep) || rm(mailname) < 0) {
280 /* TODO demail(): try use f?truncate(2) instead?! */
281 int fd = open(mailname, O_WRONLY | O_CREAT | O_TRUNC, 0600);
282 if (fd >= 0)
283 close(fd);
285 NYD2_LEAVE;
288 FL void
289 quit(void)
291 int p, modify, anystat, c;
292 FILE *fbuf = NULL, *lckfp = NULL, *rbuf, *abuf;
293 struct message *mp;
294 struct stat minfo;
295 NYD_ENTER;
297 temporary_localopts_folder_hook_unroll();
299 /* If we are read only, we can't do anything, so just return quickly */
300 /* TODO yet we cannot return quickly if resources have to be released!
301 * TODO somewhen it'll be mailbox->quit() anyway, for now do it by hand
302 *if (mb.mb_perm == 0)
303 * goto jleave;*/
304 p = (mb.mb_perm == 0);
306 /* TODO lex.c:setfile() has just called hold_sigs(); before it called
307 * TODO us, but this causes uninterruptible hangs due to blocked sigs
308 * TODO anywhere except for MB_FILE (all others install their own
309 * TODO handlers, as it seems, properly); marked YYY */
310 switch (mb.mb_type) {
311 case MB_FILE:
312 break;
313 case MB_MAILDIR:
314 rele_sigs(); /* YYY */
315 maildir_quit();
316 hold_sigs(); /* YYY */
317 goto jleave;
318 #ifdef HAVE_POP3
319 case MB_POP3:
320 rele_sigs(); /* YYY */
321 pop3_quit();
322 hold_sigs(); /* YYY */
323 goto jleave;
324 #endif
325 #ifdef HAVE_IMAP
326 case MB_IMAP:
327 case MB_CACHE:
328 rele_sigs(); /* YYY */
329 imap_quit();
330 hold_sigs(); /* YYY */
331 goto jleave;
332 #endif
333 case MB_VOID:
334 default:
335 goto jleave;
337 if (p) goto jleave; /* TODO */
339 /* If editing (not reading system mail box), then do the work in edstop() */
340 if (pstate & PS_EDIT) {
341 edstop();
342 goto jleave;
345 /* See if there any messages to save in mbox. If no, we
346 * can save copying mbox to /tmp and back.
348 * Check also to see if any files need to be preserved.
349 * Delete all untouched messages to keep them out of mbox.
350 * If all the messages are to be preserved, just exit with
351 * a message */
352 fbuf = Zopen(mailname, "r+");
353 if (fbuf == NULL) {
354 if (errno == ENOENT)
355 goto jleave;
356 jnewmail:
357 printf(_("Thou hast new mail.\n"));
358 goto jleave;
361 if ((lckfp = dot_lock(mailname, fileno(fbuf), FLT_WRITE, 0,0, 1)) == NULL) {
362 n_perr(_("Unable to (dot) lock mailbox, aborting operation"), 0);
363 Fclose(fbuf);
364 fbuf = NULL;
365 goto jleave;
368 rbuf = NULL;
369 if (!fstat(fileno(fbuf), &minfo) && minfo.st_size > mailsize) {
370 printf(_("New mail has arrived.\n"));
371 rbuf = Ftmp(NULL, "quit", OF_RDWR | OF_UNLINK | OF_REGISTER, 0600);
372 if (rbuf == NULL || fbuf == NULL)
373 goto jnewmail;
374 #ifdef APPEND
375 fseek(fbuf, (long)mailsize, SEEK_SET);
376 while ((c = getc(fbuf)) != EOF)
377 putc(c, rbuf);
378 #else
379 p = minfo.st_size - mailsize;
380 while (p-- > 0) {
381 c = getc(fbuf);
382 if (c == EOF)
383 goto jnewmail;
384 putc(c, rbuf);
386 #endif
387 fflush_rewind(rbuf);
390 anystat = holdbits();
391 modify = 0;
392 for (c = 0, p = 0, mp = message; PTRCMP(mp, <, message + msgCount); ++mp) {
393 if (mp->m_flag & MBOX)
394 c++;
395 if (mp->m_flag & MPRESERVE)
396 p++;
397 if (mp->m_flag & MODIFY)
398 modify++;
400 if (p == msgCount && !modify && !anystat) {
401 if (p == 1)
402 printf(_("Held 1 message in %s\n"), displayname);
403 else if (p > 1)
404 printf(_("Held %d messages in %s\n"), p, displayname);
405 goto jleave;
407 if (c == 0) {
408 if (p != 0) {
409 writeback(rbuf, fbuf);
410 goto jleave;
412 goto jcream;
415 if (makembox() == STOP)
416 goto jleave;
418 /* Now we are ready to copy back preserved files to the system mailbox, if
419 * any were requested */
420 if (p != 0) {
421 writeback(rbuf, fbuf);
422 goto jleave;
425 /* Finally, remove his file. If new mail has arrived, copy it back */
426 jcream:
427 if (rbuf != NULL) {
428 abuf = fbuf;
429 fseek(abuf, 0L, SEEK_SET);
430 while ((c = getc(rbuf)) != EOF)
431 putc(c, abuf);
432 Fclose(rbuf);
433 ftrunc(abuf);
434 _alter(mailname);
435 goto jleave;
437 _demail();
438 jleave:
439 if (fbuf != NULL) {
440 Fclose(fbuf);
441 if (lckfp != NULL && lckfp != (FILE*)-1)
442 Pclose(lckfp, FAL0);
444 NYD_LEAVE;
447 FL int
448 holdbits(void)
450 struct message *mp;
451 int anystat, autohold, holdbit, nohold;
452 NYD_ENTER;
454 anystat = 0;
455 autohold = ok_blook(hold);
456 holdbit = autohold ? MPRESERVE : MBOX;
457 nohold = MBOX | MSAVED | MDELETED | MPRESERVE;
458 if (ok_blook(keepsave))
459 nohold &= ~MSAVED;
460 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp) {
461 if (mp->m_flag & MNEW) {
462 mp->m_flag &= ~MNEW;
463 mp->m_flag |= MSTATUS;
465 if (mp->m_flag & (MSTATUS | MFLAG | MUNFLAG | MANSWER | MUNANSWER |
466 MDRAFT | MUNDRAFT))
467 ++anystat;
468 if (!(mp->m_flag & MTOUCH))
469 mp->m_flag |= MPRESERVE;
470 if (!(mp->m_flag & nohold))
471 mp->m_flag |= holdbit;
473 NYD_LEAVE;
474 return anystat;
477 FL enum okay
478 makembox(void) /* TODO oh my god */
480 struct message *mp;
481 char *mbox, *tempQuit;
482 int mcount, c;
483 FILE *ibuf = NULL, *obuf, *abuf;
484 enum protocol prot;
485 enum okay rv = STOP;
486 NYD_ENTER;
488 mbox = _mboxname;
489 mcount = 0;
490 if (!ok_blook(append)) {
491 if ((obuf = Ftmp(&tempQuit, "makembox",
492 OF_WRONLY | OF_HOLDSIGS | OF_REGISTER, 0600)) == NULL) {
493 n_perr(_("temporary mail quit file"), 0);
494 goto jleave;
496 if ((ibuf = Fopen(tempQuit, "r")) == NULL)
497 n_perr(tempQuit, 0);
498 Ftmp_release(&tempQuit);
499 if (ibuf == NULL) {
500 Fclose(obuf);
501 goto jleave;
504 if ((abuf = Zopen(mbox, "r")) != NULL) {
505 while ((c = getc(abuf)) != EOF)
506 putc(c, obuf);
507 Fclose(abuf);
509 if (ferror(obuf)) {
510 n_perr(_("temporary mail quit file"), 0);
511 Fclose(ibuf);
512 Fclose(obuf);
513 goto jleave;
515 Fclose(obuf);
517 if ((c = open(mbox, O_CREAT | O_TRUNC | O_WRONLY, 0600)) != -1)
518 close(c);
519 if ((obuf = Zopen(mbox, "r+")) == NULL) {
520 n_perr(mbox, 0);
521 Fclose(ibuf);
522 goto jleave;
524 } else {
525 if ((obuf = Zopen(mbox, "a")) == NULL) {
526 n_perr(mbox, 0);
527 goto jleave;
529 fchmod(fileno(obuf), 0600);
532 srelax_hold();
533 prot = which_protocol(mbox);
534 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp) {
535 if (mp->m_flag & MBOX) {
536 ++mcount;
537 if (prot == PROTO_IMAP &&
538 saveignore[0].i_count == 0 && saveignore[1].i_count == 0
539 #ifdef HAVE_IMAP /* TODO revisit */
540 && imap_thisaccount(mbox)
541 #endif
543 #ifdef HAVE_IMAP
544 if (imap_copy(mp, PTR2SIZE(mp - message + 1), mbox) == STOP)
545 #endif
546 goto jerr;
547 } else if (sendmp(mp, obuf, saveignore, NULL, SEND_MBOX, NULL) < 0) {
548 n_perr(mbox, 0);
549 jerr:
550 srelax_rele();
551 if (ibuf != NULL)
552 Fclose(ibuf);
553 Fclose(obuf);
554 goto jleave;
556 mp->m_flag |= MBOXED;
557 srelax();
560 srelax_rele();
562 /* Copy the user's old mbox contents back to the end of the stuff we just
563 * saved. If we are appending, this is unnecessary */
564 if (!ok_blook(append)) {
565 rewind(ibuf);
566 c = getc(ibuf);
567 while (c != EOF) {
568 putc(c, obuf);
569 if (ferror(obuf))
570 break;
571 c = getc(ibuf);
573 Fclose(ibuf);
574 fflush(obuf);
576 ftrunc(obuf);
577 if (ferror(obuf)) {
578 n_perr(mbox, 0);
579 Fclose(obuf);
580 goto jleave;
582 if (Fclose(obuf) != 0) {
583 if (prot != PROTO_IMAP)
584 n_perr(mbox, 0);
585 goto jleave;
587 if (mcount == 1)
588 printf(_("Saved 1 message in mbox\n"));
589 else
590 printf(_("Saved %d messages in mbox\n"), mcount);
591 rv = OKAY;
592 jleave:
593 NYD_LEAVE;
594 return rv;
597 FL void
598 save_mbox_for_possible_quitstuff(void) /* TODO try to get rid of that */
600 char const *cp;
601 NYD_ENTER;
603 if ((cp = expand("&")) == NULL)
604 cp = "";
605 n_strlcpy(_mboxname, cp, sizeof _mboxname);
606 NYD_LEAVE;
609 FL int
610 savequitflags(void)
612 enum quitflags qf = 0;
613 size_t i;
614 NYD_ENTER;
616 for (i = 0; i < NELEM(_quitnames); ++i)
617 if (_var_oklook(_quitnames[i].okey) != NULL)
618 qf |= _quitnames[i].flag;
619 NYD_LEAVE;
620 return qf;
623 FL void
624 restorequitflags(int qf)
626 size_t i;
627 NYD_ENTER;
629 for (i = 0; i < NELEM(_quitnames); ++i) {
630 char *x = _var_oklook(_quitnames[i].okey);
631 if (qf & _quitnames[i].flag) {
632 if (x == NULL)
633 _var_okset(_quitnames[i].okey, TRU1);
634 } else if (x != NULL)
635 _var_okclear(_quitnames[i].okey);
637 NYD_LEAVE;
640 /* s-it-mode */