1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Maildir folder support. FIXME rewrite - why do we chdir(2)??
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2014 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
9 * Gunnar Ritter. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by Gunnar Ritter
22 * and his contributors.
23 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40 #ifndef HAVE_AMALGAMATION
47 struct message
*md_data
;
51 static struct mditem
*_maildir_table
;
52 static ui32_t _maildir_prime
;
53 static sigjmp_buf _maildir_jmp
;
55 static void __maildircatch(int s
);
57 /* Do some cleanup in the tmp/ subdir */
58 static void _cleantmp(void);
60 static int maildir_setfile1(char const *name
, int nmail
,
63 /* In combination with the names from mkname(), this comparison function
64 * ensures that the order of messages in a maildir folder created by mailx
65 * remains always the same. In effect, if a mbox folder is transferred to
66 * a maildir folder by 'copy *', the message order wont' change */
67 static int mdcmp(void const *a
, void const *b
);
69 static int subdir(char const *name
, char const *sub
, int nmail
);
71 static void _maildir_append(char const *name
, char const *sub
,
74 static void readin(char const *name
, struct message
*m
);
76 static void maildir_update(void);
78 static void move(struct message
*m
);
80 static char * mkname(time_t t
, enum mflag f
, char const *pref
);
82 static enum okay
maildir_append1(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
);
98 NYD_X
; /* Signal handler */
99 siglongjmp(_maildir_jmp
, s
);
112 if ((dirp
= opendir("tmp")) == NULL
)
116 while ((dp
= readdir(dirp
)) != NULL
) {
117 if (dp
->d_name
[0] == '.')
119 sstpcpy(sstpcpy(dep
, "tmp/"), dp
->d_name
);
120 if (stat(dep
, &st
) < 0)
122 if (st
.st_atime
+ 36*3600 < now
)
131 maildir_setfile1(char const *name
, int nmail
, int omsgCount
)
139 mb
.mb_perm
= (options
& OPT_R_FLAG
) ? 0 : MB_DELE
;
140 if ((i
= subdir(name
, "cur", nmail
)) != 0)
142 if ((i
= subdir(name
, "new", nmail
)) != 0)
144 _maildir_append(name
, NULL
, NULL
);
145 for (i
= nmail
? omsgCount
: 0; i
< msgCount
; ++i
)
146 readin(name
, &message
[i
]);
148 if (msgCount
> omsgCount
)
149 qsort(&message
[omsgCount
], msgCount
- omsgCount
, sizeof *message
,
152 qsort(message
, msgCount
, sizeof *message
, &mdcmp
);
160 mdcmp(void const *a
, void const *b
)
162 struct message
const *mpa
= a
, *mpb
= b
;
166 if ((i
= mpa
->m_time
- mpb
->m_time
) == 0)
167 i
= strcmp(mpa
->m_maildir_file
+ 4, mpb
->m_maildir_file
+ 4);
173 subdir(char const *name
, char const *sub
, int nmail
)
180 if ((dirp
= opendir(sub
)) == NULL
) {
181 fprintf(stderr
, "Cannot open directory \"%s/%s\".\n", name
, sub
);
185 if (access(sub
, W_OK
) < 0)
187 while ((dp
= readdir(dirp
)) != NULL
) {
188 if (dp
->d_name
[0] == '.' && (dp
->d_name
[1] == '\0' ||
189 (dp
->d_name
[1] == '.' && dp
->d_name
[2] == '\0')))
191 if (dp
->d_name
[0] == '.')
193 if (!nmail
|| mdlook(dp
->d_name
, NULL
) == NULL
)
194 _maildir_append(name
, sub
, dp
->d_name
);
204 _maildir_append(char const *name
, char const *sub
, char const *fn
)
209 enum mflag f
= MUSED
| MNOFROM
| MNEWEST
;
215 if (fn
!= NULL
&& sub
!= NULL
) {
216 if (!strcmp(sub
, "new"))
218 t
= strtol(fn
, &xp
, 10);
219 if ((cp
= strrchr(xp
, ',')) != NULL
&& PTRCMP(cp
, >, xp
+ 2) &&
220 cp
[-1] == '2' && cp
[-2] == ':') {
221 while (*++cp
!= '\0') {
243 /* Ensure room (and a NULLified last entry) */
245 message_append(NULL
);
248 if (fn
== NULL
|| sub
== NULL
)
251 m
= message
+ msgCount
++;
253 m
->m_maildir_file
= smalloc((sz
= strlen(sub
)) + i
+ 2);
254 memcpy(m
->m_maildir_file
, sub
, sz
);
255 m
->m_maildir_file
[sz
] = '/';
256 memcpy(m
->m_maildir_file
+ sz
+ 1, fn
, i
+ 1);
259 m
->m_maildir_hash
= ~pjw(fn
);
266 readin(char const *name
, struct message
*m
)
269 size_t bufsize
, buflen
, cnt
;
270 long size
= 0, lines
= 0;
276 if ((fp
= Fopen(m
->m_maildir_file
, "r")) == NULL
) {
277 fprintf(stderr
, "Cannot read \"%s/%s\" for message %d\n",
278 name
, m
->m_maildir_file
, (int)PTR2SIZE(m
- message
+ 1));
279 m
->m_flag
|= MHIDDEN
;
284 fseek(mb
.mb_otf
, 0L, SEEK_END
);
285 offset
= ftell(mb
.mb_otf
);
286 buf
= smalloc(bufsize
= LINESIZE
);
288 while (fgetline(&buf
, &bufsize
, &cnt
, &buflen
, fp
, 1) != NULL
) {
289 /* Since we simply copy over data without doing any transfer
290 * encoding reclassification/adjustment we *have* to perform
291 * RFC 4155 compliant From_ quoting here */
292 if (is_head(buf
, buflen
)) {
293 putc('>', mb
.mb_otf
);
296 size
+= fwrite(buf
, 1, buflen
, mb
.mb_otf
);/*XXX err hdling*/
297 emptyline
= (*buf
== '\n');
302 putc('\n', mb
.mb_otf
);
309 m
->m_size
= m
->m_xsize
= size
;
310 m
->m_lines
= m
->m_xlines
= lines
;
311 m
->m_block
= mailx_blockof(offset
);
312 m
->m_offset
= mailx_offsetof(offset
);
322 int dodel
, c
, gotcha
= 0, held
= 0, modflags
= 0;
330 for (m
= message
, c
= 0; PTRCMP(m
, <, message
+ msgCount
); ++m
) {
331 if (m
->m_flag
& MBOX
)
335 if (makembox() == STOP
)
338 for (m
= message
, gotcha
= 0, held
= 0; PTRCMP(m
, <, message
+ msgCount
);
341 dodel
= m
->m_flag
& MDELETED
;
343 dodel
= !((m
->m_flag
& MPRESERVE
) || !(m
->m_flag
& MTOUCH
));
345 if (unlink(m
->m_maildir_file
) < 0)
346 fprintf(stderr
, /* TODO tr */
347 "Cannot delete file \"%s/%s\" for message %d.\n",
348 mailname
, m
->m_maildir_file
, (int)PTR2SIZE(m
- message
+ 1));
352 if ((m
->m_flag
& (MREAD
| MSTATUS
)) == (MREAD
| MSTATUS
) ||
353 (m
->m_flag
& (MNEW
| MBOXED
| MSAVED
| MSTATUS
| MFLAG
|
354 MUNFLAG
| MANSWER
| MUNANSWER
| MDRAFT
| MUNDRAFT
))) {
362 if ((gotcha
|| modflags
) && edit
) {
363 printf(tr(168, "\"%s\" "), displayname
);
364 printf((ok_blook(bsdcompat
) || ok_blook(bsdmsgs
))
365 ? tr(170, "complete\n") : tr(212, "updated.\n"));
366 } else if (held
&& !edit
&& mb
.mb_perm
!= 0) {
368 printf(tr(155, "Held 1 message in %s\n"), displayname
);
370 printf(tr(156, "Held %d messages in %s\n"), held
, displayname
);
374 for (m
= message
; PTRCMP(m
, <, message
+ msgCount
); ++m
)
375 free(m
->m_maildir_file
);
380 move(struct message
*m
)
385 fn
= mkname(0, m
->m_flag
, &m
->m_maildir_file
[4]);
386 new = savecat("cur/", fn
);
387 if (!strcmp(m
->m_maildir_file
, new))
389 if (link(m
->m_maildir_file
, new) < 0) {
390 fprintf(stderr
, /* TODO tr */
391 "Cannot link \"%s/%s\" to \"%s/%s\": message %d not touched.\n",
392 mailname
, m
->m_maildir_file
, mailname
, new,
393 (int)PTR2SIZE(m
- message
+ 1));
396 if (unlink(m
->m_maildir_file
) < 0)
397 fprintf(stderr
, /* TODO tr */"Cannot unlink \"%s/%s\".\n",
398 mailname
, m
->m_maildir_file
);
404 mkname(time_t t
, enum mflag f
, char const *pref
)
406 static unsigned long cnt
;
407 static pid_t mypid
; /* XXX This should possibly be global, somehow */
421 if (UICMP(32, n
, <, size
+ 8))
422 node
= srealloc(node
, size
+= 20);
425 node
[n
++] = '\\', node
[n
++] = '0',
426 node
[n
++] = '5', node
[n
++] = '7';
429 node
[n
++] = '\\', node
[n
++] = '0',
430 node
[n
++] = '7', node
[n
++] = '2';
437 size
= 60 + strlen(node
);
439 n
= snprintf(cp
, size
, "%lu.%06lu_%06lu.%s:2,",
440 (ul_it
)t
, (ul_it
)mypid
, ++cnt
, node
);
442 size
= (n
= strlen(pref
)) + 13;
444 memcpy(cp
, pref
, n
+ 1);
445 for (i
= n
; i
> 3; --i
)
446 if (cp
[i
- 1] == ',' && cp
[i
- 2] == '2' && cp
[i
- 3] == ':') {
451 memcpy(cp
+ n
, ":2,", 4);
473 maildir_append1(char const *name
, FILE *fp
, off_t off1
, long size
,
476 int const attempts
= 43200; /* XXX no magic */
477 char buf
[4096], *fn
, *tmp
, *new;
486 /* Create a unique temporary file */
487 for (i
= 0;; sleep(1), ++i
) {
489 fprintf(stderr
, tr(198,
490 "Can't create an unique file name in \"%s/tmp\".\n"), name
);
495 fn
= mkname(now
, flag
, NULL
);
496 tmp
= salloc(n
= strlen(name
) + strlen(fn
) + 6);
497 snprintf(tmp
, n
, "%s/tmp/%s", name
, fn
);
498 if (stat(tmp
, &st
) >= 0 || errno
!= ENOENT
)
501 /* Use "wx" for O_EXCL */
502 if ((op
= Fopen(tmp
, "wx")) != NULL
)
506 if (fseek(fp
, off1
, SEEK_SET
) < 0)
509 z
= size
> (long)sizeof buf
? (long)sizeof buf
: size
;
510 if ((n
= fread(buf
, 1, z
, fp
)) != z
||
511 (size_t)n
!= fwrite(buf
, 1, n
, op
)) {
513 fprintf(stderr
, "Error writing to \"%s\".\n", tmp
); /* TODO tr */
522 new = salloc(n
= strlen(name
) + strlen(fn
) + 6);
523 snprintf(new, n
, "%s/new/%s", name
, fn
);
524 if (link(tmp
, new) < 0) {
525 fprintf(stderr
, "Cannot link \"%s\" to \"%s\".\n", tmp
, new);/* TODO tr */
529 fprintf(stderr
, "Cannot unlink \"%s\".\n", tmp
); /* TODO tr */
537 trycreate(char const *name
)
543 if (stat(name
, &st
) == 0) {
544 if (!S_ISDIR(st
.st_mode
)) {
545 fprintf(stderr
, "\"%s\" is not a directory.\n", name
);/* TODO tr */
548 } else if (makedir(name
) != OKAY
) {
549 fprintf(stderr
, "Cannot create directory \"%s\".\n", name
);/* TODO tr */
552 ++imap_created_mailbox
;
560 mkmaildir(char const *name
) /* TODO proper cleanup on error; use path[] loop */
567 if (trycreate(name
) == OKAY
) {
568 np
= ac_alloc((sz
= strlen(name
)) + 5);
569 memcpy(np
, name
, sz
);
570 memcpy(np
+ sz
, "/tmp", 5);
571 if (trycreate(np
) == OKAY
) {
572 strcpy(&np
[sz
], "/new");
573 if (trycreate(np
) == OKAY
) {
574 strcpy(&np
[sz
], "/cur");
584 static struct message
*
585 mdlook(char const *name
, struct message
*data
)
591 if (data
&& data
->m_maildir_hash
)
592 h
= ~data
->m_maildir_hash
;
596 md
= &_maildir_table
[c
= h
];
598 while (md
->md_data
!= NULL
) {
599 if (!strcmp(&md
->md_data
->m_maildir_file
[4], name
))
601 c
+= n
&1 ? -((n
+1)/2) * ((n
+1)/2) : ((n
+1)/2) * ((n
+1)/2);
603 while (c
>= _maildir_prime
)
605 md
= &_maildir_table
[c
];
607 if (data
!= NULL
&& md
->md_data
== NULL
)
610 return md
->md_data
? md
->md_data
: NULL
;
619 _maildir_prime
= nextprime(msgCount
);
620 _maildir_table
= scalloc(_maildir_prime
, sizeof *_maildir_table
);
621 for (i
= 0; i
< msgCount
; i
++)
622 mdlook(&message
[i
].m_maildir_file
[4], &message
[i
]);
627 subdir_remove(char const *name
, char const *sub
)
630 int pathsize
, pathend
, namelen
, sublen
, n
;
636 namelen
= strlen(name
);
637 sublen
= strlen(sub
);
638 path
= smalloc(pathsize
= namelen
+ sublen
+ 30);
639 memcpy(path
, name
, namelen
);
641 memcpy(path
+ namelen
+ 1, sub
, sublen
);
642 path
[namelen
+sublen
+1] = '/';
643 path
[pathend
= namelen
+ sublen
+ 2] = '\0';
645 if ((dirp
= opendir(path
)) == NULL
) {
649 while ((dp
= readdir(dirp
)) != NULL
) {
650 if (dp
->d_name
[0] == '.' && (dp
->d_name
[1] == '\0' ||
651 (dp
->d_name
[1] == '.' && dp
->d_name
[2] == '\0')))
653 if (dp
->d_name
[0] == '.')
655 n
= strlen(dp
->d_name
);
656 if (UICMP(32, pathend
+ n
+ 1, >, pathsize
))
657 path
= srealloc(path
, pathsize
= pathend
+ n
+ 30);
658 memcpy(path
+ pathend
, dp
->d_name
, n
+ 1);
659 if (unlink(path
) < 0) {
667 path
[pathend
] = '\0';
668 if (rmdir(path
) < 0) {
681 maildir_setfile(char const * volatile name
, int nmail
, int isedit
)
683 sighandler_type
volatile saveint
;
685 int i
= -1, omsgCount
;
688 omsgCount
= msgCount
;
689 if (cwget(&cw
) == STOP
) {
690 alert("Cannot open current directory");
697 saveint
= safe_signal(SIGINT
, SIG_IGN
);
700 edit
= (isedit
!= 0);
710 mb
.mb_type
= MB_MAILDIR
;
713 if (chdir(name
) < 0) {
714 fprintf(stderr
, "Cannot change directory to \"%s\".\n", name
);/*TODO tr*/
715 mb
.mb_type
= MB_VOID
;
719 safe_signal(SIGINT
, saveint
);
723 _maildir_table
= NULL
;
724 if (sigsetjmp(_maildir_jmp
, 1) == 0) {
727 if (saveint
!= SIG_IGN
)
728 safe_signal(SIGINT
, &__maildircatch
);
729 i
= maildir_setfile1(name
, nmail
, omsgCount
);
731 if (nmail
&& _maildir_table
!= NULL
)
732 free(_maildir_table
);
734 safe_signal(SIGINT
, saveint
);
737 mb
.mb_type
= MB_VOID
;
742 if (cwret(&cw
) == STOP
)
743 panic("Cannot change back to current directory.");/* TODO tr */
747 if (nmail
&& mb
.mb_sorted
&& msgCount
> omsgCount
) {
753 if (!nmail
&& !edit
&& msgCount
== 0) {
754 if (mb
.mb_type
== MB_MAILDIR
/* XXX ?? */ && !ok_blook(emptystart
))
755 fprintf(stderr
, tr(258, "No mail at %s\n"), name
);
759 if (nmail
&& msgCount
> omsgCount
)
760 newmailinfo(omsgCount
);
770 sighandler_type saveint
;
774 if (cwget(&cw
) == STOP
) {
775 alert("Cannot open current directory");/* TODO tr */
779 saveint
= safe_signal(SIGINT
, SIG_IGN
);
781 if (chdir(mailname
) < 0) {
782 fprintf(stderr
, "Cannot change directory to \"%s\".\n",/* TODO tr */
785 safe_signal(SIGINT
, saveint
);
789 if (sigsetjmp(_maildir_jmp
, 1) == 0) {
790 if (saveint
!= SIG_IGN
)
791 safe_signal(SIGINT
, &__maildircatch
);
795 safe_signal(SIGINT
, saveint
);
797 if (cwret(&cw
) == STOP
)
798 panic("Cannot change back to current directory."); /* TODO tr */
805 maildir_append(char const *name
, FILE *fp
)
808 size_t bufsize
, buflen
, cnt
;
809 off_t off1
= -1, offs
;
810 int inhead
= 1, flag
= MNEW
| MNEWEST
;
815 if ((rv
= mkmaildir(name
)) != OKAY
)
818 buf
= smalloc(bufsize
= LINESIZE
);
822 do /* while (bp != NULL); */ {
823 bp
= fgetline(&buf
, &bufsize
, &cnt
, &buflen
, fp
, 1);
824 if (bp
== NULL
|| !strncmp(buf
, "From ", 5)) {
825 if (off1
!= (off_t
)-1) {
826 rv
= maildir_append1(name
, fp
, off1
, size
, flag
);
829 if (fseek(fp
, offs
+ buflen
, SEEK_SET
) == -1) {
834 off1
= offs
+ buflen
;
841 if (bp
&& buf
[0] == '\n')
843 else if (bp
&& inhead
&& !ascncasecmp(buf
, "status", 6)) {
845 while (whitechar(*lp
))
848 while (*++lp
!= '\0')
857 } else if (bp
&& inhead
&& !ascncasecmp(buf
, "x-status", 8)) {
859 while (whitechar(*lp
))
862 while (*++lp
!= '\0')
875 } while (bp
!= NULL
);
884 maildir_remove(char const *name
)
889 if (subdir_remove(name
, "tmp") == STOP
||
890 subdir_remove(name
, "new") == STOP
||
891 subdir_remove(name
, "cur") == STOP
)
893 if (rmdir(name
) < 0) {
903 /* vim:set fenc=utf-8:s-it-mode */