~moody/iso

e4322f4dffdea1ac61d67e2f0852982c65f5e98d — Jacob Moody 1 year, 6 months ago
initial commit
A  => bin/buildisos.rc +106 -0
@@ 1,106 @@
#!/bin/rc
rfork en

home=/usr/build
webfs

cd $home/code/plan9front

archs=(amd64 386 arm arm64 spim)

www=$home/www/9front

. ./sys/lib/rootstub
. ./sys/lib/rootbind

# preclean
cd /sys/src
mk clean
cd /sys/src/boot
mk clean
cd /sys/src/9
mk clean

# build for cputype
cd /sys/src
mk install

# build everything
for(i in $archs)@{
	rfork en

	objtype=$i
	bind -c /n/src9/$i /$i

	# user
	cd /sys/src
	mk install

	# kernel
	switch($objtype){
	case amd64
		cd /sys/src/9/pc64
		mk install
	case 386
		cd /sys/src/9/pc
		mk install
	case arm
		cd /sys/src/9/bcm
		mk 'CONF=pi' install
		mk 'CONF=pi2' install
	case arm64
		cd /sys/src/9/bcm64
		mk 'CONF=pi3' install
		mk 'CONF=pi4' install
		cd /sys/src/9/imx8
		mk 'CONF=reform' install
	case spim
		cd /sys/src/9/mt7688/
		mk install
	}

	# bootloader
	switch($objtype){
	case 386 amd64
		cd /sys/src/boot/pc
		mk install
		cd /sys/src/boot/efi
		mk install
	case arm arm64
		cd /sys/src/boot/bcm
		mk
		cd /sys/src/boot/reform
		mk
	}
}

# postclean
cd /sys/src
mk clean
cd /sys/src/boot
mk clean
cd /sys/src/9
mk clean

# make manpage indices
cd /sys/man
mk

fn mkdist {
	cd /sys/lib/dist
	mk $www/$1.gz
}

for(a in $archs){
	switch($a){
	case amd64
		mkdist $release.amd64.iso
	case 386
		mkdist $release.386.iso
	case arm
		mkdist $release.pi.img
	case arm64
		mkdist $release.pi3.img
		mkdist $release.reform.img
	}
}

A  => bin/nightlyiso.rc +32 -0
@@ 1,32 @@
#!/bin/rc
rfork en

flagfmt='f:force'
eval `{aux/getflags $*}

home=/usr/build

cd $home/code/plan9front
git/pull
prev=`{cat prev}
head=`{git/query HEAD}
rev=`{git/log -s | wc -l}
release=9front-$rev
log=$home/www/9front/^$release^.log
res=$home/www/9front/$rev.status
echo release $release

if(~ $prev $head && ~ $force '')
	exit

buildisos.rc >$log >[2=1]
date >>$log
if(~ `{ls $home/www/9front/*gz | grep $rev | wc -l} 5){
	echo pass >$res
	echo $rev >$home/www/9front/latest
}
if not
	echo fail >$res

echo $head >>$res
echo $head >prev

A  => namespace +1 -0
@@ 1,1 @@
bind -a /cfg/$sysname/bin /bin/

A  => namespace.httpd +2 -0
@@ 1,2 @@
bind -a /usr/build/www /usr/www
bind -a /n/magic /usr/www

A  => service/tcp17019 +11 -0
@@ 1,11 @@
#!/bin/rc
if(~ $#* 3){
	netdir=$3
	remote=$2!`{cat $3/remote}
}
fn server {
	~ $#remote 0 || echo -n $netdir $remote >/proc/$pid/args
	rm -f /env/'fn#server'
	. <{n=`{read} && ! ~ $#n 0 && read -c $n} >[2=1]
}
exec tlssrv -a /bin/rc -c server

A  => service/tcp564 +2 -0
@@ 1,2 @@
#!/bin/rc
exec /bin/exportfs -R -r /usr/build/code/plan9front

A  => service/tcp80 +3 -0
@@ 1,3 @@
#!/bin/rc
execfs -m /n/magic /cfg/$sysname/src/execfs/builds/rules
exec tcp80 >>[2]/sys/log/www

A  => src/execfs/builds/footer +1 -0
@@ 1,1 @@
</html>

A  => src/execfs/builds/header +29 -0
@@ 1,29 @@
<html>
	
	<head>
		<style type="text/css">
			body{
				padding: 3em;
				margin: auto;
				min-width: min(95vw, 50em);
				width: min-content;
				font-family: sans-serif;
				tab-space: 8;
			}
			h1{
				font-size: 1.5em;
				color: #4c4c99;
			}
			h2{
				font-size: 1.3em;
				color: #4c4c99;
			}
			h3{
				font-size: 1em;
				color: #4c4c99;
			}
		</style>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
		<meta name="viewport" content="width=device-width,initial-scale=1">
		<title>iso</title>
	</head>

A  => src/execfs/builds/index.rc +27 -0
@@ 1,27 @@
#!/bin/rc

root=/cfg/$sysname/src/execfs/builds

cat $root/header
rev=`{cat /usr/build/www/9front/latest}
echo '<h1>9front builds</h1>'

echo '<h2>Nightly ISO</h2>'
for(i in /usr/build/www/9front/*^$rev^*.gz){
	i=`{basename $i}
	echo '<a href="/9front/'^$i^'">'^$i^'</a><br>'
}

echo '<h2>Logs</h2>'
for(i in /usr/build/www/9front/*.status){
	cat $i | {
		res=`{read}
		commit=`{read}
	}
	rev=`{basename $i | sed 's/.status//g'}
	echo '<a>['^$res^']</a>'
	echo '<a href="http://git.9front.org/plan9front/plan9front/'^$commit^'/commit.html">'^$commit^'</a>'
	echo '<a href="/9front/9front-'^$rev^'.log">Build Logs</a><br>'
}

cat $root/footer

A  => src/execfs/builds/rules +2 -0
@@ 1,2 @@
/index.html	/cfg/$sysname/src/execfs/builds/index.rc
/man2html/([0-9])/([a-zA-Z0-9\.]+)	troff -manhtml /usr/build/code/plan9front/sys/man/\1/\2 | troff2html -t '\2(\1)'

A  => src/execfs/execfs.c +307 -0
@@ 1,307 @@
#include <u.h>
#include <libc.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>

#include <bio.h>
#include <regexp.h>

typedef struct Xrule Xrule;
typedef struct Xfile Xfile;

struct Xrule
{
	Reprog	*re;
	char	*sub;
	Xrule	*next;
};

struct Xfile
{
	char	*path;
	char	*dest;
	int	fd;
};

Xrule *rules;

void
readrules(char *file)
{
	Biobuf *bio;
	char *s, *p, *d;

	if((bio = Bopen(file, OREAD)) == nil)
		sysfatal("open: %r");
	while(s = Brdstr(bio, '\n', 1)){
		Xrule *r;

		p = s;
		while(strchr("\t ", *p))
			p++;
		d = nil;
		if(*p != '#'){
			if(d = strchr(p, '\t'))
				*d++ = 0;
			else if(d = strchr(p, ' '))
				*d++ = 0;
		}
		if(d == nil){
			free(s);
			continue;
		}
		while(strchr("\t ", *d))
			d++;

		r = emalloc9p(sizeof(*r));
		if(r->re = regcomp(p)){
			r->sub = estrdup9p(d);
			r->next = rules;
			rules = r;
		} else {
			fprint(2, "regcomp failed: %s\n", p);
			free(r);
		}
		free(s);
	}
	Bterm(bio);
}

char*
matchrule(char *path)
{
	Xrule *r;
	Resub m[16];
	char *s;

	for(r = rules; r; r = r->next){
		memset(m, 0, sizeof(m));
		if(regexec(r->re, path, m, nelem(m))){
			s = emalloc9p(1024);
			regsub(r->sub, s, 1024, m, nelem(m));
			return s;
		}
	}
	return nil;
}

ulong
hash(char *s)
{
	ulong h, t;
	char c;

	h = 0;
	while(c = *s++){
		t = h & 0xf8000000;
		h <<= 5;
		h ^= t>>27;
		h ^= (ulong)c;
	}
	return h;
}

void
fsattach(Req *req)
{
	Xfile *x;

	if(req->ifcall.aname && req->ifcall.aname[0]){
		respond(req, "invalid attach specifier");
		return;
	}

	x = emalloc9p(sizeof(*x));
	x->path = estrdup9p("");
	x->dest = nil;
	x->fd = -1;

	req->fid->aux = x;
	req->fid->qid.path = hash(x->path);
	req->fid->qid.type = QTDIR;
	req->fid->qid.vers = 0;
	req->ofcall.qid = req->fid->qid;

	respond(req, nil);
}

char*
fswalk1(Fid *fid, char *name, Qid *qid)
{
	Xfile *x;
	char *p;

	if(fid->qid.type != QTDIR)
		return "walk in non-directory";

	x = fid->aux;
	if(strcmp(name, ".") == 0){
	} else if(strcmp(name, "..") == 0){
		if(p = strrchr(x->path, '/'))
			*p = 0;
	} else {
		p = smprint("%s/%s", x->path, name);
		free(x->path);
		x->path = p;
	}
	fid->qid.path = hash(x->path);
	if(x->dest = matchrule(x->path))
		fid->qid.type = QTFILE;
	else
		fid->qid.type = QTDIR;
	if(qid)
		*qid = fid->qid;
	return nil;
}

char*
fsclone(Fid *old, Fid *new)
{
	Xfile *x;

	x = emalloc9p(sizeof(*x));
	memmove(x, old->aux, sizeof(*x));
	if(x->path)
		x->path = estrdup9p(x->path);
	if(x->dest)
		x->dest = estrdup9p(x->dest);
	new->aux = x;
	return nil;
}

void
fsstat(Req *req)
{
	Xfile *x;
	char *p;
	Dir *d;

	x = req->fid->aux;
	d = &req->d;
	memset(d, 0, sizeof(*d));
	d->uid = estrdup9p("execfs");
	d->gid = estrdup9p("execfs");
	d->atime = d->mtime = time(0);
	if(p = strrchr(x->path, '/'))
		d->name = estrdup9p(p+1);
	else
		d->name = estrdup9p("/");
	if(x->dest){
		d->qid.type = QTFILE;
		d->mode = 0444;
	}else {
		d->qid.type = QTDIR;
		d->mode = DMDIR|0555;
	}
	respond(req, nil);
}

void
fsread(Req *req)
{
	int n, pfd[2];
	Xfile *x;
	Srv *srv;

	x = req->fid->aux;
	if(x->dest == nil){
		req->ofcall.count = 0;
		respond(req, nil);
		return;
	}
	if(x->fd < 0){
		if(pipe(pfd) < 0){
			responderror(req);
			return;
		}
		if(rfork(RFPROC|RFNOWAIT|RFFDG|RFREND) == 0){
			char *argv[4];
			int i;

			argv[0] = "rc";
			argv[1] = "-c";
			argv[2] = x->dest;
			argv[3] = nil;

			close(0);
			open("/dev/null", OREAD);
			dup(pfd[1], 1);
			dup(pfd[1], 2);
			for(i=3; i<20; i++)
				close(i);
			exec("/bin/rc", argv);
			exits("exec");
		}
		x->fd = pfd[0];
		close(pfd[1]);
	}
	srvrelease(srv = req->srv);
	if((n = read(x->fd, req->ofcall.data, req->ifcall.count)) < 0)
		responderror(req);
	else { 
		req->ofcall.count = n;
		respond(req, nil);
	}
	srvacquire(srv);
}

void
fsclunk(Fid *fid)
{
	Xfile *x;

	if(x = fid->aux){
		fid->aux = nil;

		if(x->fd >= 0)
			close(x->fd);
		free(x->path);
		free(x->dest);
		free(x);
	}
}

Srv fs = {
	.attach = fsattach,
	.stat = fsstat,
	.read = fsread,
	.walk1 = fswalk1,
	.clone = fsclone,
	.destroyfid = fsclunk,
};

void
usage(void)
{
	fprint(2, "usage: %s [-D] [-m mtpt] [-s srv] rulefile\n", argv0);
	exits("usage");
}

void
main(int argc, char *argv[])
{
	char *srv, *mtpt;

	srv = nil;
	mtpt = "/n/execfs";
	ARGBEGIN {
	case 'D':
		chatty9p++;
		break;
	case 'm':
		mtpt = EARGF(usage());
		break;
	case 's':
		srv = EARGF(usage());
		break;
	default:
		usage();
	} ARGEND;

	if(argc != 1)
		usage();

	readrules(*argv);
	postmountsrv(&fs, srv, mtpt, MREPL);
}

A  => src/execfs/mkfile +8 -0
@@ 1,8 @@
</$objtype/mkfile

TARG=execfs
BIN=/$objtype/bin

OFILES=execfs.$O

</sys/src/cmd/mkone

A  => src/execfs/rules +2 -0
@@ 1,2 @@
# mount -q /srv/magic /usr/web/magic
/man2html/([0-9])/([a-zA-Z0-9\.]+)	troff -manhtml /sys/man/\1/\2 | troff2html -t '\2(\1)'

A  => src/tcp80/mkfile +11 -0
@@ 1,11 @@
</$objtype/mkfile

TARG=tcp80
BIN=/$objtype/bin
CFLAGS=-FTVw

HFILES=

OFILES=tcp80.$O

</sys/src/cmd/mkone

A  => src/tcp80/tcp80.c +607 -0
@@ 1,607 @@
#include <u.h>
#include <libc.h>
#include <ctype.h>
#include <auth.h>

typedef struct Pair Pair;
struct Pair
{
	Pair	*next;

	char	key[64];
	char	val[256];
	char	*att;
};

int trusted;

char remote[128];
char method[64];
char location[1024];

Pair *header;
int naheader;
Pair aheader[64];


Pair*
findhdr(Pair *h, char *key)
{
	if(h == nil)
		h = header;
	else
		h = h->next;
	for(; h; h = h->next)
		if(cistrcmp(h->key, key) == 0)
			break;
	return h;
}

char*
nstrcpy(char *d, char *s, int n)
{
	d[n-1] = 0;
	return strncpy(d, s, n-1);
}

char hex[] = "0123456789ABCDEF";

char*
urldec(char *d, char *s, int n)
{
	int c, x;
	char *r;

	r = d;
	x = 0;
	while(n > 1 && (c = *s++)){
		if(x){
			char *p;

			if((p = strchr(hex, toupper(c))) == nil)
				continue;
			*d <<= 4;
			*d |= p - hex;
			if(--x)
				continue;
		} else {
			if(c == '%'){
				x = 2;
				continue;
			}
			*d = c;
		}
		d++;
		n--;
	}
	*d = 0;
	return r;
}

char*
urlenc(char *d, char *s, int n)
{
	char *r;
	int c;

	r = d;
	while(n > 1 && (c = *s++)){
		if(isalnum(c) || strchr("$-_.+!*'(),", c) || strchr("/:;=@", c)){
			*d++ = c;
			n--;
		} else {
			if(n <= 3)
				break;
			*d++ = '%';
			*d++ = hex[(c>>4)&15];
			*d++ = hex[c&15];
			n -= 3;
		}
	}
	*d = 0;
	return r;
}

int
isleap(int year)
{
	return year%4==0 && (year%100!=0 || year%400==0);
}

long
hdate(char *s)
{
	int i;
	Tm tm;

	static int mday[2][12] = {
		31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
		31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
	};
	static char *wday[] = {
		"Sunday", "Monday", "Tuesday", "Wednesday",
		"Thursday", "Friday", "Saturday",
	};
	static char *mon[] = {
		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
	};

	/* Sunday, */
	for(i=0; i<nelem(wday); i++){
		if(cistrncmp(s, wday[i], strlen(wday[i])) == 0){
			s += strlen(wday[i]);
			break;
		}
		if(cistrncmp(s, wday[i], 3) == 0){
			s += 3;
			break;
		}
	}
	if(*s == ',')
		s++;
	if(*s == ' ')
		s++;
	/* 25- */
	if(!isdigit(s[0]) || !isdigit(s[1]) || (s[2]!='-' && s[2]!=' '))
		return -1;
	tm.mday = strtol(s, 0, 10);
	s += 3;
	/* Jan- */
	for(i=0; i<nelem(mon); i++)
		if(cistrncmp(s, mon[i], 3) == 0){
			tm.mon = i;
			s += 3;
			break;
		}
	if(i==nelem(mon))
		return -1;
	if(s[0] != '-' && s[0] != ' ')
		return -1;
	s++;
	/* 2002 */
	if(!isdigit(s[0]) || !isdigit(s[1]))
		return -1;
	tm.year = strtol(s, 0, 10);
	s += 2;
	if(isdigit(s[0]) && isdigit(s[1]))
		s += 2;
	else{
		if(tm.year <= 68)
			tm.year += 2000;
		else
			tm.year += 1900;
	}
	if(tm.mday==0 || tm.mday > mday[isleap(tm.year)][tm.mon])
		return -1;
	tm.year -= 1900;
	if(*s++ != ' ')
		return -1;
	if(!isdigit(s[0]) || !isdigit(s[1]) || s[2]!=':'
	|| !isdigit(s[3]) || !isdigit(s[4]) || s[5]!=':'
	|| !isdigit(s[6]) || !isdigit(s[7]) || s[8]!=' ')
		return -1;
	tm.hour = atoi(s);
	tm.min = atoi(s+3);
	tm.sec = atoi(s+6);
	if(tm.hour >= 24 || tm.min >= 60 || tm.sec >= 60)
		return -1;
	s += 9;
	if(cistrcmp(s, "GMT") != 0)
		return -1;
	nstrcpy(tm.zone, s, sizeof(tm.zone));
	tm.yday = 0;
	return tm2sec(&tm);
}

void
headers(char *path, Dir *d)
{
	char buf[1024], *f[6];
	int isdir;
	Tm *tm;

	if(tm = localtime(time(0))){
		nstrcpy(buf, asctime(tm), sizeof(buf));
		if(tokenize(buf, f, 6) == 6)
			print("Date: %s, %.2d %s %s %s %s\r\n",
				f[0], tm->mday, f[1], f[5], f[3], f[4]);
	}
	if(d && (tm = localtime(d->mtime))){
		nstrcpy(buf, asctime(tm), sizeof(buf));
		if(tokenize(buf, f, 6) == 6)
			print("Last-Modified: %s, %.2d %s %s %s %s\r\n",
				f[0], tm->mday, f[1], f[5], f[3], f[4]);
	}
	isdir = d && (d->qid.type & QTDIR);
	if(isdir || cistrstr(path, ".htm"))
		print("Content-Type: text/html; charset=utf-8\r\n");
	if(*path == '/')
		print("Content-Location: %s%s\r\n",
			urlenc(buf, path, sizeof(buf)), isdir ? "/" : "");
}

int
dircmp(Dir *a, Dir *b)
{
	return strcmp(a->name, b->name);
}

char*
fullurl(char *host, char *path, char *name, char *query)
{
	static char buf[1024];

	snprint(buf, sizeof(buf), "%s%s%s%s%s%s",
		host ? "http://" : "", host ? host : "", 
		path ? path : "/", name ? name : "",
		query ? "?" : "", query ? query : "");
	return buf;
}

void
respond(char *status)
{
	syslog(0, "tcp80", "%s %s %s %s", remote, method, location, status);
	print("HTTP/1.1 %s\r\n", status);
}

int
dispatch(void)
{
	static char buf[8192], tmp[1024];
	char *p, *s, *status;
	int i, n, fd, badmeth, nobody, noindex, noslash;
	Pair *h;
	Dir *d;

	nobody = !cistrcmp(method, "HEAD");
	badmeth = !nobody && cistrcmp(method, "GET");
	if(badmeth){
		werrstr("%s method unsupported", method);
		status = "405 Method Not Allowed";
Error:
		if(!nobody)
			n = snprint(buf, sizeof(buf), 
			"<html><head><title>%s</title></head>\n"
			"<body><h1>%s</h1><pre>%r</pre></body></html>\n",
			status, status);
		else
			n = 0;
		respond(status);
		headers(".html", nil);
		print("Content-Length: %d\r\n\r\n%*s", n, n, buf);
		return -badmeth;
	}

	s = location;
	if(cistrncmp(s, "http:", 5) == 0)
		s += 5;
	else if(cistrncmp(s, "https:", 6) == 0)
		s += 6;
	if(s[0] == '/' && s[1] == '/')
		s = strchr(s+2, '/');
	if(s == nil || *s == 0)
		s = "/";
	nstrcpy(tmp, s, sizeof(tmp));
	if(s = strchr(tmp, '#'))
		*s = 0;
	noindex = 0;
	if(s = strchr(tmp, '?')){
		*s++ = 0;
		noindex = !cistrcmp(s, "noindex");
	}
	urldec(buf, tmp, sizeof(buf));

	noslash = 1;
	if(s = strrchr(buf, '/'))
		if(s[1] == 0)
			noslash = 0;

	cleanname(buf);
	if((fd = open(buf, OREAD)) < 0){
		rerrstr(buf, sizeof(buf));
		if(strstr(buf, "permission denied")){
			status = "403 Forbidden";
			goto Error;
		}
		status = "404 Not found";
		goto Error;
	}

	if((d = dirfstat(fd)) == nil){
		close(fd);
		status = "500 Internal Server Error";
		goto Error;
	}

	if(d->qid.type & QTDIR){
		int fd2;
		Dir *d2;

		if(noslash){
			status = "301 Moved Permanently";
			respond(status);
			headers(buf, d);

			h = findhdr(nil, "Host");
			p = strchr(location, '?');
			s = fullurl(h ? h->val : nil, urlenc(tmp, buf, sizeof(tmp)), "/", p ? p+1 : nil);
			if(!nobody)
				n = snprint(buf, sizeof(buf), 
				"<html><head><title>%s</title></head>\n"
				"<body><h1>%s</h1><pre>Moved to <a href=\"%s\">%s</a></pre></body></html>\n",
				status, status, s, s);
			else
				n = 0;
			print("Location: %s\r\nContent-Length: %d\r\n\r\n%*s", s, n, n, buf);
			goto Out;
		}

		if(!noindex){
			snprint(tmp, sizeof(tmp), "%s/index.html", buf);
			cleanname(tmp);
			if((fd2 = open(tmp, OREAD)) >= 0){
				if(d2 = dirfstat(fd2)){
					if((d2->qid.type & QTDIR) == 0){
						nstrcpy(buf, tmp, sizeof(buf));
						close(fd);
						fd = fd2;
						free(d);
						d = d2;
						goto Filecont;
					}
					free(d2);
				}
				close(fd2);
			}
		}

		respond("200 OK");
		headers(buf, d);
		print("\r\n");
		if(nobody)
			goto Out;

		print(	"<html><head><title>%s</title></head><body>"
			"<pre>\n<a href=\"/%s\">/</a>", 
			buf, noindex ? "?noindex" : "");
		for(p = buf+1; *p; p = s+1){
			if(s = strchr(p, '/'))
				*s = 0;
			print(	"<a href=\"%s/%s\">%s</a>/", 
				urlenc(tmp, buf, sizeof(tmp)), noindex ? "?noindex" : "", p);
			if(s == nil)
				break;
			*s = '/';
		}
		print("<hr>");

		free(d);
		d = nil;
		if((n = dirreadall(fd, &d)) > 0){
			qsort(d, n, sizeof d[0], (int (*)(void*, void*))dircmp);
			for(i=0; i<n; i++)
				print("<a href=\"%s%s\">%s</a>%s\n", 
					urlenc(tmp, d[i].name, sizeof(tmp)),
					(d[i].qid.type & QTDIR) ? (noindex ? "/?noindex" : "/") : "",
					d[i].name,
					(d[i].qid.type & QTDIR) ? "/" : "");
			free(d);
		}
		print("</pre></body></html>\n");
		return 1;
	} else {
		vlong start, end;

Filecont:
		h = findhdr(nil, "If-Modified-Since");
		if(h && !nobody){
			long t;

			if((t = hdate(h->val)) != -1){
				if(d->mtime <= t){
					respond("304 Not Modified");
					headers(buf, d);
					print("\r\n");
					goto Out;
				}
			}
		}

		h = findhdr(nil, "Range");
		while(h){
			if(findhdr(h, "Range"))
				break;
			if(s = strchr(h->val, '='))
				s++;
			else
				s = h->val;
			start = strtoll(s, &s, 10);
			if(*s++ != '-')
				break;
			if(*s == 0)
				end = d->length;
			else
				end = strtoll(s, &s, 10)+1;
			if(*s != 0 || (end <= start))
				break;
			respond("206 Partial content");
			print("Content-Range: bytes %lld-%lld/%lld\r\n",
				start, end-1, d->length);
			goto Content;
		}
		start = 0;
		end = d->length;
		respond("200 OK");
Content:
		headers(buf, d);
		if(end > start){
			print("Content-Length: %lld\r\n\r\n", end - start);
			if(nobody)
				goto Out;
			while(start < end){
				n = sizeof(buf);
				if((end - start) < n)
					n = end - start;
				if((n = pread(fd, buf, n, start)) <= 0)
					return -1;
				if(write(1, buf, n) != n)
					return -1;
				start += n;
			}
		} else {
			print("\r\n");
			if(nobody)
				goto Out;
			while((n = read(fd, buf, sizeof(buf))) > 0)
				if(write(1, buf, n) != n)
					return -1;
			return 1;
		}
	}
Out:
	close(fd);
	free(d);
	return 0;
}

char*
token(char *s, char *delim, char **pe)
{
	char *e;
	int d;

	d = 0;
	while(*s == ' ' || *s == '\t')
		s++;
	for(e = s; *e; e++){
		if(*e == '(')
			d++;
		if(d > 0){
			if(*e == ')')
				d--;
			s = e+1;
			continue;
		}
		if(strchr(delim, *e)){
			*e++ = 0;
			break;
		}
	}
	if(pe)
		*pe = e;
	while(s < e && *s == ' ' || *s == '\t')
		s++;
	while(--e >= s){
		if(*e != ' ' && *e != '\t')
			break;
		*e = 0;
	}
	return s;
}

void
main(int argc, char **argv)
{
	static char buf[1024], line[1024];
	char *sys;
	char *p, *e, *k, *x, *s;
	int lineno, n;
	Pair *h;

	ARGBEGIN {
	case 't':
		trusted++;
		break;
	} ARGEND

	time(0);
	if(argc){
		int fd;
		snprint(buf, sizeof(buf), "%s/remote", argv[argc-1]);
		if((fd = open(buf, OREAD)) >= 0){
			if((n = read(fd, remote, sizeof(remote)-1)) >= 0){
				while(n > 0 && remote[n-1] == '\n')
					n--;
				remote[n] = 0;
			}
			close(fd);
		}
	}
	if(remote[0] == 0)
		strcpy(remote, "-");
	if(!trusted){
		sys = getenv("sysname");
		if(sys)
			snprint(buf, sizeof buf, "/cfg/%s/namespace.httpd", sys);
		else
			snprint(buf, sizeof buf, "/lib/namespace.httpd");
		free(sys);
		if(addns("none", buf) < 0)
			return;
		if(bind("/usr/www", "/", MREPL) < 0)
			return;
		if(rfork(RFNOMNT) < 0)
			return;
	}
	naheader = 0;
	lineno = 0;
	*line = 0;
	p = buf;
	e = buf + sizeof(buf);
	while((n = read(0, p, e - p)) > 0){
		p += n;
		while((p > buf) && (e = memchr(buf, '\n', p - buf))){
			if((e > buf) && (e[-1] == '\r'))
				e[-1] = 0;
			*e++ = 0;
			if(*buf != ' ' && *buf != '\t' && *line){
				if(lineno++ == 0){
					nstrcpy(method, token(line, "\t ", &s), sizeof(method));
					nstrcpy(location, token(s, "\t ", nil), sizeof(location));
				} else {
					if(lineno > 100)
						return;
					k = token(line, ":", &s);
					while(*s){
						if(naheader >= nelem(aheader))
							return;
						x = token(s, ",", &s);
						h = aheader + naheader++;
						nstrcpy(h->key, k, sizeof(h->key));
						nstrcpy(h->val, x, sizeof(h->val));
						if(x = strchr(h->val, ';')){
							*x++ = 0;
							x = token(x, ";", nil);
						}
						h->att = x;
						h->next = header;
						header = h;
					}
				}
			}
			nstrcpy(line, buf, sizeof(line));
			p -= e - buf;
			if(p > buf)
				memmove(buf, e, p - buf);
			if(*line == 0){
				if(method[0] == 0)
					return;
				if(dispatch())
					return;
				h = nil;
				while(h = findhdr(h, "Connection"))
					if(cistrcmp(h->val, "Keep-Alive") == 0)
						break;
				if(h == nil)
					return;
				method[0] = 0;
				naheader = 0;
				header = nil;
				lineno = 0;
			}
		}
		e = buf + sizeof(buf);
	}
}