make-config.in: complete path (leftover of [807f64e2], 2015-12-26!)
[s-mailx.git] / maildir.c
blobf071fe4dd2936624a2629ee41a6232694d47ad17
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 * 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 n_FILE
42 #define n_FILE maildir
44 #ifndef HAVE_AMALGAMATION
45 # include "nail.h"
46 #endif
48 #ifdef HAVE_MAILDIR
49 # include <dirent.h>
50 #endif
52 EMPTY_FILE()
53 #ifdef HAVE_MAILDIR
55 /* a_maildir_tbl should be a hash-indexed array of trees! */
56 static struct message **a_maildir_tbl, **a_maildir_tbl_top;
57 static ui32_t a_maildir_tbl_prime, a_maildir_tbl_maxdist;
58 static sigjmp_buf _maildir_jmp;
60 static void __maildircatch(int s);
61 static void __maildircatch_hold(int s);
63 /* Do some cleanup in the tmp/ subdir */
64 static void _cleantmp(void);
66 static int _maildir_setfile1(char const *name, enum fedit_mode fm,
67 int omsgCount);
69 static int a_maildir_cmp(void const *a, void const *b);
71 static int _maildir_subdir(char const *name, char const *sub,
72 enum fedit_mode fm);
74 static void _maildir_append(char const *name, char const *sub,
75 char const *fn);
77 static void readin(char const *name, struct message *m);
79 static void maildir_update(void);
81 static void _maildir_move(struct n_timespec const *tsp,
82 struct message *m);
84 static char * mkname(struct n_timespec const *tsp, enum mflag f,
85 char const *pref);
87 static enum okay maildir_append1(struct n_timespec const *tsp,
88 char const *name, FILE *fp, off_t off1,
89 long size, enum mflag flag);
91 static enum okay trycreate(char const *name);
93 static enum okay mkmaildir(char const *name);
95 static struct message * mdlook(char const *name, struct message *data);
97 static void mktable(void);
99 static enum okay subdir_remove(char const *name, char const *sub);
101 static void
102 __maildircatch(int s)
104 NYD_X; /* Signal handler */
105 siglongjmp(_maildir_jmp, s);
108 static void
109 __maildircatch_hold(int s)
111 NYD_X; /* Signal handler */
112 n_UNUSED(s);
113 /* TODO no STDIO in signal handler, no _() tr's -- pre-translate interrupt
114 * TODO globally; */
115 n_err_sighdl(_("\nImportant operation in progress: "
116 "interrupt again to forcefully abort\n"));
117 safe_signal(SIGINT, &__maildircatch);
120 static void
121 _cleantmp(void)
123 struct stat st;
124 struct n_string s, *sp;
125 si64_t now;
126 DIR *dirp;
127 struct dirent *dp;
128 NYD_ENTER;
130 if ((dirp = opendir("tmp")) == NULL)
131 goto jleave;
133 now = n_time_now(FAL0)->ts_sec;
134 sp = n_string_creat_auto(&s);
136 while ((dp = readdir(dirp)) != NULL) {
137 if (dp->d_name[0] == '.')
138 continue;
140 sp = n_string_trunc(sp, 0);
141 sp = n_string_push_buf(sp, "tmp/", sizeof("tmp/") -1);
142 sp = n_string_push_cp(sp, dp->d_name);
143 if (stat(n_string_cp(sp), &st) == -1)
144 continue;
145 if (st.st_atime + 36*3600 < now)
146 unlink(sp->s_dat);
148 closedir(dirp);
149 jleave:
150 NYD_LEAVE;
153 static int
154 _maildir_setfile1(char const *name, enum fedit_mode fm, int omsgCount)
156 int i;
157 NYD_ENTER;
159 if (!(fm & FEDIT_NEWMAIL))
160 _cleantmp();
162 mb.mb_perm = ((n_poption & n_PO_R_FLAG) || (fm & FEDIT_RDONLY))
163 ? 0 : MB_DELE;
164 if ((i = _maildir_subdir(name, "cur", fm)) != 0)
165 goto jleave;
166 if ((i = _maildir_subdir(name, "new", fm)) != 0)
167 goto jleave;
168 _maildir_append(name, NULL, NULL);
170 n_autorec_relax_create();
171 for (i = ((fm & FEDIT_NEWMAIL) ? omsgCount : 0); i < msgCount; ++i) {
172 readin(name, message + i);
173 n_autorec_relax_unroll();
175 n_autorec_relax_gut();
177 if (fm & FEDIT_NEWMAIL) {
178 if (msgCount > omsgCount)
179 qsort(&message[omsgCount], msgCount - omsgCount, sizeof *message,
180 &a_maildir_cmp);
181 } else if (msgCount)
182 qsort(message, msgCount, sizeof *message, &a_maildir_cmp);
183 i = msgCount;
184 jleave:
185 NYD_LEAVE;
186 return i;
189 static int
190 a_maildir_cmp(void const *xa, void const *xb){
191 char const *cpa, *cpa_pid, *cpb, *cpb_pid;
192 union {struct message const *mp; char const *cp;} a, b;
193 si64_t at, bt;
194 int rv;
195 NYD2_ENTER;
197 a.mp = xa;
198 b.mp = xb;
200 /* We could have parsed the time somewhen in the past, do a quick shot */
201 at = (si64_t)a.mp->m_time;
202 bt = (si64_t)b.mp->m_time;
203 if(at != 0 && bt != 0 && (at -= bt) != 0)
204 goto jret;
206 /* Otherwise we need to parse the name */
207 a.cp = &a.mp->m_maildir_file[4];
208 b.cp = &b.mp->m_maildir_file[4];
210 /* Interpret time stored in name, and use it for comparison */
211 if(((n_idec_si64_cp(&at, a.cp, 10, &cpa)
212 ) & n_IDEC_STATE_EMASK) != n_IDEC_STATE_EBASE || *cpa != '.' ||
213 a.cp == cpa)
214 goto jm1; /* Fishy */
215 if(((n_idec_si64_cp(&bt, b.cp, 10, &cpb)
216 ) & n_IDEC_STATE_EMASK) != n_IDEC_STATE_EBASE || *cpb != '.' ||
217 b.cp == cpb)
218 goto j1; /* Fishy */
220 if((at -= bt) != 0)
221 goto jret;
223 /* If the seconds part does not work, go deeper.
224 * We use de-facto standard "maildir — E-mail directory" from the Courier
225 * mail server, also used by, e.g., Dovecot: sec.MusecPpid.hostname:2,flags.
226 * However, a different name convention exists which uses
227 * sec.pid_counter.hostname:2,flags.
228 * First go for usec/counter, then pid */
230 /* A: exact "standard"? */
231 cpa_pid = NULL;
232 a.cp = ++cpa;
233 if((rv = *a.cp) == 'M')
235 /* Known compat? */
236 else if(digitchar(rv)){
237 cpa_pid = a.cp++;
238 while((rv = *a.cp) != '\0' && rv != '_')
239 ++a.cp;
240 if(rv == '\0')
241 goto jm1; /* Fishy */
243 /* This is compatible to what dovecot does, it surely does not do so
244 * for nothing, but i have no idea, but am too stupid to ask */
245 else for(;; rv = *++a.cp){
246 if(rv == 'M')
247 break;
248 if(rv == '\0' || rv == '.' || rv == n_MAILDIR_SEPARATOR)
249 goto jm1; /* Fishy */
251 ++a.cp;
252 if(((n_idec_si64_cp(&at, a.cp, 10, &cpa)
253 ) & n_IDEC_STATE_EMASK) != n_IDEC_STATE_EBASE)
254 goto jm1; /* Fishy */
256 /* B: as above */
257 cpb_pid = NULL;
258 b.cp = ++cpb;
259 if((rv = *b.cp) == 'M')
261 else if(digitchar(rv)){
262 cpb_pid = b.cp++;
263 while((rv = *b.cp) != '\0' && rv != '_')
264 ++b.cp;
265 if(rv == '\0')
266 goto j1;
267 }else for(;; rv = *++b.cp){
268 if(rv == 'M')
269 break;
270 if(rv == '\0' || rv == '.' || rv == n_MAILDIR_SEPARATOR)
271 goto jm1;
273 ++b.cp;
274 if(((n_idec_si64_cp(&bt, b.cp, 10, &cpb)
275 ) & n_IDEC_STATE_EMASK) != n_IDEC_STATE_EBASE)
276 goto j1;
278 if((at -= bt) != 0)
279 goto jret;
281 /* So this gets hairy: sort by PID, then hostname */
282 if(cpa_pid != NULL){
283 a.cp = cpa_pid;
284 xa = cpa;
285 }else{
286 a.cp = cpa;
287 if(*a.cp++ != 'P')
288 goto jm1; /* Fishy */
290 if(((n_idec_si64_cp(&at, a.cp, 10, &cpa)
291 ) & n_IDEC_STATE_EMASK) != n_IDEC_STATE_EBASE)
292 goto jm1; /* Fishy */
294 if(cpb_pid != NULL){
295 b.cp = cpb_pid;
296 xb = cpb;
297 }else{
298 b.cp = cpb;
299 if(*b.cp++ != 'P')
300 goto j1; /* Fishy */
302 if(((n_idec_si64_cp(&bt, b.cp, 10, &cpb)
303 ) & n_IDEC_STATE_EMASK) != n_IDEC_STATE_EBASE)
304 goto jm1; /* Fishy */
306 if((at -= bt) != 0)
307 goto jret;
309 /* Hostname */
310 a.cp = (cpa_pid != NULL) ? xa : cpa;
311 b.cp = (cpb_pid != NULL) ? xb : cpb;
312 for(;; ++a.cp, ++b.cp){
313 char ac, bc;
315 ac = *a.cp;
316 at = (ac != '\0' && ac != n_MAILDIR_SEPARATOR);
317 bc = *b.cp;
318 bt = (bc != '\0' && bc != n_MAILDIR_SEPARATOR);
319 if((at -= bt) != 0)
320 break;
321 at = ac;
322 if((at -= bc) != 0)
323 break;
324 if(ac == '\0')
325 break;
328 jret:
329 rv = (at == 0 ? 0 : (at < 0 ? -1 : 1));
330 jleave:
331 NYD2_LEAVE;
332 return rv;
333 jm1:
334 rv = -1;
335 goto jleave;
337 rv = 1;
338 goto jleave;
341 static int
342 _maildir_subdir(char const *name, char const *sub, enum fedit_mode fm)
344 DIR *dirp;
345 struct dirent *dp;
346 int rv;
347 NYD_ENTER;
349 if ((dirp = opendir(sub)) == NULL) {
350 n_err(_("Cannot open directory %s\n"),
351 n_shexp_quote_cp(savecatsep(name, '/', sub), FAL0));
352 rv = -1;
353 goto jleave;
355 if (access(sub, W_OK) == -1)
356 mb.mb_perm = 0;
357 while ((dp = readdir(dirp)) != NULL) {
358 if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' ||
359 (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
360 continue;
361 if (dp->d_name[0] == '.')
362 continue;
363 if (!(fm & FEDIT_NEWMAIL) || mdlook(dp->d_name, NULL) == NULL)
364 _maildir_append(name, sub, dp->d_name);
366 closedir(dirp);
367 rv = 0;
368 jleave:
369 NYD_LEAVE;
370 return rv;
373 static void
374 _maildir_append(char const *name, char const *sub, char const *fn)
376 struct message *m;
377 time_t t = 0;
378 enum mflag f = MUSED | MNOFROM | MNEWEST;
379 char const *cp, *xp;
380 NYD_ENTER;
381 n_UNUSED(name);
383 if (fn != NULL && sub != NULL) {
384 if (!strcmp(sub, "new"))
385 f |= MNEW;
387 /* C99 */{
388 si64_t tib;
390 (void)/*TODO*/n_idec_si64_cp(&tib, fn, 10, &xp);
391 t = (time_t)tib;
394 if ((cp = strrchr(xp, ',')) != NULL && PTRCMP(cp, >, xp + 2) &&
395 cp[-1] == '2' && cp[-2] == n_MAILDIR_SEPARATOR) {
396 while (*++cp != '\0') {
397 switch (*cp) {
398 case 'F':
399 f |= MFLAGGED;
400 break;
401 case 'R':
402 f |= MANSWERED;
403 break;
404 case 'S':
405 f |= MREAD;
406 break;
407 case 'T':
408 f |= MDELETED;
409 break;
410 case 'D':
411 f |= MDRAFT;
412 break;
418 /* Ensure room (and a NULLified last entry) */
419 ++msgCount;
420 message_append(NULL);
421 --msgCount;
423 if (fn == NULL || sub == NULL)
424 goto jleave;
426 m = &message[msgCount++];
427 /* C99 */{
428 char *tmp;
429 size_t sz, i;
431 i = strlen(fn) +1;
432 sz = strlen(sub);
433 m->m_maildir_file = tmp = n_alloc(sz + 1 + i);
434 memcpy(tmp, sub, sz);
435 tmp[sz++] = '/';
436 memcpy(&tmp[sz], fn, i);
438 m->m_time = t;
439 m->m_flag = f;
440 m->m_maildir_hash = n_torek_hash(fn);
441 jleave:
442 NYD_LEAVE;
443 return;
446 static void
447 readin(char const *name, struct message *m)
449 char *buf;
450 size_t bufsize, buflen, cnt;
451 long size = 0, lines = 0;
452 off_t offset;
453 FILE *fp;
454 int emptyline = 0;
455 NYD_ENTER;
457 if ((fp = Fopen(m->m_maildir_file, "r")) == NULL) {
458 n_err(_("Cannot read %s for message %lu\n"),
459 n_shexp_quote_cp(savecatsep(name, '/', m->m_maildir_file), FAL0),
460 (ul_i)PTR2SIZE(m - message + 1));
461 m->m_flag |= MHIDDEN;
462 goto jleave;
465 cnt = fsize(fp);
466 fseek(mb.mb_otf, 0L, SEEK_END);
467 offset = ftell(mb.mb_otf);
468 buf = n_alloc(bufsize = LINESIZE);
469 buflen = 0;
470 while (fgetline(&buf, &bufsize, &cnt, &buflen, fp, 1) != NULL) {
471 /* Since we simply copy over data without doing any transfer
472 * encoding reclassification/adjustment we *have* to perform
473 * RFC 4155 compliant From_ quoting here */
474 if (emptyline && is_head(buf, buflen, FAL0)) {
475 putc('>', mb.mb_otf);
476 ++size;
478 size += fwrite(buf, 1, buflen, mb.mb_otf);/*XXX err hdling*/
479 emptyline = (*buf == '\n');
480 ++lines;
482 n_free(buf);
483 if (!emptyline) {
484 /* TODO we need \n\n for mbox format.
485 * TODO That is to say we do it wrong here in order to get it right
486 * TODO when send.c stuff or with MBOX handling, even though THIS
487 * TODO line is solely a property of the MBOX database format! */
488 putc('\n', mb.mb_otf);
489 ++lines;
490 ++size;
493 Fclose(fp);
494 fflush(mb.mb_otf);
495 m->m_size = m->m_xsize = size;
496 m->m_lines = m->m_xlines = lines;
497 m->m_block = mailx_blockof(offset);
498 m->m_offset = mailx_offsetof(offset);
499 substdate(m);
500 jleave:
501 NYD_LEAVE;
504 static void
505 maildir_update(void)
507 struct message *m;
508 struct n_timespec const *tsp;
509 int dodel, c, gotcha = 0, held = 0, modflags = 0;
510 NYD_ENTER;
512 if (mb.mb_perm == 0)
513 goto jfree;
515 if (!(n_pstate & n_PS_EDIT)) {
516 holdbits();
517 for (m = message, c = 0; PTRCMP(m, <, message + msgCount); ++m) {
518 if (m->m_flag & MBOX)
519 c++;
521 if (c > 0)
522 if (makembox() == STOP)
523 goto jbypass;
526 tsp = n_time_now(TRU1); /* TODO FAL0, eventloop update! */
528 n_autorec_relax_create();
529 for (m = message, gotcha = 0, held = 0; PTRCMP(m, <, message + msgCount);
530 ++m) {
531 if (n_pstate & n_PS_EDIT)
532 dodel = m->m_flag & MDELETED;
533 else
534 dodel = !((m->m_flag & MPRESERVE) || !(m->m_flag & MTOUCH));
535 if (dodel) {
536 if (unlink(m->m_maildir_file) < 0)
537 n_err(_("Cannot delete file %s for message %lu\n"),
538 n_shexp_quote_cp(savecatsep(mailname, '/', m->m_maildir_file),
539 FAL0), (ul_i)PTR2SIZE(m - message + 1));
540 else
541 ++gotcha;
542 } else {
543 if ((m->m_flag & (MREAD | MSTATUS)) == (MREAD | MSTATUS) ||
544 (m->m_flag & (MNEW | MBOXED | MSAVED | MSTATUS | MFLAG |
545 MUNFLAG | MANSWER | MUNANSWER | MDRAFT | MUNDRAFT))) {
546 _maildir_move(tsp, m);
547 n_autorec_relax_unroll();
548 ++modflags;
550 ++held;
553 n_autorec_relax_gut();
555 jbypass:
556 if ((gotcha || modflags) && (n_pstate & n_PS_EDIT)) {
557 fprintf(n_stdout, "%s %s\n",
558 n_shexp_quote_cp(displayname, FAL0),
559 ((ok_blook(bsdcompat) || ok_blook(bsdmsgs))
560 ? _("complete") : _("updated.")));
561 } else if (held && !(n_pstate & n_PS_EDIT) && mb.mb_perm != 0) {
562 if (held == 1)
563 fprintf(n_stdout, _("Held 1 message in %s\n"), displayname);
564 else
565 fprintf(n_stdout, _("Held %d messages in %s\n"), held, displayname);
567 fflush(n_stdout);
568 jfree:
569 for (m = message; PTRCMP(m, <, message + msgCount); ++m)
570 n_free(n_UNCONST(m->m_maildir_file));
571 NYD_LEAVE;
574 static void
575 _maildir_move(struct n_timespec const *tsp, struct message *m)
577 char *fn, *newfn;
578 NYD_ENTER;
580 fn = mkname(tsp, m->m_flag, m->m_maildir_file + 4);
581 newfn = savecat("cur/", fn);
582 if (!strcmp(m->m_maildir_file, newfn))
583 goto jleave;
584 if (link(m->m_maildir_file, newfn) == -1) {
585 n_err(_("Cannot link %s to %s: message %lu not touched\n"),
586 n_shexp_quote_cp(savecatsep(mailname, '/', m->m_maildir_file), FAL0),
587 n_shexp_quote_cp(savecatsep(mailname, '/', newfn), FAL0),
588 (ul_i)PTR2SIZE(m - message + 1));
589 goto jleave;
591 if (unlink(m->m_maildir_file) == -1)
592 n_err(_("Cannot unlink %s\n"),
593 n_shexp_quote_cp(savecatsep(mailname, '/', m->m_maildir_file), FAL0));
594 jleave:
595 NYD_LEAVE;
598 static char *
599 mkname(struct n_timespec const *tsp, enum mflag f, char const *pref)
601 static char *node;
602 static struct n_timespec ts;
604 char *cp;
605 int size, n, i;
606 NYD_ENTER;
608 if (pref == NULL) {
609 si64_t s;
611 if(n_pid == 0)
612 n_pid = getpid();
614 if (node == NULL) {
615 cp = n_nodename(FAL0);
616 n = size = 0;
617 do {
618 if (UICMP(32, n, <, size + 8))
619 node = n_realloc(node, size += 20);
620 switch (*cp) {
621 case '/':
622 node[n++] = '\\', node[n++] = '0',
623 node[n++] = '5', node[n++] = '7';
624 break;
625 case ':':
626 node[n++] = '\\', node[n++] = '0',
627 node[n++] = '7', node[n++] = '2';
628 break;
629 default:
630 node[n++] = *cp;
632 } while (*cp++ != '\0');
635 /* Problem: Courier spec uses microseconds, not nanoseconds */
636 if((s = tsp->ts_sec) > ts.ts_sec){
637 ts.ts_sec = s;
638 ts.ts_nsec = tsp->ts_nsec / (n_DATE_NANOSSEC / n_DATE_MICROSSEC);
639 }else{
640 s = tsp->ts_nsec / (n_DATE_NANOSSEC / n_DATE_MICROSSEC);
641 if(s <= ts.ts_nsec)
642 s = ts.ts_nsec + 1;
643 if(s < n_DATE_MICROSSEC)
644 ts.ts_nsec = s;
645 else{
646 ++ts.ts_sec;
647 ts.ts_nsec = 0;
651 /* Create a name according to Courier spec */
652 size = 60 + strlen(node);
653 cp = n_autorec_alloc(size);
654 n = snprintf(cp, size, "%" PRId64 ".M%" PRIdZ "P%ld.%s:2,",
655 ts.ts_sec, ts.ts_nsec, (long)n_pid, node);
656 } else {
657 size = (n = strlen(pref)) + 13;
658 cp = n_autorec_alloc(size);
659 memcpy(cp, pref, n +1);
660 for (i = n; i > 3; --i)
661 if (cp[i - 1] == ',' && cp[i - 2] == '2' &&
662 cp[i - 3] == n_MAILDIR_SEPARATOR) {
663 n = i;
664 break;
666 if (i <= 3) {
667 memcpy(cp + n, ":2,", 3 +1);
668 n += 3;
671 if (n < size - 7) {
672 if (f & MDRAFTED)
673 cp[n++] = 'D';
674 if (f & MFLAGGED)
675 cp[n++] = 'F';
676 if (f & MANSWERED)
677 cp[n++] = 'R';
678 if (f & MREAD)
679 cp[n++] = 'S';
680 if (f & MDELETED)
681 cp[n++] = 'T';
682 cp[n] = '\0';
684 NYD_LEAVE;
685 return cp;
688 static enum okay
689 maildir_append1(struct n_timespec const *tsp, char const *name, FILE *fp,
690 off_t off1, long size, enum mflag flag)
692 char buf[4096], *fn, *tfn, *nfn;
693 struct stat st;
694 FILE *op;
695 size_t nlen, flen, n;
696 enum okay rv = STOP;
697 NYD_ENTER;
699 nlen = strlen(name);
701 /* Create a unique temporary file */
702 for (nfn = (char*)0xA /* XXX no magic */;; n_msleep(500, FAL0)) {
703 flen = strlen(fn = mkname(tsp, flag, NULL));
704 tfn = n_autorec_alloc(n = nlen + flen + 6);
705 snprintf(tfn, n, "%s/tmp/%s", name, fn);
707 /* Use "wx" for O_EXCL XXX stat(2) rather redundant; coverity:TOCTOU */
708 if ((!stat(tfn, &st) || n_err_no == n_ERR_NOENT) &&
709 (op = Fopen(tfn, "wx")) != NULL)
710 break;
712 nfn = (char*)(PTR2SIZE(nfn) - 1);
713 if (nfn == NULL) {
714 n_err(_("Can't create an unique file name in %s\n"),
715 n_shexp_quote_cp(savecat(name, "/tmp"), FAL0));
716 goto jleave;
720 if (fseek(fp, off1, SEEK_SET) == -1)
721 goto jtmperr;
722 while (size > 0) {
723 size_t z = UICMP(z, size, >, sizeof buf) ? sizeof buf : (size_t)size;
725 if (z != (n = fread(buf, 1, z, fp)) || n != fwrite(buf, 1, n, op)) {
726 jtmperr:
727 n_err(_("Error writing to %s\n"), n_shexp_quote_cp(tfn, FAL0));
728 Fclose(op);
729 goto jerr;
731 size -= n;
733 Fclose(op);
735 nfn = n_autorec_alloc(n = nlen + flen + 6);
736 snprintf(nfn, n, "%s/new/%s", name, fn);
737 if (link(tfn, nfn) == -1) {
738 n_err(_("Cannot link %s to %s\n"), n_shexp_quote_cp(tfn, FAL0),
739 n_shexp_quote_cp(nfn, FAL0));
740 goto jerr;
742 rv = OKAY;
743 jerr:
744 if (unlink(tfn) == -1)
745 n_err(_("Cannot unlink %s\n"), n_shexp_quote_cp(tfn, FAL0));
746 jleave:
747 NYD_LEAVE;
748 return rv;
751 static enum okay
752 trycreate(char const *name)
754 struct stat st;
755 enum okay rv = STOP;
756 NYD_ENTER;
758 if (!stat(name, &st)) {
759 if (!S_ISDIR(st.st_mode)) {
760 n_err(_("%s is not a directory\n"), n_shexp_quote_cp(name, FAL0));
761 goto jleave;
763 } else if (!n_path_mkdir(name)) {
764 n_err(_("Cannot create directory %s\n"), n_shexp_quote_cp(name, FAL0));
765 goto jleave;
767 rv = OKAY;
768 jleave:
769 NYD_LEAVE;
770 return rv;
773 static enum okay
774 mkmaildir(char const *name) /* TODO proper cleanup on error; use path[] loop */
776 char *np;
777 size_t sz;
778 enum okay rv = STOP;
779 NYD_ENTER;
781 if (trycreate(name) == OKAY) {
782 np = n_lofi_alloc((sz = strlen(name)) + 4 +1);
783 memcpy(np, name, sz);
784 memcpy(np + sz, "/tmp", 4 +1);
785 if (trycreate(np) == OKAY) {
786 memcpy(np + sz, "/new", 4 +1);
787 if (trycreate(np) == OKAY) {
788 memcpy(np + sz, "/cur", 4 +1);
789 rv = trycreate(np);
792 n_lofi_free(np);
794 NYD_LEAVE;
795 return rv;
798 static struct message *
799 mdlook(char const *name, struct message *data)
801 struct message **mpp, *mp;
802 ui32_t h, i;
803 NYD_ENTER;
805 if(data != NULL)
806 i = data->m_maildir_hash;
807 else
808 i = n_torek_hash(name);
809 h = i;
810 mpp = &a_maildir_tbl[i %= a_maildir_tbl_prime];
812 for(i = 0;;){
813 if((mp = *mpp) == NULL){
814 if(n_UNLIKELY(data != NULL)){
815 *mpp = mp = data;
816 if(i > a_maildir_tbl_maxdist)
817 a_maildir_tbl_maxdist = i;
819 break;
820 }else if(mp->m_maildir_hash == h && !strcmp(&mp->m_maildir_file[4], name))
821 break;
823 if(n_UNLIKELY(mpp++ == a_maildir_tbl_top))
824 mpp = a_maildir_tbl;
825 if(++i > a_maildir_tbl_maxdist && n_UNLIKELY(data == NULL)){
826 mp = NULL;
827 break;
830 NYD_LEAVE;
831 return mp;
834 static void
835 mktable(void)
837 struct message *mp;
838 size_t i;
839 NYD_ENTER;
841 i = a_maildir_tbl_prime = msgCount;
842 i <<= 1;
844 a_maildir_tbl_prime = n_prime_next(a_maildir_tbl_prime);
845 while(a_maildir_tbl_prime < i);
846 a_maildir_tbl = n_calloc(a_maildir_tbl_prime, sizeof *a_maildir_tbl);
847 a_maildir_tbl_top = &a_maildir_tbl[a_maildir_tbl_prime - 1];
848 a_maildir_tbl_maxdist = 0;
849 for(mp = message, i = msgCount; i-- != 0; ++mp)
850 mdlook(&mp->m_maildir_file[4], mp);
851 NYD_LEAVE;
854 static enum okay
855 subdir_remove(char const *name, char const *sub)
857 char *path;
858 int pathsize, pathend, namelen, sublen, n;
859 DIR *dirp;
860 struct dirent *dp;
861 enum okay rv = STOP;
862 NYD_ENTER;
864 namelen = strlen(name);
865 sublen = strlen(sub);
866 path = n_alloc(pathsize = namelen + sublen + 30 +1);
867 memcpy(path, name, namelen);
868 path[namelen] = '/';
869 memcpy(path + namelen + 1, sub, sublen);
870 path[namelen + sublen + 1] = '/';
871 path[pathend = namelen + sublen + 2] = '\0';
873 if ((dirp = opendir(path)) == NULL) {
874 n_perr(path, 0);
875 goto jleave;
877 while ((dp = readdir(dirp)) != NULL) {
878 if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' ||
879 (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
880 continue;
881 if (dp->d_name[0] == '.')
882 continue;
883 n = strlen(dp->d_name);
884 if (UICMP(32, pathend + n +1, >, pathsize))
885 path = n_realloc(path, pathsize = pathend + n + 30);
886 memcpy(path + pathend, dp->d_name, n +1);
887 if (unlink(path) == -1) {
888 n_perr(path, 0);
889 closedir(dirp);
890 goto jleave;
893 closedir(dirp);
895 path[pathend] = '\0';
896 if (rmdir(path) == -1) {
897 n_perr(path, 0);
898 goto jleave;
900 rv = OKAY;
901 jleave:
902 n_free(path);
903 NYD_LEAVE;
904 return rv;
907 FL int
908 maildir_setfile(char const *who, char const * volatile name,
909 enum fedit_mode fm)
911 sighandler_type volatile saveint;
912 struct cw cw;
913 char const *emsg;
914 int omsgCount;
915 int volatile i = -1;
916 NYD_ENTER;
918 omsgCount = msgCount;
919 if (cwget(&cw) == STOP) {
920 n_alert(_("Cannot open current directory"));
921 goto jleave;
924 if (!(fm & FEDIT_NEWMAIL) && !quit(FAL0))
925 goto jleave;
927 saveint = safe_signal(SIGINT, SIG_IGN);
929 if (!(fm & FEDIT_NEWMAIL)) {
930 if (fm & FEDIT_SYSBOX)
931 n_pstate &= ~n_PS_EDIT;
932 else
933 n_pstate |= n_PS_EDIT;
934 if (mb.mb_itf) {
935 fclose(mb.mb_itf);
936 mb.mb_itf = NULL;
938 if (mb.mb_otf) {
939 fclose(mb.mb_otf);
940 mb.mb_otf = NULL;
942 initbox(name);
943 mb.mb_type = MB_MAILDIR;
946 if(!n_is_dir(name, FAL0)){
947 emsg = N_("Not a maildir: %s\n");
948 goto jerr;
949 }else if(chdir(name) < 0){
950 emsg = N_("Cannot enter maildir://%s\n");
951 jerr:
952 n_err(V_(emsg), n_shexp_quote_cp(name, FAL0));
953 mb.mb_type = MB_VOID;
954 *mailname = '\0';
955 msgCount = 0;
956 cwrelse(&cw);
957 safe_signal(SIGINT, saveint);
958 goto jleave;
961 a_maildir_tbl = NULL;
962 if (sigsetjmp(_maildir_jmp, 1) == 0) {
963 if (fm & FEDIT_NEWMAIL)
964 mktable();
965 if (saveint != SIG_IGN)
966 safe_signal(SIGINT, &__maildircatch);
967 i = _maildir_setfile1(name, fm, omsgCount);
969 if ((fm & FEDIT_NEWMAIL) && a_maildir_tbl != NULL)
970 n_free(a_maildir_tbl);
972 safe_signal(SIGINT, saveint);
974 if (i < 0) {
975 mb.mb_type = MB_VOID;
976 *mailname = '\0';
977 msgCount = 0;
980 if (cwret(&cw) == STOP)
981 n_panic(_("Cannot change back to current directory"));
982 cwrelse(&cw);
984 setmsize(msgCount);
985 if ((fm & FEDIT_NEWMAIL) && mb.mb_sorted && msgCount > omsgCount) {
986 mb.mb_threaded = 0;
987 c_sort((void*)-1);
990 if (!(fm & FEDIT_NEWMAIL)) {
991 n_pstate &= ~n_PS_SAW_COMMAND;
992 n_pstate |= n_PS_SETFILE_OPENED;
995 if ((n_poption & n_PO_EXISTONLY) && !(n_poption & n_PO_HEADERLIST)) {
996 i = (msgCount == 0);
997 goto jleave;
1000 if (!(fm & FEDIT_NEWMAIL) && (fm & FEDIT_SYSBOX) && msgCount == 0) {
1001 if (mb.mb_type == MB_MAILDIR /* XXX ?? */ && !ok_blook(emptystart))
1002 n_err(_("No mail for %s at %s\n"), who, n_shexp_quote_cp(name, FAL0));
1003 i = 1;
1004 goto jleave;
1007 if ((fm & FEDIT_NEWMAIL) && msgCount > omsgCount)
1008 newmailinfo(omsgCount);
1009 i = 0;
1010 jleave:
1011 NYD_LEAVE;
1012 return i;
1015 FL bool_t
1016 maildir_quit(bool_t hold_sigs_on)
1018 sighandler_type saveint;
1019 struct cw cw;
1020 bool_t rv;
1021 NYD_ENTER;
1023 if(hold_sigs_on)
1024 rele_sigs();
1026 rv = FAL0;
1028 if (cwget(&cw) == STOP) {
1029 n_alert(_("Cannot open current directory"));
1030 goto jleave;
1033 saveint = safe_signal(SIGINT, SIG_IGN);
1035 if (chdir(mailname) == -1) {
1036 n_err(_("Cannot change directory to %s\n"),
1037 n_shexp_quote_cp(mailname, FAL0));
1038 cwrelse(&cw);
1039 safe_signal(SIGINT, saveint);
1040 goto jleave;
1043 if (sigsetjmp(_maildir_jmp, 1) == 0) {
1044 if (saveint != SIG_IGN)
1045 safe_signal(SIGINT, &__maildircatch_hold);
1046 maildir_update();
1049 safe_signal(SIGINT, saveint);
1051 if (cwret(&cw) == STOP)
1052 n_panic(_("Cannot change back to current directory"));
1053 cwrelse(&cw);
1054 rv = TRU1;
1055 jleave:
1056 if(hold_sigs_on)
1057 hold_sigs();
1058 NYD_LEAVE;
1059 return rv;
1062 FL enum okay
1063 maildir_append(char const *name, FILE *fp, long offset)
1065 struct n_timespec const *tsp;
1066 char *buf, *bp, *lp;
1067 size_t bufsize, buflen, cnt;
1068 off_t off1 = -1, offs;
1069 long size;
1070 int flag;
1071 enum {_NONE = 0, _INHEAD = 1<<0, _NLSEP = 1<<1} state;
1072 enum okay rv;
1073 NYD_ENTER;
1075 if ((rv = mkmaildir(name)) != OKAY)
1076 goto jleave;
1078 buf = n_alloc(bufsize = LINESIZE); /* TODO line pool; signals */
1079 buflen = 0;
1080 cnt = fsize(fp);
1081 offs = offset /* BSD will move due to O_APPEND! ftell(fp) */;
1082 size = 0;
1083 tsp = n_time_now(TRU1); /* TODO -> eventloop */
1085 n_autorec_relax_create();
1086 for (flag = MNEW, state = _NLSEP;;) {
1087 bp = fgetline(&buf, &bufsize, &cnt, &buflen, fp, 1);
1089 if (bp == NULL ||
1090 ((state & (_INHEAD | _NLSEP)) == _NLSEP &&
1091 is_head(buf, buflen, FAL0))) {
1092 if (off1 != (off_t)-1) {
1093 if ((rv = maildir_append1(tsp, name, fp, off1, size, flag)) == STOP)
1094 goto jfree;
1095 n_autorec_relax_unroll();
1096 if (fseek(fp, offs + buflen, SEEK_SET) == -1) {
1097 rv = STOP;
1098 goto jfree;
1101 off1 = offs + buflen;
1102 size = 0;
1103 state = _INHEAD;
1104 flag = MNEW;
1106 if (bp == NULL)
1107 break;
1108 } else
1109 size += buflen;
1110 offs += buflen;
1112 state &= ~_NLSEP;
1113 if (buf[0] == '\n') {
1114 state &= ~_INHEAD;
1115 state |= _NLSEP;
1116 } else if (state & _INHEAD) {
1117 if (!ascncasecmp(buf, "status", 6)) {
1118 lp = buf + 6;
1119 while (whitechar(*lp))
1120 ++lp;
1121 if (*lp == ':')
1122 while (*++lp != '\0')
1123 switch (*lp) {
1124 case 'R':
1125 flag |= MREAD;
1126 break;
1127 case 'O':
1128 flag &= ~MNEW;
1129 break;
1131 } else if (!ascncasecmp(buf, "x-status", 8)) {
1132 lp = buf + 8;
1133 while (whitechar(*lp))
1134 ++lp;
1135 if (*lp == ':') {
1136 while (*++lp != '\0')
1137 switch (*lp) {
1138 case 'F':
1139 flag |= MFLAGGED;
1140 break;
1141 case 'A':
1142 flag |= MANSWERED;
1143 break;
1144 case 'T':
1145 flag |= MDRAFTED;
1146 break;
1152 assert(rv == OKAY);
1153 jfree:
1154 n_autorec_relax_gut();
1155 n_free(buf);
1156 jleave:
1157 NYD_LEAVE;
1158 return rv;
1161 FL enum okay
1162 maildir_remove(char const *name)
1164 enum okay rv = STOP;
1165 NYD_ENTER;
1167 if (subdir_remove(name, "tmp") == STOP ||
1168 subdir_remove(name, "new") == STOP ||
1169 subdir_remove(name, "cur") == STOP)
1170 goto jleave;
1171 if (rmdir(name) == -1) {
1172 n_perr(name, 0);
1173 goto jleave;
1175 rv = OKAY;
1176 jleave:
1177 NYD_LEAVE;
1178 return rv;
1180 #endif /* HAVE_MAILDIR */
1182 /* s-it-mode */