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