1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Maildir folder support. FIXME rewrite - why do we chdir(2)??
3 *@ FIXME indeed - my S-Postman Python (!) is faster dealing with maildir!!
5 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
6 * Copyright (c) 2012 - 2014 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
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
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
41 #ifndef HAVE_AMALGAMATION
48 struct message
*md_data
;
52 static struct mditem
*_maildir_table
;
53 static ui32_t _maildir_prime
;
54 static sigjmp_buf _maildir_jmp
;
56 static void __maildircatch(int s
);
58 /* Do some cleanup in the tmp/ subdir */
59 static void _cleantmp(void);
61 static int maildir_setfile1(char const *name
, int nmail
,
64 /* In combination with the names from mkname(), this comparison function
65 * ensures that the order of messages in a maildir folder created by mailx
66 * remains always the same. In effect, if a mbox folder is transferred to
67 * a maildir folder by 'copy *', the message order wont' change */
68 static int mdcmp(void const *a
, void const *b
);
70 static int subdir(char const *name
, char const *sub
, int nmail
);
72 static void _maildir_append(char const *name
, char const *sub
,
75 static void readin(char const *name
, struct message
*m
);
77 static void maildir_update(void);
79 static void move(struct message
*m
);
81 static char * mkname(time_t t
, enum mflag f
, char const *pref
);
83 static enum okay
maildir_append1(char const *name
, FILE *fp
, off_t off1
,
84 long size
, enum mflag flag
);
86 static enum okay
trycreate(char const *name
);
88 static enum okay
mkmaildir(char const *name
);
90 static struct message
* mdlook(char const *name
, struct message
*data
);
92 static void mktable(void);
94 static enum okay
subdir_remove(char const *name
, char const *sub
);
99 NYD_X
; /* Signal handler */
100 siglongjmp(_maildir_jmp
, s
);
113 if ((dirp
= opendir("tmp")) == NULL
)
117 while ((dp
= readdir(dirp
)) != NULL
) {
118 if (dp
->d_name
[0] == '.')
120 sstpcpy(sstpcpy(dep
, "tmp/"), dp
->d_name
);
121 if (stat(dep
, &st
) < 0)
123 if (st
.st_atime
+ 36*3600 < now
)
132 maildir_setfile1(char const *name
, int nmail
, int omsgCount
)
140 mb
.mb_perm
= (options
& OPT_R_FLAG
) ? 0 : MB_DELE
;
141 if ((i
= subdir(name
, "cur", nmail
)) != 0)
143 if ((i
= subdir(name
, "new", nmail
)) != 0)
145 _maildir_append(name
, NULL
, NULL
);
146 for (i
= (nmail
? omsgCount
: 0); i
< msgCount
; ++i
)
147 readin(name
, message
+ i
);
149 if (msgCount
> omsgCount
)
150 qsort(&message
[omsgCount
], msgCount
- omsgCount
, sizeof *message
,
153 qsort(message
, msgCount
, sizeof *message
, &mdcmp
);
161 mdcmp(void const *a
, void const *b
)
163 struct message
const *mpa
= a
, *mpb
= b
;
167 if ((i
= mpa
->m_time
- mpb
->m_time
) == 0)
168 i
= strcmp(mpa
->m_maildir_file
+ 4, mpb
->m_maildir_file
+ 4);
174 subdir(char const *name
, char const *sub
, int nmail
)
181 if ((dirp
= opendir(sub
)) == NULL
) {
182 fprintf(stderr
, "Cannot open directory \"%s/%s\".\n", name
, sub
);
186 if (access(sub
, W_OK
) == -1)
188 while ((dp
= readdir(dirp
)) != NULL
) {
189 if (dp
->d_name
[0] == '.' && (dp
->d_name
[1] == '\0' ||
190 (dp
->d_name
[1] == '.' && dp
->d_name
[2] == '\0')))
192 if (dp
->d_name
[0] == '.')
194 if (!nmail
|| mdlook(dp
->d_name
, NULL
) == NULL
)
195 _maildir_append(name
, sub
, dp
->d_name
);
205 _maildir_append(char const *name
, char const *sub
, char const *fn
)
210 enum mflag f
= MUSED
| MNOFROM
| MNEWEST
;
216 if (fn
!= NULL
&& sub
!= NULL
) {
217 if (!strcmp(sub
, "new"))
219 t
= strtol(fn
, &xp
, 10);
220 if ((cp
= strrchr(xp
, ',')) != NULL
&& PTRCMP(cp
, >, xp
+ 2) &&
221 cp
[-1] == '2' && cp
[-2] == ':') {
222 while (*++cp
!= '\0') {
244 /* Ensure room (and a NULLified last entry) */
246 message_append(NULL
);
249 if (fn
== NULL
|| sub
== NULL
)
252 m
= message
+ msgCount
++;
254 m
->m_maildir_file
= smalloc((sz
= strlen(sub
)) + i
+ 1 +1);
255 memcpy(m
->m_maildir_file
, sub
, sz
);
256 m
->m_maildir_file
[sz
] = '/';
257 memcpy(m
->m_maildir_file
+ sz
+ 1, fn
, i
+1);
260 m
->m_maildir_hash
= ~pjw(fn
);
267 readin(char const *name
, struct message
*m
)
270 size_t bufsize
, buflen
, cnt
;
271 long size
= 0, lines
= 0;
277 if ((fp
= Fopen(m
->m_maildir_file
, "r")) == NULL
) {
278 fprintf(stderr
, "Cannot read \"%s/%s\" for message %d\n",
279 name
, m
->m_maildir_file
, (int)PTR2SIZE(m
- message
+ 1));
280 m
->m_flag
|= MHIDDEN
;
285 fseek(mb
.mb_otf
, 0L, SEEK_END
);
286 offset
= ftell(mb
.mb_otf
);
287 buf
= smalloc(bufsize
= LINESIZE
);
289 while (fgetline(&buf
, &bufsize
, &cnt
, &buflen
, fp
, 1) != NULL
) {
290 /* Since we simply copy over data without doing any transfer
291 * encoding reclassification/adjustment we *have* to perform
292 * RFC 4155 compliant From_ quoting here */
293 if (is_head(buf
, buflen
)) {
294 putc('>', mb
.mb_otf
);
297 size
+= fwrite(buf
, 1, buflen
, mb
.mb_otf
);/*XXX err hdling*/
298 emptyline
= (*buf
== '\n');
303 putc('\n', mb
.mb_otf
);
310 m
->m_size
= m
->m_xsize
= size
;
311 m
->m_lines
= m
->m_xlines
= lines
;
312 m
->m_block
= mailx_blockof(offset
);
313 m
->m_offset
= mailx_offsetof(offset
);
323 int dodel
, c
, gotcha
= 0, held
= 0, modflags
= 0;
331 for (m
= message
, c
= 0; PTRCMP(m
, <, message
+ msgCount
); ++m
) {
332 if (m
->m_flag
& MBOX
)
336 if (makembox() == STOP
)
339 for (m
= message
, gotcha
= 0, held
= 0; PTRCMP(m
, <, message
+ msgCount
);
342 dodel
= m
->m_flag
& MDELETED
;
344 dodel
= !((m
->m_flag
& MPRESERVE
) || !(m
->m_flag
& MTOUCH
));
346 if (unlink(m
->m_maildir_file
) < 0)
347 fprintf(stderr
, /* TODO tr */
348 "Cannot delete file \"%s/%s\" for message %d.\n",
349 mailname
, m
->m_maildir_file
, (int)PTR2SIZE(m
- message
+ 1));
353 if ((m
->m_flag
& (MREAD
| MSTATUS
)) == (MREAD
| MSTATUS
) ||
354 (m
->m_flag
& (MNEW
| MBOXED
| MSAVED
| MSTATUS
| MFLAG
|
355 MUNFLAG
| MANSWER
| MUNANSWER
| MDRAFT
| MUNDRAFT
))) {
363 if ((gotcha
|| modflags
) && edit
) {
364 printf(tr(168, "\"%s\" "), displayname
);
365 printf((ok_blook(bsdcompat
) || ok_blook(bsdmsgs
))
366 ? tr(170, "complete\n") : tr(212, "updated.\n"));
367 } else if (held
&& !edit
&& mb
.mb_perm
!= 0) {
369 printf(tr(155, "Held 1 message in %s\n"), displayname
);
371 printf(tr(156, "Held %d messages in %s\n"), held
, displayname
);
375 for (m
= message
; PTRCMP(m
, <, message
+ msgCount
); ++m
)
376 free(m
->m_maildir_file
);
381 move(struct message
*m
)
386 fn
= mkname(0, m
->m_flag
, m
->m_maildir_file
+ 4);
387 new = savecat("cur/", fn
);
388 if (!strcmp(m
->m_maildir_file
, new))
390 if (link(m
->m_maildir_file
, new) == -1) {
391 fprintf(stderr
, /* TODO tr */
392 "Cannot link \"%s/%s\" to \"%s/%s\": message %d not touched.\n",
393 mailname
, m
->m_maildir_file
, mailname
, new,
394 (int)PTR2SIZE(m
- message
+ 1));
397 if (unlink(m
->m_maildir_file
) == -1)
398 fprintf(stderr
, /* TODO tr */"Cannot unlink \"%s/%s\".\n",
399 mailname
, m
->m_maildir_file
);
405 mkname(time_t t
, enum mflag f
, char const *pref
)
407 static unsigned long cnt
;
408 static pid_t mypid
; /* XXX This should possibly be global, somehow */
422 if (UICMP(32, n
, <, size
+ 8))
423 node
= srealloc(node
, size
+= 20);
426 node
[n
++] = '\\', node
[n
++] = '0',
427 node
[n
++] = '5', node
[n
++] = '7';
430 node
[n
++] = '\\', node
[n
++] = '0',
431 node
[n
++] = '7', node
[n
++] = '2';
436 } while (*cp
++ != '\0');
438 size
= 60 + strlen(node
);
440 n
= snprintf(cp
, size
, "%lu.%06lu_%06lu.%s:2,",
441 (ul_it
)t
, (ul_it
)mypid
, ++cnt
, node
);
443 size
= (n
= strlen(pref
)) + 13;
445 memcpy(cp
, pref
, n
+1);
446 for (i
= n
; i
> 3; --i
)
447 if (cp
[i
- 1] == ',' && cp
[i
- 2] == '2' && cp
[i
- 3] == ':') {
452 memcpy(cp
+ n
, ":2,", 3 +1);
474 maildir_append1(char const *name
, FILE *fp
, off_t off1
, long size
,
477 int const attempts
= 43200; /* XXX no magic */
478 char buf
[4096], *fn
, *tmp
, *new;
487 /* Create a unique temporary file */
488 for (i
= 0;; sleep(1), ++i
) {
490 fprintf(stderr
, tr(198,
491 "Can't create an unique file name in \"%s/tmp\".\n"), name
);
496 fn
= mkname(now
, flag
, NULL
);
497 tmp
= salloc(n
= strlen(name
) + strlen(fn
) + 6);
498 snprintf(tmp
, n
, "%s/tmp/%s", name
, fn
);
499 if (stat(tmp
, &st
) >= 0 || errno
!= ENOENT
)
502 /* Use "wx" for O_EXCL */
503 if ((op
= Fopen(tmp
, "wx")) != NULL
)
507 if (fseek(fp
, off1
, SEEK_SET
) == -1)
510 z
= size
> (long)sizeof buf
? (long)sizeof buf
: size
;
511 if ((n
= fread(buf
, 1, z
, fp
)) != z
||
512 (size_t)n
!= fwrite(buf
, 1, n
, op
)) {
514 fprintf(stderr
, "Error writing to \"%s\".\n", tmp
); /* TODO tr */
523 new = salloc(n
= strlen(name
) + strlen(fn
) + 6);
524 snprintf(new, n
, "%s/new/%s", name
, fn
);
525 if (link(tmp
, new) == -1) {
526 fprintf(stderr
, "Cannot link \"%s\" to \"%s\".\n", tmp
, new);/* TODO tr */
529 if (unlink(tmp
) == -1)
530 fprintf(stderr
, "Cannot unlink \"%s\".\n", tmp
); /* TODO tr */
538 trycreate(char const *name
)
544 if (!stat(name
, &st
)) {
545 if (!S_ISDIR(st
.st_mode
)) {
546 fprintf(stderr
, "\"%s\" is not a directory.\n", name
);/* TODO tr */
549 } else if (makedir(name
) != OKAY
) {
550 fprintf(stderr
, "Cannot create directory \"%s\".\n", name
);/* TODO tr */
553 ++imap_created_mailbox
;
561 mkmaildir(char const *name
) /* TODO proper cleanup on error; use path[] loop */
568 if (trycreate(name
) == OKAY
) {
569 np
= ac_alloc((sz
= strlen(name
)) + 4 +1);
570 memcpy(np
, name
, sz
);
571 memcpy(np
+ sz
, "/tmp", 4 +1);
572 if (trycreate(np
) == OKAY
) {
573 memcpy(np
+ sz
, "/new", 4 +1);
574 if (trycreate(np
) == OKAY
) {
575 memcpy(np
+ sz
, "/cur", 4 +1);
585 static struct message
*
586 mdlook(char const *name
, struct message
*data
)
592 if (data
&& data
->m_maildir_hash
)
593 h
= ~data
->m_maildir_hash
;
598 md
= _maildir_table
+ c
;
600 while (md
->md_data
!= NULL
) {
601 if (!strcmp(md
->md_data
->m_maildir_file
+ 4, name
))
603 c
+= (n
& 1) ? -((n
+1)/2) * ((n
+1)/2) : ((n
+1)/2) * ((n
+1)/2);
605 while (c
>= _maildir_prime
)
607 md
= _maildir_table
+ c
;
609 if (data
!= NULL
&& md
->md_data
== NULL
)
622 _maildir_prime
= nextprime(msgCount
);
623 _maildir_table
= scalloc(_maildir_prime
, sizeof *_maildir_table
);
624 for (mp
= message
, i
= msgCount
; i
-- != 0;)
625 mdlook(mp
->m_maildir_file
+ 4, mp
);
630 subdir_remove(char const *name
, char const *sub
)
633 int pathsize
, pathend
, namelen
, sublen
, n
;
639 namelen
= strlen(name
);
640 sublen
= strlen(sub
);
641 path
= smalloc(pathsize
= namelen
+ sublen
+ 30 +1);
642 memcpy(path
, name
, namelen
);
644 memcpy(path
+ namelen
+ 1, sub
, sublen
);
645 path
[namelen
+ sublen
+ 1] = '/';
646 path
[pathend
= namelen
+ sublen
+ 2] = '\0';
648 if ((dirp
= opendir(path
)) == NULL
) {
652 while ((dp
= readdir(dirp
)) != NULL
) {
653 if (dp
->d_name
[0] == '.' && (dp
->d_name
[1] == '\0' ||
654 (dp
->d_name
[1] == '.' && dp
->d_name
[2] == '\0')))
656 if (dp
->d_name
[0] == '.')
658 n
= strlen(dp
->d_name
);
659 if (UICMP(32, pathend
+ n
+1, >, pathsize
))
660 path
= srealloc(path
, pathsize
= pathend
+ n
+ 30);
661 memcpy(path
+ pathend
, dp
->d_name
, n
+1);
662 if (unlink(path
) == -1) {
670 path
[pathend
] = '\0';
671 if (rmdir(path
) == -1) {
684 maildir_setfile(char const * volatile name
, int nmail
, int isedit
)
686 sighandler_type
volatile saveint
;
688 int i
= -1, omsgCount
;
691 omsgCount
= msgCount
;
692 if (cwget(&cw
) == STOP
) {
693 alert("Cannot open current directory");
700 saveint
= safe_signal(SIGINT
, SIG_IGN
);
703 edit
= (isedit
!= 0);
713 mb
.mb_type
= MB_MAILDIR
;
716 if (chdir(name
) < 0) {
717 fprintf(stderr
, "Cannot change directory to \"%s\".\n", name
);/*TODO tr*/
718 mb
.mb_type
= MB_VOID
;
722 safe_signal(SIGINT
, saveint
);
726 _maildir_table
= NULL
;
727 if (sigsetjmp(_maildir_jmp
, 1) == 0) {
730 if (saveint
!= SIG_IGN
)
731 safe_signal(SIGINT
, &__maildircatch
);
732 i
= maildir_setfile1(name
, nmail
, omsgCount
);
734 if (nmail
&& _maildir_table
!= NULL
)
735 free(_maildir_table
);
737 safe_signal(SIGINT
, saveint
);
740 mb
.mb_type
= MB_VOID
;
745 if (cwret(&cw
) == STOP
)
746 panic("Cannot change back to current directory.");/* TODO tr */
750 if (nmail
&& mb
.mb_sorted
&& msgCount
> omsgCount
) {
756 if (!nmail
&& !edit
&& msgCount
== 0) {
757 if (mb
.mb_type
== MB_MAILDIR
/* XXX ?? */ && !ok_blook(emptystart
))
758 fprintf(stderr
, tr(258, "No mail at %s\n"), name
);
762 if (nmail
&& msgCount
> omsgCount
)
763 newmailinfo(omsgCount
);
773 sighandler_type saveint
;
777 if (cwget(&cw
) == STOP
) {
778 alert("Cannot open current directory");/* TODO tr */
782 saveint
= safe_signal(SIGINT
, SIG_IGN
);
784 if (chdir(mailname
) == -1) {
785 fprintf(stderr
, "Cannot change directory to \"%s\".\n",/* TODO tr */
788 safe_signal(SIGINT
, saveint
);
792 if (sigsetjmp(_maildir_jmp
, 1) == 0) {
793 if (saveint
!= SIG_IGN
)
794 safe_signal(SIGINT
, &__maildircatch
);
798 safe_signal(SIGINT
, saveint
);
800 if (cwret(&cw
) == STOP
)
801 panic("Cannot change back to current directory."); /* TODO tr */
808 maildir_append(char const *name
, FILE *fp
)
811 size_t bufsize
, buflen
, cnt
;
812 off_t off1
= -1, offs
;
813 int inhead
= 1, flag
= MNEW
| MNEWEST
;
818 if ((rv
= mkmaildir(name
)) != OKAY
)
821 buf
= smalloc(bufsize
= LINESIZE
);
825 do /* while (bp != NULL); */ {
826 bp
= fgetline(&buf
, &bufsize
, &cnt
, &buflen
, fp
, 1);
827 if (bp
== NULL
|| !strncmp(buf
, "From ", 5)) {
828 if (off1
!= (off_t
)-1) {
829 rv
= maildir_append1(name
, fp
, off1
, size
, flag
);
832 if (fseek(fp
, offs
+ buflen
, SEEK_SET
) == -1) {
837 off1
= offs
+ buflen
;
844 if (bp
&& buf
[0] == '\n')
846 else if (bp
&& inhead
&& !ascncasecmp(buf
, "status", 6)) {
848 while (whitechar(*lp
))
851 while (*++lp
!= '\0')
860 } else if (bp
&& inhead
&& !ascncasecmp(buf
, "x-status", 8)) {
862 while (whitechar(*lp
))
865 while (*++lp
!= '\0')
878 } while (bp
!= NULL
);
887 maildir_remove(char const *name
)
892 if (subdir_remove(name
, "tmp") == STOP
||
893 subdir_remove(name
, "new") == STOP
||
894 subdir_remove(name
, "cur") == STOP
)
896 if (rmdir(name
) == -1) {
906 /* vim:set fenc=utf-8:s-it-mode */