Subversion Repositories planix.SVN

Rev

Rev 2 | Details | Compare with Previous | Last modification | View Log | RSS feed

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