/[pkg-mixmaster]/trunk/Mix/Src/maildir.c
ViewVC logotype

Contents of /trunk/Mix/Src/maildir.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 155 - (hide annotations) (download)
Wed Aug 21 12:35:51 2002 UTC (10 years, 8 months ago) by weaselp
File MIME type: text/plain
File size: 7182 byte(s)
Use Maildir++ scheme for naming files. This includes the message size in the
filename which with some mail systems is used for quota caluclation.

If link()ing from the tmp/ directory to the new/ directory fails with EEXIST
try with another target filename again after sleeping for some time (Break out
after 5 attempts).
1 weaselp 153 /* Maildir support for Mixmaster 3 - see
2     http://www.qmail.org/man/man5/maildir.html and
3     http://cr.yp.to/proto/maildir.html
4    
5     hacked by drt@un.bewaff.net - http://c0re.jp/
6    
7     To test it try:
8     $ gcc maildir.c -DUNITTEST -o test_maildir
9     $ ./test_maildir
10     this should print a single line saying "OK"
11     */
12    
13     #include "mix3.h"
14    
15     #include <unistd.h>
16     #include <fcntl.h>
17     #include <time.h>
18     #include <string.h>
19     #include <sys/stat.h>
20     #include <sys/types.h>
21     #include <errno.h>
22     #include <stdarg.h>
23    
24     static unsigned long namecounter = 0;
25    
26     int checkDirectory(char *dir, char *append, int create) {
27     char tmp[PATHMAX];
28     struct stat buf;
29     int err;
30    
31     tmp[0] = '\0';
32     strncat(tmp, dir, PATHMAX);
33     tmp[PATHMAX-1] = '\0';
34     if (append) {
35     strncat(tmp, append, PATHMAX);
36     tmp[PATHMAX-1] = '\0';
37     }
38    
39     err = stat(tmp, &buf);
40     if (err == -1) {
41     if (create) {
42     #ifndef POSIX
43     err = mkdir(tmp);
44     #else
45     err = mkdir(tmp, S_IRWXU);
46     #endif
47     if (err == 0)
48     errlog(NOTICE, "Creating directory %s.\n", tmp);
49     } else
50     err = 1;
51     } else if (!S_ISDIR(buf.st_mode))
52     err = -1;
53    
54     return err;
55     }
56    
57     /* Write "message" to "maildir", retunr 0 on success, -1 on failure */
58 weaselp 155 #define MAX_BASENAME 113 /* actual length should be smaller than 111 bytes */
59     #define MAX_SUBNAME 123 /* actual length should be smaller than 115 bytes */
60 weaselp 153 int maildirWrite(char *maildir, BUFFER *message, int create) {
61     int fd;
62     int currDir;
63     int count;
64     int returnValue;
65     char hostname[64];
66     struct stat statbuf;
67 weaselp 155 char basename[MAX_BASENAME];
68     char tmpname[MAX_SUBNAME];
69     char newname[MAX_SUBNAME];
70     int messagesize;
71 weaselp 153
72     /* Declare a handler for SIGALRM so we can time out. */
73     /* set_handler(SIGALRM, alarm_handler); */
74     /* alarm(86400); */
75    
76     hostname[0] = '\0';
77     gethostname(hostname, 63);
78     hostname[63] = '\0';
79    
80     if ((checkDirectory(maildir, NULL, create) != 0) ||
81     (checkDirectory(maildir, "tmp", create) != 0) ||
82     (checkDirectory(maildir, "cur", create) != 0) ||
83     (checkDirectory(maildir, "new", create) != 0)) {
84     returnValue = -1;
85     goto realend;
86     }
87    
88 weaselp 155 messagesize = message->length;
89    
90 weaselp 153 /* Step 1: chdir to maildir (and save actual dir by opening it) */
91 weaselp 154 if((currDir = open(".", O_RDONLY)) < 0) {
92     returnValue = -1;
93     goto realend;
94     }
95 weaselp 153 if(chdir(maildir) != 0) {
96     returnValue = -1;
97     goto functionExit;
98     }
99    
100     /* Step 2: Stat the temporary file. Wait for ENOENT as a response. */
101     for (count = 0;; count++) {
102     tmpname[0] = '\0';
103     newname[0] = '\0';
104 weaselp 155 snprintf(basename, MAX_BASENAME, "%lu.%u_%lu.%s,S=%u",
105     time(NULL), getpid(), namecounter++, hostname, messagesize);
106     basename[MAX_BASENAME-1] = '\0';
107     strncat(tmpname, "tmp/", MAX_SUBNAME);
108     strncat(tmpname, basename, MAX_SUBNAME);
109     tmpname[MAX_SUBNAME-1] = '\0';
110     strncat(newname, "new/", MAX_SUBNAME);
111     strncat(newname, basename, MAX_SUBNAME);
112     newname[MAX_SUBNAME-1] = '\0';
113 weaselp 153
114     if (stat(tmpname, &statbuf) == 0)
115     errno = EEXIST;
116     else if (errno == ENOENT) {
117     /* Step 4: create the file (at least try) */
118     fd = open(tmpname, O_WRONLY|O_CREAT|O_EXCL, S_IWUSR|S_IRUSR);
119     if (fd >= 0)
120     break; /* we managed to open the file */
121     }
122    
123     if (count > 5) {
124     /* Too many retries - give up */
125     errlog(ERRORMSG, "Can't create message in %s\n", maildir);
126     returnValue = -1;
127     goto functionExit;
128     }
129    
130     /* Step 3: sleep and retry */
131     sleep(2);
132     }
133    
134     /* Step 5: write file */
135     if(write(fd, message->data, message->length) != message->length) {
136     returnValue = -1;
137     goto functionExit;
138     }
139    
140     /* on NFS this could fail */
141     #ifndef WIN32
142     if((fsync(fd) != 0) || (close(fd) != 0)) {
143     #else
144     if((_commit(fd) != 0) || (close(fd) != 0)) {
145     #endif
146     returnValue = -1;
147     goto functionExit;
148     }
149    
150     /* Step 6: move message to 'cur' */
151     #ifdef POSIX
152 weaselp 155 for (count = 0;; count++) {
153     if(link(tmpname, newname) != 0) {
154     if (errno == EXDEV || errno == EPERM) {
155     /* We probably are on coda or some other filesystem that does not allow
156     * hardlinks. rename() the file instead of link() and unlink()
157     * I know, It's evil (PP).
158     */
159     if (rename(tmpname, newname) != 0) {
160     returnValue = -1;
161     goto functionExit;
162     };
163     break;
164     } else if (errno != EEXIST) {
165     returnValue = -1;
166     goto functionExit;
167     }
168     } else {
169     /* We successfully linked the message in new/. Now let's get
170     * rid of our tmp/ entry
171 weaselp 153 */
172 weaselp 155 if(unlink(tmpname) != 0) {
173     /* unlinking failed */
174 weaselp 153 returnValue = -1;
175     goto functionExit;
176 weaselp 155 }
177     break;
178 weaselp 153 }
179 weaselp 155
180     if (count > 5) {
181     /* Too many retries - give up */
182     errlog(ERRORMSG, "Can't move message to %s/new/\n", maildir);
183 weaselp 153 returnValue = -1;
184     goto functionExit;
185     }
186 weaselp 155
187     sleep(2);
188     newname[0] = '\0';
189     snprintf(basename, MAX_BASENAME, "%lu.%u_%lu.%s,S=%u",
190     time(NULL), getpid(), namecounter++, hostname, messagesize);
191     basename[MAX_BASENAME-1] = '\0';
192     strncat(newname, "new/", MAX_SUBNAME);
193     strncat(newname, basename, MAX_SUBNAME);
194     newname[MAX_SUBNAME-1] = '\0';
195 weaselp 153 }
196     #else /* POSIX */
197     /* On non POSIX systems we simply use rename(). Let's hobe DJB
198     * never finds out
199     */
200     if (rename(tmpname, newname) != 0) {
201     returnValue = -1;
202     goto functionExit;
203     };
204     #endif /* POSIX */
205    
206     returnValue = 0;
207    
208     functionExit:
209     /* return to original directory */
210     if ((fchdir(currDir) != 0) || (close(currDir) != 0))
211     returnValue = -1;
212    
213     realend:
214    
215     return returnValue;
216     }
217    
218     #ifdef UNITTEST
219    
220     #ifdef NDEBUG
221     #undef NDEBUG
222     #endif
223    
224     #include <dirent.h>
225     #include <assert.h>
226    
227     /* mock-up of errlog for unittest */
228     void errlog(int type, char *fmt,...)
229     {
230     va_list ap;
231    
232     va_start(ap, fmt);
233     vfprintf(stderr, fmt, ap);
234     va_end(ap);
235     }
236    
237     /* main for unittest */
238     int main()
239     {
240     int i, count = 23;
241     int fd;
242     DIR *d;
243     struct dirent *de;
244     BUFFER message;
245     char text[] = "From: nobody@un.bewaff.net\nTo: hackers@c0re.jp\nSubject: testing\n\nthis is just a test\n";
246     char buf[1024];
247    
248     /* create buffer with test data */
249     message.data = text;
250     message.length = strlen(text);
251    
252     /* write <count> messages to maildir */
253     for(i = 0; i < count; i++)
254     assert(maildirWrite("Maildir.test_maildir", message, 1) == 0);
255    
256     /* read them back */
257     assert((d = opendir("Maildir.test_maildir/new")) != NULL);
258     for (i = 0; i < count + 2; i++)
259     {
260     de = readdir(d);
261     if(de->d_name[0] != '.')
262     {
263     buf[0] = '\0';
264     strcat(buf, "Maildir.test_maildir/new/");
265     strcat(buf, de->d_name);
266     fd = open(buf, O_RDONLY);
267     assert(unlink(buf) == 0);
268     assert(read(fd, buf, strlen(text)) == strlen(text));
269     buf[strlen(text)] = '\0';
270     /* check if they match the original message */
271     assert(strcmp(text, buf) == 0);
272     close(fd);
273     }
274     }
275    
276     /* no files left in directory? */
277     assert(readdir(d) == NULL);
278    
279     /* delete maildir */
280     assert(rmdir("Maildir.test_maildir/tmp") == 0);
281     assert(rmdir("Maildir.test_maildir/new") == 0);
282     assert(rmdir("Maildir.test_maildir/cur") == 0);
283     assert(rmdir("Maildir.test_maildir") == 0);
284    
285     /* check if writing to a non existant maildir yilds an error */
286     assert(maildirWrite("Maildir.test_maildir", &message, 0) == -1);
287    
288     puts("OK");
289     }
290     #endif /* UNITTEST */

  ViewVC Help
Powered by ViewVC 1.1.5