cc-test.sh: ARGH! MBOX content statistics not portable! (Gaetan Bisson)
[s-mailx.git] / folder.c
blobea66509269b02459c328459aba565253dbd6723e
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 - 2018 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 folder
38 #ifndef HAVE_AMALGAMATION
39 # include "nail.h"
40 #endif
42 #include <pwd.h>
44 /* Update mailname (if name != NULL) and displayname, return whether 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 n_INLINE size_t __narrow_suffix(char const *cp, size_t cpl, size_t maxl);
49 #endif
51 /**/
52 static void a_folder_info(void);
54 static bool_t
55 _update_mailname(char const *name) /* TODO 2MUCH work, cache, prop of Object! */
57 char const *foldp;
58 char *mailp, *dispp;
59 size_t i, j, foldlen;
60 bool_t rv;
61 NYD_ENTER;
63 /* Don't realpath(3) if it's only an update request */
64 if(name != NULL){
65 #ifdef HAVE_REALPATH
66 char const *adjname;
67 enum protocol p;
69 p = which_protocol(name, TRU1, TRU1, &adjname);
71 if(p == PROTO_FILE || p == PROTO_MAILDIR){
72 name = adjname;
73 if (realpath(name, mailname) == NULL && n_err_no != n_ERR_NOENT) {
74 n_err(_("Can't canonicalize %s\n"), n_shexp_quote_cp(name, FAL0));
75 goto jdocopy;
77 }else
78 jdocopy:
79 #endif
80 n_strscpy(mailname, name, sizeof(mailname));
83 mailp = mailname;
84 dispp = displayname;
86 /* Don't display an absolute path but "+FOLDER" if under *folder* */
87 if(*(foldp = n_folder_query()) != '\0'){
88 foldlen = strlen(foldp);
89 if(strncmp(foldp, mailp, foldlen))
90 foldlen = 0;
91 }else
92 foldlen = 0;
94 /* We want to see the name of the folder .. on the screen */
95 i = strlen(mailp);
96 if(i < sizeof(displayname) - 3 -1){
97 if(foldlen > 0){
98 *dispp++ = '+';
99 *dispp++ = '[';
100 memcpy(dispp, mailp, foldlen);
101 dispp += foldlen;
102 mailp += foldlen;
103 *dispp++ = ']';
104 memcpy(dispp, mailp, i -= foldlen);
105 dispp[i] = '\0';
106 }else
107 memcpy(dispp, mailp, i +1);
108 rv = TRU1;
109 }else{
110 /* Avoid disrupting multibyte sequences (if possible) */
111 #ifndef HAVE_C90AMEND1
112 j = sizeof(displayname) / 3 - 3;
113 i -= sizeof(displayname) - (1/* + */ + 3) - j;
114 #else
115 j = field_detect_clip(sizeof(displayname) / 3, mailp, i);
116 i = j + __narrow_suffix(mailp + j, i - j,
117 sizeof(displayname) - (1/* + */ + 3 + 1) - j);
118 #endif
119 snprintf(dispp, sizeof(displayname), "%s%.*s...%s",
120 (foldlen > 0 ? "[+]" : ""), (int)j, mailp, mailp + i);
121 rv = FAL0;
124 n_PS_ROOT_BLOCK((ok_vset(mailbox_resolved, mailname),
125 ok_vset(mailbox_display, displayname)));
126 NYD_LEAVE;
127 return rv;
130 #ifdef HAVE_C90AMEND1
131 n_INLINE size_t
132 __narrow_suffix(char const *cp, size_t cpl, size_t maxl)
134 int err;
135 size_t i, ok;
136 NYD_ENTER;
138 for (err = ok = i = 0; cpl > maxl || err;) {
139 int ml = mblen(cp, cpl);
140 if (ml < 0) { /* XXX _narrow_suffix(): mblen() error; action? */
141 (void)mblen(NULL, 0);
142 err = 1;
143 ml = 1;
144 } else {
145 if (!err)
146 ok = i;
147 err = 0;
148 if (ml == 0)
149 break;
151 cp += ml;
152 i += ml;
153 cpl -= ml;
155 NYD_LEAVE;
156 return ok;
158 #endif /* HAVE_C90AMEND1 */
160 static void
161 a_folder_info(void){
162 struct message *mp;
163 int u, n, d, s, hidden, moved;
164 NYD2_ENTER;
166 if(mb.mb_type == MB_VOID){
167 fprintf(n_stdout, _("(Currently no active mailbox)"));
168 goto jleave;
171 s = d = hidden = moved = 0;
172 for (mp = message, n = 0, u = 0; PTRCMP(mp, <, message + msgCount); ++mp) {
173 if (mp->m_flag & MNEW)
174 ++n;
175 if ((mp->m_flag & MREAD) == 0)
176 ++u;
177 if ((mp->m_flag & (MDELETED | MSAVED)) == (MDELETED | MSAVED))
178 ++moved;
179 if ((mp->m_flag & (MDELETED | MSAVED)) == MDELETED)
180 ++d;
181 if ((mp->m_flag & (MDELETED | MSAVED)) == MSAVED)
182 ++s;
183 if (mp->m_flag & MHIDDEN)
184 ++hidden;
187 /* If displayname gets truncated the user effectively has no option to see
188 * the full pathname of the mailbox, so print it at least for '? fi' */
189 fprintf(n_stdout, "%s: ", n_shexp_quote_cp(
190 (_update_mailname(NULL) ? displayname : mailname), FAL0));
191 if (msgCount == 1)
192 fprintf(n_stdout, _("1 message"));
193 else
194 fprintf(n_stdout, _("%d messages"), msgCount);
195 if (n > 0)
196 fprintf(n_stdout, _(" %d new"), n);
197 if (u-n > 0)
198 fprintf(n_stdout, _(" %d unread"), u);
199 if (d > 0)
200 fprintf(n_stdout, _(" %d deleted"), d);
201 if (s > 0)
202 fprintf(n_stdout, _(" %d saved"), s);
203 if (moved > 0)
204 fprintf(n_stdout, _(" %d moved"), moved);
205 if (hidden > 0)
206 fprintf(n_stdout, _(" %d hidden"), hidden);
207 if (mb.mb_perm == 0)
208 fprintf(n_stdout, _(" [Read-only]"));
209 #ifdef HAVE_IMAP
210 if (mb.mb_type == MB_CACHE)
211 fprintf(n_stdout, _(" [Disconnected]"));
212 #endif
214 jleave:
215 putc('\n', n_stdout);
216 NYD2_LEAVE;
219 FL int
220 setfile(char const *name, enum fedit_mode fm) /* TODO oh my god */
222 /* TODO This all needs to be converted to URL:: and Mailbox:: */
223 static int shudclob;
225 struct stat stb;
226 size_t offset;
227 char const *who, *orig_name;
228 int rv, omsgCount = 0;
229 FILE *ibuf = NULL, *lckfp = NULL;
230 bool_t isdevnull = FAL0;
231 NYD_ENTER;
233 n_pstate &= ~n_PS_SETFILE_OPENED;
235 /* C99 */{
236 enum fexp_mode fexpm;
238 if((who = shortcut_expand(name)) != NULL){
239 fexpm = FEXP_NSHORTCUT/* XXX | FEXP_NSHELL*/;
240 name = who;
241 }else
242 fexpm = FEXP_NSHELL;
244 if(name[0] == '%'){
245 char const *cp;
247 fm |= FEDIT_SYSBOX; /* TODO fexpand() needs to tell is-valid-user! */
248 if(*(who = &name[1]) == ':')
249 ++who;
250 if((cp = strrchr(who, '/')) != NULL)
251 who = &cp[1];
252 if(*who == '\0')
253 goto jlogname;
254 }else
255 jlogname:
256 who = ok_vlook(LOGNAME);
258 if ((name = fexpand(name, fexpm)) == NULL)
259 goto jem1;
262 /* For at least substdate() users TODO -> eventloop tick */
263 time_current_update(&time_current, FAL0);
265 switch (which_protocol(orig_name = name, TRU1, TRU1, &name)) {
266 case PROTO_FILE:
267 isdevnull = ((n_poption & n_PO_BATCH_FLAG) &&
268 !strcmp(name, n_path_devnull));
269 #ifdef HAVE_REALPATH
270 do { /* TODO we need objects, goes away then */
271 # ifdef HAVE_REALPATH_NULL
272 char *cp;
274 if ((cp = realpath(name, NULL)) != NULL) {
275 name = savestr(cp);
276 (free)(cp);
278 # else
279 char cbuf[PATH_MAX];
281 if (realpath(name, cbuf) != NULL)
282 name = savestr(cbuf);
283 # endif
284 } while (0);
285 #endif
286 rv = 1;
287 break;
288 case PROTO_MAILDIR:
289 shudclob = 1;
290 rv = maildir_setfile(name, fm);
291 goto jleave;
292 #ifdef HAVE_POP3
293 case PROTO_POP3:
294 shudclob = 1;
295 rv = pop3_setfile(orig_name, fm);
296 goto jleave;
297 #endif
298 #ifdef HAVE_IMAP
299 case PROTO_IMAP:
300 shudclob = 1;
301 if((fm & FEDIT_NEWMAIL) && mb.mb_type == MB_CACHE)
302 rv = 1;
303 else
304 rv = imap_setfile(orig_name, fm);
305 goto jleave;
306 #endif
307 default:
308 n_err(_("Cannot handle protocol: %s\n"), orig_name);
309 goto jem1;
312 if ((ibuf = n_fopen_any(savecat("file://", name), "r", NULL)) == NULL) {
313 int e = n_err_no;
315 if ((fm & FEDIT_SYSBOX) && e == n_ERR_NOENT) {
316 if (strcmp(who, ok_vlook(LOGNAME)) && getpwnam(who) == NULL) {
317 n_err(_("%s is not a user of this system\n"),
318 n_shexp_quote_cp(who, FAL0));
319 goto jem2;
321 if (!(n_poption & n_PO_QUICKRUN_MASK) && ok_blook(bsdcompat))
322 n_err(_("No mail for %s\n"), who);
324 if (fm & FEDIT_NEWMAIL)
325 goto jleave;
327 mb.mb_type = MB_VOID;
328 if (ok_blook(emptystart)) {
329 if (!(n_poption & n_PO_QUICKRUN_MASK) && !ok_blook(bsdcompat))
330 n_perr(name, e);
331 /* We must avoid returning -1 and causing program exit */
332 rv = 1;
333 goto jleave;
335 n_perr(name, e);
336 goto jem1;
339 if (fstat(fileno(ibuf), &stb) == -1) {
340 if (fm & FEDIT_NEWMAIL)
341 goto jleave;
342 n_perr(_("fstat"), 0);
343 goto jem1;
346 if (S_ISREG(stb.st_mode) || isdevnull) {
347 /* EMPTY */
348 } else {
349 if (fm & FEDIT_NEWMAIL)
350 goto jleave;
351 n_err_no = S_ISDIR(stb.st_mode) ? n_ERR_ISDIR : n_ERR_INVAL;
352 n_perr(name, 0);
353 goto jem1;
356 if (shudclob && !(fm & FEDIT_NEWMAIL) && !quit(FAL0))
357 goto jem2;
359 hold_sigs();
361 #ifdef HAVE_SOCKETS
362 if (!(fm & FEDIT_NEWMAIL) && mb.mb_sock.s_fd >= 0)
363 sclose(&mb.mb_sock); /* TODO sorry? VMAILFS->close(), thank you */
364 #endif
366 /* TODO There is no intermediate VOID box we've switched to: name may
367 * TODO point to the same box that we just have written, so any updates
368 * TODO we won't see! Reopen again in this case. RACY! Goes with VOID! */
369 /* TODO In addition: in case of compressed/hook boxes we lock a temporary! */
370 /* TODO We may uselessly open twice but quit() doesn't say whether we were
371 * TODO modified so we can't tell: Mailbox::is_modified() :-(( */
372 if (/*shudclob && !(fm & FEDIT_NEWMAIL) &&*/ !strcmp(name, mailname)) {
373 name = mailname;
374 Fclose(ibuf);
376 if ((ibuf = n_fopen_any(name, "r", NULL)) == NULL ||
377 fstat(fileno(ibuf), &stb) == -1 ||
378 (!S_ISREG(stb.st_mode) && !isdevnull)) {
379 n_perr(name, 0);
380 rele_sigs();
381 goto jem2;
385 /* Copy the messages into /tmp and set pointers */
386 if (!(fm & FEDIT_NEWMAIL)) {
387 if (isdevnull) {
388 mb.mb_type = MB_VOID;
389 mb.mb_perm = 0;
390 } else {
391 mb.mb_type = MB_FILE;
392 mb.mb_perm = (((n_poption & n_PO_R_FLAG) || (fm & FEDIT_RDONLY) ||
393 access(name, W_OK) < 0) ? 0 : MB_DELE | MB_EDIT);
394 if (shudclob) {
395 if (mb.mb_itf) {
396 fclose(mb.mb_itf);
397 mb.mb_itf = NULL;
399 if (mb.mb_otf) {
400 fclose(mb.mb_otf);
401 mb.mb_otf = NULL;
405 shudclob = 1;
406 if (fm & FEDIT_SYSBOX)
407 n_pstate &= ~n_PS_EDIT;
408 else
409 n_pstate |= n_PS_EDIT;
410 initbox(name);
411 offset = 0;
412 } else {
413 fseek(mb.mb_otf, 0L, SEEK_END);
414 /* TODO Doing this without holding a lock is.. And not err checking.. */
415 fseek(ibuf, mailsize, SEEK_SET);
416 offset = mailsize;
417 omsgCount = msgCount;
420 if (isdevnull)
421 lckfp = (FILE*)-1;
422 else if (!(n_pstate & n_PS_EDIT))
423 lckfp = n_dotlock(name, fileno(ibuf), FLT_READ, offset,0,
424 (fm & FEDIT_NEWMAIL ? 0 : UIZ_MAX));
425 else if (n_file_lock(fileno(ibuf), FLT_READ, offset,0,
426 (fm & FEDIT_NEWMAIL ? 0 : UIZ_MAX)))
427 lckfp = (FILE*)-1;
429 if (lckfp == NULL) {
430 if (!(fm & FEDIT_NEWMAIL)) {
431 char const *emsg = (n_pstate & n_PS_EDIT)
432 ? N_("Unable to lock mailbox, aborting operation")
433 : N_("Unable to (dot) lock mailbox, aborting operation");
434 n_perr(V_(emsg), 0);
436 rele_sigs();
437 if (!(fm & FEDIT_NEWMAIL))
438 goto jem1;
439 goto jleave;
442 mailsize = fsize(ibuf);
444 /* TODO This is too simple minded? We should regenerate an index file
445 * TODO to be able to truly tell whether *anything* has changed! */
446 if ((fm & FEDIT_NEWMAIL) && UICMP(z, mailsize, <=, offset)) {
447 rele_sigs();
448 goto jleave;
450 setptr(ibuf, offset);
451 setmsize(msgCount);
452 if ((fm & FEDIT_NEWMAIL) && mb.mb_sorted) {
453 mb.mb_threaded = 0;
454 c_sort((void*)-1);
457 Fclose(ibuf);
458 ibuf = NULL;
459 if (lckfp != NULL && lckfp != (FILE*)-1) {
460 Pclose(lckfp, FAL0);
461 /*lckfp = NULL;*/
464 if (!(fm & FEDIT_NEWMAIL)) {
465 n_pstate &= ~n_PS_SAW_COMMAND;
466 n_pstate |= n_PS_SETFILE_OPENED;
469 rele_sigs();
471 rv = (msgCount == 0);
472 if(rv)
473 n_err_no = n_ERR_NODATA;
475 if(n_poption & n_PO_EXISTONLY)
476 goto jleave;
478 if(rv){
479 if(!(n_pstate & n_PS_EDIT) || (fm & FEDIT_NEWMAIL)){
480 if(!(fm & FEDIT_NEWMAIL)){
481 if (!ok_blook(emptystart))
482 n_err(_("No mail for %s\n"), who);
484 goto jleave;
488 if(fm & FEDIT_NEWMAIL)
489 newmailinfo(omsgCount);
490 jleave:
491 if (ibuf != NULL) {
492 Fclose(ibuf);
493 if (lckfp != NULL && lckfp != (FILE*)-1)
494 Pclose(lckfp, FAL0);
496 NYD_LEAVE;
497 return rv;
498 jem2:
499 mb.mb_type = MB_VOID;
500 jem1:
501 n_err_no = n_ERR_NOTOBACCO;
502 rv = -1;
503 goto jleave;
506 FL int
507 newmailinfo(int omsgCount)
509 int mdot, i;
510 NYD_ENTER;
512 for (i = 0; i < omsgCount; ++i)
513 message[i].m_flag &= ~MNEWEST;
515 if (msgCount > omsgCount) {
516 for (i = omsgCount; i < msgCount; ++i)
517 message[i].m_flag |= MNEWEST;
518 fprintf(n_stdout, _("New mail has arrived.\n"));
519 if ((i = msgCount - omsgCount) == 1)
520 fprintf(n_stdout, _("Loaded 1 new message.\n"));
521 else
522 fprintf(n_stdout, _("Loaded %d new messages.\n"), i);
523 } else
524 fprintf(n_stdout, _("Loaded %d messages.\n"), msgCount);
526 temporary_folder_hook_check(TRU1);
528 mdot = getmdot(1);
530 if (ok_blook(header))
531 print_headers(omsgCount + 1, msgCount, FAL0);
532 NYD_LEAVE;
533 return mdot;
536 FL void
537 setmsize(int sz)
539 NYD_ENTER;
540 if (n_msgvec != NULL)
541 free(n_msgvec);
542 n_msgvec = scalloc(sz + 1, sizeof *n_msgvec);
543 NYD_LEAVE;
546 FL void
547 print_header_summary(char const *Larg)
549 size_t bot, top, i, j;
550 NYD_ENTER;
552 if (Larg != NULL) {
553 /* Avoid any messages XXX add a make_mua_silent() and use it? */
554 if ((n_poption & (n_PO_VERB | n_PO_EXISTONLY)) == n_PO_EXISTONLY) {
555 n_stdout = freopen(n_path_devnull, "w", stdout);
556 n_stderr = freopen(n_path_devnull, "w", stderr);
558 assert(n_msgvec != NULL);
559 i = (getmsglist(/*TODO make const */n_UNCONST(Larg), n_msgvec, 0) <= 0);
560 if (n_poption & n_PO_EXISTONLY) {
561 n_exit_status = (int)i;
562 goto jleave;
564 if (i)
565 goto jleave;
566 for (bot = msgCount, top = 0, i = 0; (j = n_msgvec[i]) != 0; ++i) {
567 if (bot > j)
568 bot = j;
569 if (top < j)
570 top = j;
572 } else
573 bot = 1, top = msgCount;
574 print_headers(bot, top, (Larg != NULL)); /* TODO should take iterator!! */
575 jleave:
576 NYD_LEAVE;
579 FL void
580 n_folder_announce(enum n_announce_flags af){
581 int vec[2], mdot;
582 NYD_ENTER;
584 mdot = (mb.mb_type == MB_VOID) ? 1 : getmdot(0);
585 dot = &message[mdot - 1];
587 if(af != n_ANNOUNCE_NONE && ok_blook(header) &&
588 ((af & n_ANNOUNCE_MAIN_CALL) ||
589 ((af & n_ANNOUNCE_CHANGE) && !ok_blook(posix))))
590 af |= n_ANNOUNCE_STATUS | n__ANNOUNCE_HEADER;
592 if(af & n_ANNOUNCE_STATUS){
593 a_folder_info();
594 af |= n__ANNOUNCE_ANY;
597 if(af & n__ANNOUNCE_HEADER){
598 if(!(af & n_ANNOUNCE_MAIN_CALL) && ok_blook(bsdannounce))
599 n_OBSOLETE(_("*bsdannounce* is now default behaviour"));
600 vec[0] = mdot;
601 vec[1] = 0;
602 print_header_group(vec); /* XXX errors? */
603 af |= n__ANNOUNCE_ANY;
606 if(af & n__ANNOUNCE_ANY)
607 fflush(n_stdout);
608 NYD_LEAVE;
611 FL int
612 getmdot(int nmail)
614 struct message *mp;
615 char *cp;
616 int mdot;
617 enum mflag avoid = MHIDDEN | MDELETED;
618 NYD_ENTER;
620 if (!nmail) {
621 if (ok_blook(autothread)) {
622 n_OBSOLETE(_("please use *autosort=thread* instead of *autothread*"));
623 c_thread(NULL);
624 } else if ((cp = ok_vlook(autosort)) != NULL) {
625 if (mb.mb_sorted != NULL)
626 free(mb.mb_sorted);
627 mb.mb_sorted = sstrdup(cp);
628 c_sort(NULL);
631 if (mb.mb_type == MB_VOID) {
632 mdot = 1;
633 goto jleave;
636 if (nmail)
637 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
638 if ((mp->m_flag & (MNEWEST | avoid)) == MNEWEST)
639 break;
641 if (!nmail || PTRCMP(mp, >=, message + msgCount)) {
642 if (mb.mb_threaded) {
643 for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
644 if ((mp->m_flag & (MNEW | avoid)) == MNEW)
645 break;
646 } else {
647 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
648 if ((mp->m_flag & (MNEW | avoid)) == MNEW)
649 break;
653 if ((mb.mb_threaded ? (mp == NULL) : PTRCMP(mp, >=, message + msgCount))) {
654 if (mb.mb_threaded) {
655 for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
656 if (mp->m_flag & MFLAGGED)
657 break;
658 } else {
659 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
660 if (mp->m_flag & MFLAGGED)
661 break;
665 if ((mb.mb_threaded ? (mp == NULL) : PTRCMP(mp, >=, message + msgCount))) {
666 if (mb.mb_threaded) {
667 for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
668 if (!(mp->m_flag & (MREAD | avoid)))
669 break;
670 } else {
671 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
672 if (!(mp->m_flag & (MREAD | avoid)))
673 break;
677 if (nmail &&
678 (mb.mb_threaded ? (mp != NULL) : PTRCMP(mp, <, message + msgCount)))
679 mdot = (int)PTR2SIZE(mp - message + 1);
680 else if (ok_blook(showlast)) {
681 if (mb.mb_threaded) {
682 for (mp = this_in_thread(threadroot, -1); mp;
683 mp = prev_in_thread(mp))
684 if (!(mp->m_flag & avoid))
685 break;
686 mdot = (mp != NULL) ? (int)PTR2SIZE(mp - message + 1) : msgCount;
687 } else {
688 for (mp = message + msgCount - 1; mp >= message; --mp)
689 if (!(mp->m_flag & avoid))
690 break;
691 mdot = (mp >= message) ? (int)PTR2SIZE(mp - message + 1) : msgCount;
693 } else if (!nmail &&
694 (mb.mb_threaded ? (mp != NULL) : PTRCMP(mp, <, message + msgCount)))
695 mdot = (int)PTR2SIZE(mp - message + 1);
696 else if (mb.mb_threaded) {
697 for (mp = threadroot; mp; mp = next_in_thread(mp))
698 if (!(mp->m_flag & avoid))
699 break;
700 mdot = (mp != NULL) ? (int)PTR2SIZE(mp - message + 1) : 1;
701 } else {
702 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
703 if (!(mp->m_flag & avoid))
704 break;
705 mdot = PTRCMP(mp, <, message + msgCount)
706 ? (int)PTR2SIZE(mp - message + 1) : 1;
708 jleave:
709 NYD_LEAVE;
710 return mdot;
713 FL void
714 initbox(char const *name)
716 char *tempMesg;
717 NYD_ENTER;
719 if (mb.mb_type != MB_VOID)
720 n_strscpy(prevfile, mailname, PATH_MAX);
722 /* TODO name always NE mailname (but goes away for objects anyway)
723 * TODO Well, not true no more except that in parens */
724 _update_mailname((name != mailname) ? name : NULL);
726 if ((mb.mb_otf = Ftmp(&tempMesg, "tmpmbox", OF_WRONLY | OF_HOLDSIGS)) ==
727 NULL) {
728 n_perr(_("temporary mail message file"), 0);
729 exit(n_EXIT_ERR);
731 if ((mb.mb_itf = safe_fopen(tempMesg, "r", NULL)) == NULL) {
732 n_perr(_("temporary mail message file"), 0);
733 exit(n_EXIT_ERR);
735 Ftmp_release(&tempMesg);
737 message_reset();
738 mb.mb_active = MB_NONE;
739 mb.mb_threaded = 0;
740 #ifdef HAVE_IMAP
741 mb.mb_flags = MB_NOFLAGS;
742 #endif
743 if (mb.mb_sorted != NULL) {
744 free(mb.mb_sorted);
745 mb.mb_sorted = NULL;
747 dot = prevdot = threadroot = NULL;
748 n_pstate &= ~n_PS_DID_PRINT_DOT;
749 NYD_LEAVE;
752 FL char const *
753 n_folder_query(void){
754 struct n_string s, *sp = &s;
755 enum protocol proto;
756 char *cp;
757 char const *rv, *adjcp;
758 bool_t err;
759 NYD_ENTER;
761 sp = n_string_creat_auto(sp);
763 /* *folder* is linked with *folder_resolved*: we only use the latter */
764 for(err = FAL0;;){
765 if((rv = ok_vlook(folder_resolved)) != NULL)
766 break;
768 /* POSIX says:
769 * If directory does not start with a <slash> ('/'), the contents
770 * of HOME shall be prefixed to it.
771 * And:
772 * If folder is unset or set to null, [.] filenames beginning with
773 * '+' shall refer to files in the current directory.
774 * We may have the result already */
775 rv = n_empty;
776 err = FAL0;
778 if((cp = ok_vlook(folder)) == NULL)
779 goto jset;
781 /* Expand the *folder*; skip %: prefix for simplicity of use */
782 if(cp[0] == '%' && cp[1] == ':')
783 cp += 2;
784 if((err = (cp = fexpand(cp, FEXP_NSPECIAL | FEXP_NFOLDER | FEXP_NSHELL)
785 ) == NULL) || *cp == '\0')
786 goto jset;
787 else{
788 size_t i;
790 for(i = strlen(cp);;){
791 if(--i == 0)
792 goto jset;
793 if(cp[i] != '/'){
794 cp[++i] = '\0';
795 break;
800 switch((proto = which_protocol(cp, FAL0, FAL0, &adjcp))){
801 case PROTO_POP3:
802 n_err(_("*folder* can't be set to a flat, read-only POP3 account\n"));
803 err = TRU1;
804 goto jset;
805 case PROTO_IMAP:
806 #ifdef HAVE_IMAP
807 rv = cp;
808 if(!strcmp(rv, protbase(rv)))
809 rv = savecatsep(rv, '/', n_empty);
810 #else
811 n_err(_("*folder*: IMAP support not compiled in\n"));
812 err = TRU1;
813 #endif
814 goto jset;
815 default:
816 /* Further expansion desired */
817 break;
820 /* Prefix HOME as necessary */
821 if(*adjcp != '/'){ /* XXX path_is_absolute() */
822 size_t l1, l2;
823 char const *home;
825 home = ok_vlook(HOME);
826 l1 = strlen(home);
827 l2 = strlen(cp);
829 sp = n_string_reserve(sp, l1 + 1 + l2 +1);
830 if(cp != adjcp){
831 size_t i;
833 sp = n_string_push_buf(sp, cp, i = PTR2SIZE(adjcp - cp));
834 cp += i;
835 l2 -= i;
837 sp = n_string_push_buf(sp, home, l1);
838 sp = n_string_push_c(sp, '/');
839 sp = n_string_push_buf(sp, cp, l2);
840 cp = n_string_cp(sp);
841 sp = n_string_drop_ownership(sp);
844 /* TODO Since our visual mailname is resolved via realpath(3) if available
845 * TODO to avoid that we loose track of our currently open folder in case
846 * TODO we chdir away, but still checks the leading path portion against
847 * TODO n_folder_query() to be able to abbreviate to the +FOLDER syntax if
848 * TODO possible, we need to realpath(3) the folder, too */
849 #ifndef HAVE_REALPATH
850 rv = cp;
851 #else
852 assert(sp->s_len == 0 && sp->s_dat == NULL);
853 # ifndef HAVE_REALPATH_NULL
854 sp = n_string_reserve(sp, PATH_MAX +1);
855 # endif
857 if((sp->s_dat = realpath(cp, sp->s_dat)) != NULL){
858 # ifdef HAVE_REALPATH_NULL
859 n_string_cp(sp = n_string_assign_cp(sp, cp = sp->s_dat));
860 (free)(cp);
861 # endif
862 rv = sp->s_dat;
863 }else if(n_err_no == n_ERR_NOENT)
864 rv = cp;
865 else{
866 n_err(_("Can't canonicalize *folder*: %s\n"),
867 n_shexp_quote_cp(cp, FAL0));
868 err = TRU1;
869 rv = n_empty;
871 sp = n_string_drop_ownership(sp);
872 #endif /* HAVE_REALPATH */
874 /* Always append a solidus to our result path upon success */
875 if(!err){
876 size_t i;
878 if(rv[(i = strlen(rv)) - 1] != '/'){
879 sp = n_string_reserve(sp, i + 1 +1);
880 sp = n_string_push_buf(sp, rv, i);
881 sp = n_string_push_c(sp, '/');
882 rv = n_string_cp(sp);
883 sp = n_string_drop_ownership(sp);
887 jset:
888 n_PS_ROOT_BLOCK(ok_vset(folder_resolved, rv));
891 if(err){
892 n_err(_("*folder* is not resolvable, using CWD\n"));
893 assert(rv != NULL && *rv == '\0');
895 NYD_LEAVE;
896 return rv;
899 FL int
900 n_folder_mbox_prepare_append(FILE *fout, struct stat *st_or_null){
901 /* TODO n_folder_mbox_prepare_append -> Mailbox->append() */
902 struct stat stb;
903 char buf[2];
904 bool_t needsep;
905 int rv;
906 NYD2_ENTER;
908 if(fseek(fout, -2L, SEEK_END)){
909 rv = n_err_no;
911 if(st_or_null == NULL){
912 st_or_null = &stb;
913 if(fstat(fileno(fout), st_or_null))
914 goto jerrno;
917 if(st_or_null->st_size >= 2)
918 goto jleave;
919 if(st_or_null->st_size == 0){
920 rv = n_ERR_NONE;
921 goto jleave;
923 if(fseek(fout, -1L, SEEK_END))
924 goto jerrno;
927 rv = n_ERR_NONE;
929 needsep = FAL0;
930 switch(fread(buf, sizeof *buf, 2, fout)){
931 case 2:
932 if(buf[1] != '\n')
933 needsep = TRU1;
934 break;
935 case 1:
936 if(buf[0] != '\n')
937 needsep = TRU1;
938 break;
939 default:
940 if(ferror(fout))
941 goto jerrno;
942 break;
945 if(fflush(fout) || (needsep && putc('\n', fout) == EOF))
946 jerrno:
947 rv = n_err_no;
948 jleave:
949 NYD2_LEAVE;
950 return rv;
953 /* s-it-mode */