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
, enum fedit_mode fm
,
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 _maildir_subdir(char const *name
, char const *sub
,
73 static void _maildir_append(char const *name
, char const *sub
,
76 static void readin(char const *name
, struct message
*m
);
78 static void maildir_update(void);
80 static void move(struct message
*m
);
82 static char * mkname(time_t t
, enum mflag f
, char const *pref
);
84 static enum okay
maildir_append1(char const *name
, FILE *fp
, off_t off1
,
85 long size
, enum mflag flag
);
87 static enum okay
trycreate(char const *name
);
89 static enum okay
mkmaildir(char const *name
);
91 static struct message
* mdlook(char const *name
, struct message
*data
);
93 static void mktable(void);
95 static enum okay
subdir_remove(char const *name
, char const *sub
);
100 NYD_X
; /* Signal handler */
101 siglongjmp(_maildir_jmp
, s
);
114 if ((dirp
= opendir("tmp")) == NULL
)
118 while ((dp
= readdir(dirp
)) != NULL
) {
119 if (dp
->d_name
[0] == '.')
121 sstpcpy(sstpcpy(dep
, "tmp/"), dp
->d_name
);
122 if (stat(dep
, &st
) < 0)
124 if (st
.st_atime
+ 36*3600 < now
)
133 _maildir_setfile1(char const *name
, enum fedit_mode fm
, int omsgCount
)
138 if (!(fm
& FEDIT_NEWMAIL
))
141 mb
.mb_perm
= ((options
& OPT_R_FLAG
) || (fm
& FEDIT_RDONLY
)) ? 0 : MB_DELE
;
142 if ((i
= _maildir_subdir(name
, "cur", fm
)) != 0)
144 if ((i
= _maildir_subdir(name
, "new", fm
)) != 0)
146 _maildir_append(name
, NULL
, NULL
);
147 for (i
= ((fm
& FEDIT_NEWMAIL
) ? omsgCount
: 0); i
< msgCount
; ++i
)
148 readin(name
, message
+ i
);
149 if (fm
& FEDIT_NEWMAIL
) {
150 if (msgCount
> omsgCount
)
151 qsort(&message
[omsgCount
], msgCount
- omsgCount
, sizeof *message
,
154 qsort(message
, msgCount
, sizeof *message
, &mdcmp
);
162 mdcmp(void const *a
, void const *b
)
164 struct message
const *mpa
= a
, *mpb
= b
;
168 if ((i
= mpa
->m_time
- mpb
->m_time
) == 0)
169 i
= strcmp(mpa
->m_maildir_file
+ 4, mpb
->m_maildir_file
+ 4);
175 _maildir_subdir(char const *name
, char const *sub
, enum fedit_mode fm
)
182 if ((dirp
= opendir(sub
)) == NULL
) {
183 fprintf(stderr
, "Cannot open directory \"%s/%s\".\n", name
, sub
);
187 if (access(sub
, W_OK
) == -1)
189 while ((dp
= readdir(dirp
)) != NULL
) {
190 if (dp
->d_name
[0] == '.' && (dp
->d_name
[1] == '\0' ||
191 (dp
->d_name
[1] == '.' && dp
->d_name
[2] == '\0')))
193 if (dp
->d_name
[0] == '.')
195 if (!(fm
& FEDIT_NEWMAIL
) || mdlook(dp
->d_name
, NULL
) == NULL
)
196 _maildir_append(name
, sub
, dp
->d_name
);
206 _maildir_append(char const *name
, char const *sub
, char const *fn
)
211 enum mflag f
= MUSED
| MNOFROM
| MNEWEST
;
217 if (fn
!= NULL
&& sub
!= NULL
) {
218 if (!strcmp(sub
, "new"))
220 t
= strtol(fn
, &xp
, 10);
221 if ((cp
= strrchr(xp
, ',')) != NULL
&& PTRCMP(cp
, >, xp
+ 2) &&
222 cp
[-1] == '2' && cp
[-2] == ':') {
223 while (*++cp
!= '\0') {
245 /* Ensure room (and a NULLified last entry) */
247 message_append(NULL
);
250 if (fn
== NULL
|| sub
== NULL
)
253 m
= message
+ msgCount
++;
255 m
->m_maildir_file
= smalloc((sz
= strlen(sub
)) + i
+ 1 +1);
256 memcpy(m
->m_maildir_file
, sub
, sz
);
257 m
->m_maildir_file
[sz
] = '/';
258 memcpy(m
->m_maildir_file
+ sz
+ 1, fn
, i
+1);
261 m
->m_maildir_hash
= ~pjw(fn
);
268 readin(char const *name
, struct message
*m
)
271 size_t bufsize
, buflen
, cnt
;
272 long size
= 0, lines
= 0;
278 if ((fp
= Fopen(m
->m_maildir_file
, "r")) == NULL
) {
279 fprintf(stderr
, "Cannot read \"%s/%s\" for message %d\n",
280 name
, m
->m_maildir_file
, (int)PTR2SIZE(m
- message
+ 1));
281 m
->m_flag
|= MHIDDEN
;
286 fseek(mb
.mb_otf
, 0L, SEEK_END
);
287 offset
= ftell(mb
.mb_otf
);
288 buf
= smalloc(bufsize
= LINESIZE
);
290 while (fgetline(&buf
, &bufsize
, &cnt
, &buflen
, fp
, 1) != NULL
) {
291 /* Since we simply copy over data without doing any transfer
292 * encoding reclassification/adjustment we *have* to perform
293 * RFC 4155 compliant From_ quoting here */
294 if (is_head(buf
, buflen
)) {
295 putc('>', mb
.mb_otf
);
298 size
+= fwrite(buf
, 1, buflen
, mb
.mb_otf
);/*XXX err hdling*/
299 emptyline
= (*buf
== '\n');
304 putc('\n', mb
.mb_otf
);
311 m
->m_size
= m
->m_xsize
= size
;
312 m
->m_lines
= m
->m_xlines
= lines
;
313 m
->m_block
= mailx_blockof(offset
);
314 m
->m_offset
= mailx_offsetof(offset
);
324 int dodel
, c
, gotcha
= 0, held
= 0, modflags
= 0;
332 for (m
= message
, c
= 0; PTRCMP(m
, <, message
+ msgCount
); ++m
) {
333 if (m
->m_flag
& MBOX
)
337 if (makembox() == STOP
)
340 for (m
= message
, gotcha
= 0, held
= 0; PTRCMP(m
, <, message
+ msgCount
);
343 dodel
= m
->m_flag
& MDELETED
;
345 dodel
= !((m
->m_flag
& MPRESERVE
) || !(m
->m_flag
& MTOUCH
));
347 if (unlink(m
->m_maildir_file
) < 0)
348 fprintf(stderr
, /* TODO tr */
349 "Cannot delete file \"%s/%s\" for message %d.\n",
350 mailname
, m
->m_maildir_file
, (int)PTR2SIZE(m
- message
+ 1));
354 if ((m
->m_flag
& (MREAD
| MSTATUS
)) == (MREAD
| MSTATUS
) ||
355 (m
->m_flag
& (MNEW
| MBOXED
| MSAVED
| MSTATUS
| MFLAG
|
356 MUNFLAG
| MANSWER
| MUNANSWER
| MDRAFT
| MUNDRAFT
))) {
364 if ((gotcha
|| modflags
) && edit
) {
365 printf(_("\"%s\" "), displayname
);
366 printf((ok_blook(bsdcompat
) || ok_blook(bsdmsgs
))
367 ? _("complete\n") : _("updated.\n"));
368 } else if (held
&& !edit
&& mb
.mb_perm
!= 0) {
370 printf(_("Held 1 message in %s\n"), displayname
);
372 printf(_("Held %d messages in %s\n"), held
, displayname
);
376 for (m
= message
; PTRCMP(m
, <, message
+ msgCount
); ++m
)
377 free(m
->m_maildir_file
);
382 move(struct message
*m
)
387 fn
= mkname(0, m
->m_flag
, m
->m_maildir_file
+ 4);
388 new = savecat("cur/", fn
);
389 if (!strcmp(m
->m_maildir_file
, new))
391 if (link(m
->m_maildir_file
, new) == -1) {
392 fprintf(stderr
, /* TODO tr */
393 "Cannot link \"%s/%s\" to \"%s/%s\": message %d not touched.\n",
394 mailname
, m
->m_maildir_file
, mailname
, new,
395 (int)PTR2SIZE(m
- message
+ 1));
398 if (unlink(m
->m_maildir_file
) == -1)
399 fprintf(stderr
, /* TODO tr */"Cannot unlink \"%s/%s\".\n",
400 mailname
, m
->m_maildir_file
);
406 mkname(time_t t
, enum mflag f
, char const *pref
)
408 static unsigned long cnt
;
409 static pid_t mypid
; /* XXX This should possibly be global, somehow */
423 if (UICMP(32, n
, <, size
+ 8))
424 node
= srealloc(node
, size
+= 20);
427 node
[n
++] = '\\', node
[n
++] = '0',
428 node
[n
++] = '5', node
[n
++] = '7';
431 node
[n
++] = '\\', node
[n
++] = '0',
432 node
[n
++] = '7', node
[n
++] = '2';
437 } while (*cp
++ != '\0');
439 size
= 60 + strlen(node
);
441 n
= snprintf(cp
, size
, "%lu.%06lu_%06lu.%s:2,",
442 (ul_it
)t
, (ul_it
)mypid
, ++cnt
, node
);
444 size
= (n
= strlen(pref
)) + 13;
446 memcpy(cp
, pref
, n
+1);
447 for (i
= n
; i
> 3; --i
)
448 if (cp
[i
- 1] == ',' && cp
[i
- 2] == '2' && cp
[i
- 3] == ':') {
453 memcpy(cp
+ n
, ":2,", 3 +1);
475 maildir_append1(char const *name
, FILE *fp
, off_t off1
, long size
,
478 int const attempts
= 43200; /* XXX no magic */
479 char buf
[4096], *fn
, *tmp
, *new;
488 /* Create a unique temporary file */
489 for (i
= 0;; sleep(1), ++i
) {
492 "Can't create an unique file name in \"%s/tmp\".\n"), name
);
497 fn
= mkname(now
, flag
, NULL
);
498 tmp
= salloc(n
= strlen(name
) + strlen(fn
) + 6);
499 snprintf(tmp
, n
, "%s/tmp/%s", name
, fn
);
500 if (stat(tmp
, &st
) >= 0 || errno
!= ENOENT
)
503 /* Use "wx" for O_EXCL */
504 if ((op
= Fopen(tmp
, "wx")) != NULL
)
508 if (fseek(fp
, off1
, SEEK_SET
) == -1)
511 z
= size
> (long)sizeof buf
? (long)sizeof buf
: size
;
512 if ((n
= fread(buf
, 1, z
, fp
)) != z
||
513 (size_t)n
!= fwrite(buf
, 1, n
, op
)) {
515 fprintf(stderr
, "Error writing to \"%s\".\n", tmp
); /* TODO tr */
524 new = salloc(n
= strlen(name
) + strlen(fn
) + 6);
525 snprintf(new, n
, "%s/new/%s", name
, fn
);
526 if (link(tmp
, new) == -1) {
527 fprintf(stderr
, "Cannot link \"%s\" to \"%s\".\n", tmp
, new);/* TODO tr */
530 if (unlink(tmp
) == -1)
531 fprintf(stderr
, "Cannot unlink \"%s\".\n", tmp
); /* TODO tr */
539 trycreate(char const *name
)
545 if (!stat(name
, &st
)) {
546 if (!S_ISDIR(st
.st_mode
)) {
547 fprintf(stderr
, "\"%s\" is not a directory.\n", name
);/* TODO tr */
550 } else if (makedir(name
) != OKAY
) {
551 fprintf(stderr
, "Cannot create directory \"%s\".\n", name
);/* TODO tr */
554 ++imap_created_mailbox
;
562 mkmaildir(char const *name
) /* TODO proper cleanup on error; use path[] loop */
569 if (trycreate(name
) == OKAY
) {
570 np
= ac_alloc((sz
= strlen(name
)) + 4 +1);
571 memcpy(np
, name
, sz
);
572 memcpy(np
+ sz
, "/tmp", 4 +1);
573 if (trycreate(np
) == OKAY
) {
574 memcpy(np
+ sz
, "/new", 4 +1);
575 if (trycreate(np
) == OKAY
) {
576 memcpy(np
+ sz
, "/cur", 4 +1);
586 static struct message
*
587 mdlook(char const *name
, struct message
*data
)
593 if (data
&& data
->m_maildir_hash
)
594 h
= ~data
->m_maildir_hash
;
599 md
= _maildir_table
+ c
;
601 while (md
->md_data
!= NULL
) {
602 if (!strcmp(md
->md_data
->m_maildir_file
+ 4, name
))
604 c
+= (n
& 1) ? -((n
+1)/2) * ((n
+1)/2) : ((n
+1)/2) * ((n
+1)/2);
606 while (c
>= _maildir_prime
)
608 md
= _maildir_table
+ c
;
610 if (data
!= NULL
&& md
->md_data
== NULL
)
623 _maildir_prime
= nextprime(msgCount
);
624 _maildir_table
= scalloc(_maildir_prime
, sizeof *_maildir_table
);
625 for (mp
= message
, i
= msgCount
; i
-- != 0;)
626 mdlook(mp
->m_maildir_file
+ 4, mp
);
631 subdir_remove(char const *name
, char const *sub
)
634 int pathsize
, pathend
, namelen
, sublen
, n
;
640 namelen
= strlen(name
);
641 sublen
= strlen(sub
);
642 path
= smalloc(pathsize
= namelen
+ sublen
+ 30 +1);
643 memcpy(path
, name
, namelen
);
645 memcpy(path
+ namelen
+ 1, sub
, sublen
);
646 path
[namelen
+ sublen
+ 1] = '/';
647 path
[pathend
= namelen
+ sublen
+ 2] = '\0';
649 if ((dirp
= opendir(path
)) == NULL
) {
653 while ((dp
= readdir(dirp
)) != NULL
) {
654 if (dp
->d_name
[0] == '.' && (dp
->d_name
[1] == '\0' ||
655 (dp
->d_name
[1] == '.' && dp
->d_name
[2] == '\0')))
657 if (dp
->d_name
[0] == '.')
659 n
= strlen(dp
->d_name
);
660 if (UICMP(32, pathend
+ n
+1, >, pathsize
))
661 path
= srealloc(path
, pathsize
= pathend
+ n
+ 30);
662 memcpy(path
+ pathend
, dp
->d_name
, n
+1);
663 if (unlink(path
) == -1) {
671 path
[pathend
] = '\0';
672 if (rmdir(path
) == -1) {
685 maildir_setfile(char const * volatile name
, enum fedit_mode fm
)
687 sighandler_type
volatile saveint
;
689 int i
= -1, omsgCount
;
692 omsgCount
= msgCount
;
693 if (cwget(&cw
) == STOP
) {
694 alert("Cannot open current directory");
698 if (!(fm
& FEDIT_NEWMAIL
))
701 saveint
= safe_signal(SIGINT
, SIG_IGN
);
703 if (!(fm
& FEDIT_NEWMAIL
)) {
704 edit
= !(fm
& FEDIT_SYSBOX
);
714 mb
.mb_type
= MB_MAILDIR
;
717 if (chdir(name
) < 0) {
718 fprintf(stderr
, "Cannot change directory to \"%s\".\n", name
);/*TODO tr*/
719 mb
.mb_type
= MB_VOID
;
723 safe_signal(SIGINT
, saveint
);
727 _maildir_table
= NULL
;
728 if (sigsetjmp(_maildir_jmp
, 1) == 0) {
729 if (fm
& FEDIT_NEWMAIL
)
731 if (saveint
!= SIG_IGN
)
732 safe_signal(SIGINT
, &__maildircatch
);
733 i
= _maildir_setfile1(name
, fm
, omsgCount
);
735 if ((fm
& FEDIT_NEWMAIL
) && _maildir_table
!= NULL
)
736 free(_maildir_table
);
738 safe_signal(SIGINT
, saveint
);
741 mb
.mb_type
= MB_VOID
;
746 if (cwret(&cw
) == STOP
)
747 panic("Cannot change back to current directory.");/* TODO tr */
751 if ((fm
& FEDIT_NEWMAIL
) && mb
.mb_sorted
&& msgCount
> omsgCount
) {
755 if (!(fm
& FEDIT_NEWMAIL
))
757 if (!(fm
& FEDIT_NEWMAIL
) && (fm
& FEDIT_SYSBOX
) && msgCount
== 0) {
758 if (mb
.mb_type
== MB_MAILDIR
/* XXX ?? */ && !ok_blook(emptystart
))
759 fprintf(stderr
, _("No mail at %s\n"), name
);
763 if ((fm
& FEDIT_NEWMAIL
) && msgCount
> omsgCount
)
764 newmailinfo(omsgCount
);
774 sighandler_type saveint
;
778 if (cwget(&cw
) == STOP
) {
779 alert("Cannot open current directory");/* TODO tr */
783 saveint
= safe_signal(SIGINT
, SIG_IGN
);
785 if (chdir(mailname
) == -1) {
786 fprintf(stderr
, "Cannot change directory to \"%s\".\n",/* TODO tr */
789 safe_signal(SIGINT
, saveint
);
793 if (sigsetjmp(_maildir_jmp
, 1) == 0) {
794 if (saveint
!= SIG_IGN
)
795 safe_signal(SIGINT
, &__maildircatch
);
799 safe_signal(SIGINT
, saveint
);
801 if (cwret(&cw
) == STOP
)
802 panic("Cannot change back to current directory."); /* TODO tr */
809 maildir_append(char const *name
, FILE *fp
)
812 size_t bufsize
, buflen
, cnt
;
813 off_t off1
= -1, offs
;
814 int inhead
= 1, flag
= MNEW
| MNEWEST
;
819 if ((rv
= mkmaildir(name
)) != OKAY
)
822 buf
= smalloc(bufsize
= LINESIZE
);
826 do /* while (bp != NULL); */ {
827 bp
= fgetline(&buf
, &bufsize
, &cnt
, &buflen
, fp
, 1);
828 if (bp
== NULL
|| !strncmp(buf
, "From ", 5)) {
829 if (off1
!= (off_t
)-1) {
830 rv
= maildir_append1(name
, fp
, off1
, size
, flag
);
833 if (fseek(fp
, offs
+ buflen
, SEEK_SET
) == -1) {
838 off1
= offs
+ buflen
;
845 if (bp
&& buf
[0] == '\n')
847 else if (bp
&& inhead
&& !ascncasecmp(buf
, "status", 6)) {
849 while (whitechar(*lp
))
852 while (*++lp
!= '\0')
861 } else if (bp
&& inhead
&& !ascncasecmp(buf
, "x-status", 8)) {
863 while (whitechar(*lp
))
866 while (*++lp
!= '\0')
879 } while (bp
!= NULL
);
888 maildir_remove(char const *name
)
893 if (subdir_remove(name
, "tmp") == STOP
||
894 subdir_remove(name
, "new") == STOP
||
895 subdir_remove(name
, "cur") == STOP
)
897 if (rmdir(name
) == -1) {