]> git.lizzy.rs Git - plan9front.git/blobdiff - sys/src/cmd/upas/fs/fs.c
upas/fs: fix more locking bugs, remove debugging clutter, remove planb mbox code
[plan9front.git] / sys / src / cmd / upas / fs / fs.c
index 8f62b002a289cbccd2c0ce2d0019e85dc61f297c..3137b9d9a53f03581aa10c2b0b7015935e3e7670 100644 (file)
@@ -1,17 +1,10 @@
 #include "common.h"
-#include <auth.h>
 #include <fcall.h>
 #include <libsec.h>
-#include <ctype.h>
+#include <pool.h>
 #include "dat.h"
 
-enum
-{
-       OPERM   = 0x3,          // mask of all permission types in open mode
-};
-
 typedef struct Fid Fid;
-
 struct Fid
 {
        Qid     qid;
@@ -21,31 +14,13 @@ struct Fid
        Fid     *next;
        Mailbox *mb;
        Message *m;
-       Message *mtop;          // top level message
+       Message *mtop;          /* top level message */
 
-       //finger pointers to speed up reads of large directories
-       long    foff;   // offset/DIRLEN of finger
-       Message *fptr;  // pointer to message at off
-       int     fvers;  // mailbox version when finger was saved
+       long    foff;           /* offset/DIRLEN of finger */
+       Message *fptr;          /* pointer to message at off */
+       int     fvers;          /* mailbox version when finger was saved */
 };
 
-ulong  path;           // incremented for each new file
-Fid    *fids;
-int    mfd[2];
-char   user[Elemlen];
-int    messagesize = 4*1024+IOHDRSZ;
-uchar  mdata[8*1024+IOHDRSZ];
-uchar  mbuf[8*1024+IOHDRSZ];
-Fcall  thdr;
-Fcall  rhdr;
-int    fflg;
-char   *mntpt;
-int    biffing;
-int    plumbing = 1;
-
-QLock  mbllock;
-Mailbox        *mbl;
-
 Fid            *newfid(int);
 void           error(char*);
 void           io(void);
@@ -54,9 +29,6 @@ void          *emalloc(ulong);
 void           usage(void);
 void           reader(void);
 int            readheader(Message*, char*, int, int);
-int            cistrncmp(char*, char*, int);
-int            tokenconvert(String*, char*, int);
-String*                stringconvert(String*, char*, int);
 void           post(char*, char*, int);
 
 char   *rflush(Fid*), *rauth(Fid*),
@@ -93,30 +65,36 @@ char        Eisopen[] =     "file already open for I/O";
 char   Excl[] =        "exclusive use file already open";
 char   Ename[] =       "illegal name";
 char   Ebadctl[] =     "unknown control message";
+char   Ebadargs[] =    "invalid arguments";
+char   Enotme[] =      "path not served by this file server";
 
-char *dirtab[] =
-{
+char *dirtab[] = {
 [Qdir]         ".",
-[Qbody]                "body",
 [Qbcc]         "bcc",
+[Qbody]                "body",
 [Qcc]          "cc",
 [Qdate]                "date",
 [Qdigest]      "digest",
 [Qdisposition] "disposition",
+[Qffrom]               "ffrom",
+[Qfileid]              "fileid",
 [Qfilename]    "filename",
+[Qflags]               "flags",
 [Qfrom]                "from",
 [Qheader]      "header",
 [Qinfo]                "info",
 [Qinreplyto]   "inreplyto",
-[Qlines]       "lines",
-[Qmimeheader]  "mimeheader",
+[Qlines]               "lines",
 [Qmessageid]   "messageid",
+[Qmimeheader]  "mimeheader",
 [Qraw]         "raw",
-[Qrawunix]     "rawunix",
 [Qrawbody]     "rawbody",
 [Qrawheader]   "rawheader",
+[Qrawunix]     "rawunix",
+[Qreferences]  "references",
 [Qreplyto]     "replyto",
 [Qsender]      "sender",
+[Qsize]                "size",
 [Qsubject]     "subject",
 [Qto]          "to",
 [Qtype]                "type",
@@ -128,62 +106,265 @@ char *dirtab[] =
 
 enum
 {
-       Hsize=  1277,
+       Hsize=  1999,
 };
 
-Hash   *htab[Hsize];
 
+char   *mntpt;
+char   user[Elemlen];
+int    Dflag;
+int    Sflag;
+int    iflag;
+int    lflag;
+int    biffing;
 int    debug;
-int    fflag;
-int    logging;
+int    plumbing = 1;
+ulong  cachetarg = Maxcache;
+Mailbox        *mbl;
+QLock  mbllock;
+
+static int     messagesize = 8*1024 + IOHDRSZ;
+static int     mfd[2];
+static char    hbuf[32*1024];
+static uchar   mbuf[16*1024 + IOHDRSZ];
+static uchar   mdata[16*1024 + IOHDRSZ];
+static ulong   path;           /* incremented for each new file */
+static Hash    *htab[Hsize];
+static Fcall   rhdr;
+static Fcall   thdr;
+static Fid     *fids;
+static uintptr bos = 0xd0000000;               /* pc kernel specific */
+
+#define onstack(x)     ((uintptr)(x) >= bos)
+#define intext(x)              ((char*)(x) <= end)
+#define validgptr(x)   assert(!onstack(x) && !intext(x))
+void
+sanemsg(Message *m)
+{
+       if(bos == 0)
+               bos = absbos();
+       assert(m->refs < 100);
+       validgptr(m->whole);
+       if(debug)
+               poolcheck(mainmem);
+       validgptr(m);
+       assert(m->next != m);
+       if(m->end < m->start)
+               abort();
+       if(m->ballocd && (m->start <= m->body && m->end >= m->body))
+               abort();
+       if(m->end - m->start > Maxmsg)
+               abort();
+       if(m->size > Maxmsg)
+               abort();
+       if(m->fileid != 0 && m->fileid <= 1000000ull<<8)
+               abort();
+}
+
+void
+sanembmsg(Mailbox *mb, Message *m)
+{
+       sanemsg(m);
+       if(Topmsg(mb, m)){
+               if(m->start > end && m->size == 0)
+                       abort();
+               if(m->fileid <= 1000000ull<<8)
+                       abort();
+       }
+}
+
+static int
+Afmt(Fmt *f)
+{
+       char buf[SHA1dlen*2 + 1];
+       uchar *u, i;
+
+       u = va_arg(f->args, uchar*);
+       if(u == 0 && f->flags & FmtSharp)
+               return fmtstrcpy(f, "-");
+       if(u == 0)
+               return fmtstrcpy(f, "<nildigest>");
+       for(i = 0; i < SHA1dlen; i++)
+               sprint(buf + 2*i, "%2.2ux", u[i]);
+       return fmtstrcpy(f, buf);
+}
+
+static int
+Δfmt(Fmt *f)
+{
+       char buf[32];
+       uvlong v;
+
+       v = va_arg(f->args, uvlong);
+       if(f->flags & FmtSharp)
+               if((v>>8) == 0)
+                       return fmtstrcpy(f, "");
+       strcpy(buf, ctime(v>>8));
+       buf[28] = 0;
+       return fmtstrcpy(f, buf);
+}
+
+static int
+Dfmt(Fmt *f)
+{
+       char buf[32];
+       int seq;
+       uvlong v;
+
+       v = va_arg(f->args, uvlong);
+       seq = v & 0xff;
+       if(seq > 99)
+               seq = 99;
+       snprint(buf, sizeof buf, "%llud.%.2d", v>>8, seq);
+       return fmtstrcpy(f, buf);
+}
+
+Mpair
+mpair(Mailbox *mb, Message *m)
+{
+       Mpair mp;
+
+       mp.mb = mb;
+       mp.m = m;
+       return mp;
+}
+
+static int
+Pfmt(Fmt *f)
+{
+       char buf[128], *p, *e;
+       int i, dots;
+       Mailbox *mb;
+       Message *t[32], *m;
+       Mpair mp;
+
+       mp = va_arg(f->args, Mpair);
+       mb = mp.mb;
+       m = mp.m;
+       if(m == nil || mb == nil)
+               return fmtstrcpy(f, "<P nil>");
+       i = 0;
+       for(; !Topmsg(mb, m); m = m->whole){
+               t[i++] = m;
+               if(i == nelem(t)-1)
+                       break;
+       }
+       t[i++] = m;
+       dots = 0;
+       if(i == nelem(t))
+               dots = 1;
+       e = buf + sizeof buf;
+       p = buf;
+       if(dots)
+               p = seprint(p, e, ".../");
+       while(--i >= 1)
+               p = seprint(p, e, "%s/", t[i]->name);
+       if(i == 0)
+               seprint(p, e, "%s", t[0]->name);
+       return fmtstrcpy(f, buf);
+}
 
 void
 usage(void)
 {
-       fprint(2, "usage: upas/fs [-bdlnps] [-f mboxfile] [-m mountpoint]\n");
+       fprint(2, "usage: upas/fs [-DSbdlmnps] [-c cachetarg] [-f mboxfile] [-m mountpoint]\n");
        exits("usage");
 }
 
 void
-notifyf(void *a, char *s)
+notifyf(void *, char *s)
 {
-       USED(a);
        if(strncmp(s, "interrupt", 9) == 0)
                noted(NCONT);
+       if(strncmp(s, "die: yankee pig dog", 19) != 0)
+               /* don't want to call syslog from notify handler */
+               fprint(2, "upas/fs: user: %s; note: %s\n", getuser(), s);
        noted(NDFLT);
 }
 
+void
+setname(char **v)
+{
+       char buf[128], buf2[32], *p, *e;
+       int fd, i;
+
+       e = buf + sizeof buf;
+       p = seprint(buf, e, "%s", v[0]);
+       for(i = 0; v[++i]; )
+               p = seprint(p, e, " %s", v[i]);
+       snprint(buf2, sizeof buf2, "#p/%d/args", getpid());
+       if((fd = open(buf2, OWRITE)) >= 0){
+               write(fd, buf, p - buf);
+               close(fd);
+       }
+}
+
+ulong
+ntoi(char *s)
+{
+       ulong n;
+
+       n = strtoul(s, &s, 0);
+       for(;;)
+       switch(*s++){
+       default:
+               usage();
+       case 'g':
+               n *= 1024;
+       case 'm':
+               n *= 1024;
+       case 'k':
+               n *= 1024;
+               break;
+       case 0:
+               return n;
+       }
+}
+
 void
 main(int argc, char *argv[])
 {
-       int p[2], std, nodflt;
-       char maildir[128];
-       char mbox[128];
+       char maildir[Pathlen], mbox[Pathlen], srvfile[64], **v;
        char *mboxfile, *err;
-       char srvfile[64];
-       int srvpost;
+       int p[2], nodflt, srvpost;
 
        rfork(RFNOTEG);
-       mntpt = nil;
-       fflag = 0;
        mboxfile = nil;
-       std = 0;
        nodflt = 0;
        srvpost = 0;
+       v = argv;
 
        ARGBEGIN{
+       case 'D':
+               Dflag = 1;
+               break;
+       case 'S':
+               Sflag = 1;
+               break;
        case 'b':
                biffing = 1;
                break;
+       case 'c':
+               cachetarg = ntoi(EARGF(usage()));
+               break;
+       case 'd':
+               debug = 1;
+               mainmem->flags |= POOL_PARANOIA;
+               break;
        case 'f':
-               fflag = 1;
                mboxfile = EARGF(usage());
                break;
+       case 'i':
+               iflag++;
+               break;
+       case 'l':
+               lflag = 1;
+               break;
        case 'm':
                mntpt = EARGF(usage());
                break;
-       case 'd':
-               debug = 1;
+       case 'n':
+               nodflt = 1;
                break;
        case 'p':
                plumbing = 0;
@@ -191,18 +372,19 @@ main(int argc, char *argv[])
        case 's':
                srvpost = 1;
                break;
-       case 'l':
-               logging = 1;
-               break;
-       case 'n':
-               nodflt = 1;
-               break;
        default:
                usage();
        }ARGEND
 
        if(argc)
                usage();
+       fmtinstall('A', Afmt);
+       fmtinstall('D', Dfmt);
+       fmtinstall(L'Δ', Δfmt);
+       fmtinstall('F', fcallfmt);
+       fmtinstall('H', encodefmt);             /* forces tls stuff */
+       fmtinstall('P', Pfmt);
+       quotefmtinstall();
        if(pipe(p) < 0)
                error("pipe failed");
        mfd[0] = p[0];
@@ -215,19 +397,13 @@ main(int argc, char *argv[])
                mntpt = maildir;
        }
        if(mboxfile == nil && !nodflt){
-               snprint(mbox, sizeof(mbox), "/mail/box/%s/mbox", user);
+               snprint(mbox, sizeof mbox, "/mail/box/%s/mbox", user);
                mboxfile = mbox;
-               std = 1;
        }
 
-       if(debug)
-               fmtinstall('F', fcallfmt);
-
-       if(mboxfile != nil){
-               err = newmbox(mboxfile, "mbox", std);
-               if(err != nil)
+       if(mboxfile != nil)
+               if(err = newmbox(mboxfile, "mbox", 0, nil))
                        sysfatal("opening %s: %s", mboxfile, err);
-       }
 
        switch(rfork(RFFDG|RFPROC|RFNAMEG|RFNOTEG|RFREND)){
        case -1:
@@ -236,46 +412,67 @@ main(int argc, char *argv[])
                henter(PATH(0, Qtop), dirtab[Qctl],
                        (Qid){PATH(0, Qctl), 0, QTFILE}, nil, nil);
                close(p[1]);
+               setname(v);
                io();
-               postnote(PNGROUP, getpid(), "die yankee pig dog");
+               syncallmboxes();
+               syskillpg(getpid());
                break;
        default:
                close(p[0]);    /* don't deadlock if child fails */
                if(srvpost){
-                       sprint(srvfile, "/srv/upasfs.%s", user);
+                       snprint(srvfile, sizeof srvfile, "/srv/upasfs.%s", user);
                        post(srvfile, "upasfs", p[1]);
-               } else {
+               }else
                        if(mount(p[1], -1, mntpt, MREPL, "") < 0)
                                error("mount failed");
-               }
        }
-       exits(0);
+       exits("");
 }
 
-static int
-fileinfo(Message *m, int t, char **pp)
+char*
+sputc(char *p, char *e, int c)
 {
-       char *p;
-       int len;
+       if(p < e - 1)
+               *p++ = c;
+       return p;
+}
 
-       p = "";
-       len = 0;
+char*
+seappend(char *s, char *e, char *a, int n)
+{
+       int l;
+
+       l = e - s - 1;
+       if(l < n)
+               n = l;
+       memcpy(s, a, n);
+       s += n;
+       *s = 0;
+       return s;
+}
+
+static int
+fileinfo(Mailbox *mb, Message *m, int t, char **pp)
+{
+       char *s, *e, *p;
+       int len, i;
+       static char buf[64 + 512];
+
+       cacheidx(mb, m);
+       sanembmsg(mb, m);
+       p = nil;
+       len = -1;
        switch(t){
        case Qbody:
+               cachebody(mb, m);
                p = m->body;
-               len = m->bend - m->body;
+               len = m->bend - p;
                break;
        case Qbcc:
-               if(m->bcc822){
-                       p = s_to_c(m->bcc822);
-                       len = strlen(p);
-               }
+               p = m->bcc;
                break;
        case Qcc:
-               if(m->cc822){
-                       p = s_to_c(m->cc822);
-                       len = strlen(p);
-               }
+               p = m->cc;
                break;
        case Qdisposition:
                switch(m->disposition){
@@ -286,142 +483,131 @@ fileinfo(Message *m, int t, char **pp)
                        p = "file";
                        break;
                }
-               len = strlen(p);
                break;
        case Qdate:
-               if(m->date822){
-                       p = s_to_c(m->date822);
-                       len = strlen(p);
-               } else if(m->unixdate != nil){
-                       p = s_to_c(m->unixdate);
-                       len = strlen(p);
+               p = m->date822;
+               if(!p){
+                       p = buf;
+                       len = snprint(buf, sizeof buf, "%#Δ", m->fileid);
                }
                break;
        case Qfilename:
-               if(m->filename){
-                       p = s_to_c(m->filename);
-                       len = strlen(p);
-               }
+               p = m->filename;
+               break;
+       case Qflags:
+               p = flagbuf(buf, m->flags);
                break;
        case Qinreplyto:
-               if(m->inreplyto822){
-                       p = s_to_c(m->inreplyto822);
-                       len = strlen(p);
-               }
+               p = m->inreplyto;
                break;
        case Qmessageid:
-               if(m->messageid822){
-                       p = s_to_c(m->messageid822);
-                       len = strlen(p);
-               }
+               p = m->messageid;
                break;
        case Qfrom:
-               if(m->from822){
-                       p = s_to_c(m->from822);
-                       len = strlen(p);
-               } else if(m->unixfrom != nil){
-                       p = s_to_c(m->unixfrom);
-                       len = strlen(p);
-               }
+               if(m->from)
+                       p = m->from;
+               else
+                       p = m->unixfrom;
                break;
-       case Qheader:
-               p = m->header;
-               len = headerlen(m);
+       case Qffrom:
+               if(m->ffrom)
+                       p = m->ffrom;
                break;
        case Qlines:
-               p = m->lines;
-               if(*p == 0)
-                       countlines(m);
-               len = strlen(m->lines);
+               len = snprint(buf, sizeof buf, "%lud", m->lines);
+               p = buf;
                break;
        case Qraw:
+               cachebody(mb, m);
                p = m->start;
-               if(strncmp(m->start, "From ", 5) == 0){
-                       p = strchr(p, '\n');
-                       if(p == nil)
-                               p = m->start;
-                       else
-                               p++;
-               }
-               len = m->end - p;
+               if(strncmp(m->start, "From ", 5) == 0)
+               if(e = strchr(p, '\n'))
+                       p = e + 1;
+               len = m->rbend - p;
                break;
        case Qrawunix:
+               cachebody(mb, m);
                p = m->start;
                len = m->end - p;
                break;
        case Qrawbody:
+               cachebody(mb, m);
                p = m->rbody;
                len = m->rbend - p;
                break;
        case Qrawheader:
+               cacheheaders(mb, m);
                p = m->header;
                len = m->hend - p;
                break;
        case Qmimeheader:
+               cacheheaders(mb, m);
                p = m->mheader;
                len = m->mhend - p;
                break;
-       case Qreplyto:
-               p = nil;
-               if(m->replyto822 != nil){
-                       p = s_to_c(m->replyto822);
-                       len = strlen(p);
-               } else if(m->from822 != nil){
-                       p = s_to_c(m->from822);
-                       len = strlen(p);
-               } else if(m->sender822 != nil){
-                       p = s_to_c(m->sender822);
-                       len = strlen(p);
-               } else if(m->unixfrom != nil){
-                       p = s_to_c(m->unixfrom);
-                       len = strlen(p);
+       case Qreferences:
+               cacheheaders(mb, m);
+               e = buf + sizeof buf;
+               s = buf;
+               for(i = 0; i < nelem(m->references); i++){
+                       if(m->references[i] == 0)
+                               break;
+                       s = seprint(s, e, "%s\n", m->references[i]);
                }
+               *s = 0;
+               p = buf;
+               len = s - buf;
+               break;
+       case Qreplyto:
+               if(m->replyto != nil)
+                       p = m->replyto;
+               else if(m->from != nil)
+                       p = m->from;
+               else if(m->sender != nil)
+                       p = m->sender;
+               else if(m->unixfrom != nil)
+                       p = m->unixfrom;
                break;
        case Qsender:
-               if(m->sender822){
-                       p = s_to_c(m->sender822);
-                       len = strlen(p);
-               }
+               p = m->sender;
                break;
        case Qsubject:
-               p = nil;
-               if(m->subject822){
-                       p = s_to_c(m->subject822);
-                       len = strlen(p);
-               }
+               p = m->subject;
+               break;
+       case Qsize:
+               len = snprint(buf, sizeof buf, "%lud", m->size);
+               p = buf;
                break;
        case Qto:
-               if(m->to822){
-                       p = s_to_c(m->to822);
-                       len = strlen(p);
-               }
+               p = m->to;
                break;
        case Qtype:
-               if(m->type){
-                       p = s_to_c(m->type);
-                       len = strlen(p);
-               }
+               p = rtab[m->type].s;
+               len = rtab[m->type].l;
                break;
        case Qunixdate:
-               if(m->unixdate){
-                       p = s_to_c(m->unixdate);
-                       len = strlen(p);
-               }
+               p = buf;
+               len = snprint(buf, sizeof buf, "%#Δ", m->fileid);
+               break;
+       case Qfileid:
+               p = buf;
+               len = snprint(buf, sizeof buf, "%D", m->fileid);
                break;
        case Qunixheader:
-               if(m->unixheader){
-                       p = s_to_c(m->unixheader);
-                       len = s_len(m->unixheader);
-               }
+               cacheheaders(mb, m);
+               p = m->unixheader;
                break;
        case Qdigest:
-               if(m->sdigest){
-                       p = s_to_c(m->sdigest);
-                       len = strlen(p);
-               }
+               p = buf;
+               len = snprint(buf, sizeof buf, "%A", m->digest);
                break;
        }
+       if(p == nil)
+               p = "";
+       if(len == -1)
+               len = strlen(p);
        *pp = p;
+       putcache(mb, m);
        return len;
 }
 
@@ -442,47 +628,53 @@ int infofields[] = {
        Qsender,
        Qmessageid,
        Qlines,
-       -1,
+       Qsize,
+       Qflags,
+       Qfileid,
+       Qffrom,
 };
 
-static int
-readinfo(Message *m, char *buf, long off, int count)
+int
+readinfo(Mailbox *mb, Message *m, char *buf, long off, int count)
 {
-       char *p;
-       int len, i, n;
-       String *s;
-
-       s = s_new();
-       len = 0;
-       for(i = 0; len < count && infofields[i] >= 0; i++){
-               n = fileinfo(m, infofields[i], &p);
-               s = stringconvert(s, p, n);
-               s_append(s, "\n");
-               p = s_to_c(s);
-               n = strlen(p);
-               if(off > 0){
-                       if(off >= n){
-                               off -= n;
-                               continue;
-                       }
-                       p += off;
+       char *s, *p, *e;
+       int i, n;
+       long off0;
+
+       if(m->infolen > 0 && off >= m->infolen)
+               return 0;
+       off0 = off;
+       s = buf;
+       e = s + count;
+       for(i = 0; s < e; i++){
+               if(i == nelem(infofields)){
+                       m->infolen = s - buf + off0;
+                       break;
+               }
+               n = fileinfo(mb, m, infofields[i], &p);
+               if(off > n){
+                       off -= n + 1;
+                       continue;
+               }
+               if(off){
                        n -= off;
+                       p += off;
                        off = 0;
                }
-               if(n > count - len)
-                       n = count - len;
-               if(buf)
-                       memmove(buf+len, p, n);
-               len += n;
+               if(s + n > e)
+                       n = e - s;
+               memcpy(s, p, n);
+               s += n;
+               if(s < e)
+                       *s++ = '\n';
        }
-       s_free(s);
-       return len;
+       return s - buf;
 }
 
 static void
 mkstat(Dir *d, Mailbox *mb, Message *m, int t)
 {
-       char *p;
+       char *p, *e;
 
        d->uid = user;
        d->gid = user;
@@ -492,13 +684,13 @@ mkstat(Dir *d, Mailbox *mb, Message *m, int t)
        d->qid.type = QTFILE;
        d->type = 0;
        d->dev = 0;
-       if(mb != nil && mb->d != nil){
-               d->atime = mb->d->atime;
-               d->mtime = mb->d->mtime;
-       } else {
+       if(m && m->fileid > 1000000ull)
+               d->atime = m->fileid >> 8;
+       else if(mb && mb->d)
+               d->atime = mb->d->mtime;
+       else
                d->atime = time(0);
-               d->mtime = d->atime;
-       }
+       d->mtime = d->atime;
 
        switch(t){
        case Qtop:
@@ -531,6 +723,12 @@ mkstat(Dir *d, Mailbox *mb, Message *m, int t)
                d->length = 0;
                d->qid.path = PATH(0, Qctl);
                break;
+       case Qheader:
+               d->name = dirtab[t];
+               cacheheaders(mb, m);
+               d->length = readheader(m, hbuf, 0, sizeof hbuf);
+               putcache(mb, m);
+               break;
        case Qmboxctl:
                d->name = dirtab[t];
                d->mode = 0222;
@@ -540,12 +738,37 @@ mkstat(Dir *d, Mailbox *mb, Message *m, int t)
                break;
        case Qinfo:
                d->name = dirtab[t];
-               d->length = readinfo(m, nil, 0, 1<<30);
+               d->length = readinfo(mb, m, hbuf, 0, sizeof hbuf);
                d->qid.path = PATH(m->id, t);
                break;
+       case Qraw:
+               cacheheaders(mb, m);
+               p = m->start;
+               if(strncmp(m->start, "From ", 5) == 0)
+               if(e = strchr(p, '\n'))
+                       p = e + 1;
+               d->name = dirtab[t];
+               d->length = m->size - (p - m->start);
+               putcache(mb, m);
+               break;
+       case Qrawbody:
+               d->name = dirtab[t];
+               d->length = m->rawbsize;
+               break;
+       case Qrawunix:
+               d->name = dirtab[t];
+               d->length = m->size;
+               if(mb->addfrom && Topmsg(mb, m)){
+                       cacheheaders(mb, m);
+                       d->length += strlen(m->unixheader);
+                       putcache(mb, m);
+               }
+               break;
+       case Qflags:
+               d->mode = 0666;
        default:
                d->name = dirtab[t];
-               d->length = fileinfo(m, t, &p);
+               d->length = fileinfo(mb, m, t, &p);
                d->qid.path = PATH(m->id, t);
                break;
        }
@@ -578,9 +801,8 @@ rauth(Fid*)
 }
 
 char*
-rflush(Fid *f)
+rflush(Fid*)
 {
-       USED(f);
        return 0;
 }
 
@@ -609,47 +831,76 @@ doclone(Fid *f, int nfid)
                return nil;
        nf->busy = 1;
        nf->open = 0;
-       nf->m = f->m;
-       nf->mtop = f->mtop;
        nf->mb = f->mb;
-       if(f->mb != nil)
-               mboxincref(f->mb);
-       if(f->mtop != nil){
-               qlock(f->mb);
-               msgincref(f->mtop);
-               qunlock(f->mb);
+       if(nf->mb){
+               qlock(nf->mb);
+               mboxincref(nf->mb);
        }
+       if(nf->m = f->m)
+               msgincref(gettopmsg(nf->mb, nf->m));
+       if(nf->mtop = f->mtop)
+               msgincref(nf->mtop);
        nf->qid = f->qid;
+       if(nf->mb){
+               qunlock(nf->mb);
+       }
        return nf;
 }
 
+/* slow?  binary search? */
+static int
+dindex(char *name)
+{
+       int i;
+
+       for(i = 0; i < Qmax; i++)
+               if(dirtab[i] != nil)
+               if(strcmp(dirtab[i], name) == 0)
+                       return i;
+       return -1;
+}
+
 char*
 dowalk(Fid *f, char *name)
 {
-       int t;
-       Mailbox *omb, *mb;
        char *rv, *p;
+       int t, t1;
+       Mailbox *mb;
        Hash *h;
 
        t = FILE(f->qid.path);
-
        rv = Enotexist;
 
-       omb = f->mb;
-       if(omb)
-               qlock(omb);
-       else
-               qlock(&mbllock);
+       qlock(&mbllock);
+       if(f->mb)
+               qlock(f->mb);
 
-       // this must catch everything except . and ..
+       /* this must catch everything except . and .. */
 retry:
-       h = hlook(f->qid.path, name);
+       t1 = FILE(f->qid.path);
+       if((t1 == Qmbox || t1 == Qdir) && *name >= 'a' && *name <= 'z'){
+               h = hlook(f->qid.path, "xxx");          /* sleezy speedup */
+               t1 = dindex(name);
+               if(t1 == -1)
+                       h = nil;
+       }else
+               h = hlook(f->qid.path, name);
        if(h != nil){
+               if(f->m)
+                       msgdecref(f->mb, gettopmsg(f->mb, f->m));
+               if(f->mb && f->mb != h->mb){
+                       qunlock(f->mb);
+                       mboxdecref(f->mb);
+               }
+               if(h->mb && h->mb != f->mb)
+                       qlock(h->mb);
                f->mb = h->mb;
                f->m = h->m;
+               if(f->m)
+                       msgincref(gettopmsg(f->mb, f->m));
                switch(t){
                case Qtop:
-                       if(f->mb != nil)
+                       if(f->mb)
                                mboxincref(f->mb);
                        break;
                case Qmbox:
@@ -660,16 +911,18 @@ retry:
                        break;
                }
                f->qid = h->qid;
+               if(t1 < Qmax)
+                       f->qid.path = PATH(f->m->id, t1);       /* sleezy speedup */
                rv = nil;
-       } else if((p = strchr(name, '.')) != nil && *name != '.'){
+       }else if((p = strchr(name, '.')) != nil && *name != '.'){
                *p = 0;
                goto retry;
        }
 
-       if(omb)
-               qunlock(omb);
-       else
-               qunlock(&mbllock);
+       if(f->mb)
+               qunlock(f->mb);
+       qunlock(&mbllock);
+
        if(rv == nil)
                return rv;
 
@@ -690,21 +943,23 @@ retry:
                        f->qid.path = PATH(0, Qtop);
                        f->qid.type = QTDIR;
                        f->qid.vers = 0;
-                       qlock(&mbllock);
                        mb = f->mb;
                        f->mb = nil;
+                       qlock(&mbllock);
                        mboxdecref(mb);
                        qunlock(&mbllock);
                        break;
                case Qdir:
                        qlock(f->mb);
-                       if(f->m->whole == f->mb->root){
+                       if(Topmsg(f->mb, f->m)){
                                f->qid.path = PATH(f->mb->id, Qmbox);
                                f->qid.type = QTDIR;
                                f->qid.vers = f->mb->d->qid.vers;
                                msgdecref(f->mb, f->mtop);
+                               msgdecref(f->mb, f->m);
                                f->m = f->mtop = nil;
                        } else {
+                               /* refs don't change; still the same message */
                                f->m = f->m->whole;
                                f->qid.path = PATH(f->m->id, Qdir);
                                f->qid.type = QTDIR;
@@ -755,32 +1010,32 @@ rwalk(Fid *f)
        }
        rhdr.nwqid = i;
 
-       /* we only error out if no walk  */
+       /* we only error out if no walk */
        if(i > 0)
                rv = nil;
-
        return rv;
 }
 
-char *
+char*
 ropen(Fid *f)
 {
        int file;
 
        if(f->open)
                return Eisopen;
-
        file = FILE(f->qid.path);
        if(thdr.mode != OREAD)
-               if(file != Qctl && file != Qmboxctl)
+               if(file != Qctl && file != Qmboxctl && file != Qflags)
                        return Eperm;
 
-       // make sure we've decoded
+       /* make sure we've decoded */
        if(file == Qbody){
-               if(f->m->decoded == 0)
-                       decode(f->m);
-               if(f->m->converted == 0)
-                       convert(f->m);
+               qlock(f->mb);
+               cachebody(f->mb, f->m);
+               decode(f->m);
+               convert(f->m);
+               putcache(f->mb, f->m);
+               qunlock(f->mb);
        }
 
        rhdr.iounit = 0;
@@ -789,7 +1044,7 @@ ropen(Fid *f)
        return 0;
 }
 
-char *
+char*
 rcreate(Fid*)
 {
        return Eperm;
@@ -803,21 +1058,25 @@ readtopdir(Fid*, uchar *buf, long off, int cnt, int blen)
        long pos;
        Mailbox *mb;
 
+       qlock(&mbllock);
+
        n = 0;
        pos = 0;
        mkstat(&d, nil, nil, Qctl);
        m = convD2M(&d, &buf[n], blen);
        if(off <= pos){
                if(m <= BIT16SZ || m > cnt)
-                       return 0;
+                       goto out;
                n += m;
                cnt -= m;
        }
        pos += m;
                
        for(mb = mbl; mb != nil; mb = mb->next){
+               qlock(mb);
                mkstat(&d, mb, nil, Qmbox);
-               m = convD2M(&d, &buf[n], blen-n);
+               qunlock(mb);
+               m = convD2M(&d, &buf[n], blen - n);
                if(off <= pos){
                        if(m <= BIT16SZ || m > cnt)
                                break;
@@ -826,6 +1085,8 @@ readtopdir(Fid*, uchar *buf, long off, int cnt, int blen)
                }
                pos += m;
        }
+out:
+       qlock(&mbllock);
        return n;
 }
 
@@ -837,6 +1098,10 @@ readmboxdir(Fid *f, uchar *buf, long off, int cnt, int blen)
        long pos;
        Message *msg;
 
+       qlock(f->mb);
+       if(off == 0)
+               syncmbox(f->mb, 1);
+
        n = 0;
        if(f->mb->ctl){
                mkstat(&d, f->mb, nil, Qmboxctl);
@@ -844,7 +1109,7 @@ readmboxdir(Fid *f, uchar *buf, long off, int cnt, int blen)
                if(off == 0){
                        if(m <= BIT16SZ || m > cnt){
                                f->fptr = nil;
-                               return 0;
+                               goto out;
                        }
                        n += m;
                        cnt -= m;
@@ -852,22 +1117,22 @@ readmboxdir(Fid *f, uchar *buf, long off, int cnt, int blen)
                        off -= m;
        }
 
-       // to avoid n**2 reads of the directory, use a saved finger pointer
+       /* to avoid n**2 reads of the directory, use a saved finger pointer */
        if(f->mb->vers == f->fvers && off >= f->foff && f->fptr != nil){
                msg = f->fptr;
                pos = f->foff;
        } else {
                msg = f->mb->root->part;
                pos = 0;
-       } 
+       }
 
        for(; cnt > 0 && msg != nil; msg = msg->next){
-               // act like deleted files aren't there
+               /* act like deleted files aren't there */
                if(msg->deleted)
                        continue;
 
                mkstat(&d, f->mb, msg, Qdir);
-               m = convD2M(&d, &buf[n], blen-n);
+               m = convD2M(&d, &buf[n], blen - n);
                if(off <= pos){
                        if(m <= BIT16SZ || m > cnt)
                                break;
@@ -877,11 +1142,12 @@ readmboxdir(Fid *f, uchar *buf, long off, int cnt, int blen)
                pos += m;
        }
 
-       // save a finger pointer for next read of the mbox directory
+       /* save a finger pointer for next read of the mbox directory */
        f->foff = pos;
        f->fptr = msg;
        f->fvers = f->mb->vers;
-
+out:
+       qunlock(f->mb);
        return n;
 }
 
@@ -893,14 +1159,15 @@ readmsgdir(Fid *f, uchar *buf, long off, int cnt, int blen)
        long pos;
        Message *msg;
 
+       qlock(f->mb);
        n = 0;
        pos = 0;
        for(i = 0; i < Qmax; i++){
                mkstat(&d, f->mb, f->m, i);
-               m = convD2M(&d, &buf[n], blen-n);
+               m = convD2M(&d, &buf[n], blen - n);
                if(off <= pos){
                        if(m <= BIT16SZ || m > cnt)
-                               return n;
+                               goto out;
                        n += m;
                        cnt -= m;
                }
@@ -908,7 +1175,7 @@ readmsgdir(Fid *f, uchar *buf, long off, int cnt, int blen)
        }
        for(msg = f->m->part; msg != nil; msg = msg->next){
                mkstat(&d, f->mb, msg, Qdir);
-               m = convD2M(&d, &buf[n], blen-n);
+               m = convD2M(&d, &buf[n], blen - n);
                if(off <= pos){
                        if(m <= BIT16SZ || m > cnt)
                                break;
@@ -917,64 +1184,109 @@ readmsgdir(Fid *f, uchar *buf, long off, int cnt, int blen)
                }
                pos += m;
        }
-
+out:
+       qunlock(f->mb);
        return n;
 }
 
+static int
+mboxctlread(Mailbox *mb, char **p)
+{
+       static char buf[128];
+
+       *p = buf;
+       return snprint(*p, sizeof buf, "%s\n%ld\n", mb->path, mb->vers);
+}
+
 char*
 rread(Fid *f)
 {
-       long off;
-       int t, i, n, cnt;
        char *p;
+       int t, i, n, cnt;
+       long off;
 
        rhdr.count = 0;
        off = thdr.offset;
        cnt = thdr.count;
-
        if(cnt > messagesize - IOHDRSZ)
                cnt = messagesize - IOHDRSZ;
-
        rhdr.data = (char*)mbuf;
 
        t = FILE(f->qid.path);
        if(f->qid.type & QTDIR){
-               if(t == Qtop) {
-                       qlock(&mbllock);
+               if(t == Qtop)
                        n = readtopdir(f, mbuf, off, cnt, messagesize - IOHDRSZ);
-                       qunlock(&mbllock);
-               } else if(t == Qmbox) {
-                       qlock(f->mb);
-                       if(off == 0)
-                               syncmbox(f->mb, 1);
+               else if(t == Qmbox)
                        n = readmboxdir(f, mbuf, off, cnt, messagesize - IOHDRSZ);
-                       qunlock(f->mb);
-               } else if(t == Qmboxctl) {
+               else if(t == Qmboxctl)
                        n = 0;
-               } else {
+               else
                        n = readmsgdir(f, mbuf, off, cnt, messagesize - IOHDRSZ);
-               }
-
                rhdr.count = n;
                return nil;
        }
 
-       if(FILE(f->qid.path) == Qheader){
+       qlock(f->mb);
+       switch(t){
+       case Qctl:
+               rhdr.count = 0;
+               break;
+       case Qmboxctl:
+               i = mboxctlread(f->mb, &p);
+               goto output;
+       case Qheader:
+               cacheheaders(f->mb, f->m);
                rhdr.count = readheader(f->m, (char*)mbuf, off, cnt);
-               return nil;
-       }
-
-       if(FILE(f->qid.path) == Qinfo){
-               rhdr.count = readinfo(f->m, (char*)mbuf, off, cnt);
-               return nil;
+               putcache(f->mb, f->m);
+               break;
+       case Qinfo:
+               if(cnt > sizeof mbuf)
+                       cnt = sizeof mbuf;
+               rhdr.count = readinfo(f->mb, f->m, (char*)mbuf, off, cnt);
+               break;
+       case Qrawunix:
+               if(f->mb->addfrom && Topmsg(f->mb, f->m)){
+                       cacheheaders(f->mb, f->m);
+                       p = f->m->unixheader;
+                       if(off < strlen(p)){
+                               rhdr.count = strlen(p + off);
+                               memmove(mbuf, p + off, rhdr.count);
+                               break;
+                       }
+                       off -= strlen(p);
+                       putcache(f->mb, f->m);
+               }
+       default:
+               i = fileinfo(f->mb, f->m, t, &p);
+       output:
+               if(off < i){
+                       if(off + cnt > i)
+                               cnt = i - off;
+                       if(cnt > sizeof mbuf)
+                               cnt = sizeof mbuf;
+                       memmove(mbuf, p + off, cnt);
+                       rhdr.count = cnt;
+               }
+               break;
        }
+       qunlock(f->mb);
+       return nil;
+}
 
-       i = fileinfo(f->m, FILE(f->qid.path), &p);
-       if(off < i){
-               if((off + cnt) > i)
-                       cnt = i - off;
-               memmove(mbuf, p + off, cnt);
-               rhdr.count = cnt;
+char*
+modflags(Mailbox *mb, Message *m, char *p)
+{
+       char *err;
+       uchar f;
+
+       f = m->flags;
+       if(err = txflags(p, &f))
+               return err;
+       if(f != m->flags){
+               if(mb->modflags != nil)
+                       mb->modflags(mb, m, f);
+               m->flags = f;
+               m->cstate |= Cidxstale;
        }
        return nil;
 }
@@ -982,97 +1294,149 @@ rread(Fid *f)
 char*
 rwrite(Fid *f)
 {
-       char *err;
-       char *token[1024];
-       int t, n;
-       String *file;
+       char *argvbuf[1024], **argv, file[Pathlen], *err, *v0;
+       int i, t, argc, flags;
+       Message *m;
 
        t = FILE(f->qid.path);
        rhdr.count = thdr.count;
+       if(thdr.count == 0)
+               return Ebadctl;
+       if(thdr.data[thdr.count - 1] == '\n')
+               thdr.data[thdr.count - 1] = 0;
+       else
+               thdr.data[thdr.count] = 0;
+       argv = argvbuf;
        switch(t){
        case Qctl:
-               if(thdr.count == 0)
-                       return Ebadctl;
-               if(thdr.data[thdr.count-1] == '\n')
-                       thdr.data[thdr.count-1] = 0;
-               else
-                       thdr.data[thdr.count] = 0;
-               n = tokenize(thdr.data, token, nelem(token));
-               if(n == 0)
+               memset(argvbuf, 0, sizeof argvbuf);
+               argc = tokenize(thdr.data, argv, nelem(argvbuf) - 1);
+               if(argc == 0)
                        return Ebadctl;
-               if(strcmp(token[0], "open") == 0){
-                       file = s_new();
-                       switch(n){
-                       case 1:
-                               err = Ebadctl;
-                               break;
-                       case 2:
-                               mboxpath(token[1], getlog(), file, 0);
-                               err = newmbox(s_to_c(file), nil, 0);
-                               break;
-                       default:
-                               mboxpath(token[1], getlog(), file, 0);
-                               if(strchr(token[2], '/') != nil)
-                                       err = "/ not allowed in mailbox name";
-                               else
-                                       err = newmbox(s_to_c(file), token[2], 0);
-                               break;
-                       }
-                       s_free(file);
-                       return err;
+               if(strcmp(argv[0], "open") == 0 || strcmp(argv[0], "create") == 0){
+                       if(argc == 1 || argc > 3)
+                               return Ebadargs;
+                       mboxpathbuf(file, sizeof file, getlog(), argv[1]);
+                       if(argc == 3){
+                               if(strchr(argv[2], '/') != nil)
+                                       return "/ not allowed in mailbox name";
+                       }else
+                               argv[2] = nil;
+                       flags = 0;
+                       if(strcmp(argv[0], "create") == 0)
+                               flags |= DMcreate;
+                       return newmbox(file, argv[2], flags, nil);
                }
-               if(strcmp(token[0], "close") == 0){
-                       if(n < 2)
+               if(strcmp(argv[0], "close") == 0){
+                       if(argc < 2)
                                return nil;
-                       freembox(token[1]);
+                       for(i = 1; i < argc; i++)
+                               freembox(argv[i]);
                        return nil;
                }
-               if(strcmp(token[0], "delete") == 0){
-                       if(n < 3)
+               if(strcmp(argv[0], "delete") == 0){
+                       if(argc < 3)
                                return nil;
-                       delmessages(n-1, &token[1]);
+                       delmessages(argc - 1, argv + 1);
                        return nil;
                }
+               if(strcmp(argv[0], "flag") == 0){
+                       if(argc < 3)
+                               return nil;
+                       return flagmessages(argc - 1, argv + 1);
+               }
+               if(strcmp(argv[0], "remove") == 0){
+                       v0 = argv0;
+                       flags = 0;
+                       ARGBEGIN{
+                       default:
+                               argv0 = v0;
+                               return Ebadargs;
+                       case 'r':
+                               flags |= Rrecur;
+                               break;
+                       case 't':
+                               flags |= Rtrunc;
+                               break;
+                       }ARGEND
+                       argv0 = v0;
+                       if(argc == 0)
+                               return Ebadargs;
+                       for(; *argv; argv++){
+                               mboxpathbuf(file, sizeof file, getlog(), *argv);
+                               if(err = newmbox(file, nil, 0, nil))
+                                       return err;
+                               if(err = removembox(file, flags))
+                                       return err;
+                       }
+                       return 0;
+               }
+               if(strcmp(argv[0], "rename") == 0){
+                       v0 = argv0;
+                       flags = 0;
+                       ARGBEGIN{
+                       case 't':
+                               flags |= Rtrunc;
+                               break;
+                       }ARGEND
+                       argv0 = v0;
+                       if(argc != 2)
+                               return Ebadargs;
+                       return mboxrename(argv[0], argv[1], flags);
+               }
                return Ebadctl;
        case Qmboxctl:
                if(f->mb && f->mb->ctl){
-                       if(thdr.count == 0)
-                               return Ebadctl;
-                       if(thdr.data[thdr.count-1] == '\n')
-                               thdr.data[thdr.count-1] = 0;
-                       else
-                               thdr.data[thdr.count] = 0;
-                       n = tokenize(thdr.data, token, nelem(token));
-                       if(n == 0)
+                       argc = tokenize(thdr.data, argv, nelem(argvbuf));
+                       if(argc == 0)
                                return Ebadctl;
-                       return (*f->mb->ctl)(f->mb, n, token);
+                       qlock(f->mb);
+                       err = f->mb->ctl(f->mb, argc, argv);
+                       qunlock(f->mb);
+                       return err;
                }
+               break;
+       case Qflags:
+               /*
+                * modifying flags on subparts is a little strange.
+                */
+               if(!f->mb || !f->m)
+                       break;
+               qlock(f->mb);
+               m = gettopmsg(f->mb, f->m);
+               err = modflags(f->mb, m, thdr.data);
+               qunlock(f->mb);
+               return err;
        }
        return Eperm;
 }
 
-char *
+char*
 rclunk(Fid *f)
 {
        Mailbox *mb;
 
-       f->busy = 0;
+       f->busy = 1;
+       /* coherence(); */
+       f->fid = -1;
        f->open = 0;
-       if(f->mtop != nil){
-               qlock(f->mb);
-               msgdecref(f->mb, f->mtop);
-               qunlock(f->mb);
+       mb = f->mb;
+       if(mb){
+               qlock(mb);
        }
+       if(f->mtop)
+               msgdecref(mb, f->mtop);
+       if(f->m)
+               msgdecref(mb, gettopmsg(mb, f->m));
        f->m = f->mtop = nil;
-       mb = f->mb;
-       if(mb != nil){
+       if(mb){
                f->mb = nil;
-               assert(mb->refs > 0);
+               qunlock(mb);
                qlock(&mbllock);
                mboxdecref(mb);
                qunlock(&mbllock);
        }
-       f->fid = -1;
+       f->busy = 0;
        return 0;
 }
 
@@ -1080,9 +1444,11 @@ char *
 rremove(Fid *f)
 {
        if(f->m != nil){
-               if(f->m->deleted == 0)
+               qlock(f->mb);
+               if(!f->m->deleted)
                        mailplumb(f->mb, f->m, 1);
-               f->m->deleted = 1;
+               f->m->deleted = Deleted;
+               qunlock(f->mb);
        }
        return rclunk(f);
 }
@@ -1092,24 +1458,25 @@ rstat(Fid *f)
 {
        Dir d;
 
-       if(FILE(f->qid.path) == Qmbox){
+       if(f->mb)
                qlock(f->mb);
+       if(FILE(f->qid.path) == Qmbox)
                syncmbox(f->mb, 1);
-               qunlock(f->mb);
-       }
        mkstat(&d, f->mb, f->m, FILE(f->qid.path));
        rhdr.nstat = convD2M(&d, mbuf, messagesize - IOHDRSZ);
        rhdr.stat = mbuf;
+       if(f->mb)
+               qunlock(f->mb);
        return 0;
 }
 
-char *
+char*
 rwstat(Fid*)
 {
        return Eperm;
 }
 
-Fid *
+Fid*
 newfid(int fid)
 {
        Fid *f, *ff;
@@ -1133,19 +1500,6 @@ newfid(int fid)
        return f;
 }
 
-int
-fidmboxrefs(Mailbox *mb)
-{
-       Fid *f;
-       int refs = 0;
-
-       for(f = fids; f; f = f->next){
-               if(f->mb == mb)
-                       refs++;
-       }
-       return refs;
-}
-
 void
 io(void)
 {
@@ -1153,45 +1507,33 @@ io(void)
        int n;
 
        /* start a process to watch the mailboxes*/
-       if(plumbing){
+       if(plumbing || biffing)
                switch(rfork(RFPROC|RFMEM)){
                case -1:
                        /* oh well */
                        break;
                case 0:
                        reader();
-                       exits(nil);
+                       exits("");
                default:
                        break;
                }
-       }
 
        for(;;){
-               /*
-                * reading from a pipe or a network device
-                * will give an error after a few eof reads
-                * however, we cannot tell the difference
-                * between a zero-length read and an interrupt
-                * on the processes writing to us,
-                * so we wait for the error
-                */
-               checkmboxrefs();
                n = read9pmsg(mfd[0], mdata, messagesize);
-               if(n == 0)
-                       continue;
-               if(n < 0)
+               if(n <= 0)
                        return;
                if(convM2S(mdata, n, &thdr) == 0)
                        continue;
 
-               if(debug)
+               if(Dflag)
                        fprint(2, "%s:<-%F\n", argv0, &thdr);
 
                rhdr.data = (char*)mdata + messagesize;
                if(!fcalls[thdr.type])
                        err = "bad fcall type";
                else
-                       err = (*fcalls[thdr.type])(newfid(thdr.fid));
+                       err = fcalls[thdr.type](newfid(thdr.fid));
                if(err){
                        rhdr.type = Rerror;
                        rhdr.ename = err;
@@ -1200,14 +1542,16 @@ io(void)
                        rhdr.fid = thdr.fid;
                }
                rhdr.tag = thdr.tag;
-               if(debug)
-                       fprint(2, "%s:->%F\n", argv0, &rhdr);/**/
+               if(Dflag)
+                       fprint(2, "%s:->%F\n", argv0, &rhdr);
                n = convS2M(&rhdr, mdata, messagesize);
                if(write(mfd[1], mdata, n) != n)
                        error("mount write");
        }
 }
 
+static char *readerargv[] = {"upas/fs", "plumbing", 0};
+
 void
 reader(void)
 {
@@ -1215,31 +1559,30 @@ reader(void)
        Dir *d;
        Mailbox *mb;
 
+       setname(readerargv);
        sleep(15*1000);
        for(;;){
                t = time(0);
                qlock(&mbllock);
                for(mb = mbl; mb != nil; mb = mb->next){
-                       assert(mb->refs > 0);
-                       if(mb->waketime != 0 && t > mb->waketime){
-                               qlock(mb);
+                       if(!canqlock(mb))
+                               continue;
+                       if(mb->waketime != 0 && t >= mb->waketime){
                                mb->waketime = 0;
                                break;
                        }
-
-                       d = dirstat(mb->path);
-                       if(d == nil)
-                               continue;
-
-                       qlock(mb);
-                       if(mb->d)
-                       if(d->qid.path != mb->d->qid.path
-                          || d->qid.vers != mb->d->qid.vers){
-                               free(d);
-                               break;
+                       if(mb->d != nil && mb->d->name != nil){
+                               d = dirstat(mb->path);
+                               if(d != nil){
+                                       if(d->qid.path != mb->d->qid.path
+                                       || d->qid.vers != mb->d->qid.vers){
+                                               free(d);
+                                               break;
+                                       }
+                                       free(d);
+                               }
                        }
                        qunlock(mb);
-                       free(d);
                }
                qunlock(&mbllock);
                if(mb != nil){
@@ -1267,8 +1610,8 @@ newid(void)
 void
 error(char *s)
 {
-       postnote(PNGROUP, getpid(), "die yankee pig dog");
-       fprint(2, "%s: %s: %r\n", argv0, s);
+       syskillpg(getpid());
+       eprint("upas/fs: fatal error: %s: %r\n", s);
        exits(s);
 }
 
@@ -1277,13 +1620,13 @@ typedef struct Ignorance Ignorance;
 struct Ignorance
 {
        Ignorance *next;
-       char    *str;           /* string */
-       int     partial;        /* true if not exact match */
+       char    *str;
+       int     len;
 };
 Ignorance *ignorance;
 
 /*
- *  read the file of headers to ignore 
+ *  read the file of headers to ignore
  */
 void
 readignore(void)
@@ -1299,15 +1642,13 @@ readignore(void)
        if(b == 0)
                return;
        while(p = Brdline(b, '\n')){
-               p[Blinelen(b)-1] = 0;
+               p[Blinelen(b) - 1] = 0;
                while(*p && (*p == ' ' || *p == '\t'))
                        p++;
                if(*p == '#')
                        continue;
-               i = malloc(sizeof(Ignorance));
-               if(i == 0)
-                       break;
-               i->partial = strlen(p);
+               i = emalloc(sizeof *i);
+               i->len = strlen(p);
                i->str = strdup(p);
                if(i->str == 0){
                        free(i);
@@ -1326,159 +1667,37 @@ ignore(char *p)
 
        readignore();
        for(i = ignorance; i != nil; i = i->next)
-               if(cistrncmp(i->str, p, i->partial) == 0)
+               if(cistrncmp(i->str, p, i->len) == 0)
                        return 1;
        return 0;
 }
 
-int
-hdrlen(char *p, char *e)
-{
-       char *ep;
-
-       ep = p;
-       do {
-               ep = strchr(ep, '\n');
-               if(ep == nil){
-                       ep = e;
-                       break;
-               }
-               ep++;
-               if(ep >= e){
-                       ep = e;
-                       break;
-               }
-       } while(*ep == ' ' || *ep == '\t');
-       return ep - p;
-}
-
-// rfc2047 non-ascii: =?charset?q?encoded-text?=
-int
-rfc2047convert(String *s, char *token, int len)
-{
-       char charset[100], decoded[1024], *e, *x;
-       int l;
-
-       if(len == 0)
-               return -1;
-
-       e = token+len-2;
-       token += 2;
-
-       x = memchr(token, '?', e-token);
-       if(x == nil || (l=x-token) >= sizeof charset)
-               return -1;
-       memmove(charset, token, l);
-       charset[l] = 0;
-
-       token = x+1;
-
-       // bail if it doesn't fit 
-       if(e-token > sizeof(decoded)-1)
-               return -1;
-
-       // bail if we don't understand the encoding
-       if(cistrncmp(token, "b?", 2) == 0){
-               token += 2;
-               len = dec64((uchar*)decoded, sizeof(decoded), token, e-token);
-               decoded[len] = 0;
-       } else if(cistrncmp(token, "q?", 2) == 0){
-               token += 2;
-               len = decquoted(decoded, token, e, 1);
-               if(len > 0 && decoded[len-1] == '\n')
-                       len--;
-               decoded[len] = 0;
-       } else
-               return -1;
-
-       if(xtoutf(charset, &x, decoded, decoded+len) <= 0)
-               s_append(s, decoded);
-       else {
-               s_append(s, x);
-               free(x);
-       }
-       return 0;
-}
-
-char*
-rfc2047start(char *start, char *end)
-{
-       int quests;
-
-       if(*--end != '=')
-               return nil;
-       if(*--end != '?')
-               return nil;
-
-       quests = 0;
-       for(end--; end >= start; end--){
-               switch(*end){
-               case '=':
-                       if(quests == 3 && *(end+1) == '?')
-                               return end;
-                       break;
-               case '?':
-                       ++quests;
-                       break;
-               case ' ':
-               case '\t':
-               case '\n':
-               case '\r':
-                       /* can't have white space in a token */
-                       return nil;
-               }
-       }
-       return nil;
-}
-
-// convert a header line
-String*
-stringconvert(String *s, char *uneaten, int len)
-{
-       char *token, *p, *e;
-
-       s = s_reset(s);
-       p = uneaten;
-       for(e = p+len; p < e; ){
-               while(*p++ == '=' && (token = rfc2047start(uneaten, p))){
-                       s_nappend(s, uneaten, token-uneaten);
-                       if(rfc2047convert(s, token, p - token) < 0)
-                               s_nappend(s, token, p - token);
-                       uneaten = p;
-                       for(; p<e && isspace(*p);)
-                               p++;
-                       if(p+2 < e && p[0] == '=' && p[1] == '?')
-                               uneaten = p;    // paste
-               }
-       }
-       if(p > uneaten)
-               s_nappend(s, uneaten, p-uneaten);
-       return s;
-}
-
 int
 readheader(Message *m, char *buf, int off, int cnt)
 {
-       char *p, *e;
-       int n, ns;
-       char *to = buf;
-       String *s;
+       char *s, *end, *se, *p, *e, *to;
+       int n, ns, salloc;
 
+       to = buf;
        p = m->header;
        e = m->hend;
-       s = nil;
+       s = emalloc(salloc = 2048);
+       end = s + salloc;
 
-       // copy in good headers
+       /* copy in good headers */
        while(cnt > 0 && p < e){
                n = hdrlen(p, e);
+               assert(n > 0);
                if(ignore(p)){
                        p += n;
                        continue;
                }
-
-               // rfc2047 processing
-               s = stringconvert(s, p, n);
-               ns = s_len(s);
+               if(n + 1 > salloc){
+                       s = erealloc(s, salloc = n + 1);
+                       end = s + salloc;
+               }
+               se = rfc2047(s, end, p, n, 0);
+               ns = se - s;
                if(off > 0){
                        if(ns <= off){
                                off -= ns;
@@ -1489,34 +1708,16 @@ readheader(Message *m, char *buf, int off, int cnt)
                }
                if(ns > cnt)
                        ns = cnt;
-               memmove(to, s_to_c(s)+off, ns);
+               memmove(to, s + off, ns);
                to += ns;
                p += n;
                cnt -= ns;
                off = 0;
        }
-
-       s_free(s);
+       free(s);
        return to - buf;
 }
 
-int
-headerlen(Message *m)
-{
-       char buf[1024];
-       int i, n;
-
-       if(m->hlen >= 0)
-               return m->hlen;
-       for(n = 0; ; n += i){
-               i = readheader(m, buf, n, sizeof(buf));
-               if(i <= 0)
-                       break;
-       }
-       m->hlen = n;
-       return n;
-}
-
 QLock hashlock;
 
 uint
@@ -1556,6 +1757,7 @@ henter(ulong ppath, char *name, Qid qid, Message *m, Mailbox *mb)
        int h;
        Hash *hp, **l;
 
+//if(m)sanemsg(m);
        qlock(&hashlock);
        h = hash(ppath, name);
        for(l = &htab[h]; *l != nil; l = &(*l)->next){
@@ -1606,44 +1808,24 @@ hashmboxrefs(Mailbox *mb)
        int refs = 0;
 
        qlock(&hashlock);
-       for(h = 0; h < Hsize; h++){
+       for(h = 0; h < Hsize; h++)
                for(hp = htab[h]; hp != nil; hp = hp->next)
                        if(hp->mb == mb)
                                refs++;
-       }
        qunlock(&hashlock);
        return refs;
 }
 
-void
-checkmboxrefs(void)
-{
-       int f, refs;
-       Mailbox *mb;
-
-       qlock(&mbllock);
-       for(mb=mbl; mb; mb=mb->next){
-               qlock(mb);
-               refs = (f=fidmboxrefs(mb))+1;
-               if(refs != mb->refs){
-                       fprint(2, "mbox %s %s ref mismatch actual %d (%d+1) expected %d\n", mb->name, mb->path, refs, f, mb->refs);
-                       abort();
-               }
-               qunlock(mb);
-       }
-       qunlock(&mbllock);
-}
-
 void
 post(char *name, char *envname, int srvfd)
 {
-       int fd;
        char buf[32];
+       int fd;
 
        fd = create(name, OWRITE, 0600);
        if(fd < 0)
                error("post failed");
-       sprint(buf, "%d",srvfd);
+       snprint(buf, sizeof buf, "%d", srvfd);
        if(write(fd, buf, strlen(buf)) != strlen(buf))
                error("srv write");
        close(fd);