xref: /plan9/sys/src/cmd/upas/fs/plan9.c (revision 21887c0b3fa083db468c32f3247dbe932cf708df)
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
addtomessage(Message * m,uchar * p,int n,int done)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
readmessage(Message * m,Inbuf * inb)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
purgedeleted(Mailbox * mb)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*
_readmbox(Mailbox * mb,int doplumb,Mlock * lk)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
_writembox(Mailbox * mb,Mlock * lk)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*
plan9syncmbox(Mailbox * mb,int doplumb)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*
plan9mbox(Mailbox * mb,char * path)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 }
413