shithub: iso

Download patch

ref: e4322f4dffdea1ac61d67e2f0852982c65f5e98d
author: Jacob Moody <[email protected]>
date: Thu Mar 30 21:39:48 EDT 2023

initial commit

diff: cannot open b/bin//null: file does not exist: 'b/bin//null' diff: cannot open b/service//null: file does not exist: 'b/service//null' diff: cannot open b/src/execfs/builds//null: file does not exist: 'b/src/execfs/builds//null' diff: cannot open b/src/execfs//null: file does not exist: 'b/src/execfs//null' diff: cannot open b/src/tcp80//null: file does not exist: 'b/src/tcp80//null' diff: cannot open b/src//null: file does not exist: 'b/src//null'
--- /dev/null
+++ b/bin/buildisos.rc
@@ -1,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
+	}
+}
--- /dev/null
+++ b/bin/nightlyiso.rc
@@ -1,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
--- /dev/null
+++ b/namespace
@@ -1,0 +1,1 @@
+bind -a /cfg/$sysname/bin /bin/
--- /dev/null
+++ b/namespace.httpd
@@ -1,0 +1,2 @@
+bind -a /usr/build/www /usr/www
+bind -a /n/magic /usr/www
--- /dev/null
+++ b/service/tcp17019
@@ -1,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
--- /dev/null
+++ b/service/tcp564
@@ -1,0 +1,2 @@
+#!/bin/rc
+exec /bin/exportfs -R -r /usr/build/code/plan9front
--- /dev/null
+++ b/service/tcp80
@@ -1,0 +1,3 @@
+#!/bin/rc
+execfs -m /n/magic /cfg/$sysname/src/execfs/builds/rules
+exec tcp80 >>[2]/sys/log/www
--- /dev/null
+++ b/src/execfs/builds/footer
@@ -1,0 +1,1 @@
+</html>
--- /dev/null
+++ b/src/execfs/builds/header
@@ -1,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>
--- /dev/null
+++ b/src/execfs/builds/index.rc
@@ -1,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
--- /dev/null
+++ b/src/execfs/builds/rules
@@ -1,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)'
--- /dev/null
+++ b/src/execfs/execfs.c
@@ -1,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);
+}
--- /dev/null
+++ b/src/execfs/mkfile
@@ -1,0 +1,8 @@
+</$objtype/mkfile
+
+TARG=execfs
+BIN=/$objtype/bin
+
+OFILES=execfs.$O
+
+</sys/src/cmd/mkone
--- /dev/null
+++ b/src/execfs/rules
@@ -1,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)'
--- /dev/null
+++ b/src/tcp80/mkfile
@@ -1,0 +1,11 @@
+</$objtype/mkfile
+
+TARG=tcp80
+BIN=/$objtype/bin
+CFLAGS=-FTVw
+
+HFILES=
+
+OFILES=tcp80.$O
+
+</sys/src/cmd/mkone
--- /dev/null
+++ b/src/tcp80/tcp80.c
@@ -1,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);
+	}
+}