|  | #include "common.h" | 
|  | #include <ctype.h> | 
|  | #include <plumb.h> | 
|  | #include <libsec.h> | 
|  | #include "dat.h" | 
|  |  | 
|  | enum { | 
|  | Buffersize = 64*1024 | 
|  | }; | 
|  |  | 
|  | typedef struct Inbuf Inbuf; | 
|  | struct Inbuf | 
|  | { | 
|  | int	fd; | 
|  | uchar	*lim; | 
|  | uchar	*rptr; | 
|  | uchar	*wptr; | 
|  | uchar	data[Buffersize+7]; | 
|  | }; | 
|  |  | 
|  | static void | 
|  | addtomessage(Message *m, uchar *p, int n, int done) | 
|  | { | 
|  | int i, len; | 
|  |  | 
|  | /* add to message (+ 1 in malloc is for a trailing null) */ | 
|  | if(m->lim - m->end < n){ | 
|  | if(m->start != nil){ | 
|  | i = m->end-m->start; | 
|  | if(done) | 
|  | len = i + n; | 
|  | else | 
|  | len = (4*(i+n))/3; | 
|  | m->start = erealloc(m->start, len + 1); | 
|  | m->end = m->start + i; | 
|  | } else { | 
|  | if(done) | 
|  | len = n; | 
|  | else | 
|  | len = 2*n; | 
|  | m->start = emalloc(len + 1); | 
|  | m->end = m->start; | 
|  | } | 
|  | m->lim = m->start + len; | 
|  | } | 
|  |  | 
|  | memmove(m->end, p, n); | 
|  | m->end += n; | 
|  | } | 
|  |  | 
|  | /* */ | 
|  | /*  read in a single message */ | 
|  | /* */ | 
|  | static int | 
|  | readmessage(Message *m, Inbuf *inb) | 
|  | { | 
|  | int i, n, done; | 
|  | uchar *p, *np; | 
|  | char sdigest[SHA1dlen*2+1]; | 
|  | char tmp[64]; | 
|  |  | 
|  | for(done = 0; !done;){ | 
|  | n = inb->wptr - inb->rptr; | 
|  | if(n < 6){ | 
|  | if(n) | 
|  | memmove(inb->data, inb->rptr, n); | 
|  | inb->rptr = inb->data; | 
|  | inb->wptr = inb->rptr + n; | 
|  | i = read(inb->fd, inb->wptr, Buffersize); | 
|  | if(i < 0){ | 
|  | /* if(fd2path(inb->fd, tmp, sizeof tmp) < 0) | 
|  | strcpy(tmp, "unknown mailbox");  jpc */ | 
|  | fprint(2, "error reading '%s': %r\n", tmp); | 
|  | return -1; | 
|  | } | 
|  | if(i == 0){ | 
|  | if(n != 0) | 
|  | addtomessage(m, inb->rptr, n, 1); | 
|  | if(m->end == m->start) | 
|  | return -1; | 
|  | break; | 
|  | } | 
|  | inb->wptr += i; | 
|  | } | 
|  |  | 
|  | /* look for end of message */ | 
|  | for(p = inb->rptr; p < inb->wptr; p = np+1){ | 
|  | /* first part of search for '\nFrom ' */ | 
|  | np = memchr(p, '\n', inb->wptr - p); | 
|  | if(np == nil){ | 
|  | p = inb->wptr; | 
|  | break; | 
|  | } | 
|  |  | 
|  | /* | 
|  | *  if we've found a \n but there's | 
|  | *  not enough room for '\nFrom ', don't do | 
|  | *  the comparison till we've read in more. | 
|  | */ | 
|  | if(inb->wptr - np < 6){ | 
|  | p = np; | 
|  | break; | 
|  | } | 
|  |  | 
|  | if(strncmp((char*)np, "\nFrom ", 6) == 0){ | 
|  | done = 1; | 
|  | p = np+1; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* add to message (+ 1 in malloc is for a trailing null) */ | 
|  | n = p - inb->rptr; | 
|  | addtomessage(m, inb->rptr, n, done); | 
|  | inb->rptr += n; | 
|  | } | 
|  |  | 
|  | /* if it doesn't start with a 'From ', this ain't a mailbox */ | 
|  | if(strncmp(m->start, "From ", 5) != 0) | 
|  | return -1; | 
|  |  | 
|  | /* dump trailing newline, make sure there's a trailing null */ | 
|  | /* (helps in body searches) */ | 
|  | if(*(m->end-1) == '\n') | 
|  | m->end--; | 
|  | *m->end = 0; | 
|  | m->bend = m->rbend = m->end; | 
|  |  | 
|  | /* digest message */ | 
|  | sha1((uchar*)m->start, m->end - m->start, m->digest, nil); | 
|  | for(i = 0; i < SHA1dlen; i++) | 
|  | sprint(sdigest+2*i, "%2.2ux", m->digest[i]); | 
|  | m->sdigest = s_copy(sdigest); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* throw out deleted messages.  return number of freshly deleted messages */ | 
|  | int | 
|  | purgedeleted(Mailbox *mb) | 
|  | { | 
|  | Message *m, *next; | 
|  | int newdels; | 
|  |  | 
|  | /* forget about what's no longer in the mailbox */ | 
|  | newdels = 0; | 
|  | for(m = mb->root->part; m != nil; m = next){ | 
|  | next = m->next; | 
|  | if(m->deleted && m->refs == 0){ | 
|  | if(m->inmbox) | 
|  | newdels++; | 
|  | delmessage(mb, m); | 
|  | } | 
|  | } | 
|  | return newdels; | 
|  | } | 
|  |  | 
|  | /* */ | 
|  | /*  read in the mailbox and parse into messages. */ | 
|  | /* */ | 
|  | static char* | 
|  | _readmbox(Mailbox *mb, int doplumb, Mlock *lk) | 
|  | { | 
|  | int fd; | 
|  | String *tmp; | 
|  | Dir *d; | 
|  | static char err[128]; | 
|  | Message *m, **l; | 
|  | Inbuf *inb; | 
|  | char *x; | 
|  |  | 
|  | l = &mb->root->part; | 
|  |  | 
|  | /* | 
|  | *  open the mailbox.  If it doesn't exist, try the temporary one. | 
|  | */ | 
|  | retry: | 
|  | fd = open(mb->path, OREAD); | 
|  | if(fd < 0){ | 
|  | errstr(err, sizeof(err)); | 
|  | if(strstr(err, "exist") != 0){ | 
|  | tmp = s_copy(mb->path); | 
|  | s_append(tmp, ".tmp"); | 
|  | if(sysrename(s_to_c(tmp), mb->path) == 0){ | 
|  | s_free(tmp); | 
|  | goto retry; | 
|  | } | 
|  | s_free(tmp); | 
|  | } | 
|  | return err; | 
|  | } | 
|  |  | 
|  | /* | 
|  | *  a new qid.path means reread the mailbox, while | 
|  | *  a new qid.vers means read any new messages | 
|  | */ | 
|  | d = dirfstat(fd); | 
|  | if(d == nil){ | 
|  | close(fd); | 
|  | errstr(err, sizeof(err)); | 
|  | return err; | 
|  | } | 
|  | if(mb->d != nil){ | 
|  | if(d->qid.path == mb->d->qid.path && d->qid.vers == mb->d->qid.vers){ | 
|  | close(fd); | 
|  | free(d); | 
|  | return nil; | 
|  | } | 
|  | if(d->qid.path == mb->d->qid.path){ | 
|  | while(*l != nil) | 
|  | l = &(*l)->next; | 
|  | seek(fd, mb->d->length, 0); | 
|  | } | 
|  | free(mb->d); | 
|  | } | 
|  | mb->d = d; | 
|  | mb->vers++; | 
|  | henter(PATH(0, Qtop), mb->name, | 
|  | (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb); | 
|  |  | 
|  | inb = emalloc(sizeof(Inbuf)); | 
|  | inb->rptr = inb->wptr = inb->data; | 
|  | inb->fd = fd; | 
|  |  | 
|  | /*  read new messages */ | 
|  | snprint(err, sizeof err, "reading '%s'", mb->path); | 
|  | logmsg(err, nil); | 
|  | for(;;){ | 
|  | if(lk != nil) | 
|  | syslockrefresh(lk); | 
|  | m = newmessage(mb->root); | 
|  | m->mallocd = 1; | 
|  | m->inmbox = 1; | 
|  | if(readmessage(m, inb) < 0){ | 
|  | delmessage(mb, m); | 
|  | mb->root->subname--; | 
|  | break; | 
|  | } | 
|  |  | 
|  | /* merge mailbox versions */ | 
|  | while(*l != nil){ | 
|  | if(memcmp((*l)->digest, m->digest, SHA1dlen) == 0){ | 
|  | /* matches mail we already read, discard */ | 
|  | logmsg("duplicate", *l); | 
|  | delmessage(mb, m); | 
|  | mb->root->subname--; | 
|  | m = nil; | 
|  | l = &(*l)->next; | 
|  | break; | 
|  | } else { | 
|  | /* old mail no longer in box, mark deleted */ | 
|  | logmsg("disappeared", *l); | 
|  | if(doplumb) | 
|  | mailplumb(mb, *l, 1); | 
|  | (*l)->inmbox = 0; | 
|  | (*l)->deleted = 1; | 
|  | l = &(*l)->next; | 
|  | } | 
|  | } | 
|  | if(m == nil) | 
|  | continue; | 
|  |  | 
|  | x = strchr(m->start, '\n'); | 
|  | if(x == nil) | 
|  | m->header = m->end; | 
|  | else | 
|  | m->header = x + 1; | 
|  | m->mheader = m->mhend = m->header; | 
|  | parseunix(m); | 
|  | parse(m, 0, mb, 0); | 
|  | logmsg("new", m); | 
|  |  | 
|  | /* chain in */ | 
|  | *l = m; | 
|  | l = &m->next; | 
|  | if(doplumb) | 
|  | mailplumb(mb, m, 0); | 
|  |  | 
|  | } | 
|  | logmsg("mbox read", nil); | 
|  |  | 
|  | /* whatever is left has been removed from the mbox, mark deleted */ | 
|  | while(*l != nil){ | 
|  | if(doplumb) | 
|  | mailplumb(mb, *l, 1); | 
|  | (*l)->inmbox = 0; | 
|  | (*l)->deleted = 1; | 
|  | l = &(*l)->next; | 
|  | } | 
|  |  | 
|  | close(fd); | 
|  | free(inb); | 
|  | return nil; | 
|  | } | 
|  |  | 
|  | static void | 
|  | _writembox(Mailbox *mb, Mlock *lk) | 
|  | { | 
|  | Dir *d; | 
|  | Message *m; | 
|  | String *tmp; | 
|  | int mode, errs; | 
|  | Biobuf *b; | 
|  |  | 
|  | tmp = s_copy(mb->path); | 
|  | s_append(tmp, ".tmp"); | 
|  |  | 
|  | /* | 
|  | * preserve old files permissions, if possible | 
|  | */ | 
|  | d = dirstat(mb->path); | 
|  | if(d != nil){ | 
|  | mode = d->mode&0777; | 
|  | free(d); | 
|  | } else | 
|  | mode = MBOXMODE; | 
|  |  | 
|  | sysremove(s_to_c(tmp)); | 
|  | b = sysopen(s_to_c(tmp), "alc", mode); | 
|  | if(b == 0){ | 
|  | fprint(2, "can't write temporary mailbox %s: %r\n", s_to_c(tmp)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | logmsg("writing new mbox", nil); | 
|  | errs = 0; | 
|  | for(m = mb->root->part; m != nil; m = m->next){ | 
|  | if(lk != nil) | 
|  | syslockrefresh(lk); | 
|  | if(m->deleted) | 
|  | continue; | 
|  | logmsg("writing", m); | 
|  | if(Bwrite(b, m->start, m->end - m->start) < 0) | 
|  | errs = 1; | 
|  | if(Bwrite(b, "\n", 1) < 0) | 
|  | errs = 1; | 
|  | } | 
|  | logmsg("wrote new mbox", nil); | 
|  |  | 
|  | if(sysclose(b) < 0) | 
|  | errs = 1; | 
|  |  | 
|  | if(errs){ | 
|  | fprint(2, "error writing temporary mail file\n"); | 
|  | s_free(tmp); | 
|  | return; | 
|  | } | 
|  |  | 
|  | sysremove(mb->path); | 
|  | if(sysrename(s_to_c(tmp), mb->path) < 0) | 
|  | fprint(2, "%s: can't rename %s to %s: %r\n", argv0, | 
|  | s_to_c(tmp), mb->path); | 
|  | s_free(tmp); | 
|  | if(mb->d != nil) | 
|  | free(mb->d); | 
|  | mb->d = dirstat(mb->path); | 
|  | } | 
|  |  | 
|  | char* | 
|  | plan9syncmbox(Mailbox *mb, int doplumb) | 
|  | { | 
|  | Mlock *lk; | 
|  | char *rv; | 
|  |  | 
|  | lk = nil; | 
|  | if(mb->dolock){ | 
|  | lk = syslock(mb->path); | 
|  | if(lk == nil) | 
|  | return "can't lock mailbox"; | 
|  | } | 
|  |  | 
|  | rv = _readmbox(mb, doplumb, lk);		/* interpolate */ | 
|  | if(purgedeleted(mb) > 0) | 
|  | _writembox(mb, lk); | 
|  |  | 
|  | if(lk != nil) | 
|  | sysunlock(lk); | 
|  |  | 
|  | return rv; | 
|  | } | 
|  |  | 
|  | /* */ | 
|  | /*  look to see if we can open this mail box */ | 
|  | /* */ | 
|  | char* | 
|  | plan9mbox(Mailbox *mb, char *path) | 
|  | { | 
|  | static char err[64]; | 
|  | String *tmp; | 
|  |  | 
|  | if(access(path, AEXIST) < 0){ | 
|  | errstr(err, sizeof(err)); | 
|  | tmp = s_copy(path); | 
|  | s_append(tmp, ".tmp"); | 
|  | if(access(s_to_c(tmp), AEXIST) < 0){ | 
|  | s_free(tmp); | 
|  | return err; | 
|  | } | 
|  | s_free(tmp); | 
|  | } | 
|  |  | 
|  | mb->sync = plan9syncmbox; | 
|  | return nil; | 
|  | } |