plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

plan9.c (7547B)


      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");  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 		}
     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 }