Blob


1 #include "common.h"
2 #include <ctype.h>
3 #include <plumb.h>
4 #include <libsec.h>
5 #include "dat.h"
7 enum {
8 Buffersize = 64*1024
9 };
11 typedef struct Inbuf Inbuf;
12 struct Inbuf
13 {
14 int fd;
15 uchar *lim;
16 uchar *rptr;
17 uchar *wptr;
18 uchar data[Buffersize+7];
19 };
21 static void
22 addtomessage(Message *m, uchar *p, int n, int done)
23 {
24 int i, len;
26 /* add to message (+ 1 in malloc is for a trailing null) */
27 if(m->lim - m->end < n){
28 if(m->start != nil){
29 i = m->end-m->start;
30 if(done)
31 len = i + n;
32 else
33 len = (4*(i+n))/3;
34 m->start = erealloc(m->start, len + 1);
35 m->end = m->start + i;
36 } else {
37 if(done)
38 len = n;
39 else
40 len = 2*n;
41 m->start = emalloc(len + 1);
42 m->end = m->start;
43 }
44 m->lim = m->start + len;
45 }
47 memmove(m->end, p, n);
48 m->end += n;
49 }
51 /* */
52 /* read in a single message */
53 /* */
54 static int
55 readmessage(Message *m, Inbuf *inb)
56 {
57 int i, n, done;
58 uchar *p, *np;
59 char sdigest[SHA1dlen*2+1];
60 char tmp[64];
62 for(done = 0; !done;){
63 n = inb->wptr - inb->rptr;
64 if(n < 6){
65 if(n)
66 memmove(inb->data, inb->rptr, n);
67 inb->rptr = inb->data;
68 inb->wptr = inb->rptr + n;
69 i = read(inb->fd, inb->wptr, Buffersize);
70 if(i < 0){
71 /* if(fd2path(inb->fd, tmp, sizeof tmp) < 0)
72 strcpy(tmp, "unknown mailbox"); jpc */
73 fprint(2, "error reading '%s': %r\n", tmp);
74 return -1;
75 }
76 if(i == 0){
77 if(n != 0)
78 addtomessage(m, inb->rptr, n, 1);
79 if(m->end == m->start)
80 return -1;
81 break;
82 }
83 inb->wptr += i;
84 }
86 /* look for end of message */
87 for(p = inb->rptr; p < inb->wptr; p = np+1){
88 /* first part of search for '\nFrom ' */
89 np = memchr(p, '\n', inb->wptr - p);
90 if(np == nil){
91 p = inb->wptr;
92 break;
93 }
95 /*
96 * if we've found a \n but there's
97 * not enough room for '\nFrom ', don't do
98 * the comparison till we've read in more.
99 */
100 if(inb->wptr - np < 6){
101 p = np;
102 break;
105 if(strncmp((char*)np, "\nFrom ", 6) == 0){
106 done = 1;
107 p = np+1;
108 break;
112 /* add to message (+ 1 in malloc is for a trailing null) */
113 n = p - inb->rptr;
114 addtomessage(m, inb->rptr, n, done);
115 inb->rptr += n;
118 /* if it doesn't start with a 'From ', this ain't a mailbox */
119 if(strncmp(m->start, "From ", 5) != 0)
120 return -1;
122 /* dump trailing newline, make sure there's a trailing null */
123 /* (helps in body searches) */
124 if(*(m->end-1) == '\n')
125 m->end--;
126 *m->end = 0;
127 m->bend = m->rbend = m->end;
129 /* digest message */
130 sha1((uchar*)m->start, m->end - m->start, m->digest, nil);
131 for(i = 0; i < SHA1dlen; i++)
132 sprint(sdigest+2*i, "%2.2ux", m->digest[i]);
133 m->sdigest = s_copy(sdigest);
135 return 0;
139 /* throw out deleted messages. return number of freshly deleted messages */
140 int
141 purgedeleted(Mailbox *mb)
143 Message *m, *next;
144 int newdels;
146 /* forget about what's no longer in the mailbox */
147 newdels = 0;
148 for(m = mb->root->part; m != nil; m = next){
149 next = m->next;
150 if(m->deleted && m->refs == 0){
151 if(m->inmbox)
152 newdels++;
153 delmessage(mb, m);
156 return newdels;
159 /* */
160 /* read in the mailbox and parse into messages. */
161 /* */
162 static char*
163 _readmbox(Mailbox *mb, int doplumb, Mlock *lk)
165 int fd;
166 String *tmp;
167 Dir *d;
168 static char err[128];
169 Message *m, **l;
170 Inbuf *inb;
171 char *x;
173 l = &mb->root->part;
175 /*
176 * open the mailbox. If it doesn't exist, try the temporary one.
177 */
178 retry:
179 fd = open(mb->path, OREAD);
180 if(fd < 0){
181 errstr(err, sizeof(err));
182 if(strstr(err, "exist") != 0){
183 tmp = s_copy(mb->path);
184 s_append(tmp, ".tmp");
185 if(sysrename(s_to_c(tmp), mb->path) == 0){
186 s_free(tmp);
187 goto retry;
189 s_free(tmp);
191 return err;
194 /*
195 * a new qid.path means reread the mailbox, while
196 * a new qid.vers means read any new messages
197 */
198 d = dirfstat(fd);
199 if(d == nil){
200 close(fd);
201 errstr(err, sizeof(err));
202 return err;
204 if(mb->d != nil){
205 if(d->qid.path == mb->d->qid.path && d->qid.vers == mb->d->qid.vers){
206 close(fd);
207 free(d);
208 return nil;
210 if(d->qid.path == mb->d->qid.path){
211 while(*l != nil)
212 l = &(*l)->next;
213 seek(fd, mb->d->length, 0);
215 free(mb->d);
217 mb->d = d;
218 mb->vers++;
219 henter(PATH(0, Qtop), mb->name,
220 (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
222 inb = emalloc(sizeof(Inbuf));
223 inb->rptr = inb->wptr = inb->data;
224 inb->fd = fd;
226 /* read new messages */
227 snprint(err, sizeof err, "reading '%s'", mb->path);
228 logmsg(err, nil);
229 for(;;){
230 if(lk != nil)
231 syslockrefresh(lk);
232 m = newmessage(mb->root);
233 m->mallocd = 1;
234 m->inmbox = 1;
235 if(readmessage(m, inb) < 0){
236 delmessage(mb, m);
237 mb->root->subname--;
238 break;
241 /* merge mailbox versions */
242 while(*l != nil){
243 if(memcmp((*l)->digest, m->digest, SHA1dlen) == 0){
244 /* matches mail we already read, discard */
245 logmsg("duplicate", *l);
246 delmessage(mb, m);
247 mb->root->subname--;
248 m = nil;
249 l = &(*l)->next;
250 break;
251 } else {
252 /* old mail no longer in box, mark deleted */
253 logmsg("disappeared", *l);
254 if(doplumb)
255 mailplumb(mb, *l, 1);
256 (*l)->inmbox = 0;
257 (*l)->deleted = 1;
258 l = &(*l)->next;
261 if(m == nil)
262 continue;
264 x = strchr(m->start, '\n');
265 if(x == nil)
266 m->header = m->end;
267 else
268 m->header = x + 1;
269 m->mheader = m->mhend = m->header;
270 parseunix(m);
271 parse(m, 0, mb, 0);
272 logmsg("new", m);
274 /* chain in */
275 *l = m;
276 l = &m->next;
277 if(doplumb)
278 mailplumb(mb, m, 0);
281 logmsg("mbox read", nil);
283 /* whatever is left has been removed from the mbox, mark deleted */
284 while(*l != nil){
285 if(doplumb)
286 mailplumb(mb, *l, 1);
287 (*l)->inmbox = 0;
288 (*l)->deleted = 1;
289 l = &(*l)->next;
292 close(fd);
293 free(inb);
294 return nil;
297 static void
298 _writembox(Mailbox *mb, Mlock *lk)
300 Dir *d;
301 Message *m;
302 String *tmp;
303 int mode, errs;
304 Biobuf *b;
306 tmp = s_copy(mb->path);
307 s_append(tmp, ".tmp");
309 /*
310 * preserve old files permissions, if possible
311 */
312 d = dirstat(mb->path);
313 if(d != nil){
314 mode = d->mode&0777;
315 free(d);
316 } else
317 mode = MBOXMODE;
319 sysremove(s_to_c(tmp));
320 b = sysopen(s_to_c(tmp), "alc", mode);
321 if(b == 0){
322 fprint(2, "can't write temporary mailbox %s: %r\n", s_to_c(tmp));
323 return;
326 logmsg("writing new mbox", nil);
327 errs = 0;
328 for(m = mb->root->part; m != nil; m = m->next){
329 if(lk != nil)
330 syslockrefresh(lk);
331 if(m->deleted)
332 continue;
333 logmsg("writing", m);
334 if(Bwrite(b, m->start, m->end - m->start) < 0)
335 errs = 1;
336 if(Bwrite(b, "\n", 1) < 0)
337 errs = 1;
339 logmsg("wrote new mbox", nil);
341 if(sysclose(b) < 0)
342 errs = 1;
344 if(errs){
345 fprint(2, "error writing temporary mail file\n");
346 s_free(tmp);
347 return;
350 sysremove(mb->path);
351 if(sysrename(s_to_c(tmp), mb->path) < 0)
352 fprint(2, "%s: can't rename %s to %s: %r\n", argv0,
353 s_to_c(tmp), mb->path);
354 s_free(tmp);
355 if(mb->d != nil)
356 free(mb->d);
357 mb->d = dirstat(mb->path);
360 char*
361 plan9syncmbox(Mailbox *mb, int doplumb)
363 Mlock *lk;
364 char *rv;
366 lk = nil;
367 if(mb->dolock){
368 lk = syslock(mb->path);
369 if(lk == nil)
370 return "can't lock mailbox";
373 rv = _readmbox(mb, doplumb, lk); /* interpolate */
374 if(purgedeleted(mb) > 0)
375 _writembox(mb, lk);
377 if(lk != nil)
378 sysunlock(lk);
380 return rv;
383 /* */
384 /* look to see if we can open this mail box */
385 /* */
386 char*
387 plan9mbox(Mailbox *mb, char *path)
389 static char err[64];
390 String *tmp;
392 if(access(path, AEXIST) < 0){
393 errstr(err, sizeof(err));
394 tmp = s_copy(path);
395 s_append(tmp, ".tmp");
396 if(access(s_to_c(tmp), AEXIST) < 0){
397 s_free(tmp);
398 return err;
400 s_free(tmp);
403 mb->sync = plan9syncmbox;
404 return nil;