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 if (msgCount
+ 1 >= msgspace
) {
244 int const chunk
= 64;
245 message
= srealloc(message
, (msgspace
+= chunk
) * sizeof *message
);
246 memset(&message
[msgCount
], 0, chunk
* sizeof *message
);
249 if (fn
== NULL
|| sub
== NULL
)
252 m
= &message
[msgCount
++];
254 m
->m_maildir_file
= smalloc((sz
= strlen(sub
)) + i
+ 2);
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) < 0) {
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
) < 0)
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';
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,", 4);
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
) < 0)
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) < 0) {
526 fprintf(stderr
, "Cannot link \"%s\" to \"%s\".\n", tmp
, new);/* TODO tr */
530 fprintf(stderr
, "Cannot unlink \"%s\".\n", tmp
); /* TODO tr */
538 trycreate(char const *name
)
544 if (stat(name
, &st
) == 0) {
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
)) + 5);
570 memcpy(np
, name
, sz
);
571 memcpy(np
+ sz
, "/tmp", 5);
572 if (trycreate(np
) == OKAY
) {
573 strcpy(&np
[sz
], "/new");
574 if (trycreate(np
) == OKAY
) {
575 strcpy(&np
[sz
], "/cur");
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
;
597 md
= &_maildir_table
[c
= h
];
599 while (md
->md_data
!= NULL
) {
600 if (!strcmp(&md
->md_data
->m_maildir_file
[4], name
))
602 c
+= n
&1 ? -((n
+1)/2) * ((n
+1)/2) : ((n
+1)/2) * ((n
+1)/2);
604 while (c
>= _maildir_prime
)
606 md
= &_maildir_table
[c
];
608 if (data
!= NULL
&& md
->md_data
== NULL
)
611 return md
->md_data
? md
->md_data
: NULL
;
620 _maildir_prime
= nextprime(msgCount
);
621 _maildir_table
= scalloc(_maildir_prime
, sizeof *_maildir_table
);
622 for (i
= 0; i
< msgCount
; i
++)
623 mdlook(&message
[i
].m_maildir_file
[4], &message
[i
]);
628 subdir_remove(char const *name
, char const *sub
)
631 int pathsize
, pathend
, namelen
, sublen
, n
;
637 namelen
= strlen(name
);
638 sublen
= strlen(sub
);
639 path
= smalloc(pathsize
= namelen
+ sublen
+ 30);
640 memcpy(path
, name
, namelen
);
642 memcpy(path
+ namelen
+ 1, sub
, sublen
);
643 path
[namelen
+sublen
+1] = '/';
644 path
[pathend
= namelen
+ sublen
+ 2] = '\0';
646 if ((dirp
= opendir(path
)) == NULL
) {
650 while ((dp
= readdir(dirp
)) != NULL
) {
651 if (dp
->d_name
[0] == '.' && (dp
->d_name
[1] == '\0' ||
652 (dp
->d_name
[1] == '.' && dp
->d_name
[2] == '\0')))
654 if (dp
->d_name
[0] == '.')
656 n
= strlen(dp
->d_name
);
657 if (UICMP(32, pathend
+ n
+ 1, >, pathsize
))
658 path
= srealloc(path
, pathsize
= pathend
+ n
+ 30);
659 memcpy(path
+ pathend
, dp
->d_name
, n
+ 1);
660 if (unlink(path
) < 0) {
668 path
[pathend
] = '\0';
669 if (rmdir(path
) < 0) {
682 maildir_setfile(char const * volatile name
, int nmail
, int isedit
)
684 sighandler_type
volatile saveint
;
686 int i
= -1, omsgCount
;
689 omsgCount
= msgCount
;
690 if (cwget(&cw
) == STOP
) {
691 alert("Cannot open current directory");
698 saveint
= safe_signal(SIGINT
, SIG_IGN
);
701 edit
= (isedit
!= 0);
711 mb
.mb_type
= MB_MAILDIR
;
714 if (chdir(name
) < 0) {
715 fprintf(stderr
, "Cannot change directory to \"%s\".\n", name
);/*TODO tr*/
716 mb
.mb_type
= MB_VOID
;
720 safe_signal(SIGINT
, saveint
);
724 _maildir_table
= NULL
;
725 if (sigsetjmp(_maildir_jmp
, 1) == 0) {
728 if (saveint
!= SIG_IGN
)
729 safe_signal(SIGINT
, &__maildircatch
);
730 i
= maildir_setfile1(name
, nmail
, omsgCount
);
732 if (nmail
&& _maildir_table
!= NULL
)
733 free(_maildir_table
);
735 safe_signal(SIGINT
, saveint
);
738 mb
.mb_type
= MB_VOID
;
743 if (cwret(&cw
) == STOP
)
744 panic("Cannot change back to current directory.");/* TODO tr */
748 if (nmail
&& mb
.mb_sorted
&& msgCount
> omsgCount
) {
754 if (!nmail
&& !edit
&& msgCount
== 0) {
755 if (mb
.mb_type
== MB_MAILDIR
/* XXX ?? */ && !ok_blook(emptystart
))
756 fprintf(stderr
, tr(258, "No mail at %s\n"), name
);
760 if (nmail
&& msgCount
> omsgCount
)
761 newmailinfo(omsgCount
);
771 sighandler_type saveint
;
775 if (cwget(&cw
) == STOP
) {
776 alert("Cannot open current directory");/* TODO tr */
780 saveint
= safe_signal(SIGINT
, SIG_IGN
);
782 if (chdir(mailname
) < 0) {
783 fprintf(stderr
, "Cannot change directory to \"%s\".\n",/* TODO tr */
786 safe_signal(SIGINT
, saveint
);
790 if (sigsetjmp(_maildir_jmp
, 1) == 0) {
791 if (saveint
!= SIG_IGN
)
792 safe_signal(SIGINT
, &__maildircatch
);
796 safe_signal(SIGINT
, saveint
);
798 if (cwret(&cw
) == STOP
)
799 panic("Cannot change back to current directory."); /* TODO tr */
806 maildir_append(char const *name
, FILE *fp
)
809 size_t bufsize
, buflen
, cnt
;
810 off_t off1
= -1, offs
;
811 int inhead
= 1, flag
= MNEW
| MNEWEST
;
816 if ((rv
= mkmaildir(name
)) != OKAY
)
819 buf
= smalloc(bufsize
= LINESIZE
);
823 do /* while (bp != NULL); */ {
824 bp
= fgetline(&buf
, &bufsize
, &cnt
, &buflen
, fp
, 1);
825 if (bp
== NULL
|| strncmp(buf
, "From ", 5) == 0) {
826 if (off1
!= (off_t
)-1) {
827 rv
= maildir_append1(name
, fp
, off1
, size
, flag
);
830 if (fseek(fp
, offs
+ buflen
, SEEK_SET
) == -1) {
835 off1
= offs
+ buflen
;
842 if (bp
&& buf
[0] == '\n')
844 else if (bp
&& inhead
&& ascncasecmp(buf
, "status", 6) == 0) {
846 while (whitechar(*lp
))
849 while (*++lp
!= '\0')
858 } else if (bp
&& inhead
&& ascncasecmp(buf
, "x-status", 8) == 0) {
860 while (whitechar(*lp
))
863 while (*++lp
!= '\0')
876 } while (bp
!= NULL
);
885 maildir_remove(char const *name
)
890 if (subdir_remove(name
, "tmp") == STOP
||
891 subdir_remove(name
, "new") == STOP
||
892 subdir_remove(name
, "cur") == STOP
)
894 if (rmdir(name
) < 0) {
904 /* vim:set fenc=utf-8:s-it-mode */