aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/upas/nfs/fs.c
diff options
context:
space:
mode:
authorrsc <devnull@localhost>2006-02-15 12:39:09 +0000
committerrsc <devnull@localhost>2006-02-15 12:39:09 +0000
commit941e17134e92de7f12977f1899860b57bbf83330 (patch)
treee0c7dc5b23b603b24c2ec0cb8d3544f91bb36101 /src/cmd/upas/nfs/fs.c
parent1ea614ffaf9378df45410995d0a8c13042bba123 (diff)
downloadplan9port-941e17134e92de7f12977f1899860b57bbf83330.tar.gz
plan9port-941e17134e92de7f12977f1899860b57bbf83330.tar.bz2
plan9port-941e17134e92de7f12977f1899860b57bbf83330.zip
imap-based new upas/fs
Diffstat (limited to 'src/cmd/upas/nfs/fs.c')
-rw-r--r--src/cmd/upas/nfs/fs.c1259
1 files changed, 1259 insertions, 0 deletions
diff --git a/src/cmd/upas/nfs/fs.c b/src/cmd/upas/nfs/fs.c
new file mode 100644
index 00000000..eda3d666
--- /dev/null
+++ b/src/cmd/upas/nfs/fs.c
@@ -0,0 +1,1259 @@
+/*
+ * Mail file system.
+ *
+ * Serve the bulk of requests out of memory, so they can
+ * be in the main loop (will never see their flushes).
+ * Some requests do block and they get handled in
+ * separate threads. They're all okay to give up on
+ * early, though, so we just respond saying interrupted
+ * and then when they finish, silently discard the request.
+
+TO DO:
+
+ decode subject, etc.
+ decode body
+
+ digest
+ disposition
+ filename
+
+ ctl messages
+
+ fetch mail on demand
+
+ */
+
+#include "a.h"
+
+enum
+{
+ /* directories */
+ Qroot,
+ Qbox,
+ Qmsg,
+
+ /* control files */
+ Qctl,
+ Qboxctl,
+ Qsearch,
+
+ /* message header - same order as struct Hdr */
+ Qdate,
+ Qsubject,
+ Qfrom,
+ Qsender,
+ Qreplyto,
+ Qto,
+ Qcc,
+ Qbcc,
+ Qinreplyto,
+ Qmessageid,
+
+ /* part data - same order as stuct Part */
+ Qtype,
+ Qidstr,
+ Qdesc,
+ Qencoding, /* only here temporarily! */
+ Qcharset,
+ Qraw,
+ Qrawheader,
+ Qrawbody,
+ Qmimeheader,
+
+ /* part numbers - same order as struct Part */
+ Qsize,
+ Qlines,
+
+ /* other message files */
+ Qbody,
+ Qheader,
+ Qdigest,
+ Qdisposition,
+ Qfilename,
+ Qflags,
+ Qinfo,
+ Qrawunix,
+ Qunixdate,
+ Qunixheader,
+
+ Qfile0 = Qbody,
+ Qnfile = Qunixheader+1-Qfile0,
+};
+
+static char Egreg[] = "gone postal";
+static char Enobox[] = "no such mailbox";
+static char Enomsg[] = "no such message";
+static char Eboxgone[] = "mailbox not available";
+static char Emsggone[] = "message not available";
+static char Eperm[] = "permission denied";
+static char Ebadctl[] = "bad control message";
+
+Channel *fsreqchan;
+Srv fs;
+Qid rootqid;
+ulong t0;
+
+void
+responderror(Req *r)
+{
+ char e[ERRMAX];
+
+ rerrstr(e, sizeof e);
+ respond(r, e);
+}
+
+int
+qtype(Qid q)
+{
+ return q.path&0x3F;
+}
+
+int
+qboxid(Qid q)
+{
+ return (q.path>>40)&0xFFFF;
+}
+
+int
+qmsgid(Qid q)
+{
+ return ((q.path>>32)&0xFF000000) | ((q.path>>16)&0xFFFFFF);
+}
+
+int
+qpartid(Qid q)
+{
+ return ((q.path>>6)&0x3FF);
+}
+
+Qid
+qid(int ctl, Box *box, Msg *msg, Part *part)
+{
+ Qid q;
+
+ q.type = 0;
+ if(ctl == Qroot || ctl == Qbox || ctl == Qmsg)
+ q.type = QTDIR;
+ q.path = (vlong)((msg ? msg->id : 0)&0xFF000000)<<32;
+ q.path |= (vlong)((msg ? msg->id : 0)&0xFFFFFF)<<16;
+ q.path |= (vlong)((box ? box->id : 0)&0xFFFF)<<40;
+ q.path |= ((part ? part->ix : 0)&0x3FF)<<6;
+ q.path |= ctl&0x3F;
+ q.vers = box ? box->validity : 0;
+ return q;
+}
+
+int
+parseqid(Qid q, Box **box, Msg **msg, Part **part)
+{
+ *msg = nil;
+ *part = nil;
+
+ *box = boxbyid(qboxid(q));
+ if(*box){
+ *msg = msgbyid(*box, qmsgid(q));
+ }
+ if(*msg)
+ *part = partbyid(*msg, qpartid(q));
+ return qtype(q);
+}
+
+static struct {
+ int type;
+ char *name;
+} typenames[] = {
+ Qbody, "body",
+ Qbcc, "bcc",
+ Qcc, "cc",
+ Qdate, "date",
+ Qfilename, "filename",
+ Qflags, "flags",
+ 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",
+ Qidstr, "idstr",
+ Qdesc, "desc",
+ Qencoding, "encoding",
+ Qcharset, "charset",
+};
+
+char*
+nameoftype(int t)
+{
+ int i;
+
+ for(i=0; i<nelem(typenames); i++)
+ if(typenames[i].type == t)
+ return typenames[i].name;
+ return "???";
+}
+
+int
+typeofname(char *name)
+{
+ int i;
+
+ for(i=0; i<nelem(typenames); i++)
+ if(strcmp(typenames[i].name, name) == 0)
+ return typenames[i].type;
+ return 0;
+}
+
+static void
+fsattach(Req *r)
+{
+ r->fid->qid = rootqid;
+ r->ofcall.qid = rootqid;
+ respond(r, nil);
+}
+
+int
+isnumber(char *s)
+{
+ int n;
+
+ if(*s < '1' || *s > '9')
+ return 0;
+ n = strtol(s, &s, 10);
+ if(*s != 0)
+ return 0;
+ return n;
+}
+
+static char*
+fswalk1(Fid *fid, char *name, void *arg)
+{
+ int a, type;
+ Box *b, *box;
+ Msg *msg;
+ Part *p, *part;
+
+ USED(arg);
+
+ switch(type = parseqid(fid->qid, &box, &msg, &part)){
+ case Qroot:
+ if(strcmp(name, "..") == 0)
+ return nil;
+ if(strcmp(name, "ctl") == 0){
+ fid->qid = qid(Qctl, nil, nil, nil);
+ return nil;
+ }
+ if((box = boxbyname(name)) != nil){
+ fid->qid = qid(Qbox, box, nil, nil);
+ return nil;
+ }
+ break;
+
+ case Qbox:
+ /*
+ * Would be nice if .. could work even if the box is gone,
+ * but we don't know how deep the directory was.
+ */
+ if(box == nil)
+ return Eboxgone;
+ if(strcmp(name, "..") == 0){
+ if((box = box->parent) == nil){
+ fid->qid = rootqid;
+ return nil;
+ }
+ fid->qid = qid(Qbox, box, nil, nil);
+ return nil;
+ }
+ if(strcmp(name, "ctl") == 0){
+ fid->qid = qid(Qboxctl, box, nil, nil);
+ return nil;
+ }
+ if(strcmp(name, "search") == 0){
+ fid->qid = qid(Qsearch, box, nil, nil);
+ return nil;
+ }
+ if((b = subbox(box, name)) != nil){
+ fid->qid = qid(Qbox, b, nil, nil);
+ return nil;
+ }
+ if((a = isnumber(name)) != 0){
+ if((msg = msgbyid(box, a)) == nil){
+ return Enomsg;
+ }
+ fid->qid = qid(Qmsg, box, msg, nil);
+ return nil;
+ }
+ break;
+
+ case Qmsg:
+ if(strcmp(name, "..") == 0){
+ if(part == msg->part[0]){
+ fid->qid = qid(Qbox, box, nil, nil);
+ return nil;
+ }
+ fid->qid = qid(Qmsg, box, msg, part->parent);
+ return nil;
+ }
+ if((type = typeofname(name)) > 0){
+ /* XXX - should check that type makes sense (see msggen) */
+ fid->qid = qid(type, box, msg, part);
+ return nil;
+ }
+ if((a = isnumber(name)) != 0){
+ if((p = subpart(part, a-1)) != nil){
+ fid->qid = qid(Qmsg, box, msg, p);
+ return nil;
+ }
+ }
+ break;
+ }
+ return "not found";
+}
+
+static void
+fswalk(Req *r)
+{
+ walkandclone(r, fswalk1, nil, nil);
+}
+
+static struct {
+ int flag;
+ char *name;
+} flagtab[] = {
+ FlagJunk, "junk",
+ FlagNonJunk, "notjunk",
+ FlagReplied, "replied",
+ FlagFlagged, "flagged",
+// FlagDeleted, "deleted",
+ FlagDraft, "draft",
+ FlagSeen, "seen",
+};
+
+static void
+addaddrs(Fmt *fmt, char *prefix, char *addrs)
+{
+ char **f;
+ int i, nf, inquote;
+ char *p, *sep;
+
+ if(addrs == nil)
+ return;
+ addrs = estrdup(addrs);
+ nf = 0;
+ inquote = 0;
+ for(p=addrs; *p; p++){
+ if(*p == ' ' && !inquote)
+ nf++;
+ if(*p == '\'')
+ inquote = !inquote;
+ }
+ nf += 10;
+ f = emalloc(nf*sizeof f[0]);
+ nf = tokenize(addrs, f, nf);
+ fmtprint(fmt, "%s:", prefix);
+ sep = " ";
+ for(i=0; i+1<nf; i+=2){
+ if(f[i][0])
+ fmtprint(fmt, "%s%s <%s>", sep, f[i], f[i+1]);
+ else
+ fmtprint(fmt, "%s%s", sep, f[i+1]);
+ sep = ", ";
+ }
+ fmtprint(fmt, "\n");
+ free(addrs);
+}
+
+static void
+mkbody(Part *p, Qid q)
+{
+ char *t;
+ int len;
+
+ if(p->msg->part[0] == p)
+ t = p->rawbody;
+ else
+ t = p->raw;
+ if(t == nil)
+ return;
+
+ len = -1;
+ if(p->encoding && cistrcmp(p->encoding, "quoted-printable") == 0)
+ t = decode(QuotedPrintable, t, &len);
+ else if(p->encoding && cistrcmp(p->encoding, "base64") == 0)
+ t = decode(Base64, t, &len);
+ else
+ t = estrdup(t);
+
+ if(p->charset){
+ t = tcs(p->charset, t);
+ len = -1;
+ }
+ p->body = t;
+ if(len == -1)
+ p->nbody = strlen(t);
+ else
+ p->nbody = len;
+}
+
+static Qid ZQ;
+
+static int
+filedata(int type, Box *box, Msg *msg, Part *part, char **pp, int *len, int *freeme, int force, Qid q)
+{
+ int i, inquote, n, t;
+ char *from, *s;
+ static char buf[256];
+ Fmt fmt;
+
+ *pp = nil;
+ *freeme = 0;
+ if(len)
+ *len = -1;
+
+ if(msg == nil || part == nil){
+ werrstr(Emsggone);
+ return -1;
+ }
+ switch(type){
+ case Qdate:
+ case Qsubject:
+ case Qfrom:
+ case Qsender:
+ case Qreplyto:
+ case Qto:
+ case Qcc:
+ case Qbcc:
+ case Qinreplyto:
+ case Qmessageid:
+ if(part->hdr == nil){
+ werrstr(Emsggone);
+ return -1;
+ }
+ *pp = ((char**)&part->hdr->date)[type-Qdate];
+ return 0;
+
+ case Qunixdate:
+ strcpy(buf, ctime(msg->date));
+ *pp = buf;
+ return 0;
+
+ case Qunixheader:
+ if(part->hdr == nil){
+ werrstr(Emsggone);
+ return -1;
+ }
+ from = part->hdr->from;
+ if(from == nil)
+ from = "???";
+ else{
+ inquote = 0;
+ for(; *from; from++){
+ if(*from == '\'')
+ inquote = !inquote;
+ if(!inquote && *from == ' '){
+ from++;
+ break;
+ }
+ }
+ if(*from == 0)
+ from = part->hdr->from;
+ }
+ n = snprint(buf, sizeof buf, "From %s %s", from, ctime(msg->date));
+ if(n+1 < sizeof buf){
+ *pp = buf;
+ return 0;
+ }
+ fmtstrinit(&fmt);
+ fmtprint(&fmt, "From %s %s", from, ctime(msg->date));
+ s = fmtstrflush(&fmt);
+ if(s){
+ *pp = s;
+ *freeme = 1;
+ }else
+ *pp = buf;
+ return 0;
+
+ case Qtype:
+ case Qidstr:
+ case Qdesc:
+ case Qencoding:
+ case Qcharset:
+ case Qraw:
+ case Qrawheader:
+ case Qrawbody:
+ case Qmimeheader:
+ *pp = ((char**)&part->type)[type-Qtype];
+ if(*pp == nil && force){
+ switch(type){
+ case Qraw:
+ imapfetchraw(imap, part);
+ break;
+ case Qrawheader:
+ imapfetchrawheader(imap, part);
+ break;
+ case Qrawbody:
+ imapfetchrawbody(imap, part);
+ break;
+ case Qmimeheader:
+ imapfetchrawmime(imap, part);
+ break;
+ default:
+ return 0;
+ }
+ /*
+ * We ran fetchsomething, which might have changed
+ * the mailbox contents. Msg might even be gone.
+ */
+ t = parseqid(q, &box, &msg, &part);
+ if(t != type || msg == nil || part == nil)
+ return 0;
+ *pp = ((char**)&part->type)[type-Qtype];
+ }
+ return 0;
+
+ case Qbody:
+ if(part->body){
+ *pp = part->body;
+ if(len)
+ *len = part->nbody;
+ return 0;
+ }
+ if(!force)
+ return 0;
+ if(part->rawbody == nil){
+ if(part->msg->part[0] == part)
+ imapfetchrawbody(imap, part);
+ else
+ imapfetchraw(imap, part);
+ t = parseqid(q, &box, &msg, &part);
+ if(t != type || msg == nil || part == nil)
+ return 0;
+ }
+ mkbody(part, q);
+ *pp = part->body;
+ if(len)
+ *len = part->nbody;
+ return 0;
+
+ case Qsize:
+ case Qlines:
+ n = ((uint*)&part->size)[type-Qsize];
+ snprint(buf, sizeof buf, "%d", n);
+ *pp = buf;
+ return 0;
+
+ case Qflags:
+ s = buf;
+ *s = 0;
+ for(i=0; i<nelem(flagtab); i++){
+ if(msg->flags&flagtab[i].flag){
+ if(s > buf)
+ *s++ = ' ';
+ strcpy(s, flagtab[i].name);
+ s += strlen(s);
+ }
+ }
+ *pp = buf;
+ return 0;
+
+ case Qinfo:
+ fmtstrinit(&fmt);
+ if(part == msg->part[0]){
+ if(msg->date)
+ fmtprint(&fmt, "unixdate %lud %s", msg->date, ctime(msg->date));
+ if(msg->flags){
+ filedata(Qflags, box, msg, part, pp, nil, freeme, 0, ZQ);
+ fmtprint(&fmt, "flags %s\n", buf);
+ }
+ }
+ if(part->hdr){
+ if(part->hdr->digest)
+ fmtprint(&fmt, "digest %s\n", part->hdr->digest);
+ if(part->hdr->from)
+ fmtprint(&fmt, "from %s\n", part->hdr->from);
+ if(part->hdr->to)
+ fmtprint(&fmt, "to %s\n", part->hdr->to);
+ if(part->hdr->cc)
+ fmtprint(&fmt, "cc %s\n", part->hdr->cc);
+ if(part->hdr->replyto)
+ fmtprint(&fmt, "replyto %s\n", part->hdr->replyto);
+ if(part->hdr->bcc)
+ fmtprint(&fmt, "bcc %s\n", part->hdr->bcc);
+ if(part->hdr->inreplyto)
+ fmtprint(&fmt, "inreplyto %s\n", part->hdr->inreplyto);
+ if(part->hdr->date)
+ fmtprint(&fmt, "date %s\n", part->hdr->date);
+ if(part->hdr->sender)
+ fmtprint(&fmt, "sender %s\n", part->hdr->sender);
+ if(part->hdr->messageid)
+ fmtprint(&fmt, "messageid %s\n", part->hdr->messageid);
+ if(part->hdr->subject)
+ fmtprint(&fmt, "subject %s\n", part->hdr->subject);
+ }
+ if(part->type)
+ fmtprint(&fmt, "type %s\n", part->type);
+ if(part->lines)
+ fmtprint(&fmt, "lines %d\n", part->lines);
+ // fmtprint(&fmt, "disposition %s\n", "" /* disposition */);
+ // fmtprint(&fmt, "filename %s\n", "" /* filename */);
+ // fmtprint(&fmt, "digest %s\n", "" /* digest */);
+ s = fmtstrflush(&fmt);
+ if(s == nil)
+ s = estrdup("");
+ *freeme = 1;
+ *pp = s;
+ return 0;
+
+ case Qheader:
+ if(part->hdr == nil)
+ return 0;
+ fmtstrinit(&fmt);
+ if(part == msg->part[0])
+ fmtprint(&fmt, "Date: %s", ctime(msg->date));
+ else
+ fmtprint(&fmt, "Date: %s\n", part->hdr->date);
+ addaddrs(&fmt, "To", part->hdr->to);
+ addaddrs(&fmt, "From", part->hdr->from);
+ if(part->hdr->from==nil
+ || (part->hdr->sender && strcmp(part->hdr->sender, part->hdr->from) != 0))
+ addaddrs(&fmt, "Sender", part->hdr->sender);
+ if(part->hdr->from==nil
+ || (part->hdr->replyto && strcmp(part->hdr->replyto, part->hdr->from) != 0))
+ addaddrs(&fmt, "Reply-To", part->hdr->replyto);
+ addaddrs(&fmt, "Subject", part->hdr->subject);
+ s = fmtstrflush(&fmt);
+ if(s == nil)
+ s = estrdup("");
+ *freeme = 1;
+ *pp = s;
+ return 0;
+
+ default:
+ werrstr(Egreg);
+ return -1;
+ }
+}
+
+int
+filldir(Dir *d, int type, Box *box, Msg *msg, Part *part)
+{
+ int freeme, len;
+ char *s;
+
+ memset(d, 0, sizeof *d);
+ if(box){
+ d->atime = box->time;
+ d->mtime = box->time;
+ }else{
+ d->atime = t0;
+ d->mtime = t0;
+ }
+ d->uid = estrdup9p("upas");
+ d->gid = estrdup9p("upas");
+ d->muid = estrdup9p("upas");
+ d->qid = qid(type, box, msg, part);
+
+ switch(type){
+ case Qroot:
+ case Qbox:
+ case Qmsg:
+ d->mode = 0555|DMDIR;
+ if(box && !(box->flags&FlagNoInferiors))
+ d->mode = 0775|DMDIR;
+ break;
+ case Qctl:
+ case Qboxctl:
+ d->mode = 0222;
+ break;
+ case Qsearch:
+ d->mode = 0666;
+ break;
+
+ case Qflags:
+ d->mode = 0666;
+ goto msgfile;
+ default:
+ d->mode = 0444;
+ msgfile:
+ if(filedata(type, box, msg, part, &s, &len, &freeme, 0, ZQ) >= 0){
+ if(s){
+ if(len == -1)
+ d->length = strlen(s);
+ else
+ d->length = len;
+ if(freeme)
+ free(s);
+ }
+ }else if(type == Qraw && msg && part == msg->part[0])
+ d->length = msg->size;
+ break;
+ }
+
+ switch(type){
+ case Qroot:
+ d->name = estrdup9p("/");
+ break;
+ case Qbox:
+ if(box == nil){
+ werrstr(Enobox);
+ return -1;
+ }
+ d->name = estrdup9p(box->elem);
+ break;
+ case Qmsg:
+ if(msg == nil){
+ werrstr(Enomsg);
+ return -1;
+ }
+ if(part == nil || part == msg->part[0])
+ d->name = esmprint("%d", msg->id);
+ else
+ d->name = esmprint("%d", part->pix+1);
+ break;
+ case Qctl:
+ case Qboxctl:
+ d->name = estrdup9p("ctl");
+ break;
+ case Qsearch:
+ d->name = estrdup9p("search");
+ break;
+ default:
+ d->name = estrdup9p(nameoftype(type));
+ break;
+ }
+ return 0;
+}
+
+static void
+fsstat(Req *r)
+{
+ int type;
+ Box *box;
+ Msg *msg;
+ Part *part;
+
+ type = parseqid(r->fid->qid, &box, &msg, &part);
+ if(filldir(&r->d, type, box, msg, part) < 0)
+ responderror(r);
+ else
+ respond(r, nil);
+}
+
+int
+rootgen(int i, Dir *d, void *aux)
+{
+ USED(aux);
+
+ if(i == 0)
+ return filldir(d, Qctl, nil, nil, nil);
+ i--;
+ if(i < rootbox->nsub)
+ return filldir(d, Qbox, rootbox->sub[i], nil, nil);
+ return -1;
+}
+
+int
+boxgen(int i, Dir *d, void *aux)
+{
+ Box *box;
+
+ box = aux;
+if(i==0) fprint(2, "boxgen %s %d nsub=%d nmsg=%d\n", box->name, i, box->nsub, box->nmsg);
+ if(i == 0)
+ return filldir(d, Qboxctl, box, nil, nil);
+ i--;
+ if(i == 0)
+ return filldir(d, Qsearch, box, nil, nil);
+ if(i < box->nsub)
+ return filldir(d, Qbox, box->sub[i], nil, nil);
+ i -= box->nsub;
+ if(i < box->nmsg)
+ return filldir(d, Qmsg, box, box->msg[i], nil);
+ return -1;
+}
+
+static int msgdir[] = {
+ Qtype,
+ Qbody, Qbcc, Qcc, Qdate, Qflags, Qfrom, Qheader, Qinfo,
+ Qinreplyto, Qlines, Qmimeheader, Qmessageid,
+ Qraw, Qrawunix, Qrawbody, Qrawheader,
+ Qreplyto, Qsender, Qsubject, Qto,
+ Qunixdate, Qunixheader
+};
+static int mimemsgdir[] = {
+ Qtype,
+ Qbody, Qbcc, Qcc, Qdate, Qfrom, Qheader, Qinfo,
+ Qinreplyto, Qlines, Qmimeheader, Qmessageid,
+ Qraw, Qrawunix, Qrawbody, Qrawheader,
+ Qreplyto, Qsender, Qsubject, Qto,
+};
+static int mimedir[] = {
+ Qtype,
+ Qbody,
+ Qmimeheader,
+ Qraw,
+};
+
+int
+msggen(int i, Dir *d, void *aux)
+{
+ Box *box;
+ Msg *msg;
+ Part *part;
+
+ part = aux;
+ msg = part->msg;
+ box = msg->box;
+ if(part->ix == 0){
+ if(i < nelem(msgdir))
+ return filldir(d, msgdir[i], box, msg, part);
+ i -= nelem(msgdir);
+ }else if(part->type && strcmp(part->type, "message/rfc822") == 0){
+ if(i < nelem(mimemsgdir))
+ return filldir(d, mimemsgdir[i], box, msg, part);
+ i -= nelem(mimemsgdir);
+ }else{
+ if(i < nelem(mimedir))
+ return filldir(d, mimedir[i], box, msg, part);
+ i -= nelem(mimedir);
+ }
+ if(i < part->nsub)
+ return filldir(d, Qmsg, box, msg, part->sub[i]);
+ return -1;
+}
+
+enum
+{
+ CMhangup,
+};
+static Cmdtab ctltab[] =
+{
+ CMhangup, "hangup", 2,
+};
+
+enum
+{
+ CMdelete,
+ CMrefresh,
+ CMreplied,
+ CMread,
+ CMsave,
+ CMjunk,
+ CMnonjunk,
+};
+static Cmdtab boxctltab[] =
+{
+ CMdelete, "delete", 0,
+ CMrefresh, "refresh", 1,
+ CMreplied, "replied", 0,
+ CMread, "read", 0,
+ CMsave, "save", 0,
+ CMjunk, "junk", 0,
+ CMnonjunk, "nonjunk", 0,
+};
+
+static void
+fsread(Req *r)
+{
+ char *s;
+ int type, len, freeme;
+ Box *box;
+ Msg *msg;
+ Part *part;
+
+ switch(type = parseqid(r->fid->qid, &box, &msg, &part)){
+ case Qroot:
+ dirread9p(r, rootgen, nil);
+ respond(r, nil);
+ return;
+
+ case Qbox:
+ if(box == nil){
+ respond(r, Eboxgone);
+ return;
+ }
+ if(box->nmsg == 0)
+ imapcheckbox(imap, box);
+ parseqid(r->fid->qid, &box, &msg, &part);
+ if(box == nil){
+ respond(r, Eboxgone);
+ return;
+ }
+ dirread9p(r, boxgen, box);
+ respond(r, nil);
+ return;
+
+ case Qmsg:
+ if(msg == nil || part == nil){
+ respond(r, Emsggone);
+ return;
+ }
+ dirread9p(r, msggen, part);
+ respond(r, nil);
+ return;
+
+ case Qctl:
+ case Qboxctl:
+ respond(r, Egreg);
+ return;
+
+ case Qsearch:
+ readstr(r, r->fid->aux);
+ respond(r, nil);
+ return;
+
+ default:
+ if(filedata(type, box, msg, part, &s, &len, &freeme, 1, r->fid->qid) < 0){
+ responderror(r);
+ return;
+ }
+ if(s && len == -1)
+ len = strlen(s);
+ readbuf(r, s, len);
+ if(freeme)
+ free(s);
+ respond(r, nil);
+ return;
+ }
+}
+
+int
+mkmsglist(Box *box, char **f, int nf, Msg ***mm)
+{
+ int i, nm;
+ Msg **m;
+
+ m = emalloc(nf*sizeof m[0]);
+ nm = 0;
+ for(i=0; i<nf; i++)
+ if((m[nm] = msgbyid(box, atoi(f[i]))) != nil)
+ nm++;
+ *mm = m;
+ return nm;
+}
+
+static void
+fswrite(Req *r)
+{
+ int i, j, c, type, flag, unflag, flagset, f, reset;
+ Box *box;
+ Msg *msg;
+ Part *part;
+ Cmdbuf *cb;
+ Cmdtab *ct;
+ Msg **m;
+ int nm;
+ Fmt fmt;
+
+ r->ofcall.count = r->ifcall.count;
+ switch(type = parseqid(r->fid->qid, &box, &msg, &part)){
+ default:
+ respond(r, Egreg);
+ break;
+
+ case Qctl:
+ cb = parsecmd(r->ifcall.data, r->ifcall.count);
+ if((ct = lookupcmd(cb, ctltab, nelem(ctltab))) == nil){
+ respondcmderror(r, cb, "unknown message");
+ free(cb);
+ return;
+ }
+ r->ofcall.count = r->ifcall.count;
+ switch(ct->index){
+ case CMhangup:
+ imaphangup(imap, atoi(cb->f[1]));
+ respond(r, nil);
+ break;
+ default:
+ respond(r, Egreg);
+ break;
+ }
+ free(cb);
+ return;
+
+ case Qboxctl:
+ cb = parsecmd(r->ifcall.data, r->ifcall.count);
+ if((ct = lookupcmd(cb, boxctltab, nelem(boxctltab))) == nil){
+ respondcmderror(r, cb, "bad message");
+ free(cb);
+ return;
+ }
+ r->ofcall.count = r->ifcall.count;
+ switch(ct->index){
+ case CMsave:
+ if(cb->nf <= 2){
+ respondcmderror(r, cb, Ebadctl);
+ break;
+ }
+ nm = mkmsglist(box, cb->f+2, cb->nf-2, &m);
+ if(nm != cb->nf-2){
+ // free(m);
+ respond(r, Enomsg);
+ break;
+ }
+ if(nm > 0 && imapcopylist(imap, cb->f[1], m, nm) < 0)
+ responderror(r);
+ else
+ respond(r, nil);
+ free(m);
+ break;
+
+ case CMjunk:
+ flag = FlagJunk;
+ goto flagit;
+ case CMnonjunk:
+ flag = FlagNonJunk;
+ goto flagit;
+ case CMreplied:
+ flag = FlagReplied;
+ goto flagit;
+ case CMread:
+ flag = FlagSeen;
+ flagit:
+ if(cb->nf <= 1){
+ respondcmderror(r, cb, Ebadctl);
+ break;
+ }
+ nm = mkmsglist(box, cb->f+1, cb->nf-1, &m);
+ if(nm != cb->nf-1){
+ free(m);
+ respond(r, Enomsg);
+ break;
+ }
+ if(nm > 0 && imapflaglist(imap, +1, flag, m, nm) < 0)
+ responderror(r);
+ else
+ respond(r, nil);
+ free(m);
+ break;
+
+ case CMrefresh:
+ imapcheckbox(imap, box);
+ respond(r, nil);
+ break;
+
+ case CMdelete:
+ if(cb->nf <= 1){
+ respondcmderror(r, cb, Ebadctl);
+ break;
+ }
+ nm = mkmsglist(box, cb->f+1, cb->nf-1, &m);
+ if(nm > 0 && imapremovelist(imap, m, nm) < 0)
+ responderror(r);
+ else
+ respond(r, nil);
+ free(m);
+ break;
+
+ default:
+ respond(r, Egreg);
+ break;
+ }
+ free(cb);
+ return;
+
+ case Qflags:
+ if(msg == nil){
+ respond(r, Enomsg);
+ return;
+ }
+ cb = parsecmd(r->ifcall.data, r->ifcall.count);
+ flag = 0;
+ unflag = 0;
+ flagset = 0;
+ reset = 0;
+ for(i=0; i<cb->nf; i++){
+ f = 0;
+ c = cb->f[i][0];
+ if(c == '+' || c == '-')
+ cb->f[i]++;
+ for(j=0; j<nelem(flagtab); j++){
+ if(strcmp(flagtab[j].name, cb->f[i]) == 0){
+ f = flagtab[j].flag;
+ break;
+ }
+ }
+ if(f == 0){
+ respondcmderror(r, cb, "unknown flag %s", cb->f[i]);
+ free(cb);
+ return;
+ }
+ if(c == '+')
+ flag |= f;
+ else if(c == '-')
+ unflag |= f;
+ else
+ flagset |= f;
+ }
+ free(cb);
+ if((flagset!=0)+(unflag!=0)+(flag!=0) != 1){
+ respondcmderror(r, cb, Ebadctl);
+ return;
+ }
+ if(flag)
+ i = 1;
+ else if(unflag){
+ i = -1;
+ flag = unflag;
+ }else{
+ i = 0;
+ flag = flagset;
+ }
+ if(imapflaglist(imap, i, flag, &msg, 1) < 0)
+ responderror(r);
+ else
+ respond(r, nil);
+ return;
+
+ case Qsearch:
+ if(box == nil){
+ respond(r, Eboxgone);
+ return;
+ }
+ fmtstrinit(&fmt);
+ nm = imapsearchbox(imap, box, r->ifcall.data, &m);
+ for(i=0; i<nm; i++){
+ if(i>0)
+ fmtrune(&fmt, ' ');
+ fmtprint(&fmt, "%d", m[i]->id);
+ }
+ free(r->fid->aux);
+ r->fid->aux = fmtstrflush(&fmt);
+ respond(r, nil);
+ return;
+ }
+}
+
+static void
+fsopen(Req *r)
+{
+ switch(qtype(r->fid->qid)){
+ case Qctl:
+ case Qboxctl:
+ if((r->ifcall.mode&~OTRUNC) != OWRITE){
+ respond(r, Eperm);
+ return;
+ }
+ respond(r, nil);
+ return;
+
+ case Qflags:
+ case Qsearch:
+ if((r->ifcall.mode&~OTRUNC) > ORDWR){
+ respond(r, Eperm);
+ return;
+ }
+ respond(r, nil);
+ return;
+
+ default:
+ if(r->ifcall.mode != OREAD){
+ respond(r, Eperm);
+ return;
+ }
+ respond(r, nil);
+ return;
+ }
+}
+
+static void
+fsflush(Req *r)
+{
+ /*
+ * We only handle reads and writes outside the main loop,
+ * so we must be flushing one of those. In both cases it's
+ * okay to just ignore the results of the request, whenever
+ * they're ready.
+ */
+ incref(&r->oldreq->ref);
+ respond(r->oldreq, "interrupted");
+ respond(r, nil);
+}
+
+static void
+fsthread(void *v)
+{
+ Req *r;
+
+ r = v;
+ switch(r->ifcall.type){
+ case Tread:
+ fsread(r);
+ break;
+ case Twrite:
+ fswrite(r);
+ break;
+ }
+}
+
+static void
+fsrecv(void *v)
+{
+ Req *r;
+
+ while((r = recvp(fsreqchan)) != nil){
+ switch(r->ifcall.type){
+ case Tattach:
+ fsattach(r);
+ break;
+ case Tflush:
+ fsflush(r);
+ break;
+ case Topen:
+ fsopen(r);
+ break;
+ case Twalk:
+ fswalk(r);
+ break;
+ case Tstat:
+ fsstat(r);
+ break;
+ default:
+ threadcreate(fsthread, r, STACK);
+ break;
+ }
+ }
+}
+
+static void
+fssend(Req *r)
+{
+ sendp(fsreqchan, r);
+}
+
+static void
+fsdestroyfid(Fid *f)
+{
+ free(f->aux);
+}
+
+void
+fsinit0(void) /* bad planning - clash with lib9pclient */
+{
+ t0 = time(0);
+
+ fs.attach = fssend;
+ fs.flush = fssend;
+ fs.open = fssend;
+ fs.walk = fssend;
+ fs.read = fssend;
+ fs.write = fssend;
+ fs.stat = fssend;
+ fs.destroyfid = fsdestroyfid;
+
+ rootqid = qid(Qroot, nil, nil, nil);
+
+ fsreqchan = chancreate(sizeof(void*), 0);
+ mailthread(fsrecv, nil);
+}
+