(BWDIC!) Allow `source' in `call'ed macros..
[s-mailx.git] / folder.c
blob4bd454f84d066d1338f35da9ee6ee86d8519b3ed
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Folder (mailbox) initialization, newmail announcement and related.
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 folder
38 #ifndef HAVE_AMALGAMATION
39 # include "nail.h"
40 #endif
42 #include <pwd.h>
44 /* Update mailname (if name != NULL) and displayname, return wether displayname
45 * was large enough to swallow mailname */
46 static bool_t _update_mailname(char const *name);
47 #ifdef HAVE_C90AMEND1 /* TODO unite __narrow_suffix() into one fun! */
48 SINLINE size_t __narrow_suffix(char const *cp, size_t cpl, size_t maxl);
49 #endif
51 #ifdef HAVE_C90AMEND1
52 SINLINE size_t
53 __narrow_suffix(char const *cp, size_t cpl, size_t maxl)
55 int err;
56 size_t i, ok;
57 NYD_ENTER;
59 for (err = ok = i = 0; cpl > maxl || err;) {
60 int ml = mblen(cp, cpl);
61 if (ml < 0) { /* XXX _narrow_suffix(): mblen() error; action? */
62 (void)mblen(NULL, 0);
63 err = 1;
64 ml = 1;
65 } else {
66 if (!err)
67 ok = i;
68 err = 0;
69 if (ml == 0)
70 break;
72 cp += ml;
73 i += ml;
74 cpl -= ml;
76 NYD_LEAVE;
77 return ok;
79 #endif /* HAVE_C90AMEND1 */
81 static bool_t
82 _update_mailname(char const *name)
84 char tbuf[PATH_MAX], *mailp, *dispp;
85 size_t i, j;
86 bool_t rv = TRU1;
87 NYD_ENTER;
89 /* Don't realpath(3) if it's only an update request */
90 if (name != NULL) {
91 #ifdef HAVE_REALPATH
92 enum protocol p = which_protocol(name);
93 if (p == PROTO_FILE || p == PROTO_MAILDIR) {
94 if (realpath(name, mailname) == NULL) {
95 n_err(_("Can't canonicalize \"%s\"\n"), name);
96 rv = FAL0;
97 goto jdocopy;
99 } else
100 jdocopy:
101 #endif
102 n_strscpy(mailname, name, sizeof(mailname));
105 mailp = mailname;
106 dispp = displayname;
108 /* Don't display an absolute path but "+FOLDER" if under *folder* */
109 if (getfold(tbuf, sizeof tbuf)) {
110 i = strlen(tbuf);
111 if (i < sizeof(tbuf) -1)
112 tbuf[i++] = '/';
113 if (!strncmp(tbuf, mailp, i)) {
114 mailp += i;
115 *dispp++ = '+';
119 /* We want to see the name of the folder .. on the screen */
120 i = strlen(mailp);
121 if (i < sizeof(displayname) -1)
122 memcpy(dispp, mailp, i +1);
123 else {
124 rv = FAL0;
125 /* Avoid disrupting multibyte sequences (if possible) */
126 #ifndef HAVE_C90AMEND1
127 j = sizeof(displayname) / 3 - 1;
128 i -= sizeof(displayname) - (1/* + */ + 3) - j;
129 #else
130 j = field_detect_clip(sizeof(displayname) / 3, mailp, i);
131 i = j + __narrow_suffix(mailp + j, i - j,
132 sizeof(displayname) - (1/* + */ + 3 + 1) - j);
133 #endif
134 snprintf(dispp, sizeof(displayname), "%.*s...%s",
135 (int)j, mailp, mailp + i);
137 NYD_LEAVE;
138 return rv;
141 FL int
142 setfile(char const *name, enum fedit_mode fm) /* TODO oh my god */
144 /* Note we don't 'userid(myname) != getuid()', preliminary steps are usually
145 * necessary to make a mailbox accessible by a different user, and if that
146 * has happened, let's just let the usual file perms decide */
147 static int shudclob;
149 struct stat stb;
150 size_t offset;
151 char const *who;
152 int rv, omsgCount = 0;
153 FILE *ibuf = NULL, *lckfp = NULL;
154 bool_t isdevnull = FAL0;
155 NYD_ENTER;
157 pstate &= ~PS_SETFILE_OPENED;
159 if (name[0] == '%' || ((who = shortcut_expand(name)) != NULL && *who == '%'))
160 fm |= FEDIT_SYSBOX; /* TODO fexpand() needs to tell is-valid-user! */
162 who = (name[1] != '\0') ? name + 1 : myname;
164 if ((name = expand(name)) == NULL)
165 goto jem1;
167 switch (which_protocol(name)) {
168 case PROTO_FILE:
169 if (temporary_protocol_ext != NULL)
170 name = savecat(name, temporary_protocol_ext);
171 isdevnull = ((options & OPT_BATCH_FLAG) && !strcmp(name, "/dev/null"));
172 #ifdef HAVE_REALPATH
173 do { /* TODO we need objects, goes away then */
174 char ebuf[PATH_MAX];
175 if (realpath(name, ebuf) != NULL)
176 name = savestr(ebuf);
177 } while (0);
178 #endif
179 rv = 1;
180 break;
181 case PROTO_MAILDIR:
182 shudclob = 1;
183 rv = maildir_setfile(name, fm);
184 goto jleave;
185 #ifdef HAVE_POP3
186 case PROTO_POP3:
187 shudclob = 1;
188 rv = pop3_setfile(name, fm);
189 goto jleave;
190 #endif
191 default:
192 n_err(_("Cannot handle protocol: %s\n"), name);
193 goto jem1;
196 if ((ibuf = Zopen(name, "r")) == NULL) {
197 if ((fm & FEDIT_SYSBOX) && errno == ENOENT) {
198 if (strcmp(who, myname) && getpwnam(who) == NULL) {
199 n_err(_("\"%s\" is not a user of this system\n"), who);
200 goto jem2;
202 if (!(options & OPT_QUICKRUN_MASK) && ok_blook(bsdcompat))
203 n_err(_("No mail for %s\n"), who);
204 if (fm & FEDIT_NEWMAIL)
205 goto jleave;
206 if (ok_blook(emptystart)) {
207 if (!(options & OPT_QUICKRUN_MASK) && !ok_blook(bsdcompat))
208 n_perr(name, 0);
209 /* We must avoid returning -1 and causing program exit */
210 mb.mb_type = MB_VOID;
211 rv = 1;
212 goto jleave;
214 goto jem2;
215 } else if (fm & FEDIT_NEWMAIL)
216 goto jleave;
217 n_perr(name, 0);
218 goto jem1;
221 if (fstat(fileno(ibuf), &stb) == -1) {
222 if (fm & FEDIT_NEWMAIL)
223 goto jleave;
224 n_perr(_("fstat"), 0);
225 goto jem1;
228 if (S_ISREG(stb.st_mode) || isdevnull) {
229 /* EMPTY */
230 } else {
231 if (fm & FEDIT_NEWMAIL)
232 goto jleave;
233 errno = S_ISDIR(stb.st_mode) ? EISDIR : EINVAL;
234 n_perr(name, 0);
235 goto jem1;
238 /* Looks like all will be well. We must now relinquish our hold on the
239 * current set of stuff. Must hold signals while we are reading the new
240 * file, else we will ruin the message[] data structure */
242 hold_sigs(); /* TODO note on this one in quit.c:quit() */
243 if (shudclob && !(fm & FEDIT_NEWMAIL) && !quit()) {
244 rele_sigs();
245 goto jem2;
248 #ifdef HAVE_SOCKETS
249 if (!(fm & FEDIT_NEWMAIL) && mb.mb_sock.s_fd >= 0)
250 sclose(&mb.mb_sock); /* TODO sorry? VMAILFS->close(), thank you */
251 #endif
253 /* TODO There is no intermediate VOID box we've switched to: name may
254 * TODO point to the same box that we just have written, so any updates
255 * TODO we won't see! Reopen again in this case. RACY! Goes with VOID! */
256 /* TODO In addition: in case of compressed/hook boxes we lock a temporary! */
257 /* TODO We may uselessly open twice but quit() doesn't say wether we were
258 * TODO modified so we can't tell: Mailbox::is_modified() :-(( */
259 if (/*shudclob && !(fm & FEDIT_NEWMAIL) &&*/ !strcmp(name, mailname)) {
260 name = mailname;
261 Fclose(ibuf);
263 if ((ibuf = Zopen(name, "r")) == NULL ||
264 fstat(fileno(ibuf), &stb) == -1 ||
265 (!S_ISREG(stb.st_mode) && !isdevnull)) {
266 n_perr(name, 0);
267 rele_sigs();
268 goto jem2;
272 /* Copy the messages into /tmp and set pointers */
273 if (!(fm & FEDIT_NEWMAIL)) {
274 mb.mb_type = MB_FILE;
275 mb.mb_perm = (((options & OPT_R_FLAG) || (fm & FEDIT_RDONLY) ||
276 access(name, W_OK) < 0) ? 0 : MB_DELE | MB_EDIT);
277 if (shudclob) {
278 if (mb.mb_itf) {
279 fclose(mb.mb_itf);
280 mb.mb_itf = NULL;
282 if (mb.mb_otf) {
283 fclose(mb.mb_otf);
284 mb.mb_otf = NULL;
287 shudclob = 1;
288 if (fm & FEDIT_SYSBOX)
289 pstate &= ~PS_EDIT;
290 else
291 pstate |= PS_EDIT;
292 initbox(name);
293 offset = 0;
294 } else {
295 fseek(mb.mb_otf, 0L, SEEK_END);
296 /* TODO Doing this without holding a lock is.. And not err checking.. */
297 fseek(ibuf, mailsize, SEEK_SET);
298 offset = mailsize;
299 omsgCount = msgCount;
302 if (isdevnull)
303 lckfp = (FILE*)-1;
304 else if (!(pstate & PS_EDIT))
305 lckfp = n_dotlock(name, fileno(ibuf), FLT_READ, offset,0,
306 (fm & FEDIT_NEWMAIL ? 0 : 1));
307 else if (n_file_lock(fileno(ibuf), FLT_READ, offset,0,
308 (fm & FEDIT_NEWMAIL ? 0 : 1)))
309 lckfp = (FILE*)-1;
311 if (lckfp == NULL) {
312 if (!(fm & FEDIT_NEWMAIL)) {
313 char const *emsg = (pstate & PS_EDIT)
314 ? N_("Unable to lock mailbox, aborting operation")
315 : N_("Unable to (dot) lock mailbox, aborting operation");
316 n_perr(V_(emsg), 0);
318 rele_sigs();
319 if (!(fm & FEDIT_NEWMAIL))
320 goto jem1;
321 goto jleave;
324 mailsize = fsize(ibuf);
326 /* TODO This is too simple minded? We should regenerate an index file
327 * TODO to be able to truly tell wether *anything* has changed! */
328 if ((fm & FEDIT_NEWMAIL) && UICMP(z, mailsize, <=, offset)) {
329 rele_sigs();
330 goto jleave;
332 setptr(ibuf, offset);
333 setmsize(msgCount);
334 if ((fm & FEDIT_NEWMAIL) && mb.mb_sorted) {
335 mb.mb_threaded = 0;
336 c_sort((void*)-1);
339 Fclose(ibuf);
340 ibuf = NULL;
341 if (lckfp != NULL && lckfp != (FILE*)-1) {
342 Pclose(lckfp, FAL0);
343 /*lckfp = NULL;*/
345 rele_sigs();
347 if (!(fm & FEDIT_NEWMAIL)) {
348 pstate &= ~PS_SAW_COMMAND;
349 pstate |= PS_SETFILE_OPENED;
352 if ((options & OPT_EXISTONLY) && !(options & OPT_HEADERLIST)) {
353 rv = (msgCount == 0);
354 goto jleave;
357 if ((!(pstate & PS_EDIT) || (fm & FEDIT_NEWMAIL)) && msgCount == 0) {
358 if (!(fm & FEDIT_NEWMAIL)) {
359 if (!ok_blook(emptystart))
360 n_err(_("No mail for %s\n"), who);
362 goto jleave;
365 if (fm & FEDIT_NEWMAIL)
366 newmailinfo(omsgCount);
368 rv = 0;
369 jleave:
370 if (ibuf != NULL) {
371 Fclose(ibuf);
372 if (lckfp != NULL && lckfp != (FILE*)-1)
373 Pclose(lckfp, FAL0);
375 NYD_LEAVE;
376 return rv;
377 jem2:
378 mb.mb_type = MB_VOID;
379 jem1:
380 rv = -1;
381 goto jleave;
384 FL int
385 newmailinfo(int omsgCount)
387 int mdot, i;
388 NYD_ENTER;
390 for (i = 0; i < omsgCount; ++i)
391 message[i].m_flag &= ~MNEWEST;
393 if (msgCount > omsgCount) {
394 for (i = omsgCount; i < msgCount; ++i)
395 message[i].m_flag |= MNEWEST;
396 printf(_("New mail has arrived.\n"));
397 if ((i = msgCount - omsgCount) == 1)
398 printf(_("Loaded 1 new message.\n"));
399 else
400 printf(_("Loaded %d new messages.\n"), i);
401 } else
402 printf(_("Loaded %d messages.\n"), msgCount);
404 check_folder_hook(TRU1);
406 mdot = getmdot(1);
408 if (ok_blook(header))
409 print_headers(omsgCount + 1, msgCount, FAL0);
410 NYD_LEAVE;
411 return mdot;
414 FL void
415 setmsize(int sz)
417 NYD_ENTER;
418 if (n_msgvec != NULL)
419 free(n_msgvec);
420 n_msgvec = scalloc(sz + 1, sizeof *n_msgvec);
421 NYD_LEAVE;
424 FL void
425 print_header_summary(char const *Larg)
427 size_t bot, top, i, j;
428 NYD_ENTER;
430 if (Larg != NULL) {
431 /* Avoid any messages XXX add a make_mua_silent() and use it? */
432 if ((options & (OPT_VERB | OPT_EXISTONLY)) == OPT_EXISTONLY) {
433 freopen("/dev/null", "w", stdout);
434 freopen("/dev/null", "w", stderr);
436 assert(n_msgvec != NULL);
437 i = (getmsglist(/*TODO make arg const */UNCONST(Larg), n_msgvec, 0) <= 0);
438 if (options & OPT_EXISTONLY) {
439 exit_status = (int)i;
440 goto jleave;
442 if (i)
443 goto jleave;
444 for (bot = msgCount, top = 0, i = 0; (j = n_msgvec[i]) != 0; ++i) {
445 if (bot > j)
446 bot = j;
447 if (top < j)
448 top = j;
450 } else
451 bot = 1, top = msgCount;
452 print_headers(bot, top, (Larg != NULL)); /* TODO should take iterator!! */
453 jleave:
454 NYD_LEAVE;
457 FL void
458 announce(int printheaders)
460 int vec[2], mdot;
461 NYD_ENTER;
463 mdot = newfileinfo();
464 vec[0] = mdot;
465 vec[1] = 0;
466 dot = message + mdot - 1;
467 if (printheaders && msgCount > 0 && ok_blook(header)) {
468 print_header_group(vec); /* XXX errors? */
470 NYD_LEAVE;
473 FL int
474 newfileinfo(void)
476 struct message *mp;
477 int u, n, mdot, d, s, hidden, moved;
478 NYD_ENTER;
480 if (mb.mb_type == MB_VOID) {
481 mdot = 1;
482 goto jleave;
485 mdot = getmdot(0);
486 s = d = hidden = moved =0;
487 for (mp = message, n = 0, u = 0; PTRCMP(mp, <, message + msgCount); ++mp) {
488 if (mp->m_flag & MNEW)
489 ++n;
490 if ((mp->m_flag & MREAD) == 0)
491 ++u;
492 if ((mp->m_flag & (MDELETED | MSAVED)) == (MDELETED | MSAVED))
493 ++moved;
494 if ((mp->m_flag & (MDELETED | MSAVED)) == MDELETED)
495 ++d;
496 if ((mp->m_flag & (MDELETED | MSAVED)) == MSAVED)
497 ++s;
498 if (mp->m_flag & MHIDDEN)
499 ++hidden;
502 /* If displayname gets truncated the user effectively has no option to see
503 * the full pathname of the mailbox, so print it at least for '? fi' */
504 printf(_("\"%s\": "),
505 (_update_mailname(NULL) ? displayname : mailname));
506 if (msgCount == 1)
507 printf(_("1 message"));
508 else
509 printf(_("%d messages"), msgCount);
510 if (n > 0)
511 printf(_(" %d new"), n);
512 if (u-n > 0)
513 printf(_(" %d unread"), u);
514 if (d > 0)
515 printf(_(" %d deleted"), d);
516 if (s > 0)
517 printf(_(" %d saved"), s);
518 if (moved > 0)
519 printf(_(" %d moved"), moved);
520 if (hidden > 0)
521 printf(_(" %d hidden"), hidden);
522 else if (mb.mb_perm == 0)
523 printf(_(" [Read only]"));
524 printf("\n");
525 jleave:
526 NYD_LEAVE;
527 return mdot;
530 FL int
531 getmdot(int nmail)
533 struct message *mp;
534 char *cp;
535 int mdot;
536 enum mflag avoid = MHIDDEN | MDELETED;
537 NYD_ENTER;
539 if (!nmail) {
540 if (ok_blook(autothread)) {
541 OBSOLETE(_("please use *autosort=thread* instead of *autothread*"));
542 c_thread(NULL);
543 } else if ((cp = ok_vlook(autosort)) != NULL) {
544 if (mb.mb_sorted != NULL)
545 free(mb.mb_sorted);
546 mb.mb_sorted = sstrdup(cp);
547 c_sort(NULL);
550 if (mb.mb_type == MB_VOID) {
551 mdot = 1;
552 goto jleave;
555 if (nmail)
556 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
557 if ((mp->m_flag & (MNEWEST | avoid)) == MNEWEST)
558 break;
560 if (!nmail || PTRCMP(mp, >=, message + msgCount)) {
561 if (mb.mb_threaded) {
562 for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
563 if ((mp->m_flag & (MNEW | avoid)) == MNEW)
564 break;
565 } else {
566 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
567 if ((mp->m_flag & (MNEW | avoid)) == MNEW)
568 break;
572 if ((mb.mb_threaded ? (mp == NULL) : PTRCMP(mp, >=, message + msgCount))) {
573 if (mb.mb_threaded) {
574 for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
575 if (mp->m_flag & MFLAGGED)
576 break;
577 } else {
578 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
579 if (mp->m_flag & MFLAGGED)
580 break;
584 if ((mb.mb_threaded ? (mp == NULL) : PTRCMP(mp, >=, message + msgCount))) {
585 if (mb.mb_threaded) {
586 for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
587 if (!(mp->m_flag & (MREAD | avoid)))
588 break;
589 } else {
590 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
591 if (!(mp->m_flag & (MREAD | avoid)))
592 break;
596 if (nmail &&
597 (mb.mb_threaded ? (mp != NULL) : PTRCMP(mp, <, message + msgCount)))
598 mdot = (int)PTR2SIZE(mp - message + 1);
599 else if (ok_blook(showlast)) {
600 if (mb.mb_threaded) {
601 for (mp = this_in_thread(threadroot, -1); mp;
602 mp = prev_in_thread(mp))
603 if (!(mp->m_flag & avoid))
604 break;
605 mdot = (mp != NULL) ? (int)PTR2SIZE(mp - message + 1) : msgCount;
606 } else {
607 for (mp = message + msgCount - 1; mp >= message; --mp)
608 if (!(mp->m_flag & avoid))
609 break;
610 mdot = (mp >= message) ? (int)PTR2SIZE(mp - message + 1) : msgCount;
612 } else if (!nmail &&
613 (mb.mb_threaded ? (mp != NULL) : PTRCMP(mp, <, message + msgCount)))
614 mdot = (int)PTR2SIZE(mp - message + 1);
615 else if (mb.mb_threaded) {
616 for (mp = threadroot; mp; mp = next_in_thread(mp))
617 if (!(mp->m_flag & avoid))
618 break;
619 mdot = (mp != NULL) ? (int)PTR2SIZE(mp - message + 1) : 1;
620 } else {
621 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
622 if (!(mp->m_flag & avoid))
623 break;
624 mdot = PTRCMP(mp, <, message + msgCount)
625 ? (int)PTR2SIZE(mp - message + 1) : 1;
627 jleave:
628 NYD_LEAVE;
629 return mdot;
632 FL void
633 initbox(char const *name)
635 char *tempMesg;
636 NYD_ENTER;
638 if (mb.mb_type != MB_VOID)
639 n_strscpy(prevfile, mailname, PATH_MAX);
641 /* TODO name always NE mailname (but goes away for objects anyway)
642 * TODO Well, not true no more except that in parens */
643 _update_mailname((name != mailname) ? name : NULL);
645 if ((mb.mb_otf = Ftmp(&tempMesg, "tmpmbox", OF_WRONLY | OF_HOLDSIGS)) ==
646 NULL) {
647 n_perr(_("temporary mail message file"), 0);
648 exit(EXIT_ERR);
650 if ((mb.mb_itf = safe_fopen(tempMesg, "r", NULL)) == NULL) {
651 n_perr(_("temporary mail message file"), 0);
652 exit(EXIT_ERR);
654 Ftmp_release(&tempMesg);
656 message_reset();
657 mb.mb_threaded = 0;
658 if (mb.mb_sorted != NULL) {
659 free(mb.mb_sorted);
660 mb.mb_sorted = NULL;
662 dot = prevdot = threadroot = NULL;
663 pstate &= ~PS_DID_PRINT_DOT;
664 NYD_LEAVE;
667 FL bool_t
668 getfold(char *name, size_t size)
670 char const *folder;
671 NYD_ENTER;
673 if ((folder = ok_vlook(folder)) != NULL)
674 n_strscpy(name, folder, size);
675 NYD_LEAVE;
676 return (folder != NULL);
679 /* s-it-mode */