9fans - fans of the OS Plan 9 from Bell Labs
 help / color / mirror / Atom feed
* [9fans] tiny patches for cmds on the bitsy
@ 2001-05-03 10:37 nemo
  0 siblings, 0 replies; 2+ messages in thread
From: nemo @ 2001-05-03 10:37 UTC (permalink / raw)
  To: 9fans

[-- Attachment #1: Type: text/plain, Size: 1064 bytes --]

Hi, 

  some silly changes I made:

  The first one changes acme Mail to include the option `-S' to: 
	- avoid  showing message parts before the message is shown
	- use a shorter summary for mails (not too much screen space on the ipaq)
  It also changes Mail to avoid reading body files for mails before the
  information is really needed. Together, these changes allow me to use
  acme on the ipaq to read my mail w/o having to read all the
  attachments.

  The first one also includes the `-s' option, which  is like `-S', but
  does not try so hard to make the summary fit on one line of the bitsy
  display.  I'm using `-s' on the laptop and `-S' on the bitsy.

  The second diff changes stats to show the bitsy battery too. The
/dev/battery file is different and has a different format. What I do is
to open /dev/battery if the regular /mnt/apm/battery is not found. This
is a kludge AFAIK; I think that /mnt/apm/battery and /dev/battery
should be actually the same file, using the same format. But for now,
this makes stats work.



[-- Attachment #2: diff --]
[-- Type: text/plain, Size: 3336 bytes --]

/acme/mail/src/dat.h:56 a /usr/nemo/src/9/sys/src/cmd/acmemail/dat.h:57,58
> 	uchar	recursed;
> 	uchar	level;
/acme/mail/src/dat.h:138 a /usr/nemo/src/9/sys/src/cmd/acmemail/dat.h:141
> extern	int		shortmenu;
Only in /usr/nemo/src/9/sys/src/cmd/acmemail: diff
Only in /usr/nemo/src/9/sys/src/cmd/acmemail: guide
/acme/mail/src/mail.c:29 a /usr/nemo/src/9/sys/src/cmd/acmemail/mail.c:30,31
> int			shortmenu;
> 
/acme/mail/src/mail.c:33 c /usr/nemo/src/9/sys/src/cmd/acmemail/mail.c:35
< 	threadprint(2, "usage: Mail [mailboxname [directoryname]]\n");
---
> 	threadprint(2, "usage: Mail [-sS] [mailboxname [directoryname]]\n");
/acme/mail/src/mail.c:47 a /usr/nemo/src/9/sys/src/cmd/acmemail/mail.c:50
> 
/acme/mail/src/mail.c:59 a /usr/nemo/src/9/sys/src/cmd/acmemail/mail.c:63
> 	shortmenu = 0;
/acme/mail/src/mail.c:60 a /usr/nemo/src/9/sys/src/cmd/acmemail/mail.c:65,72
> 	case 's':
> 		shortmenu = 1;
> 		break;
> 	case 'S':
> 		shortmenu = 2;
> 		break;
> 	default:
> 		usage();
/acme/mail/src/mail.c:118 a /usr/nemo/src/9/sys/src/cmd/acmemail/mail.c:131
> 	mbox.level= 0;
/acme/mail/src/mesg.c:161 a /usr/nemo/src/9/sys/src/cmd/acmemail/mesg.c:162
> 	mbox->recursed = 1;
/acme/mail/src/mesg.c:180 a /usr/nemo/src/9/sys/src/cmd/acmemail/mesg.c:182,183
> 	m->level= mbox->level+1;
> 	m->recursed = 0;
/acme/mail/src/mesg.c:195 c /usr/nemo/src/9/sys/src/cmd/acmemail/mesg.c:198,202
< 	readmbox(m, dir, m->name);
---
> 
> 	if (m->level != 1){
> 		m->recursed = 1;
> 		readmbox(m, dir, m->name); 
> 	}
/acme/mail/src/mesg.c:269 a /usr/nemo/src/9/sys/src/cmd/acmemail/mesg.c:277,296
> 	if (ind == 0 && shortmenu) {
> 		char fmt[80];
> 		char s[80];
> 		int len;
> 		int lens;
> 
> 		len = (shortmenu > 1) ? 10 : 30;
> 		lens = (shortmenu > 1) ? 25 : 30;
> 		if (ind == 0 && m->subject[0] == '\0'){
> 			snprint(fmt, 80, " %%-%d.%ds", len, len);
> 			snprint(s, 80, fmt, m->fromcolon);
> 		}else{
> 			snprint(fmt, 80, " %%-%d.%ds  %%-%d.%ds", len, len, lens, lens);
> 			snprint(s, 80, fmt, m->fromcolon, m->subject);
> 		}
> 		i = estrdup(s);
> 
> 		return i;
> 	} 
> 
/acme/mail/src/mesg.c:289 c /usr/nemo/src/9/sys/src/cmd/acmemail/mesg.c:316
< mesgmenu0(Window *w, Message *mbox, char *realdir, char *dir, int ind, Biobuf *fd, int onlyone)
---
> mesgmenu0(Window *w, Message *mbox, char *realdir, char *dir, int ind, Biobuf *fd, int onlyone, int dotail)
/acme/mail/src/mesg.c:309,310 c /usr/nemo/src/9/sys/src/cmd/acmemail/mesg.c:336,337
< 		if(m->tail)
< 			mesgmenu0(w, m, realdir, name, ind+1, fd, 0);
---
> 		if(dotail && m->tail)
> 			mesgmenu0(w, m, realdir, name, ind+1, fd, 0, dotail);
/acme/mail/src/mesg.c:325 c /usr/nemo/src/9/sys/src/cmd/acmemail/mesg.c:352
< 	mesgmenu0(w, mbox, mbox->name, "", 0, w->body, 0);
---
> 	mesgmenu0(w, mbox, mbox->name, "", 0, w->body, 0, !shortmenu);
/acme/mail/src/mesg.c:339 c /usr/nemo/src/9/sys/src/cmd/acmemail/mesg.c:366
< 	mesgmenu0(w, mbox, mbox->name, "", 0, b, 1);
---
> 	mesgmenu0(w, mbox, mbox->name, "", 0, b, 1, !shortmenu);
/acme/mail/src/mesg.c:491 d /usr/nemo/src/9/sys/src/cmd/acmemail/mesg.c:517
< 
/acme/mail/src/mesg.c:812 a /usr/nemo/src/9/sys/src/cmd/acmemail/mesg.c:839,842
> 	if(m->level == 1 && m->recursed == 0){
> 		m->recursed = 1;
> 		readmbox(m, rootdir, m->name);
> 	}

[-- Attachment #3: diff-stats --]
[-- Type: text/plain, Size: 1132 bytes --]

/sys/src/cmd/stats.c:48 c stats.c:48
< 	/* /mnt/apm/battery */
---
> 	/* /mnt/apm/battery | /dev/battery */
/sys/src/cmd/stats.c:59 a stats.c:60
> 	int		bitsybatfd;
/sys/src/cmd/stats.c:576,577 c stats.c:577,586
< 	if(loadbuf(m, &m->batteryfd) && readnums(m, nelem(m->batterystats), a, 0))
< 		memmove(m->batterystats, a, sizeof(m->batterystats));
---
> 	m->bitsybatfd = -1;
> 	if (m->batteryfd >= 0){
> 		if(loadbuf(m, &m->batteryfd) && readnums(m, nelem(m->batterystats), a, 0))
> 			memmove(m->batterystats, a, sizeof(m->batterystats));
> 	}else{
> 		snprint(buf, sizeof buf, "%s/dev/battery", mpt);
> 		m->bitsybatfd = open(buf, OREAD);
> 		if(loadbuf(m, &m->bitsybatfd) && readnums(m, 1, a, 0))
> 			memmove(m->batterystats, a, sizeof(m->batterystats));
> 	}
/sys/src/cmd/stats.c:654 a stats.c:664,665
> 	if(needbattery(init) && loadbuf(m, &m->bitsybatfd) && readnums(m, 1, a, 0))
> 		memmove(m->batterystats, a, sizeof(m->batterystats));
/sys/src/cmd/stats.c:783 c stats.c:794,797
< 	*vmax = 100;
---
> 	if (m->bitsybatfd >= 0)
> 		*vmax = 184;		// at least on my bitsy...
> 	else
> 		*vmax = 100;

^ permalink raw reply	[flat|nested] 2+ messages in thread

* Re: [9fans] tiny patches for cmds on the bitsy
@ 2001-05-03 13:24 nemo
  0 siblings, 0 replies; 2+ messages in thread
From: nemo @ 2001-05-03 13:24 UTC (permalink / raw)
  To: 9fans

[-- Attachment #1: Type: text/plain, Size: 122 bytes --]

Probably more people in 9fans also hate applying diffs, 
So here you have the full files w/ the diffs applied.

hth


[-- Attachment #2: Type: message/rfc822, Size: 784 bytes --]

From: "rob pike" <rob@plan9.bell-labs.com>
To: nemo@gsyc.escet.urjc.es
Subject: Re: [9fans] tiny patches for cmds on the bitsy
Date: Thu, 3 May 2001 09:16:31 -0400
Message-ID: <200105031316.PAA14066@gsyc.escet.urjc.es>

can you just send me the source files? i hate applying diffs.
thanks.

-rob

[-- Attachment #3: stats.c --]
[-- Type: text/plain, Size: 26326 bytes --]

#include <u.h>
#include <libc.h>
#include <ctype.h>
#include <auth.h>
#include <fcall.h>
#include <draw.h>
#include <event.h>

#define	MAXNUM	8	/* maximum number of numbers on data line */

typedef struct Graph		Graph;
typedef struct Machine	Machine;

struct Graph
{
	int		colindex;
	Rectangle	r;
	int		*data;
	int		ndata;
	char		*label;
	void		(*newvalue)(Machine*, long*, long*, int);
	void		(*update)(Graph*, long, long);
	Machine	*mach;
	int		overflow;
	Image	*overtmp;
};

enum
{
	/* /dev/swap */
	Mem		= 0,
	Maxmem,
	Swap,
	Maxswap,
	/* /dev/sysstats */
	Procno	= 0,
	Context,
	Interrupt,
	Syscall,
	Fault,
	TLBfault,
	TLBpurge,
	Load,
	/* /net/ether0/0/stats */
	In		= 0,
	Out,
	Err0,
	/* /mnt/apm/battery | /dev/battery */
	Battery,
};

struct Machine
{
	char		*name;
	int		remote;
	int		statsfd;
	int		swapfd;
	int		etherfd;
	int		batteryfd;
	int		bitsybatfd;
	int		disable;

	long		devswap[4];
	long		devsysstat[8];
	long		prevsysstat[8];
	int		nproc;
	long		netetherstats[8];
	long		prevetherstats[8];
	long		batterystats[2];

	char		buf[1024];
	char		*bufp;
	char		*ebufp;
};

enum
{
	Mainproc,
	Mouseproc,
	NPROC,
};

enum
{
	Ncolor	= 6,
	Ysqueeze	= 2,	/* vertical squeezing of label text */
	Labspace	= 2,	/* room around label */
	Dot		= 2,	/* height of dot */
	Opwid	= 5,	/* strlen("add  ") or strlen("drop ") */
	Nlab		= 3,	/* max number of labels on y axis */
	Lablen	= 16,	/* max length of label */
	Lx		= 4,	/* label tick length */
};

enum Menu2
{
	Mcontext,
	Mether,
	Methererr,
	Metherin,
	Metherout,
	Mfault,
	Mintr,
	Mload,
	Mmem,
	Mswap,
	Msyscall,
	Mtlbmiss,
	Mtlbpurge,
	Mbattery,
	Nmenu2,
};

char	*menu2str[Nmenu2+1] = {
	"add  context ",
	"add  ether   ",
	"add  ethererr",
	"add  etherin ",
	"add  etherout",
	"add  fault   ",
	"add  intr    ",
	"add  load    ",
	"add  mem     ",
	"add  swap    ",
	"add  syscall ",
	"add  tlbmiss ",
	"add  tlbpurge",
	"add  battery ",
	nil,
};


void	contextval(Machine*, long*, long*, int),
	etherval(Machine*, long*, long*, int),
	ethererrval(Machine*, long*, long*, int),
	etherinval(Machine*, long*, long*, int),
	etheroutval(Machine*, long*, long*, int),
	faultval(Machine*, long*, long*, int),
	intrval(Machine*, long*, long*, int),
	loadval(Machine*, long*, long*, int),
	memval(Machine*, long*, long*, int),
	swapval(Machine*, long*, long*, int),
	syscallval(Machine*, long*, long*, int),
	tlbmissval(Machine*, long*, long*, int),
	tlbpurgeval(Machine*, long*, long*, int),
	batteryval(Machine*, long*, long*, int);

Menu	menu2 = {menu2str, nil};
int		present[Nmenu2];
void		(*newvaluefn[Nmenu2])(Machine*, long*, long*, int init) = {
	contextval,
	etherval,
	ethererrval,
	etherinval,
	etheroutval,
	faultval,
	intrval,
	loadval,
	memval,
	swapval,
	syscallval,
	tlbmissval,
	tlbpurgeval,
	batteryval,
};

Image	*cols[Ncolor][3];
Graph	*graph;
Machine	*mach;
Font		*mediumfont;
char		*mysysname;
char		argchars[] = "bceEfimlnpstw";
int		pids[NPROC];
int 		parity;	/* toggled to avoid patterns in textured background */
int		nmach;
int		ngraph;	/* totaly number is ngraph*nmach */
double	scale = 1.0;
int		logscale = 0;
int		ylabels = 0;

char		*procnames[NPROC] = {"main", "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");
	exits(s);
}

void*
emalloc(ulong sz)
{
	void *v;
	v = malloc(sz);
	if(v == nil) {
		fprint(2, "stats: out of memory allocating %ld: %r\n", sz);
		killall("mem");
	}
	memset(v, 0, sz);
	return v;
}

void*
erealloc(void *v, ulong sz)
{
	v = realloc(v, sz);
	if(v == nil) {
		fprint(2, "stats: out of memory reallocating %ld: %r\n", sz);
		killall("mem");
	}
	return v;
}

char*
estrdup(char *s)
{
	char *t;
	if((t = strdup(s)) == nil) {
		fprint(2, "stats: out of memory in strdup(%.10s): %r\n", s);
		killall("mem");
	}
	return t;
}

void
mkcol(int i, int c0, int c1, int c2)
{
	cols[i][0] = allocimagemix(display, c0, DWhite);
	cols[i][1] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, c1);
	cols[i][2] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, c2);
}

void
colinit(void)
{
	mediumfont = openfont(display, "/lib/font/bit/pelm/latin1.8.font");
	if(mediumfont == nil)
		mediumfont = font;

	/* Peach */
	mkcol(0, 0xFFAAAAFF, 0xFFAAAAFF, 0xBB5D5DFF);
	/* Aqua */
	mkcol(1, DPalebluegreen, DPalegreygreen, DPurpleblue);
	/* Yellow */
	mkcol(2, DPaleyellow, DDarkyellow, DYellowgreen);
	/* Green */
	mkcol(3, DPalegreen, DMedgreen, DDarkgreen);
	/* Blue */
	mkcol(4, 0x00AAFFFF, 0x00AAFFFF, 0x0088CCFF);
	/* Grey */
	cols[5][0] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0xEEEEEEFF);
	cols[5][1] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0xCCCCCCFF);
	cols[5][2] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0x888888FF);
}

int
loadbuf(Machine *m, int *fd)
{
	int n;


	if(*fd < 0)
		return 0;
	seek(*fd, 0, 0);
	n = read(*fd, m->buf, sizeof m->buf);
	if(n <= 0){
		close(*fd);
		*fd = -1;
		return 0;
	}
	m->bufp = m->buf;
	m->ebufp = m->buf+n;
	return 1;
}

void
label(Point p, int dy, char *text)
{
	char *s;
	Rune r[2];
	int w, maxw, maxy;

	p.x += Labspace;
	maxy = p.y+dy;
	maxw = 0;
	r[1] = '\0';
	for(s=text; *s; ){
		if(p.y+mediumfont->height-Ysqueeze > maxy)
			break;
		w = chartorune(r, s);
		s += w;
		w = runestringwidth(mediumfont, r);
		if(w > maxw)
			maxw = w;
		runestring(screen, p, display->black, ZP, mediumfont, r);
		p.y += mediumfont->height-Ysqueeze;
	}
}

Point
paritypt(int x)
{
	return Pt(x+parity, 0);
}

Point
datapoint(Graph *g, int x, long v, long vmax)
{
	Point p;
	double y;

	p.x = x;
	y = ((double)v)/(vmax*scale);
	if(logscale){
		/*
		 * Arrange scale to cover a factor of 1000.
		 * vmax corresponds to the 100 mark.
		 * 10*vmax is the top of the scale.
		 */
		if(y <= 0.)
			y = 0;
		else{
			y = log10(y);
			/* 1 now corresponds to the top; -2 to the bottom; rescale */
			y = (y+2.)/3.;
		}
	}
	p.y = g->r.max.y - Dy(g->r)*y - Dot;
	if(p.y < g->r.min.y)
		p.y = g->r.min.y;
	if(p.y > g->r.max.y-Dot)
		p.y = g->r.max.y-Dot;
	return p;
}

void
drawdatum(Graph *g, int x, long prev, long v, long vmax)
{
	int c;
	Point p, q;

	c = g->colindex;
	p = datapoint(g, x, v, vmax);
	q = datapoint(g, x, prev, vmax);
	if(p.y < q.y){
		draw(screen, Rect(p.x, g->r.min.y, p.x+1, p.y), cols[c][0], nil, paritypt(p.x));
		draw(screen, Rect(p.x, p.y, p.x+1, q.y+Dot), cols[c][2], nil, ZP);
		draw(screen, Rect(p.x, q.y+Dot, p.x+1, g->r.max.y), cols[c][1], nil, ZP);
	}else{
		draw(screen, Rect(p.x, g->r.min.y, p.x+1, q.y), cols[c][0], nil, paritypt(p.x));
		draw(screen, Rect(p.x, q.y, p.x+1, p.y+Dot), cols[c][2], nil, ZP);
		draw(screen, Rect(p.x, p.y+Dot, p.x+1, g->r.max.y), cols[c][1], nil, ZP);
	}

}

void
redraw(Graph *g, int vmax)
{
	int i, c;

	c = g->colindex;
	draw(screen, g->r, cols[c][0], nil, paritypt(g->r.min.x));
	for(i=1; i<Dx(g->r); i++)
		drawdatum(g, g->r.max.x-i, g->data[i-1], g->data[i], vmax);
	drawdatum(g, g->r.min.x, g->data[i], g->data[i], vmax);
	g->overflow = 0;
}

void
update1(Graph *g, long v, long vmax)
{
	char buf[32];
	int overflow;

	if(g->overflow && g->overtmp!=nil)
		draw(screen, g->overtmp->r, g->overtmp, nil, g->overtmp->r.min);
	draw(screen, g->r, screen, nil, Pt(g->r.min.x+1, g->r.min.y));
	drawdatum(g, g->r.max.x-1, g->data[0], v, vmax);
	memmove(g->data+1, g->data, (g->ndata-1)*sizeof(g->data[0]));
	g->data[0] = v;
	g->overflow = 0;
	if(logscale)
		overflow = (v>10*vmax*scale);
	else
		overflow = (v>vmax*scale);
	if(overflow && g->overtmp!=nil){
		g->overflow = 1;
		draw(g->overtmp, g->overtmp->r, screen, nil, g->overtmp->r.min);
		sprint(buf, "%ld", v);
		string(screen, g->overtmp->r.min, display->black, ZP, mediumfont, buf);
	}
}

/* read one line of text from buffer and process integers */
int
readnums(Machine *m, int n, long *a, int spanlines)
{
	int i;
	char *p, *ep;

	if(spanlines)
		ep = m->ebufp;
	else
		for(ep=m->bufp; ep<m->ebufp; ep++)
			if(*ep == '\n')
				break;
	p = m->bufp;
	for(i=0; i<n && p<ep; i++){
		while(p<ep && !isdigit(*p))
			p++;
		if(p == ep)
			break;
		a[i] = strtol(p, &p, 10);
	}
	if(ep < m->ebufp)
		ep++;
	m->bufp = ep;
	return i == n;
}

/* Network on fd1, mount driver on fd0 */
static int
filter(int fd)
{
	int p[2];

	if(pipe(p) < 0){
		fprint(2, "stats: can't pipe: %r\n");
		killall("pipe");
	}

	switch(rfork(RFNOWAIT|RFPROC|RFFDG)) {
	case -1:
		sysfatal("rfork record module");
	case 0:
		dup(fd, 1);
		close(fd);
		dup(p[0], 0);
		close(p[0]);
		close(p[1]);
		execl("/bin/aux/fcall", "fcall", 0);
		fprint(2, "stats: can't exec fcall: %r\n");
		killall("fcall");
	default:
		close(fd);
		close(p[0]);
	}
	return p[1];	
}

/*
 * 9fs
 */
int
connect9fs(char *addr)
{
	char dir[4*NAMELEN], *na;
	int fd;

	fprint(2, "connect9fs...");
	na = netmkaddr(addr, 0, "9fs");

	fprint(2, "dial %s...", na);
	if((fd = dial(na, 0, dir, 0)) < 0)
		return -1;

	fprint(2, "dir %s...", dir);
	if(strstr(dir, "tcp"))
		fd = filter(fd);
	return fd;
}

/*
 * exportfs
 */
int 
connectexportfs(char *addr)
{
	char buf[ERRLEN], dir[4*NAMELEN], *na;
	int fd, n;
	char *tree;

	tree = "/";
	na = netmkaddr(addr, 0, "exportfs");
	if((fd = dial(na, 0, dir, 0)) < 0)
		return -1;

	if(auth(fd) < 0){
		close(fd);
		return -1;
	}

	n = write(fd, tree, strlen(tree));
	if(n < 0){
		close(fd);
		return -1;
	}

	strcpy(buf, "can't read tree");
	n = read(fd, buf, sizeof buf - 1);
	if(n!=2 || buf[0]!='O' || buf[1]!='K'){
		buf[sizeof buf - 1] = '\0';
		werrstr("bad remote tree: %s\n", buf);
		close(fd);
		return -1;
	}

	if(strstr(dir, "tcp"))
		fd = filter(fd);

	return fd;
}

void
initmach(Machine *m, char *name)
{
	int n, fd;
	long a[MAXNUM];
	char *p, mpt[256], buf[256];

	p = strchr(name, '!');
	if(p){
		p++;
		m->name = estrdup(p+1);
	}else
		p = name;
	m->name = estrdup(p);
	m->remote = (strcmp(p, mysysname) != 0);
	if(m->remote == 0)
		strcpy(mpt, "");
	else{
		snprint(mpt, sizeof mpt, "/n/%s", p);
		fd = connectexportfs(name);
		if(fd < 0){
			fprint(2, "can't connect to %s: %r\n", name);
			killall("connect");
		}
		if(mount(fd, mpt, MREPL, "") < 0){
			fprint(2, "stats: mount %s on %s failed (%r); trying /n/sid\n", name, mpt);
			strcpy(mpt, "/n/sid");
			if(mount(fd, mpt, MREPL, "") < 0){
				fprint(2, "stats: mount %s on %s failed: %r\n", name, mpt);
				killall("mount");
			}
		}
	}

	snprint(buf, sizeof buf, "%s/dev/swap", mpt);
	m->swapfd = open(buf, OREAD);
	if(loadbuf(m, &m->swapfd) && readnums(m, nelem(m->devswap), a, 0))
		memmove(m->devswap, a, sizeof m->devswap);
	else
		m->devswap[Maxmem] = m->devswap[Maxswap] = 100;

	snprint(buf, sizeof buf, "%s/dev/sysstat", mpt);
	m->statsfd = open(buf, OREAD);
	if(loadbuf(m, &m->statsfd)){
		for(n=0; readnums(m, nelem(m->devsysstat), a, 0); n++)
			;
		m->nproc = n;
	}else
		m->nproc = 1;

	snprint(buf, sizeof buf, "%s/net/ether0/0/stats", mpt);
	m->etherfd = open(buf, OREAD);
	if(loadbuf(m, &m->etherfd) &&  readnums(m, nelem(m->netetherstats), a, 1))
		memmove(m->netetherstats, a, sizeof m->netetherstats);

	snprint(buf, sizeof buf, "%s/mnt/apm/battery", mpt);
	m->batteryfd = open(buf, OREAD);
	m->bitsybatfd = -1;
	if (m->batteryfd >= 0){
		if(loadbuf(m, &m->batteryfd) && readnums(m, nelem(m->batterystats), a, 0))
			memmove(m->batterystats, a, sizeof(m->batterystats));
	}else{
		snprint(buf, sizeof buf, "%s/dev/battery", mpt);
		m->bitsybatfd = open(buf, OREAD);
		if(loadbuf(m, &m->bitsybatfd) && readnums(m, 1, a, 0))
			memmove(m->batterystats, a, sizeof(m->batterystats));
	}
}

jmp_buf catchalarm;

void
alarmed(void *a, char *s)
{
	if(strcmp(s, "alarm") == 0)
		notejmp(a, catchalarm, 1);
	noted(NDFLT);
}

int
needswap(int init)
{
	return init | present[Mmem] | present[Mswap];
}


int
needstat(int init)
{
	return init | present[Mcontext]  | present[Mfault] | present[Mintr] | present[Mload] |
		present[Msyscall] | present[Mtlbmiss] | present[Mtlbpurge];
}


int
needether(int init)
{
	return init | present[Mether] | present[Metherin] | present[Metherout] | present[Methererr];
}

int
needbattery(int init)
{
	return init | present[Mbattery];
}

void
readmach(Machine *m, int init)
{
	int n, i;
	long a[8];
	char buf[32];

	if(m->remote && (m->disable || setjmp(catchalarm))){
		if(m->disable == 0){
			snprint(buf, sizeof buf, "%s(dead)", m->name);
			m->name = estrdup(buf);
			if(display != nil)	/* else we're still initializing */
				eresized(0);
		}
		m->disable = 1;
		memmove(m->devsysstat, m->prevsysstat, sizeof m->devsysstat);
		memmove(m->netetherstats, m->prevetherstats, sizeof m->netetherstats);
		return;
	}
	if(m->remote){
		notify(alarmed);
		alarm(5000);
	}
	if(needswap(init) && loadbuf(m, &m->swapfd) && readnums(m, nelem(m->devswap), a, 0))
		memmove(m->devswap, a, sizeof m->devswap);
	if(needstat(init) && loadbuf(m, &m->statsfd)){
		memmove(m->prevsysstat, m->devsysstat, sizeof m->devsysstat);
		memset(m->devsysstat, 0, sizeof m->devsysstat);
		for(n=0; n<m->nproc && readnums(m, nelem(m->devsysstat), a, 0); n++)
			for(i=0; i<nelem(m->devsysstat); i++)
				m->devsysstat[i] += a[i];
	}
	if(needether(init) && loadbuf(m, &m->etherfd) && readnums(m, nelem(m->netetherstats), a, 1)){
		memmove(m->prevetherstats, m->netetherstats, sizeof m->netetherstats);
		memmove(m->netetherstats, a, sizeof m->netetherstats);
	}
	if(needbattery(init) && loadbuf(m, &m->batteryfd) && readnums(m, nelem(m->batterystats), a, 0))
		memmove(m->batterystats, a, sizeof(m->batterystats));
	if(needbattery(init) && loadbuf(m, &m->bitsybatfd) && readnums(m, 1, a, 0))
		memmove(m->batterystats, a, sizeof(m->batterystats));

	if(m->remote){
		alarm(0);
		notify(nil);
	}
}

void
memval(Machine *m, long *v, long *vmax, int)
{
	*v = m->devswap[Mem];
	*vmax = m->devswap[Maxmem];
}

void
swapval(Machine *m, long *v, long *vmax, int)
{
	*v = m->devswap[Swap];
	*vmax = m->devswap[Maxswap];
}

void
contextval(Machine *m, long *v, long *vmax, int init)
{
	*v = m->devsysstat[Context]-m->prevsysstat[Context];
	*vmax = 1000*m->nproc;
	if(init)
		*vmax = 1000;
}

void
intrval(Machine *m, long *v, long *vmax, int init)
{
	*v = m->devsysstat[Interrupt]-m->prevsysstat[Interrupt];
	*vmax = 1000*m->nproc;
	if(init)
		*vmax = 1000;
}

void
syscallval(Machine *m, long *v, long *vmax, int init)
{
	*v = m->devsysstat[Syscall]-m->prevsysstat[Syscall];
	*vmax = 1000*m->nproc;
	if(init)
		*vmax = 1000;
}

void
faultval(Machine *m, long *v, long *vmax, int init)
{
	*v = m->devsysstat[Fault]-m->prevsysstat[Fault];
	*vmax = 1000*m->nproc;
	if(init)
		*vmax = 1000;
}

void
tlbmissval(Machine *m, long *v, long *vmax, int init)
{
	*v = m->devsysstat[TLBfault]-m->prevsysstat[TLBfault];
	*vmax = 10*m->nproc;
	if(init)
		*vmax = 10;
}

void
tlbpurgeval(Machine *m, long *v, long *vmax, int init)
{
	*v = m->devsysstat[TLBpurge]-m->prevsysstat[TLBpurge];
	*vmax = 10*m->nproc;
	if(init)
		*vmax = 10;
}

void
loadval(Machine *m, long *v, long *vmax, int init)
{
	*v = m->devsysstat[Load];
	*vmax = 1000*m->nproc;
	if(init)
		*vmax = 1000;
}

void
etherval(Machine *m, long *v, long *vmax, int init)
{
	*v = m->netetherstats[In]-m->prevetherstats[In] + m->netetherstats[Out]-m->prevetherstats[Out];
	*vmax = 1000*m->nproc;
	if(init)
		*vmax = 1000;
}

void
etherinval(Machine *m, long *v, long *vmax, int init)
{
	*v = m->netetherstats[In]-m->prevetherstats[In];
	*vmax = 1000*m->nproc;
	if(init)
		*vmax = 1000;
}

void
etheroutval(Machine *m, long *v, long *vmax, int init)
{
	*v = m->netetherstats[Out]-m->prevetherstats[Out];
	*vmax = 1000*m->nproc;
	if(init)
		*vmax = 1000;
}

void
ethererrval(Machine *m, long *v, long *vmax, int init)
{
	int i;

	*v = 0;
	for(i=Err0; i<nelem(m->netetherstats); i++)
		*v += m->netetherstats[i];
	*vmax = 10*m->nproc;
	if(init)
		*vmax = 10;
}

void
batteryval(Machine *m, long *v, long *vmax, int)
{
	*v = m->batterystats[0];
	if (m->bitsybatfd >= 0)
		*vmax = 184;		// at least on my bitsy...
	else
		*vmax = 100;
}

void
usage(void)
{
	fprint(2, "usage: stats [-S scale] [-LY] [-%s] [machine...]\n", argchars);
	exits("usage");
}

void
addgraph(int n)
{
	Graph *g, *ograph;
	int i, j;
	static int nadd;

	if(n > nelem(menu2str))
		abort();
	/* avoid two adjacent graphs of same color */
	if(ngraph>0 && graph[ngraph-1].colindex==nadd%Ncolor)
		nadd++;
	ograph = graph;
	graph = emalloc(nmach*(ngraph+1)*sizeof(Graph));
	for(i=0; i<nmach; i++)
		for(j=0; j<ngraph; j++)
			graph[i*(ngraph+1)+j] = ograph[i*ngraph+j];
	free(ograph);
	ngraph++;
	for(i=0; i<nmach; i++){
		g = &graph[i*ngraph+(ngraph-1)];
		memset(g, 0, sizeof(Graph));
		g->label = menu2str[n]+Opwid;
		g->newvalue = newvaluefn[n];
		g->update = update1;	/* no other update functions yet */
		g->mach = &mach[i];
		g->colindex = nadd%Ncolor;
	}
	present[n] = 1;
	nadd++;
}

void
dropgraph(int which)
{
	Graph *ograph;
	int i, j, n;

	if(which > nelem(menu2str))
		abort();
	/* convert n to index in graph table */
	n = -1;
	for(i=0; i<ngraph; i++)
		if(strcmp(menu2str[which]+Opwid, graph[i].label) == 0){
			n = i;
			break;
		}
	if(n < 0){
		fprint(2, "stats: internal error can't drop graph\n");
		killall("error");
	}
	ograph = graph;
	graph = emalloc(nmach*(ngraph-1)*sizeof(Graph));
	for(i=0; i<nmach; i++){
		for(j=0; j<n; j++)
			graph[i*(ngraph-1)+j] = ograph[i*ngraph+j];
		free(ograph[i*ngraph+j].data);
		freeimage(ograph[i*ngraph+j].overtmp);
		for(j++; j<ngraph; j++)
			graph[i*(ngraph-1)+j-1] = ograph[i*ngraph+j];
	}
	free(ograph);
	ngraph--;
	present[which] = 0;
}

void
addmachine(char *name)
{
	if(ngraph > 0){
		fprint(2, "stats: internal error: ngraph>0 in addmachine()\n");
		usage();
	}
	if(mach == nil)
		nmach = 0;	/* a little dance to get us started with local machine by default */
	mach = erealloc(mach, (nmach+1)*sizeof(Machine));
	memset(mach+nmach, 0, sizeof(Machine));
	initmach(mach+nmach, name);
	nmach++;
}

void
labelstrs(Graph *g, char strs[Nlab][Lablen], int *np)
{
	int j;
	long v, vmax;

	g->newvalue(g->mach, &v, &vmax, 1);
	if(logscale){
		for(j=1; j<=2; j++)
			sprint(strs[j-1], "%g", scale*pow(10., j)*(double)vmax/100.);
		*np = 2;
	}else{
		for(j=1; j<=3; j++)
			sprint(strs[j-1], "%g", scale*(double)j*(double)vmax/4.0);
		*np = 3;
	}
}

int
labelwidth(void)
{
	int i, j, n, w, maxw;
	char strs[Nlab][Lablen];

	maxw = 0;
	for(i=0; i<ngraph; i++){
		/* choose value for rightmost graph */
		labelstrs(&graph[ngraph*(nmach-1)+i], strs, &n);
		for(j=0; j<n; j++){
			w = stringwidth(mediumfont, strs[j]);
			if(w > maxw)
				maxw = w;
		}
	}
	return maxw;
}

void
resize(void)
{
	int i, j, k, n, startx, starty, x, y, dx, dy, ly, ondata, maxx, wid, nlab;
	Graph *g;
	Rectangle machr, r;
	long v, vmax;
	char buf[128], labs[Nlab][Lablen];

	draw(screen, screen->r, display->white, nil, ZP);

	/* label left edge */
	x = screen->r.min.x;
	y = screen->r.min.y + Labspace+mediumfont->height+Labspace;
	dy = (screen->r.max.y - y)/ngraph;
	dx = Labspace+stringwidth(mediumfont, "0")+Labspace;
	startx = x+dx+1;
	starty = y;
	for(i=0; i<ngraph; i++,y+=dy){
		draw(screen, Rect(x, y-1, screen->r.max.x, y), display->black, nil, ZP);
		draw(screen, Rect(x, y, x+dx, screen->r.max.y), cols[graph[i].colindex][0], nil, paritypt(x));
		label(Pt(x, y), dy, graph[i].label);
		draw(screen, Rect(x+dx, y, x+dx+1, screen->r.max.y), cols[graph[i].colindex][2], nil, ZP);
	}

	/* label top edge */
	dx = (screen->r.max.x - startx)/nmach;
	for(x=startx, i=0; i<nmach; i++,x+=dx){
		draw(screen, Rect(x-1, starty-1, x, screen->r.max.y), display->black, nil, ZP);
		j = dx/stringwidth(mediumfont, "0");
		n = mach[i].nproc;
		if(n>1 && j>=1+3+(n>10)+(n>100)){	/* first char of name + (n) */
			j -= 3+(n>10)+(n>100);
			if(j <= 0)
				j = 1;
			snprint(buf, sizeof buf, "%.*s(%d)", j, mach[i].name, n);
		}else
			snprint(buf, sizeof buf, "%.*s", j, mach[i].name);
		string(screen, Pt(x+Labspace, screen->r.min.y + Labspace), display->black, ZP, mediumfont, buf);
	}

	maxx = screen->r.max.x;

	/* label right, if requested */
	if(ylabels && dy>Nlab*(mediumfont->height+1)){
		wid = labelwidth();
		if(wid < (maxx-startx)-30){
			/* else there's not enough room */
			maxx -= 1+Lx+wid;
			draw(screen, Rect(maxx, starty, maxx+1, screen->r.max.y), display->black, nil, ZP);
			y = starty;
			for(j=0; j<ngraph; j++, y+=dy){
				/* choose value for rightmost graph */
				g = &graph[ngraph*(nmach-1)+j];
				labelstrs(g, labs, &nlab);
				r = Rect(maxx+1, y, screen->r.max.x, y+dy-1);
				if(j == ngraph-1)
					r.max.y = screen->r.max.y;
				draw(screen, r, cols[g->colindex][0], nil, paritypt(r.min.x));
				for(k=0; k<nlab; k++){
					ly = y + (dy*(nlab-k)/(nlab+1));
					draw(screen, Rect(maxx+1, ly, maxx+1+Lx, ly+1), display->black, nil, ZP);
					ly -= mediumfont->height/2;
					string(screen, Pt(maxx+1+Lx, ly), display->black, ZP, mediumfont, labs[k]);
				}
			}
		}
	}

	/* create graphs */
	for(i=0; i<nmach; i++){
		machr = Rect(startx+i*dx, starty, maxx, screen->r.max.y);
		if(i < nmach-1)
			machr.max.x = startx+(i+1)*dx - 1;
		y = starty;
		for(j=0; j<ngraph; j++, y+=dy){
			g = &graph[i*ngraph+j];
			/* allocate data */
			ondata = g->ndata;
			g->ndata = Dx(machr)+1;	/* may be too many if label will be drawn here; so what? */
			g->data = erealloc(g->data, g->ndata*sizeof(long));
			if(g->ndata > ondata)
				memset(g->data+ondata, 0, (g->ndata-ondata)*sizeof(long));
			/* set geometry */
			g->r = machr;
			g->r.min.y = y;
			g->r.max.y = y+dy - 1;
			if(j == ngraph-1)
				g->r.max.y = screen->r.max.y;
			draw(screen, g->r, cols[g->colindex][0], nil, paritypt(g->r.min.x));
			g->overflow = 0;
			r = g->r;
			r.max.y = r.min.y+mediumfont->height;
			r.max.x = r.min.x+stringwidth(mediumfont, "9999999");
			freeimage(g->overtmp);
			g->overtmp = nil;
			if(r.max.x <= g->r.max.x)
				g->overtmp = allocimage(display, r, screen->chan, 0, -1);
			g->newvalue(g->mach, &v, &vmax, 0);
			redraw(g, vmax);
		}
	}

	flushimage(display, 1);
}

void
eresized(int new)
{
	lockdisplay(display);
	if(new && getwindow(display, Refnone) < 0) {
		fprint(2, "stats: can't reattach to window\n");
		killall("reattach");
	}
	resize();
	unlockdisplay(display);
}

void
mouseproc(void)
{
	Mouse mouse;
	int i;

	for(;;){
		mouse = emouse();
		if(mouse.buttons == 4){
			lockdisplay(display);
			for(i=0; i<Nmenu2; i++)
				if(present[i])
					memmove(menu2str[i], "drop ", Opwid);
				else
					memmove(menu2str[i], "add  ", Opwid);
			i = emenuhit(3, &mouse, &menu2);
			if(i >= 0){
				if(!present[i])
					addgraph(i);
				else if(ngraph > 1)
					dropgraph(i);
				resize();
			}
			unlockdisplay(display);
		}
	}
}

void
startproc(void (*f)(void), int index)
{
	int pid;

	switch(pid = rfork(RFPROC|RFMEM|RFNOWAIT)){
	case -1:
		fprint(2, "stats: fork failed: %r\n");
		killall("fork failed");
	case 0:
		f();
		fprint(2, "stats: %s process exits\n", procnames[index]);
		if(index >= 0)
			killall("process died");
		exits(nil);
	}
	if(index >= 0)
		pids[index] = pid;
}

void
main(int argc, char *argv[])
{
	int i, j;
	char *s;
	long v, vmax, nargs;
	char args[100];

	nmach = 1;
	mysysname = getenv("sysname");
	if(mysysname == nil){
		fprint(2, "stats: can't find $sysname: %r\n");
		exits("sysname");
	}
	mysysname = estrdup(mysysname);

	nargs = 0;
	ARGBEGIN{
	case 'S':
		s = ARGF();
		if(s == nil)
			usage();
		scale = atof(s);
		if(scale <= 0.)
			usage();
		break;
	case 'L':
		logscale++;
		break;
	case 'Y':
		ylabels++;
		break;
	default:
		if(nargs>=sizeof args || strchr(argchars, ARGC())==nil)
			usage();
		args[nargs++] = ARGC();
	}ARGEND

	if(argc == 0){
		mach = emalloc(nmach*sizeof(Machine));
		initmach(&mach[0], mysysname);
		readmach(&mach[0], 1);
	}else{
		for(i=0; i<argc; i++){
			addmachine(argv[i]);
			readmach(&mach[i], 1);
		}
	}

	for(i=0; i<nargs; i++)
	switch(args[i]){
	default:
		fprint(2, "stats: internal error: unknown arg %c\n", args[i]);
		usage();
	case 'b':
		addgraph(Mbattery);
		break;
	case 'c':
		addgraph(Mcontext);
		break;
	case 'e':
		addgraph(Mether);
		break;
	case 'E':
		addgraph(Metherin);
		addgraph(Metherout);
		break;
	case 'f':
		addgraph(Mfault);
		break;
	case 'i':
		addgraph(Mintr);
		break;
	case 'l':
		addgraph(Mload);
		break;
	case 'm':
		addgraph(Mmem);
		break;
	case 'n':
		addgraph(Metherin);
		addgraph(Metherout);
		addgraph(Methererr);
		break;
	case 'p':
		addgraph(Mtlbpurge);
		break;
	case 's':
		addgraph(Msyscall);
		break;
	case 't':
		addgraph(Mtlbmiss);
		addgraph(Mtlbpurge);
		break;
	case 'w':
		addgraph(Mswap);
		break;
	}

	if(ngraph == 0)
		addgraph(Mload);

	for(i=0; i<nmach; i++)
		for(j=0; j<ngraph; j++)
			graph[i*ngraph+j].mach = &mach[i];

	if(initdraw(nil, nil, "stats") < 0){
		fprint(2, "stats: initdraw failed: %r\n");
		exits("initdraw");
	}
	colinit();
	einit(Emouse);
	notify(nil);
	startproc(mouseproc, Mouseproc);
	pids[Mainproc] = getpid();
	display->locking = 1;	/* tell library we're using the display lock */

	resize();

	unlockdisplay(display); /* display is still locked from initdraw() */
	for(;;){
		for(i=0; i<nmach; i++)
			readmach(&mach[i], 0);
		lockdisplay(display);
		parity = 1-parity;
		for(i=0; i<nmach*ngraph; i++){
			graph[i].newvalue(graph[i].mach, &v, &vmax, 0);
			graph[i].update(&graph[i], v, vmax);
		}
		flushimage(display, 1);
		unlockdisplay(display);
		sleep(1000);
	}
}

[-- Attachment #4: dat.h --]
[-- Type: text/plain, Size: 3470 bytes --]

typedef struct Event Event;
typedef struct Message Message;
typedef struct Window Window;

enum
{
	STACK		= 8192,
	EVENTSIZE	= 256,
	NEVENT		= 5,
};

struct Event
{
	int	c1;
	int	c2;
	int	q0;
	int	q1;
	int	flag;
	int	nb;
	int	nr;
	char	b[EVENTSIZE*UTFmax+1];
	Rune	r[EVENTSIZE+1];
};

struct Window
{
	/* file descriptors */
	int		ctl;
	int		event;
	int		addr;
	int		data;
	Biobuf	*body;

	/* event input */
	char		buf[512];
	char		*bufp;
	int		nbuf;
	Event	e[NEVENT];

	int		id;
	int		open;
	Channel	*cevent;
};

struct Message
{
	Window	*w;
	int		ctlfd;
	char		*name;
	char		*replyname;
	uchar	opened;
	uchar	dirty;
	uchar	isreply;
	uchar	deleted;
	uchar	writebackdel;
	uchar	tagposted;
	uchar	recursed;
	uchar	level;

	/* header info */
	char		*fromcolon;	/* from header file; all rest are from info file */
	char		*from;
	char		*to;
	char		*cc;
	char		*replyto;
	char		*date;
	char		*subject;
	char		*type;
	char		*disposition;
	char		*filename;
	char		*digest;

	Message	*next;	/* next in this mailbox */
	Message	*prev;	/* prev in this mailbox */
	Message	*head;	/* first subpart */
	Message	*tail;		/* last subpart */
};

extern	Window*	newwindow(void);
extern	int		winopenfile(Window*, char*);
extern	void		winopenbody(Window*, int);
extern	void		winclosebody(Window*);
extern	void		wintagwrite(Window*, char*, int);
extern	void		winname(Window*, char*);
extern	void		winwriteevent(Window*, Event*);
extern	void		winread(Window*, uint, uint, char*);
extern	int		windel(Window*, int);
extern	void		wingetevent(Window*, Event*);
extern	void		wineventproc(void*);
extern	void		winwritebody(Window*, char*, int);
extern	void		winclean(Window*);
extern	int		winselect(Window*, char*, int);
extern	int		winsetaddr(Window*, char*, int);
extern	char*	winreadbody(Window*, int*);
extern	void		windormant(Window*);
extern	void		winsetdump(Window*, char*, char*);

extern	void		readmbox(Message*, char*, char*);
extern	void		rewritembox(Window*, Message*);

extern	void		mkreply(Message*, char*, char*);
extern	void		delreply(Message*);

extern	int		mesgadd(Message*, char*, Dir*, char*);
extern	void		mesgmenu(Window*, Message*);
extern	void		mesgmenunew(Window*, Message*);
extern	int		mesgopen(Message*, char*, char*, Message*, int, char*);
extern	void		mesgctl(void*);
extern	void		mesgsend(Message*);
extern	void		mesgdel(Message*, Message*);
extern	void		mesgmenudel(Window*, Message*, Message*);
extern	void		mesgmenumark(Window*, char*, char*);
extern	void		mesgmenumarkdel(Window*, Message*, Message*, int);
extern	Message*	mesglookup(Message*, char*, char*);
extern	Message*	mesglookupfile(Message*, char*, char*);
extern	void		mesgfreeparts(Message*);

extern	char*	readfile(char*, char*, int*);
extern	void		ctlprint(int, char*, ...);
extern	void*	emalloc(uint);
extern	char*	estrdup(char*);
extern	char*	estrstrdup(char*, char*);
extern	char*	egrow(char*, char*, char*);
extern	char*	eappend(char*, char*, char*);
extern	void		error(char*, ...);
extern	int		tokenizec(char*, char**, int, char*);

#pragma	varargck	argpos	error	1
#pragma	varargck	argpos	ctlprint	2

extern	Window	*wbox;
extern	Message	mbox;
extern	Message	replies;
extern	char		*fsname;
extern	int		plumbsendfd;
extern	int		plumbseemailfd;
extern	char		*home;
extern	char		*mailboxdir;
extern	char		deleted[];
extern	int		wctlfd;
extern	int		shortmenu;

[-- Attachment #5: mail.c --]
[-- Type: text/plain, Size: 8984 bytes --]

#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include <plumb.h>
#include "dat.h"

char	*maildir = "/mail/fs/";			/* mountpoint of mail file system */
char	*mailtermdir = "/mnt/term/mail/fs/";	/* alternate mountpoint */
char *mboxname = "mbox";			/* mailboxdir/mboxname is mail spool file */
char	*mailboxdir = nil;				/* nil == /mail/box/$user */
char *fsname;						/* filesystem for mailboxdir/mboxname is at maildir/fsname */

Window	*wbox;
Message	mbox;
Message	replies;
char		*home;
int		plumbsendfd;
int		plumbseemailfd;
int		plumbshowmailfd;
Channel	*cplumb;
Channel	*cplumbshow;
int		wctlfd;
void		mainctl(void*);
void		plumbproc(void*);
void		plumbshowproc(void*);
void		plumbthread(void);
void		plumbshowthread(void*);

int			shortmenu;

void
usage(void)
{
	threadprint(2, "usage: Mail [-sS] [mailboxname [directoryname]]\n");
	threadexitsall("usage");
}

void
removeupasfs(void)
{
	char buf[256];

	if(strcmp(mboxname, "mbox") == 0)
		return;
	snprint(buf, sizeof buf, "close %s", mboxname);
	write(mbox.ctlfd, buf, strlen(buf));
}


void
threadmain(int argc, char *argv[])
{
	char *s, *user, *name;
	char err[ERRLEN], cmd[256];
	int i;

	/* open these early so we won't miss notification of new mail messages while we read mbox */
	plumbsendfd = plumbopen("send", OWRITE|OCEXEC);
	plumbseemailfd = plumbopen("seemail", OREAD|OCEXEC);
	plumbshowmailfd = plumbopen("showmail", OREAD|OCEXEC);

	shortmenu = 0;
	ARGBEGIN{
	case 's':
		shortmenu = 1;
		break;
	case 'S':
		shortmenu = 2;
		break;
	default:
		usage();
	}ARGEND
	name = "mbox";
	if(argc > 0){
		i = strlen(argv[0]);
		if(argc>2 || i==0)
			usage();
		if(argv[0][i-1] == '/')
			argv[0][i-1] = '\0';
		s = strrchr(argv[0], '/');
		if(s == nil)
			mboxname = estrdup(argv[0]);
		else{
			*s++ = '\0';
			if(*s == '\0')
				usage();
			mailboxdir = argv[0];
			mboxname = estrdup(s);
		}
		if(argc > 1)
			name = argv[1];
		else
			name = mboxname;
	}

	user = getenv("user");
	if(user == nil)
		user = "none";
	if(mailboxdir == nil)
		mailboxdir = estrstrdup("/mail/box/", user);

	if(access(maildir, 0)<0 && access(mailtermdir, 0)==0)
		bind(mailtermdir, maildir, MAFTER);

	s = estrstrdup(maildir, "ctl");
	mbox.ctlfd = open(s, ORDWR|OCEXEC);
	if(mbox.ctlfd < 0)
		error("Mail: can't open %s: %r\n", s);

	fsname = estrdup(name);
	if(argc > 0){
		s = emalloc(5+strlen(mailboxdir)+strlen(mboxname)+strlen(name)+10+1);
		for(i=0; i<10; i++){
			sprint(s, "open %s/%s %s", mailboxdir, mboxname, fsname);
			if(write(mbox.ctlfd, s, strlen(s)) >= 0)
				break;
			err[0] = '\0';
			errstr(err);
			if(strcmp(err, "mbox name in use") != 0)
				error("Mail: can't create directory %s for mail: %s\n", name, err);
			free(fsname);
			fsname = emalloc(strlen(name)+10);
			sprint(fsname, "%s-%d", name, i);
		}
		free(s);
	}

	s = estrstrdup(fsname, "/");
	mbox.name = estrstrdup(maildir, s);
	mbox.level= 0;
	readmbox(&mbox, maildir, s);
	home = getenv("home");
	if(home == nil)
		home = "/";

	wbox = newwindow();
	winname(wbox, mbox.name);
	wintagwrite(wbox, "Put Mail", 4+4);
	threadcreate(mainctl, wbox, STACK);
	snprint(cmd, sizeof cmd, "Mail %s", name);
	winsetdump(wbox, "/acme/mail", cmd);
	mbox.w = wbox;

	mesgmenu(wbox, &mbox);
	winclean(wbox);

	wctlfd = open("/dev/wctl", OWRITE|OCEXEC);	/* for acme window */
	cplumb = chancreate(sizeof(Plumbmsg*), 0);
	cplumbshow = chancreate(sizeof(Plumbmsg*), 0);
	/* start plumb reader as separate proc ... */
	proccreate(plumbproc, nil, STACK);
	proccreate(plumbshowproc, nil, STACK);
	threadcreate(plumbshowthread, nil, STACK);
	/* ... and use this thread to read the messages */
	plumbthread();
}

void
plumbproc(void*)
{
	Plumbmsg *m;

	threadsetname("plumbproc");
	for(;;){
		m = plumbrecv(plumbseemailfd);
		sendp(cplumb, m);
		if(m == nil)
			threadexits(nil);
	}
}

void
plumbshowproc(void*)
{
	Plumbmsg *m;

	threadsetname("plumbshowproc");
	for(;;){
		m = plumbrecv(plumbshowmailfd);
		sendp(cplumbshow, m);
		if(m == nil)
			threadexits(nil);
	}
}

void
newmesg(char *name, char *digest)
{
	Dir d;

	if(strncmp(name, mbox.name, strlen(mbox.name)) != 0)
		return;	/* message is about another mailbox */
	if(mesglookupfile(&mbox, name, digest) != nil)
		return;
	if(dirstat(name, &d) < 0)
		return;
	if(mesgadd(&mbox, mbox.name, &d, digest))
		mesgmenunew(wbox, &mbox);
}

void
showmesg(char *name, char *digest)
{
	char *n;

	if(strncmp(name, mbox.name, strlen(mbox.name)) != 0)
		return;	/* message is about another mailbox */
	n = estrdup(name+strlen(mbox.name));
	if(n[strlen(n)-1] != '/')
		n = egrow(n, "/", nil);
	mesgopen(&mbox, mbox.name, name+strlen(mbox.name), nil, 1, digest);
	free(n);
}

void
delmesg(char *name, char *digest)
{
	Message *m;

//threadprint(2, "Mail: ignoring delete message for %s\n", name);
	m = mesglookupfile(&mbox, name, digest);
	if(m != nil)
		mesgmenumarkdel(wbox, &mbox, m, 0);
}

void
plumbthread(void)
{
	Plumbmsg *m;
	Plumbattr *a;
	char *type, *digest;

	threadsetname("plumbthread");
	while((m = recvp(cplumb)) != nil){
		a = m->attr;
		digest = plumblookup(a, "digest");
		type = plumblookup(a, "mailtype");
		if(type == nil)
			threadprint(2, "Mail: plumb message with no mailtype attribute\n");
		else if(strcmp(type, "new") == 0)
			newmesg(m->data, digest);
		else if(strcmp(type, "delete") == 0)
			delmesg(m->data, digest);
		else
			threadprint(2, "Mail: unknown plumb attribute %s\n", type);
		plumbfree(m);
	}
	threadexits(nil);
}

void
plumbshowthread(void*)
{
	Plumbmsg *m;

	threadsetname("plumbshowthread");
	while((m = recvp(cplumbshow)) != nil){
		showmesg(m->data, plumblookup(m->attr, "digest"));
		plumbfree(m);
	}
	threadexits(nil);
}

int
mboxcommand(Window *w, char *s)
{
	char *t;
	Message *m, *next;
	int ok;

	while(*s==' ' || *s=='\t' || *s=='\n')
		s++;
	if(strncmp(s, "Mail", 4) == 0){
		s += 4;
		while(*s==' ' || *s=='\t' || *s=='\n')
			s++;
		t = s;
		while(*s && *s!=' ' && *s!='\t' && *s!='\n')
			s++;
		*s = 0;
		mkreply(nil, "Mail", t);
		return 1;
	}
	if(strcmp(s, "Del") == 0){
		if(mbox.dirty){
			mbox.dirty = 0;
			threadprint(2, "mail: mailbox not written\n");
			return 1;
		}
		ok = 1;
		for(m=mbox.head; m!=nil; m=next){
			next = m->next;
			if(m->w){
				if(windel(m->w, 0))
					m->w = nil;
				else
					ok = 0;
			}
		}
		for(m=replies.head; m!=nil; m=next){
			next = m->next;
			if(m->w){
				if(windel(m->w, 0))
					m->w = nil;
				else
					ok = 0;
			}
		}
		if(ok){
			windel(w, 1);
			removeupasfs();
			threadexitsall(nil);
		}
		return 1;
	}
	if(strcmp(s, "Put") == 0){
		rewritembox(wbox, &mbox);
		return 1;
	}
	return 0;
}

void
mainctl(void *v)
{
	Window *w;
	Event *e, *e2, *eq, *ea;
	int na, nopen;
	char *s, *t, *buf;

	w = v;
	proccreate(wineventproc, w, STACK);

	for(;;){
		e = recvp(w->cevent);
		switch(e->c1){
		default:
		Unknown:
			print("unknown message %c%c\n", e->c1, e->c2);
			break;
	
		case 'E':	/* write to body; can't affect us */
			break;
	
		case 'F':	/* generated by our actions; ignore */
			break;
	
		case 'K':	/* type away; we don't care */
			break;
	
		case 'M':
			switch(e->c2){
			case 'x':
			case 'X':
				ea = nil;
				e2 = nil;
				if(e->flag & 2)
					e2 = recvp(w->cevent);
				if(e->flag & 8){
					ea = recvp(w->cevent);
					na = ea->nb;
					recvp(w->cevent);
				}else
					na = 0;
				s = e->b;
				/* if it's a known command, do it */
				if((e->flag&2) && e->nb==0)
					s = e2->b;
				if(na){
					t = emalloc(strlen(s)+1+na+1);
					sprint(t, "%s %s", s, ea->b);
					s = t;
				}
				/* if it's a long message, it can't be for us anyway */
				if(!mboxcommand(w, s))	/* send it back */
					winwriteevent(w, e);
				if(na)
					free(s);
				break;
	
			case 'l':
			case 'L':
				buf = nil;
				eq = e;
				if(e->flag & 2){
					e2 = recvp(w->cevent);
					eq = e2;
				}
				s = eq->b;
				if(eq->q1>eq->q0 && eq->nb==0){
					buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
					winread(w, eq->q0, eq->q1, buf);
					s = buf;
				}
				nopen = 0;
				do{
					/* skip 'deleted' string if present' */
					if(strncmp(s, deleted, strlen(deleted)) == 0)
						s += strlen(deleted);
					/* skip mail box name if present */
					if(strncmp(s, mbox.name, strlen(mbox.name)) == 0)
						s += strlen(mbox.name);
					nopen += mesgopen(&mbox, mbox.name, s, nil, 0, nil);
					while(*s!='\0' && *s++!='\n')
						;
				}while(*s);
				if(nopen == 0)	/* send it back */
					winwriteevent(w, e);
				free(buf);
				break;
	
			case 'I':	/* modify away; we don't care */
			case 'D':
			case 'd':
			case 'i':
				break;
	
			default:
				goto Unknown;
			}
		}
	}
}

[-- Attachment #6: mesg.c --]
[-- Type: text/plain, Size: 23475 bytes --]

#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include <ctype.h>
#include <plumb.h>
#include "dat.h"

enum
{
	DIRCHUNK = 32*sizeof(Dir)
};

char	regexchars[] = "\\/[].+?()*^$";
char	deleted[] = "(deleted)-";
char	deletedrx[] = "\\(deleted\\)-";
char	deletedrx01[] = "(\\(deleted\\)-)?";
char	deletedaddr[] = "-#0;/^\\(deleted\\)-/";

struct{
	char	*type;
	char	*port;
	char *suffix;
} ports[] = {
	"text/",			"edit",	"txt", /* must be first for plumbport() */
	"image/gif",		"image",	"gif",
	"image/jpeg",		"image",	"jpg",
	"application/postscript",	"postscript",	"ps",
	"application/pdf",	"postscript",	"pdf",
	"application/msword",	"msword",	"doc",
	"application/rtf",	"msword",	"rtf",
	nil,	nil
};

char *goodtypes[] = {
	"text",
	"text/plain",
	"message/rfc822",
	"text/richtext",
	"text/tab-separated-values",
	"application/octet-stream",
	nil,
};

struct{
	char *type;
	char	*ext;
} exts[] = {
	"image/gif",	".gif",
	"image/jpeg",	".jpg",
	nil, nil
};

char*
line(char *data, char **pp)
{
	char *p, *q;

	for(p=data; *p!='\0' && *p!='\n'; p++)
		;
	if(*p == '\n')
		*pp = p+1;
	else
		*pp = p;
	q = emalloc(p-data + 1);
	memmove(q, data, p-data);
	return q;
}

void
scanheaders(Message *m, char *dir)
{
	char *s, *t, *u;

	s = readfile(dir, "header", nil);
	if(s != nil)
		while(*s){
			t = line(s, &s);
			if(strncmp(t, "From: ", 6) == 0){
				m->fromcolon = estrdup(t+6);
				/* remove all quotes; they're ugly and irregular */
				for(u=m->fromcolon; *u; u++)
					if(*u == '"')
						memmove(u, u+1, strlen(u));
			}
			if(strncmp(t, "Subject: ", 9) == 0)
				m->subject = estrdup(t+9);
			free(t);
		}
	if(m->fromcolon == nil)
		m->fromcolon = estrdup(m->from);
}

int
loadinfo(Message *m, char *dir)
{
	int n;
	char *data, *p, *s;

	data = readfile(dir, "info", &n);
	if(data == nil)
		return 0;
	m->from = line(data, &p);
	scanheaders(m, dir);	/* depends on m->from being set */
	m->to = line(p, &p);
	m->cc = line(p, &p);
	m->replyto = line(p, &p);
	m->date = line(p, &p);
	s = line(p, &p);
	if(m->subject == nil)
		m->subject = s;
	else
		free(s);
	m->type = line(p, &p);
	m->disposition = line(p, &p);
	m->filename = line(p, &p);
	m->digest = line(p, &p);
	free(data);
	return 1;
}

int
isnumeric(char *s)
{
	while(*s){
		if(!isdigit(*s))
			return 0;
		s++;
	}
	return 1;
}

Dir*
loaddir(char *name)
{
	int m, n, fd;
	Dir *dp;

	fd = open(name, OREAD);
	if(fd < 0)
		error("can't open %s: %r\n", name);
	dp = nil;
	for(n=0; ; n+=m){
		dp = realloc(dp, n+DIRCHUNK);
		memset(dp+n/sizeof(Dir), 0, DIRCHUNK);
		m = dirread(fd, dp+n/sizeof(Dir), DIRCHUNK);
		if(m <= 0)
			break;
	}
	close(fd);
	return dp;
}

void
readmbox(Message *mbox, char *dir, char *subdir)
{
	char *name;
	Dir *d, *dirp;

	name = estrstrdup(dir, subdir);
	dirp = loaddir(name);
	mbox->recursed = 1;
	for(d=dirp; d->name[0]!='\0'; d++)
		if(isnumeric(d->name))
			mesgadd(mbox, name, d, nil);
	free(dirp);
	free(name);
}

/* add message to box, in increasing numerical order */
int
mesgadd(Message *mbox, char *dir, Dir *d, char *digest)
{
	Message *m;
	char *name;
	int loaded;

	m = emalloc(sizeof(Message));
	m->name = estrstrdup(d->name, "/");
	m->next = nil;
	m->prev = mbox->tail;
	m->level= mbox->level+1;
	m->recursed = 0;
	name = estrstrdup(dir, m->name);
	loaded = loadinfo(m, name);
	free(name);
	/* if two upas/fs are running, we can get misled, so check digest before accepting message */
	if(loaded==0 || (digest!=nil && m->digest!=nil && strcmp(digest, m->digest)!=0)){
		mesgfreeparts(m);
		free(m);
		return 0;
	}
	if(mbox->tail != nil)
		mbox->tail->next = m;
	mbox->tail = m;
	if(mbox->head == nil)
		mbox->head = m;

	if (m->level != 1){
		m->recursed = 1;
		readmbox(m, dir, m->name); 
	}
	return 1;
}

int
thisyear(char *year)
{
	static char now[10];
	char *s;

	if(now[0] == '\0'){
		s = ctime(time(nil));
		strcpy(now, s+24);
	}
	return strncmp(year, now, 4) == 0;
}

char*
stripdate(char *as)
{
	int n;
	char *s, *fld[10];

	as = estrdup(as);
	s = estrdup(as);
	n = tokenize(s, fld, 10);
	if(n > 5){
		sprint(as, "%.3s ", fld[0]);	/* day */
		/* some dates have 19 Apr, some Apr 19 */
		if(strlen(fld[1])<4 && isnumeric(fld[1]))
			sprint(as+strlen(as), "%.3s %.3s ", fld[1], fld[2]);	/* date, month */
		else
			sprint(as+strlen(as), "%.3s %.3s ", fld[2], fld[1]);	/* date, month */
		/* do we use time or year?  depends on whether year matches this one */
		if(thisyear(fld[5])){
			if(strchr(fld[3], ':') != nil)
				sprint(as+strlen(as), "%.5s ", fld[3]);	/* time */
			else if(strchr(fld[4], ':') != nil)
				sprint(as+strlen(as), "%.5s ", fld[4]);	/* time */
		}else
			sprint(as+strlen(as), "%.4s ", fld[5]);	/* year */
	}
	free(s);
	return as;
}

char*
readfile(char *dir, char *name, int *np)
{
	char *file, *data;
	int fd;
	Dir d;

	if(np != nil)
		*np = 0;
	file = estrstrdup(dir, name);
	fd = open(file, OREAD);
	if(fd < 0)
		return nil;
	dirfstat(fd, &d);
	free(file);
	data = emalloc(d.length+1);
	read(fd, data, d.length);
	close(fd);
	if(np != nil)
		*np = d.length;
	return data;
}

char*
info(Message *m, int ind)
{
	char *i;
	int j;

	if (ind == 0 && shortmenu) {
		char fmt[80];
		char s[80];
		int len;
		int lens;

		len = (shortmenu > 1) ? 10 : 30;
		lens = (shortmenu > 1) ? 25 : 30;
		if (ind == 0 && m->subject[0] == '\0'){
			snprint(fmt, 80, " %%-%d.%ds", len, len);
			snprint(s, 80, fmt, m->fromcolon);
		}else{
			snprint(fmt, 80, " %%-%d.%ds  %%-%d.%ds", len, len, lens, lens);
			snprint(s, 80, fmt, m->fromcolon, m->subject);
		}
		i = estrdup(s);

		return i;
	} 

	i = estrdup("");
	i = eappend(i, "\t", m->fromcolon);
	i = egrow(i, "\t", stripdate(m->date));
	if(ind == 0){
		if(strcmp(m->type, "text")!=0 && strncmp(m->type, "text/", 5)!=0 && 
		   strncmp(m->type, "multipart/", 10)!=0)
			i = egrow(i, "\t(", estrstrdup(m->type, ")"));
	}else if(strncmp(m->type, "multipart/", 10) != 0)
		i = egrow(i, "\t(", estrstrdup(m->type, ")"));
	if(m->subject[0] != '\0'){
		i = eappend(i, "\n", nil);
		for(j=0; j<ind; j++)
			i = eappend(i, "\t", nil);
		i = eappend(i, "\t", m->subject);
	}
	return i;
}

void
mesgmenu0(Window *w, Message *mbox, char *realdir, char *dir, int ind, Biobuf *fd, int onlyone, int dotail)
{
	int i;
	Message *m;
	char *name, *tmp;

	/* show mail box in reverse order, pieces in forward order */
	if(ind > 0)
		m = mbox->head;
	else
		m = mbox->tail;
	while(m != nil){
		for(i=0; i<ind; i++)
			Bprint(fd, "\t");
		if(ind != 0)
			Bprint(fd, "  ");
		name = estrstrdup(dir, m->name);
		tmp = info(m, ind);
		Bprint(fd, "%s%s\n", name, tmp);
		free(tmp);
		if(dotail && m->tail)
			mesgmenu0(w, m, realdir, name, ind+1, fd, 0, dotail);
		free(name);
		if(ind)
			m = m->next;
		else
			m = m->prev;
		if(onlyone)
			m = nil;
	}
}

void
mesgmenu(Window *w, Message *mbox)
{
	winopenbody(w, OWRITE);
	mesgmenu0(w, mbox, mbox->name, "", 0, w->body, 0, !shortmenu);
	winclosebody(w);
}

/* one new message has arrived, as mbox->tail */
void
mesgmenunew(Window *w, Message *mbox)
{
	Biobuf *b;

	winselect(w, "0", 0);
	w->data = winopenfile(w, "data");
	b = emalloc(sizeof(Biobuf));
	Binit(b, w->data, OWRITE);
	mesgmenu0(w, mbox, mbox->name, "", 0, b, 1, !shortmenu);
	Bterm(b);
	if(!mbox->dirty)
		winclean(w);
	/* select tag line plus following indented lines, but not final newline (it's distinctive) */
	winselect(w, "0/.*\\n((\t.*\\n)*\t.*)?/", 1);
	close(w->addr);
	close(w->data);
	w->addr = -1;
	w->data = -1;
}

char*
name2regexp(char *prefix, char *s)
{
	char *buf, *p, *q;

	buf = emalloc(strlen(prefix)+2*strlen(s)+50);	/* leave room to append more */
	p = buf;
	*p++ = '0';
	*p++ = '/';
	*p++ = '^';
	strcpy(p, prefix);
	p += strlen(prefix);
	for(q=s; *q!='\0'; q++){
		if(strchr(regexchars, *q) != nil)
			*p++ = '\\';
		*p++ = *q;
	}
	*p++ = '/';
	*p = '\0';
	return buf;
}

void
mesgmenumarkdel(Window *w, Message *mbox, Message *m, int writeback)
{
	char *buf;


	if(m->deleted)
		return;
	m->writebackdel = writeback;
	if(w->data < 0)
		w->data = winopenfile(w, "data");
	buf = name2regexp("", m->name);
	strcat(buf, "-#0");
	if(winselect(w, buf, 1))
		write(w->data, deleted, 10);
	free(buf);
	close(w->data);
	close(w->addr);
	w->addr = w->data = -1;
	mbox->dirty = 1;
	m->deleted = 1;
}

void
mesgmenumarkundel(Window *w, Message*, Message *m)
{
	char *buf;

	if(m->deleted == 0)
		return;
	if(w->data < 0)
		w->data = winopenfile(w, "data");
	buf = name2regexp(deletedrx, m->name);
	if(winselect(w, buf, 1))
		if(winsetaddr(w, deletedaddr, 1))
			write(w->data, "", 0);
	free(buf);
	close(w->data);
	close(w->addr);
	w->addr = w->data = -1;
	m->deleted = 0;
}

void
mesgmenudel(Window *w, Message *mbox, Message *m)
{
	char *buf;

	if(w->data < 0)
		w->data = winopenfile(w, "data");
	buf = name2regexp(deletedrx, m->name);
	if(winsetaddr(w, buf, 1) && winsetaddr(w, ".,./.*\\n(\t.*\\n)*/", 1))
		write(w->data, "", 0);
	free(buf);
	close(w->data);
	close(w->addr);
	w->addr = w->data = -1;
	mbox->dirty = 1;
	m->deleted = 1;
}

void
mesgmenumark(Window *w, char *which, char *mark)
{
	char *buf;

	if(w->data < 0)
		w->data = winopenfile(w, "data");
	buf = name2regexp(deletedrx01, which);
	if(winsetaddr(w, buf, 1) && winsetaddr(w, "+0-#1", 1))	/* go to end of line */
		write(w->data, mark, strlen(mark));
	free(buf);
	close(w->data);
	close(w->addr);
	w->addr = w->data = -1;
	if(!mbox.dirty)
		winclean(w);
}

void
mesgfreeparts(Message *m)
{
	free(m->name);
	free(m->replyname);
	free(m->fromcolon);
	free(m->from);
	free(m->to);
	free(m->cc);
	free(m->replyto);
	free(m->date);
	free(m->subject);
	free(m->type);
	free(m->disposition);
	free(m->filename);
	free(m->digest);
}

void
mesgdel(Message *mbox, Message *m)
{
	Message *n, *next;

	if(m->opened)
		error("Mail: internal error: deleted message still open in mesgdel\n");
	/* delete subparts */
	for(n=m->head; n!=nil; n=next){
		next = n->next;
		mesgdel(m, n);
	}
	/* remove this message from list */
	if(m->next)
		m->next->prev = m->prev;
	else
		mbox->tail = m->prev;
	if(m->prev)
		m->prev->next = m->next;
	else
		mbox->head = m->next;
	mesgfreeparts(m);
}

int
mesgsave(Message *m, char *s)
{
	int ofd, n, k, ret;
	char *t, *raw, *unixheader, *all;

	t = estrstrdup(mbox.name, m->name);
	raw = readfile(t, "raw", &n);
	unixheader = readfile(t, "unixheader", &k);
	if(raw==nil || unixheader==nil){
		threadprint(2, "Mail: can't read %s: %r\n", t);
		free(t);
		return 0;
	}
	free(t);

	all = emalloc(n+k+1);
	memmove(all, unixheader, k);
	memmove(all+k, raw, n);
	memmove(all+k+n, "\n", 1);
	n = k+n+1;
	free(unixheader);
	free(raw);
	ret = 1;
	s = estrdup(s);
	if(s[0] != '/')
		s = egrow(estrdup(mailboxdir), "/", s);
	ofd = open(s, OWRITE);
	if(ofd < 0){
		threadprint(2, "Mail: can't open %s: %r\n", s);
		ret = 0;
	}else if(seek(ofd, 0LL, 2)<0 || write(ofd, all, n)!=n){
		threadprint(2, "Mail: save failed: can't write %s: %r\n", s);
		ret = 0;
	}
	free(all);
	close(ofd);
	free(s);
	return ret;
}

int
mesgcommand(Message *m, char *cmd)
{
	char *s, *t, *u;
	int ok, ret;

	s = cmd;
	ret = 1;
	while(*s==' ' || *s=='\t' || *s=='\n')
		s++;
	if(strcmp(s, "Post") == 0){
		mesgsend(m);
		goto Return;
	}
	if(strncmp(s, "Save", 4) == 0){
		if(m->isreply)
			goto Return;
		s += 4;
		while(*s==' ' || *s=='\t' || *s=='\n')
			s++;
		u = estrdup("\t[saved");
		if(*s == '\0'){
			ok = mesgsave(m, "stored");
		}else{
			t = s;
			while(*s!='\0' && *s!=' ' && *s!='\t' && *s!='\n')
				s++;
			*s = 0;
			ok = mesgsave(m, t);
			u = eappend(u, " ", t);
		}
		if(ok){
			u = egrow(u, "]", nil);
			mesgmenumark(mbox.w, m->name, u);
		}
		free(u);
		goto Return;
	}
	if(strcmp(s, "Reply") == 0){
		mkreply(m, s, nil);
		goto Return;
	}
	if(strncmp(s, "Reply all", 9) == 0 || strcmp(s, "Replyall") == 0){
		mkreply(m, "Replyall", nil);
		goto Return;
	}
	if(strcmp(s, "Del") == 0){
		if(windel(m->w, 0)){
			chanfree(m->w->cevent);
			free(m->w);
			m->w = nil;
			if(m->isreply)
				delreply(m);
			else{
				m->opened = 0;
				m->tagposted = 0;
			}
			free(cmd);
			threadexits(nil);
		}
		goto Return;
	}
	if(strcmp(s, "Delmesg") == 0){
		if(!m->isreply){
			mesgmenumarkdel(wbox, &mbox, m, 1);
			free(cmd);	/* mesgcommand might not return */
			mesgcommand(m, estrdup("Del"));
			return 1;
		}
		goto Return;
	}
	if(strcmp(s, "UnDelmesg") == 0){
		if(!m->isreply && m->deleted)
			mesgmenumarkundel(wbox, &mbox, m);
		goto Return;
	}
//	if(strcmp(s, "Headers") == 0){
//		m->showheaders();
//		return True;
//	}

	ret = 0;

    Return:
	free(cmd);
	return ret;
}

void
mesgtagpost(Message *m)
{
	if(m->tagposted)
		return;
	wintagwrite(m->w, " Post", 5);
	m->tagposted = 1;
}

void
mesgctl(void *v)
{
	Message *m;
	Window *w;
	Event *e, *eq, *e2, *ea;
	int na, nopen, i, j;
	char *s, *t, *buf;

	m = v;
	w = m->w;
	threadsetname("mesgctl");
	proccreate(wineventproc, w, STACK);
	for(;;){
		e = recvp(w->cevent);
		switch(e->c1){
		default:
		Unk:
			print("unknown message %c%c\n", e->c1, e->c2);
			break;

		case 'E':	/* write to body; can't affect us */
			break;

		case 'F':	/* generated by our actions; ignore */
			break;

		case 'K':	/* type away; we don't care */
		case 'M':
			switch(e->c2){
			case 'x':	/* mouse only */
			case 'X':
				ea = nil;
				eq = e;
				if(e->flag & 2){
					e2 = recvp(w->cevent);
					eq = e2;
				}
				if(e->flag & 8){
					ea = recvp(w->cevent);
					recvp(w->cevent);
					na = ea->nb;
				}else
					na = 0;
				if(eq->q1>eq->q0 && eq->nb==0){
					s = emalloc((eq->q1-eq->q0)*UTFmax+1);
					winread(w, eq->q0, eq->q1, s);
				}else
					s = estrdup(eq->b);
				if(na){
					t = emalloc(strlen(s)+1+na+1);
					sprint(t, "%s %s", s, ea->b);
					free(s);
					s = t;
				}
				if(!mesgcommand(m, s))	/* send it back */
					winwriteevent(w, e);
				break;

			case 'l':	/* mouse only */
			case 'L':
				buf = nil;
				eq = e;
				if(e->flag & 2){
					e2 = recvp(w->cevent);
					eq = e2;
				}
				s = eq->b;
				if(eq->q1>eq->q0 && eq->nb==0){
					buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
					winread(w, eq->q0, eq->q1, buf);
					s = buf;
				}
				nopen = 0;
				do{
					/* skip mail box name if present */
					if(strncmp(s, mbox.name, strlen(mbox.name)) == 0)
						s += strlen(mbox.name);
					if(strstr(s, "body") != nil){
						/* strip any known extensions */
						for(i=0; exts[i].ext!=nil; i++){
							j = strlen(exts[i].ext);
							if(strlen(s)>j && strcmp(s+strlen(s)-j, exts[i].ext)==0){
								s[strlen(s)-j] = '\0';
								break;
							}
						}
						if(strlen(s)>5 && strcmp(s+strlen(s)-5, "/body")==0)
							s[strlen(s)-4] = '\0';	/* leave / in place */
					}
					nopen += mesgopen(&mbox, mbox.name, s, m, 0, nil);
					while(*s!=0 && *s++!='\n')
						;
				}while(*s);
				if(nopen == 0)	/* send it back */
					winwriteevent(w, e);
				free(buf);
				break;

			case 'I':	/* modify away; we don't care */
			case 'D':
				mesgtagpost(m);
				/* fall through */
			case 'd':
			case 'i':
				break;

			default:
				goto Unk;
			}
		}
	}
}

void
mesgline(Message *m, char *header, char *value)
{
	if(strlen(value) > 0)
		Bprint(m->w->body, "%s: %s\n", header, value);
}

int
isprintable(char *type)
{
	int i;

	for(i=0; goodtypes[i]!=nil; i++)
		if(strcmp(type, goodtypes[i])==0)
			return 1;
	return 0;
}

char*
ext(char *type)
{
	int i;

	for(i=0; exts[i].type!=nil; i++)
		if(strcmp(type, exts[i].type)==0)
			return exts[i].ext;
	return "";
}

void
mimedisplay(Message *m, char *name, char *rootdir, Window *w, int fileonly)
{
	char *dest;

	if(strcmp(m->disposition, "file")==0 || strlen(m->filename)!=0){
		if(strlen(m->filename) == 0){
			dest = estrdup(m->name);
			dest[strlen(dest)-1] = '\0';
		}else
			dest = estrdup(m->filename);
		if(m->filename[0] != '/')
			dest = egrow(estrdup("/tmp"), "/", dest);
		Bprint(w->body, "\tcp %s%sbody%s %s\n", rootdir, name, ext(m->type), dest);
		free(dest);
	}else if(!fileonly)
		Bprint(w->body, "\tfile is %s%sbody%s\n", rootdir, name, ext(m->type));
}

void
mesgload(Message *m, char *rootdir, char *file, Window *w)
{
	char *s, *subdir, *name, *dir;
	Message *mp, *thisone;
	int n;

	dir = estrstrdup(rootdir, file);
	if(strlen(m->from) > 0)
		Bprint(w->body, "From: %s\n", m->from);
	else
		Bprint(w->body, "\n");
	mesgline(m, "Date", m->date);
	mesgline(m, "To", m->to);
	mesgline(m, "CC", m->cc);
	mesgline(m, "Subject", m->subject);
	Bprint(w->body, "\n");

	if(m->level == 1 && m->recursed == 0){
		m->recursed = 1;
		readmbox(m, rootdir, m->name);
	}
	if(m->head == nil){	/* single part message */
		if(strcmp(m->type, "text")==0 || strncmp(m->type, "text/", 5)==0){
			mimedisplay(m, m->name, rootdir, w, 1);
			s = readfile(dir, "body", &n);
			winwritebody(w, s, n);
			free(s);
		}else
			mimedisplay(m, m->name, rootdir, w, 0);
	}else{	/* multi-part message */
		thisone = nil;
		if(strcmp(m->type, "multipart/alternative") == 0){
			thisone = m->head;	/* in case we can't find a good one */
			for(mp=m->head; mp!=nil; mp=mp->next)
				if(isprintable(mp->type)){
					thisone = mp;
					break;
				}
		}
		for(mp=m->head; mp!=nil; mp=mp->next){
			if(thisone!=nil && mp!=thisone)
				continue;
			subdir = estrstrdup(dir, mp->name);
			name = estrstrdup(file, mp->name);
			/* skip first element in name because it's already in window name */
			if(mp != m->head)
				Bprint(w->body, "\n===> %s (%s) [%s]\n", strchr(name, '/')+1, mp->type, mp->disposition);
			if(strcmp(mp->type, "text")==0 || strncmp(mp->type, "text/", 5)==0){
				mimedisplay(mp, name, rootdir, w, 1);
				s = readfile(subdir, "header", &n);
				winwritebody(w, s, n);
				free(s);
				winwritebody(w, "\n", 1);
				s = readfile(subdir, "body", &n);
				winwritebody(w, s, n);
				free(s);
			}else{
				if(strncmp(mp->type, "multipart/", 10)==0){
					mp->w = w;
					mesgload(mp, rootdir, name, w);
					mp->w = nil;
				}else
					mimedisplay(mp, name, rootdir, w, 0);
			}
			free(name);
			free(subdir);
		}
	}
	free(dir);
}

int
tokenizec(char *str, char **args, int max, char *splitc)
{
	int na;
	int intok = 0;

	if(max <= 0)
		return 0;	
	for(na=0; *str != '\0';str++){
		if(strchr(splitc, *str) == nil){
			if(intok)
				continue;
			args[na++] = str;
			intok = 1;
		}else{
			/* it's a separator/skip character */
			*str = '\0';
			if(intok){
				intok = 0;
				if(na >= max)
					break;
			}
		}
	}
	return na;
}

Message*
mesglookup(Message *mbox, char *name, char *digest)
{
	int n;
	Message *m;
	char *t;

	if(digest){
		/* can find exactly */
		for(m=mbox->head; m!=nil; m=m->next)
			if(strcmp(digest, m->digest) == 0)
				break;
		return m;
	}

	n = strlen(name);
	if(n == 0)
		return nil;
	if(name[n-1] == '/')
		t = estrdup(name);
	else
		t = estrstrdup(name, "/");
	for(m=mbox->head; m!=nil; m=m->next)
		if(strcmp(t, m->name) == 0)
			break;
	free(t);
	return m;
}

int
plumbport(char *s)
{
	int i;

	for(i=0; ports[i].type!=nil; i++)
		if(strncmp(s, ports[i].type, strlen(ports[i].type)) == 0)
			return i;
	/* see if it's a text type */
	for(i=0; goodtypes[i]!=nil; i++)
		if(strncmp(s, goodtypes[i], strlen(goodtypes[i])) == 0)
			return 0;
	return -1;
}

void
plumb(Message *m, char *dir)
{
	int i;
	char *port;
	Plumbmsg *pm;

	if(strlen(m->type) == 0)
		return;
	i = plumbport(m->type);
	if(i < 0)
		threadprint(2, "can't find destination for message subpart\n");
	else{
		port = ports[i].port;
		pm = emalloc(sizeof(Plumbmsg));
		pm->src = estrdup("Mail");
		if(port)
			pm->dst = estrdup(port);
		else
			pm->dst = nil;
		pm->wdir = nil;
		pm->type = estrdup("text");
		pm->ndata = -1;
		pm->data = estrstrdup(dir, "body");
		pm->data = eappend(pm->data, ".", ports[i].suffix);
		if(plumbsend(plumbsendfd, pm) < 0)
			threadprint(2, "error writing plumb message: %r\n");
		plumbfree(pm);
	}
}

int
mesgopen(Message *mbox, char *dir, char *s, Message *mesg, int plumbed, char *digest)
{
	char *t, *u, *v;
	Message *m;
	char *direlem[10];
	int i, ndirelem;

	/* find white-space-delimited first word */
	for(t=s; *t!='\0' && !isspace(*t); t++)
		;
	u = emalloc(t-s+1);
	memmove(u, s, t-s);
	/* separate it on slashes */
	ndirelem = tokenizec(u, direlem, nelem(direlem), "/");
	if(ndirelem <= 0){
    Error:
		free(u);
		return 0;
	}
	if(plumbed){
		write(wctlfd, "top", 3);
		write(wctlfd, "current", 7);
	}
	/* open window for message */
	m = mesglookup(mbox, direlem[0], digest);
	if(m == nil)
		goto Error;
	if(mesg!=nil && m!=mesg)	/* string looked like subpart but isn't part of this message */
		goto Error;
	if(m->opened == 0){
		m->w = newwindow();
		v = estrstrdup(mbox->name, m->name);
		winname(m->w, v);
		free(v);
		if(m->deleted)
			wintagwrite(m->w, "Reply all UnDelmesg Save ", 6+4+10+5);
		else
			wintagwrite(m->w, "Reply all Delmesg Save ", 6+4+8+5);
		threadcreate(mesgctl, m, STACK);
		winopenbody(m->w, OWRITE);
		mesgload(m, dir, m->name, m->w);
		winclosebody(m->w);
		winclean(m->w);
		m->opened = 1;
		if(ndirelem == 1){
			free(u);
			return 1;
		}
	}
	if(ndirelem == 1 && plumbport(m->type) <= 0){
		/* make sure dot is visible */
		ctlprint(m->w->ctl, "show\n");
		return 0;
	}
	/* walk to subpart */
	dir = estrstrdup(dir, m->name);
	for(i=1; i<ndirelem; i++){
		m = mesglookup(m, direlem[i], digest);
		if(m == nil)
			break;
		dir = egrow(dir, m->name, nil);
	}
	if(m != nil && plumbport(m->type) > 0)
		plumb(m, dir);
	free(dir);
	free(u);
	return 1;
}

void
rewritembox(Window *w, Message *mbox)
{
	Message *m, *next;
	char *deletestr, *t;
	int nopen;

	deletestr = estrstrdup("delete ", fsname);

	nopen = 0;
	for(m=mbox->head; m!=nil; m=next){
		next = m->next;
		if(m->deleted == 0)
			continue;
		if(m->opened){
			nopen++;
			continue;
		}
		if(m->writebackdel){
			/* messages deleted by plumb message are not removed again */
			t = estrdup(m->name);
			if(strlen(t) > 0)
				t[strlen(t)-1] = '\0';
			deletestr = egrow(deletestr, " ", t);
		}
		mesgmenudel(w, mbox, m);
		mesgdel(mbox, m);
	}
	if(write(mbox->ctlfd, deletestr, strlen(deletestr)) < 0)
		threadprint(2, "Mail: warning: error removing mail message files: %r\n");
	free(deletestr);
	winselect(w, "0", 0);
	if(nopen == 0)
		winclean(w);
	mbox->dirty = 0;
}

/* name is a full file name, but it might not belong to us */
Message*
mesglookupfile(Message *mbox, char *name, char *digest)
{
	int k, n;

	k = strlen(name);
	n = strlen(mbox->name);
	if(k==0 || strncmp(name, mbox->name, n) != 0){
//		threadprint(2, "Mail: message %s not in this mailbox\n", name);
		return nil;
	}
	return mesglookup(mbox, name+n, digest);
}

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2001-05-03 13:24 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2001-05-03 10:37 [9fans] tiny patches for cmds on the bitsy nemo
2001-05-03 13:24 nemo

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).