srealloc() -> n_realloc()
[s-mailx.git] / maildir.c
blob8de06dcb1ad2949b9948371e07e49f8a54138da1
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 #include <dirent.h>
49 /* a_maildir_tbl should be a hash-indexed array of trees! */
50 static struct message **a_maildir_tbl, **a_maildir_tbl_top;
51 static ui32_t a_maildir_tbl_prime, a_maildir_tbl_maxdist;
52 static sigjmp_buf _maildir_jmp;
54 static void __maildircatch(int s);
55 static void __maildircatch_hold(int s);
57 /* Do some cleanup in the tmp/ subdir */
58 static void _cleantmp(void);
60 static int _maildir_setfile1(char const *name, enum fedit_mode fm,
61 int omsgCount);
63 static int a_maildir_cmp(void const *a, void const *b);
65 static int _maildir_subdir(char const *name, char const *sub,
66 enum fedit_mode fm);
68 static void _maildir_append(char const *name, char const *sub,
69 char const *fn);
71 static void readin(char const *name, struct message *m);
73 static void maildir_update(void);
75 static void _maildir_move(struct n_timespec const *tsp,
76 struct message *m);
78 static char * mkname(struct n_timespec const *tsp, enum mflag f,
79 char const *pref);
81 static enum okay maildir_append1(struct n_timespec const *tsp,
82 char const *name, FILE *fp, off_t off1,
83 long size, enum mflag flag);
85 static enum okay trycreate(char const *name);
87 static enum okay mkmaildir(char const *name);
89 static struct message * mdlook(char const *name, struct message *data);
91 static void mktable(void);
93 static enum okay subdir_remove(char const *name, char const *sub);
95 static void
96 __maildircatch(int s)
98 NYD_X; /* Signal handler */
99 siglongjmp(_maildir_jmp, s);
102 static void
103 __maildircatch_hold(int s)
105 NYD_X; /* Signal handler */
106 n_UNUSED(s);
107 /* TODO no STDIO in signal handler, no _() tr's -- pre-translate interrupt
108 * TODO globally; */
109 n_err_sighdl(_("\nImportant operation in progress: "
110 "interrupt again to forcefully abort\n"));
111 safe_signal(SIGINT, &__maildircatch);
114 static void
115 _cleantmp(void)
117 struct stat st;
118 struct n_string s, *sp;
119 si64_t now;
120 DIR *dirp;
121 struct dirent *dp;
122 NYD_ENTER;
124 if ((dirp = opendir("tmp")) == NULL)
125 goto jleave;
127 now = n_time_now(FAL0)->ts_sec;
128 sp = n_string_creat_auto(&s);
130 while ((dp = readdir(dirp)) != NULL) {
131 if (dp->d_name[0] == '.')
132 continue;
134 sp = n_string_trunc(sp, 0);
135 sp = n_string_push_buf(sp, "tmp/", sizeof("tmp/") -1);
136 sp = n_string_push_cp(sp, dp->d_name);
137 if (stat(n_string_cp(sp), &st) == -1)
138 continue;
139 if (st.st_atime + 36*3600 < now)
140 unlink(sp->s_dat);
142 closedir(dirp);
143 jleave:
144 NYD_LEAVE;
147 static int
148 _maildir_setfile1(char const *name, enum fedit_mode fm, int omsgCount)
150 int i;
151 NYD_ENTER;
153 if (!(fm & FEDIT_NEWMAIL))
154 _cleantmp();
156 mb.mb_perm = ((n_poption & n_PO_R_FLAG) || (fm & FEDIT_RDONLY))
157 ? 0 : MB_DELE;
158 if ((i = _maildir_subdir(name, "cur", fm)) != 0)
159 goto jleave;
160 if ((i = _maildir_subdir(name, "new", fm)) != 0)
161 goto jleave;
162 _maildir_append(name, NULL, NULL);
164 n_autorec_relax_create();
165 for (i = ((fm & FEDIT_NEWMAIL) ? omsgCount : 0); i < msgCount; ++i) {
166 readin(name, message + i);
167 n_autorec_relax_unroll();
169 n_autorec_relax_gut();
171 if (fm & FEDIT_NEWMAIL) {
172 if (msgCount > omsgCount)
173 qsort(&message[omsgCount], msgCount - omsgCount, sizeof *message,
174 &a_maildir_cmp);
175 } else if (msgCount)
176 qsort(message, msgCount, sizeof *message, &a_maildir_cmp);
177 i = msgCount;
178 jleave:
179 NYD_LEAVE;
180 return i;
183 static int
184 a_maildir_cmp(void const *xa, void const *xb){
185 char const *cpa, *cpa_pid, *cpb, *cpb_pid;
186 union {struct message const *mp; char const *cp;} a, b;
187 si64_t at, bt;
188 int rv;
189 NYD2_ENTER;
191 a.mp = xa;
192 b.mp = xb;
194 /* We could have parsed the time somewhen in the past, do a quick shot */
195 at = (si64_t)a.mp->m_time;
196 bt = (si64_t)b.mp->m_time;
197 if(at != 0 && bt != 0 && (at -= bt) != 0)
198 goto jret;
200 /* Otherwise we need to parse the name */
201 a.cp = &a.mp->m_maildir_file[4];
202 b.cp = &b.mp->m_maildir_file[4];
204 /* Interpret time stored in name, and use it for comparison */
205 if(((n_idec_si64_cp(&at, a.cp, 10, &cpa)
206 ) & n_IDEC_STATE_EMASK) != n_IDEC_STATE_EBASE || *cpa != '.' ||
207 a.cp == cpa)
208 goto jm1; /* Fishy */
209 if(((n_idec_si64_cp(&bt, b.cp, 10, &cpb)
210 ) & n_IDEC_STATE_EMASK) != n_IDEC_STATE_EBASE || *cpb != '.' ||
211 b.cp == cpb)
212 goto j1; /* Fishy */
214 if((at -= bt) != 0)
215 goto jret;
217 /* If the seconds part does not work, go deeper.
218 * We use de-facto standard "maildir — E-mail directory" from the Courier
219 * mail server, also used by, e.g., Dovecot: sec.MusecPpid.hostname:2,flags.
220 * However, a different name convention exists which uses
221 * sec.pid_counter.hostname:2,flags.
222 * First go for usec/counter, then pid */
224 /* A: exact "standard"? */
225 cpa_pid = NULL;
226 a.cp = ++cpa;
227 if((rv = *a.cp) == 'M')
229 /* Known compat? */
230 else if(digitchar(rv)){
231 cpa_pid = a.cp++;
232 while((rv = *a.cp) != '\0' && rv != '_')
233 ++a.cp;
234 if(rv == '\0')
235 goto jm1; /* Fishy */
237 /* This is compatible to what dovecot does, it surely does not do so
238 * for nothing, but i have no idea, but am too stupid to ask */
239 else for(;; rv = *++a.cp){
240 if(rv == 'M')
241 break;
242 if(rv == '\0' || rv == '.' || rv == n_MAILDIR_SEPARATOR)
243 goto jm1; /* Fishy */
245 ++a.cp;
246 if(((n_idec_si64_cp(&at, a.cp, 10, &cpa)
247 ) & n_IDEC_STATE_EMASK) != n_IDEC_STATE_EBASE)
248 goto jm1; /* Fishy */
250 /* B: as above */
251 cpb_pid = NULL;
252 b.cp = ++cpb;
253 if((rv = *b.cp) == 'M')
255 else if(digitchar(rv)){
256 cpb_pid = b.cp++;
257 while((rv = *b.cp) != '\0' && rv != '_')
258 ++b.cp;
259 if(rv == '\0')
260 goto j1;
261 }else for(;; rv = *++b.cp){
262 if(rv == 'M')
263 break;
264 if(rv == '\0' || rv == '.' || rv == n_MAILDIR_SEPARATOR)
265 goto jm1;
267 ++b.cp;
268 if(((n_idec_si64_cp(&bt, b.cp, 10, &cpb)
269 ) & n_IDEC_STATE_EMASK) != n_IDEC_STATE_EBASE)
270 goto j1;
272 if((at -= bt) != 0)
273 goto jret;
275 /* So this gets hairy: sort by PID, then hostname */
276 if(cpa_pid != NULL){
277 a.cp = cpa_pid;
278 xa = cpa;
279 }else{
280 a.cp = cpa;
281 if(*a.cp++ != 'P')
282 goto jm1; /* Fishy */
284 if(((n_idec_si64_cp(&at, a.cp, 10, &cpa)
285 ) & n_IDEC_STATE_EMASK) != n_IDEC_STATE_EBASE)
286 goto jm1; /* Fishy */
288 if(cpb_pid != NULL){
289 b.cp = cpb_pid;
290 xb = cpb;
291 }else{
292 b.cp = cpb;
293 if(*b.cp++ != 'P')
294 goto j1; /* Fishy */
296 if(((n_idec_si64_cp(&bt, b.cp, 10, &cpb)
297 ) & n_IDEC_STATE_EMASK) != n_IDEC_STATE_EBASE)
298 goto jm1; /* Fishy */
300 if((at -= bt) != 0)
301 goto jret;
303 /* Hostname */
304 a.cp = (cpa_pid != NULL) ? xa : cpa;
305 b.cp = (cpb_pid != NULL) ? xb : cpb;
306 for(;; ++a.cp, ++b.cp){
307 char ac, bc;
309 ac = *a.cp;
310 at = (ac != '\0' && ac != n_MAILDIR_SEPARATOR);
311 bc = *b.cp;
312 bt = (bc != '\0' && bc != n_MAILDIR_SEPARATOR);
313 if((at -= bt) != 0)
314 break;
315 at = ac;
316 if((at -= bc) != 0)
317 break;
318 if(ac == '\0')
319 break;
322 jret:
323 rv = (at == 0 ? 0 : (at < 0 ? -1 : 1));
324 jleave:
325 NYD2_LEAVE;
326 return rv;
327 jm1:
328 rv = -1;
329 goto jleave;
331 rv = 1;
332 goto jleave;
335 static int
336 _maildir_subdir(char const *name, char const *sub, enum fedit_mode fm)
338 DIR *dirp;
339 struct dirent *dp;
340 int rv;
341 NYD_ENTER;
343 if ((dirp = opendir(sub)) == NULL) {
344 n_err(_("Cannot open directory %s\n"),
345 n_shexp_quote_cp(savecatsep(name, '/', sub), FAL0));
346 rv = -1;
347 goto jleave;
349 if (access(sub, W_OK) == -1)
350 mb.mb_perm = 0;
351 while ((dp = readdir(dirp)) != NULL) {
352 if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' ||
353 (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
354 continue;
355 if (dp->d_name[0] == '.')
356 continue;
357 if (!(fm & FEDIT_NEWMAIL) || mdlook(dp->d_name, NULL) == NULL)
358 _maildir_append(name, sub, dp->d_name);
360 closedir(dirp);
361 rv = 0;
362 jleave:
363 NYD_LEAVE;
364 return rv;
367 static void
368 _maildir_append(char const *name, char const *sub, char const *fn)
370 struct message *m;
371 time_t t = 0;
372 enum mflag f = MUSED | MNOFROM | MNEWEST;
373 char const *cp, *xp;
374 NYD_ENTER;
375 n_UNUSED(name);
377 if (fn != NULL && sub != NULL) {
378 if (!strcmp(sub, "new"))
379 f |= MNEW;
381 /* C99 */{
382 si64_t tib;
384 (void)/*TODO*/n_idec_si64_cp(&tib, fn, 10, &xp);
385 t = (time_t)tib;
388 if ((cp = strrchr(xp, ',')) != NULL && PTRCMP(cp, >, xp + 2) &&
389 cp[-1] == '2' && cp[-2] == n_MAILDIR_SEPARATOR) {
390 while (*++cp != '\0') {
391 switch (*cp) {
392 case 'F':
393 f |= MFLAGGED;
394 break;
395 case 'R':
396 f |= MANSWERED;
397 break;
398 case 'S':
399 f |= MREAD;
400 break;
401 case 'T':
402 f |= MDELETED;
403 break;
404 case 'D':
405 f |= MDRAFT;
406 break;
412 /* Ensure room (and a NULLified last entry) */
413 ++msgCount;
414 message_append(NULL);
415 --msgCount;
417 if (fn == NULL || sub == NULL)
418 goto jleave;
420 m = &message[msgCount++];
421 /* C99 */{
422 char *tmp;
423 size_t sz, i;
425 i = strlen(fn) +1;
426 sz = strlen(sub);
427 m->m_maildir_file = tmp = n_alloc(sz + 1 + i);
428 memcpy(tmp, sub, sz);
429 tmp[sz++] = '/';
430 memcpy(&tmp[sz], fn, i);
432 m->m_time = t;
433 m->m_flag = f;
434 m->m_maildir_hash = n_torek_hash(fn);
435 jleave:
436 NYD_LEAVE;
437 return;
440 static void
441 readin(char const *name, struct message *m)
443 char *buf;
444 size_t bufsize, buflen, cnt;
445 long size = 0, lines = 0;
446 off_t offset;
447 FILE *fp;
448 int emptyline = 0;
449 NYD_ENTER;
451 if ((fp = Fopen(m->m_maildir_file, "r")) == NULL) {
452 n_err(_("Cannot read %s for message %lu\n"),
453 n_shexp_quote_cp(savecatsep(name, '/', m->m_maildir_file), FAL0),
454 (ul_i)PTR2SIZE(m - message + 1));
455 m->m_flag |= MHIDDEN;
456 goto jleave;
459 cnt = fsize(fp);
460 fseek(mb.mb_otf, 0L, SEEK_END);
461 offset = ftell(mb.mb_otf);
462 buf = n_alloc(bufsize = LINESIZE);
463 buflen = 0;
464 while (fgetline(&buf, &bufsize, &cnt, &buflen, fp, 1) != NULL) {
465 /* Since we simply copy over data without doing any transfer
466 * encoding reclassification/adjustment we *have* to perform
467 * RFC 4155 compliant From_ quoting here */
468 if (emptyline && is_head(buf, buflen, FAL0)) {
469 putc('>', mb.mb_otf);
470 ++size;
472 size += fwrite(buf, 1, buflen, mb.mb_otf);/*XXX err hdling*/
473 emptyline = (*buf == '\n');
474 ++lines;
476 n_free(buf);
477 if (!emptyline) {
478 /* TODO we need \n\n for mbox format.
479 * TODO That is to say we do it wrong here in order to get it right
480 * TODO when send.c stuff or with MBOX handling, even though THIS
481 * TODO line is solely a property of the MBOX database format! */
482 putc('\n', mb.mb_otf);
483 ++lines;
484 ++size;
487 Fclose(fp);
488 fflush(mb.mb_otf);
489 m->m_size = m->m_xsize = size;
490 m->m_lines = m->m_xlines = lines;
491 m->m_block = mailx_blockof(offset);
492 m->m_offset = mailx_offsetof(offset);
493 substdate(m);
494 jleave:
495 NYD_LEAVE;
498 static void
499 maildir_update(void)
501 struct message *m;
502 struct n_timespec const *tsp;
503 int dodel, c, gotcha = 0, held = 0, modflags = 0;
504 NYD_ENTER;
506 if (mb.mb_perm == 0)
507 goto jfree;
509 if (!(n_pstate & n_PS_EDIT)) {
510 holdbits();
511 for (m = message, c = 0; PTRCMP(m, <, message + msgCount); ++m) {
512 if (m->m_flag & MBOX)
513 c++;
515 if (c > 0)
516 if (makembox() == STOP)
517 goto jbypass;
520 tsp = n_time_now(TRU1); /* TODO FAL0, eventloop update! */
522 n_autorec_relax_create();
523 for (m = message, gotcha = 0, held = 0; PTRCMP(m, <, message + msgCount);
524 ++m) {
525 if (n_pstate & n_PS_EDIT)
526 dodel = m->m_flag & MDELETED;
527 else
528 dodel = !((m->m_flag & MPRESERVE) || !(m->m_flag & MTOUCH));
529 if (dodel) {
530 if (unlink(m->m_maildir_file) < 0)
531 n_err(_("Cannot delete file %s for message %lu\n"),
532 n_shexp_quote_cp(savecatsep(mailname, '/', m->m_maildir_file),
533 FAL0), (ul_i)PTR2SIZE(m - message + 1));
534 else
535 ++gotcha;
536 } else {
537 if ((m->m_flag & (MREAD | MSTATUS)) == (MREAD | MSTATUS) ||
538 (m->m_flag & (MNEW | MBOXED | MSAVED | MSTATUS | MFLAG |
539 MUNFLAG | MANSWER | MUNANSWER | MDRAFT | MUNDRAFT))) {
540 _maildir_move(tsp, m);
541 n_autorec_relax_unroll();
542 ++modflags;
544 ++held;
547 n_autorec_relax_gut();
549 jbypass:
550 if ((gotcha || modflags) && (n_pstate & n_PS_EDIT)) {
551 fprintf(n_stdout, "%s %s\n",
552 n_shexp_quote_cp(displayname, FAL0),
553 ((ok_blook(bsdcompat) || ok_blook(bsdmsgs))
554 ? _("complete") : _("updated.")));
555 } else if (held && !(n_pstate & n_PS_EDIT) && mb.mb_perm != 0) {
556 if (held == 1)
557 fprintf(n_stdout, _("Held 1 message in %s\n"), displayname);
558 else
559 fprintf(n_stdout, _("Held %d messages in %s\n"), held, displayname);
561 fflush(n_stdout);
562 jfree:
563 for (m = message; PTRCMP(m, <, message + msgCount); ++m)
564 n_free(n_UNCONST(m->m_maildir_file));
565 NYD_LEAVE;
568 static void
569 _maildir_move(struct n_timespec const *tsp, struct message *m)
571 char *fn, *newfn;
572 NYD_ENTER;
574 fn = mkname(tsp, m->m_flag, m->m_maildir_file + 4);
575 newfn = savecat("cur/", fn);
576 if (!strcmp(m->m_maildir_file, newfn))
577 goto jleave;
578 if (link(m->m_maildir_file, newfn) == -1) {
579 n_err(_("Cannot link %s to %s: message %lu not touched\n"),
580 n_shexp_quote_cp(savecatsep(mailname, '/', m->m_maildir_file), FAL0),
581 n_shexp_quote_cp(savecatsep(mailname, '/', newfn), FAL0),
582 (ul_i)PTR2SIZE(m - message + 1));
583 goto jleave;
585 if (unlink(m->m_maildir_file) == -1)
586 n_err(_("Cannot unlink %s\n"),
587 n_shexp_quote_cp(savecatsep(mailname, '/', m->m_maildir_file), FAL0));
588 jleave:
589 NYD_LEAVE;
592 static char *
593 mkname(struct n_timespec const *tsp, enum mflag f, char const *pref)
595 static char *node;
596 static struct n_timespec ts;
598 char *cp;
599 int size, n, i;
600 NYD_ENTER;
602 if (pref == NULL) {
603 si64_t s;
605 if(n_pid == 0)
606 n_pid = getpid();
608 if (node == NULL) {
609 cp = n_nodename(FAL0);
610 n = size = 0;
611 do {
612 if (UICMP(32, n, <, size + 8))
613 node = n_realloc(node, size += 20);
614 switch (*cp) {
615 case '/':
616 node[n++] = '\\', node[n++] = '0',
617 node[n++] = '5', node[n++] = '7';
618 break;
619 case ':':
620 node[n++] = '\\', node[n++] = '0',
621 node[n++] = '7', node[n++] = '2';
622 break;
623 default:
624 node[n++] = *cp;
626 } while (*cp++ != '\0');
629 /* Problem: Courier spec uses microseconds, not nanoseconds */
630 if((s = tsp->ts_sec) > ts.ts_sec){
631 ts.ts_sec = s;
632 ts.ts_nsec = tsp->ts_nsec / (n_DATE_NANOSSEC / n_DATE_MICROSSEC);
633 }else{
634 s = tsp->ts_nsec / (n_DATE_NANOSSEC / n_DATE_MICROSSEC);
635 if(s <= ts.ts_nsec)
636 s = ts.ts_nsec + 1;
637 if(s < n_DATE_MICROSSEC)
638 ts.ts_nsec = s;
639 else{
640 ++ts.ts_sec;
641 ts.ts_nsec = 0;
645 /* Create a name according to Courier spec */
646 size = 60 + strlen(node);
647 cp = n_autorec_alloc(size);
648 n = snprintf(cp, size, "%" PRId64 ".M%" PRIdZ "P%ld.%s:2,",
649 ts.ts_sec, ts.ts_nsec, (long)n_pid, node);
650 } else {
651 size = (n = strlen(pref)) + 13;
652 cp = salloc(size);
653 memcpy(cp, pref, n +1);
654 for (i = n; i > 3; --i)
655 if (cp[i - 1] == ',' && cp[i - 2] == '2' &&
656 cp[i - 3] == n_MAILDIR_SEPARATOR) {
657 n = i;
658 break;
660 if (i <= 3) {
661 memcpy(cp + n, ":2,", 3 +1);
662 n += 3;
665 if (n < size - 7) {
666 if (f & MDRAFTED)
667 cp[n++] = 'D';
668 if (f & MFLAGGED)
669 cp[n++] = 'F';
670 if (f & MANSWERED)
671 cp[n++] = 'R';
672 if (f & MREAD)
673 cp[n++] = 'S';
674 if (f & MDELETED)
675 cp[n++] = 'T';
676 cp[n] = '\0';
678 NYD_LEAVE;
679 return cp;
682 static enum okay
683 maildir_append1(struct n_timespec const *tsp, char const *name, FILE *fp,
684 off_t off1, long size, enum mflag flag)
686 char buf[4096], *fn, *tfn, *nfn;
687 struct stat st;
688 FILE *op;
689 size_t nlen, flen, n;
690 enum okay rv = STOP;
691 NYD_ENTER;
693 nlen = strlen(name);
695 /* Create a unique temporary file */
696 for (nfn = (char*)0xA /* XXX no magic */;; n_msleep(500, FAL0)) {
697 flen = strlen(fn = mkname(tsp, flag, NULL));
698 tfn = n_autorec_alloc(n = nlen + flen + 6);
699 snprintf(tfn, n, "%s/tmp/%s", name, fn);
701 /* Use "wx" for O_EXCL XXX stat(2) rather redundant; coverity:TOCTOU */
702 if ((!stat(tfn, &st) || n_err_no == n_ERR_NOENT) &&
703 (op = Fopen(tfn, "wx")) != NULL)
704 break;
706 nfn = (char*)(PTR2SIZE(nfn) - 1);
707 if (nfn == NULL) {
708 n_err(_("Can't create an unique file name in %s\n"),
709 n_shexp_quote_cp(savecat(name, "/tmp"), FAL0));
710 goto jleave;
714 if (fseek(fp, off1, SEEK_SET) == -1)
715 goto jtmperr;
716 while (size > 0) {
717 size_t z = UICMP(z, size, >, sizeof buf) ? sizeof buf : (size_t)size;
719 if (z != (n = fread(buf, 1, z, fp)) || n != fwrite(buf, 1, n, op)) {
720 jtmperr:
721 n_err(_("Error writing to %s\n"), n_shexp_quote_cp(tfn, FAL0));
722 Fclose(op);
723 goto jerr;
725 size -= n;
727 Fclose(op);
729 nfn = n_autorec_alloc(n = nlen + flen + 6);
730 snprintf(nfn, n, "%s/new/%s", name, fn);
731 if (link(tfn, nfn) == -1) {
732 n_err(_("Cannot link %s to %s\n"), n_shexp_quote_cp(tfn, FAL0),
733 n_shexp_quote_cp(nfn, FAL0));
734 goto jerr;
736 rv = OKAY;
737 jerr:
738 if (unlink(tfn) == -1)
739 n_err(_("Cannot unlink %s\n"), n_shexp_quote_cp(tfn, FAL0));
740 jleave:
741 NYD_LEAVE;
742 return rv;
745 static enum okay
746 trycreate(char const *name)
748 struct stat st;
749 enum okay rv = STOP;
750 NYD_ENTER;
752 if (!stat(name, &st)) {
753 if (!S_ISDIR(st.st_mode)) {
754 n_err(_("%s is not a directory\n"), n_shexp_quote_cp(name, FAL0));
755 goto jleave;
757 } else if (!n_path_mkdir(name)) {
758 n_err(_("Cannot create directory %s\n"), n_shexp_quote_cp(name, FAL0));
759 goto jleave;
761 rv = OKAY;
762 jleave:
763 NYD_LEAVE;
764 return rv;
767 static enum okay
768 mkmaildir(char const *name) /* TODO proper cleanup on error; use path[] loop */
770 char *np;
771 size_t sz;
772 enum okay rv = STOP;
773 NYD_ENTER;
775 if (trycreate(name) == OKAY) {
776 np = ac_alloc((sz = strlen(name)) + 4 +1);
777 memcpy(np, name, sz);
778 memcpy(np + sz, "/tmp", 4 +1);
779 if (trycreate(np) == OKAY) {
780 memcpy(np + sz, "/new", 4 +1);
781 if (trycreate(np) == OKAY) {
782 memcpy(np + sz, "/cur", 4 +1);
783 rv = trycreate(np);
786 ac_free(np);
788 NYD_LEAVE;
789 return rv;
792 static struct message *
793 mdlook(char const *name, struct message *data)
795 struct message **mpp, *mp;
796 ui32_t h, i;
797 NYD_ENTER;
799 if(data != NULL)
800 i = data->m_maildir_hash;
801 else
802 i = n_torek_hash(name);
803 h = i;
804 mpp = &a_maildir_tbl[i %= a_maildir_tbl_prime];
806 for(i = 0;;){
807 if((mp = *mpp) == NULL){
808 if(n_UNLIKELY(data != NULL)){
809 *mpp = mp = data;
810 if(i > a_maildir_tbl_maxdist)
811 a_maildir_tbl_maxdist = i;
813 break;
814 }else if(mp->m_maildir_hash == h && !strcmp(&mp->m_maildir_file[4], name))
815 break;
817 if(n_UNLIKELY(mpp++ == a_maildir_tbl_top))
818 mpp = a_maildir_tbl;
819 if(++i > a_maildir_tbl_maxdist && n_UNLIKELY(data == NULL)){
820 mp = NULL;
821 break;
824 NYD_LEAVE;
825 return mp;
828 static void
829 mktable(void)
831 struct message *mp;
832 size_t i;
833 NYD_ENTER;
835 i = a_maildir_tbl_prime = msgCount;
836 i <<= 1;
838 a_maildir_tbl_prime = n_prime_next(a_maildir_tbl_prime);
839 while(a_maildir_tbl_prime < i);
840 a_maildir_tbl = n_calloc(a_maildir_tbl_prime, sizeof *a_maildir_tbl);
841 a_maildir_tbl_top = &a_maildir_tbl[a_maildir_tbl_prime - 1];
842 a_maildir_tbl_maxdist = 0;
843 for(mp = message, i = msgCount; i-- != 0; ++mp)
844 mdlook(&mp->m_maildir_file[4], mp);
845 NYD_LEAVE;
848 static enum okay
849 subdir_remove(char const *name, char const *sub)
851 char *path;
852 int pathsize, pathend, namelen, sublen, n;
853 DIR *dirp;
854 struct dirent *dp;
855 enum okay rv = STOP;
856 NYD_ENTER;
858 namelen = strlen(name);
859 sublen = strlen(sub);
860 path = n_alloc(pathsize = namelen + sublen + 30 +1);
861 memcpy(path, name, namelen);
862 path[namelen] = '/';
863 memcpy(path + namelen + 1, sub, sublen);
864 path[namelen + sublen + 1] = '/';
865 path[pathend = namelen + sublen + 2] = '\0';
867 if ((dirp = opendir(path)) == NULL) {
868 n_perr(path, 0);
869 goto jleave;
871 while ((dp = readdir(dirp)) != NULL) {
872 if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' ||
873 (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
874 continue;
875 if (dp->d_name[0] == '.')
876 continue;
877 n = strlen(dp->d_name);
878 if (UICMP(32, pathend + n +1, >, pathsize))
879 path = n_realloc(path, pathsize = pathend + n + 30);
880 memcpy(path + pathend, dp->d_name, n +1);
881 if (unlink(path) == -1) {
882 n_perr(path, 0);
883 closedir(dirp);
884 goto jleave;
887 closedir(dirp);
889 path[pathend] = '\0';
890 if (rmdir(path) == -1) {
891 n_perr(path, 0);
892 goto jleave;
894 rv = OKAY;
895 jleave:
896 n_free(path);
897 NYD_LEAVE;
898 return rv;
901 FL int
902 maildir_setfile(char const * volatile name, enum fedit_mode fm)
904 sighandler_type volatile saveint;
905 struct cw cw;
906 char const *emsg;
907 int omsgCount;
908 int volatile i = -1;
909 NYD_ENTER;
911 omsgCount = msgCount;
912 if (cwget(&cw) == STOP) {
913 n_alert(_("Cannot open current directory"));
914 goto jleave;
917 if (!(fm & FEDIT_NEWMAIL) && !quit(FAL0))
918 goto jleave;
920 saveint = safe_signal(SIGINT, SIG_IGN);
922 if (!(fm & FEDIT_NEWMAIL)) {
923 if (fm & FEDIT_SYSBOX)
924 n_pstate &= ~n_PS_EDIT;
925 else
926 n_pstate |= n_PS_EDIT;
927 if (mb.mb_itf) {
928 fclose(mb.mb_itf);
929 mb.mb_itf = NULL;
931 if (mb.mb_otf) {
932 fclose(mb.mb_otf);
933 mb.mb_otf = NULL;
935 initbox(name);
936 mb.mb_type = MB_MAILDIR;
939 if(!n_is_dir(name, FAL0)){
940 emsg = N_("Not a maildir: %s\n");
941 goto jerr;
942 }else if(chdir(name) < 0){
943 emsg = N_("Cannot enter maildir://%s\n");
944 jerr:
945 n_err(V_(emsg), n_shexp_quote_cp(name, FAL0));
946 mb.mb_type = MB_VOID;
947 *mailname = '\0';
948 msgCount = 0;
949 cwrelse(&cw);
950 safe_signal(SIGINT, saveint);
951 goto jleave;
954 a_maildir_tbl = NULL;
955 if (sigsetjmp(_maildir_jmp, 1) == 0) {
956 if (fm & FEDIT_NEWMAIL)
957 mktable();
958 if (saveint != SIG_IGN)
959 safe_signal(SIGINT, &__maildircatch);
960 i = _maildir_setfile1(name, fm, omsgCount);
962 if ((fm & FEDIT_NEWMAIL) && a_maildir_tbl != NULL)
963 n_free(a_maildir_tbl);
965 safe_signal(SIGINT, saveint);
967 if (i < 0) {
968 mb.mb_type = MB_VOID;
969 *mailname = '\0';
970 msgCount = 0;
973 if (cwret(&cw) == STOP)
974 n_panic(_("Cannot change back to current directory"));
975 cwrelse(&cw);
977 setmsize(msgCount);
978 if ((fm & FEDIT_NEWMAIL) && mb.mb_sorted && msgCount > omsgCount) {
979 mb.mb_threaded = 0;
980 c_sort((void*)-1);
983 if (!(fm & FEDIT_NEWMAIL)) {
984 n_pstate &= ~n_PS_SAW_COMMAND;
985 n_pstate |= n_PS_SETFILE_OPENED;
988 if ((n_poption & n_PO_EXISTONLY) && !(n_poption & n_PO_HEADERLIST)) {
989 i = (msgCount == 0);
990 goto jleave;
993 if (!(fm & FEDIT_NEWMAIL) && (fm & FEDIT_SYSBOX) && msgCount == 0) {
994 if (mb.mb_type == MB_MAILDIR /* XXX ?? */ && !ok_blook(emptystart))
995 n_err(_("No mail at %s\n"), n_shexp_quote_cp(name, FAL0));
996 i = 1;
997 goto jleave;
1000 if ((fm & FEDIT_NEWMAIL) && msgCount > omsgCount)
1001 newmailinfo(omsgCount);
1002 i = 0;
1003 jleave:
1004 NYD_LEAVE;
1005 return i;
1008 FL bool_t
1009 maildir_quit(bool_t hold_sigs_on)
1011 sighandler_type saveint;
1012 struct cw cw;
1013 bool_t rv;
1014 NYD_ENTER;
1016 if(hold_sigs_on)
1017 rele_sigs();
1019 rv = FAL0;
1021 if (cwget(&cw) == STOP) {
1022 n_alert(_("Cannot open current directory"));
1023 goto jleave;
1026 saveint = safe_signal(SIGINT, SIG_IGN);
1028 if (chdir(mailname) == -1) {
1029 n_err(_("Cannot change directory to %s\n"),
1030 n_shexp_quote_cp(mailname, FAL0));
1031 cwrelse(&cw);
1032 safe_signal(SIGINT, saveint);
1033 goto jleave;
1036 if (sigsetjmp(_maildir_jmp, 1) == 0) {
1037 if (saveint != SIG_IGN)
1038 safe_signal(SIGINT, &__maildircatch_hold);
1039 maildir_update();
1042 safe_signal(SIGINT, saveint);
1044 if (cwret(&cw) == STOP)
1045 n_panic(_("Cannot change back to current directory"));
1046 cwrelse(&cw);
1047 rv = TRU1;
1048 jleave:
1049 if(hold_sigs_on)
1050 hold_sigs();
1051 NYD_LEAVE;
1052 return rv;
1055 FL enum okay
1056 maildir_append(char const *name, FILE *fp, long offset)
1058 struct n_timespec const *tsp;
1059 char *buf, *bp, *lp;
1060 size_t bufsize, buflen, cnt;
1061 off_t off1 = -1, offs;
1062 long size;
1063 int flag;
1064 enum {_NONE = 0, _INHEAD = 1<<0, _NLSEP = 1<<1} state;
1065 enum okay rv;
1066 NYD_ENTER;
1068 if ((rv = mkmaildir(name)) != OKAY)
1069 goto jleave;
1071 buf = n_alloc(bufsize = LINESIZE); /* TODO line pool; signals */
1072 buflen = 0;
1073 cnt = fsize(fp);
1074 offs = offset /* BSD will move due to O_APPEND! ftell(fp) */;
1075 size = 0;
1076 tsp = n_time_now(TRU1); /* TODO -> eventloop */
1078 n_autorec_relax_create();
1079 for (flag = MNEW, state = _NLSEP;;) {
1080 bp = fgetline(&buf, &bufsize, &cnt, &buflen, fp, 1);
1082 if (bp == NULL ||
1083 ((state & (_INHEAD | _NLSEP)) == _NLSEP &&
1084 is_head(buf, buflen, FAL0))) {
1085 if (off1 != (off_t)-1) {
1086 if ((rv = maildir_append1(tsp, name, fp, off1, size, flag)) == STOP)
1087 goto jfree;
1088 n_autorec_relax_unroll();
1089 if (fseek(fp, offs + buflen, SEEK_SET) == -1) {
1090 rv = STOP;
1091 goto jfree;
1094 off1 = offs + buflen;
1095 size = 0;
1096 state = _INHEAD;
1097 flag = MNEW;
1099 if (bp == NULL)
1100 break;
1101 } else
1102 size += buflen;
1103 offs += buflen;
1105 state &= ~_NLSEP;
1106 if (buf[0] == '\n') {
1107 state &= ~_INHEAD;
1108 state |= _NLSEP;
1109 } else if (state & _INHEAD) {
1110 if (!ascncasecmp(buf, "status", 6)) {
1111 lp = buf + 6;
1112 while (whitechar(*lp))
1113 ++lp;
1114 if (*lp == ':')
1115 while (*++lp != '\0')
1116 switch (*lp) {
1117 case 'R':
1118 flag |= MREAD;
1119 break;
1120 case 'O':
1121 flag &= ~MNEW;
1122 break;
1124 } else if (!ascncasecmp(buf, "x-status", 8)) {
1125 lp = buf + 8;
1126 while (whitechar(*lp))
1127 ++lp;
1128 if (*lp == ':') {
1129 while (*++lp != '\0')
1130 switch (*lp) {
1131 case 'F':
1132 flag |= MFLAGGED;
1133 break;
1134 case 'A':
1135 flag |= MANSWERED;
1136 break;
1137 case 'T':
1138 flag |= MDRAFTED;
1139 break;
1145 assert(rv == OKAY);
1146 jfree:
1147 n_autorec_relax_gut();
1148 n_free(buf);
1149 jleave:
1150 NYD_LEAVE;
1151 return rv;
1154 FL enum okay
1155 maildir_remove(char const *name)
1157 enum okay rv = STOP;
1158 NYD_ENTER;
1160 if (subdir_remove(name, "tmp") == STOP ||
1161 subdir_remove(name, "new") == STOP ||
1162 subdir_remove(name, "cur") == STOP)
1163 goto jleave;
1164 if (rmdir(name) == -1) {
1165 n_perr(name, 0);
1166 goto jleave;
1168 rv = OKAY;
1169 jleave:
1170 NYD_LEAVE;
1171 return rv;
1174 /* s-it-mode */