Commit Diff


commit - 663ddde9d07417ab51239c0c4305708a1a319c62
commit + b330c942b468ab82fd8853590145187e859258cb
blob - /dev/null
blob + b5c023f59e596ad4dad8e8791571a6bac8377634 (mode 644)
--- /dev/null
+++ src/cmd/faces/dblook.c
@@ -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);
+}
blob - /dev/null
blob + 0c4f2d79b6b628a15bb8839aeed286bb06136ab1 (mode 644)
--- /dev/null
+++ src/cmd/faces/facedb.c
@@ -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;
+	}
+}
blob - /dev/null
blob + 7974a8b3230c54704a9dbb0392d1eb115c23a03f (mode 644)
--- /dev/null
+++ src/cmd/faces/faces.h
@@ -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*);
blob - /dev/null
blob + 4be56230a4a4b8ad5f05c97186204b1fa2fd513e (mode 644)
--- /dev/null
+++ src/cmd/faces/main.c
@@ -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);
+}
blob - /dev/null
blob + 58cc4d253fcaad12564b85bfd3bb3bb306f4963e (mode 644)
--- /dev/null
+++ src/cmd/faces/mkfile
@@ -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
blob - /dev/null
blob + a61e2d81c2cd75dd5ba1f11d797d8ae1b142b67d (mode 644)
--- /dev/null
+++ src/cmd/faces/plumb.c
@@ -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;
+}
blob - /dev/null
blob + 22f57549cfd7aa4c1fca773c40b643e9030d1cd0 (mode 644)
--- /dev/null
+++ src/cmd/faces/util.c
@@ -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;
+}
+