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 * SPDX-License-Identifier: BSD-3-Clause
9 * Copyright (c) 1980, 1993
10 * The Regents of the University of California. All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
39 #ifndef HAVE_AMALGAMATION
48 QUITFLAG_KEEPSAVE
= 1<<2,
49 QUITFLAG_APPEND
= 1<<3
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
}
64 static char _mboxname
[PATH_MAX
]; /* Name of mbox */
66 /* Touch the indicated file */
67 static void _alter(char const *name
);
69 /* Preserve all the appropriate messages back in the system mailbox, and print
70 * a nice message indicated how many were saved. On any error, just return -1.
71 * Else return 0. Incorporate the any new mail that we found */
72 static int writeback(FILE *res
, FILE *obuf
);
74 /* Terminate an editing session by attempting to write out the user's file from
75 * the temporary. Save any new stuff appended to the file */
76 static bool_t
edstop(void);
79 _alter(char const *name
) /* TODO error handling */
82 struct timespec tsa
[2];
87 struct n_timespec
const *tsp
;
90 tsp
= n_time_now(TRU1
); /* TODO -> eventloop */
93 tsa
[0].tv_sec
= tsp
->ts_sec
+ 1;
94 tsa
[0].tv_nsec
= tsp
->ts_nsec
;
95 tsa
[1].tv_nsec
= UTIME_OMIT
;
96 utimensat(AT_FDCWD
, name
, tsa
, 0);
98 if (!stat(name
, &sb
)) {
99 utb
.actime
= tsp
->ts_sec
;
100 utb
.modtime
= sb
.st_mtime
;
108 writeback(FILE *res
, FILE *obuf
) /* TODO errors */
114 if (fseek(obuf
, 0L, SEEK_SET
) == -1)
118 for (p
= 0, mp
= message
; PTRCMP(mp
, <, message
+ msgCount
); ++mp
)
119 if ((mp
->m_flag
& MPRESERVE
) || !(mp
->m_flag
& MTOUCH
)) {
121 if (sendmp(mp
, obuf
, NULL
, NULL
, SEND_MBOX
, NULL
) < 0) {
133 for(lastnl
= FAL0
; (c
= getc(res
)) != EOF
&& putc(c
, obuf
) != EOF
;)
134 lastnl
= (c
== '\n') ? (lastnl
? TRU2
: TRU1
) : FAL0
;
143 fseek(obuf
, 0L, SEEK_SET
);
146 if (fseek(obuf
, 0L, SEEK_SET
) == -1)
151 fprintf(n_stdout
, _("Held 1 message in %s\n"), displayname
);
153 fprintf(n_stdout
, _("Held %d messages in %s\n"), p
, displayname
);
161 edstop(void) /* TODO oh my god */
165 FILE *obuf
= NULL
, *ibuf
= NULL
;
167 enum n_fopen_state fs
;
176 for (mp
= message
, gotcha
= 0; PTRCMP(mp
, <, message
+ msgCount
); ++mp
) {
177 if (mp
->m_flag
& MNEW
) {
179 mp
->m_flag
|= MSTATUS
;
181 if (mp
->m_flag
& (MODIFY
| MDELETED
| MSTATUS
| MFLAG
| MUNFLAG
|
182 MANSWER
| MUNANSWER
| MDRAFT
| MUNDRAFT
))
190 /* TODO This is too simple minded? We should regenerate an index file
191 * TODO to be able to truly tell whether *anything* has changed!
192 * TODO (Or better: only come here.. then! It is an *object method!* */
193 /* TODO Ignoring stat error is easy, huh? */
194 if (!stat(mailname
, &statb
) && statb
.st_size
> mailsize
) {
195 if ((obuf
= Ftmp(NULL
, "edstop", OF_RDWR
| OF_UNLINK
| OF_REGISTER
)) ==
197 n_perr(_("tmpfile"), 0);
200 if ((ibuf
= n_fopen_any(mailname
, "r", NULL
)) == NULL
) {
205 n_file_lock(fileno(ibuf
), FLT_READ
, 0,0, UIZ_MAX
); /* TODO ign. lock err*/
206 fseek(ibuf
, (long)mailsize
, SEEK_SET
);
207 while ((c
= getc(ibuf
)) != EOF
) /* xxx bytewise??? TODO ... I/O error? */
215 fprintf(n_stdout
, _("%s "), n_shexp_quote_cp(displayname
, FAL0
));
218 if ((obuf
= n_fopen_any(mailname
, "r+", &fs
)) == NULL
) {
220 n_perr(n_shexp_quote_cp(mailname
, FAL0
), e
);
223 n_file_lock(fileno(obuf
), FLT_WRITE
, 0,0, UIZ_MAX
); /* TODO ign. lock err! */
228 for (mp
= message
; PTRCMP(mp
, <, message
+ msgCount
); ++mp
) {
229 if (mp
->m_flag
& MDELETED
)
232 if (sendmp(mp
, obuf
, NULL
, NULL
, SEND_MBOX
, NULL
) < 0) {
234 n_err(_("Failed to finalize %s\n"), n_shexp_quote_cp(mailname
, FAL0
));
241 gotcha
= (c
== 0 && ibuf
== NULL
);
245 for(lastnl
= FAL0
; (c
= getc(ibuf
)) != EOF
&& putc(c
, obuf
) != EOF
;)
246 lastnl
= (c
== '\n') ? (lastnl
? TRU2
: TRU1
) : FAL0
;
247 if(lastnl
!= TRU2
&& (fs
& n_PROTO_MASK
) == n_PROTO_FILE
)
250 /* May nonetheless be a broken MBOX TODO really: VFS, object KNOWS!! */
251 else if(!gotcha
&& (fs
& n_PROTO_MASK
) == n_PROTO_FILE
)
252 n_folder_mbox_prepare_append(obuf
, NULL
);
255 n_err(_("Failed to finalize %s\n"), n_shexp_quote_cp(mailname
, FAL0
));
260 /* Non-system boxes are never removed except forced via POSIX mode */
261 #ifdef HAVE_FTRUNCATE
262 ftruncate(fileno(obuf
), 0);
266 if((fd
= open(mailname
, (O_WRONLY
| O_CREAT
| n_O_NOXY_BITS
| O_TRUNC
),
271 if(ok_blook(posix
) && !ok_blook(keep
) && n_path_rm(mailname
))
272 fputs(_("removed\n"), n_stdout
);
274 fputs(_("truncated\n"), n_stdout
);
276 fputs((ok_blook(bsdcompat
) || ok_blook(bsdmsgs
))
277 ? _("complete\n") : _("updated.\n"), n_stdout
);
287 /* TODO The codebase aborted by jumping to the main loop here.
288 * TODO The OpenBSD mailx simply ignores this error.
289 * TODO For now we follow the latter unless we are interactive,
290 * TODO in which case we ask the user whether the error is to be
291 * TODO ignored or not. More of this around here in this file! */
292 rv
= getapproval(_("Continue, possibly losing changes"), TRU1
);
300 quit(bool_t hold_sigs_on
)
302 int p
, modify
, anystat
, c
;
303 FILE *fbuf
, *lckfp
, *rbuf
, *abuf
;
313 fbuf
= lckfp
= rbuf
= NULL
;
314 if(mb
.mb_digmsg
!= NULL
)
315 n_dig_msg_on_mailbox_close(&mb
);
316 temporary_folder_hook_unroll();
318 /* If we are read only, we can't do anything, so just return quickly */
319 /* TODO yet we cannot return quickly if resources have to be released!
320 * TODO somewhen it'll be mailbox->quit() anyway, for now do it by hand
321 *if (mb.mb_perm == 0)
323 p
= (mb
.mb_perm
== 0);
325 switch (mb
.mb_type
) {
330 rv
= maildir_quit(TRU1
);
335 rv
= pop3_quit(TRU1
);
341 rv
= imap_quit(TRU1
);
352 goto jleave
; /* TODO */
355 /* If editing (not reading system mail box), then do the work in edstop() */
356 if (n_pstate
& n_PS_EDIT
) {
361 /* See if there any messages to save in mbox. If no, we
362 * can save copying mbox to /tmp and back.
364 * Check also to see if any files need to be preserved.
365 * Delete all untouched messages to keep them out of mbox.
366 * If all the messages are to be preserved, just exit with
368 fbuf
= n_fopen_any(mailname
, "r+", NULL
);
370 if (n_err_no
!= n_ERR_NOENT
)
372 fprintf(n_stdout
, _("Thou hast new mail.\n"));
377 if ((lckfp
= n_dotlock(mailname
, fileno(fbuf
), FLT_WRITE
, 0,0, UIZ_MAX
)
379 n_perr(_("Unable to (dot) lock mailbox"), 0);
382 rv
= getapproval(_("Continue, possibly losing changes"), TRU1
);
387 if (!fstat(fileno(fbuf
), &minfo
) && minfo
.st_size
> mailsize
) {
390 fprintf(n_stdout
, _("New mail has arrived.\n"));
391 rbuf
= Ftmp(NULL
, "quit", OF_RDWR
| OF_UNLINK
| OF_REGISTER
);
392 if (rbuf
== NULL
|| fbuf
== NULL
)
394 fseek(fbuf
, (long)mailsize
, SEEK_SET
);
395 for(lastnl
= FAL0
; (c
= getc(fbuf
)) != EOF
&& putc(c
, rbuf
) != EOF
;)
396 lastnl
= (c
== '\n') ? (lastnl
? TRU2
: TRU1
) : FAL0
;
402 anystat
= holdbits();
404 for (c
= 0, p
= 0, mp
= message
; PTRCMP(mp
, <, message
+ msgCount
); ++mp
) {
405 if (mp
->m_flag
& MBOX
)
407 if (mp
->m_flag
& MPRESERVE
)
409 if (mp
->m_flag
& MODIFY
)
412 if (p
== msgCount
&& !modify
&& !anystat
) {
415 fprintf(n_stdout
, _("Held 1 message in %s\n"), displayname
);
417 fprintf(n_stdout
, _("Held %d messages in %s\n"), p
, displayname
);
423 if (writeback(rbuf
, fbuf
) >= 0)
426 rv
= getapproval(_("Continue, possibly losing changes"), TRU1
);
432 if (makembox() == STOP
) {
433 rv
= getapproval(_("Continue, possibly losing changes"), TRU1
);
437 /* Now we are ready to copy back preserved files to the system mailbox, if
438 * any were requested */
440 if (writeback(rbuf
, fbuf
) < 0)
441 rv
= getapproval(_("Continue, possibly losing changes"), TRU1
);
445 /* Finally, remove his file. If new mail has arrived, copy it back */
449 fseek(abuf
, 0L, SEEK_SET
);
450 while ((c
= getc(rbuf
)) != EOF
)
456 #ifdef HAVE_FTRUNCATE
457 ftruncate(fileno(fbuf
), 0);
461 if((fd
= open(mailname
, (O_WRONLY
| O_CREAT
| n_O_NOXY_BITS
| O_TRUNC
),
474 if (lckfp
!= NULL
&& lckfp
!= (FILE*)-1)
488 int anystat
, autohold
, holdbit
, nohold
;
492 autohold
= ok_blook(hold
);
493 holdbit
= autohold
? MPRESERVE
: MBOX
;
494 nohold
= MBOX
| MSAVED
| MDELETED
| MPRESERVE
;
495 if (ok_blook(keepsave
))
497 for (mp
= message
; PTRCMP(mp
, <, message
+ msgCount
); ++mp
) {
498 if (mp
->m_flag
& MNEW
) {
500 mp
->m_flag
|= MSTATUS
;
502 if (mp
->m_flag
& (MSTATUS
| MFLAG
| MUNFLAG
| MANSWER
| MUNANSWER
|
505 if (!(mp
->m_flag
& MTOUCH
))
506 mp
->m_flag
|= MPRESERVE
;
507 if (!(mp
->m_flag
& nohold
))
508 mp
->m_flag
|= holdbit
;
515 makembox(void) /* TODO oh my god (also error reporting) */
518 char *mbox
, *tempQuit
;
520 FILE *ibuf
= NULL
, *obuf
, *abuf
;
521 enum n_fopen_state fs
;
527 if (ok_blook(append
)) {
528 if ((obuf
= n_fopen_any(mbox
, "a+", &fs
)) == NULL
) {
532 if((fs
& n_PROTO_MASK
) == n_PROTO_FILE
)
533 n_folder_mbox_prepare_append(obuf
, NULL
);
535 if ((obuf
= Ftmp(&tempQuit
, "makembox",
536 OF_WRONLY
| OF_HOLDSIGS
| OF_REGISTER
)) == NULL
) {
537 n_perr(_("temporary mail quit file"), 0);
540 if ((ibuf
= Fopen(tempQuit
, "r")) == NULL
)
542 Ftmp_release(&tempQuit
);
548 if ((abuf
= n_fopen_any(mbox
, "r", &fs
)) != NULL
) {
551 for (lastnl
= FAL0
; (c
= getc(abuf
)) != EOF
&& putc(c
, obuf
) != EOF
;)
552 lastnl
= (c
== '\n') ? (lastnl
? TRU2
: TRU1
) : FAL0
;
553 if(lastnl
!= TRU2
&& (fs
& n_PROTO_MASK
) == n_PROTO_FILE
)
559 n_perr(_("temporary mail quit file"), 0);
566 if ((c
= open(mbox
, (O_WRONLY
| O_CREAT
| n_O_NOXY_BITS
| O_TRUNC
),
569 if ((obuf
= n_fopen_any(mbox
, "r+", &fs
)) == NULL
) {
577 for (mp
= message
; PTRCMP(mp
, <, message
+ msgCount
); ++mp
) {
578 if (mp
->m_flag
& MBOX
) {
581 if((fs
& n_PROTO_MASK
) == n_PROTO_IMAP
&&
582 !n_ignore_is_any(n_IGNORE_SAVE
) && imap_thisaccount(mbox
)){
583 if(imap_copy(mp
, PTR2SIZE(mp
- message
+ 1), mbox
) == STOP
)
587 if (sendmp(mp
, obuf
, n_IGNORE_SAVE
, NULL
, SEND_MBOX
, NULL
) < 0) {
598 mp
->m_flag
|= MBOXED
;
604 /* Copy the user's old mbox contents back to the end of the stuff we just
605 * saved. If we are appending, this is unnecessary */
606 if (!ok_blook(append
)) {
610 for(lastnl
= FAL0
; (c
= getc(ibuf
)) != EOF
&& putc(c
, obuf
) != EOF
;)
611 lastnl
= (c
== '\n') ? (lastnl
? TRU2
: TRU1
) : FAL0
;
612 if(lastnl
!= TRU2
&& (fs
& n_PROTO_MASK
) == n_PROTO_FILE
)
623 if (Fclose(obuf
) != 0) {
625 if((fs
& n_PROTO_MASK
) != n_PROTO_IMAP
)
631 fprintf(n_stdout
, _("Saved 1 message in mbox\n"));
633 fprintf(n_stdout
, _("Saved %d messages in mbox\n"), mcount
);
641 save_mbox_for_possible_quitstuff(void){ /* TODO try to get rid of that */
645 if((cp
= fexpand("&", FEXP_NVAR
)) == NULL
)
647 n_strscpy(_mboxname
, cp
, sizeof _mboxname
);
654 enum quitflags qf
= 0;
658 for (i
= 0; i
< n_NELEM(_quitnames
); ++i
)
659 if (n_var_oklook(_quitnames
[i
].okey
) != NULL
)
660 qf
|= _quitnames
[i
].flag
;
666 restorequitflags(int qf
)
671 for (i
= 0; i
< n_NELEM(_quitnames
); ++i
) {
672 char *x
= n_var_oklook(_quitnames
[i
].okey
);
673 if (qf
& _quitnames
[i
].flag
) {
675 n_var_okset(_quitnames
[i
].okey
, TRU1
);
676 } else if (x
!= NULL
)
677 n_var_okclear(_quitnames
[i
].okey
);