Add n_hy[] ("-"), use it
[s-mailx.git] / maildir.c
bloba14135bd9f81a1da7a7ae72e1493386366650df8
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 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
7 */
8 /*
9 * Copyright (c) 2004
10 * Gunnar Ritter. All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
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. All advertising materials mentioning features or use of this software
21 * must display the following acknowledgement:
22 * This product includes software developed by Gunnar Ritter
23 * and his contributors.
24 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
25 * may be used to endorse or promote products derived from this software
26 * without specific prior written permission.
28 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
29 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
30 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
31 * ARE DISCLAIMED. IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
32 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
33 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
34 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
35 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
36 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
37 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38 * SUCH DAMAGE.
40 #undef n_FILE
41 #define n_FILE maildir
43 #ifndef HAVE_AMALGAMATION
44 # include "nail.h"
45 #endif
47 #ifdef HAVE_MAILDIR
48 # include <dirent.h>
49 #endif
51 EMPTY_FILE()
52 #ifdef HAVE_MAILDIR
54 /* a_maildir_tbl should be a hash-indexed array of trees! */
55 static struct message **a_maildir_tbl, **a_maildir_tbl_top;
56 static ui32_t a_maildir_tbl_prime, a_maildir_tbl_maxdist;
57 static sigjmp_buf _maildir_jmp;
59 static void __maildircatch(int s);
60 static void __maildircatch_hold(int s);
62 /* Do some cleanup in the tmp/ subdir */
63 static void _cleantmp(void);
65 static int _maildir_setfile1(char const *name, enum fedit_mode fm,
66 int omsgCount);
68 static int a_maildir_cmp(void const *a, void const *b);
70 static int _maildir_subdir(char const *name, char const *sub,
71 enum fedit_mode fm);
73 static void _maildir_append(char const *name, char const *sub,
74 char const *fn);
76 static void readin(char const *name, struct message *m);
78 static void maildir_update(void);
80 static void _maildir_move(struct n_timespec const *tsp,
81 struct message *m);
83 static char * mkname(struct n_timespec const *tsp, enum mflag f,
84 char const *pref);
86 static enum okay maildir_append1(struct n_timespec const *tsp,
87 char const *name, FILE *fp, off_t off1,
88 long size, enum mflag flag);
90 static enum okay trycreate(char const *name);
92 static enum okay mkmaildir(char const *name);
94 static struct message * mdlook(char const *name, struct message *data);
96 static void mktable(void);
98 static enum okay subdir_remove(char const *name, char const *sub);
100 static void
101 __maildircatch(int s)
103 NYD_X; /* Signal handler */
104 siglongjmp(_maildir_jmp, s);
107 static void
108 __maildircatch_hold(int s)
110 NYD_X; /* Signal handler */
111 n_UNUSED(s);
112 /* TODO no STDIO in signal handler, no _() tr's -- pre-translate interrupt
113 * TODO globally; */
114 n_err_sighdl(_("\nImportant operation in progress: "
115 "interrupt again to forcefully abort\n"));
116 safe_signal(SIGINT, &__maildircatch);
119 static void
120 _cleantmp(void)
122 struct stat st;
123 struct n_string s, *sp;
124 si64_t now;
125 DIR *dirp;
126 struct dirent *dp;
127 NYD_ENTER;
129 if ((dirp = opendir("tmp")) == NULL)
130 goto jleave;
132 now = n_time_now(FAL0)->ts_sec;
133 sp = n_string_creat_auto(&s);
135 while ((dp = readdir(dirp)) != NULL) {
136 if (dp->d_name[0] == '.')
137 continue;
139 sp = n_string_trunc(sp, 0);
140 sp = n_string_push_buf(sp, "tmp/", sizeof("tmp/") -1);
141 sp = n_string_push_cp(sp, dp->d_name);
142 if (stat(n_string_cp(sp), &st) == -1)
143 continue;
144 if (st.st_atime + 36*3600 < now)
145 unlink(sp->s_dat);
147 closedir(dirp);
148 jleave:
149 NYD_LEAVE;
152 static int
153 _maildir_setfile1(char const *name, enum fedit_mode fm, int omsgCount)
155 int i;
156 NYD_ENTER;
158 if (!(fm & FEDIT_NEWMAIL))
159 _cleantmp();
161 mb.mb_perm = ((n_poption & n_PO_R_FLAG) || (fm & FEDIT_RDONLY))
162 ? 0 : MB_DELE;
163 if ((i = _maildir_subdir(name, "cur", fm)) != 0)
164 goto jleave;
165 if ((i = _maildir_subdir(name, "new", fm)) != 0)
166 goto jleave;
167 _maildir_append(name, NULL, NULL);
169 n_autorec_relax_create();
170 for (i = ((fm & FEDIT_NEWMAIL) ? omsgCount : 0); i < msgCount; ++i) {
171 readin(name, message + i);
172 n_autorec_relax_unroll();
174 n_autorec_relax_gut();
176 if (fm & FEDIT_NEWMAIL) {
177 if (msgCount > omsgCount)
178 qsort(&message[omsgCount], msgCount - omsgCount, sizeof *message,
179 &a_maildir_cmp);
180 } else if (msgCount)
181 qsort(message, msgCount, sizeof *message, &a_maildir_cmp);
182 i = msgCount;
183 jleave:
184 NYD_LEAVE;
185 return i;
188 static int
189 a_maildir_cmp(void const *xa, void const *xb){
190 char const *cpa, *cpa_pid, *cpb, *cpb_pid;
191 union {struct message const *mp; char const *cp;} a, b;
192 si64_t at, bt;
193 int rv;
194 NYD2_ENTER;
196 a.mp = xa;
197 b.mp = xb;
199 /* We could have parsed the time somewhen in the past, do a quick shot */
200 at = (si64_t)a.mp->m_time;
201 bt = (si64_t)b.mp->m_time;
202 if(at != 0 && bt != 0 && (at -= bt) != 0)
203 goto jret;
205 /* Otherwise we need to parse the name */
206 a.cp = &a.mp->m_maildir_file[4];
207 b.cp = &b.mp->m_maildir_file[4];
209 /* Interpret time stored in name, and use it for comparison */
210 if(((n_idec_si64_cp(&at, a.cp, 10, &cpa)
211 ) & n_IDEC_STATE_EMASK) != n_IDEC_STATE_EBASE || *cpa != '.' ||
212 a.cp == cpa)
213 goto jm1; /* Fishy */
214 if(((n_idec_si64_cp(&bt, b.cp, 10, &cpb)
215 ) & n_IDEC_STATE_EMASK) != n_IDEC_STATE_EBASE || *cpb != '.' ||
216 b.cp == cpb)
217 goto j1; /* Fishy */
219 if((at -= bt) != 0)
220 goto jret;
222 /* If the seconds part does not work, go deeper.
223 * We use de-facto standard "maildir — E-mail directory" from the Courier
224 * mail server, also used by, e.g., Dovecot: sec.MusecPpid.hostname:2,flags.
225 * However, a different name convention exists which uses
226 * sec.pid_counter.hostname:2,flags.
227 * First go for usec/counter, then pid */
229 /* A: exact "standard"? */
230 cpa_pid = NULL;
231 a.cp = ++cpa;
232 if((rv = *a.cp) == 'M')
234 /* Known compat? */
235 else if(digitchar(rv)){
236 cpa_pid = a.cp++;
237 while((rv = *a.cp) != '\0' && rv != '_')
238 ++a.cp;
239 if(rv == '\0')
240 goto jm1; /* Fishy */
242 /* This is compatible to what dovecot does, it surely does not do so
243 * for nothing, but i have no idea, but am too stupid to ask */
244 else for(;; rv = *++a.cp){
245 if(rv == 'M')
246 break;
247 if(rv == '\0' || rv == '.' || rv == n_MAILDIR_SEPARATOR)
248 goto jm1; /* Fishy */
250 ++a.cp;
251 if(((n_idec_si64_cp(&at, a.cp, 10, &cpa)
252 ) & n_IDEC_STATE_EMASK) != n_IDEC_STATE_EBASE)
253 goto jm1; /* Fishy */
255 /* B: as above */
256 cpb_pid = NULL;
257 b.cp = ++cpb;
258 if((rv = *b.cp) == 'M')
260 else if(digitchar(rv)){
261 cpb_pid = b.cp++;
262 while((rv = *b.cp) != '\0' && rv != '_')
263 ++b.cp;
264 if(rv == '\0')
265 goto j1;
266 }else for(;; rv = *++b.cp){
267 if(rv == 'M')
268 break;
269 if(rv == '\0' || rv == '.' || rv == n_MAILDIR_SEPARATOR)
270 goto jm1;
272 ++b.cp;
273 if(((n_idec_si64_cp(&bt, b.cp, 10, &cpb)
274 ) & n_IDEC_STATE_EMASK) != n_IDEC_STATE_EBASE)
275 goto j1;
277 if((at -= bt) != 0)
278 goto jret;
280 /* So this gets hairy: sort by PID, then hostname */
281 if(cpa_pid != NULL){
282 a.cp = cpa_pid;
283 xa = cpa;
284 }else{
285 a.cp = cpa;
286 if(*a.cp++ != 'P')
287 goto jm1; /* Fishy */
289 if(((n_idec_si64_cp(&at, a.cp, 10, &cpa)
290 ) & n_IDEC_STATE_EMASK) != n_IDEC_STATE_EBASE)
291 goto jm1; /* Fishy */
293 if(cpb_pid != NULL){
294 b.cp = cpb_pid;
295 xb = cpb;
296 }else{
297 b.cp = cpb;
298 if(*b.cp++ != 'P')
299 goto j1; /* Fishy */
301 if(((n_idec_si64_cp(&bt, b.cp, 10, &cpb)
302 ) & n_IDEC_STATE_EMASK) != n_IDEC_STATE_EBASE)
303 goto jm1; /* Fishy */
305 if((at -= bt) != 0)
306 goto jret;
308 /* Hostname */
309 a.cp = (cpa_pid != NULL) ? xa : cpa;
310 b.cp = (cpb_pid != NULL) ? xb : cpb;
311 for(;; ++a.cp, ++b.cp){
312 char ac, bc;
314 ac = *a.cp;
315 at = (ac != '\0' && ac != n_MAILDIR_SEPARATOR);
316 bc = *b.cp;
317 bt = (bc != '\0' && bc != n_MAILDIR_SEPARATOR);
318 if((at -= bt) != 0)
319 break;
320 at = ac;
321 if((at -= bc) != 0)
322 break;
323 if(ac == '\0')
324 break;
327 jret:
328 rv = (at == 0 ? 0 : (at < 0 ? -1 : 1));
329 jleave:
330 NYD2_LEAVE;
331 return rv;
332 jm1:
333 rv = -1;
334 goto jleave;
336 rv = 1;
337 goto jleave;
340 static int
341 _maildir_subdir(char const *name, char const *sub, enum fedit_mode fm)
343 DIR *dirp;
344 struct dirent *dp;
345 int rv;
346 NYD_ENTER;
348 if ((dirp = opendir(sub)) == NULL) {
349 n_err(_("Cannot open directory %s\n"),
350 n_shexp_quote_cp(savecatsep(name, '/', sub), FAL0));
351 rv = -1;
352 goto jleave;
354 if (access(sub, W_OK) == -1)
355 mb.mb_perm = 0;
356 while ((dp = readdir(dirp)) != NULL) {
357 if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' ||
358 (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
359 continue;
360 if (dp->d_name[0] == '.')
361 continue;
362 if (!(fm & FEDIT_NEWMAIL) || mdlook(dp->d_name, NULL) == NULL)
363 _maildir_append(name, sub, dp->d_name);
365 closedir(dirp);
366 rv = 0;
367 jleave:
368 NYD_LEAVE;
369 return rv;
372 static void
373 _maildir_append(char const *name, char const *sub, char const *fn)
375 struct message *m;
376 time_t t = 0;
377 enum mflag f = MUSED | MNOFROM | MNEWEST;
378 char const *cp, *xp;
379 NYD_ENTER;
380 n_UNUSED(name);
382 if (fn != NULL && sub != NULL) {
383 if (!strcmp(sub, "new"))
384 f |= MNEW;
386 /* C99 */{
387 si64_t tib;
389 (void)/*TODO*/n_idec_si64_cp(&tib, fn, 10, &xp);
390 t = (time_t)tib;
393 if ((cp = strrchr(xp, ',')) != NULL && PTRCMP(cp, >, xp + 2) &&
394 cp[-1] == '2' && cp[-2] == n_MAILDIR_SEPARATOR) {
395 while (*++cp != '\0') {
396 switch (*cp) {
397 case 'F':
398 f |= MFLAGGED;
399 break;
400 case 'R':
401 f |= MANSWERED;
402 break;
403 case 'S':
404 f |= MREAD;
405 break;
406 case 'T':
407 f |= MDELETED;
408 break;
409 case 'D':
410 f |= MDRAFT;
411 break;
417 /* Ensure room (and a NULLified last entry) */
418 ++msgCount;
419 message_append(NULL);
420 --msgCount;
422 if (fn == NULL || sub == NULL)
423 goto jleave;
425 m = &message[msgCount++];
426 /* C99 */{
427 char *tmp;
428 size_t sz, i;
430 i = strlen(fn) +1;
431 sz = strlen(sub);
432 m->m_maildir_file = tmp = n_alloc(sz + 1 + i);
433 memcpy(tmp, sub, sz);
434 tmp[sz++] = '/';
435 memcpy(&tmp[sz], fn, i);
437 m->m_time = t;
438 m->m_flag = f;
439 m->m_maildir_hash = n_torek_hash(fn);
440 jleave:
441 NYD_LEAVE;
442 return;
445 static void
446 readin(char const *name, struct message *m)
448 char *buf;
449 size_t bufsize, buflen, cnt;
450 long size = 0, lines = 0;
451 off_t offset;
452 FILE *fp;
453 int emptyline = 0;
454 NYD_ENTER;
456 if ((fp = Fopen(m->m_maildir_file, "r")) == NULL) {
457 n_err(_("Cannot read %s for message %lu\n"),
458 n_shexp_quote_cp(savecatsep(name, '/', m->m_maildir_file), FAL0),
459 (ul_i)PTR2SIZE(m - message + 1));
460 m->m_flag |= MHIDDEN;
461 goto jleave;
464 cnt = fsize(fp);
465 fseek(mb.mb_otf, 0L, SEEK_END);
466 offset = ftell(mb.mb_otf);
467 buf = n_alloc(bufsize = LINESIZE);
468 buflen = 0;
469 while (fgetline(&buf, &bufsize, &cnt, &buflen, fp, 1) != NULL) {
470 /* Since we simply copy over data without doing any transfer
471 * encoding reclassification/adjustment we *have* to perform
472 * RFC 4155 compliant From_ quoting here */
473 if (emptyline && is_head(buf, buflen, FAL0)) {
474 putc('>', mb.mb_otf);
475 ++size;
477 size += fwrite(buf, 1, buflen, mb.mb_otf);/*XXX err hdling*/
478 emptyline = (*buf == '\n');
479 ++lines;
481 n_free(buf);
482 if (!emptyline) {
483 /* TODO we need \n\n for mbox format.
484 * TODO That is to say we do it wrong here in order to get it right
485 * TODO when send.c stuff or with MBOX handling, even though THIS
486 * TODO line is solely a property of the MBOX database format! */
487 putc('\n', mb.mb_otf);
488 ++lines;
489 ++size;
492 Fclose(fp);
493 fflush(mb.mb_otf);
494 m->m_size = m->m_xsize = size;
495 m->m_lines = m->m_xlines = lines;
496 m->m_block = mailx_blockof(offset);
497 m->m_offset = mailx_offsetof(offset);
498 substdate(m);
499 jleave:
500 NYD_LEAVE;
503 static void
504 maildir_update(void)
506 struct message *m;
507 struct n_timespec const *tsp;
508 int dodel, c, gotcha = 0, held = 0, modflags = 0;
509 NYD_ENTER;
511 if (mb.mb_perm == 0)
512 goto jfree;
514 if (!(n_pstate & n_PS_EDIT)) {
515 holdbits();
516 for (m = message, c = 0; PTRCMP(m, <, message + msgCount); ++m) {
517 if (m->m_flag & MBOX)
518 c++;
520 if (c > 0)
521 if (makembox() == STOP)
522 goto jbypass;
525 tsp = n_time_now(TRU1); /* TODO FAL0, eventloop update! */
527 n_autorec_relax_create();
528 for (m = message, gotcha = 0, held = 0; PTRCMP(m, <, message + msgCount);
529 ++m) {
530 if (n_pstate & n_PS_EDIT)
531 dodel = m->m_flag & MDELETED;
532 else
533 dodel = !((m->m_flag & MPRESERVE) || !(m->m_flag & MTOUCH));
534 if (dodel) {
535 if (unlink(m->m_maildir_file) < 0)
536 n_err(_("Cannot delete file %s for message %lu\n"),
537 n_shexp_quote_cp(savecatsep(mailname, '/', m->m_maildir_file),
538 FAL0), (ul_i)PTR2SIZE(m - message + 1));
539 else
540 ++gotcha;
541 } else {
542 if ((m->m_flag & (MREAD | MSTATUS)) == (MREAD | MSTATUS) ||
543 (m->m_flag & (MNEW | MBOXED | MSAVED | MSTATUS | MFLAG |
544 MUNFLAG | MANSWER | MUNANSWER | MDRAFT | MUNDRAFT))) {
545 _maildir_move(tsp, m);
546 n_autorec_relax_unroll();
547 ++modflags;
549 ++held;
552 n_autorec_relax_gut();
554 jbypass:
555 if ((gotcha || modflags) && (n_pstate & n_PS_EDIT)) {
556 fprintf(n_stdout, "%s %s\n",
557 n_shexp_quote_cp(displayname, FAL0),
558 ((ok_blook(bsdcompat) || ok_blook(bsdmsgs))
559 ? _("complete") : _("updated.")));
560 } else if (held && !(n_pstate & n_PS_EDIT) && mb.mb_perm != 0) {
561 if (held == 1)
562 fprintf(n_stdout, _("Held 1 message in %s\n"), displayname);
563 else
564 fprintf(n_stdout, _("Held %d messages in %s\n"), held, displayname);
566 fflush(n_stdout);
567 jfree:
568 for (m = message; PTRCMP(m, <, message + msgCount); ++m)
569 n_free(n_UNCONST(m->m_maildir_file));
570 NYD_LEAVE;
573 static void
574 _maildir_move(struct n_timespec const *tsp, struct message *m)
576 char *fn, *newfn;
577 NYD_ENTER;
579 fn = mkname(tsp, m->m_flag, m->m_maildir_file + 4);
580 newfn = savecat("cur/", fn);
581 if (!strcmp(m->m_maildir_file, newfn))
582 goto jleave;
583 if (link(m->m_maildir_file, newfn) == -1) {
584 n_err(_("Cannot link %s to %s: message %lu not touched\n"),
585 n_shexp_quote_cp(savecatsep(mailname, '/', m->m_maildir_file), FAL0),
586 n_shexp_quote_cp(savecatsep(mailname, '/', newfn), FAL0),
587 (ul_i)PTR2SIZE(m - message + 1));
588 goto jleave;
590 if (unlink(m->m_maildir_file) == -1)
591 n_err(_("Cannot unlink %s\n"),
592 n_shexp_quote_cp(savecatsep(mailname, '/', m->m_maildir_file), FAL0));
593 jleave:
594 NYD_LEAVE;
597 static char *
598 mkname(struct n_timespec const *tsp, enum mflag f, char const *pref)
600 static char *node;
601 static struct n_timespec ts;
603 char *cp;
604 int size, n, i;
605 NYD_ENTER;
607 if (pref == NULL) {
608 si64_t s;
610 if(n_pid == 0)
611 n_pid = getpid();
613 if (node == NULL) {
614 cp = n_nodename(FAL0);
615 n = size = 0;
616 do {
617 if (UICMP(32, n, <, size + 8))
618 node = n_realloc(node, size += 20);
619 switch (*cp) {
620 case '/':
621 node[n++] = '\\', node[n++] = '0',
622 node[n++] = '5', node[n++] = '7';
623 break;
624 case ':':
625 node[n++] = '\\', node[n++] = '0',
626 node[n++] = '7', node[n++] = '2';
627 break;
628 default:
629 node[n++] = *cp;
631 } while (*cp++ != '\0');
634 /* Problem: Courier spec uses microseconds, not nanoseconds */
635 if((s = tsp->ts_sec) > ts.ts_sec){
636 ts.ts_sec = s;
637 ts.ts_nsec = tsp->ts_nsec / (n_DATE_NANOSSEC / n_DATE_MICROSSEC);
638 }else{
639 s = tsp->ts_nsec / (n_DATE_NANOSSEC / n_DATE_MICROSSEC);
640 if(s <= ts.ts_nsec)
641 s = ts.ts_nsec + 1;
642 if(s < n_DATE_MICROSSEC)
643 ts.ts_nsec = s;
644 else{
645 ++ts.ts_sec;
646 ts.ts_nsec = 0;
650 /* Create a name according to Courier spec */
651 size = 60 + strlen(node);
652 cp = n_autorec_alloc(size);
653 n = snprintf(cp, size, "%" PRId64 ".M%" PRIdZ "P%ld.%s:2,",
654 ts.ts_sec, ts.ts_nsec, (long)n_pid, node);
655 } else {
656 size = (n = strlen(pref)) + 13;
657 cp = n_autorec_alloc(size);
658 memcpy(cp, pref, n +1);
659 for (i = n; i > 3; --i)
660 if (cp[i - 1] == ',' && cp[i - 2] == '2' &&
661 cp[i - 3] == n_MAILDIR_SEPARATOR) {
662 n = i;
663 break;
665 if (i <= 3) {
666 memcpy(cp + n, ":2,", 3 +1);
667 n += 3;
670 if (n < size - 7) {
671 if (f & MDRAFTED)
672 cp[n++] = 'D';
673 if (f & MFLAGGED)
674 cp[n++] = 'F';
675 if (f & MANSWERED)
676 cp[n++] = 'R';
677 if (f & MREAD)
678 cp[n++] = 'S';
679 if (f & MDELETED)
680 cp[n++] = 'T';
681 cp[n] = '\0';
683 NYD_LEAVE;
684 return cp;
687 static enum okay
688 maildir_append1(struct n_timespec const *tsp, char const *name, FILE *fp,
689 off_t off1, long size, enum mflag flag)
691 char buf[4096], *fn, *tfn, *nfn;
692 struct stat st;
693 FILE *op;
694 size_t nlen, flen, n;
695 enum okay rv = STOP;
696 NYD_ENTER;
698 nlen = strlen(name);
700 /* Create a unique temporary file */
701 for (nfn = (char*)0xA /* XXX no magic */;; n_msleep(500, FAL0)) {
702 flen = strlen(fn = mkname(tsp, flag, NULL));
703 tfn = n_autorec_alloc(n = nlen + flen + 6);
704 snprintf(tfn, n, "%s/tmp/%s", name, fn);
706 /* Use "wx" for O_EXCL XXX stat(2) rather redundant; coverity:TOCTOU */
707 if ((!stat(tfn, &st) || n_err_no == n_ERR_NOENT) &&
708 (op = Fopen(tfn, "wx")) != NULL)
709 break;
711 nfn = (char*)(PTR2SIZE(nfn) - 1);
712 if (nfn == NULL) {
713 n_err(_("Can't create an unique file name in %s\n"),
714 n_shexp_quote_cp(savecat(name, "/tmp"), FAL0));
715 goto jleave;
719 if (fseek(fp, off1, SEEK_SET) == -1)
720 goto jtmperr;
721 while (size > 0) {
722 size_t z = UICMP(z, size, >, sizeof buf) ? sizeof buf : (size_t)size;
724 if (z != (n = fread(buf, 1, z, fp)) || n != fwrite(buf, 1, n, op)) {
725 jtmperr:
726 n_err(_("Error writing to %s\n"), n_shexp_quote_cp(tfn, FAL0));
727 Fclose(op);
728 goto jerr;
730 size -= n;
732 Fclose(op);
734 nfn = n_autorec_alloc(n = nlen + flen + 6);
735 snprintf(nfn, n, "%s/new/%s", name, fn);
736 if (link(tfn, nfn) == -1) {
737 n_err(_("Cannot link %s to %s\n"), n_shexp_quote_cp(tfn, FAL0),
738 n_shexp_quote_cp(nfn, FAL0));
739 goto jerr;
741 rv = OKAY;
742 jerr:
743 if (unlink(tfn) == -1)
744 n_err(_("Cannot unlink %s\n"), n_shexp_quote_cp(tfn, FAL0));
745 jleave:
746 NYD_LEAVE;
747 return rv;
750 static enum okay
751 trycreate(char const *name)
753 struct stat st;
754 enum okay rv = STOP;
755 NYD_ENTER;
757 if (!stat(name, &st)) {
758 if (!S_ISDIR(st.st_mode)) {
759 n_err(_("%s is not a directory\n"), n_shexp_quote_cp(name, FAL0));
760 goto jleave;
762 } else if (!n_path_mkdir(name)) {
763 n_err(_("Cannot create directory %s\n"), n_shexp_quote_cp(name, FAL0));
764 goto jleave;
766 rv = OKAY;
767 jleave:
768 NYD_LEAVE;
769 return rv;
772 static enum okay
773 mkmaildir(char const *name) /* TODO proper cleanup on error; use path[] loop */
775 char *np;
776 size_t sz;
777 enum okay rv = STOP;
778 NYD_ENTER;
780 if (trycreate(name) == OKAY) {
781 np = n_lofi_alloc((sz = strlen(name)) + 4 +1);
782 memcpy(np, name, sz);
783 memcpy(np + sz, "/tmp", 4 +1);
784 if (trycreate(np) == OKAY) {
785 memcpy(np + sz, "/new", 4 +1);
786 if (trycreate(np) == OKAY) {
787 memcpy(np + sz, "/cur", 4 +1);
788 rv = trycreate(np);
791 n_lofi_free(np);
793 NYD_LEAVE;
794 return rv;
797 static struct message *
798 mdlook(char const *name, struct message *data)
800 struct message **mpp, *mp;
801 ui32_t h, i;
802 NYD_ENTER;
804 if(data != NULL)
805 i = data->m_maildir_hash;
806 else
807 i = n_torek_hash(name);
808 h = i;
809 mpp = &a_maildir_tbl[i %= a_maildir_tbl_prime];
811 for(i = 0;;){
812 if((mp = *mpp) == NULL){
813 if(n_UNLIKELY(data != NULL)){
814 *mpp = mp = data;
815 if(i > a_maildir_tbl_maxdist)
816 a_maildir_tbl_maxdist = i;
818 break;
819 }else if(mp->m_maildir_hash == h && !strcmp(&mp->m_maildir_file[4], name))
820 break;
822 if(n_UNLIKELY(mpp++ == a_maildir_tbl_top))
823 mpp = a_maildir_tbl;
824 if(++i > a_maildir_tbl_maxdist && n_UNLIKELY(data == NULL)){
825 mp = NULL;
826 break;
829 NYD_LEAVE;
830 return mp;
833 static void
834 mktable(void)
836 struct message *mp;
837 size_t i;
838 NYD_ENTER;
840 i = a_maildir_tbl_prime = msgCount;
841 i <<= 1;
843 a_maildir_tbl_prime = n_prime_next(a_maildir_tbl_prime);
844 while(a_maildir_tbl_prime < i);
845 a_maildir_tbl = n_calloc(a_maildir_tbl_prime, sizeof *a_maildir_tbl);
846 a_maildir_tbl_top = &a_maildir_tbl[a_maildir_tbl_prime - 1];
847 a_maildir_tbl_maxdist = 0;
848 for(mp = message, i = msgCount; i-- != 0; ++mp)
849 mdlook(&mp->m_maildir_file[4], mp);
850 NYD_LEAVE;
853 static enum okay
854 subdir_remove(char const *name, char const *sub)
856 char *path;
857 int pathsize, pathend, namelen, sublen, n;
858 DIR *dirp;
859 struct dirent *dp;
860 enum okay rv = STOP;
861 NYD_ENTER;
863 namelen = strlen(name);
864 sublen = strlen(sub);
865 path = n_alloc(pathsize = namelen + sublen + 30 +1);
866 memcpy(path, name, namelen);
867 path[namelen] = '/';
868 memcpy(path + namelen + 1, sub, sublen);
869 path[namelen + sublen + 1] = '/';
870 path[pathend = namelen + sublen + 2] = '\0';
872 if ((dirp = opendir(path)) == NULL) {
873 n_perr(path, 0);
874 goto jleave;
876 while ((dp = readdir(dirp)) != NULL) {
877 if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' ||
878 (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
879 continue;
880 if (dp->d_name[0] == '.')
881 continue;
882 n = strlen(dp->d_name);
883 if (UICMP(32, pathend + n +1, >, pathsize))
884 path = n_realloc(path, pathsize = pathend + n + 30);
885 memcpy(path + pathend, dp->d_name, n +1);
886 if (unlink(path) == -1) {
887 n_perr(path, 0);
888 closedir(dirp);
889 goto jleave;
892 closedir(dirp);
894 path[pathend] = '\0';
895 if (rmdir(path) == -1) {
896 n_perr(path, 0);
897 goto jleave;
899 rv = OKAY;
900 jleave:
901 n_free(path);
902 NYD_LEAVE;
903 return rv;
906 FL int
907 maildir_setfile(char const * volatile name, enum fedit_mode fm)
909 sighandler_type volatile saveint;
910 struct cw cw;
911 char const *emsg;
912 int omsgCount;
913 int volatile i = -1;
914 NYD_ENTER;
916 omsgCount = msgCount;
917 if (cwget(&cw) == STOP) {
918 n_alert(_("Cannot open current directory"));
919 goto jleave;
922 if (!(fm & FEDIT_NEWMAIL) && !quit(FAL0))
923 goto jleave;
925 saveint = safe_signal(SIGINT, SIG_IGN);
927 if (!(fm & FEDIT_NEWMAIL)) {
928 if (fm & FEDIT_SYSBOX)
929 n_pstate &= ~n_PS_EDIT;
930 else
931 n_pstate |= n_PS_EDIT;
932 if (mb.mb_itf) {
933 fclose(mb.mb_itf);
934 mb.mb_itf = NULL;
936 if (mb.mb_otf) {
937 fclose(mb.mb_otf);
938 mb.mb_otf = NULL;
940 initbox(name);
941 mb.mb_type = MB_MAILDIR;
944 if(!n_is_dir(name, FAL0)){
945 emsg = N_("Not a maildir: %s\n");
946 goto jerr;
947 }else if(chdir(name) < 0){
948 emsg = N_("Cannot enter maildir://%s\n");
949 jerr:
950 n_err(V_(emsg), n_shexp_quote_cp(name, FAL0));
951 mb.mb_type = MB_VOID;
952 *mailname = '\0';
953 msgCount = 0;
954 cwrelse(&cw);
955 safe_signal(SIGINT, saveint);
956 goto jleave;
959 a_maildir_tbl = NULL;
960 if (sigsetjmp(_maildir_jmp, 1) == 0) {
961 if (fm & FEDIT_NEWMAIL)
962 mktable();
963 if (saveint != SIG_IGN)
964 safe_signal(SIGINT, &__maildircatch);
965 i = _maildir_setfile1(name, fm, omsgCount);
967 if ((fm & FEDIT_NEWMAIL) && a_maildir_tbl != NULL)
968 n_free(a_maildir_tbl);
970 safe_signal(SIGINT, saveint);
972 if (i < 0) {
973 mb.mb_type = MB_VOID;
974 *mailname = '\0';
975 msgCount = 0;
978 if (cwret(&cw) == STOP)
979 n_panic(_("Cannot change back to current directory"));
980 cwrelse(&cw);
982 setmsize(msgCount);
983 if ((fm & FEDIT_NEWMAIL) && mb.mb_sorted && msgCount > omsgCount) {
984 mb.mb_threaded = 0;
985 c_sort((void*)-1);
988 if (!(fm & FEDIT_NEWMAIL)) {
989 n_pstate &= ~n_PS_SAW_COMMAND;
990 n_pstate |= n_PS_SETFILE_OPENED;
993 if ((n_poption & n_PO_EXISTONLY) && !(n_poption & n_PO_HEADERLIST)) {
994 i = (msgCount == 0);
995 goto jleave;
998 if (!(fm & FEDIT_NEWMAIL) && (fm & FEDIT_SYSBOX) && msgCount == 0) {
999 if (mb.mb_type == MB_MAILDIR /* XXX ?? */ && !ok_blook(emptystart))
1000 n_err(_("No mail at %s\n"), n_shexp_quote_cp(name, FAL0));
1001 i = 1;
1002 goto jleave;
1005 if ((fm & FEDIT_NEWMAIL) && msgCount > omsgCount)
1006 newmailinfo(omsgCount);
1007 i = 0;
1008 jleave:
1009 NYD_LEAVE;
1010 return i;
1013 FL bool_t
1014 maildir_quit(bool_t hold_sigs_on)
1016 sighandler_type saveint;
1017 struct cw cw;
1018 bool_t rv;
1019 NYD_ENTER;
1021 if(hold_sigs_on)
1022 rele_sigs();
1024 rv = FAL0;
1026 if (cwget(&cw) == STOP) {
1027 n_alert(_("Cannot open current directory"));
1028 goto jleave;
1031 saveint = safe_signal(SIGINT, SIG_IGN);
1033 if (chdir(mailname) == -1) {
1034 n_err(_("Cannot change directory to %s\n"),
1035 n_shexp_quote_cp(mailname, FAL0));
1036 cwrelse(&cw);
1037 safe_signal(SIGINT, saveint);
1038 goto jleave;
1041 if (sigsetjmp(_maildir_jmp, 1) == 0) {
1042 if (saveint != SIG_IGN)
1043 safe_signal(SIGINT, &__maildircatch_hold);
1044 maildir_update();
1047 safe_signal(SIGINT, saveint);
1049 if (cwret(&cw) == STOP)
1050 n_panic(_("Cannot change back to current directory"));
1051 cwrelse(&cw);
1052 rv = TRU1;
1053 jleave:
1054 if(hold_sigs_on)
1055 hold_sigs();
1056 NYD_LEAVE;
1057 return rv;
1060 FL enum okay
1061 maildir_append(char const *name, FILE *fp, long offset)
1063 struct n_timespec const *tsp;
1064 char *buf, *bp, *lp;
1065 size_t bufsize, buflen, cnt;
1066 off_t off1 = -1, offs;
1067 long size;
1068 int flag;
1069 enum {_NONE = 0, _INHEAD = 1<<0, _NLSEP = 1<<1} state;
1070 enum okay rv;
1071 NYD_ENTER;
1073 if ((rv = mkmaildir(name)) != OKAY)
1074 goto jleave;
1076 buf = n_alloc(bufsize = LINESIZE); /* TODO line pool; signals */
1077 buflen = 0;
1078 cnt = fsize(fp);
1079 offs = offset /* BSD will move due to O_APPEND! ftell(fp) */;
1080 size = 0;
1081 tsp = n_time_now(TRU1); /* TODO -> eventloop */
1083 n_autorec_relax_create();
1084 for (flag = MNEW, state = _NLSEP;;) {
1085 bp = fgetline(&buf, &bufsize, &cnt, &buflen, fp, 1);
1087 if (bp == NULL ||
1088 ((state & (_INHEAD | _NLSEP)) == _NLSEP &&
1089 is_head(buf, buflen, FAL0))) {
1090 if (off1 != (off_t)-1) {
1091 if ((rv = maildir_append1(tsp, name, fp, off1, size, flag)) == STOP)
1092 goto jfree;
1093 n_autorec_relax_unroll();
1094 if (fseek(fp, offs + buflen, SEEK_SET) == -1) {
1095 rv = STOP;
1096 goto jfree;
1099 off1 = offs + buflen;
1100 size = 0;
1101 state = _INHEAD;
1102 flag = MNEW;
1104 if (bp == NULL)
1105 break;
1106 } else
1107 size += buflen;
1108 offs += buflen;
1110 state &= ~_NLSEP;
1111 if (buf[0] == '\n') {
1112 state &= ~_INHEAD;
1113 state |= _NLSEP;
1114 } else if (state & _INHEAD) {
1115 if (!ascncasecmp(buf, "status", 6)) {
1116 lp = buf + 6;
1117 while (whitechar(*lp))
1118 ++lp;
1119 if (*lp == ':')
1120 while (*++lp != '\0')
1121 switch (*lp) {
1122 case 'R':
1123 flag |= MREAD;
1124 break;
1125 case 'O':
1126 flag &= ~MNEW;
1127 break;
1129 } else if (!ascncasecmp(buf, "x-status", 8)) {
1130 lp = buf + 8;
1131 while (whitechar(*lp))
1132 ++lp;
1133 if (*lp == ':') {
1134 while (*++lp != '\0')
1135 switch (*lp) {
1136 case 'F':
1137 flag |= MFLAGGED;
1138 break;
1139 case 'A':
1140 flag |= MANSWERED;
1141 break;
1142 case 'T':
1143 flag |= MDRAFTED;
1144 break;
1150 assert(rv == OKAY);
1151 jfree:
1152 n_autorec_relax_gut();
1153 n_free(buf);
1154 jleave:
1155 NYD_LEAVE;
1156 return rv;
1159 FL enum okay
1160 maildir_remove(char const *name)
1162 enum okay rv = STOP;
1163 NYD_ENTER;
1165 if (subdir_remove(name, "tmp") == STOP ||
1166 subdir_remove(name, "new") == STOP ||
1167 subdir_remove(name, "cur") == STOP)
1168 goto jleave;
1169 if (rmdir(name) == -1) {
1170 n_perr(name, 0);
1171 goto jleave;
1173 rv = OKAY;
1174 jleave:
1175 NYD_LEAVE;
1176 return rv;
1178 #endif /* HAVE_MAILDIR */
1180 /* s-it-mode */