Subversion Repositories planix.SVN

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

#include "common.h"
#include <auth.h>
#include <fcall.h>
#include <libsec.h>
#include <ctype.h>
#include "dat.h"

enum
{
        OPERM   = 0x3,          // mask of all permission types in open mode
};

typedef struct Fid Fid;

struct Fid
{
        Qid     qid;
        short   busy;
        short   open;
        int     fid;
        Fid     *next;
        Mailbox *mb;
        Message *m;
        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
};

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);
void            *erealloc(void*, ulong);
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*),
        *rattach(Fid*), *rwalk(Fid*),
        *ropen(Fid*), *rcreate(Fid*),
        *rread(Fid*), *rwrite(Fid*), *rclunk(Fid*),
        *rremove(Fid*), *rstat(Fid*), *rwstat(Fid*),
        *rversion(Fid*);

char    *(*fcalls[])(Fid*) = {
        [Tflush]        rflush,
        [Tversion]      rversion,
        [Tauth] rauth,
        [Tattach]       rattach,
        [Twalk]         rwalk,
        [Topen]         ropen,
        [Tcreate]       rcreate,
        [Tread]         rread,
        [Twrite]        rwrite,
        [Tclunk]        rclunk,
        [Tremove]       rremove,
        [Tstat]         rstat,
        [Twstat]        rwstat,
};

char    Eperm[] =       "permission denied";
char    Enotdir[] =     "not a directory";
char    Enoauth[] =     "upas/fs: authentication not required";
char    Enotexist[] =   "file does not exist";
char    Einuse[] =      "file in use";
char    Eexist[] =      "file exists";
char    Enotowner[] =   "not owner";
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 *dirtab[] =
{
[Qdir]          ".",
[Qbody]         "body",
[Qbcc]          "bcc",
[Qcc]           "cc",
[Qdate]         "date",
[Qdigest]       "digest",
[Qdisposition]  "disposition",
[Qfilename]     "filename",
[Qfrom]         "from",
[Qheader]       "header",
[Qinfo]         "info",
[Qinreplyto]    "inreplyto",
[Qlines]        "lines",
[Qmimeheader]   "mimeheader",
[Qmessageid]    "messageid",
[Qraw]          "raw",
[Qrawunix]      "rawunix",
[Qrawbody]      "rawbody",
[Qrawheader]    "rawheader",
[Qreplyto]      "replyto",
[Qsender]       "sender",
[Qsubject]      "subject",
[Qto]           "to",
[Qtype]         "type",
[Qunixdate]     "unixdate",
[Qunixheader]   "unixheader",
[Qctl]          "ctl",
[Qmboxctl]      "ctl",
};

enum
{
        Hsize=  1277,
};

Hash    *htab[Hsize];

int     debug;
int     fflag;
int     logging;

void
usage(void)
{
        fprint(2, "usage: upas/fs [-bdlnps] [-f mboxfile] [-m mountpoint]\n");
        exits("usage");
}

void
notifyf(void *a, char *s)
{
        USED(a);
        if(strncmp(s, "interrupt", 9) == 0)
                noted(NCONT);
        noted(NDFLT);
}

void
main(int argc, char *argv[])
{
        int p[2], std, nodflt;
        char maildir[128];
        char mbox[128];
        char *mboxfile, *err;
        char srvfile[64];
        int srvpost;

        rfork(RFNOTEG);
        mntpt = nil;
        fflag = 0;
        mboxfile = nil;
        std = 0;
        nodflt = 0;
        srvpost = 0;

        ARGBEGIN{
        case 'b':
                biffing = 1;
                break;
        case 'f':
                fflag = 1;
                mboxfile = EARGF(usage());
                break;
        case 'm':
                mntpt = EARGF(usage());
                break;
        case 'd':
                debug = 1;
                break;
        case 'p':
                plumbing = 0;
                break;
        case 's':
                srvpost = 1;
                break;
        case 'l':
                logging = 1;
                break;
        case 'n':
                nodflt = 1;
                break;
        default:
                usage();
        }ARGEND

        if(argc)
                usage();
        if(pipe(p) < 0)
                error("pipe failed");
        mfd[0] = p[0];
        mfd[1] = p[0];

        notify(notifyf);
        strcpy(user, getuser());
        if(mntpt == nil){
                snprint(maildir, sizeof(maildir), "/mail/fs");
                mntpt = maildir;
        }
        if(mboxfile == nil && !nodflt){
                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)
                        sysfatal("opening %s: %s", mboxfile, err);
        }

        switch(rfork(RFFDG|RFPROC|RFNAMEG|RFNOTEG|RFREND)){
        case -1:
                error("fork");
        case 0:
                henter(PATH(0, Qtop), dirtab[Qctl],
                        (Qid){PATH(0, Qctl), 0, QTFILE}, nil, nil);
                close(p[1]);
                io();
                postnote(PNGROUP, getpid(), "die yankee pig dog");
                break;
        default:
                close(p[0]);    /* don't deadlock if child fails */
                if(srvpost){
                        sprint(srvfile, "/srv/upasfs.%s", user);
                        post(srvfile, "upasfs", p[1]);
                } else {
                        if(mount(p[1], -1, mntpt, MREPL, "") < 0)
                                error("mount failed");
                }
        }
        exits(0);
}

static int
fileinfo(Message *m, int t, char **pp)
{
        char *p;
        int len;

        p = "";
        len = 0;
        switch(t){
        case Qbody:
                p = m->body;
                len = m->bend - m->body;
                break;
        case Qbcc:
                if(m->bcc822){
                        p = s_to_c(m->bcc822);
                        len = strlen(p);
                }
                break;
        case Qcc:
                if(m->cc822){
                        p = s_to_c(m->cc822);
                        len = strlen(p);
                }
                break;
        case Qdisposition:
                switch(m->disposition){
                case Dinline:
                        p = "inline";
                        break;
                case Dfile:
                        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);
                }
                break;
        case Qfilename:
                if(m->filename){
                        p = s_to_c(m->filename);
                        len = strlen(p);
                }
                break;
        case Qinreplyto:
                if(m->inreplyto822){
                        p = s_to_c(m->inreplyto822);
                        len = strlen(p);
                }
                break;
        case Qmessageid:
                if(m->messageid822){
                        p = s_to_c(m->messageid822);
                        len = strlen(p);
                }
                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);
                }
                break;
        case Qheader:
                p = m->header;
                len = headerlen(m);
                break;
        case Qlines:
                p = m->lines;
                if(*p == 0)
                        countlines(m);
                len = strlen(m->lines);
                break;
        case Qraw:
                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;
                break;
        case Qrawunix:
                p = m->start;
                len = m->end - p;
                break;
        case Qrawbody:
                p = m->rbody;
                len = m->rbend - p;
                break;
        case Qrawheader:
                p = m->header;
                len = m->hend - p;
                break;
        case Qmimeheader:
                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);
                }
                break;
        case Qsender:
                if(m->sender822){
                        p = s_to_c(m->sender822);
                        len = strlen(p);
                }
                break;
        case Qsubject:
                p = nil;
                if(m->subject822){
                        p = s_to_c(m->subject822);
                        len = strlen(p);
                }
                break;
        case Qto:
                if(m->to822){
                        p = s_to_c(m->to822);
                        len = strlen(p);
                }
                break;
        case Qtype:
                if(m->type){
                        p = s_to_c(m->type);
                        len = strlen(p);
                }
                break;
        case Qunixdate:
                if(m->unixdate){
                        p = s_to_c(m->unixdate);
                        len = strlen(p);
                }
                break;
        case Qunixheader:
                if(m->unixheader){
                        p = s_to_c(m->unixheader);
                        len = s_len(m->unixheader);
                }
                break;
        case Qdigest:
                if(m->sdigest){
                        p = s_to_c(m->sdigest);
                        len = strlen(p);
                }
                break;
        }
        *pp = p;
        return len;
}

int infofields[] = {
        Qfrom,
        Qto,
        Qcc,
        Qreplyto,
        Qunixdate,
        Qsubject,
        Qtype,
        Qdisposition,
        Qfilename,
        Qdigest,
        Qbcc,
        Qinreplyto,
        Qdate,
        Qsender,
        Qmessageid,
        Qlines,
        -1,
};

static int
readinfo(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;
                        n -= off;
                        off = 0;
                }
                if(n > count - len)
                        n = count - len;
                if(buf)
                        memmove(buf+len, p, n);
                len += n;
        }
        s_free(s);
        return len;
}

static void
mkstat(Dir *d, Mailbox *mb, Message *m, int t)
{
        char *p;

        d->uid = user;
        d->gid = user;
        d->muid = user;
        d->mode = 0444;
        d->qid.vers = 0;
        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 {
                d->atime = time(0);
                d->mtime = d->atime;
        }

        switch(t){
        case Qtop:
                d->name = ".";
                d->mode = DMDIR|0555;
                d->atime = d->mtime = time(0);
                d->length = 0;
                d->qid.path = PATH(0, Qtop);
                d->qid.type = QTDIR;
                break;
        case Qmbox:
                d->name = mb->name;
                d->mode = DMDIR|0555;
                d->length = 0;
                d->qid.path = PATH(mb->id, Qmbox);
                d->qid.type = QTDIR;
                d->qid.vers = mb->vers;
                break;
        case Qdir:
                d->name = m->name;
                d->mode = DMDIR|0555;
                d->length = 0;
                d->qid.path = PATH(m->id, Qdir);
                d->qid.type = QTDIR;
                break;
        case Qctl:
                d->name = dirtab[t];
                d->mode = 0666;
                d->atime = d->mtime = time(0);
                d->length = 0;
                d->qid.path = PATH(0, Qctl);
                break;
        case Qmboxctl:
                d->name = dirtab[t];
                d->mode = 0222;
                d->atime = d->mtime = time(0);
                d->length = 0;
                d->qid.path = PATH(mb->id, Qmboxctl);
                break;
        case Qinfo:
                d->name = dirtab[t];
                d->length = readinfo(m, nil, 0, 1<<30);
                d->qid.path = PATH(m->id, t);
                break;
        default:
                d->name = dirtab[t];
                d->length = fileinfo(m, t, &p);
                d->qid.path = PATH(m->id, t);
                break;
        }
}

char*
rversion(Fid*)
{
        Fid *f;

        if(thdr.msize < 256)
                return "max messagesize too small";
        if(thdr.msize < messagesize)
                messagesize = thdr.msize;
        rhdr.msize = messagesize;
        if(strncmp(thdr.version, "9P2000", 6) != 0)
                return "unknown 9P version";
        else
                rhdr.version = "9P2000";
        for(f = fids; f; f = f->next)
                if(f->busy)
                        rclunk(f);
        return nil;
}

char*
rauth(Fid*)
{
        return Enoauth;
}

char*
rflush(Fid *f)
{
        USED(f);
        return 0;
}

char*
rattach(Fid *f)
{
        f->busy = 1;
        f->m = nil;
        f->mb = nil;
        f->qid.path = PATH(0, Qtop);
        f->qid.type = QTDIR;
        f->qid.vers = 0;
        rhdr.qid = f->qid;
        if(strcmp(thdr.uname, user) != 0)
                return Eperm;
        return 0;
}

static Fid*
doclone(Fid *f, int nfid)
{
        Fid *nf;

        nf = newfid(nfid);
        if(nf->busy)
                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);
        }
        nf->qid = f->qid;
        return nf;
}

char*
dowalk(Fid *f, char *name)
{
        int t;
        Mailbox *omb, *mb;
        char *rv, *p;
        Hash *h;

        t = FILE(f->qid.path);

        rv = Enotexist;

        omb = f->mb;
        if(omb)
                qlock(omb);
        else
                qlock(&mbllock);

        // this must catch everything except . and ..
retry:
        h = hlook(f->qid.path, name);
        if(h != nil){
                f->mb = h->mb;
                f->m = h->m;
                switch(t){
                case Qtop:
                        if(f->mb != nil)
                                mboxincref(f->mb);
                        break;
                case Qmbox:
                        if(f->m){
                                msgincref(f->m);
                                f->mtop = f->m;
                        }
                        break;
                }
                f->qid = h->qid;
                rv = nil;
        } else if((p = strchr(name, '.')) != nil && *name != '.'){
                *p = 0;
                goto retry;
        }

        if(omb)
                qunlock(omb);
        else
                qunlock(&mbllock);
        if(rv == nil)
                return rv;

        if(strcmp(name, ".") == 0)
                return nil;

        if(f->qid.type != QTDIR)
                return Enotdir;

        if(strcmp(name, "..") == 0){
                switch(t){
                case Qtop:
                        f->qid.path = PATH(0, Qtop);
                        f->qid.type = QTDIR;
                        f->qid.vers = 0;
                        break;
                case Qmbox:
                        f->qid.path = PATH(0, Qtop);
                        f->qid.type = QTDIR;
                        f->qid.vers = 0;
                        qlock(&mbllock);
                        mb = f->mb;
                        f->mb = nil;
                        mboxdecref(mb);
                        qunlock(&mbllock);
                        break;
                case Qdir:
                        qlock(f->mb);
                        if(f->m->whole == f->mb->root){
                                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);
                                f->m = f->mtop = nil;
                        } else {
                                f->m = f->m->whole;
                                f->qid.path = PATH(f->m->id, Qdir);
                                f->qid.type = QTDIR;
                        }
                        qunlock(f->mb);
                        break;
                }
                rv = nil;
        }
        return rv;
}

char*
rwalk(Fid *f)
{
        Fid *nf;
        char *rv;
        int i;

        if(f->open)
                return Eisopen;

        rhdr.nwqid = 0;
        nf = nil;

        /* clone if requested */
        if(thdr.newfid != thdr.fid){
                nf = doclone(f, thdr.newfid);
                if(nf == nil)
                        return "new fid in use";
                f = nf;
        }

        /* if it's just a clone, return */
        if(thdr.nwname == 0 && nf != nil)
                return nil;

        /* walk each element */
        rv = nil;
        for(i = 0; i < thdr.nwname; i++){
                rv = dowalk(f, thdr.wname[i]);
                if(rv != nil){
                        if(nf != nil)   
                                rclunk(nf);
                        break;
                }
                rhdr.wqid[i] = f->qid;
        }
        rhdr.nwqid = i;

        /* we only error out if no walk  */
        if(i > 0)
                rv = nil;

        return rv;
}

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)
                        return Eperm;

        // make sure we've decoded
        if(file == Qbody){
                if(f->m->decoded == 0)
                        decode(f->m);
                if(f->m->converted == 0)
                        convert(f->m);
        }

        rhdr.iounit = 0;
        rhdr.qid = f->qid;
        f->open = 1;
        return 0;
}

char *
rcreate(Fid*)
{
        return Eperm;
}

int
readtopdir(Fid*, uchar *buf, long off, int cnt, int blen)
{
        Dir d;
        int m, n;
        long pos;
        Mailbox *mb;

        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;
                n += m;
                cnt -= m;
        }
        pos += m;
                
        for(mb = mbl; mb != nil; mb = mb->next){
                mkstat(&d, mb, nil, Qmbox);
                m = convD2M(&d, &buf[n], blen-n);
                if(off <= pos){
                        if(m <= BIT16SZ || m > cnt)
                                break;
                        n += m;
                        cnt -= m;
                }
                pos += m;
        }
        return n;
}

int
readmboxdir(Fid *f, uchar *buf, long off, int cnt, int blen)
{
        Dir d;
        int n, m;
        long pos;
        Message *msg;

        n = 0;
        if(f->mb->ctl){
                mkstat(&d, f->mb, nil, Qmboxctl);
                m = convD2M(&d, &buf[n], blen);
                if(off == 0){
                        if(m <= BIT16SZ || m > cnt){
                                f->fptr = nil;
                                return 0;
                        }
                        n += m;
                        cnt -= m;
                } else
                        off -= m;
        }

        // 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
                if(msg->deleted)
                        continue;

                mkstat(&d, f->mb, msg, Qdir);
                m = convD2M(&d, &buf[n], blen-n);
                if(off <= pos){
                        if(m <= BIT16SZ || m > cnt)
                                break;
                        n += m;
                        cnt -= m;
                }
                pos += m;
        }

        // save a finger pointer for next read of the mbox directory
        f->foff = pos;
        f->fptr = msg;
        f->fvers = f->mb->vers;

        return n;
}

int
readmsgdir(Fid *f, uchar *buf, long off, int cnt, int blen)
{
        Dir d;
        int i, n, m;
        long pos;
        Message *msg;

        n = 0;
        pos = 0;
        for(i = 0; i < Qmax; i++){
                mkstat(&d, f->mb, f->m, i);
                m = convD2M(&d, &buf[n], blen-n);
                if(off <= pos){
                        if(m <= BIT16SZ || m > cnt)
                                return n;
                        n += m;
                        cnt -= m;
                }
                pos += m;
        }
        for(msg = f->m->part; msg != nil; msg = msg->next){
                mkstat(&d, f->mb, msg, Qdir);
                m = convD2M(&d, &buf[n], blen-n);
                if(off <= pos){
                        if(m <= BIT16SZ || m > cnt)
                                break;
                        n += m;
                        cnt -= m;
                }
                pos += m;
        }

        return n;
}

char*
rread(Fid *f)
{
        long off;
        int t, i, n, cnt;
        char *p;

        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);
                        n = readtopdir(f, mbuf, off, cnt, messagesize - IOHDRSZ);
                        qunlock(&mbllock);
                } else if(t == Qmbox) {
                        qlock(f->mb);
                        if(off == 0)
                                syncmbox(f->mb, 1);
                        n = readmboxdir(f, mbuf, off, cnt, messagesize - IOHDRSZ);
                        qunlock(f->mb);
                } else if(t == Qmboxctl) {
                        n = 0;
                } else {
                        n = readmsgdir(f, mbuf, off, cnt, messagesize - IOHDRSZ);
                }

                rhdr.count = n;
                return nil;
        }

        if(FILE(f->qid.path) == Qheader){
                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;
        }

        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;
        }
        return nil;
}

char*
rwrite(Fid *f)
{
        char *err;
        char *token[1024];
        int t, n;
        String *file;

        t = FILE(f->qid.path);
        rhdr.count = thdr.count;
        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)
                        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(token[0], "close") == 0){
                        if(n < 2)
                                return nil;
                        freembox(token[1]);
                        return nil;
                }
                if(strcmp(token[0], "delete") == 0){
                        if(n < 3)
                                return nil;
                        delmessages(n-1, &token[1]);
                        return nil;
                }
                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)
                                return Ebadctl;
                        return (*f->mb->ctl)(f->mb, n, token);
                }
        }
        return Eperm;
}

char *
rclunk(Fid *f)
{
        Mailbox *mb;

        f->busy = 0;
        f->open = 0;
        if(f->mtop != nil){
                qlock(f->mb);
                msgdecref(f->mb, f->mtop);
                qunlock(f->mb);
        }
        f->m = f->mtop = nil;
        mb = f->mb;
        if(mb != nil){
                f->mb = nil;
                assert(mb->refs > 0);
                qlock(&mbllock);
                mboxdecref(mb);
                qunlock(&mbllock);
        }
        f->fid = -1;
        return 0;
}

char *
rremove(Fid *f)
{
        if(f->m != nil){
                if(f->m->deleted == 0)
                        mailplumb(f->mb, f->m, 1);
                f->m->deleted = 1;
        }
        return rclunk(f);
}

char *
rstat(Fid *f)
{
        Dir d;

        if(FILE(f->qid.path) == Qmbox){
                qlock(f->mb);
                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;
        return 0;
}

char *
rwstat(Fid*)
{
        return Eperm;
}

Fid *
newfid(int fid)
{
        Fid *f, *ff;

        ff = 0;
        for(f = fids; f; f = f->next)
                if(f->fid == fid)
                        return f;
                else if(!ff && !f->busy)
                        ff = f;
        if(ff){
                ff->fid = fid;
                ff->fptr = nil;
                return ff;
        }
        f = emalloc(sizeof *f);
        f->fid = fid;
        f->fptr = nil;
        f->next = fids;
        fids = f;
        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)
{
        char *err;
        int n;

        /* start a process to watch the mailboxes*/
        if(plumbing){
                switch(rfork(RFPROC|RFMEM)){
                case -1:
                        /* oh well */
                        break;
                case 0:
                        reader();
                        exits(nil);
                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)
                        return;
                if(convM2S(mdata, n, &thdr) == 0)
                        continue;

                if(debug)
                        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));
                if(err){
                        rhdr.type = Rerror;
                        rhdr.ename = err;
                }else{
                        rhdr.type = thdr.type + 1;
                        rhdr.fid = thdr.fid;
                }
                rhdr.tag = thdr.tag;
                if(debug)
                        fprint(2, "%s:->%F\n", argv0, &rhdr);/**/
                n = convS2M(&rhdr, mdata, messagesize);
                if(write(mfd[1], mdata, n) != n)
                        error("mount write");
        }
}

void
reader(void)
{
        ulong t;
        Dir *d;
        Mailbox *mb;

        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);
                                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;
                        }
                        qunlock(mb);
                        free(d);
                }
                qunlock(&mbllock);
                if(mb != nil){
                        syncmbox(mb, 1);
                        qunlock(mb);
                } else
                        sleep(15*1000);
        }
}

int
newid(void)
{
        int rv;
        static int id;
        static Lock idlock;

        lock(&idlock);
        rv = ++id;
        unlock(&idlock);

        return rv;
}

void
error(char *s)
{
        postnote(PNGROUP, getpid(), "die yankee pig dog");
        fprint(2, "%s: %s: %r\n", argv0, s);
        exits(s);
}


typedef struct Ignorance Ignorance;
struct Ignorance
{
        Ignorance *next;
        char    *str;           /* string */
        int     partial;        /* true if not exact match */
};
Ignorance *ignorance;

/*
 *  read the file of headers to ignore 
 */
void
readignore(void)
{
        char *p;
        Ignorance *i;
        Biobuf *b;

        if(ignorance != nil)
                return;

        b = Bopen("/mail/lib/ignore", OREAD);
        if(b == 0)
                return;
        while(p = Brdline(b, '\n')){
                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->str = strdup(p);
                if(i->str == 0){
                        free(i);
                        break;
                }
                i->next = ignorance;
                ignorance = i;
        }
        Bterm(b);
}

int
ignore(char *p)
{
        Ignorance *i;

        readignore();
        for(i = ignorance; i != nil; i = i->next)
                if(cistrncmp(i->str, p, i->partial) == 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;

        p = m->header;
        e = m->hend;
        s = nil;

        // copy in good headers
        while(cnt > 0 && p < e){
                n = hdrlen(p, e);
                if(ignore(p)){
                        p += n;
                        continue;
                }

                // rfc2047 processing
                s = stringconvert(s, p, n);
                ns = s_len(s);
                if(off > 0){
                        if(ns <= off){
                                off -= ns;
                                p += n;
                                continue;
                        }
                        ns -= off;
                }
                if(ns > cnt)
                        ns = cnt;
                memmove(to, s_to_c(s)+off, ns);
                to += ns;
                p += n;
                cnt -= ns;
                off = 0;
        }

        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
hash(ulong ppath, char *name)
{
        uchar *p;
        uint h;

        h = 0;
        for(p = (uchar*)name; *p; p++)
                h = h*7 + *p;
        h += ppath;

        return h % Hsize;
}

Hash*
hlook(ulong ppath, char *name)
{
        int h;
        Hash *hp;

        qlock(&hashlock);
        h = hash(ppath, name);
        for(hp = htab[h]; hp != nil; hp = hp->next)
                if(ppath == hp->ppath && strcmp(name, hp->name) == 0){
                        qunlock(&hashlock);
                        return hp;
                }
        qunlock(&hashlock);
        return nil;
}

void
henter(ulong ppath, char *name, Qid qid, Message *m, Mailbox *mb)
{
        int h;
        Hash *hp, **l;

        qlock(&hashlock);
        h = hash(ppath, name);
        for(l = &htab[h]; *l != nil; l = &(*l)->next){
                hp = *l;
                if(ppath == hp->ppath && strcmp(name, hp->name) == 0){
                        hp->m = m;
                        hp->mb = mb;
                        hp->qid = qid;
                        qunlock(&hashlock);
                        return;
                }
        }

        *l = hp = emalloc(sizeof(*hp));
        hp->m = m;
        hp->mb = mb;
        hp->qid = qid;
        hp->name = name;
        hp->ppath = ppath;
        qunlock(&hashlock);
}

void
hfree(ulong ppath, char *name)
{
        int h;
        Hash *hp, **l;

        qlock(&hashlock);
        h = hash(ppath, name);
        for(l = &htab[h]; *l != nil; l = &(*l)->next){
                hp = *l;
                if(ppath == hp->ppath && strcmp(name, hp->name) == 0){
                        hp->mb = nil;
                        *l = hp->next;
                        free(hp);
                        break;
                }
        }
        qunlock(&hashlock);
}

int
hashmboxrefs(Mailbox *mb)
{
        int h;
        Hash *hp;
        int refs = 0;

        qlock(&hashlock);
        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];

        fd = create(name, OWRITE, 0600);
        if(fd < 0)
                error("post failed");
        sprint(buf, "%d",srvfd);
        if(write(fd, buf, strlen(buf)) != strlen(buf))
                error("srv write");
        close(fd);
        putenv(envname, name);
}