tinitial faces (John Cummings) - plan9port - [fork] Plan 9 from user space
 (HTM) git clone git://src.adamsgaard.dk/plan9port
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit b330c942b468ab82fd8853590145187e859258cb
 (DIR) parent 663ddde9d07417ab51239c0c4305708a1a319c62
 (HTM) Author: rsc <devnull@localhost>
       Date:   Mon, 31 Oct 2005 14:47:39 +0000
       
       initial faces (John Cummings)
       
       Diffstat:
         A src/cmd/faces/dblook.c              |      30 ++++++++++++++++++++++++++++++
         A src/cmd/faces/facedb.c              |     562 +++++++++++++++++++++++++++++++
         A src/cmd/faces/faces.h               |      68 +++++++++++++++++++++++++++++++
         A src/cmd/faces/main.c                |     783 ++++++++++++++++++++++++++++++
         A src/cmd/faces/mkfile                |      26 ++++++++++++++++++++++++++
         A src/cmd/faces/plumb.c               |     398 +++++++++++++++++++++++++++++++
         A src/cmd/faces/util.c                |      42 +++++++++++++++++++++++++++++++
       
       7 files changed, 1909 insertions(+), 0 deletions(-)
       ---
 (DIR) diff --git a/src/cmd/faces/dblook.c b/src/cmd/faces/dblook.c
       t@@ -0,0 +1,30 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <draw.h>
       +#include <plumb.h>
       +#include <regexp.h>
       +#include <bio.h>
       +#include <9pclient.h>
       +#include <thread.h>
       +#include "faces.h"
       +
       +void
       +threadmain(int argc, char **argv)
       +{
       +        Face f;
       +        char *q;
       +
       +        if(argc != 3){
       +                fprint(2, "usage: dblook name domain\n");
       +                threadexitsall("usage");
       +        }
       +
       +        q = findfile(&f, argv[2], argv[1]);
       +        print("%s\n", q);
       +}
       +
       +void
       +killall(char *s)
       +{
       +        USED(s);
       +}
 (DIR) diff --git a/src/cmd/faces/facedb.c b/src/cmd/faces/facedb.c
       t@@ -0,0 +1,562 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <draw.h>
       +#include <plumb.h>
       +#include <regexp.h>
       +#include <bio.h>
       +#include <9pclient.h>
       +#include "faces.h"
       +
       +enum        /* number of deleted faces to cache */
       +{
       +        Nsave        = 20,
       +};
       +
       +static Facefile        *facefiles;
       +static int                nsaved;
       +static char        *facedom;
       +
       +/*
       + * Loading the files is slow enough on a dial-up line to be worth this trouble
       + */
       +typedef struct Readcache        Readcache;
       +struct Readcache {
       +        char *file;
       +        char *data;
       +        long mtime;
       +        long rdtime;
       +        Readcache *next;
       +};
       +
       +static Readcache *rcache;
       +
       +ulong
       +dirlen(char *s)
       +{
       +        Dir *d;
       +        ulong len;
       +
       +        d = dirstat(s);
       +        if(d == nil)
       +                return 0;
       +        len = d->length;
       +        free(d);
       +        return len;
       +}
       +
       +ulong
       +fsdirlen(CFsys *fs,char *s)
       +{
       +        Dir *d;
       +        ulong len;
       +
       +        d = fsdirstat(fs,s);
       +        if(d == nil)
       +                return 0;
       +        len = d->length;
       +        free(d);
       +        return len;
       +}
       +
       +ulong
       +dirmtime(char *s)
       +{
       +        Dir *d;
       +        ulong t;
       +
       +        d = dirstat(s);
       +        if(d == nil)
       +                return 0;
       +        t = d->mtime;
       +        free(d);
       +        return t;
       +}
       +
       +static char*
       +doreadfile(char *s)
       +{
       +        char *p;
       +        int fd, n;
       +        ulong len;
       +
       +        len = dirlen(s);
       +        if(len == 0)
       +                return nil;
       +
       +        p = malloc(len+1);
       +        if(p == nil)
       +                return nil;
       +
       +        if((fd = open(s, OREAD)) < 0
       +        || (n = readn(fd, p, len)) < 0) {
       +                close(fd);
       +                free(p);
       +                return nil;
       +        }
       +
       +        p[n] = '\0';
       +        return p;
       +}
       +
       +static char*
       +readfile(char *s)
       +{
       +        Readcache *r, **l;
       +        char *p;
       +        ulong mtime;
       +
       +        for(l=&rcache, r=*l; r; l=&r->next, r=*l) {
       +                if(strcmp(r->file, s) != 0)
       +                        continue;
       +
       +                /*
       +                 * if it's less than 30 seconds since we read it, or it 
       +                 * hasn't changed, send back our copy
       +                 */
       +                if(time(0) - r->rdtime < 30)
       +                        return strdup(r->data);
       +                if(dirmtime(s) == r->mtime) {
       +                        r->rdtime = time(0);
       +                        return strdup(r->data);
       +                }
       +
       +                /* out of date, remove this and fall out of loop */
       +                *l = r->next;
       +                free(r->file);
       +                free(r->data);
       +                free(r);
       +                break;
       +        }
       +
       +        /* add to cache */
       +        mtime = dirmtime(s);
       +        if(mtime == 0)
       +                return nil;
       +
       +        if((p = doreadfile(s)) == nil)
       +                return nil;
       +
       +        r = malloc(sizeof(*r));
       +        if(r == nil)
       +                return nil;
       +        r->mtime = mtime;
       +        r->file = estrdup(s);
       +        r->data = p;
       +        r->rdtime = time(0);
       +        r->next = rcache;
       +        rcache = r;
       +        return strdup(r->data);
       +}
       +
       +
       +static char*
       +translatedomain(char *dom)
       +{
       +        static char buf[200];
       +        char *p, *ep, *q, *nextp, *file;
       +        char *bbuf, *ebuf;
       +        Reprog *exp;
       +
       +        if(dom == nil || *dom == 0)
       +                return nil;
       +
       +        if((file = readfile(unsharp("#9/lib/face/.machinelist"))) == nil)
       +                return dom;
       +
       +        for(p=file; p; p=nextp) {
       +                if(nextp = strchr(p, '\n'))
       +                        *nextp++ = '\0';
       +
       +                if(*p == '#' || (q = strpbrk(p, " \t")) == nil || q-p > sizeof(buf)-2)
       +                        continue;
       +
       +                bbuf = buf+1;
       +                ebuf = buf+(1+(q-p));
       +                strncpy(bbuf, p, ebuf-bbuf);
       +                *ebuf = 0;
       +                if(*bbuf != '^')
       +                        *--bbuf = '^';
       +                if(ebuf[-1] != '$') {
       +                        *ebuf++ = '$';
       +                        *ebuf = 0;
       +                }
       +
       +                if((exp = regcomp(bbuf)) == nil){
       +                        fprint(2, "bad regexp in machinelist: %s\n", bbuf);
       +                        killall("regexp");
       +                }
       +
       +                if(regexec(exp, dom, 0, 0)){
       +                        free(exp);
       +                        ep = p+strlen(p);
       +                        q += strspn(q, " \t");
       +                        if(ep-q+2 > sizeof buf) {
       +                                fprint(2, "huge replacement in machinelist: %.*s\n", utfnlen(q, ep-q), q);
       +                                exits("bad big replacement");
       +                        }
       +                        strncpy(buf, q, ep-q);
       +                        ebuf = buf+(ep-q);
       +                        *ebuf = 0;
       +                        while(ebuf > buf && (ebuf[-1] == ' ' || ebuf[-1] == '\t'))
       +                                *--ebuf = 0;
       +                        free(file);
       +                        return buf;
       +                }
       +                free(exp);
       +        }
       +        free(file);
       +
       +        return dom;
       +}
       +
       +static char*
       +tryfindpicture_user(char *dom, char *user, int depth)
       +{
       +        static char buf[200];
       +        char *p, *q, *nextp, *file, *usr;
       +        usr = getuser();
       +
       +        sprint(buf, "/usr/%s/lib/face/48x48x%d/.dict", usr, depth);
       +        if((file = readfile(buf)) == nil)
       +                return nil;
       +
       +        snprint(buf, sizeof buf, "%s/%s", dom, user);
       +
       +        for(p=file; p; p=nextp) {
       +                if(nextp = strchr(p, '\n'))
       +                        *nextp++ = '\0';
       +
       +                if(*p == '#' || (q = strpbrk(p, " \t")) == nil)
       +                        continue;
       +                *q++ = 0;
       +
       +                if(strcmp(buf, p) == 0) {
       +                        q += strspn(q, " \t");
       +                        q = buf+snprint(buf, sizeof buf, "/usr/%s/lib/face/48x48x%d/%s", usr, depth, q);
       +                        while(q > buf && (q[-1] == ' ' || q[-1] == '\t'))
       +                                *--q = 0;
       +                        free(file);
       +                        return buf;
       +                }
       +        }
       +        free(file);
       +        return nil;                        
       +}
       +
       +static char*
       +tryfindpicture_global(char *dom, char *user, int depth)
       +{
       +        static char buf[200];
       +        char *p, *q, *nextp, *file;
       +
       +        sprint(buf, "#9/lib/face/48x48x%d/.dict", depth);
       +        if((file = readfile(unsharp(buf))) == nil)
       +                return nil;
       +
       +        snprint(buf, sizeof buf, "%s/%s", dom, user);
       +
       +        for(p=file; p; p=nextp) {
       +                if(nextp = strchr(p, '\n'))
       +                        *nextp++ = '\0';
       +
       +                if(*p == '#' || (q = strpbrk(p, " \t")) == nil)
       +                        continue;
       +                *q++ = 0;
       +
       +                if(strcmp(buf, p) == 0) {
       +                        q += strspn(q, " \t");
       +                        q = buf+snprint(buf, sizeof buf, "#9/lib/face/48x48x%d/%s", depth, q);
       +                        while(q > buf && (q[-1] == ' ' || q[-1] == '\t'))
       +                                *--q = 0;
       +                        free(file);
       +                        return unsharp(buf);
       +                }
       +        }
       +        free(file);
       +        return nil;                        
       +}
       +
       +static char*
       +tryfindpicture(char *dom, char *user, int depth)
       +{
       +        char* result;
       +
       +        if((result = tryfindpicture_user(dom, user, depth)) != nil)
       +                return result;
       +
       +        return tryfindpicture_global(dom, user, depth);
       +}
       +
       +static char*
       +tryfindfile(char *dom, char *user, int depth)
       +{
       +        char *p, *q;
       +
       +        for(;;){
       +                for(p=dom; p; (p=strchr(p, '.')) && p++)
       +                        if(q = tryfindpicture(p, user, depth))
       +                                return q;
       +                depth >>= 1;
       +                if(depth == 0)
       +                        break;
       +        }
       +        return nil;
       +}
       +
       +char*
       +findfile(Face *f, char *dom, char *user)
       +{
       +        char *p;
       +        int depth;
       +
       +        if(facedom == nil){
       +                facedom = getenv("facedom");
       +                if(facedom == nil)
       +                        facedom = DEFAULT;
       +        }
       +
       +        dom = translatedomain(dom);
       +        if(dom == nil)
       +                dom = facedom;
       +
       +        if(screen == nil)
       +                depth = 8;
       +        else
       +                depth = screen->depth;
       +
       +        if(depth > 8)
       +                depth = 8;
       +
       +        f->unknown = 0;
       +        if(p = tryfindfile(dom, user, depth))
       +                return p;
       +        f->unknown = 1;
       +        p = tryfindfile(dom, "unknown", depth);
       +        if(p != nil || strcmp(dom, facedom)==0)
       +                return p;
       +        return tryfindfile("unknown", "unknown", depth);
       +}
       +
       +static
       +void
       +clearsaved(void)
       +{
       +        Facefile *f, *next, **lf;
       +
       +        lf = &facefiles;
       +        for(f=facefiles; f!=nil; f=next){
       +                next = f->next;
       +                if(f->ref > 0){
       +                        *lf = f;
       +                        lf = &(f->next);
       +                        continue;
       +                }
       +                if(f->image != display->black && f->image != display->white)
       +                        freeimage(f->image);
       +                free(f->file);
       +                free(f);
       +        }
       +        *lf = nil;
       +        nsaved = 0;
       +}
       +
       +void
       +freefacefile(Facefile *f)
       +{
       +        if(f==nil || f->ref-->1)
       +                return;
       +        if(++nsaved > Nsave)
       +                clearsaved();
       +}        
       +
       +static Image*
       +myallocimage(ulong chan)
       +{
       +        Image *img;
       +        img = allocimage(display, Rect(0,0,Facesize,Facesize), chan, 0, DNofill);
       +        if(img == nil){
       +                clearsaved();
       +                img = allocimage(display, Rect(0,0,Facesize,Facesize), chan, 0, DNofill);
       +                if(img == nil)
       +                        return nil;
       +        }
       +        return img;
       +}
       +                
       +
       +static Image*
       +readbit(int fd, ulong chan)
       +{
       +        char buf[4096], hx[4], *p;
       +        uchar data[Facesize*Facesize];        /* more than enough */
       +        int nhx, i, n, ndata, nbit;
       +        Image *img;
       +
       +        n = readn(fd, buf, sizeof buf);
       +        if(n <= 0)
       +                return nil;
       +        if(n >= sizeof buf)
       +                n = sizeof(buf)-1;
       +        buf[n] = '\0';
       +
       +        n = 0;
       +        nhx = 0;
       +        nbit = chantodepth(chan);
       +        ndata = (Facesize*Facesize*nbit)/8;
       +        p = buf;
       +        while(n < ndata) {
       +                p = strpbrk(p+1, "0123456789abcdefABCDEF");
       +                if(p == nil)
       +                        break;
       +                if(p[0] == '0' && p[1] == 'x')
       +                        continue;
       +
       +                hx[nhx] = *p;
       +                if(++nhx == 2) {
       +                        hx[nhx] = 0;
       +                        i = strtoul(hx, 0, 16);
       +                        data[n++] = i;
       +                        nhx = 0;
       +                }
       +        }
       +        if(n < ndata)
       +                return allocimage(display, Rect(0,0,Facesize,Facesize), CMAP8, 0, 0x88888888);
       +
       +        img = myallocimage(chan);
       +        if(img == nil)
       +                return nil;
       +        loadimage(img, img->r, data, ndata);
       +        return img;
       +}
       +
       +static Facefile*
       +readface(char *fn)
       +{
       +        int x, y, fd;
       +        uchar bits;
       +        uchar *p;
       +        Image *mask;
       +        Image *face;
       +        char buf[16];
       +        uchar data[Facesize*Facesize];
       +        uchar mdata[(Facesize*Facesize)/8];
       +        Facefile *f;
       +        Dir *d;
       +
       +        for(f=facefiles; f!=nil; f=f->next){
       +                if(strcmp(fn, f->file) == 0){
       +                        if(f->image == nil)
       +                                break;
       +                        if(time(0) - f->rdtime >= 30) {
       +                                if(dirmtime(fn) != f->mtime){
       +                                        f = nil;
       +                                        break;
       +                                }
       +                                f->rdtime = time(0);
       +                        }
       +                        f->ref++;
       +                        return f;
       +                }
       +        }
       +
       +        if((fd = open(fn, OREAD)) < 0)
       +                return nil;
       +
       +        if(readn(fd, buf, sizeof buf) != sizeof buf){
       +                close(fd);
       +                return nil;
       +        }
       +
       +        seek(fd, 0, 0);
       +
       +        mask = nil;
       +        if(buf[0] == '0' && buf[1] == 'x'){
       +                /* greyscale faces are just masks that we draw black through! */
       +                if(buf[2+8] == ',')        /* ldepth 1 */
       +                        mask = readbit(fd, GREY2);
       +                else
       +                        mask = readbit(fd, GREY1);
       +                face = display->black;
       +        }else{
       +                face = readimage(display, fd, 0);
       +                if(face == nil)
       +                        goto Done;
       +                else if(face->chan == GREY4 || face->chan == GREY8){        /* greyscale: use inversion as mask */
       +                        mask = myallocimage(face->chan);
       +                        /* okay if mask is nil: that will copy the image white background and all */
       +                        if(mask == nil)
       +                                goto Done;
       +
       +                        /* invert greyscale image */
       +                        draw(mask, mask->r, display->white, nil, ZP);
       +                        gendraw(mask, mask->r, display->black, ZP, face, face->r.min);
       +                        freeimage(face);
       +                        face = display->black;
       +                }else if(face->depth == 8){        /* snarf the bytes back and do a fill. */
       +                        mask = myallocimage(GREY1);
       +                        if(mask == nil)
       +                                goto Done;
       +                        if(unloadimage(face, face->r, data, Facesize*Facesize) != Facesize*Facesize){        
       +                                freeimage(mask);
       +                                goto Done;
       +                        }
       +                        bits = 0;
       +                        p = mdata;
       +                        for(y=0; y<Facesize; y++){
       +                                for(x=0; x<Facesize; x++){        
       +                                        bits <<= 1;
       +                                        if(data[Facesize*y+x] != 0xFF)
       +                                                bits |= 1;
       +                                        if((x&7) == 7)
       +                                                *p++ = bits&0xFF;
       +                                }
       +                        }
       +                        if(loadimage(mask, mask->r, mdata, sizeof mdata) != sizeof mdata){
       +                                freeimage(mask);
       +                                goto Done;
       +                        }
       +                }
       +        }
       +
       +Done:
       +        /* always add at beginning of list, so updated files don't collide in cache */
       +        if(f == nil){
       +                f = emalloc(sizeof(Facefile));
       +                f->file = estrdup(fn);
       +                d = dirfstat(fd);
       +                if(d != nil){
       +                        f->mtime = d->mtime;
       +                        free(d);
       +                }
       +                f->next = facefiles;
       +                facefiles = f;
       +        }
       +        f->ref++;
       +        f->image = face;
       +        f->mask = mask;
       +        f->rdtime = time(0);
       +        close(fd);
       +        return f;
       +}
       +
       +void
       +findbit(Face *f)
       +{
       +        char *fn;
       +
       +        fn = findfile(f, f->str[Sdomain], f->str[Suser]);
       +        if(fn) {
       +                if(strstr(fn, "unknown"))
       +                        f->unknown = 1;
       +                f->file = readface(fn);
       +        }
       +        if(f->file){
       +                f->bit = f->file->image;
       +                f->mask = f->file->mask;
       +        }else{
       +                /* if returns nil, this is still ok: draw(nil) works */
       +                f->bit = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DYellow);
       +                replclipr(f->bit, 1, Rect(0, 0, Facesize, Facesize));
       +                f->mask = nil;
       +        }
       +}
 (DIR) diff --git a/src/cmd/faces/faces.h b/src/cmd/faces/faces.h
       t@@ -0,0 +1,68 @@
       +enum        /* face strings */
       +{
       +        Suser,
       +        Sdomain,
       +        Sshow,
       +        Sdigest,
       +        Nstring
       +};
       +
       +enum
       +{
       +        Facesize = 48,
       +};
       +
       +typedef struct Face                Face;
       +typedef struct Facefile        Facefile;
       +
       +struct Face
       +{
       +        Image        *bit;                /* unless there's an error, this is file->image */
       +        Image        *mask;        /* unless there's an error, this is file->mask */
       +        char                *str[Nstring];
       +        int                recent;
       +        ulong        time;
       +        Tm                tm;
       +        int                unknown;
       +        Facefile        *file;
       +};
       +
       +/*
       + * Loading the files is slow enough on a dial-up line to be worth this trouble
       + */
       +struct Facefile
       +{
       +        Image        *image;
       +        Image        *mask;
       +        ulong        mtime;
       +        ulong        rdtime;
       +        int                ref;
       +        char                *file;
       +        Facefile        *next;
       +};
       +
       +extern char        date[];
       +extern char        *maildir;
       +extern char        **maildirs;
       +extern int        nmaildirs;
       +extern CFsys        *upasfs;
       +
       +Face*        nextface(void);
       +void        findbit(Face*);
       +void        freeface(Face*);
       +void        initplumb(void);
       +void        killall(char*);
       +void        showmail(Face*);
       +void        delete(char*, char*);
       +void        freefacefile(Facefile*);
       +Face*        dirface(char*, char*);
       +void        resized(void);
       +int        alreadyseen(char*);
       +ulong        dirlen(char*);
       +ulong        fsdirlen(CFsys*, char*);
       +
       +void        *emalloc(ulong);
       +void        *erealloc(void*, ulong);
       +char        *estrdup(char*);
       +char        *findfile(Face*, char*, char*);
       +void        addmaildir(char*);
 (DIR) diff --git a/src/cmd/faces/main.c b/src/cmd/faces/main.c
       t@@ -0,0 +1,783 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <draw.h>
       +#include <plumb.h>
       +#include <regexp.h>
       +//jpc #include <event.h>        /* for support routines only */
       +#include <bio.h>
       +#include <thread.h>
       +#include <mouse.h>
       +#include <cursor.h>
       +#include <9pclient.h>
       +#include "faces.h"
       +
       +int        history = 0;        /* use old interface, showing history of mailbox rather than current state */
       +int        initload = 0;        /* initialize program with contents of mail box */
       +
       +enum
       +{
       +        Facesep = 6,        /* must be even to avoid damaging background stipple */
       +        Infolines = 9,
       +
       +        HhmmTime = 18*60*60,        /* max age of face to display hh:mm time */
       +};
       +
       +enum
       +{
       +        Mainp,
       +        Timep,
       +        Mousep,
       +        NPROC
       +};
       +
       +int pids[NPROC];
       +char *procnames[] = {
       +        "main",
       +        "time",
       +        "mouse"
       +};
       +
       +Rectangle leftright = {0, 0, 20, 15};
       +
       +uchar leftdata[] = {
       +        0x00, 0x80, 0x00, 0x01, 0x80, 0x00, 0x03, 0x80,
       +        0x00, 0x07, 0x80, 0x00, 0x0f, 0x00, 0x00, 0x1f,
       +        0xff, 0xf0, 0x3f, 0xff, 0xf0, 0xff, 0xff, 0xf0,
       +        0x3f, 0xff, 0xf0, 0x1f, 0xff, 0xf0, 0x0f, 0x00,
       +        0x00, 0x07, 0x80, 0x00, 0x03, 0x80, 0x00, 0x01,
       +        0x80, 0x00, 0x00, 0x80, 0x00
       +};
       +
       +uchar rightdata[] = {
       +        0x00, 0x10, 0x00, 0x00, 0x18, 0x00, 0x00, 0x1c,
       +        0x00, 0x00, 0x1e, 0x00, 0x00, 0x0f, 0x00, 0xff,
       +        0xff, 0x80, 0xff, 0xff, 0xc0, 0xff, 0xff, 0xf0,
       +        0xff, 0xff, 0xc0, 0xff, 0xff, 0x80, 0x00, 0x0f,
       +        0x00, 0x00, 0x1e, 0x00, 0x00, 0x1c, 0x00, 0x00,
       +        0x18, 0x00, 0x00, 0x10, 0x00
       +};
       +
       +CFsys        *upasfs;
       +Mousectl        *mousectl;
       +Image        *blue;                /* full arrow */
       +Image        *bgrnd;                /* pale blue background color */
       +Image        *left;                /* left-pointing arrow mask */
       +Image        *right;                /* right-pointing arrow mask */
       +Font        *tinyfont;
       +Font        *mediumfont;
       +Font        *datefont;
       +int        first, last;        /* first and last visible face; last is first invisible */
       +int        nfaces;
       +int        mousefd;
       +int        nacross;
       +int        ndown;
       +
       +char        date[64];
       +Face        **faces;
       +char        *maildir = "/mail/fs/mbox";
       +ulong        now;
       +
       +Point        datep = { 8, 6 };
       +Point        facep = { 8, 6+0+4 };        /* 0 updated to datefont->height in init() */
       +Point        enddate;                        /* where date ends on display; used to place arrows */
       +Rectangle        leftr;                        /* location of left arrow on display */
       +Rectangle        rightr;                /* location of right arrow on display */
       +void updatetimes(void);
       +
       +void
       +setdate(void)
       +{
       +        now = time(nil);
       +        strcpy(date, ctime(now));
       +        date[4+4+3+5] = '\0';        /* change from Thu Jul 22 14:28:43 EDT 1999\n to Thu Jul 22 14:28 */
       +}
       +
       +void
       +init(void)
       +{
       +#if 0
       +        mousefd = open("/dev/mouse", OREAD);
       +        if(mousefd < 0){
       +                fprint(2, "faces: can't open mouse: %r\n");
       +                threadexitsall("mouse");
       +        }
       +#endif
       +        upasfs = nsmount("upasfs",nil);
       +        mousectl = initmouse(nil,screen);
       +        initplumb();
       +
       +        /* make background color */
       +        bgrnd = allocimagemix(display, DPalebluegreen, DWhite);
       +        blue = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x008888FF);        /* blue-green */
       +        left = allocimage(display, leftright, GREY1, 0, DWhite);
       +        right = allocimage(display, leftright, GREY1, 0, DWhite);
       +        if(bgrnd==nil || blue==nil || left==nil || right==nil){
       +                fprint(2, "faces: can't create images: %r\n");
       +                threadexitsall("image");
       +        }
       +
       +        loadimage(left, leftright, leftdata, sizeof leftdata);
       +        loadimage(right, leftright, rightdata, sizeof rightdata);
       +
       +        /* initialize little fonts */
       +        tinyfont = openfont(display, "/lib/font/bit/misc/ascii.5x7.font");
       +        if(tinyfont == nil)
       +                tinyfont = font;
       +        mediumfont = openfont(display, "/lib/font/bit/pelm/latin1.8.font");
       +        if(mediumfont == nil)
       +                mediumfont = font;
       +        datefont = font;
       +
       +        facep.y += datefont->height;
       +        if(datefont->height & 1)        /* stipple parity */
       +                facep.y++;
       +        faces = nil;
       +}
       +
       +void
       +drawtime(void)
       +{
       +        Rectangle r;
       +
       +        r.min = addpt(screen->r.min, datep);
       +        if(eqpt(enddate, ZP)){
       +                enddate = r.min;
       +                enddate.x += stringwidth(datefont, "Wed May 30 22:54");        /* nice wide string */
       +                enddate.x += Facesep;        /* for safety */
       +        }
       +        r.max.x = enddate.x;
       +        r.max.y = enddate.y+datefont->height;
       +        draw(screen, r, bgrnd, nil, ZP);
       +        string(screen, r.min, display->black, ZP, datefont, date);
       +}
       +
       +void
       +timeproc(void *dummy)
       +{
       +        for(;;){
       +                lockdisplay(display);
       +                drawtime();
       +                updatetimes();
       +                flushimage(display, 1);
       +                unlockdisplay(display);
       +                sleep(60000);
       +                setdate();
       +        }
       +}
       +
       +int
       +alreadyseen(char *digest)
       +{
       +        int i;
       +        Face *f;
       +
       +        if(!digest)
       +                return 0;
       +
       +        /* can do accurate check */
       +        for(i=0; i<nfaces; i++){
       +                f = faces[i];
       +                if(f->str[Sdigest]!=nil && strcmp(digest, f->str[Sdigest])==0)
       +                        return 1;
       +        }
       +        return 0;
       +}
       +
       +int
       +torune(Rune *r, char *s, int nr)
       +{
       +        int i;
       +
       +        for(i=0; i<nr-1 && *s!='\0'; i++)
       +                s += chartorune(r+i, s);
       +        r[i] = L'\0';
       +        return i;
       +}
       +
       +void
       +center(Font *f, Point p, char *s, Image *color)
       +{
       +        int i, n, dx;
       +        Rune rbuf[32];
       +        char sbuf[32*UTFmax+1];
       +
       +        dx = stringwidth(f, s);
       +        if(dx > Facesize){
       +                n = torune(rbuf, s, nelem(rbuf));
       +                for(i=0; i<n; i++){
       +                        dx = runestringnwidth(f, rbuf, i+1);
       +                        if(dx > Facesize)
       +                                break;
       +                }
       +                sprint(sbuf, "%.*S", i, rbuf);
       +                s = sbuf;
       +                dx = stringwidth(f, s);
       +        }
       +        p.x += (Facesize-dx)/2;
       +        string(screen, p, color, ZP, f, s);
       +}
       +
       +Rectangle
       +facerect(int index)        /* index is geometric; 0 is always upper left face */
       +{
       +        Rectangle r;
       +        int x, y;
       +
       +        x = index % nacross;
       +        y = index / nacross;
       +        r.min = addpt(screen->r.min, facep);
       +        r.min.x += x*(Facesize+Facesep);
       +        r.min.y += y*(Facesize+Facesep+2*mediumfont->height);
       +        r.max = addpt(r.min, Pt(Facesize, Facesize));
       +        r.max.y += 2*mediumfont->height;
       +        /* simple fix to avoid drawing off screen, allowing customers to use position */
       +        if(index<0 || index>=nacross*ndown)
       +                r.max.x = r.min.x;
       +        return r;
       +}
       +
       +static char *mon = "JanFebMarAprMayJunJulAugSepOctNovDec";
       +char*
       +facetime(Face *f, int *recent)
       +{
       +        static char buf[30];
       +
       +        if((long)(now - f->time) > HhmmTime){
       +                *recent = 0;
       +                sprint(buf, "%.3s %2d", mon+3*f->tm.mon, f->tm.mday);
       +                return buf;
       +        }else{
       +                *recent = 1;
       +                sprint(buf, "%02d:%02d", f->tm.hour, f->tm.min);
       +                return buf;
       +        }
       +}
       +
       +void
       +drawface(Face *f, int i)
       +{
       +        char *tstr;
       +        Rectangle r;
       +        Point p;
       +
       +        if(f == nil)
       +                return;
       +        if(i<first || i>=last)
       +                return;
       +        r = facerect(i-first);
       +        draw(screen, r, bgrnd, nil, ZP);
       +        draw(screen, r, f->bit, f->mask, ZP);
       +        r.min.y += Facesize;
       +        center(mediumfont, r.min, f->str[Suser], display->black);
       +        r.min.y += mediumfont->height;
       +        tstr = facetime(f, &f->recent);
       +        center(mediumfont, r.min, tstr, display->black);
       +        if(f->unknown){
       +                r.min.y -= mediumfont->height + tinyfont->height + 2;
       +                for(p.x=-1; p.x<=1; p.x++)
       +                        for(p.y=-1; p.y<=1; p.y++)
       +                                center(tinyfont, addpt(r.min, p), f->str[Sdomain], display->white);
       +                center(tinyfont, r.min, f->str[Sdomain], display->black);
       +        }
       +}
       +
       +void
       +updatetimes(void)
       +{
       +        int i;
       +        Face *f;
       +
       +        for(i=0; i<nfaces; i++){
       +                f = faces[i];
       +                if(f == nil)
       +                        continue;
       +                if(((long)(now - f->time) <= HhmmTime) != f->recent)
       +                        drawface(f, i);
       +        }        
       +}
       +
       +void
       +setlast(void)
       +{
       +        last = first+nacross*ndown;
       +        if(last > nfaces)
       +                last = nfaces;
       +}
       +
       +void
       +drawarrows(void)
       +{
       +        Point p;
       +
       +        p = enddate;
       +        p.x += Facesep;
       +        if(p.x & 1)
       +                p.x++;        /* align background texture */
       +        leftr = rectaddpt(leftright, p);
       +        p.x += Dx(leftright) + Facesep;
       +        rightr = rectaddpt(leftright, p);
       +        draw(screen, leftr, first>0? blue : bgrnd, left, leftright.min);
       +        draw(screen, rightr, last<nfaces? blue : bgrnd, right, leftright.min);
       +}
       +
       +void
       +addface(Face *f)        /* always adds at 0 */
       +{
       +        Face **ofaces;
       +        Rectangle r0, r1, r;
       +        int y, nx, ny;
       +
       +        if(f == nil)
       +                return;
       +        lockdisplay(display);
       +        if(first != 0){
       +                first = 0;
       +                resized();
       +        }
       +        findbit(f);
       +
       +        nx = nacross;
       +        ny = (nfaces+(nx-1)) / nx;
       +
       +        for(y=ny; y>=0; y--){
       +                /* move them along */
       +                r0 = facerect(y*nx+0);
       +                r1 = facerect(y*nx+1);
       +                r = r1;
       +                r.max.x = r.min.x + (nx - 1)*(Facesize+Facesep);
       +                draw(screen, r, screen, nil, r0.min);
       +                /* copy one down from row above */
       +                if(y != 0){
       +                        r = facerect((y-1)*nx+nx-1);
       +                        draw(screen, r0, screen, nil, r.min);
       +                }
       +        }
       +
       +        ofaces = faces;
       +        faces = emalloc((nfaces+1)*sizeof(Face*));
       +        memmove(faces+1, ofaces, nfaces*(sizeof(Face*)));
       +        free(ofaces);
       +        nfaces++;
       +        setlast();
       +        drawarrows();
       +        faces[0] = f;
       +        drawface(f, 0);
       +        flushimage(display, 1);
       +        unlockdisplay(display);
       +}
       +
       +#if 0
       +void
       +loadmboxfaces(char *maildir)
       +{
       +        int dirfd;
       +        Dir *d;
       +        int i, n;
       +
       +        dirfd = open(maildir, OREAD);
       +        if(dirfd >= 0){
       +                chdir(maildir);
       +                while((n = dirread(dirfd, &d)) > 0){
       +                        for(i=0; i<n; i++)
       +                                addface(dirface(maildir, d[i].name));
       +                        free(d);
       +                }
       +                close(dirfd);
       +        }
       +}
       +#endif
       +
       +void
       +loadmboxfaces(char *maildir)
       +{
       +        CFid *dirfd;
       +        Dir *d;
       +        int i, n;
       +
       +        dirfd = fsopen(upasfs,maildir, OREAD);
       +        if(dirfd != nil){
       +                //jpc chdir(maildir);
       +                while((n = fsdirread(dirfd, &d)) > 0){
       +                        for(i=0; i<n; i++) {
       +                                addface(dirface(maildir, d[i].name));
       +                        }
       +                        free(d);
       +                }
       +                fsclose(dirfd);
       +        }
       +        else {
       +                error("cannot open %s: %r",maildir);
       +        }
       +}
       +
       +void
       +freeface(Face *f)
       +{
       +        int i;
       +
       +        if(f->file!=nil && f->bit!=f->file->image)
       +                freeimage(f->bit);
       +        freefacefile(f->file);
       +        for(i=0; i<Nstring; i++)
       +                free(f->str[i]);
       +        free(f);
       +}
       +
       +void
       +delface(int j)
       +{
       +        Rectangle r0, r1, r;
       +        int nx, ny, x, y;
       +
       +        if(j < first)
       +                first--;
       +        else if(j < last){
       +                nx = nacross;
       +                ny = (nfaces+(nx-1)) / nx;
       +                x = (j-first)%nx;
       +                for(y=(j-first)/nx; y<ny; y++){
       +                        if(x != nx-1){
       +                                /* move them along */
       +                                r0 = facerect(y*nx+x);
       +                                r1 = facerect(y*nx+x+1);
       +                                r = r0;
       +                                r.max.x = r.min.x + (nx - x - 1)*(Facesize+Facesep);
       +                                draw(screen, r, screen, nil, r1.min);
       +                        }
       +                        if(y != ny-1){
       +                                /* copy one up from row below */
       +                                r = facerect((y+1)*nx);
       +                                draw(screen, facerect(y*nx+nx-1), screen, nil, r.min);
       +                        }
       +                        x = 0;
       +                }
       +                if(last < nfaces)        /* first off-screen becomes visible */
       +                        drawface(faces[last], last-1);
       +                else{
       +                        /* clear final spot */
       +                        r = facerect(last-first-1);
       +                        draw(screen, r, bgrnd, nil, r.min);
       +                }
       +        }
       +        freeface(faces[j]);
       +        memmove(faces+j, faces+j+1, (nfaces-(j+1))*sizeof(Face*));
       +        nfaces--;
       +        setlast();
       +        drawarrows();
       +}
       +
       +void
       +dodelete(int i)
       +{
       +        Face *f;
       +
       +        f = faces[i];
       +        if(history){
       +                free(f->str[Sshow]);
       +                f->str[Sshow] = estrdup("");
       +        }else{
       +                delface(i);
       +                flushimage(display, 1);
       +        }
       +}
       +
       +void
       +delete(char *s, char *digest)
       +{
       +        int i;
       +        Face *f;
       +
       +        lockdisplay(display);
       +        for(i=0; i<nfaces; i++){
       +                f = faces[i];
       +                if(digest != nil){
       +                        if(f->str[Sdigest]!=nil && strcmp(digest, f->str[Sdigest]) == 0){
       +                                dodelete(i);
       +                                break;
       +                        }
       +                }else{
       +                        if(f->str[Sshow] && strcmp(s, f->str[Sshow]) == 0){
       +                                dodelete(i);
       +                                break;
       +                        }
       +                }
       +        }
       +        unlockdisplay(display);
       +}
       +
       +void
       +faceproc(void)
       +{
       +        for(;;)
       +                addface(nextface());
       +}
       +
       +void
       +resized(void)
       +{
       +        int i;
       +
       +        nacross = (Dx(screen->r)-2*facep.x+Facesep)/(Facesize+Facesep);
       +        for(ndown=1; rectinrect(facerect(ndown*nacross), screen->r); ndown++)
       +                ;
       +        setlast();
       +        draw(screen, screen->r, bgrnd, nil, ZP);
       +        enddate = ZP;
       +        drawtime();
       +        for(i=0; i<nfaces; i++)
       +                drawface(faces[i], i);
       +        drawarrows();
       +        flushimage(display, 1);
       +}
       +
       +void
       +eresized(int new)
       +{
       +        lockdisplay(display);
       +        if(new && getwindow(display, Refnone) < 0) {
       +                fprint(2, "can't reattach to window\n");
       +                killall("reattach");
       +        }
       +        resized();
       +        unlockdisplay(display);
       +}
       +
       +#if 0
       +int
       +getmouse(Mouse *m)
       +{
       +        int n;
       +        static int eof;
       +        char buf[128];
       +
       +        if(eof)
       +                return 0;
       +        for(;;){
       +                n = read(mousefd, buf, sizeof(buf));
       +                if(n <= 0){
       +                        /* so callers needn't check return value every time */
       +                        eof = 1;
       +                        m->buttons = 0;
       +                        return 0;
       +                }
       +                //jpc n = eatomouse(m, buf, n);
       +                if(n > 0)
       +                        return 1;
       +        }
       +}
       +#endif
       +int
       +getmouse(Mouse *m)
       +{
       +        static int eof;
       +
       +        if(eof)
       +                return 0;
       +        if( readmouse(mousectl) < 0 ) {
       +                eof = 1;
       +                m->buttons = 0;
       +                return 0;
       +        }
       +        else {
       +                *m = mousectl->m;
       +/*                m->buttons = mousectl->m.buttons;
       +                m->xy.x = mousectl->m.xy.x;
       +                m->xy.y = mousectl->m.xy.y;
       +                m->msec = mousectl->m.msec;        */
       +                return 1;
       +        }
       +}
       +
       +enum
       +{
       +        Clicksize        = 3,                /* pixels */
       +};
       +
       +int
       +scroll(int but, Point p)
       +{
       +        int delta;
       +
       +        delta = 0;
       +        lockdisplay(display);
       +        if(ptinrect(p, leftr) && first>0){
       +                if(but == 2)
       +                        delta = -first;
       +                else{
       +                        delta = nacross;
       +                        if(delta > first)
       +                                delta = first;
       +                        delta = -delta;
       +                }
       +        }else if(ptinrect(p, rightr) && last<nfaces){
       +                if(but == 2)
       +                        delta = (nfaces-nacross*ndown) - first;
       +                else{
       +                        delta = nacross;
       +                        if(delta > nfaces-last)
       +                                delta = nfaces-last;
       +                }
       +        }
       +        first += delta;
       +        last += delta;
       +        unlockdisplay(display);
       +        if(delta)
       +                eresized(0);
       +        return delta;
       +}
       +
       +void
       +click(int button, Mouse *m)
       +{
       +        Point p;
       +        int i;
       +
       +        p = m->xy;
       +        while(m->buttons == (1<<(button-1)))
       +                getmouse(m);
       +        if(m->buttons)
       +                return;
       +        if(abs(p.x-m->xy.x)>Clicksize || abs(p.y-m->xy.y)>Clicksize)
       +                return;
       +        switch(button){
       +        case 1:
       +                if(scroll(1, p))
       +                        break;
       +                if(history){
       +                        /* click clears display */
       +                        lockdisplay(display);
       +                        for(i=0; i<nfaces; i++)
       +                                freeface(faces[i]);
       +                        free(faces);
       +                        faces=nil;
       +                        nfaces = 0;
       +                        unlockdisplay(display);
       +                        eresized(0);
       +                        return;
       +                }else{
       +                        for(i=first; i<last; i++)        /* clear vwhois faces */
       +                                if(ptinrect(p, facerect(i-first)) 
       +                                && strstr(faces[i]->str[Sshow], "/XXXvwhois")){
       +                                        delface(i);
       +                                        flushimage(display, 1);
       +                                }
       +                }
       +                break;
       +        case 2:
       +                scroll(2, p);
       +                break;
       +        case 3:
       +                scroll(3, p);
       +                lockdisplay(display);
       +                for(i=first; i<last; i++)
       +                        if(ptinrect(p, facerect(i-first))){
       +                                showmail(faces[i]);
       +                                break;
       +                        }
       +                unlockdisplay(display);
       +                break;
       +        }
       +}
       +
       +void
       +mouseproc(void *dummy)
       +{
       +        Mouse mouse;
       +
       +        while(getmouse(&mouse)){
       +                if(mouse.buttons == 1)
       +                        click(1, &mouse);
       +                else if(mouse.buttons == 2)
       +                        click(2, &mouse);
       +                else if(mouse.buttons == 4)
       +                        click(3, &mouse);
       +
       +                while(mouse.buttons)
       +                        getmouse(&mouse);
       +        }
       +}
       +
       +void
       +killall(char *s)
       +{
       +        int i, pid;
       +
       +        pid = getpid();
       +        for(i=0; i<NPROC; i++)
       +                if(pids[i] && pids[i]!=pid)
       +                        postnote(PNPROC, pids[i], "kill");
       +        threadexitsall(s);
       +}
       +
       +void
       +startproc(void (*f)(void), int index)
       +{
       +        int pid;
       +
       +        switch(pid = rfork(RFPROC|RFNOWAIT)){ //jpc removed |RFMEM
       +        case -1:
       +                fprint(2, "faces: fork failed: %r\n");
       +                killall("fork failed");
       +        case 0:
       +                f();
       +                fprint(2, "faces: %s process exits\n", procnames[index]);
       +                if(index >= 0)
       +                        killall("process died");
       +                threadexitsall(nil);
       +        }
       +        if(index >= 0)
       +                pids[index] = pid;
       +}
       +
       +void
       +usage(void)
       +{
       +        fprint(2, "usage: faces [-hi] [-m maildir] -W winsize\n");
       +        threadexitsall("usage");
       +}
       +
       +void
       +threadmain(int argc, char *argv[])
       +{
       +        int i;
       +
       +        ARGBEGIN{
       +        case 'h':
       +                history++;
       +                break;
       +        case 'i':
       +                initload++;
       +                break;
       +        case 'm':
       +                addmaildir(EARGF(usage()));
       +                maildir = nil;
       +                break;
       +        case 'W':
       +                winsize = EARGF(usage());
       +                break;
       +        default:
       +                usage();
       +        }ARGEND
       +
       +        if(initdraw(nil, nil, "faces") < 0){
       +                fprint(2, "faces: initdraw failed: %r\n");
       +                threadexitsall("initdraw");
       +        }
       +        if(maildir)
       +                addmaildir(maildir);
       +        init();
       +        unlockdisplay(display);        /* initdraw leaves it locked */
       +        display->locking = 1;        /* tell library we're using the display lock */
       +        setdate();
       +        eresized(0);
       +
       +        pids[Mainp] = getpid();
       +        pids[Timep] = proccreate(timeproc, nil, 16000);
       +        pids[Mousep] = proccreate(mouseproc, nil, 16000);
       +        if(initload)
       +                for(i = 0; i < nmaildirs; i++)
       +                        loadmboxfaces(maildirs[i]);
       +        faceproc();
       +        fprint(2, "faces: %s process exits\n", procnames[Mainp]);
       +        killall(nil);
       +}
 (DIR) diff --git a/src/cmd/faces/mkfile b/src/cmd/faces/mkfile
       t@@ -0,0 +1,26 @@
       +<$PLAN9/src/mkhdr
       +
       +# default domain for faces, overridden by $facedom
       +DEFAULT=\"astro\"
       +
       +TARG=faces
       +
       +OFILES=main.$O\
       +        facedb.$O\
       +        plumb.$O\
       +        util.$O\
       +
       +HFILES=faces.h\
       +
       +BIN=$PLAN9/bin
       +
       +UPDATE=\
       +        mkfile\
       +        $HFILES\
       +        ${OFILES:%.$O=%.c}\
       +
       +<$PLAN9/src/mkone
       +CFLAGS=$CFLAGS '-DDEFAULT='$DEFAULT
       +
       +$O.dblook: dblook.$O facedb.$O util.$O
       +        $LD -o $target $prereq
 (DIR) diff --git a/src/cmd/faces/plumb.c b/src/cmd/faces/plumb.c
       t@@ -0,0 +1,398 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <draw.h>
       +#include <plumb.h>
       +#include <regexp.h>
       +#include <bio.h>
       +#include <9pclient.h>
       +#include "faces.h"
       +
       +static int                showfd = -1;
       +static int                seefd = -1;
       +static int                logfd = -1;
       +static char        *user;
       +static char        *logtag;
       +
       +char                **maildirs;
       +int                nmaildirs;
       +
       +void
       +initplumb(void)
       +{
       +        showfd = plumbopen("send", OWRITE);
       +        seefd = plumbopen("seemail", OREAD);
       +
       +        if(seefd < 0){
       +                logfd = open(unsharp("#9/log/mail"), OREAD);
       +                seek(logfd, 0LL, 2);
       +                user = getenv("user");
       +                if(user == nil){
       +                        fprint(2, "faces: can't find user name: %r\n");
       +                        exits("$user");
       +                }
       +                logtag = emalloc(32+strlen(user)+1);
       +                sprint(logtag, " delivered %s From ", user);
       +        }
       +}
       +
       +void
       +addmaildir(char *dir)
       +{
       +        maildirs = erealloc(maildirs, (nmaildirs+1)*sizeof(char*));
       +        maildirs[nmaildirs++] = dir;
       +}
       +
       +char*
       +attr(Face *f)
       +{
       +        static char buf[128];
       +
       +        if(f->str[Sdigest]){
       +                snprint(buf, sizeof buf, "digest=%s", f->str[Sdigest]);
       +                return buf;
       +        }
       +        return nil;
       +}
       +
       +void
       +showmail(Face *f)
       +{
       +        Plumbmsg pm;
       +        Plumbattr a;
       +        char *s;
       +
       +        if(showfd<0 || f->str[Sshow]==nil || f->str[Sshow][0]=='\0')
       +                return;
       +        s = emalloc(strlen("/mail/fs")+1+strlen(f->str[Sshow]));
       +        sprint(s,"/mail/fs/%s",f->str[Sshow]);
       +        pm.src = "faces";
       +        pm.dst = "showmail";
       +        pm.wdir = "/mail/fs";
       +        pm.type = "text";
       +        a.name = "digest";
       +        a.value = f->str[Sdigest];
       +        a.next = nil;
       +        pm.attr = &a;
       +        pm.ndata = strlen(s);
       +        pm.data = s;
       +        plumbsend(showfd,&pm);
       +}
       +
       +char*
       +value(Plumbattr *attr, char *key, char *def)
       +{
       +        char *v;
       +
       +        v = plumblookup(attr, key);
       +        if(v)
       +                return v;
       +        return def;
       +}
       +
       +void
       +setname(Face *f, char *sender)
       +{
       +        char *at, *bang;
       +        char *p;
       +
       +        /* works with UTF-8, although it's written as ASCII */
       +        for(p=sender; *p!='\0'; p++)
       +                *p = tolower(*p);
       +        f->str[Suser] = sender;
       +        at = strchr(sender, '@');
       +        if(at){
       +                *at++ = '\0';
       +                f->str[Sdomain] = estrdup(at);
       +                return;
       +        }
       +        bang = strchr(sender, '!');
       +        if(bang){
       +                *bang++ = '\0';
       +                f->str[Suser] = estrdup(bang);
       +                f->str[Sdomain] = sender;
       +                return;
       +        }
       +}
       +
       +int
       +getc(void)
       +{
       +        static uchar buf[512];
       +        static int nbuf = 0;
       +        static int i = 0;
       +
       +        while(i == nbuf){
       +                i = 0;
       +                nbuf = read(logfd, buf, sizeof buf);
       +                if(nbuf == 0){
       +                        sleep(15000);
       +                        continue;
       +                }
       +                if(nbuf < 0)
       +                        return -1;
       +        }
       +        return buf[i++];
       +}
       +
       +char*
       +getline(char *buf, int n)
       +{
       +        int i, c;
       +
       +        for(i=0; i<n-1; i++){
       +                c = getc();
       +                if(c <= 0)
       +                        return nil;
       +                if(c == '\n')
       +                        break;
       +                buf[i] = c;
       +        }
       +        buf[i] = '\0';
       +        return buf;
       +}
       +
       +static char* months[] = {
       +        "jan", "feb", "mar", "apr",
       +        "may", "jun", "jul", "aug", 
       +        "sep", "oct", "nov", "dec"
       +};
       +
       +static int
       +getmon(char *s)
       +{
       +        int i;
       +
       +        for(i=0; i<nelem(months); i++)
       +                if(cistrcmp(months[i], s) == 0)
       +                        return i;
       +        return -1;
       +}
       +
       +/* Fri Jul 23 14:05:14 EDT 1999 */
       +ulong
       +parsedatev(char **a)
       +{
       +        char *p;
       +        Tm tm;
       +
       +        memset(&tm, 0, sizeof tm);
       +        if((tm.mon=getmon(a[1])) == -1)
       +                goto Err;
       +        tm.mday = strtol(a[2], &p, 10);
       +        if(*p != '\0')
       +                goto Err;
       +        tm.hour = strtol(a[3], &p, 10);
       +        if(*p != ':')
       +                goto Err;
       +        tm.min = strtol(p+1, &p, 10);
       +        if(*p != ':')
       +                goto Err;
       +        tm.sec = strtol(p+1, &p, 10);
       +        if(*p != '\0')
       +                goto Err;
       +        if(strlen(a[4]) != 3)
       +                goto Err;
       +        strcpy(tm.zone, a[4]);
       +        if(strlen(a[5]) != 4)
       +                goto Err;
       +        tm.year = strtol(a[5], &p, 10);
       +        if(*p != '\0')
       +                goto Err;
       +        tm.year -= 1900;
       +        return tm2sec(&tm);
       +Err:
       +        return time(0);
       +}
       +
       +ulong
       +parsedate(char *s)
       +{
       +        char *f[10];
       +        int nf;
       +
       +        nf = getfields(s, f, nelem(f), 1, " ");
       +        if(nf < 6)
       +                return time(0);
       +        return parsedatev(f);
       +}
       +
       +/* achille Jul 23 14:05:15 delivered jmk From ms.com!bub Fri Jul 23 14:05:14 EDT 1999 (plan9.bell-labs.com!jmk) 1352 */
       +/* achille Oct 26 13:45:42 remote local!rsc From rsc Sat Oct 26 13:45:41 EDT 2002 (rsc) 170 */
       +int
       +parselog(char *s, char **sender, ulong *xtime)
       +{
       +        char *f[20];
       +        int nf;
       +
       +        nf = getfields(s, f, nelem(f), 1, " ");
       +        if(nf < 14)
       +                return 0;
       +        if(strcmp(f[4], "delivered") == 0 && strcmp(f[5], user) == 0)
       +                goto Found;
       +        if(strcmp(f[4], "remote") == 0 && strncmp(f[5], "local!", 6) == 0 && strcmp(f[5]+6, user) == 0)
       +                goto Found;
       +        return 0;
       +
       +Found:
       +        *sender = estrdup(f[7]);
       +        *xtime = parsedatev(&f[8]);
       +        return 1;
       +}
       +
       +int
       +logrecv(char **sender, ulong *xtime)
       +{
       +        char buf[4096];
       +
       +        for(;;){
       +                if(getline(buf, sizeof buf) == nil)
       +                        return 0;
       +                if(parselog(buf, sender, xtime))
       +                        return 1;
       +        }
       +        return -1;
       +}
       +
       +char*
       +tweakdate(char *d)
       +{
       +        char e[8];
       +
       +        /* d, date = "Mon Aug  2 23:46:55 EDT 1999" */
       +
       +        if(strlen(d) < strlen("Mon Aug  2 23:46:55 EDT 1999"))
       +                return estrdup("");
       +        if(strncmp(date, d, 4+4+3) == 0)
       +                snprint(e, sizeof e, "%.5s", d+4+4+3);        /* 23:46 */
       +        else
       +                snprint(e, sizeof e, "%.6s", d+4);        /* Aug  2 */
       +        return estrdup(e);
       +}
       +
       +Face*
       +nextface(void)
       +{
       +        int i;
       +        Face *f;
       +        Plumbmsg *m;
       +        char *t, *senderp, *showmailp, *digestp;
       +        ulong xtime;
       +
       +        f = emalloc(sizeof(Face));
       +        for(;;){
       +                if(seefd >= 0){
       +                        m = plumbrecv(seefd);
       +                        if(m == nil)
       +                                killall("error on seemail plumb port");
       +                        t = value(m->attr, "mailtype", "");
       +                        if(strcmp(t, "delete") == 0)
       +                                delete(m->data, value(m->attr, "digest", nil));
       +                        else if(strcmp(t, "new") != 0)
       +                                fprint(2, "faces: unknown plumb message type %s\n", t);
       +                        else for(i=0; i<nmaildirs; i++) {
       +                                if(strncmp(m->data,"/mail/fs/",strlen("/mail/fs/")) == 0)
       +                                        m->data += strlen("/mail/fs/");
       +                                if(strncmp(m->data, maildirs[i], strlen(maildirs[i])) == 0)
       +                                        goto Found;
       +                        }
       +                        plumbfree(m);
       +                        continue;
       +
       +                Found:
       +                        xtime = parsedate(value(m->attr, "date", date));
       +                        digestp = value(m->attr, "digest", nil);
       +                        if(alreadyseen(digestp)){
       +                                /* duplicate upas/fs can send duplicate messages */
       +                                plumbfree(m);
       +                                continue;
       +                        }
       +                        senderp = estrdup(value(m->attr, "sender", "???"));
       +                        showmailp = estrdup(m->data);
       +                        if(digestp)
       +                                digestp = estrdup(digestp);
       +                        plumbfree(m);
       +                }else{
       +                        if(logrecv(&senderp, &xtime) <= 0)
       +                                killall("error reading log file");
       +                        showmailp = estrdup("");
       +                        digestp = nil;
       +                }
       +                setname(f, senderp);
       +                f->time = xtime;
       +                f->tm = *localtime(xtime);
       +                f->str[Sshow] = showmailp;
       +                f->str[Sdigest] = digestp;
       +                return f;
       +        }
       +        return nil;
       +}
       +
       +char*
       +iline(char *data, char **pp)
       +{
       +        char *p;
       +
       +        for(p=data; *p!='\0' && *p!='\n'; p++)
       +                ;
       +        if(*p == '\n')
       +                *p++ = '\0';
       +        *pp = p;
       +        return data;
       +}
       +
       +Face*
       +dirface(char *dir, char *num)
       +{
       +        Face *f;
       +        char *from, *date;
       +        char buf[1024],  *info, *p, *digest;
       +        int n;
       +        ulong len;
       +        CFid *fid;
       +
       +#if 0
       +        /*
       +         * loadmbox leaves us in maildir, so we needn't
       +         * walk /mail/fs/mbox for each face; this makes startup
       +         * a fair bit quicker.
       +         */
       +        if(getwd(pwd, sizeof pwd) != nil && strcmp(pwd, dir) == 0)
       +                sprint(buf, "%s/info", num);
       +        else
       +                sprint(buf, "%s/%s/info", dir, num);
       +#endif
       +        sprint(buf, "%s/%s/info", dir, num);
       +        len = fsdirlen(upasfs, buf);
       +        if(len <= 0)
       +                return nil;
       +        fid = fsopen(upasfs,buf, OREAD);
       +        if(fid == nil)
       +                return nil;
       +        info = emalloc(len+1);
       +        n = fsreadn(fid, info, len);
       +        fsclose(fid);
       +        if(n < 0){
       +                free(info);
       +                return nil;
       +        }
       +        info[n] = '\0';
       +        f = emalloc(sizeof(Face));
       +        from = iline(info, &p);        /* from */
       +        iline(p, &p);        /* to */
       +        iline(p, &p);        /* cc */
       +        iline(p, &p);        /* replyto */
       +        date = iline(p, &p);        /* date */
       +        setname(f, estrdup(from));
       +        f->time = parsedate(date);
       +        f->tm = *localtime(f->time);
       +        sprint(buf, "%s/%s", dir, num);
       +        f->str[Sshow] = estrdup(buf);
       +        iline(p, &p);        /* subject */
       +        iline(p, &p);        /* mime content type */
       +        iline(p, &p);        /* mime disposition */
       +        iline(p, &p);        /* filename */
       +        digest = iline(p, &p);        /* digest */
       +        f->str[Sdigest] = estrdup(digest);
       +        free(info);
       +        return f;
       +}
 (DIR) diff --git a/src/cmd/faces/util.c b/src/cmd/faces/util.c
       t@@ -0,0 +1,42 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <draw.h>
       +#include <plumb.h>
       +#include <9pclient.h>
       +#include "faces.h"
       +
       +void*
       +emalloc(ulong sz)
       +{
       +        void *v;
       +        v = malloc(sz);
       +        if(v == nil) {
       +                fprint(2, "out of memory allocating %ld\n", sz);
       +                exits("mem");
       +        }
       +        memset(v, 0, sz);
       +        return v;
       +}
       +
       +void*
       +erealloc(void *v, ulong sz)
       +{
       +        v = realloc(v, sz);
       +        if(v == nil) {
       +                fprint(2, "out of memory allocating %ld\n", sz);
       +                exits("mem");
       +        }
       +        return v;
       +}
       +
       +char*
       +estrdup(char *s)
       +{
       +        char *t;
       +        if((t = strdup(s)) == nil) {
       +                fprint(2, "out of memory in strdup(%.10s)\n", s);
       +                exits("mem");
       +        }
       +        return t;
       +}
       +