2 * Heirloom mailx - a mail user agent derived from Berkeley Mail.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
8 * Gunnar Ritter. All rights reserved.
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 * 3. All advertising materials mentioning features or use of this software
19 * must display the following acknowledgement:
20 * This product includes software developed by Gunnar Ritter
21 * and his contributors.
22 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
23 * may be used to endorse or promote products derived from this software
24 * without specific prior written permission.
26 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
27 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 * ARE DISCLAIMED. IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
30 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
41 static char sccsid
[] = "@(#)maildir.c 1.20 (gritter) 12/28/06";
56 * Mail -- a mail program
58 * Maildir folder support.
61 static struct mditem
{
62 struct message
*md_data
;
67 static sigjmp_buf maildirjmp
;
69 static int maildir_setfile1(const char *name
, int newmail
, int omsgCount
);
70 static int mdcmp(const void *a
, const void *b
);
71 static int subdir(const char *name
, const char *sub
, int newmail
);
72 static void cleantmp(const char *name
);
73 static void append(const char *name
, const char *sub
, const char *fn
);
74 static void readin(const char *name
, struct message
*m
);
75 static void maildir_update(void);
76 static void move(struct message
*m
);
77 static char *mkname(time_t t
, enum mflag f
, const char *pref
);
78 static void maildircatch(int s
);
79 static enum okay
maildir_append1(const char *name
, FILE *fp
, off_t off1
,
80 long size
, enum mflag flag
);
81 static enum okay
trycreate(const char *name
);
82 static enum okay
mkmaildir(const char *name
);
83 static struct message
*mdlook(const char *name
, struct message
*data
);
84 static void mktable(void);
85 static enum okay
subdir_remove(const char *name
, const char *sub
);
88 maildir_setfile(const char *name
, int newmail
, int isedit
)
90 sighandler_type saveint
;
92 int i
= -1, omsgCount
;
97 if (cwget(&cw
) == STOP
) {
98 fprintf(stderr
, "Fatal: Cannot open current directory\n");
103 saveint
= safe_signal(SIGINT
, SIG_IGN
);
104 if (chdir(name
) < 0) {
105 fprintf(stderr
, "Cannot change directory to \"%s\".\n", name
);
120 mb
.mb_type
= MB_MAILDIR
;
123 if (sigsetjmp(maildirjmp
, 1) == 0) {
126 if (saveint
!= SIG_IGN
)
127 safe_signal(SIGINT
, maildircatch
);
128 i
= maildir_setfile1(name
, newmail
, omsgCount
);
132 safe_signal(SIGINT
, saveint
);
134 mb
.mb_type
= MB_VOID
;
138 if (cwret(&cw
) == STOP
) {
139 fputs("Fatal: Cannot change back to current directory.\n",
145 if (newmail
&& mb
.mb_sorted
&& msgCount
> omsgCount
) {
151 if (!newmail
&& !edit
&& msgCount
== 0) {
152 if (mb
.mb_type
== MB_MAILDIR
&& value("emptystart") == NULL
)
153 fprintf(stderr
, "No mail at %s\n", name
);
156 if (newmail
&& msgCount
> omsgCount
)
157 newmailinfo(omsgCount
);
162 maildir_setfile1(const char *name
, int newmail
, int omsgCount
)
168 mb
.mb_perm
= Rflag
? 0 : MB_DELE
;
169 if ((i
= subdir(name
, "cur", newmail
)) != 0)
171 if ((i
= subdir(name
, "new", newmail
)) != 0)
173 append(name
, NULL
, NULL
);
174 for (i
= newmail
?omsgCount
:0; i
< msgCount
; i
++)
175 readin(name
, &message
[i
]);
177 if (msgCount
> omsgCount
)
178 qsort(&message
[omsgCount
],
179 msgCount
- omsgCount
,
180 sizeof *message
, mdcmp
);
183 qsort(message
, msgCount
, sizeof *message
, mdcmp
);
189 * In combination with the names from mkname(), this comparison function
190 * ensures that the order of messages in a maildir folder created by mailx
191 * remains always the same. In effect, if a mbox folder is transferred to
192 * a maildir folder by 'copy *', the order of the messages in mailx will
196 mdcmp(const void *a
, const void *b
)
200 if ((i
= ((struct message
*)a
)->m_time
-
201 ((struct message
*)b
)->m_time
) == 0)
202 i
= strcmp(&((struct message
*)a
)->m_maildir_file
[4],
203 &((struct message
*)b
)->m_maildir_file
[4]);
208 subdir(const char *name
, const char *sub
, int newmail
)
213 if ((dirfd
= opendir(sub
)) == NULL
) {
214 fprintf(stderr
, "Cannot open directory \"%s/%s\".\n",
218 if (access(sub
, W_OK
) < 0)
220 while ((dp
= readdir(dirfd
)) != NULL
) {
221 if (dp
->d_name
[0] == '.' &&
222 (dp
->d_name
[1] == '\0' ||
223 (dp
->d_name
[1] == '.' &&
224 dp
->d_name
[2] == '\0')))
226 if (dp
->d_name
[0] == '.')
228 if (!newmail
|| mdlook(dp
->d_name
, NULL
) == NULL
)
229 append(name
, sub
, dp
->d_name
);
236 cleantmp(const char *name
)
242 size_t fnsz
= 0, ssz
;
245 if ((dirfd
= opendir("tmp")) == NULL
)
248 while ((dp
= readdir(dirfd
)) != NULL
) {
249 if (dp
->d_name
[0] == '.' &&
250 (dp
->d_name
[1] == '\0' ||
251 (dp
->d_name
[1] == '.' &&
252 dp
->d_name
[2] == '\0')))
254 if (dp
->d_name
[0] == '.')
256 if ((ssz
= strlen(dp
->d_name
)) + 5 > fnsz
) {
258 fn
= smalloc(fnsz
= ssz
+ 40);
261 strcpy(&fn
[4], dp
->d_name
);
262 if (stat(fn
, &st
) < 0)
264 if (st
.st_atime
+ 36*3600 < now
)
272 append(const char *name
, const char *sub
, const char *fn
)
277 enum mflag f
= MUSED
|MNOFROM
|MNEWEST
;
282 if (strcmp(sub
, "new") == 0)
284 t
= strtol(fn
, &xp
, 10);
285 if ((cp
= strrchr(xp
, ',')) != NULL
&&
286 cp
> &xp
[2] && cp
[-1] == '2' && cp
[-2] == ':') {
308 if (msgCount
+ 1 >= msgspace
) {
309 const int chunk
= 64;
310 message
= srealloc(message
,
311 (msgspace
+= chunk
) * sizeof *message
);
312 memset(&message
[msgCount
], 0, chunk
* sizeof *message
);
314 if (fn
== NULL
|| sub
== NULL
)
316 m
= &message
[msgCount
++];
317 m
->m_maildir_file
= smalloc((sz
= strlen(sub
)) + strlen(fn
) + 2);
318 strcpy(m
->m_maildir_file
, sub
);
319 m
->m_maildir_file
[sz
] = '/';
320 strcpy(&m
->m_maildir_file
[sz
+1], fn
);
323 m
->m_maildir_hash
= ~pjw(fn
);
328 readin(const char *name
, struct message
*m
)
331 size_t bufsize
, buflen
, count
;
332 long size
= 0, lines
= 0;
337 if ((fp
= Fopen(m
->m_maildir_file
, "r")) == NULL
) {
338 fprintf(stderr
, "Cannot read \"%s/%s\" for message %d\n",
339 name
, m
->m_maildir_file
,
340 (int)(m
- &message
[0] + 1));
341 m
->m_flag
|= MHIDDEN
;
344 buf
= smalloc(bufsize
= LINESIZE
);
347 fseek(mb
.mb_otf
, 0L, SEEK_END
);
348 offset
= ftell(mb
.mb_otf
);
349 while (fgetline(&buf
, &bufsize
, &count
, &buflen
, fp
, 1) != NULL
) {
351 if (buf
[0] == 'F' && buf
[1] == 'r' && buf
[2] == 'o' &&
352 buf
[3] == 'm' && buf
[4] == ' ') {
353 putc('>', mb
.mb_otf
);
357 size
+= fwrite(bp
, 1, buflen
, mb
.mb_otf
);
358 emptyline
= *bp
== '\n';
361 putc('\n', mb
.mb_otf
);
367 m
->m_size
= m
->m_xsize
= size
;
368 m
->m_lines
= m
->m_xlines
= lines
;
369 m
->m_block
= mailx_blockof(offset
);
370 m
->m_offset
= mailx_offsetof(offset
);
378 sighandler_type saveint
;
382 if (cwget(&cw
) == STOP
) {
383 fprintf(stderr
, "Fatal: Cannot open current directory\n");
386 saveint
= safe_signal(SIGINT
, SIG_IGN
);
387 if (chdir(mailname
) < 0) {
388 fprintf(stderr
, "Cannot change directory to \"%s\".\n",
393 if (sigsetjmp(maildirjmp
, 1) == 0) {
394 if (saveint
!= SIG_IGN
)
395 safe_signal(SIGINT
, maildircatch
);
398 safe_signal(SIGINT
, saveint
);
399 if (cwret(&cw
) == STOP
) {
400 fputs("Fatal: Cannot change back to current directory.\n",
410 FILE *readstat
= NULL
;
412 int dodel
, c
, gotcha
= 0, held
= 0, modflags
= 0;
417 if ((readstat
= Zopen(Tflag
, "w", NULL
)) == NULL
)
422 for (m
= &message
[0], c
= 0; m
< &message
[msgCount
]; m
++) {
423 if (m
->m_flag
& MBOX
)
427 if (makembox() == STOP
)
430 for (m
= &message
[0], gotcha
=0, held
=0; m
< &message
[msgCount
]; m
++) {
431 if (readstat
!= NULL
&& (m
->m_flag
& (MREAD
|MDELETED
)) != 0) {
433 if ((id
= hfield("message-id", m
)) != NULL
||
434 (id
= hfield("article-id", m
)) != NULL
)
435 fprintf(readstat
, "%s\n", id
);
438 dodel
= m
->m_flag
& MDELETED
;
440 dodel
= !((m
->m_flag
&MPRESERVE
) ||
441 (m
->m_flag
&MTOUCH
) == 0);
443 if (unlink(m
->m_maildir_file
) < 0)
444 fprintf(stderr
, "Cannot delete file \"%s/%s\" "
446 mailname
, m
->m_maildir_file
,
447 (int)(m
- &message
[0] + 1));
451 if ((m
->m_flag
&(MREAD
|MSTATUS
)) == (MREAD
|MSTATUS
) ||
452 m
->m_flag
& (MNEW
|MBOXED
|MSAVED
|MSTATUS
|
462 bypass
: if (readstat
!= NULL
)
464 if ((gotcha
|| modflags
) && edit
) {
465 printf(catgets(catd
, CATSET
, 168, "\"%s\" "), mailname
);
466 printf(value("bsdcompat") || value("bsdmsgs") ?
467 catgets(catd
, CATSET
, 170, "complete\n") :
468 catgets(catd
, CATSET
, 212, "updated.\n"));
469 } else if (held
&& !edit
&& mb
.mb_perm
!= 0) {
471 printf(catgets(catd
, CATSET
, 155,
472 "Held 1 message in %s\n"), mailname
);
474 printf(catgets(catd
, CATSET
, 156,
475 "Held %d messages in %s\n"), held
, mailname
);
478 free
: for (m
= &message
[0]; m
< &message
[msgCount
]; m
++)
479 free(m
->m_maildir_file
);
483 move(struct message
*m
)
487 fn
= mkname(0, m
->m_flag
, &m
->m_maildir_file
[4]);
488 new = savecat("cur/", fn
);
489 if (strcmp(m
->m_maildir_file
, new) == 0)
491 if (link(m
->m_maildir_file
, new) < 0) {
492 fprintf(stderr
, "Cannot link \"%s/%s\" to \"%s/%s\": "
493 "message %d not touched.\n",
494 mailname
, m
->m_maildir_file
,
496 (int)(m
- &message
[0] + 1));
499 if (unlink(m
->m_maildir_file
) < 0)
500 fprintf(stderr
, "Cannot unlink \"%s/%s\".\n",
501 mailname
, m
->m_maildir_file
);
505 mkname(time_t t
, enum mflag f
, const char *pref
)
507 static unsigned long count
;
521 node
= srealloc(node
, size
+= 20);
524 node
[n
++] = '\\', node
[n
++] = '0',
525 node
[n
++] = '5', node
[n
++] = '7';
528 node
[n
++] = '\\', node
[n
++] = '0',
529 node
[n
++] = '7', node
[n
++] = '2';
536 size
= 60 + strlen(node
);
538 n
= snprintf(cp
, size
, "%lu.%06lu_%06lu.%s:2,",
540 (unsigned long)mypid
, ++count
, node
);
542 size
= (n
= strlen(pref
)) + 13;
545 for (i
= n
; i
> 3; i
--)
546 if (cp
[i
-1] == ',' && cp
[i
-2] == '2' &&
552 strcpy(&cp
[n
], ":2,");
575 siglongjmp(maildirjmp
, s
);
579 maildir_append(const char *name
, FILE *fp
)
582 size_t bufsize
, buflen
, count
;
583 off_t off1
= -1, offs
;
585 int flag
= MNEW
|MNEWEST
;
589 if (mkmaildir(name
) != OKAY
)
591 buf
= smalloc(bufsize
= LINESIZE
);
596 bp
= fgetline(&buf
, &bufsize
, &count
, &buflen
, fp
, 1);
597 if (bp
== NULL
|| strncmp(buf
, "From ", 5) == 0) {
598 if (off1
!= (off_t
)-1) {
599 ok
= maildir_append1(name
, fp
, off1
,
603 fseek(fp
, offs
+buflen
, SEEK_SET
);
605 off1
= offs
+ buflen
;
612 if (bp
&& buf
[0] == '\n')
614 else if (bp
&& inhead
&& ascncasecmp(buf
, "status", 6) == 0) {
616 while (whitechar(*lp
&0377))
619 while (*++lp
!= '\0')
628 } else if (bp
&& inhead
&&
629 ascncasecmp(buf
, "x-status", 8) == 0) {
631 while (whitechar(*lp
&0377))
634 while (*++lp
!= '\0')
647 } while (bp
!= NULL
);
653 maildir_append1(const char *name
, FILE *fp
, off_t off1
, long size
,
656 const int attempts
= 43200;
659 char *fn
, *tmp
, *new;
665 for (i
= 0; i
< attempts
; i
++) {
667 fn
= mkname(now
, flag
, NULL
);
668 tmp
= salloc(n
= strlen(name
) + strlen(fn
) + 6);
669 snprintf(tmp
, n
, "%s/tmp/%s", name
, fn
);
670 if (stat(tmp
, &st
) < 0 && errno
== ENOENT
)
676 "Cannot create unique file name in \"%s/tmp\".\n",
680 if ((op
= Fopen(tmp
, "w")) == NULL
) {
681 fprintf(stderr
, "Cannot write to \"%s\".\n", tmp
);
684 fseek(fp
, off1
, SEEK_SET
);
686 z
= size
> sizeof buf
? sizeof buf
: size
;
687 if ((n
= fread(buf
, 1, z
, fp
)) != z
||
688 fwrite(buf
, 1, n
, op
) != n
) {
689 fprintf(stderr
, "Error writing to \"%s\".\n", tmp
);
697 new = salloc(n
= strlen(name
) + strlen(fn
) + 6);
698 snprintf(new, n
, "%s/new/%s", name
, fn
);
699 if (link(tmp
, new) < 0) {
700 fprintf(stderr
, "Cannot link \"%s\" to \"%s\".\n", tmp
, new);
704 fprintf(stderr
, "Cannot unlink \"%s\".\n", tmp
);
709 trycreate(const char *name
)
713 if (stat(name
, &st
) == 0) {
714 if (!S_ISDIR(st
.st_mode
)) {
715 fprintf(stderr
, "\"%s\" is not a directory.\n", name
);
718 } else if (makedir(name
) != OKAY
) {
719 fprintf(stderr
, "Cannot create directory \"%s\".\n", name
);
722 imap_created_mailbox
++;
727 mkmaildir(const char *name
)
733 if (trycreate(name
) == OKAY
) {
734 np
= ac_alloc((sz
= strlen(name
)) + 5);
736 strcpy(&np
[sz
], "/tmp");
737 if (trycreate(np
) == OKAY
) {
738 strcpy(&np
[sz
], "/new");
739 if (trycreate(np
) == OKAY
) {
740 strcpy(&np
[sz
], "/cur");
741 if (trycreate(np
) == OKAY
)
750 static struct message
*
751 mdlook(const char *name
, struct message
*data
)
754 unsigned c
, h
, n
= 0;
756 if (data
&& data
->m_maildir_hash
)
757 h
= ~data
->m_maildir_hash
;
761 md
= &mdtable
[c
= h
];
762 while (md
->md_data
!= NULL
) {
763 if (strcmp(&md
->md_data
->m_maildir_file
[4], name
) == 0)
765 c
+= n
&1 ? -((n
+1)/2) * ((n
+1)/2) : ((n
+1)/2) * ((n
+1)/2);
771 if (data
!= NULL
&& md
->md_data
== NULL
)
773 return md
->md_data
? md
->md_data
: NULL
;
781 mdprime
= nextprime(msgCount
);
782 mdtable
= scalloc(mdprime
, sizeof *mdtable
);
783 for (i
= 0; i
< msgCount
; i
++)
784 mdlook(&message
[i
].m_maildir_file
[4], &message
[i
]);
788 subdir_remove(const char *name
, const char *sub
)
791 int pathsize
, pathend
, namelen
, sublen
, n
;
795 namelen
= strlen(name
);
796 sublen
= strlen(sub
);
797 path
= smalloc(pathsize
= namelen
+ sublen
+ 30);
800 strcpy(&path
[namelen
+1], sub
);
801 path
[namelen
+sublen
+1] = '/';
802 path
[pathend
= namelen
+ sublen
+ 2] = '\0';
803 if ((dirfd
= opendir(path
)) == NULL
) {
808 while ((dp
= readdir(dirfd
)) != NULL
) {
809 if (dp
->d_name
[0] == '.' &&
810 (dp
->d_name
[1] == '\0' ||
811 (dp
->d_name
[1] == '.' &&
812 dp
->d_name
[2] == '\0')))
814 if (dp
->d_name
[0] == '.')
816 n
= strlen(dp
->d_name
);
817 if (pathend
+ n
+ 1 > pathsize
)
818 path
= srealloc(path
, pathsize
= pathend
+ n
+ 30);
819 strcpy(&path
[pathend
], dp
->d_name
);
820 if (unlink(path
) < 0) {
828 path
[pathend
] = '\0';
829 if (rmdir(path
) < 0) {
839 maildir_remove(const char *name
)
841 if (subdir_remove(name
, "tmp") == STOP
||
842 subdir_remove(name
, "new") == STOP
||
843 subdir_remove(name
, "cur") == STOP
)
845 if (rmdir(name
) < 0) {