THANKS: Coverity.com (overdue)
[s-mailx.git] / src / mx / maildir.c
blobbf4f956d80c55d06aeba88ec633de83238623c8a
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Maildir folder support. FIXME rewrite - why do we chdir(2)??
3 *@ FIXME Simply truncating paths isn't really it.
5 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
6 * Copyright (c) 2012 - 2020 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
7 * SPDX-License-Identifier: BSD-4-Clause
8 */
9 /*
10 * Copyright (c) 2004
11 * Gunnar Ritter. All rights reserved.
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
15 * are met:
16 * 1. Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
18 * 2. Redistributions in binary form must reproduce the above copyright
19 * notice, this list of conditions and the following disclaimer in the
20 * documentation and/or other materials provided with the distribution.
21 * 3. All advertising materials mentioning features or use of this software
22 * must display the following acknowledgement:
23 * This product includes software developed by Gunnar Ritter
24 * and his contributors.
25 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
26 * may be used to endorse or promote products derived from this software
27 * without specific prior written permission.
29 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
30 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
31 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
32 * ARE DISCLAIMED. IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
33 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
34 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
35 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
37 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
38 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
39 * SUCH DAMAGE.
41 #undef su_FILE
42 #define su_FILE maildir
43 #define mx_SOURCE
45 #ifndef mx_HAVE_AMALGAMATION
46 # include "mx/nail.h"
47 #endif
49 su_EMPTY_FILE()
50 #ifdef mx_HAVE_MAILDIR
52 #include <dirent.h>
54 #include <su/cs.h>
55 #include <su/icodec.h>
56 #include <su/mem.h>
57 #include <su/prime.h>
59 #include "mx/file-streams.h"
60 #include "mx/sigs.h"
62 /* TODO fake */
63 #include "su/code-in.h"
65 /* a_maildir_tbl should be a hash-indexed array of trees! */
66 static struct message **a_maildir_tbl, **a_maildir_tbl_top;
67 static u32 a_maildir_tbl_prime, a_maildir_tbl_maxdist;
68 static sigjmp_buf _maildir_jmp;
70 static void __maildircatch(int s);
71 static void __maildircatch_hold(int s);
73 /* Do some cleanup in the tmp/ subdir */
74 static void _cleantmp(void);
76 static int a_maildir_setfile1(char const *name, enum fedit_mode fm,
77 int omsgCount);
79 static int a_maildir_cmp(void const *a, void const *b);
81 static int _maildir_subdir(char const *name, char const *sub,
82 enum fedit_mode fm);
84 static void _maildir_append(char const *name, char const *sub,
85 char const *fn);
87 static boole a_maildir_readin(char const *name, struct message *mp);
89 static void maildir_update(void);
91 static void _maildir_move(struct n_timespec const *tsp,
92 struct message *m);
94 static char * mkname(struct n_timespec const *tsp, enum mflag f,
95 char const *pref);
97 static enum okay maildir_append1(struct n_timespec const *tsp,
98 char const *name, FILE *fp, off_t off1,
99 long size, enum mflag flag);
101 static enum okay trycreate(char const *name);
103 static enum okay mkmaildir(char const *name);
105 static struct message * mdlook(char const *name, struct message *data);
107 static void mktable(void);
109 static enum okay subdir_remove(char const *name, char const *sub);
111 static void
112 __maildircatch(int s)
114 NYD; /* Signal handler */
115 siglongjmp(_maildir_jmp, s);
118 static void
119 __maildircatch_hold(int s)
121 NYD; /* Signal handler */
122 UNUSED(s);
123 /* TODO no STDIO in signal handler, no _() tr's -- pre-translate interrupt
124 * TODO globally; */
125 n_err_sighdl(_("\nImportant operation in progress: "
126 "interrupt again to forcefully abort\n"));
127 safe_signal(SIGINT, &__maildircatch);
130 static void
131 _cleantmp(void)
133 struct stat st;
134 struct n_string s_b, *s;
135 s64 now;
136 DIR *dirp;
137 struct dirent *dp;
138 NYD_IN;
140 if ((dirp = opendir("tmp")) == NULL)
141 goto jleave;
143 now = n_time_now(FAL0)->ts_sec - 36*3600;
144 s = n_string_creat_auto(&s_b);
146 while ((dp = readdir(dirp)) != NULL) {
147 if (dp->d_name[0] == '.')
148 continue;
150 s = n_string_trunc(s, 0);
151 s = n_string_push_buf(s, "tmp/", sizeof("tmp/") -1);
152 s = n_string_push_cp(s, dp->d_name);
153 if (stat(n_string_cp(s), &st) == -1)
154 continue;
155 if (st.st_atime <= now)
156 unlink(s->s_dat);
158 closedir(dirp);
159 jleave:
160 NYD_OU;
163 static int
164 a_maildir_setfile1(char const *name, enum fedit_mode fm, int omsgCount)
166 int i;
167 NYD_IN;
169 if (!(fm & FEDIT_NEWMAIL))
170 _cleantmp();
172 mb.mb_perm = ((n_poption & n_PO_R_FLAG) || (fm & FEDIT_RDONLY))
173 ? 0 : MB_DELE;
174 if ((i = _maildir_subdir(name, "cur", fm)) != 0)
175 goto jleave;
176 if ((i = _maildir_subdir(name, "new", fm)) != 0)
177 goto jleave;
178 _maildir_append(name, NULL, NULL);
180 n_autorec_relax_create();
181 for(i = ((fm & FEDIT_NEWMAIL) ? omsgCount : 0); i < msgCount; ++i){
182 if(!a_maildir_readin(name, &message[i])){
183 i = -1;
184 break;
186 n_autorec_relax_unroll();
188 n_autorec_relax_gut();
189 if(i < 0)
190 goto jleave;
192 if (fm & FEDIT_NEWMAIL) {
193 if (msgCount > omsgCount)
194 qsort(&message[omsgCount], msgCount - omsgCount, sizeof *message,
195 &a_maildir_cmp);
196 } else if (msgCount)
197 qsort(message, msgCount, sizeof *message, &a_maildir_cmp);
198 i = msgCount;
199 jleave:
200 NYD_OU;
201 return i;
204 static int
205 a_maildir_cmp(void const *xa, void const *xb){
206 char const *cpa, *cpa_pid, *cpb, *cpb_pid;
207 union {struct message const *mp; char const *cp;} a, b;
208 s64 at, bt;
209 int rv;
210 NYD2_IN;
212 a.mp = xa;
213 b.mp = xb;
215 /* We could have parsed the time somewhen in the past, do a quick shot */
216 at = (s64)a.mp->m_time;
217 bt = (s64)b.mp->m_time;
218 if(at != 0 && bt != 0 && (at -= bt) != 0)
219 goto jret;
221 /* Otherwise we need to parse the name */
222 a.cp = &a.mp->m_maildir_file[4];
223 b.cp = &b.mp->m_maildir_file[4];
225 /* Interpret time stored in name, and use it for comparison */
226 if(((su_idec_s64_cp(&at, a.cp, 10, &cpa)
227 ) & su_IDEC_STATE_EMASK) != su_IDEC_STATE_EBASE || *cpa != '.' ||
228 a.cp == cpa)
229 goto jm1; /* Fishy */
230 if(((su_idec_s64_cp(&bt, b.cp, 10, &cpb)
231 ) & su_IDEC_STATE_EMASK) != su_IDEC_STATE_EBASE || *cpb != '.' ||
232 b.cp == cpb)
233 goto j1; /* Fishy */
235 if((at -= bt) != 0)
236 goto jret;
238 /* If the seconds part does not work, go deeper.
239 * We use de-facto standard "maildir — E-mail directory" from the Courier
240 * mail server, also used by, e.g., Dovecot: sec.MusecPpid.hostname:2,flags.
241 * However, a different name convention exists which uses
242 * sec.pid_counter.hostname:2,flags.
243 * First go for usec/counter, then pid */
245 /* A: exact "standard"? */
246 cpa_pid = NULL;
247 a.cp = ++cpa;
248 if((rv = *a.cp) == 'M')
250 /* Known compat? */
251 else if(su_cs_is_digit(rv)){
252 cpa_pid = a.cp++;
253 while((rv = *a.cp) != '\0' && rv != '_')
254 ++a.cp;
255 if(rv == '\0')
256 goto jm1; /* Fishy */
258 /* This is compatible to what dovecot does, it surely does not do so
259 * for nothing, but i have no idea, but am too stupid to ask */
260 else for(;; rv = *++a.cp){
261 if(rv == 'M')
262 break;
263 if(rv == '\0' || rv == '.' || rv == n_MAILDIR_SEPARATOR)
264 goto jm1; /* Fishy */
266 ++a.cp;
267 if(((su_idec_s64_cp(&at, a.cp, 10, &cpa)
268 ) & su_IDEC_STATE_EMASK) != su_IDEC_STATE_EBASE)
269 goto jm1; /* Fishy */
271 /* B: as above */
272 cpb_pid = NULL;
273 b.cp = ++cpb;
274 if((rv = *b.cp) == 'M')
276 else if(su_cs_is_digit(rv)){
277 cpb_pid = b.cp++;
278 while((rv = *b.cp) != '\0' && rv != '_')
279 ++b.cp;
280 if(rv == '\0')
281 goto j1;
282 }else for(;; rv = *++b.cp){
283 if(rv == 'M')
284 break;
285 if(rv == '\0' || rv == '.' || rv == n_MAILDIR_SEPARATOR)
286 goto jm1;
288 ++b.cp;
289 if(((su_idec_s64_cp(&bt, b.cp, 10, &cpb)
290 ) & su_IDEC_STATE_EMASK) != su_IDEC_STATE_EBASE)
291 goto j1;
293 if((at -= bt) != 0)
294 goto jret;
296 /* So this gets hairy: sort by PID, then hostname */
297 if(cpa_pid != NULL){
298 a.cp = cpa_pid;
299 xa = cpa;
300 }else{
301 a.cp = cpa;
302 if(*a.cp++ != 'P')
303 goto jm1; /* Fishy */
305 if(((su_idec_s64_cp(&at, a.cp, 10, &cpa)
306 ) & su_IDEC_STATE_EMASK) != su_IDEC_STATE_EBASE)
307 goto jm1; /* Fishy */
309 if(cpb_pid != NULL){
310 b.cp = cpb_pid;
311 xb = cpb;
312 }else{
313 b.cp = cpb;
314 if(*b.cp++ != 'P')
315 goto j1; /* Fishy */
317 if(((su_idec_s64_cp(&bt, b.cp, 10, &cpb)
318 ) & su_IDEC_STATE_EMASK) != su_IDEC_STATE_EBASE)
319 goto jm1; /* Fishy */
321 if((at -= bt) != 0)
322 goto jret;
324 /* Hostname */
325 a.cp = (cpa_pid != NULL) ? xa : cpa;
326 b.cp = (cpb_pid != NULL) ? xb : cpb;
327 for(;; ++a.cp, ++b.cp){
328 char ac, bc;
330 ac = *a.cp;
331 at = (ac != '\0' && ac != n_MAILDIR_SEPARATOR);
332 bc = *b.cp;
333 bt = (bc != '\0' && bc != n_MAILDIR_SEPARATOR);
334 if((at -= bt) != 0)
335 break;
336 at = ac;
337 if((at -= bc) != 0)
338 break;
339 if(ac == '\0')
340 break;
343 jret:
344 rv = (at == 0 ? 0 : (at < 0 ? -1 : 1));
345 jleave:
346 NYD2_OU;
347 return rv;
348 jm1:
349 rv = -1;
350 goto jleave;
352 rv = 1;
353 goto jleave;
356 static int
357 _maildir_subdir(char const *name, char const *sub, enum fedit_mode fm)
359 DIR *dirp;
360 struct dirent *dp;
361 int rv;
362 NYD_IN;
364 if ((dirp = opendir(sub)) == NULL) {
365 n_err(_("Cannot open directory %s\n"),
366 n_shexp_quote_cp(savecatsep(name, '/', sub), FAL0));
367 rv = -1;
368 goto jleave;
370 if (access(sub, W_OK) == -1)
371 mb.mb_perm = 0;
372 while ((dp = readdir(dirp)) != NULL) {
373 if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' ||
374 (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
375 continue;
376 if (dp->d_name[0] == '.')
377 continue;
378 if (!(fm & FEDIT_NEWMAIL) || mdlook(dp->d_name, NULL) == NULL)
379 _maildir_append(name, sub, dp->d_name);
381 closedir(dirp);
382 rv = 0;
383 jleave:
384 NYD_OU;
385 return rv;
388 static void
389 _maildir_append(char const *name, char const *sub, char const *fn)
391 struct message *m;
392 time_t t = 0;
393 enum mflag f = MUSED | MNOFROM | MNEWEST;
394 char const *cp, *xp;
395 NYD_IN;
396 UNUSED(name);
398 if (fn != NULL && sub != NULL) {
399 if (!su_cs_cmp(sub, "new"))
400 f |= MNEW;
402 /* C99 */{
403 s64 tib;
405 (void)/*TODO*/su_idec_s64_cp(&tib, fn, 10, &xp);
406 t = (time_t)tib;
409 if ((cp = su_cs_rfind_c(xp, ',')) != NULL && PCMP(cp, >, xp + 2) &&
410 cp[-1] == '2' && cp[-2] == n_MAILDIR_SEPARATOR) {
411 while (*++cp != '\0') {
412 switch (*cp) {
413 case 'F':
414 f |= MFLAGGED;
415 break;
416 case 'R':
417 f |= MANSWERED;
418 break;
419 case 'S':
420 f |= MREAD;
421 break;
422 case 'T':
423 f |= MDELETED;
424 break;
425 case 'D':
426 f |= MDRAFT;
427 break;
433 /* Ensure room (and a NULLified last entry) */
434 ++msgCount;
435 message_append(NULL);
436 --msgCount;
438 if (fn == NULL || sub == NULL)
439 goto jleave;
441 m = &message[msgCount++];
442 /* C99 */{
443 char *tmp;
444 uz i, j;
446 i = su_cs_len(fn) +1;
447 j = su_cs_len(sub);
448 m->m_maildir_file = tmp = n_alloc(j + 1 + i);
449 su_mem_copy(tmp, sub, j);
450 tmp[j++] = '/';
451 su_mem_copy(&tmp[j], fn, i);
453 m->m_time = t;
454 m->m_flag = f;
455 m->m_maildir_hash = su_cs_hash(fn);
456 jleave:
457 NYD_OU;
458 return;
461 static boole
462 a_maildir_readin(char const *name, struct message *mp){
463 long size, lines;
464 off_t offset;
465 char const *emsg;
466 FILE *fp;
467 uz bufsize, cnt, buflen;
468 char *buf;
469 boole rv, b;
470 NYD_IN;
472 rv = FAL0;
473 mx_fs_linepool_aquire(&buf, &bufsize);
475 if((fp = mx_fs_open(mp->m_maildir_file, "r")) == NIL){
476 emsg = _("Cannot read %s for message %lu\n");
477 goto jerr;
479 emsg = _("I/O error reading %s for message %lu\n");
481 offset = ftell(mb.mb_otf);
482 cnt = fsize(fp);
484 b = FAL0;
485 size = lines = 0;
486 while(fgetline(&buf, &bufsize, &cnt, &buflen, fp, TRU1) != NIL){
487 /* Since we simply copy over data without doing any transfer
488 * encoding reclassification/adjustment we *have* to perform
489 * RFC 4155 compliant From_ quoting here */
490 if(b && is_head(buf, buflen, FAL0)){
491 if(putc('>', mb.mb_otf) == EOF)
492 goto jerr;
493 ++size;
495 if(fwrite(buf, 1, buflen, mb.mb_otf) != buflen)
496 goto jerr;
497 size += buflen;
498 b = (*buf == '\n');
499 ++lines;
501 if(ferror(fp))
502 goto jerr;
504 if(!b){
505 /* TODO we need \n\n for mbox format.
506 * TODO That is to say we do it wrong here in order to get it right
507 * TODO when send.c stuff or with MBOX handling, even though THIS
508 * TODO line is solely a property of the MBOX database format! */
509 if(putc('\n', mb.mb_otf) == EOF)
510 goto jerr;
511 ++lines;
512 ++size;
515 if(fflush(mb.mb_otf) == EOF)
516 goto jerr;
518 mp->m_size = mp->m_xsize = size;
519 mp->m_lines = mp->m_xlines = lines;
520 mp->m_block = mailx_blockof(offset);
521 mp->m_offset = mailx_offsetof(offset);
522 substdate(mp);
524 rv = TRU1;
525 jleave:
526 if(fp != NIL)
527 mx_fs_close(fp);
529 mx_fs_linepool_release(buf, bufsize);
531 NYD_OU;
532 return rv;
534 jerr:
535 n_err(emsg,
536 n_shexp_quote_cp(savecatsep(name, '/', mp->m_maildir_file), FAL0),
537 S(ul,P2UZ(mp - message + 1)));
538 goto jleave;
541 static void
542 maildir_update(void)
544 struct message *m;
545 struct n_timespec const *tsp;
546 int dodel, c, gotcha = 0, held = 0, modflags = 0;
547 NYD_IN;
549 if (mb.mb_perm == 0)
550 goto jfree;
552 if (!(n_pstate & n_PS_EDIT)) {
553 holdbits();
554 for (m = message, c = 0; PCMP(m, <, message + msgCount); ++m) {
555 if (m->m_flag & MBOX)
556 c++;
558 if (c > 0)
559 if (makembox() == STOP)
560 goto jbypass;
563 tsp = n_time_now(TRU1); /* TODO FAL0, eventloop update! */
565 n_autorec_relax_create();
566 for (m = message, gotcha = 0, held = 0; PCMP(m, <, message + msgCount);
567 ++m) {
568 if (n_pstate & n_PS_EDIT)
569 dodel = m->m_flag & MDELETED;
570 else
571 dodel = !((m->m_flag & MPRESERVE) || !(m->m_flag & MTOUCH));
572 if (dodel) {
573 if (unlink(m->m_maildir_file) < 0)
574 n_err(_("Cannot delete file %s for message %lu\n"),
575 n_shexp_quote_cp(savecatsep(mailname, '/', m->m_maildir_file),
576 FAL0), (ul)P2UZ(m - message + 1));
577 else
578 ++gotcha;
579 } else {
580 if ((m->m_flag & (MREAD | MSTATUS)) == (MREAD | MSTATUS) ||
581 (m->m_flag & (MNEW | MBOXED | MSAVED | MSTATUS | MFLAG |
582 MUNFLAG | MANSWER | MUNANSWER | MDRAFT | MUNDRAFT))) {
583 _maildir_move(tsp, m);
584 n_autorec_relax_unroll();
585 ++modflags;
587 ++held;
590 n_autorec_relax_gut();
592 jbypass:
593 if ((gotcha || modflags) && (n_pstate & n_PS_EDIT)) {
594 fprintf(n_stdout, "%s %s\n",
595 n_shexp_quote_cp(displayname, FAL0),
596 ((ok_blook(bsdcompat) || ok_blook(bsdmsgs))
597 ? _("complete") : _("updated.")));
598 } else if (held && !(n_pstate & n_PS_EDIT) && mb.mb_perm != 0) {
599 if (held == 1)
600 fprintf(n_stdout, _("Held 1 message in %s\n"), displayname);
601 else
602 fprintf(n_stdout, _("Held %d messages in %s\n"), held, displayname);
604 fflush(n_stdout);
605 jfree:
606 for (m = message; PCMP(m, <, message + msgCount); ++m)
607 n_free(n_UNCONST(m->m_maildir_file));
608 NYD_OU;
611 static void
612 _maildir_move(struct n_timespec const *tsp, struct message *m)
614 char *fn, *newfn;
615 NYD_IN;
617 fn = mkname(tsp, m->m_flag, m->m_maildir_file + 4);
618 newfn = savecat("cur/", fn);
619 if (!su_cs_cmp(m->m_maildir_file, newfn))
620 goto jleave;
621 if (link(m->m_maildir_file, newfn) == -1) {
622 n_err(_("Cannot link %s to %s: message %lu not touched\n"),
623 n_shexp_quote_cp(savecatsep(mailname, '/', m->m_maildir_file), FAL0),
624 n_shexp_quote_cp(savecatsep(mailname, '/', newfn), FAL0),
625 (ul)P2UZ(m - message + 1));
626 goto jleave;
628 if (unlink(m->m_maildir_file) == -1)
629 n_err(_("Cannot unlink %s\n"),
630 n_shexp_quote_cp(savecatsep(mailname, '/', m->m_maildir_file), FAL0));
631 jleave:
632 NYD_OU;
635 static char *
636 mkname(struct n_timespec const *tsp, enum mflag f, char const *pref)
638 static char *node;
639 static struct n_timespec ts;
641 char *cp;
642 int size, n, i;
643 NYD_IN;
645 if (pref == NULL) {
646 s64 s;
648 if(n_pid == 0)
649 n_pid = getpid();
651 if (node == NULL) {
652 cp = n_nodename(FAL0);
653 n = size = 0;
654 do {
655 if (UCMP(32, n, <, size + 8))
656 node = n_realloc(node, size += 20);
657 switch (*cp) {
658 case '/':
659 node[n++] = '\\', node[n++] = '0',
660 node[n++] = '5', node[n++] = '7';
661 break;
662 case ':':
663 node[n++] = '\\', node[n++] = '0',
664 node[n++] = '7', node[n++] = '2';
665 break;
666 default:
667 node[n++] = *cp;
669 } while (*cp++ != '\0');
672 /* Problem: Courier spec uses microseconds, not nanoseconds */
673 if((s = tsp->ts_sec) > ts.ts_sec){
674 ts.ts_sec = s;
675 ts.ts_nsec = tsp->ts_nsec / (n_DATE_NANOSSEC / n_DATE_MICROSSEC);
676 }else{
677 s = tsp->ts_nsec / (n_DATE_NANOSSEC / n_DATE_MICROSSEC);
678 if(s <= ts.ts_nsec)
679 s = ts.ts_nsec + 1;
680 if(s < n_DATE_MICROSSEC)
681 ts.ts_nsec = s;
682 else{
683 ++ts.ts_sec;
684 ts.ts_nsec = 0;
688 /* Create a name according to Courier spec */
689 size = 60 + su_cs_len(node);
690 cp = n_autorec_alloc(size);
691 n = snprintf(cp, size, "%" PRId64 ".M%" PRIdZ "P%ld.%s:2,",
692 ts.ts_sec, ts.ts_nsec, (long)n_pid, node);
693 } else {
694 size = (n = su_cs_len(pref)) + 13;
695 cp = n_autorec_alloc(size);
696 su_mem_copy(cp, pref, n +1);
697 for (i = n; i > 3; --i)
698 if (cp[i - 1] == ',' && cp[i - 2] == '2' &&
699 cp[i - 3] == n_MAILDIR_SEPARATOR) {
700 n = i;
701 break;
703 if (i <= 3) {
704 su_mem_copy(cp + n, ":2,", 3 +1);
705 n += 3;
708 if (n < size - 7) {
709 if (f & MDRAFTED)
710 cp[n++] = 'D';
711 if (f & MFLAGGED)
712 cp[n++] = 'F';
713 if (f & MANSWERED)
714 cp[n++] = 'R';
715 if (f & MREAD)
716 cp[n++] = 'S';
717 if (f & MDELETED)
718 cp[n++] = 'T';
719 cp[n] = '\0';
721 NYD_OU;
722 return cp;
725 static enum okay
726 maildir_append1(struct n_timespec const *tsp, char const *name, FILE *fp,
727 off_t off1, long size, enum mflag flag)
729 char buf[4096], *fn, *tfn, *nfn;
730 struct stat st;
731 FILE *op;
732 uz nlen, flen, n;
733 enum okay rv = STOP;
734 NYD_IN;
736 nlen = su_cs_len(name);
738 /* Create a unique temporary file */
739 for (nfn = (char*)0xA /* XXX no magic */;; n_msleep(500, FAL0)) {
740 flen = su_cs_len(fn = mkname(tsp, flag, NULL));
741 tfn = n_autorec_alloc(n = nlen + flen + 6);
742 snprintf(tfn, n, "%s/tmp/%s", name, fn);
744 /* Use "wx" for O_EXCL XXX stat(2) rather redundant; coverity:TOCTOU */
745 if((!stat(tfn, &st) || su_err_no() == su_ERR_NOENT) &&
746 (op = mx_fs_open(tfn, "wx")) != NIL)
747 break;
749 nfn = (char*)(P2UZ(nfn) - 1);
750 if (nfn == NULL) {
751 n_err(_("Can't create an unique file name in %s\n"),
752 n_shexp_quote_cp(savecat(name, "/tmp"), FAL0));
753 goto jleave;
757 if (fseek(fp, off1, SEEK_SET) == -1)
758 goto jtmperr;
759 while (size > 0) {
760 uz z = UCMP(z, size, >, sizeof buf) ? sizeof buf : S(uz,size);
762 if (z != (n = fread(buf, 1, z, fp)) || n != fwrite(buf, 1, n, op)) {
763 jtmperr:
764 n_err(_("Error writing to %s\n"), n_shexp_quote_cp(tfn, FAL0));
765 mx_fs_close(op);
766 goto jerr;
768 size -= n;
770 mx_fs_close(op);
772 nfn = n_autorec_alloc(n = nlen + flen + 6);
773 snprintf(nfn, n, "%s/new/%s", name, fn);
774 if (link(tfn, nfn) == -1) {
775 n_err(_("Cannot link %s to %s\n"), n_shexp_quote_cp(tfn, FAL0),
776 n_shexp_quote_cp(nfn, FAL0));
777 goto jerr;
779 rv = OKAY;
780 jerr:
781 if (unlink(tfn) == -1)
782 n_err(_("Cannot unlink %s\n"), n_shexp_quote_cp(tfn, FAL0));
783 jleave:
784 NYD_OU;
785 return rv;
788 static enum okay
789 trycreate(char const *name)
791 struct stat st;
792 enum okay rv = STOP;
793 NYD_IN;
795 if (!stat(name, &st)) {
796 if (!S_ISDIR(st.st_mode)) {
797 n_err(_("%s is not a directory\n"), n_shexp_quote_cp(name, FAL0));
798 goto jleave;
800 } else if (!n_path_mkdir(name)) {
801 n_err(_("Cannot create directory %s\n"), n_shexp_quote_cp(name, FAL0));
802 goto jleave;
804 rv = OKAY;
805 jleave:
806 NYD_OU;
807 return rv;
810 static enum okay
811 mkmaildir(char const *name) /* TODO proper cleanup on error; use path[] loop */
813 char *np;
814 uz i;
815 enum okay rv = STOP;
816 NYD_IN;
818 if (trycreate(name) == OKAY) {
819 np = n_lofi_alloc((i = su_cs_len(name)) + 4 +1);
820 su_mem_copy(np, name, i);
821 su_mem_copy(&np[i], "/tmp", 4 +1);
822 if (trycreate(np) == OKAY) {
823 su_mem_copy(&np[i], "/new", 4);
824 if (trycreate(np) == OKAY) {
825 su_mem_copy(&np[i], "/cur", 4);
826 rv = trycreate(np);
829 n_lofi_free(np);
831 NYD_OU;
832 return rv;
835 static struct message *
836 mdlook(char const *name, struct message *data)
838 struct message **mpp, *mp;
839 u32 h, i;
840 NYD_IN;
842 if(data != NULL)
843 i = data->m_maildir_hash;
844 else
845 i = su_cs_hash(name);
846 h = i;
847 mpp = &a_maildir_tbl[i %= a_maildir_tbl_prime];
849 for(i = 0;;){
850 if((mp = *mpp) == NULL){
851 if(UNLIKELY(data != NULL)){
852 *mpp = mp = data;
853 if(i > a_maildir_tbl_maxdist)
854 a_maildir_tbl_maxdist = i;
856 break;
857 }else if(mp->m_maildir_hash == h &&
858 !su_cs_cmp(&mp->m_maildir_file[4], name))
859 break;
861 if(UNLIKELY(mpp++ == a_maildir_tbl_top))
862 mpp = a_maildir_tbl;
863 if(++i > a_maildir_tbl_maxdist && UNLIKELY(data == NULL)){
864 mp = NULL;
865 break;
868 NYD_OU;
869 return mp;
872 static void
873 mktable(void)
875 struct message *mp;
876 uz i;
877 NYD_IN;
879 i = a_maildir_tbl_prime = msgCount;
880 i <<= 1;
882 a_maildir_tbl_prime = su_prime_lookup_next(a_maildir_tbl_prime);
883 while(a_maildir_tbl_prime < i);
884 a_maildir_tbl = n_calloc(a_maildir_tbl_prime, sizeof *a_maildir_tbl);
885 a_maildir_tbl_top = &a_maildir_tbl[a_maildir_tbl_prime - 1];
886 a_maildir_tbl_maxdist = 0;
887 for(mp = message, i = msgCount; i-- != 0; ++mp)
888 mdlook(&mp->m_maildir_file[4], mp);
889 NYD_OU;
892 static enum okay
893 subdir_remove(char const *name, char const *sub)
895 char *path;
896 int pathsize, pathend, namelen, sublen, n;
897 DIR *dirp;
898 struct dirent *dp;
899 enum okay rv = STOP;
900 NYD_IN;
902 namelen = su_cs_len(name);
903 sublen = su_cs_len(sub);
904 path = n_alloc(pathsize = namelen + sublen + 30 +1);
905 su_mem_copy(path, name, namelen);
906 path[namelen] = '/';
907 su_mem_copy(path + namelen + 1, sub, sublen);
908 path[namelen + sublen + 1] = '/';
909 path[pathend = namelen + sublen + 2] = '\0';
911 if ((dirp = opendir(path)) == NULL) {
912 n_perr(path, 0);
913 goto jleave;
915 while ((dp = readdir(dirp)) != NULL) {
916 if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' ||
917 (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
918 continue;
919 if (dp->d_name[0] == '.')
920 continue;
921 n = su_cs_len(dp->d_name);
922 if (UCMP(32, pathend + n +1, >, pathsize))
923 path = n_realloc(path, pathsize = pathend + n + 30);
924 su_mem_copy(path + pathend, dp->d_name, n +1);
925 if (unlink(path) == -1) {
926 n_perr(path, 0);
927 closedir(dirp);
928 goto jleave;
931 closedir(dirp);
933 path[pathend] = '\0';
934 if (rmdir(path) == -1) {
935 n_perr(path, 0);
936 goto jleave;
938 rv = OKAY;
939 jleave:
940 n_free(path);
941 NYD_OU;
942 return rv;
945 FL int
946 maildir_setfile(char const *who, char const * volatile name,
947 enum fedit_mode fm)
949 n_sighdl_t volatile saveint;
950 struct cw cw;
951 char const *emsg;
952 int omsgCount;
953 int volatile i = -1;
954 NYD_IN;
956 omsgCount = msgCount;
957 if (cwget(&cw) == STOP) {
958 n_alert(_("Cannot open current directory"));
959 goto jleave;
962 if (!(fm & FEDIT_NEWMAIL) && !quit(FAL0))
963 goto jleave;
965 saveint = safe_signal(SIGINT, SIG_IGN);
967 if (!(fm & FEDIT_NEWMAIL)) {
968 if (fm & FEDIT_SYSBOX)
969 n_pstate &= ~n_PS_EDIT;
970 else
971 n_pstate |= n_PS_EDIT;
972 if (mb.mb_itf) {
973 fclose(mb.mb_itf);
974 mb.mb_itf = NULL;
976 if (mb.mb_otf) {
977 fclose(mb.mb_otf);
978 mb.mb_otf = NULL;
980 initbox(name);
981 mb.mb_type = MB_MAILDIR;
984 if(!n_is_dir(name, FAL0)){
985 emsg = N_("Not a maildir: %s\n");
986 goto jerr;
987 }else if(chdir(name) < 0){
988 emsg = N_("Cannot enter maildir://%s\n");
989 jerr:
990 n_err(V_(emsg), n_shexp_quote_cp(name, FAL0));
991 UNUSED(emsg);
992 mb.mb_type = MB_VOID;
993 *mailname = '\0';
994 msgCount = 0;
995 cwrelse(&cw);
996 safe_signal(SIGINT, saveint);
997 goto jleave;
1000 a_maildir_tbl = NULL;
1001 if (sigsetjmp(_maildir_jmp, 1) == 0) {
1002 if (fm & FEDIT_NEWMAIL)
1003 mktable();
1004 if (saveint != SIG_IGN)
1005 safe_signal(SIGINT, &__maildircatch);
1006 if(a_maildir_setfile1(name, fm, omsgCount) < 0){
1007 if((fm & FEDIT_NEWMAIL) && a_maildir_tbl != NIL)
1008 n_free(a_maildir_tbl);
1009 emsg = N_("Cannot setup maildir://%s\n");
1010 goto jerr;
1013 if ((fm & FEDIT_NEWMAIL) && a_maildir_tbl != NULL)
1014 n_free(a_maildir_tbl);
1016 safe_signal(SIGINT, saveint);
1018 if (cwret(&cw) == STOP)
1019 n_panic(_("Cannot change back to current directory"));
1020 cwrelse(&cw);
1022 setmsize(msgCount);
1023 if ((fm & FEDIT_NEWMAIL) && mb.mb_sorted && msgCount > omsgCount) {
1024 mb.mb_threaded = 0;
1025 c_sort((void*)-1);
1028 if (!(fm & FEDIT_NEWMAIL)) {
1029 n_pstate &= ~n_PS_SAW_COMMAND;
1030 n_pstate |= n_PS_SETFILE_OPENED;
1033 if ((n_poption & n_PO_EXISTONLY) && !(n_poption & n_PO_HEADERLIST)) {
1034 i = (msgCount == 0);
1035 goto jleave;
1038 if (!(fm & FEDIT_NEWMAIL) && (fm & FEDIT_SYSBOX) && msgCount == 0) {
1039 if (mb.mb_type == MB_MAILDIR /* XXX ?? */ && !ok_blook(emptystart))
1040 n_err(_("No mail for %s at %s\n"), who, n_shexp_quote_cp(name, FAL0));
1041 i = 1;
1042 goto jleave;
1045 if ((fm & FEDIT_NEWMAIL) && msgCount > omsgCount)
1046 newmailinfo(omsgCount);
1047 i = 0;
1048 jleave:
1049 NYD_OU;
1050 return i;
1053 FL boole
1054 maildir_quit(boole hold_sigs_on)
1056 n_sighdl_t saveint;
1057 struct cw cw;
1058 boole rv;
1059 NYD_IN;
1061 if(hold_sigs_on)
1062 rele_sigs();
1064 rv = FAL0;
1066 if (cwget(&cw) == STOP) {
1067 n_alert(_("Cannot open current directory"));
1068 goto jleave;
1071 saveint = safe_signal(SIGINT, SIG_IGN);
1073 if (chdir(mailname) == -1) {
1074 n_err(_("Cannot change directory to %s\n"),
1075 n_shexp_quote_cp(mailname, FAL0));
1076 cwrelse(&cw);
1077 safe_signal(SIGINT, saveint);
1078 goto jleave;
1081 if (sigsetjmp(_maildir_jmp, 1) == 0) {
1082 if (saveint != SIG_IGN)
1083 safe_signal(SIGINT, &__maildircatch_hold);
1084 maildir_update();
1087 safe_signal(SIGINT, saveint);
1089 if (cwret(&cw) == STOP)
1090 n_panic(_("Cannot change back to current directory"));
1091 cwrelse(&cw);
1092 rv = TRU1;
1093 jleave:
1094 if(hold_sigs_on)
1095 hold_sigs();
1096 NYD_OU;
1097 return rv;
1100 FL enum okay
1101 maildir_append(char const *name, FILE *fp, long offset)
1103 struct n_timespec const *tsp;
1104 char *buf, *bp, *lp;
1105 uz bufsize, buflen, cnt;
1106 off_t off1 = -1, offs;
1107 long size;
1108 int flag;
1109 enum {_NONE = 0, _INHEAD = 1<<0, _NLSEP = 1<<1} state;
1110 enum okay rv;
1111 NYD_IN;
1113 if ((rv = mkmaildir(name)) != OKAY)
1114 goto jleave;
1116 mx_fs_linepool_aquire(&buf, &bufsize);
1117 buflen = 0;
1118 cnt = fsize(fp);
1119 offs = offset /* BSD will move due to O_APPEND! ftell(fp) */;
1120 size = 0;
1121 tsp = n_time_now(TRU1); /* TODO -> eventloop */
1123 n_autorec_relax_create();
1124 for (flag = MNEW, state = _NLSEP;;) {
1125 bp = fgetline(&buf, &bufsize, &cnt, &buflen, fp, TRU1);
1127 if (bp == NULL ||
1128 ((state & (_INHEAD | _NLSEP)) == _NLSEP &&
1129 is_head(buf, buflen, FAL0))) {
1130 if (off1 != (off_t)-1) {
1131 if((rv = maildir_append1(tsp, name, fp, off1, size, flag)) == STOP)
1132 goto jfree;
1133 n_autorec_relax_unroll();
1134 if (fseek(fp, offs + buflen, SEEK_SET) == -1) {
1135 rv = STOP;
1136 goto jfree;
1139 off1 = offs + buflen;
1140 size = 0;
1141 state = _INHEAD;
1142 flag = MNEW;
1144 if(bp == NIL){
1145 if(ferror(fp)){
1146 rv = STOP;
1147 goto jfree;
1149 break;
1151 } else
1152 size += buflen;
1153 offs += buflen;
1155 state &= ~_NLSEP;
1156 if (buf[0] == '\n') {
1157 state &= ~_INHEAD;
1158 state |= _NLSEP;
1159 } else if (state & _INHEAD) {
1160 if (!su_cs_cmp_case_n(buf, "status", 6)) {
1161 lp = buf + 6;
1162 while (su_cs_is_white(*lp))
1163 ++lp;
1164 if (*lp == ':')
1165 while (*++lp != '\0')
1166 switch (*lp) {
1167 case 'R':
1168 flag |= MREAD;
1169 break;
1170 case 'O':
1171 flag &= ~MNEW;
1172 break;
1174 } else if (!su_cs_cmp_case_n(buf, "x-status", 8)) {
1175 lp = buf + 8;
1176 while (su_cs_is_white(*lp))
1177 ++lp;
1178 if (*lp == ':') {
1179 while (*++lp != '\0')
1180 switch (*lp) {
1181 case 'F':
1182 flag |= MFLAGGED;
1183 break;
1184 case 'A':
1185 flag |= MANSWERED;
1186 break;
1187 case 'T':
1188 flag |= MDRAFTED;
1189 break;
1196 ASSERT(rv == OKAY);
1197 jfree:
1198 n_autorec_relax_gut();
1199 mx_fs_linepool_release(buf, bufsize);
1200 jleave:
1201 NYD_OU;
1202 return rv;
1205 FL enum okay
1206 maildir_remove(char const *name)
1208 enum okay rv = STOP;
1209 NYD_IN;
1211 if (subdir_remove(name, "tmp") == STOP ||
1212 subdir_remove(name, "new") == STOP ||
1213 subdir_remove(name, "cur") == STOP)
1214 goto jleave;
1215 if (rmdir(name) == -1) {
1216 n_perr(name, 0);
1217 goto jleave;
1219 rv = OKAY;
1220 jleave:
1221 NYD_OU;
1222 return rv;
1225 #include "su/code-ou.h"
1226 #endif /* mx_HAVE_MAILDIR */
1227 /* s-it-mode */