xref: /plan9-contrib/sys/src/cmd/upas/fs/plan9.c (revision ec59a3ddbfceee0efe34584c2c9981a5e5ff1ec4)
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 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 	}
46 
47 	memmove(m->end, p, n);
48 	m->end += n;
49 }
50 
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];
61 
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");
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 		}
85 
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 			}
94 
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;
103 			}
104 
105 			if(strncmp((char*)np, "\nFrom ", 6) == 0){
106 				done = 1;
107 				p = np+1;
108 				break;
109 			}
110 		}
111 
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;
116 	}
117 
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;
121 
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;
128 
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);
134 
135 	return 0;
136 }
137 
138 
139 // throw out deleted messages.  return number of freshly deleted messages
140 int
141 purgedeleted(Mailbox *mb)
142 {
143 	Message *m, *next;
144 	int newdels;
145 
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);
154 		}
155 	}
156 	return newdels;
157 }
158 
159 //
160 //  read in the mailbox and parse into messages.
161 //
162 static char*
163 _readmbox(Mailbox *mb, int doplumb, Mlock *lk)
164 {
165 	int fd;
166 	String *tmp;
167 	Dir *d;
168 	static char err[128];
169 	Message *m, **l;
170 	Inbuf *inb;
171 	char *x;
172 
173 	l = &mb->root->part;
174 
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;
188 			}
189 			s_free(tmp);
190 		}
191 		return err;
192 	}
193 
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;
203 	}
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;
209 		}
210 		if(d->qid.path == mb->d->qid.path){
211 			while(*l != nil)
212 				l = &(*l)->next;
213 			seek(fd, mb->d->length, 0);
214 		}
215 		free(mb->d);
216 	}
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);
221 
222 	inb = emalloc(sizeof(Inbuf));
223 	inb->rptr = inb->wptr = inb->data;
224 	inb->fd = fd;
225 
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;
239 		}
240 
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;
259 			}
260 		}
261 		if(m == nil)
262 			continue;
263 
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);
273 
274 		/* chain in */
275 		*l = m;
276 		l = &m->next;
277 		if(doplumb)
278 			mailplumb(mb, m, 0);
279 
280 	}
281 	logmsg("mbox read", nil);
282 
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;
290 	}
291 
292 	close(fd);
293 	free(inb);
294 	return nil;
295 }
296 
297 static void
298 _writembox(Mailbox *mb, Mlock *lk)
299 {
300 	Dir *d;
301 	Message *m;
302 	String *tmp;
303 	int mode, errs;
304 	Biobuf *b;
305 
306 	tmp = s_copy(mb->path);
307 	s_append(tmp, ".tmp");
308 
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;
318 
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;
324 	}
325 
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;
338 	}
339 	logmsg("wrote new mbox", nil);
340 
341 	if(sysclose(b) < 0)
342 		errs = 1;
343 
344 	if(errs){
345 		fprint(2, "error writing temporary mail file\n");
346 		s_free(tmp);
347 		return;
348 	}
349 
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);
358 }
359 
360 char*
361 plan9syncmbox(Mailbox *mb, int doplumb)
362 {
363 	Mlock *lk;
364 	char *rv;
365 
366 	lk = nil;
367 	if(mb->dolock){
368 		lk = syslock(mb->path);
369 		if(lk == nil)
370 			return "can't lock mailbox";
371 	}
372 
373 	rv = _readmbox(mb, doplumb, lk);		/* interpolate */
374 	if(purgedeleted(mb) > 0)
375 		_writembox(mb, lk);
376 
377 	if(lk != nil)
378 		sysunlock(lk);
379 
380 	return rv;
381 }
382 
383 //
384 //  look to see if we can open this mail box
385 //
386 char*
387 plan9mbox(Mailbox *mb, char *path)
388 {
389 	static char err[64];
390 	String *tmp;
391 
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;
399 		}
400 		s_free(tmp);
401 	}
402 
403 	mb->sync = plan9syncmbox;
404 	return nil;
405 }
406