#include "common.h" #include #include #include #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"); 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(stdmbox); 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; }