ref: 2d076037d8dddd20eaeb72f6ed31c8a359ce978b
author: Ori Bernstein <ori@eigenstate.org>
date: Tue Nov 3 01:39:20 EST 2020
initial commit
--- /dev/null
+++ b/comp.c
@@ -1,0 +1,190 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <regexp.h>
+
+#include "mail.h"
+
+typedef struct Fn Fn;
+
+struct Fn {
+ char *name;
+ void (*fn)(Comp *, char **, int);
+};
+
+void
+execmarshal(void *p)
+{
+ Comp *c;
+
+ c = p;
+ rfork(RFFDG);
+ dup(c->fd[0], 0);
+ close(c->fd[0]);
+ close(c->fd[1]);
+ sendul(c->sync, 0);
+ procexecl(nil, "/bin/upas/marshal", "marshal", "-8", nil);
+}
+
+static void
+postmesg(Comp *c, char **, int nf)
+{
+ char *buf, wpath[64], *path;
+ int n, fd;
+ Mesg *m;
+
+ snprint(wpath, sizeof(wpath), "/mnt/acme/%d/body", c->id);
+ if(nf != 0){
+ fprint(2, "Post: too many args\n");
+ return;
+ }
+ if((fd = open(wpath, OREAD)) == -1){
+ fprint(2, "open body: %r\n");
+ return;
+ }
+ if(pipe(c->fd) == -1){
+ fprint(2, "pipe: %r\n");
+ close(fd);
+ return;
+ }
+
+ proccreate(execmarshal, c, Stack);
+ recvul(c->sync);
+ close(c->fd[0]);
+
+ buf = emalloc(Bufsz);
+ while((n = read(fd, buf, Bufsz)) > 0)
+ if(write(c->fd[1], buf, n) != n)
+ break;
+ close(c->fd[1]);
+ close(fd);
+ if(n == -1)
+ return;
+
+ if(c->replyto != nil){
+ if((m = mesglookup(c->rname, c->rdigest)) == nil)
+ return;
+ m->flags |= Fresp;
+ path = estrjoin(mbox.path, "/", m->name, "/flags", nil);
+ if((fd = open(path, OWRITE)) != -1){
+ fprint(fd, "+a");
+ close(fd);
+ }
+ mbredraw(m, 0, 0);
+ free(path);
+ }
+
+}
+
+static Fn compfn[] = {
+ {"Post", postmesg},
+ {nil},
+};
+
+static void
+compmain(void *cp)
+{
+ char *a, *f[32];
+ int nf;
+ Event ev;
+ Comp *c;
+ Fn *p;
+
+ c = cp;
+ c->quitting = 0;
+ fprint(c->ctl, "clean\n");
+ mbox.nopen++;
+ while(!c->quitting){
+ if(winevent(c, &ev) != 'M')
+ continue;
+ if(strcmp(ev.text, "Del") == 0)
+ break;
+ switch(ev.type){
+ case 'l':
+ case 'L':
+ if((a = matchaddr(&mbox, &ev)) != nil)
+ compose1(a, nil, 0);
+ else if(matchmesg(&mbox, ev.text))
+ mesgopen(ev.text, nil);
+ else if(!(ev.flags & 0x2))
+ winsendevent(&mbox, &ev);
+ free(a);
+ break;
+ case 'x':
+ case 'X':
+ if((nf = tokenize(ev.text, f, nelem(f))) == 0)
+ continue;
+ for(p = compfn; p->name != nil; p++)
+ if(strcmp(p->name, f[0]) == 0)
+ p->fn(c, &f[1], nf - 1);
+ if(p->name == nil && !(ev.flags & 0x2))
+ winsendevent(&mbox, &ev);
+ break;
+ break;
+ }
+ }
+ mbox.nopen--;
+ winclose(c);
+ free(c->replyto);
+ free(c->rname);
+ free(c->rdigest);
+ threadexits(nil);
+}
+
+void
+compose(char **to, int nto, char **cc, int ncc, Mesg *r, int quote)
+{
+ static int ncompose;
+ char *path, *ln;
+ Biobuf *rfd, *wfd;
+ Comp *c;
+ int i;
+
+ c = emalloc(sizeof(Comp));
+ c->sync = chancreate(sizeof(ulong), 0);
+ if(r != nil)
+ path = esmprint("%s%s%s.%d", mbox.path, r->name, "Reply", ncompose++);
+ else
+ path = esmprint("%sCompose.%d", mbox.path, ncompose++);
+ wininit(c, path);
+ free(path);
+
+ wintagwrite(c, "Delmesg Save Post ");
+ wfd = bwinopen(c, "body", OWRITE);
+ for(i = 0; i < nto; i++)
+ Bprint(wfd, "To: %s\n", to[i]);
+ for(i = 0; i < ncc; i++)
+ Bprint(wfd, "Cc: %s\n", cc[i]);
+ if(r == nil){
+ Bprint(wfd, "\n");
+ }else{
+ if(r->messageid != nil)
+ c->replyto = estrdup(r->messageid);
+ c->rname = estrdup(r->name);
+ c->rdigest = estrdup(r->digest);
+ Bprint(wfd, "Subject: ");
+ if(r->subject != nil && cistrncmp(r->subject, "Re", 2) == 0)
+ Bprint(wfd, "Re: ");
+ Bprint(wfd, "%s\n\n", r->subject);
+ if(quote){
+ path = estrjoin(mbox.path, r->name, "body", nil);
+ rfd = Bopen(path, OREAD);
+ free(path);
+ if(rfd != nil)
+ while((ln = Brdstr(rfd, '\n', 0)) != nil)
+ if(Bprint(wfd, "> %s", ln) == -1)
+ break;
+ Bterm(rfd);
+ }
+ Bterm(wfd);
+ }
+ Bterm(wfd);
+ proccreate(compmain, c, Stack);
+}
+
+void
+compose1(char *to, Mesg *resp, int quote)
+{
+ compose(&to, 1, nil, 0, resp, quote);
+}
--- /dev/null
+++ b/mail.h
@@ -1,0 +1,188 @@
+typedef struct Event Event;
+typedef struct Win Win;
+typedef struct Mesg Mesg;
+typedef struct Mbox Mbox;
+typedef struct Comp Comp;
+
+enum {
+ Stack = 64*1024,
+ Bufsz = 8192,
+ Eventsz = 256*UTFmax,
+ Subjlen = 56,
+};
+
+enum {
+ Fdummy = 1<<0, /* message placeholder */
+ Ftoplev = 1<<1, /* not a response to anything */
+ Fopen = 1<<2, /* opened for viewing */
+
+ Fresp = 1<<3, /* has been responded to */
+ Funseen = 1<<4, /* has been viewed */
+ Fdel = 1<<5, /* was deleted */
+ Ftodel = 1<<6, /* pending deletion */
+};
+
+enum {
+ Vflat,
+ Vgroup,
+};
+
+struct Event {
+ char action;
+ char type;
+ int q0;
+ int q1;
+ int flags;
+ int ntext;
+ char text[Eventsz + 1];
+};
+
+struct Win {
+ Ioproc *io;
+ Biobuf *event;
+ int id;
+ int ctl;
+ int addr;
+ int data;
+};
+
+/*
+ * A composing message.
+ */
+struct Comp {
+ Win;
+
+ /* exec setup */
+ Channel *sync;
+ int fd[2];
+
+ /* to relate back the message */
+ char *replyto;
+ char *rname;
+ char *rdigest;
+
+ char **to;
+ int nto;
+ char **cc;
+ int ncc;
+ char **bcc;
+ int nbcc;
+
+ int quitting;
+};
+
+/*
+ * A message in the mailbox
+ */
+struct Mesg {
+ Win;
+
+ /* bookkeeping */
+ char *name;
+ int flags;
+ u32int hash;
+ Mesg *hnext;
+ char quitting;
+
+ Mesg *parent;
+ Mesg **child;
+ int nchild;
+ int nsub; /* transitive children */
+ Mesg **attachments;
+ int nattachments;
+
+ /* info fields */
+ char *from;
+ char *to;
+ char *cc;
+ char *replyto;
+ char *date;
+ char *subject;
+ char *type;
+ char *disposition;
+ char *messageid;
+ char *filename;
+ char *digest;
+ char *mflags;
+ char *fromcolon;
+ char *inreplyto;
+
+ vlong time;
+};
+
+/*
+ *The mailbox we're showing.
+ */
+struct Mbox {
+ Win;
+
+ /* lock protects mesg, hash */
+ Mesg **mesg;
+ Mesg **hash;
+ int mesgsz;
+ int hashsz;
+ int nmesg;
+ int ndead;
+
+ Channel *see;
+ Channel *show;
+ Channel *event;
+
+ int view;
+ int nopen;
+ char *path;
+};
+
+extern Mbox mbox;
+extern int threadsort;
+extern int plumbsendfd;
+extern int plumbseemailfd;
+extern int plumbshowmailfd;
+extern int plumbsendmailfd;
+extern Reprog *addrpat;
+extern Reprog *mesgpat;
+
+/* window management */
+void wininit(Win*, char*);
+int winopen(Win*, char*, int);
+Biobuf *bwinopen(Win*, char*, int);
+Biobuf *bwindata(Win*, int);
+void winclose(Win*);
+void wintagwrite(Win*, char*);
+int winevent(Win*, Event*);
+void winsendevent(Win*, Event*);
+int wineval(Win*, char*, ...);
+int winread(Win*, int, int, char*, int);
+char *matchaddr(Win*, Event*);
+int matchmesg(Win*, char*);
+char *winreadsel(Win*);
+void wingetsel(Win*, int*, int*);
+void winsetsel(Win*, int, int);
+
+/* messages */
+Mesg *mblookupid(char*);
+Mesg *mesglookup(char*, char*);
+Mesg *mesgload(char*);
+int mesgmatch(Mesg*, char*, char*);
+void mesgopen(char*, char*);
+void mesgclear(Mesg*);
+void mesgfree(Mesg*);
+void mesgpath2name(char*, int, char*);
+
+/* mailbox */
+void mbredraw(Mesg*, int, int);
+
+/* composition */
+void compose(char**, int, char**, int, Mesg*, int);
+void compose1(char*, Mesg*, int);
+
+/* utils */
+void *emalloc(ulong);
+void *erealloc(void*, ulong);
+char *estrdup(char*);
+char *estrjoin(char*, ...);
+char *esmprint(char*, ...);
+char *rslurp(Mesg*, char*, int*);
+char *fslurp(int, int*);
+u32int strhash(char*);
+
--- /dev/null
+++ b/mbox.c
@@ -1,0 +1,767 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <plumb.h>
+#include <ctype.h>
+#include <regexp.h>
+
+#include "mail.h"
+
+typedef struct Fn Fn;
+
+struct Fn {
+ char *name;
+ void (*fn)(char **, int);
+};
+
+enum {
+ Cevent,
+ Cseemail,
+ Cshowmail,
+ Nchan,
+};
+
+
+char *maildir = "/mail/fs";
+char *mailbox = "tmptest";
+Mesg dead = {.messageid="", .hash=42};
+
+Reprog *addrpat;
+Reprog *mesgpat;
+
+int threadsort = 1;
+
+int plumbsendfd;
+int plumbseemailfd;
+int plumbshowmailfd;
+Channel *cwait;
+
+Mbox mbox;
+
+static void showmesg(Biobuf*, Mesg*, int, int);
+
+static void
+plumbloop(Channel *ch, int fd)
+{
+ Plumbmsg *m;
+
+ while(1){
+ if((m = plumbrecv(fd)) == nil)
+ threadexitsall("plumber gone");
+ sendp(ch, m);
+ }
+}
+
+static void
+plumbshow(void*)
+{
+ threadsetname("plumbshow");
+ plumbloop(mbox.show, plumbshowmailfd);
+}
+
+static void
+plumbsee(void*)
+{
+ threadsetname("plumbsee");
+ plumbloop(mbox.see, plumbseemailfd);
+}
+
+static void
+eventread(void*)
+{
+ Event *ev;
+
+ while(1){
+ ev = emalloc(sizeof(Event));
+ if(winevent(&mbox, ev) == -1)
+ threadexitsall(nil);
+ sendp(mbox.event, ev);
+ }
+}
+
+static int
+ideq(Mesg *a, Mesg *b)
+{
+ if(a->messageid == nil || b->messageid == nil)
+ return 0;
+ return strcmp(a->messageid, b->messageid) == 0;
+}
+
+static int
+cmpmesg(void *pa, void *pb)
+{
+ Mesg *a, *b;
+
+ a = *(Mesg**)pa;
+ b = *(Mesg**)pb;
+
+ return b->time - a->time;
+}
+
+static void
+mbsort(void)
+{
+ qsort(mbox.mesg, mbox.nmesg, sizeof(Mesg*), cmpmesg);
+}
+
+static int
+mesglineno(Mesg *msg, int *depth)
+{
+ Mesg *r, *m;
+ int i, o, n, d;
+
+ o = 0;
+ n = 0;
+ d = 0;
+ r = msg;
+ if(msg->parent != nil) {
+ m = msg->parent;
+ for(i = 0; i < m->nchild; i++){
+ if(m->child[i] == msg)
+ break;
+ o += m->child[i]->nsub;
+ }
+ }
+ while(r->parent != nil){
+ r = r->parent;
+ o++;
+ d++;
+ }
+ for(i = 0; i < mbox.nmesg; i++){
+ m = mbox.mesg[i];
+ if(m == r)
+ break;
+ if(m->parent == nil){
+ n += mbox.mesg[i]->nsub;
+ if(!(m->flags & Fdummy))
+ n++;
+ }
+
+ }
+ if(depth != nil)
+ *depth = d;
+ assert(n + o < mbox.nmesg);
+ return n + o;
+}
+
+static int
+addchild(Mesg *p, Mesg *m)
+{
+ Mesg *q;
+
+ assert(m->parent == nil);
+ for(q = p; q != nil; q = q->parent){
+ if(ideq(m, q)){
+ fprint(2, "wonky message replies to self\n");
+ return 0;
+ }
+ if(m->time > q->time)
+ q->time = m->time;
+ }
+ for(q = p; q != nil; q = q->parent)
+ q->nsub++;
+ p->child = erealloc(p->child, ++p->nchild*sizeof(Mesg*));
+ p->child[p->nchild - 1] = m;
+ m->parent = p;
+ return 1;
+}
+
+static int
+slotfor(Mesg *m)
+{
+ int i;
+
+ for(i = 0; i < mbox.nmesg; i++)
+ if(cmpmesg(&mbox.mesg[i], &m) >= 0)
+ break;
+ return i;
+}
+
+static void
+removeid(Mesg *m)
+{
+ Mesg *e;
+ int i;
+
+ /* Dummies don't go in the table */
+ if(m->flags & Fdummy)
+ return;
+ i = m->hash % mbox.hashsz;
+ while(1){
+ e = mbox.hash[i];
+ if(e == nil)
+ return;
+ if(e == &dead)
+ continue;
+ if(e->hash == m->hash && strcmp(e->messageid, m->messageid) == 0){
+ mbox.hash[i] = &dead;
+ mbox.ndead++;
+ }
+ i = (i + 1) % mbox.hashsz;
+ }
+}
+
+Mesg*
+lookupid(char *msgid)
+{
+ u32int h, i;
+ Mesg *e;
+
+ if(msgid == nil)
+ return nil;
+ h = strhash(msgid);
+ i = h % mbox.hashsz;
+ while(1){
+ e = mbox.hash[i];
+ if(e == nil)
+ return nil;
+ if(e == &dead)
+ continue;
+ if(e->hash == h && strcmp(e->messageid, msgid) == 0)
+ return e;
+ i = (i + 1) % mbox.hashsz;
+ }
+}
+
+static void
+addmesg(Mesg *m, int ins)
+{
+ Mesg *o, *e, **oldh;
+ int i, oldsz, idx;
+
+ /* add to flat list */
+ if(mbox.nmesg == mbox.mesgsz){
+ mbox.mesgsz *= 2;
+ mbox.mesg = erealloc(mbox.mesg, mbox.mesgsz*sizeof(Mesg*));
+ }
+ /*
+ * on initial load, it's faster to append everything then sort,
+ * but on subsequent messages it's better to just put it in the
+ * right place; we don't want to shuffle the already-sorted
+ * messages.
+ */
+ if(ins)
+ idx = slotfor(m);
+ else
+ idx = mbox.nmesg;
+ memmove(&mbox.mesg[idx + 1], &mbox.mesg[idx], mbox.nmesg - idx);
+ mbox.mesg[idx] = m;
+ mbox.nmesg++;
+ if(m->messageid == nil)
+ return;
+
+ /* grow hash table, or squeeze out deadwood */
+ if(mbox.hashsz <= 2*(mbox.nmesg + mbox.ndead)){
+ oldsz = mbox.hashsz;
+ oldh = mbox.hash;
+ if(mbox.hashsz <= 2*mbox.nmesg)
+ mbox.hashsz *= 2;
+ mbox.ndead = 0;
+ mbox.hash = emalloc(mbox.hashsz*sizeof(Mesg*));
+ for(i = 0; i < oldsz; i++){
+ if((o = oldh[i]) == nil)
+ continue;
+ mbox.hash[o->hash % mbox.hashsz] = o;
+ }
+ free(oldh);
+ }
+ i = m->hash % mbox.hashsz;
+ while(1){
+ e = mbox.hash[i % mbox.hashsz];
+ if(e == nil || e == &dead)
+ break;
+ i = (i + 1) % mbox.hashsz;
+ }
+ mbox.hash[i] = m;
+}
+
+static Mesg *
+placeholder(char *msgid, vlong time, int ins)
+{
+ Mesg *m;
+
+ m = emalloc(sizeof(Mesg));
+ m->flags |= Fdummy|Ftoplev;
+ m->messageid = estrdup(msgid);
+ m->hash = strhash(msgid);
+ m->time = time;
+ addmesg(m, ins);
+ return m;
+}
+
+static Mesg*
+change(char *name, char *digest)
+{
+ Mesg *m;
+ char *f;
+
+ if((m = mesglookup(name, digest)) == nil)
+ return nil;
+ if((f = rslurp(m, "flags", nil)) == nil)
+ return nil;
+ free(m->mflags);
+ m->mflags = f;
+ m->flags = Funseen;
+ if(strchr(m->mflags, 'd')) m->flags |= Fdel;
+ if(strchr(m->mflags, 's')) m->flags &= ~Funseen;
+ if(strchr(m->mflags, 'a')) m->flags |= Fresp;
+ return m;
+}
+
+static Mesg*
+delete(char *name, char *digest)
+{
+ Mesg *m;
+
+ if((m = mesglookup(name, digest)) == nil)
+ return nil;
+ m->flags |= Fdel;
+ return m;
+}
+
+static Mesg*
+load(char *name, char *digest, int ins)
+{
+ Mesg *m, *p, **c;
+ char *n;
+ int nc;
+
+ if((n = strrchr(name, '/')) == nil)
+ n = name;
+ if((m = mesgload(n)) == nil)
+ goto error;
+
+ if(digest != nil && strcmp(digest, m->digest) != 0)
+ goto error;
+ /* if we already have a dummy, populate it */
+ if((p = lookupid(m->messageid)) != nil){
+ c = p->child;
+ nc = p->nchild;
+ mesgclear(p);
+ memcpy(p, m, sizeof(*p));
+ free(m);
+
+ m = p;
+ m->child = c;
+ m->nchild = nc;
+ }else
+ addmesg(m, ins);
+
+ if(!threadsort || m->inreplyto == nil){
+ m->flags |= Ftoplev;
+ return 0;
+ }
+
+ p = lookupid(m->inreplyto);
+ if(p == nil)
+ p = placeholder(m->inreplyto, m->time, ins);
+ addchild(p, m);
+ return m;
+error:
+ fprint(2, "load failed: %r\n");
+ mesgfree(m);
+ return nil;
+}
+
+void
+mbredraw(Mesg *m, int add, int rec)
+{
+ Biobuf *bfd;
+ int ln, depth;
+
+ ln = mesglineno(m, &depth);
+ werrstr("");
+ fprint(mbox.addr, "%d%s", ln+1, add ? "-#0" : "");
+ fprint(mbox.ctl, "dot=addr\n");
+ bfd = bwindata(&mbox, OWRITE);
+ showmesg(bfd, m, depth, rec);
+ Bterm(bfd);
+}
+
+static void
+mbload(void)
+{
+ int i, n, fd;
+ Dir *d;
+
+ mbox.mesgsz = 128;
+ mbox.hashsz = 128;
+ mbox.mesg = emalloc(mbox.mesgsz*sizeof(Mesg*));
+ mbox.hash = emalloc(mbox.hashsz*sizeof(Mesg*));
+ mbox.path = esmprint("%s/%s/", maildir, mailbox);
+ cleanname(mbox.path);
+ n = strlen(mbox.path);
+ if(mbox.path[n - 1] != '/')
+ mbox.path[n] = '/';
+ if((fd = open(mbox.path, OREAD)) == -1)
+ sysfatal("%s: open: %r", mbox.path);
+ while(1){
+ n = dirread(fd, &d);
+ if(n == -1)
+ sysfatal("%s read: %r", mbox.path);
+ if(n == 0)
+ break;
+ for(i = 0; i < n; i++)
+ if(strcmp(d[i].name, "ctl") != 0)
+ load(d[i].name, nil, 0);
+ free(d);
+ }
+}
+
+static void
+showmesg(Biobuf *bfd, Mesg *m, int depth, int recurse)
+{
+ char *sep, *flag, *dots;
+ int i, width;
+
+ if(!(m->flags & Fdummy)){
+ dots = "";
+ flag = " ";
+ sep = depth ? "\t" : "";
+ width = depth ? Subjlen - 4 : Subjlen;
+ if(m->flags & Funseen) flag = "★";
+ if(m->flags & Fresp) flag = "←";
+ if(m->flags & Fdel) flag = "∉";
+ if(m->flags & Ftodel) flag = "∉";
+ if(utflen(m->subject) > Subjlen){
+ width -= 3;
+ dots = "...";
+ }
+
+ Bprint(bfd, "%-6s\t%s %s%*.*s%s\t«%s»\n",
+ m->name,
+ flag, sep, -width, width,
+ m->subject,
+ dots,
+ m->fromcolon);
+ depth++;
+ }
+ if(recurse && mbox.view != Vflat)
+ for(i = 0; i < m->nchild; i++)
+ showmesg(bfd, m->child[i], depth, recurse);
+}
+
+static void
+mark(char **f, int nf, int flags, int add)
+{
+ char *sel, *p, *q, *e;
+ int i, q0, q1;
+ Mesg *m;
+
+ wingetsel(&mbox, &q0, &q1);
+ if(nf == 0){
+ sel = winreadsel(&mbox);
+ for(p = sel; p != nil; p = e){
+ if((e = strchr(p, '\n')) != nil)
+ *e++ = 0;
+ if(!matchmesg(&mbox, p))
+ continue;
+ if((q = strchr(p, '/')) != nil)
+ q[1] = 0;
+ if((m = mesglookup(p, nil)) != nil){
+ if(add)
+ m->flags |= flags;
+ else
+ m->flags &= ~flags;
+ mbredraw(m, 0, 0);
+ }
+ }
+ free(sel);
+ }else for(i = 0; i < nf; i++){
+ if((m = mesglookup(f[i], nil)) != nil){
+ m->flags |= Ftodel;
+ mbredraw(m, 0, 0);
+ }
+ }
+ winsetsel(&mbox, q0, q1);
+}
+
+static void
+removemesg(Mesg *m)
+{
+ Mesg *c, *p;
+ int i, j;
+
+ /* remove child, preserving order */
+ j = 0;
+ p = m->parent;
+ for(i = 0; p && i < p->nchild; i++){
+ if(p->child[i] != m)
+ j++;
+ p->child[j] = p->child[i];
+ }
+
+ /* reparent children */
+ for(i = 0; i < m->nchild; i++){
+ c = m->child[i];
+ c->parent = nil;
+ if(c->parent != nil)
+ addchild(p, c);
+ else
+ c->flags |= Ftoplev;
+ }
+}
+
+static void
+mbflush(char **, int)
+{
+ Mesg *m, **p;
+ int i, j, ln;
+
+ p = mbox.mesg;
+ for(i = 0; i < mbox.nmesg; i++){
+ m = mbox.mesg[i];
+ if((m->flags & Fopen) || !(m->flags & (Fdel|Ftodel))){
+ *p++ = m;
+ continue;
+ }
+ ln = mesglineno(m, nil);
+ fprint(mbox.addr, "%d,%d", ln+1, ln+1+m->nsub);
+ write(mbox.data, "", 0);
+
+ removemesg(m);
+ removeid(m);
+ for(j = 0; j < m->nchild; j++)
+ mbredraw(m->child[j], 1, 1);
+ // if(m->flags & Ftodel)
+ // deletemesg(m);
+ mesgfree(m);
+
+ *p = m;
+ }
+}
+
+static void
+mbdelmesg(char **f, int nf)
+{
+ mark(f, nf, Ftodel, 1);
+}
+
+static void
+mbmarkmesg(char **f, int nf)
+{
+ int flg, add;
+
+ if(nf != 1)
+ return;
+ if(strlen(f[0]) != 1){
+ fprint(2, "unknown mark %s", f[0]);
+ return;
+ }
+ switch(*f[0]){
+ case 'D':
+ flg = Ftodel;
+ add = 1;
+ break;
+ case 'K':
+ flg = Ftodel;
+ add = 0;
+ break;
+ case 'U':
+ flg = Funseen;
+ add = 1;
+ break;
+ case 'R':
+ flg = Funseen;
+ add = 0;
+ break;
+ default:
+ fprint(2, "unknown mark %s", f[0]);
+ return;
+ }
+ mark(f, nf, flg, add);
+
+}
+
+static void
+mbshow(void)
+{
+ Biobuf *bfd;
+ Mesg *m;
+ int i;
+
+ bfd = bwinopen(&mbox, "body", OWRITE);
+ for(i = 0; i < mbox.nmesg; i++){
+ m = mbox.mesg[i];
+ if(mbox.view == Vflat || m->flags & (Fdummy|Ftoplev))
+ showmesg(bfd, m, 0, 1);
+ }
+ Bterm(bfd);
+}
+
+static void
+mbquit(char **, int)
+{
+ if(mbox.nopen == 0)
+ threadexitsall(nil);
+ fprint(2, "Del: %d open messages", mbox.nopen);
+}
+
+static void
+changemesg(Plumbmsg *pm)
+{
+ char *digest, *action;
+ Mesg *m;
+ int add;
+
+ m = nil;
+ add = 0;
+
+ digest = plumblookup(pm->attr, "digest");
+ action = plumblookup(pm->attr, "mailtype");
+ fprint(2, "changing message %s, %s %s\n", action, pm->data, digest);
+ if(strcmp(action, "new") == 0){
+ m = load(pm->data, digest, 1);
+ add = 1;
+ }else if(strcmp(action, "delete") == 0)
+ m = delete(pm->data, digest);
+ else if(strcmp(action, "modify") == 0)
+ m = change(pm->data, digest);
+ if(m == nil)
+ return;
+ mbredraw(m, add, 0);
+}
+
+static void
+viewmesg(Plumbmsg *pm)
+{
+ mesgopen(pm->data, plumblookup(pm->attr, "digest"));
+}
+
+Fn mboxfn[] = {
+ {"Put", mbflush},
+ {"Delmesg", mbdelmesg},
+ {"Mark", mbmarkmesg},
+ {"Del", mbquit},
+#ifdef NOTYET
+ {"Get", mbrefresh},
+ {"Sort", mbsort},
+ {"Next", mboxnext},
+#endif
+ {nil}
+};
+
+
+static void
+doevent(Event *ev)
+{
+ char *a, *f[32];
+ int nf;
+ Fn *p;
+
+ if(ev->action != 'M')
+ return;
+ switch(ev->type){
+ case 'l':
+ case 'L':
+ if((a = matchaddr(&mbox, ev)) != nil)
+ compose1(a, nil, 0);
+ else if(matchmesg(&mbox, ev->text))
+ mesgopen(ev->text, nil);
+ else
+ winsendevent(&mbox, ev);
+ free(a);
+ break;
+ case 'x':
+ case 'X':
+ if((nf = tokenize(ev->text, f, nelem(f))) == 0)
+ return;
+ for(p = mboxfn; p->name != nil; p++)
+ if(strcmp(p->name, f[0]) == 0 && p->fn != nil)
+ p->fn(&f[1], nf - 1);
+ if(p->fn == nil && !(ev->flags & 0x2))
+ winsendevent(&mbox, ev);
+ break;
+ break;
+ }
+}
+
+static void
+mbmain(void *)
+{
+ Event *ev;
+ Plumbmsg *pm;
+ Alt a[] = {
+ [Cevent] = {mbox.event, &ev, CHANRCV},
+ [Cseemail] = {mbox.see, &pm, CHANRCV},
+ [Cshowmail] = {mbox.show, &pm, CHANRCV},
+ [Nchan] = {nil, nil, CHANEND},
+ };
+
+ wininit(&mbox, mbox.path);
+ wintagwrite(&mbox, "Put Mail Delmesg Save Next ");
+ mbshow();
+ fprint(2, "shown\n");
+ fprint(mbox.ctl, "clean\n");
+ proccreate(eventread, nil, Stack);
+ fprint(2, "started\n");
+ while(1){
+ switch(alt(a)){
+ case Cevent:
+ doevent(ev);
+ free(ev);
+ break;
+ case Cseemail:
+ changemesg(pm);
+ plumbfree(pm);
+ break;
+ case Cshowmail:
+ viewmesg(pm);
+ plumbfree(pm);
+ break;
+ }
+ }
+}
+
+static void
+usage(void)
+{
+ fprint(2, "usage: %s [-T] [-f mailfs] [mbox]\n", argv0);
+ exits("usage");
+}
+
+void
+threadmain(int argc, char **argv)
+{
+ mbox.view = Vgroup;
+
+ ARGBEGIN{
+ case 'f':
+ maildir = EARGF(usage());
+ break;
+ case 'T':
+ mbox.view = Vflat;
+ break;
+ default:
+ usage();
+ break;
+ }ARGEND;
+
+ doquote = needsrcquote;
+ quotefmtinstall();
+ tmfmtinstall();
+ /* open these early so we won't miss notification of new mail messages while we read mbox */
+ plumbsendfd = plumbopen("send", OWRITE|OCEXEC);
+ plumbseemailfd = plumbopen("seemail", OREAD|OCEXEC);
+ plumbshowmailfd = plumbopen("showmail", OREAD|OCEXEC);
+ mbox.event = chancreate(sizeof(Event*), 1);
+ mbox.see = chancreate(sizeof(Plumbmsg*), 1);
+ mbox.show = chancreate(sizeof(Plumbmsg*), 1);
+
+ addrpat = regcomp("[^ \t]*@[^ \t]*\\.[^ \t]*");
+ mesgpat = regcomp("(\\(deleted\\)-)?[0-9]+/.*");
+ cwait = threadwaitchan();
+
+ if(argc > 1)
+ usage();
+ if(argc == 1)
+ mailbox = argv[0];
+ mbload();
+ mbsort();
+
+ threadcreate(mbmain, nil, Stack);
+ proccreate(plumbsee, nil, Stack);
+ proccreate(plumbshow, nil, Stack);
+// threadexitsall(nil);
+}
--- /dev/null
+++ b/mesg.c
@@ -1,0 +1,323 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <regexp.h>
+
+#include "mail.h"
+
+#define Datefmt "?WWW, ?MMM ?DD hh:mm:ss ?Z YYYY"
+
+typedef struct Fn Fn;
+
+struct Fn {
+ char *name;
+ void (*fn)(Mesg *, char **, int);
+};
+
+void
+mesgclear(Mesg *m)
+{
+ free(m->name);
+ free(m->from);
+ free(m->to);
+ free(m->cc);
+ free(m->replyto);
+ free(m->date);
+ free(m->subject);
+ free(m->type);
+ free(m->disposition);
+ free(m->messageid);
+ free(m->filename);
+ free(m->digest);
+ free(m->mflags);
+ free(m->fromcolon);
+}
+
+void
+mesgfree(Mesg *m)
+{
+ if(m == nil)
+ return;
+ mesgclear(m);
+ free(m);
+}
+
+static char*
+line(char *data, char **pp)
+{
+ char *p, *q;
+
+ for(p=data; *p!='\0' && *p!='\n'; p++)
+ ;
+ if(*p == '\n')
+ *pp = p+1;
+ else
+ *pp = p;
+ if(p == data)
+ return nil;
+ q = emalloc(p-data + 1);
+ memmove(q, data, p-data);
+ return q;
+}
+
+static char*
+fc(Mesg *m, char *s)
+{
+ char *r;
+
+ if(s != nil && m->from != nil){
+ r = smprint("%s <%s>", s, m->from);
+ free(s);
+ return r;
+ }
+ if(m->from != nil)
+ return estrdup(m->from);
+ if(s != nil)
+ return s;
+ return estrdup("??");
+}
+
+Mesg*
+mesgload(char *name)
+{
+ char *info, *p;
+ int ninfo;
+ Mesg *m;
+ Tm tm;
+
+ m = emalloc(sizeof(Mesg));
+ m->name = estrjoin(name, "/", nil);
+ if((info = rslurp(m, "info", &ninfo)) == nil)
+ return nil;
+
+ p = info;
+ m->from = line(p, &p);
+ m->to = line(p, &p);
+ m->cc = line(p, &p);
+ m->replyto = line(p, &p);
+ m->date = line(p, &p);
+ m->subject = line(p, &p);
+ m->type = line(p, &p);
+ m->disposition = line(p, &p);
+ m->filename = line(p, &p);
+ m->digest = line(p, &p);
+ /* m->bcc = */ free(line(p, &p));
+ m->inreplyto = line(p, &p);
+ /* m->date = */ free(line(p, &p));
+ /* m->sender = */ free(line(p, &p));
+ m->messageid = line(p, &p);
+ /* m->lines = */ free(line(p, &p));
+ /* m->size = */ free(line(p, &p));
+ m->mflags = line(p, &p);
+ /* m->fileid = */ free(line(p, &p));
+ m->fromcolon = fc(m, line(p, &p));
+ free(info);
+
+ m->flags = Funseen;
+ if(strchr(m->mflags, 'd')) m->flags |= Fdel;
+ if(strchr(m->mflags, 's')) m->flags &= ~Funseen;
+ if(strchr(m->mflags, 'a')) m->flags |= Fresp;
+
+ m->time = time(nil);
+ if(tmparse(&tm, Datefmt, m->date, nil, nil) != nil)
+ m->time = tmnorm(&tm);
+ m->hash = 0;
+ if(m->messageid != nil)
+ m->hash = strhash(m->messageid);
+ return m;
+}
+
+static int
+mesgshow(Mesg *m)
+{
+ int rfd, wfd;
+ char *buf, *path;
+ int n;
+
+ buf = emalloc(Bufsz);
+ path = estrjoin(mbox.path, m->name, "body", nil);
+ if((wfd = winopen(m, "body", OWRITE)) == -1)
+ return -1;
+ if((rfd = open(path, OREAD)) == -1)
+ return -1;
+ fprint(wfd, "From: %s\n", m->from);
+ fprint(wfd, "Date: %s\n", m->to);
+ fprint(wfd, "Subject: %s\n\n", m->subject);
+ while(1){
+ n = read(rfd, buf, Bufsz);
+ if(n <= 0)
+ break;
+ if(write(wfd, buf, n) != n)
+ break;
+ }
+ close(rfd);
+ close(wfd);
+ free(buf);
+ free(path);
+ return n;
+}
+
+static void
+reply(Mesg *m, char **f, int nf)
+{
+ if(nf >= 2
+ || nf >= 1 && strcmp(f[0], "All") != 0){
+ fprint(2, "Q: invaid args\n");
+ return;
+ }
+
+ /* FIXME: get all recievers of the message */
+ compose1(m->from, m, 0);
+}
+
+static void
+qreply(Mesg *m, char **f, int nf)
+{
+ if(nf >= 3
+ || nf >= 2 && strcmp(f[1], "All") != 0
+ || nf >= 1 && strcmp(f[0], "Reply") != 0
+ || nf == 0){
+ fprint(2, "Q: invaid args\n");
+ return;
+ }
+
+ /* FIXME: get all recievers of the message */
+ compose1(m->from, m, 1);
+}
+
+static void
+mesgquit(Mesg *m, char **, int)
+{
+ m->quitting = 1;
+}
+
+static Fn mesgfn[] = {
+ {"Q", qreply},
+ {"Reply", reply},
+// {"Delmesg", delmesg},
+ {"Save", nil},
+ {"Del", mesgquit},
+ {nil}
+};
+
+static void
+mesgmain(void *mp)
+{
+ char *a, *path, *f[32];
+ Event ev;
+ Mesg *m;
+ Fn *p;
+ int nf;
+
+ m = mp;
+ m->quitting = 0;
+
+ path = estrjoin(mbox.path, m->name, nil);
+ wininit(m, path);
+ free(path);
+
+ wintagwrite(m, "Q Reply all Delmesg Save ");
+ mesgshow(m);
+ fprint(m->ctl, "clean\n");
+ mbox.nopen++;
+ while(!m->quitting){
+ if(winevent(m, &ev) != 'M')
+ continue;
+ fprint(2, "%s\n", ev.text);
+ if(strcmp(ev.text, "Del") == 0)
+ break;
+ switch(ev.type){
+ case 'l':
+ case 'L':
+ if((a = matchaddr(m, &ev)) != nil)
+ compose1(a, nil, 0);
+ else if(matchmesg(m, ev.text))
+ mesgopen(ev.text, nil);
+ else
+ winsendevent(m, &ev);
+ free(a);
+ break;
+ case 'x':
+ case 'X':
+ if((nf = tokenize(ev.text, f, nelem(f))) == 0)
+ continue;
+ for(p = mesgfn; p->name != nil; p++){
+ if(strcmp(p->name, f[0]) == 0 && p->fn != nil){
+ p->fn(m, &f[1], nf - 1);
+ break;
+ }
+ }
+ if(p->fn == nil)
+ winsendevent(m, &ev);
+ break;
+ }
+ }
+ mbox.nopen--;
+ m->flags &= ~Fopen;
+ winclose(m);
+ threadexits(nil);
+}
+
+void
+mesgpath2name(char *buf, int nbuf, char *name)
+{
+ char *e;
+ int n;
+
+ n = strlen(mbox.path);
+ if(strncmp(name, mbox.path, n) == 0){
+ e = strecpy(buf, buf + nbuf - 2, name + n);
+ e[0] = '/';
+ e[1] = 0;
+ }else
+ strecpy(buf, buf+nbuf, name);
+}
+
+int
+mesgmatch(Mesg *m, char *name, char *digest)
+{
+ if(m->flags & Fdummy)
+ return 0;
+ if(strcmp(m->name, name) == 0)
+ return digest == nil || strcmp(m->digest, digest) == 0;
+ return 0;
+}
+
+Mesg*
+mesglookup(char *name, char *digest)
+{
+ char buf[32];
+ int i;
+
+ mesgpath2name(buf, sizeof(buf), name);
+ for(i = 0; i < mbox.nmesg; i++)
+ if(mesgmatch(mbox.mesg[i], buf, digest))
+ return mbox.mesg[i];
+ return nil;
+}
+
+void
+mesgopen(char *name, char *digest)
+{
+ Mesg *m;
+ char *path;
+ int fd;
+
+ m = mesglookup(name, digest);
+ if(m == nil || (m->flags & Fopen))
+ return;
+ assert(!(m->flags & Fdummy));
+ m->flags |= Fopen;
+ if(m->flags & Funseen){
+ m->flags &= ~Funseen;
+ path = estrjoin(mbox.path, "/", m->name, "/flags", nil);
+ if((fd = open(path, OWRITE)) != -1){
+ fprint(fd, "+s");
+ close(fd);
+ }
+ mbredraw(m, 0, 0);
+ free(path);
+ }
+ threadcreate(mesgmain, m, Stack);
+}
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,15 @@
+</$objtype/mkfile
+
+TARG=Nail
+OFILES=\
+ mbox.$O\
+ mesg.$O\
+ comp.$O\
+ util.$O\
+ win.$O
+
+HFILES=mail.h
+
+BIN=/acme/bin/$objtype
+
+</sys/src/cmd/mkone
--- /dev/null
+++ b/util.c
@@ -1,0 +1,140 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <regexp.h>
+
+#include "mail.h"
+
+void *
+emalloc(ulong n)
+{
+ void *v;
+
+ v = mallocz(n, 1);
+ if(v == nil)
+ sysfatal("malloc: %r");
+ setmalloctag(v, getcallerpc(&n));
+ return v;
+}
+
+void *
+erealloc(void *p, ulong n)
+{
+ void *v;
+
+ v = realloc(p, n);
+ if(v == nil)
+ sysfatal("realloc: %r");
+ setmalloctag(v, getcallerpc(&p));
+ return v;
+}
+
+char*
+estrdup(char *s)
+{
+ s = strdup(s);
+ if(s == nil)
+ sysfatal("strdup: %r");
+ setmalloctag(s, getcallerpc(&s));
+ return s;
+}
+
+char*
+estrjoin(char *s, ...)
+{
+ va_list ap;
+ char *r, *t, *p, *e;
+ int n;
+
+ va_start(ap, s);
+ n = strlen(s) + 1;
+ while((p = va_arg(ap, char*)) != nil)
+ n += strlen(p);
+ va_end(ap);
+
+ r = emalloc(n);
+ e = r + n;
+ va_start(ap, s);
+ t = strecpy(r, e, s);
+ while((p = va_arg(ap, char*)) != nil)
+ t = strecpy(t, e, p);
+ va_end(ap);
+ return r;
+}
+
+char*
+esmprint(char *fmt, ...)
+{
+ char *s;
+ va_list ap;
+
+ va_start(ap, fmt);
+ s = vsmprint(fmt, ap);
+ va_end(ap);
+ if(s == nil)
+ sysfatal("smprint: %r");
+ setmalloctag(s, getcallerpc(&fmt));
+ return s;
+}
+
+char*
+fslurp(int fd, int *nbuf)
+{
+ int n, sz, r;
+ char *buf;
+
+ n = 0;
+ sz = 128;
+ buf = emalloc(sz);
+ while(1){
+ r = read(fd, buf + n, sz - n);
+ if(r == 0)
+ break;
+ if(r == -1)
+ goto error;
+ n += r;
+ if(n == sz){
+ sz += sz/2;
+ buf = erealloc(buf, sz);
+ }
+ }
+ buf[n] = 0;
+ if(nbuf)
+ *nbuf = n;
+ return buf;
+error:
+ free(buf);
+ return nil;
+}
+
+char *
+rslurp(Mesg *m, char *f, int *nbuf)
+{
+ char *path;
+ int fd;
+ char *r;
+
+ if(m == nil)
+ path = estrjoin(mbox.path, "/", f, nil);
+ else
+ path = estrjoin(mbox.path, "/", m->name, "/", f, nil);
+ fd = open(path, OREAD);
+ free(path);
+ if(fd == -1)
+ return nil;
+ r = fslurp(fd, nbuf);
+ close(fd);
+ return r;
+}
+
+u32int
+strhash(char *s)
+{
+ u32int h, c;
+
+ h = 5381;
+ while(c = *s++ & 0xff)
+ h = ((h << 5) + h) + c;
+ return h;
+}
--- /dev/null
+++ b/win.c
@@ -1,0 +1,308 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <regexp.h>
+
+#include "mail.h"
+
+static int
+procrd(Biobufhdr *f, void *buf, long len)
+{
+ return ioread(f->aux, f->fid, buf, len);
+}
+
+static int
+procwr(Biobufhdr *f, void *buf, long len)
+{
+ return iowrite(f->aux, f->fid, buf, len);
+}
+
+/*
+ * NB: this function consumes integers with
+ * a trailing space, as generated by acme;
+ * it's not a general purpose number parsing
+ * function.
+ */
+static int
+evgetnum(Biobuf *f)
+{
+ int c, n;
+
+ n = 0;
+ while('0'<=(c=Bgetc(f)) && c<='9')
+ n = n*10+(c-'0');
+ if(c != ' ')
+ sysfatal("event number syntax: %c(%d)", c, c);
+ return n;
+}
+
+static int
+evgetdata(Biobuf *f, Event *e)
+{
+ int i, n, o;
+ Rune r;
+
+ o = 0;
+ n = evgetnum(f);
+ for(i = 0; i < n; i++){
+ if((r = Bgetrune(f)) == -1)
+ break;
+ o += runetochar(e->text + o, &r);
+ }
+ e->text[o] = 0;
+ return o;
+}
+
+int
+winevent(Win *w, Event *e)
+{
+ e->action = Bgetc(w->event);
+ e->type = Bgetc(w->event);
+ e->q0 = evgetnum(w->event);
+ e->q1 = evgetnum(w->event);
+ e->flags = evgetnum(w->event);
+ e->ntext = evgetdata(w->event, e);
+ if(Bgetc(w->event) != '\n')
+ sysfatal("unterminated message");
+ return e->action;
+}
+
+void
+winsendevent(Win *w, Event *e)
+{
+ Bprint(w->event, "%c%c%d %d\n", e->action, e->type, e->q0, e->q1);
+ Bflush(w->event);
+}
+
+int
+winopen(Win *w, char *f, int mode)
+{
+ char buf[128];
+ int fd;
+
+ snprint(buf, sizeof(buf), "/mnt/wsys/%d/%s", w->id, f);
+ if((fd = open(buf, mode|OCEXEC)) == -1)
+ sysfatal("open %s: %r", buf);
+ return fd;
+}
+
+Biobuf*
+bwinopen(Win *w, char *f, int mode)
+{
+ char buf[128];
+ Biobuf *bfd;
+
+ snprint(buf, sizeof(buf), "/mnt/wsys/%d/%s", w->id, f);
+ if((bfd = Bopen(buf, mode|OCEXEC)) == nil)
+ sysfatal("open %s: %r", buf);
+ bfd->aux = w->io;
+ Biofn(bfd, (mode == OREAD)?procrd:procwr);
+ return bfd;
+}
+
+Biobuf*
+bwindata(Win *w, int mode)
+{
+ int fd;
+
+ if((fd = dup(w->data, -1)) == -1)
+ sysfatal("dup: %r");
+ return Bfdopen(fd, mode);
+}
+
+void
+wininit(Win *w, char *name)
+{
+ char buf[12];
+
+ w->ctl = open("/mnt/wsys/new/ctl", ORDWR|OCEXEC);
+ if(w->ctl < 0)
+ sysfatal("winopen: %r");
+ if(read(w->ctl, buf, 12)!=12)
+ sysfatal("read ctl: %r");
+ if(fprint(w->ctl, "name %s\n", name) == -1)
+ sysfatal("write ctl: %r");
+ if(fprint(w->ctl, "noscroll\n") == -1)
+ sysfatal("write ctl: %r");
+ if((w->io = ioproc()) == nil)
+ sysfatal("ioproc alloc: %r");
+ w->id = atoi(buf);
+ w->event = bwinopen(w, "event", OREAD);
+ w->addr = winopen(w, "addr", ORDWR);
+ w->data = winopen(w, "data", ORDWR);
+}
+
+void
+winclose(Win *w)
+{
+ fprint(w->ctl, "del\n");
+ if(w->data != -1)
+ close(w->data);
+ if(w->addr != -1)
+ close(w->addr);
+ if(w->event != nil)
+ Bterm(w->event);
+ if(w->io)
+ closeioproc(w->io);
+ if(w->ctl != -1)
+ close(w->ctl);
+}
+
+void
+wintagwrite(Win *w, char *s)
+{
+ int fd, n;
+
+ n = strlen(s);
+ fd = winopen(w, "tag", OWRITE);
+ if(write(fd, s, n) != n)
+ sysfatal("tag write: %r");
+ close(fd);
+}
+
+int
+wineval(Win *w, char *s, ...)
+{
+ char buf[25];
+ va_list arg;
+
+ va_start(arg, s);
+ vfprint(w->addr, s, arg);
+ va_end(arg);
+ if(pread(w->addr, buf, 24, 0) != 24)
+ return -1;
+ buf[24] = 0;
+ return strtol(buf, nil, 10);
+}
+
+int
+winread(Win *w, int q0, int q1, char *data, int ndata)
+{
+ int m, n, nr;
+ char *buf;
+
+ m = q0;
+ buf = emalloc(Bufsz);
+ while(m < q1){
+ n = sprint(buf, "#%d", m);
+ if(write(w->addr, buf, n) != n){
+ fprint(2, "error writing addr: %r");
+ goto err;
+ }
+ n = read(w->data, buf, Bufsz);
+ if(n <= 0){
+ fprint(2, "reading data: %r");
+ goto err;
+ }
+ nr = utfnlen(buf, n);
+ while(m+nr >q1){
+ do; while(n>0 && (buf[--n]&0xC0)==0x80);
+ --nr;
+ }
+ if(n == 0 || n > ndata)
+ break;
+ memmove(data, buf, n);
+ ndata -= n;
+ data += n;
+ *data = 0;
+ m += nr;
+ }
+ free(buf);
+ return 0;
+err:
+ free(buf);
+ return -1;
+}
+
+char*
+winreadsel(Win *w)
+{
+ int n, q0, q1;
+ char *r;
+
+ wingetsel(w, &q0, &q1);
+ n = UTFmax*(q1-q0);
+ r = emalloc(n + 1);
+ if(winread(w, q0, q1, r, n) == -1){
+ free(r);
+ return nil;
+ }
+ return r;
+}
+
+void
+wingetsel(Win *w, int *q0, int *q1)
+{
+ char *e, buf[25];
+
+ fprint(w->ctl, "addr=dot");
+ if(pread(w->addr, buf, 24, 0) != 24)
+ sysfatal("read addr: %r");
+ buf[24] = 0;
+ *q0 = strtol(buf, &e, 10);
+ *q1 = strtol(e, nil, 10);
+}
+
+void
+winsetsel(Win *w, int q0, int q1)
+{
+ fprint(w->addr, "#%d,#%d", q0, q1);
+ fprint(w->ctl, "dot=addr");
+}
+
+static char*
+expandaddr(Win *w, Event *e)
+{
+ static char *delim = "/[ \t\\n<>()\\[\\]]/";
+ char *s;
+ int q0, q1, ns;
+
+ if(e->q0 != e->q1)
+ return nil;
+
+ q0 = wineval(w, "#%d-%s", e->q0, delim);
+ if(q0 == -1) /* bad char not found */
+ q0 = 0;
+ else /* increment past bad char */
+ q0++;
+
+ q1 = wineval(w, "#%d+%s", e->q0, delim);
+ if(q1 < 0){
+ q1 = wineval(w, "$");
+ if(q1 < 0)
+ return nil;
+ }
+ if(q0 >= q1)
+ return nil;
+ ns = (q1-q0)*UTFmax+1;
+ s = emalloc(ns);
+ winread(w, q0, q1, s, ns);
+ return s;
+}
+
+char*
+matchaddr(Win *w, Event *e)
+{
+ char *s;
+
+ if((s = expandaddr(w, e)) != nil)
+ if(regexec(addrpat, s, nil, 0))
+ return s;
+ return nil;
+}
+
+int
+matchmesg(Win *, char *text)
+{
+ char *p;
+
+ if(strncmp(text, mbox.path, strlen(mbox.path)) == 0)
+ return 1;
+ if(regexec(mesgpat, text, nil, 0)){
+ if((p = strchr(text, '/')) != nil)
+ p[1] = 0;
+ return 1;
+ }
+ return 0;
+}