nail.1: last fixes
[s-mailx.git] / maildir.c
blobebc48bbd617263d5138362a57ee1b42d31f527ca
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 - 2017 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 static struct message **a_maildir_table;
50 static ui32_t a_maildir_prime;
51 static sigjmp_buf _maildir_jmp;
53 static void __maildircatch(int s);
54 static void __maildircatch_hold(int s);
56 /* Do some cleanup in the tmp/ subdir */
57 static void _cleantmp(void);
59 static int _maildir_setfile1(char const *name, enum fedit_mode fm,
60 int omsgCount);
62 static int a_maildir_cmp(void const *a, void const *b);
64 static int _maildir_subdir(char const *name, char const *sub,
65 enum fedit_mode fm);
67 static void _maildir_append(char const *name, char const *sub,
68 char const *fn);
70 static void readin(char const *name, struct message *m);
72 static void maildir_update(void);
74 static void _maildir_move(struct n_timespec const *tsp,
75 struct message *m);
77 static char * mkname(struct n_timespec const *tsp, enum mflag f,
78 char const *pref);
80 static enum okay maildir_append1(struct n_timespec const *tsp,
81 char const *name, FILE *fp, off_t off1,
82 long size, enum mflag flag);
84 static enum okay trycreate(char const *name);
86 static enum okay mkmaildir(char const *name);
88 static struct message * mdlook(char const *name, struct message *data);
90 static void mktable(void);
92 static enum okay subdir_remove(char const *name, char const *sub);
94 static void
95 __maildircatch(int s)
97 NYD_X; /* Signal handler */
98 siglongjmp(_maildir_jmp, s);
101 static void
102 __maildircatch_hold(int s)
104 NYD_X; /* Signal handler */
105 n_UNUSED(s);
106 /* TODO no STDIO in signal handler, no _() tr's -- pre-translate interrupt
107 * TODO globally; */
108 n_err_sighdl(_("\nImportant operation in progress: "
109 "interrupt again to forcefully abort\n"));
110 safe_signal(SIGINT, &__maildircatch);
113 static void
114 _cleantmp(void)
116 struct stat st;
117 struct n_string s, *sp;
118 si64_t now;
119 DIR *dirp;
120 struct dirent *dp;
121 NYD_ENTER;
123 if ((dirp = opendir("tmp")) == NULL)
124 goto jleave;
126 now = n_time_now(FAL0)->ts_sec;
127 sp = n_string_creat_auto(&s);
129 while ((dp = readdir(dirp)) != NULL) {
130 if (dp->d_name[0] == '.')
131 continue;
133 sp = n_string_trunc(sp, 0);
134 sp = n_string_push_buf(sp, "tmp/", sizeof("tmp/") -1);
135 sp = n_string_push_cp(sp, dp->d_name);
136 if (stat(n_string_cp(sp), &st) == -1)
137 continue;
138 if (st.st_atime + 36*3600 < now)
139 unlink(sp->s_dat);
141 closedir(dirp);
142 jleave:
143 NYD_LEAVE;
146 static int
147 _maildir_setfile1(char const *name, enum fedit_mode fm, int omsgCount)
149 int i;
150 NYD_ENTER;
152 if (!(fm & FEDIT_NEWMAIL))
153 _cleantmp();
155 mb.mb_perm = ((n_poption & n_PO_R_FLAG) || (fm & FEDIT_RDONLY))
156 ? 0 : MB_DELE;
157 if ((i = _maildir_subdir(name, "cur", fm)) != 0)
158 goto jleave;
159 if ((i = _maildir_subdir(name, "new", fm)) != 0)
160 goto jleave;
161 _maildir_append(name, NULL, NULL);
163 n_autorec_relax_create();
164 for (i = ((fm & FEDIT_NEWMAIL) ? omsgCount : 0); i < msgCount; ++i) {
165 readin(name, message + i);
166 n_autorec_relax_unroll();
168 n_autorec_relax_gut();
170 if (fm & FEDIT_NEWMAIL) {
171 if (msgCount > omsgCount)
172 qsort(&message[omsgCount], msgCount - omsgCount, sizeof *message,
173 &a_maildir_cmp);
174 } else if (msgCount)
175 qsort(message, msgCount, sizeof *message, &a_maildir_cmp);
176 i = msgCount;
177 jleave:
178 NYD_LEAVE;
179 return i;
182 static int
183 a_maildir_cmp(void const *xa, void const *xb){
184 char const *cpa, *cpa_pid, *cpb, *cpb_pid;
185 union {struct message const *mp; char const *cp;} a, b;
186 si64_t at, bt;
187 int rv;
188 NYD2_ENTER;
190 a.mp = xa;
191 b.mp = xb;
193 /* We could have parsed the time somewhen in the past, do a quick shot */
194 at = (si64_t)a.mp->m_time;
195 bt = (si64_t)b.mp->m_time;
196 if(at != 0 && bt != 0 && (at -= bt) != 0)
197 goto jret;
199 /* Otherwise we need to parse the name */
200 a.cp = &a.mp->m_maildir_file[4];
201 b.cp = &b.mp->m_maildir_file[4];
203 /* Interpret time stored in name, and use it for comparison */
204 if(((n_idec_si64_cp(&at, a.cp, 10, &cpa)
205 ) & n_IDEC_STATE_EMASK) != n_IDEC_STATE_EBASE || *cpa != '.' ||
206 a.cp == cpa)
207 goto jm1; /* Fishy */
208 if(((n_idec_si64_cp(&bt, b.cp, 10, &cpb)
209 ) & n_IDEC_STATE_EMASK) != n_IDEC_STATE_EBASE || *cpb != '.' ||
210 b.cp == cpb)
211 goto j1; /* Fishy */
213 if((at -= bt) != 0)
214 goto jret;
216 /* If the seconds part does not work, go deeper.
217 * We use de-facto standard "maildir — E-mail directory" from the Courier
218 * mail server, also used by, e.g., Dovecot: sec.MusecPpid.hostname:2,flags.
219 * However, a different name convention exists which uses
220 * sec.pid_counter.hostname:2,flags.
221 * First go for usec/counter, then pid */
223 /* A: exact "standard"? */
224 cpa_pid = NULL;
225 a.cp = ++cpa;
226 if((rv = *a.cp) == 'M')
228 /* Known compat? */
229 else if(digitchar(rv)){
230 cpa_pid = a.cp++;
231 while((rv = *a.cp) != '\0' && rv != '_')
232 ++a.cp;
233 if(rv == '\0')
234 goto jm1; /* Fishy */
236 /* This is compatible to what dovecot does, it surely does not do so
237 * for nothing, but i have no idea, but am too stupid to ask */
238 else for(;; rv = *++a.cp){
239 if(rv == 'M')
240 break;
241 if(rv == '\0' || rv == '.' || rv == n_MAILDIR_SEPARATOR)
242 goto jm1; /* Fishy */
244 ++a.cp;
245 if(((n_idec_si64_cp(&at, a.cp, 10, &cpa)
246 ) & n_IDEC_STATE_EMASK) != n_IDEC_STATE_EBASE)
247 goto jm1; /* Fishy */
249 /* B: as above */
250 cpb_pid = NULL;
251 b.cp = ++cpb;
252 if((rv = *b.cp) == 'M')
254 else if(digitchar(rv)){
255 cpb_pid = b.cp++;
256 while((rv = *b.cp) != '\0' && rv != '_')
257 ++b.cp;
258 if(rv == '\0')
259 goto j1;
260 }else for(;; rv = *++b.cp){
261 if(rv == 'M')
262 break;
263 if(rv == '\0' || rv == '.' || rv == n_MAILDIR_SEPARATOR)
264 goto jm1;
266 ++b.cp;
267 if(((n_idec_si64_cp(&bt, b.cp, 10, &cpb)
268 ) & n_IDEC_STATE_EMASK) != n_IDEC_STATE_EBASE)
269 goto j1;
271 if((at -= bt) != 0)
272 goto jret;
274 /* So this gets hairy: sort by PID, then hostname */
275 if(cpa_pid != NULL){
276 a.cp = cpa_pid;
277 xa = cpa;
278 }else{
279 a.cp = cpa;
280 if(*a.cp++ != 'P')
281 goto jm1; /* Fishy */
283 if(((n_idec_si64_cp(&at, a.cp, 10, &cpa)
284 ) & n_IDEC_STATE_EMASK) != n_IDEC_STATE_EBASE)
285 goto jm1; /* Fishy */
287 if(cpb_pid != NULL){
288 b.cp = cpb_pid;
289 xb = cpb;
290 }else{
291 b.cp = cpb;
292 if(*b.cp++ != 'P')
293 goto j1; /* Fishy */
295 if(((n_idec_si64_cp(&bt, b.cp, 10, &cpb)
296 ) & n_IDEC_STATE_EMASK) != n_IDEC_STATE_EBASE)
297 goto jm1; /* Fishy */
299 if((at -= bt) != 0)
300 goto jret;
302 /* Hostname */
303 a.cp = (cpa_pid != NULL) ? xa : cpa;
304 b.cp = (cpb_pid != NULL) ? xb : cpb;
305 for(;; ++a.cp, ++b.cp){
306 char ac, bc;
308 ac = *a.cp;
309 at = (ac != '\0' && ac != n_MAILDIR_SEPARATOR);
310 bc = *b.cp;
311 bt = (bc != '\0' && bc != n_MAILDIR_SEPARATOR);
312 if((at -= bt) != 0)
313 break;
314 at = ac;
315 if((at -= bc) != 0)
316 break;
317 if(ac == '\0')
318 break;
321 jret:
322 rv = (at == 0 ? 0 : (at < 0 ? -1 : 1));
323 jleave:
324 NYD2_LEAVE;
325 return rv;
326 jm1:
327 rv = -1;
328 goto jleave;
330 rv = 1;
331 goto jleave;
334 static int
335 _maildir_subdir(char const *name, char const *sub, enum fedit_mode fm)
337 DIR *dirp;
338 struct dirent *dp;
339 int rv;
340 NYD_ENTER;
342 if ((dirp = opendir(sub)) == NULL) {
343 n_err(_("Cannot open directory %s\n"),
344 n_shexp_quote_cp(savecatsep(name, '/', sub), FAL0));
345 rv = -1;
346 goto jleave;
348 if (access(sub, W_OK) == -1)
349 mb.mb_perm = 0;
350 while ((dp = readdir(dirp)) != NULL) {
351 if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' ||
352 (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
353 continue;
354 if (dp->d_name[0] == '.')
355 continue;
356 if (!(fm & FEDIT_NEWMAIL) || mdlook(dp->d_name, NULL) == NULL)
357 _maildir_append(name, sub, dp->d_name);
359 closedir(dirp);
360 rv = 0;
361 jleave:
362 NYD_LEAVE;
363 return rv;
366 static void
367 _maildir_append(char const *name, char const *sub, char const *fn)
369 struct message *m;
370 time_t t = 0;
371 enum mflag f = MUSED | MNOFROM | MNEWEST;
372 char const *cp, *xp;
373 NYD_ENTER;
374 n_UNUSED(name);
376 if (fn != NULL && sub != NULL) {
377 if (!strcmp(sub, "new"))
378 f |= MNEW;
380 /* C99 */{
381 si64_t tib;
383 (void)/*TODO*/n_idec_si64_cp(&tib, fn, 10, &xp);
384 t = (time_t)tib;
387 if ((cp = strrchr(xp, ',')) != NULL && PTRCMP(cp, >, xp + 2) &&
388 cp[-1] == '2' && cp[-2] == n_MAILDIR_SEPARATOR) {
389 while (*++cp != '\0') {
390 switch (*cp) {
391 case 'F':
392 f |= MFLAGGED;
393 break;
394 case 'R':
395 f |= MANSWERED;
396 break;
397 case 'S':
398 f |= MREAD;
399 break;
400 case 'T':
401 f |= MDELETED;
402 break;
403 case 'D':
404 f |= MDRAFT;
405 break;
411 /* Ensure room (and a NULLified last entry) */
412 ++msgCount;
413 message_append(NULL);
414 --msgCount;
416 if (fn == NULL || sub == NULL)
417 goto jleave;
419 m = &message[msgCount++];
420 /* C99 */{
421 char *tmp;
422 size_t sz, i;
424 i = strlen(fn) +1;
425 sz = strlen(sub);
426 m->m_maildir_file = tmp = smalloc(sz + 1 + i);
427 memcpy(tmp, sub, sz);
428 tmp[sz++] = '/';
429 memcpy(&tmp[sz], fn, i);
431 m->m_time = t;
432 m->m_flag = f;
433 m->m_maildir_hash = ~n_torek_hash(fn);
434 jleave:
435 NYD_LEAVE;
436 return;
439 static void
440 readin(char const *name, struct message *m)
442 char *buf;
443 size_t bufsize, buflen, cnt;
444 long size = 0, lines = 0;
445 off_t offset;
446 FILE *fp;
447 int emptyline = 0;
448 NYD_ENTER;
450 if ((fp = Fopen(m->m_maildir_file, "r")) == NULL) {
451 n_err(_("Cannot read %s for message %lu\n"),
452 n_shexp_quote_cp(savecatsep(name, '/', m->m_maildir_file), FAL0),
453 (ul_i)PTR2SIZE(m - message + 1));
454 m->m_flag |= MHIDDEN;
455 goto jleave;
458 cnt = fsize(fp);
459 fseek(mb.mb_otf, 0L, SEEK_END);
460 offset = ftell(mb.mb_otf);
461 buf = smalloc(bufsize = LINESIZE);
462 buflen = 0;
463 while (fgetline(&buf, &bufsize, &cnt, &buflen, fp, 1) != NULL) {
464 /* Since we simply copy over data without doing any transfer
465 * encoding reclassification/adjustment we *have* to perform
466 * RFC 4155 compliant From_ quoting here */
467 if (emptyline && is_head(buf, buflen, FAL0)) {
468 putc('>', mb.mb_otf);
469 ++size;
471 size += fwrite(buf, 1, buflen, mb.mb_otf);/*XXX err hdling*/
472 emptyline = (*buf == '\n');
473 ++lines;
475 free(buf);
476 if (!emptyline) {
477 putc('\n', mb.mb_otf);
478 ++lines;
479 ++size;
482 Fclose(fp);
483 fflush(mb.mb_otf);
484 m->m_size = m->m_xsize = size;
485 m->m_lines = m->m_xlines = lines;
486 m->m_block = mailx_blockof(offset);
487 m->m_offset = mailx_offsetof(offset);
488 substdate(m);
489 jleave:
490 NYD_LEAVE;
493 static void
494 maildir_update(void)
496 struct message *m;
497 struct n_timespec const *tsp;
498 int dodel, c, gotcha = 0, held = 0, modflags = 0;
499 NYD_ENTER;
501 if (mb.mb_perm == 0)
502 goto jfree;
504 if (!(n_pstate & n_PS_EDIT)) {
505 holdbits();
506 for (m = message, c = 0; PTRCMP(m, <, message + msgCount); ++m) {
507 if (m->m_flag & MBOX)
508 c++;
510 if (c > 0)
511 if (makembox() == STOP)
512 goto jbypass;
515 tsp = n_time_now(TRU1); /* TODO FAL0, eventloop update! */
517 n_autorec_relax_create();
518 for (m = message, gotcha = 0, held = 0; PTRCMP(m, <, message + msgCount);
519 ++m) {
520 if (n_pstate & n_PS_EDIT)
521 dodel = m->m_flag & MDELETED;
522 else
523 dodel = !((m->m_flag & MPRESERVE) || !(m->m_flag & MTOUCH));
524 if (dodel) {
525 if (unlink(m->m_maildir_file) < 0)
526 n_err(_("Cannot delete file %s for message %lu\n"),
527 n_shexp_quote_cp(savecatsep(mailname, '/', m->m_maildir_file),
528 FAL0), (ul_i)PTR2SIZE(m - message + 1));
529 else
530 ++gotcha;
531 } else {
532 if ((m->m_flag & (MREAD | MSTATUS)) == (MREAD | MSTATUS) ||
533 (m->m_flag & (MNEW | MBOXED | MSAVED | MSTATUS | MFLAG |
534 MUNFLAG | MANSWER | MUNANSWER | MDRAFT | MUNDRAFT))) {
535 _maildir_move(tsp, m);
536 n_autorec_relax_unroll();
537 ++modflags;
539 ++held;
542 n_autorec_relax_gut();
544 jbypass:
545 if ((gotcha || modflags) && (n_pstate & n_PS_EDIT)) {
546 fprintf(n_stdout, "%s %s\n",
547 n_shexp_quote_cp(displayname, FAL0),
548 ((ok_blook(bsdcompat) || ok_blook(bsdmsgs))
549 ? _("complete") : _("updated.")));
550 } else if (held && !(n_pstate & n_PS_EDIT) && mb.mb_perm != 0) {
551 if (held == 1)
552 fprintf(n_stdout, _("Held 1 message in %s\n"), displayname);
553 else
554 fprintf(n_stdout, _("Held %d messages in %s\n"), held, displayname);
556 fflush(n_stdout);
557 jfree:
558 for (m = message; PTRCMP(m, <, message + msgCount); ++m)
559 free(n_UNCONST(m->m_maildir_file));
560 NYD_LEAVE;
563 static void
564 _maildir_move(struct n_timespec const *tsp, struct message *m)
566 char *fn, *newfn;
567 NYD_ENTER;
569 fn = mkname(tsp, m->m_flag, m->m_maildir_file + 4);
570 newfn = savecat("cur/", fn);
571 if (!strcmp(m->m_maildir_file, newfn))
572 goto jleave;
573 if (link(m->m_maildir_file, newfn) == -1) {
574 n_err(_("Cannot link %s to %s: message %lu not touched\n"),
575 n_shexp_quote_cp(savecatsep(mailname, '/', m->m_maildir_file), FAL0),
576 n_shexp_quote_cp(savecatsep(mailname, '/', newfn), FAL0),
577 (ul_i)PTR2SIZE(m - message + 1));
578 goto jleave;
580 if (unlink(m->m_maildir_file) == -1)
581 n_err(_("Cannot unlink %s\n"),
582 n_shexp_quote_cp(savecatsep(mailname, '/', m->m_maildir_file), FAL0));
583 jleave:
584 NYD_LEAVE;
587 static char *
588 mkname(struct n_timespec const *tsp, enum mflag f, char const *pref)
590 static char *node;
591 static struct n_timespec ts;
593 char *cp;
594 int size, n, i;
595 NYD_ENTER;
597 if (pref == NULL) {
598 si64_t s;
600 if(n_pid == 0)
601 n_pid = getpid();
603 if (node == NULL) {
604 cp = n_nodename(FAL0);
605 n = size = 0;
606 do {
607 if (UICMP(32, n, <, size + 8))
608 node = srealloc(node, size += 20);
609 switch (*cp) {
610 case '/':
611 node[n++] = '\\', node[n++] = '0',
612 node[n++] = '5', node[n++] = '7';
613 break;
614 case ':':
615 node[n++] = '\\', node[n++] = '0',
616 node[n++] = '7', node[n++] = '2';
617 break;
618 default:
619 node[n++] = *cp;
621 } while (*cp++ != '\0');
624 /* Problem: Courier spec uses microseconds, not nanoseconds */
625 if((s = tsp->ts_sec) > ts.ts_sec){
626 ts.ts_sec = s;
627 ts.ts_nsec = tsp->ts_nsec / (n_DATE_NANOSSEC / n_DATE_MICROSSEC);
628 }else{
629 s = tsp->ts_nsec / (n_DATE_NANOSSEC / n_DATE_MICROSSEC);
630 if(s <= ts.ts_nsec)
631 s = ts.ts_nsec + 1;
632 if(s < n_DATE_MICROSSEC)
633 ts.ts_nsec = s;
634 else{
635 ++ts.ts_sec;
636 ts.ts_nsec = 0;
640 /* Create a name according to Courier spec */
641 size = 60 + strlen(node);
642 cp = n_autorec_alloc(size);
643 n = snprintf(cp, size, "%" PRId64 ".M%" PRIdZ "P%ld.%s:2,",
644 ts.ts_sec, ts.ts_nsec, (long)n_pid, node);
645 } else {
646 size = (n = strlen(pref)) + 13;
647 cp = salloc(size);
648 memcpy(cp, pref, n +1);
649 for (i = n; i > 3; --i)
650 if (cp[i - 1] == ',' && cp[i - 2] == '2' &&
651 cp[i - 3] == n_MAILDIR_SEPARATOR) {
652 n = i;
653 break;
655 if (i <= 3) {
656 memcpy(cp + n, ":2,", 3 +1);
657 n += 3;
660 if (n < size - 7) {
661 if (f & MDRAFTED)
662 cp[n++] = 'D';
663 if (f & MFLAGGED)
664 cp[n++] = 'F';
665 if (f & MANSWERED)
666 cp[n++] = 'R';
667 if (f & MREAD)
668 cp[n++] = 'S';
669 if (f & MDELETED)
670 cp[n++] = 'T';
671 cp[n] = '\0';
673 NYD_LEAVE;
674 return cp;
677 static enum okay
678 maildir_append1(struct n_timespec const *tsp, char const *name, FILE *fp,
679 off_t off1, long size, enum mflag flag)
681 char buf[4096], *fn, *tfn, *nfn;
682 struct stat st;
683 FILE *op;
684 size_t nlen, flen, n;
685 enum okay rv = STOP;
686 NYD_ENTER;
688 nlen = strlen(name);
690 /* Create a unique temporary file */
691 for (nfn = (char*)0xA /* XXX no magic */;; n_msleep(500, FAL0)) {
692 flen = strlen(fn = mkname(tsp, flag, NULL));
693 tfn = n_autorec_alloc(n = nlen + flen + 6);
694 snprintf(tfn, n, "%s/tmp/%s", name, fn);
696 /* Use "wx" for O_EXCL XXX stat(2) rather redundant; coverity:TOCTOU */
697 if ((!stat(tfn, &st) || n_err_no == n_ERR_NOENT) &&
698 (op = Fopen(tfn, "wx")) != NULL)
699 break;
701 nfn = (char*)(PTR2SIZE(nfn) - 1);
702 if (nfn == NULL) {
703 n_err(_("Can't create an unique file name in %s\n"),
704 n_shexp_quote_cp(savecat(name, "/tmp"), FAL0));
705 goto jleave;
709 if (fseek(fp, off1, SEEK_SET) == -1)
710 goto jtmperr;
711 while (size > 0) {
712 size_t z = UICMP(z, size, >, sizeof buf) ? sizeof buf : (size_t)size;
714 if (z != (n = fread(buf, 1, z, fp)) || n != fwrite(buf, 1, n, op)) {
715 jtmperr:
716 n_err(_("Error writing to %s\n"), n_shexp_quote_cp(tfn, FAL0));
717 Fclose(op);
718 goto jerr;
720 size -= n;
722 Fclose(op);
724 nfn = n_autorec_alloc(n = nlen + flen + 6);
725 snprintf(nfn, n, "%s/new/%s", name, fn);
726 if (link(tfn, nfn) == -1) {
727 n_err(_("Cannot link %s to %s\n"), n_shexp_quote_cp(tfn, FAL0),
728 n_shexp_quote_cp(nfn, FAL0));
729 goto jerr;
731 rv = OKAY;
732 jerr:
733 if (unlink(tfn) == -1)
734 n_err(_("Cannot unlink %s\n"), n_shexp_quote_cp(tfn, FAL0));
735 jleave:
736 NYD_LEAVE;
737 return rv;
740 static enum okay
741 trycreate(char const *name)
743 struct stat st;
744 enum okay rv = STOP;
745 NYD_ENTER;
747 if (!stat(name, &st)) {
748 if (!S_ISDIR(st.st_mode)) {
749 n_err(_("%s is not a directory\n"), n_shexp_quote_cp(name, FAL0));
750 goto jleave;
752 } else if (!n_path_mkdir(name)) {
753 n_err(_("Cannot create directory %s\n"), n_shexp_quote_cp(name, FAL0));
754 goto jleave;
756 rv = OKAY;
757 jleave:
758 NYD_LEAVE;
759 return rv;
762 static enum okay
763 mkmaildir(char const *name) /* TODO proper cleanup on error; use path[] loop */
765 char *np;
766 size_t sz;
767 enum okay rv = STOP;
768 NYD_ENTER;
770 if (trycreate(name) == OKAY) {
771 np = ac_alloc((sz = strlen(name)) + 4 +1);
772 memcpy(np, name, sz);
773 memcpy(np + sz, "/tmp", 4 +1);
774 if (trycreate(np) == OKAY) {
775 memcpy(np + sz, "/new", 4 +1);
776 if (trycreate(np) == OKAY) {
777 memcpy(np + sz, "/cur", 4 +1);
778 rv = trycreate(np);
781 ac_free(np);
783 NYD_LEAVE;
784 return rv;
787 static struct message *
788 mdlook(char const *name, struct message *data)
790 struct message *mp;
791 ui32_t c, h, n = 0;
792 NYD_ENTER;
794 if (data && data->m_maildir_hash)
795 h = ~data->m_maildir_hash;
796 else
797 h = n_torek_hash(name);
798 h %= a_maildir_prime;
799 c = h;
800 mp = a_maildir_table[c];
802 while (mp != NULL) {
803 if (!strcmp(mp->m_maildir_file + 4, name))
804 break;
805 c += (n & 1) ? -((n+1)/2) * ((n+1)/2) : ((n+1)/2) * ((n+1)/2);
806 n++;
807 while (c >= a_maildir_prime)
808 c -= a_maildir_prime;
809 mp = a_maildir_table[c];
811 if (data != NULL && mp == NULL)
812 mp = data;
813 NYD_LEAVE;
814 return mp;
817 static void
818 mktable(void)
820 struct message *mp;
821 size_t i;
822 NYD_ENTER;
824 a_maildir_prime = n_prime_next(msgCount);
825 a_maildir_table = scalloc(a_maildir_prime, sizeof *a_maildir_table);
826 for (mp = message, i = msgCount; i-- != 0; ++mp)
827 mdlook(mp->m_maildir_file + 4, mp);
828 NYD_LEAVE;
831 static enum okay
832 subdir_remove(char const *name, char const *sub)
834 char *path;
835 int pathsize, pathend, namelen, sublen, n;
836 DIR *dirp;
837 struct dirent *dp;
838 enum okay rv = STOP;
839 NYD_ENTER;
841 namelen = strlen(name);
842 sublen = strlen(sub);
843 path = smalloc(pathsize = namelen + sublen + 30 +1);
844 memcpy(path, name, namelen);
845 path[namelen] = '/';
846 memcpy(path + namelen + 1, sub, sublen);
847 path[namelen + sublen + 1] = '/';
848 path[pathend = namelen + sublen + 2] = '\0';
850 if ((dirp = opendir(path)) == NULL) {
851 n_perr(path, 0);
852 goto jleave;
854 while ((dp = readdir(dirp)) != NULL) {
855 if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' ||
856 (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
857 continue;
858 if (dp->d_name[0] == '.')
859 continue;
860 n = strlen(dp->d_name);
861 if (UICMP(32, pathend + n +1, >, pathsize))
862 path = srealloc(path, pathsize = pathend + n + 30);
863 memcpy(path + pathend, dp->d_name, n +1);
864 if (unlink(path) == -1) {
865 n_perr(path, 0);
866 closedir(dirp);
867 goto jleave;
870 closedir(dirp);
872 path[pathend] = '\0';
873 if (rmdir(path) == -1) {
874 n_perr(path, 0);
875 goto jleave;
877 rv = OKAY;
878 jleave:
879 free(path);
880 NYD_LEAVE;
881 return rv;
884 FL int
885 maildir_setfile(char const * volatile name, enum fedit_mode fm)
887 sighandler_type volatile saveint;
888 struct cw cw;
889 char const *emsg;
890 int omsgCount;
891 int volatile i = -1;
892 NYD_ENTER;
894 omsgCount = msgCount;
895 if (cwget(&cw) == STOP) {
896 n_alert(_("Cannot open current directory"));
897 goto jleave;
900 if (!(fm & FEDIT_NEWMAIL) && !quit(FAL0))
901 goto jleave;
903 saveint = safe_signal(SIGINT, SIG_IGN);
905 if (!(fm & FEDIT_NEWMAIL)) {
906 if (fm & FEDIT_SYSBOX)
907 n_pstate &= ~n_PS_EDIT;
908 else
909 n_pstate |= n_PS_EDIT;
910 if (mb.mb_itf) {
911 fclose(mb.mb_itf);
912 mb.mb_itf = NULL;
914 if (mb.mb_otf) {
915 fclose(mb.mb_otf);
916 mb.mb_otf = NULL;
918 initbox(name);
919 mb.mb_type = MB_MAILDIR;
922 if(!n_is_dir(name, FAL0)){
923 emsg = N_("Not a maildir: %s\n");
924 goto jerr;
925 }else if(chdir(name) < 0){
926 emsg = N_("Cannot enter maildir://%s\n");
927 jerr:
928 n_err(V_(emsg), n_shexp_quote_cp(name, FAL0));
929 mb.mb_type = MB_VOID;
930 *mailname = '\0';
931 msgCount = 0;
932 cwrelse(&cw);
933 safe_signal(SIGINT, saveint);
934 goto jleave;
937 a_maildir_table = NULL;
938 if (sigsetjmp(_maildir_jmp, 1) == 0) {
939 if (fm & FEDIT_NEWMAIL)
940 mktable();
941 if (saveint != SIG_IGN)
942 safe_signal(SIGINT, &__maildircatch);
943 i = _maildir_setfile1(name, fm, omsgCount);
945 if ((fm & FEDIT_NEWMAIL) && a_maildir_table != NULL)
946 free(a_maildir_table);
948 safe_signal(SIGINT, saveint);
950 if (i < 0) {
951 mb.mb_type = MB_VOID;
952 *mailname = '\0';
953 msgCount = 0;
956 if (cwret(&cw) == STOP)
957 n_panic(_("Cannot change back to current directory"));
958 cwrelse(&cw);
960 setmsize(msgCount);
961 if ((fm & FEDIT_NEWMAIL) && mb.mb_sorted && msgCount > omsgCount) {
962 mb.mb_threaded = 0;
963 c_sort((void*)-1);
966 if (!(fm & FEDIT_NEWMAIL)) {
967 n_pstate &= ~n_PS_SAW_COMMAND;
968 n_pstate |= n_PS_SETFILE_OPENED;
971 if ((n_poption & n_PO_EXISTONLY) && !(n_poption & n_PO_HEADERLIST)) {
972 i = (msgCount == 0);
973 goto jleave;
976 if (!(fm & FEDIT_NEWMAIL) && (fm & FEDIT_SYSBOX) && msgCount == 0) {
977 if (mb.mb_type == MB_MAILDIR /* XXX ?? */ && !ok_blook(emptystart))
978 n_err(_("No mail at %s\n"), n_shexp_quote_cp(name, FAL0));
979 i = 1;
980 goto jleave;
983 if ((fm & FEDIT_NEWMAIL) && msgCount > omsgCount)
984 newmailinfo(omsgCount);
985 i = 0;
986 jleave:
987 NYD_LEAVE;
988 return i;
991 FL bool_t
992 maildir_quit(bool_t hold_sigs_on)
994 sighandler_type saveint;
995 struct cw cw;
996 bool_t rv;
997 NYD_ENTER;
999 if(hold_sigs_on)
1000 rele_sigs();
1002 rv = FAL0;
1004 if (cwget(&cw) == STOP) {
1005 n_alert(_("Cannot open current directory"));
1006 goto jleave;
1009 saveint = safe_signal(SIGINT, SIG_IGN);
1011 if (chdir(mailname) == -1) {
1012 n_err(_("Cannot change directory to %s\n"),
1013 n_shexp_quote_cp(mailname, FAL0));
1014 cwrelse(&cw);
1015 safe_signal(SIGINT, saveint);
1016 goto jleave;
1019 if (sigsetjmp(_maildir_jmp, 1) == 0) {
1020 if (saveint != SIG_IGN)
1021 safe_signal(SIGINT, &__maildircatch_hold);
1022 maildir_update();
1025 safe_signal(SIGINT, saveint);
1027 if (cwret(&cw) == STOP)
1028 n_panic(_("Cannot change back to current directory"));
1029 cwrelse(&cw);
1030 rv = TRU1;
1031 jleave:
1032 if(hold_sigs_on)
1033 hold_sigs();
1034 NYD_LEAVE;
1035 return rv;
1038 FL enum okay
1039 maildir_append(char const *name, FILE *fp, long offset)
1041 struct n_timespec const *tsp;
1042 char *buf, *bp, *lp;
1043 size_t bufsize, buflen, cnt;
1044 off_t off1 = -1, offs;
1045 long size;
1046 int flag;
1047 enum {_NONE = 0, _INHEAD = 1<<0, _NLSEP = 1<<1} state;
1048 enum okay rv;
1049 NYD_ENTER;
1051 if ((rv = mkmaildir(name)) != OKAY)
1052 goto jleave;
1054 buf = smalloc(bufsize = LINESIZE); /* TODO line pool; signals */
1055 buflen = 0;
1056 cnt = fsize(fp);
1057 offs = offset /* BSD will move due to O_APPEND! ftell(fp) */;
1058 size = 0;
1059 tsp = n_time_now(TRU1); /* TODO -> eventloop */
1061 n_autorec_relax_create();
1062 for (flag = MNEW, state = _NLSEP;;) {
1063 bp = fgetline(&buf, &bufsize, &cnt, &buflen, fp, 1);
1065 if (bp == NULL ||
1066 ((state & (_INHEAD | _NLSEP)) == _NLSEP &&
1067 is_head(buf, buflen, FAL0))) {
1068 if (off1 != (off_t)-1) {
1069 if ((rv = maildir_append1(tsp, name, fp, off1, size, flag)) == STOP)
1070 goto jfree;
1071 n_autorec_relax_unroll();
1072 if (fseek(fp, offs + buflen, SEEK_SET) == -1) {
1073 rv = STOP;
1074 goto jfree;
1077 off1 = offs + buflen;
1078 size = 0;
1079 state = _INHEAD;
1080 flag = MNEW;
1082 if (bp == NULL)
1083 break;
1084 } else
1085 size += buflen;
1086 offs += buflen;
1088 state &= ~_NLSEP;
1089 if (buf[0] == '\n') {
1090 state &= ~_INHEAD;
1091 state |= _NLSEP;
1092 } else if (state & _INHEAD) {
1093 if (!ascncasecmp(buf, "status", 6)) {
1094 lp = buf + 6;
1095 while (whitechar(*lp))
1096 ++lp;
1097 if (*lp == ':')
1098 while (*++lp != '\0')
1099 switch (*lp) {
1100 case 'R':
1101 flag |= MREAD;
1102 break;
1103 case 'O':
1104 flag &= ~MNEW;
1105 break;
1107 } else if (!ascncasecmp(buf, "x-status", 8)) {
1108 lp = buf + 8;
1109 while (whitechar(*lp))
1110 ++lp;
1111 if (*lp == ':') {
1112 while (*++lp != '\0')
1113 switch (*lp) {
1114 case 'F':
1115 flag |= MFLAGGED;
1116 break;
1117 case 'A':
1118 flag |= MANSWERED;
1119 break;
1120 case 'T':
1121 flag |= MDRAFTED;
1122 break;
1128 assert(rv == OKAY);
1129 jfree:
1130 n_autorec_relax_gut();
1131 free(buf);
1132 jleave:
1133 NYD_LEAVE;
1134 return rv;
1137 FL enum okay
1138 maildir_remove(char const *name)
1140 enum okay rv = STOP;
1141 NYD_ENTER;
1143 if (subdir_remove(name, "tmp") == STOP ||
1144 subdir_remove(name, "new") == STOP ||
1145 subdir_remove(name, "cur") == STOP)
1146 goto jleave;
1147 if (rmdir(name) == -1) {
1148 n_perr(name, 0);
1149 goto jleave;
1151 rv = OKAY;
1152 jleave:
1153 NYD_LEAVE;
1154 return rv;
1157 /* s-it-mode */