xref: /plan9/sys/src/cmd/upas/fs/plan9.c (revision f9e1cf08d3be51592e03e639fc848a68dc31a55e)
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;
168 	String *tmp;
169 	Dir *d;
170 	static char err[128];
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 retry:
181 	fd = open(mb->path, OREAD);
182 	if(fd < 0){
183 		errstr(err, sizeof(err));
184 		if(strstr(err, "exist") != 0){
185 			tmp = s_copy(mb->path);
186 			s_append(tmp, ".tmp");
187 			if(sysrename(s_to_c(tmp), mb->path) == 0){
188 				s_free(tmp);
189 				goto retry;
190 			}
191 			s_free(tmp);
192 		}
193 		return err;
194 	}
195 
196 	/*
197 	 *  a new qid.path means reread the mailbox, while
198 	 *  a new qid.vers means read any new messages
199 	 */
200 	d = dirfstat(fd);
201 	if(d == nil){
202 		close(fd);
203 		errstr(err, sizeof(err));
204 		return err;
205 	}
206 	if(mb->d != nil){
207 		if(d->qid.path == mb->d->qid.path && d->qid.vers == mb->d->qid.vers){
208 			close(fd);
209 			free(d);
210 			return nil;
211 		}
212 		if(d->qid.path == mb->d->qid.path){
213 			while(*l != nil)
214 				l = &(*l)->next;
215 			seek(fd, mb->d->length, 0);
216 		}
217 		free(mb->d);
218 	}
219 	mb->d = d;
220 	mb->vers++;
221 	henter(PATH(0, Qtop), mb->name,
222 		(Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
223 
224 	inb = emalloc(sizeof(Inbuf));
225 	inb->rptr = inb->wptr = inb->data;
226 	inb->fd = fd;
227 
228 	//  read new messages
229 	snprint(err, sizeof err, "reading '%s'", mb->path);
230 	logmsg(err, nil);
231 	for(;;){
232 		if(lk != nil)
233 			syslockrefresh(lk);
234 		m = newmessage(mb->root);
235 		m->mallocd = 1;
236 		m->inmbox = 1;
237 		if(readmessage(m, inb) < 0){
238 			delmessage(mb, m);
239 			mb->root->subname--;
240 			break;
241 		}
242 
243 		// merge mailbox versions
244 		while(*l != nil){
245 			if(memcmp((*l)->digest, m->digest, SHA1dlen) == 0){
246 				// matches mail we already read, discard
247 				logmsg("duplicate", *l);
248 				delmessage(mb, m);
249 				mb->root->subname--;
250 				m = nil;
251 				l = &(*l)->next;
252 				break;
253 			} else {
254 				// old mail no longer in box, mark deleted
255 				logmsg("disappeared", *l);
256 				if(doplumb)
257 					mailplumb(mb, *l, 1);
258 				(*l)->inmbox = 0;
259 				(*l)->deleted = 1;
260 				l = &(*l)->next;
261 			}
262 		}
263 		if(m == nil)
264 			continue;
265 
266 		x = strchr(m->start, '\n');
267 		if(x == nil)
268 			m->header = m->end;
269 		else
270 			m->header = x + 1;
271 		m->mheader = m->mhend = m->header;
272 		parseunix(m);
273 		parse(m, 0, mb, 0);
274 		logmsg("new", m);
275 
276 		/* chain in */
277 		*l = m;
278 		l = &m->next;
279 		if(doplumb)
280 			mailplumb(mb, m, 0);
281 
282 	}
283 	logmsg("mbox read", nil);
284 
285 	// whatever is left has been removed from the mbox, mark deleted
286 	while(*l != nil){
287 		if(doplumb)
288 			mailplumb(mb, *l, 1);
289 		(*l)->inmbox = 0;
290 		(*l)->deleted = 1;
291 		l = &(*l)->next;
292 	}
293 
294 	close(fd);
295 	free(inb);
296 	return nil;
297 }
298 
299 static void
300 _writembox(Mailbox *mb, Mlock *lk)
301 {
302 	Dir *d;
303 	Message *m;
304 	String *tmp;
305 	int mode, errs;
306 	Biobuf *b;
307 
308 	tmp = s_copy(mb->path);
309 	s_append(tmp, ".tmp");
310 
311 	/*
312 	 * preserve old files permissions, if possible
313 	 */
314 	d = dirstat(mb->path);
315 	if(d != nil){
316 		mode = d->mode&0777;
317 		free(d);
318 	} else
319 		mode = MBOXMODE;
320 
321 	sysremove(s_to_c(tmp));
322 	b = sysopen(s_to_c(tmp), "alc", mode);
323 	if(b == 0){
324 		fprint(2, "can't write temporary mailbox %s: %r\n", s_to_c(tmp));
325 		return;
326 	}
327 
328 	logmsg("writing new mbox", nil);
329 	errs = 0;
330 	for(m = mb->root->part; m != nil; m = m->next){
331 		if(lk != nil)
332 			syslockrefresh(lk);
333 		if(m->deleted)
334 			continue;
335 		logmsg("writing", m);
336 		if(Bwrite(b, m->start, m->end - m->start) < 0)
337 			errs = 1;
338 		if(Bwrite(b, "\n", 1) < 0)
339 			errs = 1;
340 	}
341 	logmsg("wrote new mbox", nil);
342 
343 	if(sysclose(b) < 0)
344 		errs = 1;
345 
346 	if(errs){
347 		fprint(2, "error writing temporary mail file\n");
348 		s_free(tmp);
349 		return;
350 	}
351 
352 	sysremove(mb->path);
353 	if(sysrename(s_to_c(tmp), mb->path) < 0)
354 		fprint(2, "%s: can't rename %s to %s: %r\n", argv0,
355 			s_to_c(tmp), mb->path);
356 	s_free(tmp);
357 	if(mb->d != nil)
358 		free(mb->d);
359 	mb->d = dirstat(mb->path);
360 }
361 
362 char*
363 plan9syncmbox(Mailbox *mb, int doplumb)
364 {
365 	Mlock *lk;
366 	char *rv;
367 
368 	lk = nil;
369 	if(mb->dolock){
370 		lk = syslock(mb->path);
371 		if(lk == nil)
372 			return "can't lock mailbox";
373 	}
374 
375 	rv = _readmbox(mb, doplumb, lk);		/* interpolate */
376 	if(purgedeleted(mb) > 0)
377 		_writembox(mb, lk);
378 
379 	if(lk != nil)
380 		sysunlock(lk);
381 
382 	return rv;
383 }
384 
385 //
386 //  look to see if we can open this mail box
387 //
388 char*
389 plan9mbox(Mailbox *mb, char *path)
390 {
391 	static char err[64];
392 	String *tmp;
393 
394 	if(access(path, AEXIST) < 0){
395 		errstr(err, sizeof(err));
396 		tmp = s_copy(path);
397 		s_append(tmp, ".tmp");
398 		if(access(s_to_c(tmp), AEXIST) < 0){
399 			s_free(tmp);
400 			return err;
401 		}
402 		s_free(tmp);
403 	}
404 
405 	mb->sync = plan9syncmbox;
406 	return nil;
407 }
408