shithub: riscv

Download patch

ref: b21b9ba89cf66a8fac6f94efb79cfb425a2c4df2
parent: ef1c1863051d0530a31b291f4e334ee7601c318c
author: aiju <[email protected]>
date: Tue Aug 7 13:57:04 EDT 2012

added hjfs

--- a/rc/bin/fstype
+++ b/rc/bin/fstype
@@ -19,6 +19,10 @@
 	echo paqfs
 	exit
 }
+if(~ $"m 011ce50d){
+	echo hjfs
+	exit
+}
 dd -if $1 -count 1 >[2]/dev/null | \
 awk '
 /^kfs/{fs["kfs"]++}
--- a/rc/bin/inst/bootsetup
+++ b/rc/bin/inst/bootsetup
@@ -37,7 +37,7 @@
 			bootfile=9pcf
 		@{
 			echo 'bootfile='^$bootfile
-			echo 'bootargs=local!'^$fs
+			echo 'bootargs=local!'^$fs^$fsflags
 			if(~ $#nvram 1)
 				echo 'nvram='^$nvram
 			echo 'mouseport='^$mouseport
--- a/rc/bin/inst/configfs
+++ b/rc/bin/inst/configfs
@@ -14,8 +14,9 @@
 	echo 'You can install the following types of file systems:'
 	echo
 	echo '	cwfs64x	the cached-worm file server'
+	echo '  hjfs    the new 9front file server (experimental!)'
 	echo
-	prompt -d cwfs64x 'File system' cwfs64x
+	prompt -d cwfs64x 'File system' cwfs64x hjfs
 	fstype=$rd
 	export fstype
 }
--- a/rc/bin/inst/mountcwfs
+++ b/rc/bin/inst/mountcwfs
@@ -22,6 +22,8 @@
 	prompt $default 'Cwfs cache partition' $files
 	fs=$rd
 	export fs
+	fsflags=
+	export fsflags
 
 	files=(`{ls /dev/sd*/fsworm* /dev/fs/fsworm* >[2]/dev/null})
 	if(! ~ $#files 0)
--- a/rc/bin/inst/mountfs
+++ b/rc/bin/inst/mountfs
@@ -6,6 +6,8 @@
 switch($fstype){
 case cwfs cwfs64 cwfs64x
 	exec mountcwfs $*
+case hjfs
+	exec mounthjfs $*
 case *
 	mountfs=notdone
 	export mountfs
--- /dev/null
+++ b/rc/bin/inst/mounthjfs
@@ -1,0 +1,70 @@
+#!/bin/rc
+
+# desc: choose and mount file system partition
+# prereq: systype
+
+service=hjfs
+
+switch($1){
+case go
+	echo
+	echo The please choose your $fstype partition
+	echo
+
+	files=(`{ls /dev/sd*/fs* >[2]/dev/null})
+	if(! ~ $#files 0)
+		ls -l $files
+	echo
+	if(~ $#files 1)
+		default=(-d $files)
+	if not
+		default=()
+	prompt $default 'Hjfs partition' $files
+	fs=$rd
+	export fs
+
+	mem=`{awk ' $2 == "pagesize" { p = $1 } $2 == "user" { split($1, a, "/"); print int((a[2] * p / 4 + 1048575) / 1048576)  } ' '#c'/swap}
+	prompt -d $mem 'Size of RAM filesystem cache (MB)?'
+	fsflags=(-m $rd)
+	export fsflags
+
+	log Starting $fstype file server for $fs
+	unmount /n/newfs >[2]/dev/null
+	echo halt >>/srv/$service.cmd >[2]/dev/null
+	rm -f /srv/$service /srv/$service.cmd
+
+	hjfs -n $service $fsflags -Srf $fs
+
+	log Configuring $fstype file server for $fs
+	{
+		echo echo on
+		echo create /dist sys sys 775 d
+		echo create /usr sys sys 775 d
+		echo newuser $user
+		echo newuser adm +$user
+		echo newuser sys +$user
+		echo newuser upas +$user
+		echo echo off
+		sleep 2
+	} >>/srv/$service.cmd
+
+	log Mounting $fstype file server for $fs
+	while(! logprog mount -c /srv/$service /n/newfs)
+		sleep 2
+	if(! ~ $fsother ''){
+		log Mounting $fstype file server for $fsother
+		logprog mount -c /srv/$service /n/other other
+	}
+
+case checkready checkdone
+	if(! ~ $fstype '' && ~ $#fs 1 && test -f $fs){
+		if(test -f /srv/$service && test -d /n/newfs/dist){
+			mountfs=done
+			export mountfs
+			exit
+		}
+	}
+	mountfs=ready
+	export mountfs
+	exit
+}
--- a/rc/bin/inst/prepdisk
+++ b/rc/bin/inst/prepdisk
@@ -7,6 +7,8 @@
 	switch($fstype){
 	case cwfs cwfs64 cwfs64x
 		echo -a 9fat -a nvram -a fscache -a fsworm -a other
+	case hjfs
+		echo -a 9fat -a nvram -a fs
 	}
 }
 
--- a/sys/src/9/port/bootfs.proto
+++ b/sys/src/9/port/bootfs.proto
@@ -24,6 +24,7 @@
 		mntgen
 		mount
 		mv
+		hjfs
 		rc
 		rm
 		sed
--- /dev/null
+++ b/sys/src/cmd/hjfs/9p.c
@@ -1,0 +1,250 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <fcall.h>
+#include <9p.h>
+#include "dat.h"
+#include "fns.h"
+
+extern Fs *fsmain;
+
+static void
+tauth(Req *req)
+{
+	if((fsmain->flags & FSNOAUTH) != 0)
+		respond(req, "no authentication required");
+	else
+		auth9p(req);
+}
+
+static void
+tattach(Req *req)
+{
+	Chan *ch;
+	int flags;
+	short uid;
+
+	if((fsmain->flags & FSNOAUTH) == 0 && authattach(req) < 0)
+		return;
+	if(name2uid(fsmain, req->ifcall.uname, &uid) <= 0){
+		respond(req, "no such user");
+		return;
+	}
+	if(req->ifcall.aname == nil || *req->ifcall.aname == 0)
+		flags = 0;
+	else if(strcmp(req->ifcall.aname, "dump") == 0)
+		flags = CHFDUMP|CHFRO;
+	else{
+		respond(req, Einval);
+		return;
+	}
+	ch = chanattach(fsmain, flags);
+	ch->uid = uid;
+	req->fid->aux = ch;
+	req->fid->qid = ch->loc->Qid;
+	req->ofcall.qid = ch->loc->Qid;
+	respond(req, nil);
+}
+
+static void
+tqueue(Req *req)
+{
+	Chan *ch;
+
+	if((req->fid->qid.type & QTAUTH) != 0){
+		switch(req->ifcall.type){
+		case Tread:
+			authread(req);
+			return;
+		case Twrite:
+			authwrite(req);
+			return;
+		default:
+			respond(req, Einval);
+			return;
+		}
+	}
+	ch = req->fid->aux;
+	if(ch == nil){
+		respond(req, "operation on closed fid");
+		return;
+	}
+	qlock(&chanqu);
+	req->aux = nil;
+	if(ch->freq == nil)
+		ch->freq = req;
+	if(ch->lreq != nil)
+		((Req *) ch->lreq)->aux = req;
+	ch->lreq = req;
+	if(ch->qnext == nil){
+		ch->qnext = &readych;
+		ch->qprev = ch->qnext->qprev;
+		ch->qnext->qprev = ch;
+		ch->qprev->qnext = ch;
+	}
+	if(req->ifcall.type == Tremove)
+		req->fid->aux = nil;
+	rwakeup(&chanre);
+	qunlock(&chanqu);
+}
+
+static void
+tdestroyfid(Fid *fid)
+{
+	Chan *ch;
+
+	if((fid->qid.type & QTAUTH) != 0){
+		authdestroy(fid);
+		return;
+	}
+	qlock(&chanqu);
+	ch = fid->aux;
+	fid->aux = nil;
+	qunlock(&chanqu);
+	if(ch != nil)
+		chanclunk(ch);
+}
+
+static void
+tend(Srv *)
+{
+	shutdown();
+}
+
+static Srv mysrv = {
+	.auth = tauth,
+	.attach = tattach,
+	.walk = tqueue,
+	.open = tqueue,
+	.create = tqueue,
+	.read = tqueue,
+	.write = tqueue,
+	.stat = tqueue,
+	.wstat = tqueue,
+	.remove = tqueue,
+	.destroyfid = tdestroyfid,
+	.end = tend,
+};
+
+void
+start9p(char *service, int stdio)
+{
+	if(stdio){
+		mysrv.infd = 1;
+		mysrv.outfd = 1;
+		srv(&mysrv);
+	}else
+		threadpostmountsrv(&mysrv, service, nil, 0);
+}
+
+static char *
+tclone(Fid *old, Fid *new, void *)
+{
+	Chan *ch;
+	
+	ch = old->aux;
+	if(ch->open != 0)
+		return "trying to clone an open fid";
+	new->aux = chanclone(ch);
+	return nil;
+}
+
+static char *
+twalk(Fid *fid, char *name, void *)
+{
+	Chan *ch;
+	char buf[ERRMAX];
+	
+	ch = fid->aux;
+	if(chanwalk(ch, name) < 0){
+		rerrstr(buf, ERRMAX);
+		return strdup(buf);
+	}
+	fid->qid = ch->loc->Qid;
+	return nil;
+}
+
+static void
+workerproc(void *)
+{
+	Chan *ch;
+	Req *req;
+	int rc;
+	Fcall *i, *o;
+
+	qlock(&chanqu);
+	for(;;){
+		while(readych.qnext == &readych)
+			rsleep(&chanre);
+		ch = readych.qnext;
+		ch->qnext->qprev = ch->qprev;
+		ch->qprev->qnext = ch->qnext;
+		ch->qprev = nil;
+		ch->qnext = nil;
+		while(ch != nil && ch->freq != nil){
+			req = ch->freq;
+			ch->freq = req->aux;
+			if(ch->lreq == req)
+				ch->lreq = nil;
+			req->aux = nil;
+			qunlock(&chanqu);
+			i = &req->ifcall;
+			o = &req->ofcall;
+			switch(req->ifcall.type){
+			case Twalk:
+				walkandclone(req, twalk, tclone, nil);
+				goto noret;
+			case Topen:
+				rc = chanopen(ch, i->mode);
+				break;
+			case Tcreate:
+				rc = chancreat(ch, i->name, i->perm, i->mode);
+				if(rc >= 0){
+					o->qid = ch->loc->Qid;
+					req->fid->qid = o->qid;
+				}
+				break;
+			case Tread:
+				rc = o->count = chanread(ch, o->data, i->count, i->offset);
+				break;
+			case Twrite:
+				rc = o->count = chanwrite(ch, i->data, i->count, i->offset);
+				break;
+			case Tremove:
+				rc = chanremove(ch);
+				req->fid->aux = ch = nil;
+				break;
+			case Tstat:
+				rc = chanstat(ch, &req->d);
+				break;
+			case Twstat:
+				rc = chanwstat(ch, &req->d);
+				break;
+			default:
+				werrstr(Einval);
+				rc = -1;
+			}
+			if(rc < 0)
+				responderror(req);
+			else
+				respond(req, nil);
+		noret:
+			qlock(&chanqu);
+		}
+	}
+}
+
+QLock chanqu;
+Chan readych;
+Rendez chanre;
+
+void
+workerinit(void)
+{
+	int i;
+	
+	readych.qnext = readych.qprev = &readych;
+	chanre.l = &chanqu;
+	for(i = 0; i < NWORKERS; i++)
+		threadcreate(workerproc, nil, mainstacksize);
+}
--- /dev/null
+++ b/sys/src/cmd/hjfs/auth.c
@@ -1,0 +1,508 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "dat.h"
+#include "fns.h"
+
+typedef struct User User;
+typedef struct PUser PUser;
+
+enum { USERMAX = 64 };
+
+struct User {
+	short uid;
+	char name[USERMAX];
+	short lead;
+	int nmemb;
+	short *memb;
+};
+
+struct PUser {
+	short uid;
+	char name[USERMAX];
+	char lead[USERMAX];
+	int nmemb;
+	char (*memb)[USERMAX];
+};
+
+User udef[] = {
+	{-1, "adm", -1, 0, nil},
+	{0, "none", -1, 0, nil},
+	{1, "tor", 1, 0, nil},
+	{2, "glenda", 2, 0, nil},
+	{10000, "sys", NOUID, 0, nil},
+	{10001, "map", 10001, 0, nil},
+	{10002, "doc", NOUID, 0, nil},
+	{10003, "upas", 10003, 0, nil},
+	{10004, "font", NOUID, 0, nil},
+};
+
+static int
+validuser(char *n)
+{
+	char *p;
+
+	if(*n == 0)
+		return 0;
+	for(p = n; *p != 0; p++)
+		if((uchar) *p < ' ' || strchr("?=+-/:", *p) != nil)
+			return 0;
+	return n - p < USERMAX;
+}
+
+static void
+usersparseline(char *l, PUser **u, int *nu)
+{
+	PUser v;
+	char *f[5], *r, *s;
+	int c;
+	
+	if(*l == 0 || *l == '#')
+		return;
+	c = getfields(l, f, 5, 0, ":");
+	if(c < 4)
+		return;
+	v.uid = strtol(f[0], &r, 10);
+	if(*r != 0)
+		return;
+	if(!validuser(f[1]) || *f[2] != 0 && !validuser(f[2]))
+		return;
+	strcpy(v.name, f[1]);
+	strcpy(v.lead, f[2]);
+	v.memb = nil;
+	v.nmemb = 0;
+	r = f[3];
+	while(r != nil && *r != 0){
+		s = strchr(r, ',');
+		if(s != nil)
+			*s = 0;
+		if(!validuser(r)){
+			free(v.memb);
+			return;
+		}
+		v.memb = realloc(v.memb, (v.nmemb + 1) * USERMAX);
+		strcpy(v.memb[v.nmemb++], r);
+		if(s == nil)
+			r = nil;
+		else
+			r = s + 1;
+	}
+	*u = realloc(*u, (*nu + 1) * sizeof(PUser));
+	memcpy(&(*u)[(*nu)++], &v, sizeof(PUser));
+}
+
+static int
+puserlook(PUser *u, int nu, char *name)
+{
+	PUser *v;
+
+	if(*name == 0)
+		return NOUID;
+	for(v = u; v < u + nu; v++)
+		if(strcmp(v->name, name) == 0)
+			return v->uid;
+	return NOUID;
+}
+
+static int
+uidcomp(void *a, void *b)
+{
+	short *aa, *bb;
+	
+	aa = a;
+	bb = b;
+	return *aa - *bb;
+}
+
+static int
+usercomp(void *a, void *b)
+{
+	User *aa, *bb;
+	
+	aa = a;
+	bb = b;
+	return aa->uid - bb->uid;
+}
+
+int
+usersload(Fs *fs, Chan *ch)
+{
+	char *buf, *p, *q;
+	int bufl, i, j, rc, nu;
+	PUser *u;
+	User *v;
+	
+	buf = nil;
+	bufl = 0;
+	u = nil;
+	v = nil;
+	nu = 0;
+	for(;;){
+		if((bufl & 1023) == 0)
+			buf = realloc(buf, bufl + 1024);
+		rc = chanread(ch, buf + bufl, 1024, bufl);
+		if(rc < 0)
+			goto err;
+		if(rc == 0)
+			break;
+		bufl += rc;
+	}
+	if(buf == nil)
+		goto done;
+	buf[bufl] = 0;
+	for(p = buf; q = strchr(p, '\n'); p = q + 1){
+		*q = 0;
+		usersparseline(p, &u, &nu);
+	}
+	usersparseline(p, &u, &nu);
+	free(buf);
+	if(nu == 0)
+		goto done;
+	v = emalloc(sizeof(User) * nu);
+	for(i = 0; i < nu; i++){
+		v[i].uid = u[i].uid;
+		strcpy(v[i].name, u[i].name);
+		v[i].lead = puserlook(u, nu, u[i].lead);
+		v[i].nmemb = u[i].nmemb;
+		v[i].memb = emalloc(sizeof(short) * v[i].nmemb);
+		for(j = 0; j < v[i].nmemb; j++)
+			v[i].memb[j] = puserlook(u, nu, u[i].memb[j]);
+		qsort(v[i].memb, v[i].nmemb, sizeof(ushort), uidcomp);
+	}
+	qsort(v, nu, sizeof(User), usercomp);
+done:
+	wlock(&fs->udatal);
+	if(fs->udata != nil){
+		for(i = 0; i < fs->nudata; i++)
+			free(((User *)fs->udata)[i].memb);
+		free(fs->udata);
+	}
+	fs->udata = v;
+	fs->nudata = nu;
+	wunlock(&fs->udatal);
+	return 0;
+err:
+	free(buf);
+	return -1;
+}
+
+int
+userssave(Fs *fs, Chan *ch)
+{
+	User *u, *v;
+	int nu, i;
+	char buf[512], *p, *e;
+	uvlong off;
+	
+	rlock(&fs->udatal);
+	u = fs->udata;
+	if(u == nil){
+		u = udef;
+		nu = nelem(udef);
+	}else
+		nu = fs->nudata;
+	off = 0;
+	for(v = u; v < u + nu; v++){
+		p = buf;
+		e = buf + sizeof(buf);
+		p = seprint(p, e, "%d:%s:", v->uid, v->name);
+		if(v->lead != NOUID)
+			p = strecpy(p, e, uid2name(fs, v->lead));
+		if(p < e)
+			*p++ = ':';
+		for(i = 0; i < v->nmemb; i++){
+			if(v->memb[i] == NOUID)
+				continue;
+			if(p < e && i > 0)
+				*p++ = ',';
+			p = strecpy(p, e, uid2name(fs, v->memb[i]));
+		}
+		*p++ = '\n';
+		if(ch == nil)
+			write(2, buf, p - buf);
+		else if(chanwrite(ch, buf, p - buf, off) < p - buf)
+			goto err;
+		off += p - buf;
+	}
+	runlock(&fs->udatal);
+	return 0;
+err:
+	runlock(&fs->udatal);
+	return -1;
+}
+
+static User *
+lookupuid(Fs *fs, short uid)
+{
+	User *u;
+	int i, j, k;
+
+	u = fs->udata;
+	i = 0;
+	j = fs->nudata;
+	if(u == nil){
+		u = udef;
+		j = nelem(udef);
+	}
+	if(j == 0)
+		return nil;
+	while(i < j){
+		k = (i + j) / 2;
+		if(u[k].uid < uid)
+			i = k + 1;
+		else
+			j = k;
+	}
+	if(u[i].uid == uid)
+		return &u[i];
+	return nil;
+}
+
+int
+ingroup(Fs *fs, short uid, short gid, int leader)
+{
+	User *g;
+	int i, j, k;
+
+	if(uid == gid)
+		return 1;
+	rlock(&fs->udatal);
+	g = lookupuid(fs, gid);
+	if(g == nil)
+		goto nope;
+	if(g->lead == uid)
+		goto yes;
+	if(leader && g->lead != NOUID)
+		goto nope;
+	if(g->nmemb == 0)
+		goto nope;
+	i = 0;
+	j = g->nmemb;
+	while(i < j){
+		k = (i + j) / 2;
+		if(g->memb[k] < uid)
+			i = k + 1;
+		else
+			j = k;
+	}
+	if(g->memb[i] == uid)
+		goto yes;
+nope:
+	runlock(&fs->udatal);
+	return 0;
+yes:
+	runlock(&fs->udatal);
+	return 1;
+}
+
+int
+permcheck(Fs *fs, Dentry *d, short uid, int mode)
+{
+	int perm;
+
+	if((fs->flags & FSNOPERM) != 0)
+		return 1;
+	perm = d->mode & 0777;
+	if(d->uid == uid)
+		perm >>= 6;
+	else if(ingroup(fs, uid, d->gid, 0))
+		perm >>= 3;
+	switch(mode & 3){
+	case OREAD:
+		return (perm & 4) != 0;
+	case OWRITE:
+		return (perm & 2) != 0;
+	case OEXEC:
+		return (perm & 1) != 0;
+	case ORDWR:
+		return (perm & 5) == 5;
+	}
+	return 0;
+}
+
+char *
+uid2name(Fs *fs, short uid)
+{
+	User *u;
+	char *s;
+	
+	rlock(&fs->udatal);
+	u = lookupuid(fs, uid);
+	if(u == nil)
+		s = smprint("%d", uid);
+	else
+		s = strdup(u->name);
+	runlock(&fs->udatal);
+	return s;
+}
+
+int
+name2uid(Fs *fs, char *name, short *uid)
+{
+	char *r;
+	User *u, *v;
+
+	*uid = strtol(name, &r, 10);
+	if(*r == 0)
+		return 1;
+	rlock(&fs->udatal);
+	u = fs->udata;
+	v = u + fs->nudata;
+	if(u == nil){
+		u = udef;
+		v = udef + nelem(udef);
+	}
+	for(; u < v; u++)
+		if(strcmp(u->name, name) == 0){
+			*uid = u->uid;
+			runlock(&fs->udatal);
+			return 1;
+		}
+	runlock(&fs->udatal);
+	werrstr(Einval);
+	return -1;
+}
+
+static void
+createuserdir(Fs *fs, char *name, short uid)
+{
+	Chan *ch;
+	Buf *b, *c;
+	Dentry *d;
+	FLoc f;
+
+	ch = chanattach(fs, 0);
+	if(ch == nil)
+		return;
+	ch->uid = -1;
+	if(chanwalk(ch, "usr") <= 0 || (ch->loc->type & QTDIR) == 0){
+	direrr:
+		chanclunk(ch);
+		return;
+	}
+	chbegin(ch);
+	if(willmodify(ch->fs, ch->loc, 0) < 0){
+	direrr1:
+		chend(ch);
+		goto direrr;
+	}
+	b = getbuf(ch->fs->d, ch->loc->blk, TDENTRY, 0);
+	if(b == nil)
+		goto direrr1;
+	if(newentry(ch->fs, ch->loc, b, name, &f) <= 0){
+	direrr2:
+		putbuf(b);
+		goto direrr1;
+	}
+	modified(ch, &b->de[ch->loc->deind]);
+	c = getbuf(ch->fs->d, f.blk, TDENTRY, 0);
+	if(c == nil)
+		goto direrr2;
+	d = &c->de[f.deind];
+	memset(d, 0, sizeof(Dentry));
+	strcpy(d->name, name);
+	d->uid = uid;
+	d->muid = uid;
+	d->gid = uid;
+	d->mode = DALLOC | 0775;
+	if(newqid(fs, &d->path) < 0){
+	direrr3:
+		putbuf(c);
+		goto direrr2;
+	}
+	d->type = QTDIR;
+	d->atime = time(0);
+	d->mtime = d->atime;
+	c->op |= BDELWRI;
+	goto direrr3;
+}
+
+int
+cmdnewuser(int argc, char **argv)
+{
+	short uid, gid;
+	User *u, *v;
+	Fs *fs;
+	int resort, createdir, i, j;
+	extern Fs *fsmain;
+
+	if(argc < 2)
+		return -9001;
+	if(!validuser(argv[1])){
+		werrstr(Einval);
+		return -1;
+	}
+	fs = fsmain;
+	resort = 0;
+	createdir = 0;
+	wlock(&fs->udatal);
+	if(fs->udata == nil){
+		wunlock(&fs->udatal);
+		werrstr("newuser: no user database");
+		return -1;
+	}
+	uid = 0;
+	gid = 10000;
+	for(u = fs->udata; u < fs->udata + fs->nudata; u++){
+		if(strcmp(u->name, argv[1]) == 0)
+			goto found;
+		if(u->uid == uid)
+			uid++;
+		if(u->uid == gid)
+			gid++;
+	}
+	resort = 1;
+	fs->udata = realloc(fs->udata, sizeof(User) * (fs->nudata + 1));
+	u = fs->udata + fs->nudata++;
+	strcpy(u->name, argv[1]);
+	u->nmemb = 0;
+	u->memb = nil;
+	u->uid = gid;
+	u->lead = NOUID;
+	if(argc == 2 || strcmp(argv[2], ":") != 0){
+		u->lead = u->uid = uid;
+		createdir = 1;
+	}
+found:
+	for(i = 2; i < argc; i++){
+		if(strcmp(argv[i], ":") == 0)
+			continue;
+		if(*argv[i] != '+' && *argv[i] != '-' && *argv[i] != '='){
+			if(!validuser(argv[i]))
+				goto erropt;
+			strcpy(u->name, argv[i]);
+			continue;
+		}
+		for(v = fs->udata; v < fs->udata + fs->nudata; v++)
+			if(strcmp(v->name, argv[i] + 1) == 0)
+				break;
+		if(v == fs->udata + fs->nudata)
+			goto erropt;
+		if(*argv[i] == '='){
+			u->lead = v->uid;
+			continue;
+		}
+		for(j = 0; j < u->nmemb && u->memb[j] < v->uid; j++)
+			;
+		if(*argv[i] == '-'){
+			if(u->memb[j] != v->uid)
+				goto erropt;
+			memmove(&u->memb[j], &u->memb[j + 1], sizeof(short) * (u->nmemb - j - 1));
+			u->memb = realloc(u->memb, sizeof(short) * --u->nmemb);
+		}else{
+			u->memb = realloc(u->memb, sizeof(short) * ++u->nmemb);
+			memmove(&u->memb[j + 1], &u->memb[j], sizeof(short) * (u->nmemb - j - 1));
+			u->memb[j] = v->uid;
+		}
+		continue;
+	erropt:
+		dprint("hjfs: newuser: ignoring erroneous option %s\n", argv[i]);
+	}
+	if(resort)
+		qsort(fs->udata, fs->nudata, sizeof(User), usercomp);
+	wunlock(&fs->udatal);
+	writeusers(fs);
+	if(createdir)
+		createuserdir(fs, argv[2], uid);
+	return 1;
+}
--- /dev/null
+++ b/sys/src/cmd/hjfs/buf.c
@@ -1,0 +1,314 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "dat.h"
+#include "fns.h"
+
+Channel *getb, *putb, *syncb;
+Buf bfree;
+BufReq *freereq, *freereqlast;
+
+static void
+markbusy(Buf *b)
+{
+	b->busy = 1;
+	if(b->fnext != nil){
+		b->fnext->fprev = b->fprev;
+		b->fprev->fnext = b->fnext;
+		b->fnext = b->fprev = nil;
+	}
+}
+
+static void
+markfree(Buf *b)
+{
+	b->busy = 0;
+	b->fnext = &bfree;
+	b->fprev = bfree.fprev;
+	b->fnext->fprev = b;
+	b->fprev->fnext = b;
+}
+
+static void
+changedev(Buf *b, Dev *d, uvlong off)
+{
+	if(b->dnext != nil){
+		b->dnext->dprev = b->dprev;
+		b->dprev->dnext = b->dnext;
+		b->dprev = nil;
+		b->dnext = nil;
+	}
+	b->off = off;
+	b->d = d;
+	if(d != nil){
+		b->dnext = &d->buf[b->off & BUFHASH];
+		b->dprev = b->dnext->dprev;
+		b->dnext->dprev = b;
+		b->dprev->dnext = b;
+	}
+}
+
+static void
+delayreq(BufReq req, BufReq **first, BufReq **last)
+{
+	BufReq *r;
+
+	r = emalloc(sizeof(*r));
+	memcpy(r, &req, sizeof(*r));
+	r->next = nil;
+	if(*first == nil)
+		*first = *last = r;
+	else{
+		(*last)->next = r;
+		*last = r;
+	}
+}
+
+static void
+work(Dev *d, Buf *b)
+{
+	qlock(&d->workl);
+	b->wnext = &d->work;
+	b->wprev = b->wnext->wprev;
+	b->wnext->wprev = b;
+	b->wprev->wnext = b;
+	rwakeup(&d->workr);
+	qunlock(&d->workl);
+}
+
+static void
+givebuf(BufReq req, Buf *b)
+{
+	Buf *c, *l;
+
+	markbusy(b);
+	if(req.d == b->d && req.off == b->off){
+		send(req.resp, &b);
+		return;
+	}
+	if(b->op & BDELWRI){
+		b->op &= ~BDELWRI;
+		b->op |= BWRITE;
+		delayreq(req, &b->next, &b->last);
+		b->resp = putb;
+		work(b->d, b);
+		return;
+	}
+	l = &req.d->buf[req.off & BUFHASH];
+	for(c = l->dnext; c != l; c = c->dnext)
+		if(c->off == req.off)
+			abort();
+	changedev(b, req.d, req.off);
+	b->op &= ~(BWRITE|BDELWRI|BWRIM);
+	if(req.nodata)
+		send(req.resp, &b);
+	else{
+		b->resp = req.resp;
+		work(b->d, b);
+	}
+}
+
+static void
+undelayreq(Buf *b, BufReq **first, BufReq **last)
+{
+	BufReq *r;
+	
+	r = *first;
+	*first = r->next;
+	if(*last == r)
+		*last = nil;
+	givebuf(*r, b);
+	free(r);
+}
+
+static void
+handleget(BufReq req)
+{
+	Buf *b, *l;
+	Dev *d;
+	
+	d = req.d;
+	l = &d->buf[req.off & BUFHASH];
+	for(b = l->dnext; b != l; b = b->dnext)
+		if(b->off == req.off){
+			if(b->busy){
+				delayreq(req, &b->next, &b->last);
+				return;
+			}
+			givebuf(req, b);
+			return;
+		}
+	if(bfree.fnext == &bfree){
+		delayreq(req, &freereq, &freereqlast);
+		return;
+	}
+	b = bfree.fnext;
+	givebuf(req, b);
+}
+
+static void
+handleput(Buf *b)
+{
+	if(b->op & BWRIM){
+		b->op &= ~(BWRIM | BDELWRI);
+		b->op |= BWRITE;
+		b->resp = putb;
+		work(b->d, b);
+		return;
+	}
+	if(b->error != nil){
+		b->error = nil;
+		b->op &= ~BDELWRI;
+		changedev(b, nil, -1);
+	}
+	b->op &= ~BWRITE;
+	markfree(b);
+	if(b->next != nil)
+		undelayreq(b, &b->next, &b->last);
+	else if(freereq != nil)
+		undelayreq(b, &freereq, &freereqlast);
+}
+
+static void
+handlesync(Channel *resp)
+{
+	Buf *b, *c;
+
+	for(b = bfree.fnext; b != &bfree; b = c){
+		c = b->fnext;
+		if(b->d != nil && b->op & BDELWRI){
+			markbusy(b);
+			b->resp = putb;
+			b->op &= ~BDELWRI;
+			b->op |= BWRITE;
+			work(b->d, b);
+		}
+	}
+	if(resp != nil)
+		sendp(resp, nil);
+}
+
+static void
+bufproc(void *)
+{
+	BufReq req;
+	Buf *buf;
+	Channel *r;
+	Alt a[] = {{getb, &req, CHANRCV}, {putb, &buf, CHANRCV}, {syncb, &r, CHANRCV}, {nil, nil, CHANEND}};
+
+	workerinit();
+	for(;;)
+		switch(alt(a)){
+		case 0:
+			handleget(req);
+			break;
+		case 1:
+			handleput(buf);
+			break;
+		case 2:
+			handlesync(r);
+			break;
+		case -1:
+			sysfatal("alt: %r");
+		}
+}
+
+static char *
+typenames[] = {
+	[TRAW] "raw",
+	[TSUPERBLOCK] "superblock",
+	[TDENTRY] "dentry",
+	[TINDIR] "indir",
+	[TREF] "ref",
+	nil
+};
+
+static int
+Tfmt(Fmt *f)
+{
+	int t;
+	
+	t = va_arg(f->args, uint);
+	if(t >= nelem(typenames) || typenames[t] == nil)
+		return fmtprint(f, "??? (%d)", t);
+	return fmtstrcpy(f, typenames[t]);
+}
+
+void
+bufinit(int nbuf)
+{
+	Buf *b;
+
+	fmtinstall('T', Tfmt);
+	b = emalloc(sizeof(*b) * nbuf);
+	bfree.fnext = bfree.fprev = &bfree;
+	while(nbuf--)
+		markfree(b++);
+	getb = chancreate(sizeof(BufReq), 0);
+	putb = chancreate(sizeof(Buf *), 32);
+	syncb = chancreate(sizeof(ulong), 0);
+	proccreate(bufproc, nil, mainstacksize);
+}
+
+Buf *
+getbuf(Dev *d, uvlong off, int type, int nodata)
+{
+	ThrData *th;
+	BufReq req;
+	Buf *b;
+
+	if(off >= d->size)
+		abort();
+	th = getthrdata();
+	req.d = d;
+	req.off = off;
+	req.resp = th->resp;
+	req.nodata = nodata;
+	send(getb, &req);
+	recv(th->resp, &b);
+	if(nodata)
+		b->type = type;
+	if(b->type != type && type != -1){
+		dprint("hjfs: type mismatch, dev %s, block %lld, got %T, want %T, caller %#p\n", d->name, off, b->type, type, getcallerpc(&d));
+		werrstr("phase error -- type mismatch");
+		putbuf(b);
+		return nil;
+	}
+	if(b->error != nil){
+		werrstr("%s", b->error);
+		putbuf(b);
+		return nil;
+	}
+	b->callerpc = getcallerpc(&d);
+	return b;
+}
+
+void
+putbuf(Buf *b)
+{
+	send(putb, &b);
+}
+
+void
+sync(int wait)
+{
+	Channel *r;
+	Dev *d;
+	Buf b;
+
+	r = nil;
+	if(wait)
+		r = getthrdata()->resp;
+	sendp(syncb, r);
+	memset(&b, 0, sizeof(Buf));
+	if(wait){
+		recvp(r);
+		for(d = devs; d != nil; d = d->next){
+			b.d = nil;
+			b.resp = r;
+			b.busy = 1;
+			work(d, &b);
+			recvp(r);
+		}
+	}
+}
--- /dev/null
+++ b/sys/src/cmd/hjfs/cons.c
@@ -1,0 +1,238 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <bio.h>
+#include "dat.h"
+#include "fns.h"
+
+enum { MAXARGS = 16 };
+
+typedef struct Cmd Cmd;
+static int echo;
+extern Fs *fsmain;
+
+struct Cmd {
+	char *name;
+	int args;
+	int (*f)(int, char **);
+};
+
+static int
+walkpath(Chan *ch, char *path, char **cr)
+{
+	char buf[NAMELEN], *p, *fp;
+	
+	buf[NAMELEN - 1] = 0;
+	fp = path;
+	if(*path != '/'){
+	noent:
+		werrstr("%s: %s", fp, Enoent);
+		return -1;
+	}
+	path++;
+	for(;;){
+		p = strchr(path, '/');
+		if(p == nil){
+			if(cr != nil){
+				if(*path == 0){
+					werrstr("%s: trailing slash", fp);
+					return -1;
+				}
+				*cr = path;
+				break;
+			}
+			p = path + strlen(path);
+		}
+		if(*path == '/'){
+			path++;
+			continue;
+		}
+		if(*path == 0)
+			break;
+		if(p - path >= NAMELEN)
+			goto noent;
+		memcpy(buf, path, p - path);
+		if(chanwalk(ch, buf) <= 0){
+			werrstr("%s: %r", fp);
+			return -1;
+		}
+		if(*p == 0)
+			break;
+		path = p + 1;
+	}
+	return 1;
+}
+
+int
+cmdhalt(int, char **)
+{
+	shutdown();
+	return 0;
+}
+
+int
+cmddump(int, char **)
+{
+	fsdump(fsmain);
+	dprint("hjfs: dumped\n");
+	return 0;
+}
+
+int
+cmdallow(int, char **)
+{
+	fsmain->flags |= FSNOPERM | FSCHOWN;
+	dprint("hjfs: allow\n");
+	return 0;
+}
+
+int
+cmdchatty(int, char **)
+{
+	extern int chatty9p;
+
+	chatty9p = !chatty9p;
+	return 0;
+}
+
+int
+cmddisallow(int, char **)
+{
+	fsmain->flags &= ~(FSNOPERM | FSCHOWN);
+	dprint("hjfs: disallow\n");
+	return 0;
+}
+
+int
+cmdnoauth(int, char **)
+{
+	fsmain->flags ^= FSNOAUTH;
+	if((fsmain->flags & FSNOAUTH) != 0)
+		dprint("hjfs: auth enabled\n");
+	else
+		dprint("hjfs: auth disabled\n");
+	return 1;
+}
+
+int
+cmdcreate(int argc, char **argv)
+{
+	Chan *ch;
+	char *n;
+	short uid, gid;
+	ulong perm;
+	Dir di;
+
+	if(argc != 5 && argc != 6)
+		return -9001;
+	perm = strtol(argv[4], &n, 8) & 0777;
+	if(*n != 0)
+		return -9001;
+	if(argc == 6)
+		for(n = argv[5]; *n != 0; n++)
+			switch(*n){
+			case 'l': perm |= DMEXCL; break;
+			case 'd': perm |= DMDIR; break;
+			case 'a': perm |= DMAPPEND; break;
+			default: return -9001;
+			}
+	if(name2uid(fsmain, argv[2], &uid) < 0)
+		return -1;
+	if(name2uid(fsmain, argv[3], &gid) < 0)
+		return -1;
+	ch = chanattach(fsmain, 0);
+	if(ch == nil)
+		return -1;
+	ch->uid = uid;
+	if(walkpath(ch, argv[1], &n) < 0){
+		chanclunk(ch);
+		return -1;
+	}
+	if(chancreat(ch, n, perm, OREAD) < 0){
+		chanclunk(ch);
+		return -1;
+	}
+	nulldir(&di);
+	di.gid = argv[3];
+	chanwstat(ch, &di);
+	chanclunk(ch);
+	return 1;
+}
+
+int
+cmdecho(int, char **argv)
+{
+	echo = strcmp(argv[1], "on") == 0;
+	return 1;
+}
+
+extern int cmdnewuser(int, char **);
+
+Cmd cmds[] = {
+	{"allow", 1, cmdallow},
+	{"noauth", 1, cmdnoauth},
+	{"chatty", 1, cmdchatty},
+	{"create", 0, cmdcreate},
+	{"disallow", 1, cmddisallow},
+	{"dump", 1, cmddump},
+	{"halt", 1, cmdhalt},
+	{"newuser", 0, cmdnewuser},
+	{"echo", 2, cmdecho},
+};
+
+
+static void
+consproc(void *v)
+{
+	Biobuf *in;
+	Cmd *c;
+	char *s;
+	char *args[MAXARGS];
+	int rc;
+	
+	in = (Biobuf *) v;
+	for(;;){
+		s = Brdstr(in, '\n', 1);
+		if(s == nil)
+			continue;
+		if(echo)
+			dprint("hjfs: >%s\n", s);
+		rc = tokenize(s, args, MAXARGS);
+		if(rc == 0)
+			goto syntax;
+		for(c = cmds; c < cmds + nelem(cmds); c++)
+			if(strcmp(c->name, args[0]) == 0){
+				if(c->args != 0 && c->args != rc)
+					goto syntax;
+				if(c->f != nil){
+					rc = c->f(rc, args);
+					if(rc == -9001)
+						goto syntax;
+					if(rc < 0)
+						dprint("hjfs: %r\n");
+					goto done;
+				}
+			}
+	syntax:
+		dprint("hjfs: syntax error\n");
+	done:
+		free(s);
+	}
+}
+
+void
+initcons(char *service)
+{
+	int fd, pfd[2];
+	static Biobuf bio;
+	char buf[512];
+
+	snprint(buf, sizeof(buf), "/srv/%s.cmd", service);
+	fd = create(buf, OWRITE|ORCLOSE, 0600);
+	if(fd < 0)
+		return;
+	pipe(pfd);
+	fprint(fd, "%d", pfd[1]);
+	Binit(&bio, pfd[0], OREAD);
+	proccreate(consproc, &bio, mainstacksize);
+}
--- /dev/null
+++ b/sys/src/cmd/hjfs/conv.c
@@ -1,0 +1,137 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "dat.h"
+#include "fns.h"
+
+#define GET8(x) {x = *p++;}
+#define GET16(x) {x = *p++; x |= *p++ << 8;}
+#define GET24(x) {x = *p++; x |= *p++ << 8; x |= *p++ << 16;}
+#define GET32(x) {x = *p++; x |= *p++ << 8; x |= *p++ << 16; x |= *p++ << 24;}
+#define GET64(x) \
+		{x = (uvlong) *p++; \
+		x |= (uvlong) *p++ << 8; \
+		x |= (uvlong) *p++ << 16; \
+		x |= (uvlong) *p++ << 24; \
+		x |= (uvlong) *p++ << 32; \
+		x |= (uvlong) *p++ << 40; \
+		x |= (uvlong) *p++ << 48; \
+		x |= (uvlong) *p++ << 56;}
+#define GETS(x, n) {memcpy(x, p, n); p += n;}
+
+#define PUT8(x) {*p++ = x;}
+#define PUT16(x) {*p++ = x; *p++ = x >> 8;}
+#define PUT24(x) {*p++ = x; *p++ = x >> 8; *p++ = x >> 16;}
+#define PUT32(x) {*p++ = x; *p++ = x >> 8; *p++ = x >> 16; *p++ = x >> 24;}
+#define PUT64(x) \
+		{*p++ = x; \
+		*p++ = x >> 8; \
+		*p++ = x >> 16; \
+		*p++ = x >> 24; \
+		*p++ = x >> 32; \
+		*p++ = x >> 40; \
+		*p++ = x >> 48; \
+		*p++ = x >> 56;}
+#define PUTS(x, n) {memcpy(p, x, n); p += n;}
+
+void
+unpack(Buf *b, uchar *p)
+{
+	Dentry *d;
+	int i;
+
+	switch(b->type = *p++){
+	default:
+		memcpy(b->data, p, RBLOCK);
+		break;
+	case TSUPERBLOCK:
+		GET32(b->sb.magic);
+		GET64(b->sb.size);
+		GET64(b->sb.fstart);
+		GET64(b->sb.fend);
+		GET64(b->sb.root);
+		GET64(b->sb.qidpath);
+		break;
+	case TDENTRY:
+		for(d = b->de; d < b->de + nelem(b->de); d++){
+			GETS(d->name, NAMELEN);
+			GET16(d->uid);
+			GET16(d->muid);
+			GET16(d->gid);
+			GET16(d->mode);
+			GET64(d->path);
+			GET32(d->vers);
+			GET8(d->type);
+			GET64(d->size);
+			for(i = 0; i < NDIRECT; i++)
+				GET64(d->db[i]);
+			for(i = 0; i < NINDIRECT; i++)
+				GET64(d->ib[i]);
+			GET64(d->atime);
+			GET64(d->mtime);
+		}
+		break;
+	case TINDIR:
+		for(i = 0; i < OFFPERBLK; i++)
+			GET64(b->offs[i]);
+		break;
+	case TREF:
+		for(i = 0; i < REFPERBLK; i++)
+			GET24(b->refs[i]);
+		break;
+	}
+	USED(p);
+}
+
+void
+pack(Buf *b, uchar *p)
+{
+	Dentry *d;
+	int i;
+
+	switch(*p++ = b->type){
+	case TRAW:
+		memcpy(p, b->data, RBLOCK);
+		break;
+
+	case TSUPERBLOCK:
+		PUT32(b->sb.magic);
+		PUT64(b->sb.size);
+		PUT64(b->sb.fstart);
+		PUT64(b->sb.fend);
+		PUT64(b->sb.root);
+		PUT64(b->sb.qidpath);
+		break;
+	case TDENTRY:
+		for(d = b->de; d < b->de + nelem(b->de); d++){
+			PUTS(d->name, NAMELEN);
+			PUT16(d->uid);
+			PUT16(d->muid);
+			PUT16(d->gid);
+			PUT16(d->mode);
+			PUT64(d->path);
+			PUT32(d->vers);
+			PUT8(d->type);
+			PUT64(d->size);
+			for(i = 0; i < NDIRECT; i++)
+				PUT64(d->db[i]);
+			for(i = 0; i < NINDIRECT; i++)
+				PUT64(d->ib[i]);
+			PUT64(d->atime);
+			PUT64(d->mtime);
+		}
+		break;
+	case TINDIR:
+		for(i = 0; i < OFFPERBLK; i++)
+			PUT64(b->offs[i]);
+		break;
+	case TREF:
+		for(i = 0; i < REFPERBLK; i++)
+			PUT24(b->refs[i]);
+		break;
+	default:
+		abort();
+	}
+	USED(p);
+}
+
--- /dev/null
+++ b/sys/src/cmd/hjfs/dat.h
@@ -1,0 +1,228 @@
+enum {
+	/* affects on-disk structure */
+	BLOCK = 4096,
+	RBLOCK = BLOCK - 1,
+	SUPERMAGIC = 0x6E0DE51C,
+	SUPERBLK = 0,
+	
+	NAMELEN = 256,
+	NDIRECT = 15,
+	NINDIRECT = 4,
+	
+	ROOTQID = 1,
+	DUMPROOTQID = 2,
+	
+	/* affects just run-time behaviour */
+	SYNCINTERVAL = 10000,
+	FREELISTLEN = 256,
+	BUFHASHBITS = 8,
+	BUFHASH = (1<<BUFHASHBITS)-1,
+	NWORKERS = 5,
+	EXCLDUR = 300,
+
+	NOUID = (short)0x8000,
+};
+
+typedef struct Fs Fs;
+typedef struct Buf Buf;
+typedef struct Dev Dev;
+typedef struct BufReq BufReq;
+typedef struct ThrData ThrData;
+typedef struct Superblock Superblock;
+typedef struct Dentry Dentry;
+typedef struct Chan Chan;
+typedef struct FLoc FLoc;
+typedef struct Loc Loc;
+typedef struct User User;
+
+#pragma incomplete struct User
+#pragma varargck type "T" int
+#pragma varargck type "T" uint
+enum {
+	TRAW,
+	TSUPERBLOCK,
+	TDENTRY,
+	TINDIR,
+	TREF,
+	TDONTCARE = -1,
+};
+
+struct Superblock {
+	ulong magic;
+	uvlong size;
+	uvlong fstart;
+	uvlong fend;
+	uvlong root;
+	uvlong qidpath;
+};
+
+enum {
+	DALLOC = 1<<15,
+	DGONE = 1<<14,
+};
+
+struct Dentry {
+	char name[NAMELEN];
+	short uid;
+	short muid;
+	short gid;
+	ushort mode;
+	Qid;
+	uvlong size; /* bytes for files and blocks for dirs */
+	uvlong db[NDIRECT];
+	uvlong ib[NINDIRECT];
+	vlong atime;
+	vlong mtime;
+};
+
+enum {
+	DENTRYSIZ = NAMELEN + 4 * sizeof(ushort) + 13 + (3 + NDIRECT + NINDIRECT) * sizeof(uvlong),
+	DEPERBLK = RBLOCK / DENTRYSIZ,
+	OFFPERBLK = RBLOCK / 12,
+	REFPERBLK = RBLOCK / 3,
+};
+
+struct BufReq {
+	Dev *d;
+	uvlong off;
+	int nodata;
+	Channel *resp;
+	BufReq *next;
+};
+
+enum {
+	BWRITE = 1, /* used only for the worker */
+	BWRIM = 2, /* write immediately after putbuf */
+	BDELWRI = 4, /* write delayed */
+};
+
+struct Buf {
+	uchar op, type;
+	union {
+		uchar data[RBLOCK];
+		Superblock sb;
+		Dentry de[DEPERBLK];
+		uvlong offs[OFFPERBLK];
+		ulong refs[REFPERBLK];
+	};
+
+	/* do not use anything below (for the bufproc only) */
+	uchar busy;
+	char *error;
+	Buf *dnext, *dprev;
+	Buf *fnext, *fprev;
+	BufReq;
+	BufReq *last;
+	ulong callerpc; /* debugging */
+	
+	Buf *wnext, *wprev;
+};
+
+struct ThrData {
+	Channel *resp;
+};
+
+struct Dev {
+	char *name;
+	int size;
+	Buf buf[BUFHASH+1]; /* doubly-linked list */
+	Dev *next;
+	int fd;
+	Rendez workr;
+	QLock workl;
+	Buf work;
+};
+
+extern Dev *devs;
+
+struct FLoc {
+	uvlong blk;
+	int deind;
+	Qid;
+};
+
+enum {
+	LGONE = 1,
+};
+
+struct Loc {
+	FLoc;
+	int ref, flags;
+	Loc *next, *child;
+	Loc *cnext, *cprev;
+	Loc *gnext, *gprev;
+	
+	QLock ex;
+	Chan *exlock;
+	ulong lwrite;
+};
+
+enum {
+	FSNOAUTH = 1,
+	FSNOPERM = 2,
+	FSCHOWN = 4,
+};
+
+struct Fs {
+	RWLock;
+	Dev *d;
+	int flags;
+	uvlong root, fstart;
+	
+	Channel *freelist;
+	Loc *rootloc, *dumprootloc;
+	QLock loctree;
+
+	User *udata;
+	int nudata;
+	RWLock udatal;
+};
+
+enum {
+	CHREAD = 1,
+	CHWRITE = 2,
+	CHRCLOSE = 4,
+	CHFDUMP = 1,
+	CHFNOLOCK = 2,
+	CHFRO = 4,
+};
+
+
+struct Chan {
+	Fs *fs;
+	Loc *loc;
+	uchar open;
+	uchar flags;
+	ushort uid;
+
+	/* dir walk */
+	uvlong dwloff;
+	uvlong dwblk;
+	int dwind;
+	
+	/* workers */
+	void *freq, *lreq;
+	Chan *qnext, *qprev;
+};
+
+extern QLock chanqu;
+extern Rendez chanre;
+extern Chan readych;
+
+extern char Eio[];
+extern char Enotadir[];
+extern char Enoent[];
+extern char Einval[];
+extern char Eperm[];
+extern char Eexists[];
+extern char Elocked[];
+
+enum { /* getblk modes */
+	GBREAD = 0,
+	GBWRITE = 1,
+	GBCREATE = 2,
+	GBOVERWR = 3,
+};
+
+#define HOWMANY(a, b) (((a)+((b)-1))/(b))
+#define ROUNDUP(a, b) (HOWMANY(a,b)*(b))
--- /dev/null
+++ b/sys/src/cmd/hjfs/dev.c
@@ -1,0 +1,93 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "dat.h"
+#include "fns.h"
+
+Dev *devs;
+
+void
+devwork(void *v)
+{
+	Dev *d;
+	Buf *b;
+	Channel *r;
+	uchar buf[BLOCK];
+	
+	d = v;
+	for(;;){
+		qlock(&d->workl);
+		while(d->work.wnext == &d->work)
+			rsleep(&d->workr);
+		b = d->work.wnext;
+		b->wnext->wprev = b->wprev;
+		b->wprev->wnext = b->wnext;
+		b->wnext = b->wprev = nil;
+		qunlock(&d->workl);
+		if(b->d == nil) /* this is a sync request */
+			goto reply;
+		if(b->off >= d->size){
+			b->error = Einval;
+			goto reply;
+		}
+		seek(d->fd, b->off * BLOCK, 0);
+		b->error = nil;
+		if(b->op & BWRITE){
+			memset(buf, 0, sizeof(buf));
+			pack(b, buf);
+			if(write(d->fd, buf, BLOCK) < BLOCK){
+				dprint("hjfs: write: %r\n");
+				b->error = Eio;
+			}
+		}else{
+			if(readn(d->fd, buf, BLOCK) < 0){
+				dprint("hjfs: read: %r\n");
+				b->error = Eio;
+			}else
+				unpack(b, buf);
+		}
+	reply:
+		r = b->resp;
+		b->resp = nil;
+		if(r != nil)
+			send(r, &b);
+	}
+}
+
+Dev *
+newdev(char *file)
+{
+	Dev *d, **e;
+	Dir *dir;
+	Buf *b;
+	
+	d = emalloc(sizeof(*d));
+	d->fd = open(file, ORDWR);
+	if(d->fd < 0){
+		free(d);
+		return nil;
+	}
+	dir = dirfstat(d->fd);
+	if(dir == nil){
+	error:
+		close(d->fd);
+		free(d);
+		return nil;
+	}
+	d->size = dir->length / BLOCK;
+	free(dir);
+	if(d->size == 0){
+		werrstr("device file too short");
+		goto error;
+	}
+	d->name = strdup(file);
+	for(b = d->buf; b < d->buf + BUFHASH + 1; b++)
+		b->dnext = b->dprev = b;
+	d->workr.l = &d->workl;
+	d->work.wnext = d->work.wprev = &d->work;
+	proccreate(devwork, d, mainstacksize);
+	for(e = &devs; *e != nil; e = &(*e)->next)
+		;
+	*e = d;
+	return d;
+}
--- /dev/null
+++ b/sys/src/cmd/hjfs/dump.c
@@ -1,0 +1,169 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "dat.h"
+#include "fns.h"
+
+int
+copydentry(Fs *fs, FLoc *a, Loc *b, char *nname)
+{
+	Buf *ba, *bb, *bc;
+	Dentry *d;
+	int i, rc;
+	FLoc c;
+
+	if(!namevalid(nname)){
+		werrstr(Einval);
+		return -1;
+	}
+	ba = getbuf(fs->d, a->blk, TDENTRY, 0);
+	if(ba == nil)
+		return -1;
+	bb = getbuf(fs->d, b->blk, TDENTRY, 0);
+	if(bb == nil){
+		putbuf(ba);
+		return -1;
+	}
+	rc = newentry(fs, b, bb, nname, &c);
+	if(rc < 0){
+	err1:
+		putbuf(bb);
+		putbuf(ba);
+		return -1;
+	}
+	bc = getbuf(fs->d, c.blk, TDENTRY, 0);
+	if(bc == nil)
+		goto err1;
+	d = &bc->de[c.deind];
+	memcpy(d, &ba->de[a->deind], sizeof(Dentry));
+	strcpy(d->name, nname);
+	for(i = 0; i < NDIRECT; i++)
+		if(d->db[i] != 0)
+			chref(fs, d->db[i], 1);
+	for(i = 0; i < NINDIRECT; i++)
+		if(d->ib[i] != 0)
+			chref(fs, d->ib[i], 1);
+	bc->op |= BDELWRI;
+	putbuf(bc);
+	putbuf(bb);
+	putbuf(ba);
+	return 0;
+}
+
+int
+fsdump(Fs *fs)
+{
+	char buf[20], *p, *e;
+	int n, rc;
+	Tm *tm;
+	Chan *ch, *chh;
+	Buf *b;
+
+	wlock(fs);
+	tm = localtime(time(0));
+	snprint(buf, sizeof(buf), "%.4d", tm->year + 1900);
+	ch = chanattach(fs, CHFNOLOCK|CHFDUMP);
+	ch->uid = -1;
+	if(ch == nil){
+		wunlock(fs);
+		return -1;
+	}
+	if(chanwalk(ch, buf) < 0){
+		chh = chanclone(ch);
+		rc = chancreat(chh, buf, DMDIR|0555, OREAD);
+		chanclunk(chh);
+		if(rc < 0)
+			goto err;
+		if(chanwalk(ch, buf) < 0)
+			goto err;
+	}
+	b = getbuf(fs->d, ch->loc->blk, TDENTRY, 0);
+	if(b == nil)
+		goto err;
+	for(n = 0; ; n++){
+		e = buf + sizeof(buf);
+		p = seprint(buf, e, "%.2d%.2d", tm->mon + 1, tm->mday);
+		if(n > 0)
+			seprint(p, e, "%d", n);
+		rc = findentry(fs, ch->loc, b, buf, nil, 1);
+		if(rc < 0)
+			goto err;
+		if(rc == 0)
+			break;
+	}
+	putbuf(b);
+	rc = copydentry(fs, fs->rootloc, ch->loc, buf);
+	chanclunk(ch);
+	wunlock(fs);
+	return rc;
+err:
+	chanclunk(ch);
+	wunlock(fs);
+	return -1;
+}
+
+int
+willmodify(Fs *fs, Loc *l, int nolock)
+{
+	Buf *p;
+	uvlong i, r;
+	Dentry *d;
+	int rc;
+
+	if(!nolock){
+again:
+		runlock(fs);
+		wlock(fs);
+	}
+	rc = chref(fs, l->blk, 0);
+	if(rc < 0)
+		goto err;
+	if(rc == 0){
+		dprint("hjfs: willmodify: block %lld has refcount 0\n", l->blk);
+		werrstr("phase error -- willmodify");
+		goto err;
+	}
+	if(rc == 1)
+		goto done;
+	if(willmodify(fs, l->next, 1) < 0)
+		goto err;
+	p = getbuf(fs->d, l->next->blk, TDENTRY, 0);
+	if(p == nil)
+		goto err;
+	d = &p->de[l->next->deind];
+	for(i = 0; i < d->size; i++){
+		rc = getblk(fs, l->next, p, i, &r, GBREAD);
+		if(rc <= 0)
+			continue;
+		if(r == l->blk)
+			goto found;
+	}
+phase:
+	werrstr("willmodify -- phase error");
+	putbuf(p);
+	goto err;
+found:
+	rc = getblk(fs, l->next, p, i, &r, GBWRITE);
+	if(rc < 0){
+		putbuf(p);
+		goto err;
+	}
+	if(rc == 0)
+		goto phase;
+	putbuf(p);
+	l->blk = r;
+done:
+	if(!nolock){
+		wunlock(fs);
+		rlock(fs);
+		if(chref(fs, l->blk, 0) != 1)
+			goto again;
+	}
+	return 0;
+err:
+	if(!nolock){
+		wunlock(fs);
+		rlock(fs);
+	}
+	return -1;
+}
--- /dev/null
+++ b/sys/src/cmd/hjfs/fns.h
@@ -1,0 +1,52 @@
+void*	emalloc(int);
+void	bufinit(int);
+Buf*	getbuf(Dev *, uvlong, int, int);
+void	putbuf(Buf *);
+void	sync(int);
+void	pack(Buf *, uchar *);
+void	unpack(Buf *, uchar *);
+Dev*	newdev(char *);
+ThrData*	getthrdata(void);
+Fs*	initfs(Dev *, int, int);
+int	getfree(Fs *, uvlong *);
+int	putfree(Fs *, uvlong);
+Chan*	chanattach(Fs *, int);
+Chan*	chanclone(Chan *);
+int	chanwalk(Chan *, char *);
+int	chancreat(Chan *, char *, int, int);
+int	chanopen(Chan *, int mode);
+int	chanwrite(Chan *, void *, ulong, uvlong);
+int	chanread(Chan *, void *, ulong, uvlong);
+int	chanstat(Chan *, Dir *);
+int	chanwstat(Chan *, Dir *);
+int	permcheck(Fs *, Dentry *, short, int);
+char *	uid2name(Fs *, short);
+int	name2uid(Fs *, char *, short *);
+void	start9p(char *, int);
+int	chanclunk(Chan *);
+int	chanremove(Chan *);
+int	getblk(Fs *, FLoc *, Buf *, uvlong, uvlong *, int);
+void	initcons(char *);
+void	shutdown(void);
+int	fsdump(Fs *);
+int	willmodify(Fs *, Loc *, int);
+void	chbegin(Chan *);
+void	chend(Chan *);
+int	newqid(Fs *, uvlong *);
+Loc *	getloc(Fs *, FLoc, Loc *);
+int	haveloc(Fs *, uvlong, int, Loc *);
+Loc *	cloneloc(Fs *, Loc *);
+void	putloc(Fs *, Loc *, int);
+int	findentry(Fs *, FLoc *, Buf *, char *, FLoc *, int);
+void	modified(Chan *, Dentry *);
+int	trunc(Fs *, FLoc *, Buf *, uvlong);
+int	dprint(char *fmt, ...);
+int	delete(Fs *, FLoc *, Buf *);
+int	chref(Fs *, uvlong, int);
+int	newentry(Fs *, Loc *, Buf *, char *, FLoc *);
+int	namevalid(char *);
+int	usersload(Fs *, Chan *);
+int	userssave(Fs *, Chan *);
+int	ingroup(Fs *, short, short, int);
+void	workerinit(void);
+void	writeusers(Fs *);
--- /dev/null
+++ b/sys/src/cmd/hjfs/fs1.c
@@ -1,0 +1,945 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <fcall.h>
+#include "dat.h"
+#include "fns.h"
+
+int
+chref(Fs *fs, uvlong r, int stat)
+{
+	uvlong i;
+	int j;
+	ulong rc;
+	Buf *c;
+
+	i = fs->fstart + r / REFPERBLK;
+	j = r % REFPERBLK;
+	c = getbuf(fs->d, i, TREF, 0);
+	if(c == nil)
+		return -1;
+	if(stat < 0 && c->refs[j] < -stat)
+		c->refs[j] = 0;
+	else
+		c->refs[j] += stat;
+	rc = c->refs[j];
+	if(stat != 0)
+		c->op |= BDELWRI;
+	putbuf(c);
+	return rc;
+}
+
+int
+getfree(Fs *fs, uvlong *r)
+{
+	Dev *d;
+	Buf *b, *c;
+	uvlong i, l;
+	int j, have;
+
+	d = fs->d;
+	b = getbuf(d, SUPERBLK, TSUPERBLOCK, 0);
+	if(b == nil)
+		return -1;
+	if(nbrecv(fs->freelist, r) > 0)
+		goto found;
+	have = 0;
+	for(i = b->sb.fstart, l = 0; i < b->sb.fend; i++){
+		c = getbuf(d, i, TREF, 0);
+		if(c == nil){
+			putbuf(b);
+			return -1;
+		}
+		for(j = 0; j < REFPERBLK; j++, l++)
+			if(c->refs[j] == 0){
+				if(!have){
+					have = 1;
+					*r = l;
+				}else if(nbsend(fs->freelist, &l) <= 0){
+					putbuf(c);
+					goto done;
+				}
+			}
+		putbuf(c);
+	}
+done:
+	if(!have){
+		werrstr("disk full");
+		return -1;
+	}
+found:
+	putbuf(b);
+	if(chref(fs, *r, 1) < 0)
+		return -1;
+	return 1;
+}
+
+int
+putfree(Fs *fs, uvlong r)
+{
+	int rc;
+
+	rc = chref(fs, r, -1);
+	if(rc < 0)
+		return -1;
+	if(rc == 0)
+		nbsend(fs->freelist, &r);
+	return 1;
+}
+
+static void
+createroot(Fs *fs)
+{
+	uvlong r;
+	Buf *c;
+	Dentry *d;
+
+	if(getfree(fs, &r) < 0)
+		sysfatal("ream: getfree: %r");
+	c = getbuf(fs->d, r, TDENTRY, 1);
+	if(c == nil)
+	error:
+		sysfatal("ream: getbuf: %r");
+	memset(c->de, 0, sizeof(c->de));
+	d = &c->de[0];
+	strcpy(d->name, "/");
+	d->mode = DALLOC | 0775;
+	d->path = ROOTQID;
+	d->type = QTDIR;
+	d->uid = -1;
+	d->muid = -1;
+	d->gid = -1;
+	d->mtime = time(0);
+	d->atime = d->mtime;
+	d++;
+	strcpy(d->name, "/");
+	d->mode = DALLOC | 0775;
+	d->path = ROOTQID;
+	d->type = QTDIR;
+	d->uid = -1;
+	d->muid = -1;
+	d->gid = -1;
+	d->mtime = time(0);
+	d->atime = d->mtime;
+	c->op |= BWRIM;
+	putbuf(c);
+	c = getbuf(fs->d, SUPERBLK, TSUPERBLOCK, 1);
+	if(c == nil)
+		goto error;
+	fs->root = r;
+	c->sb.root = r;
+	putbuf(c);
+}
+
+void
+writeusers(Fs *fs)
+{
+	Chan *ch;
+	
+	ch = chanattach(fs, 0);
+	if(ch == nil)
+		goto error;
+	ch->uid = -1;
+	chancreat(ch, "adm", DMDIR | 0755, OREAD);
+	chanclunk(ch);
+	ch = chanattach(fs, 0);
+	if(ch == nil)
+		goto error;
+	ch->uid = -1;
+	if(chanwalk(ch, "adm") <= 0)
+		goto error;
+	if(chanwalk(ch, "users") > 0){
+		if(chanopen(ch, OWRITE|OTRUNC) <= 0)
+			goto error;
+	}else if(chancreat(ch, "users", 0644, OWRITE) <= 0)
+			goto error;
+	if(userssave(fs, ch) < 0){
+		chanremove(ch);
+		ch = nil;
+		goto error;
+	}
+	chanclunk(ch);
+	return;
+error:
+	if(ch != nil)
+		chanclunk(ch);
+	dprint("writeusers: %r\n");
+}
+
+static void
+readusers(Fs *fs)
+{
+	Chan *ch;
+	
+	ch = chanattach(fs, 0);
+	if(ch == nil)
+		goto err;
+	ch->uid = -1;
+	if(chanwalk(ch, "adm") <= 0)
+		goto err;
+	if(chanwalk(ch, "users") <= 0)
+		goto err;
+	if(chanopen(ch, OREAD) < 0)
+		goto err;
+	if(usersload(fs, ch) < 0)
+		goto err;
+	chanclunk(ch);
+	return;
+err:
+	if(ch != nil)
+		chanclunk(ch);
+	dprint("hjfs: readusers: %r\nhjfs: using default user db\n");
+}
+
+void
+ream(Fs *fs)
+{
+	Dev *d;
+	Buf *b, *c;
+	uvlong i, firsti, lasti;
+	int j, je;
+	
+	d = fs->d;
+	dprint("hjfs: reaming %s\n", d->name);
+	b = getbuf(d, SUPERBLK, TSUPERBLOCK, 1);
+	if(b == nil)
+	err:
+		sysfatal("ream: getbuf: %r");
+	memset(&b->sb, 0, sizeof(b->sb));
+	b->sb.magic = SUPERMAGIC;
+	b->sb.size = d->size;
+	b->sb.fstart = SUPERBLK + 1;
+	fs->fstart = b->sb.fstart;
+	b->sb.fend = b->sb.fstart + HOWMANY(b->sb.size * 3, RBLOCK);
+	b->sb.qidpath = DUMPROOTQID + 1;
+	firsti = b->sb.fstart + SUPERBLK / REFPERBLK;
+	lasti = b->sb.fstart + b->sb.fend / REFPERBLK;
+	for(i = b->sb.fstart; i < b->sb.fend; i++){
+		c = getbuf(d, i, TREF, 1);
+		if(c == nil)
+			goto err;
+		memset(c->refs, 0, sizeof(b->data));
+		if(i >= firsti && i <= lasti){
+			j = 0;
+			je = REFPERBLK;
+			if(i == firsti)
+				j = SUPERBLK % REFPERBLK;
+			if(i == lasti)
+				je = b->sb.fend % REFPERBLK;
+			for(; j < je; j++)
+				c->refs[j] = 1;
+		}
+		c->op |= BWRIM;
+		putbuf(c);
+	}
+	b->op |= BDELWRI;
+	putbuf(b);
+	createroot(fs);
+	sync(1);
+	dprint("hjfs: ream successful\n");
+}
+
+Fs *
+initfs(Dev *d, int doream, int flags)
+{
+	Fs *fs;
+	Buf *b;
+	FLoc f;
+	
+	fs = emalloc(sizeof(*fs));
+	fs->d = d;
+	if(doream)
+		ream(fs);
+	b = getbuf(d, SUPERBLK, TSUPERBLOCK, 0);
+	if(b == nil || b->sb.magic != SUPERMAGIC)
+		goto error;
+	fs->root = b->sb.root;
+	fs->fstart = b->sb.fstart;
+	fs->freelist = chancreate(sizeof(uvlong), FREELISTLEN);
+	f.blk = fs->root;
+	f.deind = 0;
+	f.path = ROOTQID;
+	f.vers = 0;
+	f.type = QTDIR;
+	fs->rootloc = getloc(fs, f, nil);
+	f.deind++;
+	f.path = DUMPROOTQID;
+	fs->dumprootloc = getloc(fs, f, nil);
+	putbuf(b);
+	fs->flags = flags;
+	if(doream)
+		writeusers(fs);
+	readusers(fs);
+	return fs;
+
+error:
+	if(b != nil)
+		putbuf(b);
+	free(fs);
+	return nil;
+}
+
+void
+chbegin(Chan *ch)
+{
+	if((ch->flags & CHFNOLOCK) == 0)
+		rlock(ch->fs);
+}
+
+void
+chend(Chan *ch)
+{
+	if((ch->flags & CHFNOLOCK) == 0)
+		runlock(ch->fs);
+}
+
+int
+newqid(Fs *fs, uvlong *q)
+{
+	Buf *b;
+	
+	b = getbuf(fs->d, SUPERBLK, TSUPERBLOCK, 0);
+	if(b == nil)
+		return -1;
+	*q = b->sb.qidpath++;
+	b->op |= BDELWRI;
+	putbuf(b);
+	return 1;
+}
+
+Loc *
+getloc(Fs *fs, FLoc f, Loc *next)
+{
+	Loc *l;
+	
+	qlock(&fs->loctree);
+	if(next != nil && next->child != nil){
+		l = next->child;
+		do{
+			if(l->blk == f.blk && l->deind == f.deind){
+				l->ref++;
+				qunlock(&fs->loctree);
+				return l;
+			}
+			l = l->cnext;
+		}while(l != next->child);
+	}
+	l = emalloc(sizeof(*l));
+	l->ref = 1;
+	l->FLoc = f;
+	l->next = next;
+	if(fs->rootloc != nil){
+		l->gnext = fs->rootloc;
+		l->gprev = l->gnext->gprev;
+		l->gnext->gprev = l;
+		l->gprev->gnext = l;
+	}else
+		l->gnext = l->gprev = l;
+	l->cprev = l->cnext = l;
+	if(next != nil){
+		if(next->child == nil)
+			next->child = l;
+		else{
+			l->cnext = next->child;
+			l->cprev = next->child->cprev;
+			l->cnext->cprev = l;
+			l->cprev->cnext = l;
+		}
+	}
+	qunlock(&fs->loctree);
+	return l;
+}
+
+int
+haveloc(Fs *fs, uvlong blk, int deind, Loc *next)
+{
+	Loc *l;
+
+	qlock(&fs->loctree);
+	l = next->child;
+	do{
+		if(l->blk == blk && l->deind == deind){
+			qunlock(&fs->loctree);
+			return 1;
+		}
+		l = l->cnext;
+	}while(l != next->child);
+	qunlock(&fs->loctree);
+	return 0;
+}
+
+Loc *
+cloneloc(Fs *fs, Loc *l)
+{
+	Loc *m;
+
+	qlock(&fs->loctree);
+	for(m = l; m != nil; m = m->next)
+		m->ref++;
+	qunlock(&fs->loctree);
+	return l;
+}
+
+void
+putloc(Fs *fs, Loc *l, int loop)
+{
+	Loc *m;
+	Buf *b;
+
+	qlock(&fs->loctree);
+	while(loop && l != nil && l->ref <= 1){
+		if((l->flags & LGONE) != 0){
+			b = getbuf(fs->d, l->blk, TDENTRY, 0);
+			if(b != nil){
+				delete(fs, l, b);
+				putbuf(b);
+			}
+		}
+		l->cnext->cprev = l->cprev;
+		l->cprev->cnext = l->cnext;
+		l->gnext->gprev = l->gprev;
+		l->gprev->gnext = l->gnext;
+		if(l->next != nil && l->next->child == l){
+			if(l->cnext == l)
+				l->next->child = nil;
+			else
+				l->next->child = l->cnext;
+		}
+		m = l->next;
+		free(l);
+		l = m;
+	}
+	for(; loop && l != nil; l = l->next)
+		--l->ref;
+	qunlock(&fs->loctree);
+}
+
+static int
+isopen(Fs *fs, FLoc *p, uvlong blk, int deind)
+{
+	Loc *l;
+
+	qlock(&fs->loctree);
+	for(l = fs->rootloc->gnext; l != fs->rootloc; l = l->gnext)
+		if(l->blk == blk && l->deind == deind && l->next->blk == p->blk && l->next->deind == p->deind){
+			qunlock(&fs->loctree);
+			return 1;
+		}
+	qunlock(&fs->loctree);
+	return 0;
+}
+
+static int
+dumpblk(Fs *fs, FLoc *p, uvlong *l)
+{
+	uvlong n;
+	int i;
+	Buf *b, *c;
+	Dentry *d;
+
+	b = getbuf(fs->d, *l, TDONTCARE, 0);
+	if(b == nil)
+		return -1;
+	if(getfree(fs, &n) < 0){
+	berr:
+		putbuf(b);
+		return -1;
+	}
+	c = getbuf(fs->d, n, b->type, 1);
+	if(c == nil){
+		putfree(fs, n);
+		goto berr;
+	}
+	switch(b->type){
+	case TINDIR:
+		memcpy(c->offs, b->offs, sizeof(b->offs));
+		for(i = 0; i < OFFPERBLK; i++)
+			if(b->offs[i] != 0)
+				chref(fs, b->offs[i], 1);
+		break;
+	case TRAW:
+		memcpy(c->data, b->data, sizeof(b->data));
+		break;
+	case TDENTRY:
+		memcpy(c->de, b->de, sizeof(b->de));
+		for(d = b->de; d < &b->de[DEPERBLK]; d++){
+			if((d->mode & DGONE) != 0 && !isopen(fs, p, *l, d - b->de))
+				memset(d, 0, sizeof(Dentry));
+			if((d->mode & DALLOC) == 0)
+				continue;
+			if((d->type & QTTMP) != 0)
+				continue;
+			for(i = 0; i < NDIRECT; i++)
+				if(d->db[i] != 0)
+					chref(fs, d->db[i], 1);
+			for(i = 0; i < NINDIRECT; i++)
+				if(d->ib[i] != 0)
+					chref(fs, d->ib[i], 1);
+		}
+		break;
+	default:
+		werrstr("dumpblk -- unknown type %d", b->type);
+		putfree(fs, n);
+		putbuf(c);
+		putbuf(b);
+		return -1;
+	}
+	c->op |= BDELWRI;
+	putbuf(c);
+	putbuf(b);
+	putfree(fs, *l);
+	*l = n;
+	return 0;
+}
+
+/*
+ * getblk returns the address of a block in a file
+ * given its relative address blk
+ * the address and generation are returned in *r and *gen
+ * mode has to be one of:
+ * - GBREAD: this block will only be read
+ * - GBWRITE: this block will be written, but don't create it
+ *            if it doesn't exist
+ * - GBCREATE: this block will be written, create it if necessary
+ * - GBOVERWR: like GBCREATE, but return an empty block if a dump
+ *             would be necessary
+ * return value is 1 if the block existed, -1 on error
+ * a return value of 0 means the block did not exist
+ * this is only an error in case of GBREAD and GBWRITE
+ * gen == nil allowed
+ */
+
+int
+getblk(Fs *fs, FLoc *L, Buf *bd, uvlong blk, uvlong *r, int mode)
+{
+	uvlong k, l;
+	uvlong *loc;
+	int i, j, rc, prc;
+	Buf *b;
+	Dentry *d;
+
+	b = bd;
+	d = &bd->de[L->deind];
+	if(blk < NDIRECT){
+		loc = &d->db[blk];
+		goto found;
+	}
+	blk -= NDIRECT;
+	l = 1;
+	for(i = 0; i < NINDIRECT; i++){
+		l *= OFFPERBLK;
+		if(blk < l)
+			break;
+		blk -= l;
+	}
+	if(i == NINDIRECT){
+		werrstr("getblk: block offset too large");
+		return -1;
+	}
+	loc = &d->ib[i];
+	for(j = 0; j <= i; j++){
+		if(*loc == 0){
+			if(mode == GBREAD || mode == GBWRITE){
+				if(b != bd)
+					putbuf(b);
+				return 0;
+			}
+			if(getfree(fs, loc) < 0){
+				if(b != bd)
+					putbuf(b);
+				return -1;
+			}
+			b->op |= BDELWRI;
+			k = *loc;
+			if(b != bd)
+				putbuf(b);
+			b = getbuf(fs->d, k, TINDIR, 1);
+			if(b == nil)
+				return -1;
+			memset(b->offs, 0, sizeof(b->offs));
+		}else{
+			if(mode != GBREAD && chref(fs, *loc, 0) > 1)
+				if(dumpblk(fs, L, loc) < 0){
+					if(b != bd)
+						putbuf(b);
+					return -1;
+				}
+			k = *loc;
+			if(b != bd)
+				putbuf(b);
+			b = getbuf(fs->d, k, TINDIR, 0);
+			if(b == nil)
+				return -1;
+		}
+		l /= OFFPERBLK;
+		loc = &b->offs[blk / l];
+		blk %= l;
+	}
+
+found:
+	rc = 0;
+	prc = 0;
+	if(*loc != 0){
+		if(mode == GBREAD)
+			goto okay;
+		if((rc = chref(fs, *loc, 0)) > 1){
+			if(mode == GBOVERWR){
+				putfree(fs, *loc);
+				*loc = 0;
+				b->op |= BDELWRI;
+				prc = 1;
+				goto new;
+			}
+			if(dumpblk(fs, L, loc) < 0){
+				rc = -1;
+				goto end;
+			}
+			b->op |= BDELWRI;
+		}
+		if(rc < 0)
+			goto end;
+		if(rc == 0){
+			dprint("hjfs: getblk: block %lld has refcount 0\n");
+			werrstr("phase error -- getblk");
+			rc = -1;
+			goto end;
+		}
+okay:
+		*r = *loc;
+		rc = 1;
+	}else if(mode == GBCREATE || mode == GBOVERWR){
+new:
+		rc = getfree(fs, r);
+		if(rc > 0){
+			*loc = *r;
+			b->op |= BDELWRI;
+			rc = prc;
+		}
+	}
+end:
+	if(b != bd)
+		putbuf(b);
+	return rc;
+}
+
+static void
+delindir(Fs *fs, uvlong *l, int n)
+{
+	Buf *b;
+	int k;
+
+	if(*l == 0)
+		return;
+	if(chref(fs, *l, 0) > 1){
+		*l = 0;
+		return;
+	}
+	if(n > 0){
+		b = getbuf(fs->d, *l, TINDIR, 0);
+		if(b != nil){
+			for(k = 0; k < OFFPERBLK; k++)
+				if(b->offs[k] != 0)
+					delindir(fs, &b->offs[k], n-1);
+			b->op |= BDELWRI;
+			putbuf(b);
+		}
+	}
+	putfree(fs, *l);
+	*l = 0;
+}
+
+static int
+zeroes(int *a, int i)
+{
+	while(i--)
+		if(*a++ != 0)
+			return 0;
+	return 1;
+}
+
+static void
+delindirpart(Fs *fs, FLoc *p, uvlong *l, int *a, int n)
+{
+	Buf *b;
+	int k;
+
+	if(*l == 0)
+		return;
+	if(n == 0){
+		putfree(fs, *l);
+		*l = 0;
+		return;
+	}
+	if(zeroes(a, n)){
+		delindir(fs, l, n);
+		return;
+	}
+	if(chref(fs, *l, 0) > 1)
+		dumpblk(fs, p, l);
+	b = getbuf(fs->d, *l, TINDIR, 0);
+	if(b == nil)
+		return;
+	delindirpart(fs, p, &b->offs[*a], a + 1, n - 1);
+	for(k = a[0] + 1; k < OFFPERBLK; k++)
+		if(b->offs[k] != 0)
+			delindir(fs, &b->offs[k], n - 1);
+	b->op |= BDELWRI;
+	putbuf(b);
+}
+
+/*
+ * call willmodify() before and modified()
+ * after calling this function
+ */
+int
+trunc(Fs *fs, FLoc *ll, Buf *bd, uvlong size)
+{
+	uvlong blk;
+	Dentry *d;
+	int a[NINDIRECT];
+	uvlong l;
+	int i, j;
+
+	d = &bd->de[ll->deind];
+	if(size >= d->size)
+		goto done;
+	blk = HOWMANY(size, RBLOCK);
+	while(blk < NDIRECT){
+		if(d->db[blk] != 0){
+			putfree(fs, d->db[blk]);
+			d->db[blk] = 0;
+		}
+		blk++;
+	}
+	blk -= NDIRECT;
+	l = 1;
+	for(i = 0; i < NINDIRECT; i++){
+		l *= OFFPERBLK;
+		if(blk < l)
+			break;
+		blk -= l;
+	}
+	if(blk >= l){
+		werrstr("phase error -- truncate");
+		return -1;
+	}
+	if(blk == 0)
+		goto rest;
+	if(d->ib[i] == 0){
+		i++;
+		goto rest;
+	}
+	for(j = 0; j <= i; j++){
+		l /= OFFPERBLK;
+		a[j] = blk / l;
+		blk %= l;
+	}
+	delindirpart(fs, ll, &d->ib[i], a, i + 1);
+	i++;
+rest:
+	for(; i < NINDIRECT; i++)
+		delindir(fs, &d->ib[i], i + 1);
+done:
+	d->size = size;
+	bd->op |= BDELWRI;
+	return 1;
+}
+
+/*
+ * find a direntry
+ * name == nil allows any entry to match
+ * rl == nil allowed
+ * return value 1 on success, 0 on Enoent,
+ * -1 on other errors
+ */
+int
+findentry(Fs *fs, FLoc *l, Buf *b, char *name, FLoc *rl, int dump)
+{
+	uvlong i;
+	int j;
+	Dentry *d;
+	uvlong r;
+	Buf *c;
+	
+	d = &b->de[l->deind];
+	for(i = 0; i < d->size; i++){
+		if(getblk(fs, l, b, i, &r, GBREAD) <= 0)
+			continue;
+		c = getbuf(fs->d, r, TDENTRY, 0);
+		if(c == nil)
+			continue;
+		for(j = 0; j < DEPERBLK; j++)
+			if((c->de[j].mode & DALLOC) != 0 &&
+			   (name == nil || strcmp(c->de[j].name, name) == 0)){
+				if(dump && (c->de[j].type & QTTMP) != 0)
+					continue;
+			   	if(rl != nil){
+					rl->Qid = c->de[j].Qid;
+			   		rl->blk = r;
+			   		rl->deind = j;
+			   	}
+				putbuf(c);
+				return 1;
+			}
+		putbuf(c);
+	}
+	werrstr(Enoent);
+	return 0;	
+}
+
+void
+modified(Chan *ch, Dentry *d)
+{
+	d->mtime = time(0);
+	d->atime = d->mtime;
+	d->muid = ch->uid;
+	ch->loc->vers = ++d->vers;
+}
+
+typedef struct Del Del;
+
+struct Del {
+	FLoc;
+	Del *next, *prev;
+};
+
+static int
+deltraverse(Fs *fs, Del *p, Buf *b, Del **last)
+{
+	Buf *c;
+	int frb;
+	Dentry *d;
+	uvlong i, s, r;
+	int j, rc;
+	Del *dd;
+	
+	frb = b == nil;
+	if(b == nil){
+		b = getbuf(fs->d, p->blk, TDENTRY, 0);
+		if(b == nil)
+			return -1;
+	}
+	s = b->de[p->deind].size;
+	for(i = 0; i < s; i++){
+		rc = getblk(fs, p, b, i, &r, GBREAD);
+		if(rc <= 0)
+			continue;
+		c = getbuf(fs->d, r, TDENTRY, 0);
+		if(c == nil)
+			continue;
+		for(j = 0; j < DEPERBLK; j++){
+			d = &c->de[j];
+			if((d->mode & (DALLOC|DGONE)) != 0){
+				if((d->type & QTDIR) == 0){
+					trunc(fs, p, b, 0);
+					memset(d, 0, sizeof(*d));
+					c->op |= BDELWRI;
+				}else{
+					dd = emalloc(sizeof(Del));
+					dd->blk = i;
+					dd->deind = j;
+					dd->prev = *last;
+					(*last)->next = dd;
+					*last = dd;
+				}
+			}
+		}
+		putbuf(c);
+	}
+	if(frb)
+		putbuf(b);
+	return 0;
+}
+
+int
+delete(Fs *fs, FLoc *l, Buf *b)
+{
+	Dentry *d;
+	Buf *c;
+	Del *first, *last, *p, *q;
+	
+	d = &b->de[l->deind];
+	if((d->type & QTDIR) == 0){
+		trunc(fs, l, b, 0);
+		memset(d, 0, sizeof(*d));
+		return 0;
+	}
+	first = last = emalloc(sizeof(Del));
+	first->FLoc = *l;
+	for(p = first; p != nil; p = p->next)
+		deltraverse(fs, p, p == first ? b : nil, &last);
+	for(p = last; p != nil; q = p->prev, free(p), p = q){
+		if(p == first)
+			c = b;
+		else
+			c = getbuf(fs->d, p->blk, TDENTRY, 0);
+		if(c == nil)
+			continue;
+		trunc(fs, p, c, 0);
+		memset(&c->de[p->deind], 0, sizeof(Dentry));
+		c->op |= BDELWRI;
+		if(p != first)
+			putbuf(c);
+	}
+	return 0;
+}
+
+int
+newentry(Fs *fs, Loc *l, Buf *b, char *name, FLoc *res)
+{
+	Dentry *d;
+	uvlong i, si, r;
+	int j, sj, rc;
+	Buf *c;
+	FLoc f;
+
+	si = sj = -1;
+	d = &b->de[l->deind];
+	for(i = 0; i <= d->size; i++){
+		if(i == d->size && si != -1)
+			break;
+		rc = getblk(fs, l, b, i, &r, si == -1 ? GBCREATE : GBREAD);
+		if(rc < 0)
+			continue;
+		if(rc == 0 && si != -1)
+			continue;
+		c = getbuf(fs->d, r, TDENTRY, rc == 0);
+		if(c == nil)
+			continue;
+		if(rc == 0){
+			memset(c->de, 0, sizeof(c->de));
+			if(i == d->size)
+				d->size++;
+		}
+		for(j = 0; j < DEPERBLK; j++){
+			if(si == -1 && (c->de[j].mode & DALLOC) == 0){
+				si = i;
+				sj = j;
+			}
+			if(si == -1 && (c->de[j].mode & DGONE) != 0 && !haveloc(fs, r, j, l)){
+				memset(&f, 0, sizeof(f));
+				f.blk = r;
+				f.deind = j;
+				if(delete(fs, &f, c) >= 0){
+					si = i;
+					sj = j;
+				}
+			}
+			if((c->de[j].mode & DALLOC) != 0 &&
+			   strcmp(c->de[j].name, name) == 0){
+				werrstr(Eexists);
+				putbuf(c);
+				return 0;
+			}
+		}
+		putbuf(c);
+	}
+	if(si == -1 || sj == -1){
+		werrstr("phase error -- create");
+		return -1;
+	}
+	if(getblk(fs, l, b, si, &res->blk, GBWRITE) <= 0)
+		return -1;
+	res->deind = sj;
+	return 1;
+}
--- /dev/null
+++ b/sys/src/cmd/hjfs/fs2.c
@@ -1,0 +1,742 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <fcall.h>
+#include "dat.h"
+#include "fns.h"
+
+Chan *
+chanattach(Fs *fs, int flags)
+{	
+	Chan *ch;
+
+	ch = emalloc(sizeof(*ch));
+	ch->fs = fs;
+	ch->flags = flags;
+	ch->loc = cloneloc(fs, (flags & CHFDUMP) != 0 ? fs->dumprootloc : fs->rootloc);
+	return ch;
+}
+
+Chan *
+chanclone(Chan *ch)
+{
+	Chan *d;
+
+	chbegin(ch);
+	d = emalloc(sizeof(*d));
+	d->fs = ch->fs;
+	d->flags = ch->flags;
+	d->uid = ch->uid;
+	d->loc = cloneloc(ch->fs, ch->loc);
+	chend(ch);
+	return d;
+}
+
+int
+chanwalk(Chan *ch, char *name)
+{
+	Buf *b;
+	Dentry *d;
+	Loc *l;
+	FLoc f;
+	
+	if(name == nil || name[0] == 0 || name[0] == '.' && name[1] == 0)
+		return 1;
+	chbegin(ch);
+	b = getbuf(ch->fs->d, ch->loc->blk, TDENTRY, 0);
+	if(b == nil){
+		chend(ch);
+		return -1;
+	}
+	d = &b->de[ch->loc->deind];
+	if((d->type & QTDIR) == 0){
+		werrstr(Enotadir);
+		goto error;
+	}
+	if(!permcheck(ch->fs, d, ch->uid, OEXEC)){
+		werrstr(Eperm);
+		goto error;
+	}
+	if(strcmp(name, "..") == 0){
+		l = ch->loc->next;
+		if(l == nil)
+			goto done;
+		putloc(ch->fs, ch->loc, 0);
+		ch->loc = l;
+		goto done;
+	}
+	if(findentry(ch->fs, ch->loc, b, name, &f, ch->flags & CHFDUMP) <= 0)
+		goto error;
+	ch->loc = getloc(ch->fs, f, ch->loc);
+done:
+	putbuf(b);
+	chend(ch);
+	return 1;
+error:
+	putbuf(b);
+	chend(ch);
+	return -1;
+}
+
+int
+namevalid(char *name)
+{
+	char *p;
+	
+	if(name == nil || name[0] == 0)
+		return 0;
+	if(name[0] == '.' && (name[1] == 0 || name[1] == '.' && name[2] == 0))
+		return 0;
+	for(p = name; *p; p++)
+		if((uchar) *p < ' ' || *p == '/')
+			return 0;
+	return p - name < NAMELEN;
+}
+
+
+int
+chancreat(Chan *ch, char *name, int perm, int mode)
+{
+	Buf *b, *c;
+	Dentry *d;
+	int isdir;
+	FLoc f;
+	
+	if((ch->flags & CHFRO) != 0){
+		werrstr(Einval);
+		return -1;
+	}
+	chbegin(ch);
+	if(willmodify(ch->fs, ch->loc, ch->flags & CHFNOLOCK) < 0){
+		chend(ch);
+		return -1;
+	}
+	if(!namevalid || ch->open != 0){
+		werrstr(Einval);
+		chend(ch);
+		return -1;
+	}
+	if(isdir = ((perm & DMDIR) != 0))
+		if((mode & (OWRITE | OEXEC | ORCLOSE | OTRUNC)) != 0){
+			werrstr(Einval);
+			chend(ch);
+			return -1;
+		}
+	b = getbuf(ch->fs->d, ch->loc->blk, TDENTRY, 0);
+	if(b == nil){
+		chend(ch);
+		return -1;
+	}
+	d = &b->de[ch->loc->deind];
+	if((d->type & QTDIR) == 0){
+		werrstr(Enotadir);
+		goto error;
+	}
+	if(!permcheck(ch->fs, d, ch->uid, OWRITE)){
+		werrstr(Eperm);
+		goto error;
+	}
+	if(newentry(ch->fs, ch->loc, b, name, &f) <= 0)
+		goto error;
+	c = getbuf(ch->fs->d, f.blk, TDENTRY, 0);
+	if(c == nil)
+		goto error;
+	modified(ch, d);
+	if(isdir)
+		perm &= ~0777 | d->mode & 0777;
+	else
+		perm &= ~0666 | d->mode & 0666;
+	d = &c->de[f.deind];
+	memset(d, 0, sizeof(Dentry));
+	if(newqid(ch->fs, &d->path) < 0)
+		goto error;
+	d->type = perm >> 24;
+	strcpy(d->name, name);
+	d->mtime = time(0);
+	d->atime = d->mtime;
+	d->gid = d->uid = d->muid = ch->uid;
+	d->mode = DALLOC | perm & 0777;
+	f.Qid = d->Qid;
+	ch->loc = getloc(ch->fs, f, ch->loc);
+	if((d->type & QTEXCL) != 0){
+		qlock(&ch->loc->ex);
+		ch->loc->exlock = ch;
+		ch->loc->lwrite = d->atime;
+		qunlock(&ch->loc->ex);
+	}
+	c->op |= BDELWRI;
+	b->op |= BDELWRI;
+	putbuf(c);
+	putbuf(b);
+	switch(mode & OEXEC){
+	case ORDWR:
+		ch->open |= CHREAD;
+	case OWRITE:
+		ch->open |= CHWRITE;
+		break;
+	case OEXEC:
+	case OREAD:
+		ch->open |= CHREAD;
+		break;
+	}
+	chend(ch);
+	return 1;
+
+error:
+	putbuf(b);
+	chend(ch);
+	return -1;
+}
+
+int
+chanopen(Chan *ch, int mode)
+{
+	Buf *b;
+	Dentry *d;
+	int isdir;
+
+	chbegin(ch);
+	if(ch->open != 0){
+		werrstr(Einval);
+		chend(ch);
+		return -1;
+	}
+	b = getbuf(ch->fs->d, ch->loc->blk, TDENTRY, 0);
+	if(b == nil){
+		chend(ch);
+		return -1;
+	}
+	d = &b->de[ch->loc->deind];
+	if(!permcheck(ch->fs, d, ch->uid, mode & OEXEC)){
+	permerr:
+		werrstr(Eperm);
+	err:
+		putbuf(b);
+		chend(ch);
+		return -1;
+	}
+	if((d->type & QTAPPEND) != 0)
+		mode &= ~OTRUNC;
+	isdir = (d->type & QTDIR) != 0;
+	if(isdir && (mode & (ORCLOSE | OTRUNC | OWRITE | ORDWR)) != 0){
+		werrstr(Einval);
+		goto err;
+	}
+	if((mode & OTRUNC) != 0 && !permcheck(ch->fs, d, ch->uid, OWRITE))
+		goto permerr;
+	if((ch->flags & CHFRO) != 0 && (mode & (ORCLOSE | OTRUNC | OWRITE | ORDWR)) != 0){
+		werrstr(Einval);
+		goto err;
+	}
+	if((ch->loc->type & QTEXCL) != 0){
+		qlock(&ch->loc->ex);
+		if(ch->loc->exlock == nil || ch->loc->lwrite < time(0) - EXCLDUR){
+			ch->loc->exlock = ch;
+			ch->loc->lwrite = time(0);
+			qunlock(&ch->loc->ex);
+		}else{
+			qunlock(&ch->loc->ex);
+			werrstr(Elocked);
+			goto err;
+		}
+	}
+	switch(mode & OEXEC){
+	case ORDWR:
+		ch->open |= CHREAD;
+	case OWRITE:
+		ch->open |= CHWRITE;
+		break;
+	case OEXEC:
+	case OREAD:
+		ch->open |= CHREAD;
+		break;
+	}
+	if((mode & OTRUNC) != 0){
+		trunc(ch->fs, ch->loc, b, 0);
+		modified(ch, d);
+	}
+	if((mode & ORCLOSE) != 0)
+		ch->open |= CHRCLOSE;
+	putbuf(b);
+	chend(ch);
+	return 1;
+}
+
+static int
+checklock(Chan *ch)
+{
+	int rc;
+
+	qlock(&ch->loc->ex);
+	rc = 1;
+	if(ch->loc->exlock == ch){
+		if(ch->loc->lwrite < time(0) - EXCLDUR){
+			ch->loc->exlock = nil;
+			werrstr("lock broken");
+			rc = -1;
+		}else
+			ch->loc->lwrite = time(0);
+	}else{
+		werrstr(Elocked);
+		rc = -1;
+	}
+	qunlock(&ch->loc->ex);
+	return rc;
+}
+
+int
+chanwrite(Chan *ch, void *buf, ulong n, uvlong off)
+{
+	uvlong i, e, bl;
+	int r, rn, rc;
+	Buf *b, *c;
+	Dentry *d;
+	uchar *p;
+
+	if(n == 0)
+		return 0;
+	if((ch->flags & CHFRO) != 0){
+		werrstr(Einval);
+		return -1;
+	}
+	if((ch->open & CHWRITE) == 0){
+		werrstr(Einval);
+		return -1;
+	}
+	chbegin(ch);
+	if((ch->loc->type & QTEXCL) != 0 && checklock(ch) < 0){
+		chend(ch);
+		return -1;
+	}
+	if(willmodify(ch->fs, ch->loc, ch->flags & CHFNOLOCK) < 0){
+		chend(ch);
+		return -1;
+	}
+	b = getbuf(ch->fs->d, ch->loc->blk, TDENTRY, 0);
+	if(b == nil){
+		chend(ch);
+		return -1;
+	}
+	d = &b->de[ch->loc->deind];
+	if((d->type & QTAPPEND) != 0)
+		off = d->size;
+	e = off + n;
+	i = off;
+	p = buf;
+	while(i < e){
+		bl = i / RBLOCK;
+		r = i % RBLOCK;
+		rn = RBLOCK - r;
+		if(i + rn > e)
+			rn = e - i;
+		rc = getblk(ch->fs, ch->loc, b, bl, &bl, rn == RBLOCK ? GBOVERWR : GBCREATE);
+		if(rc < 0)
+			goto done;
+		c = getbuf(ch->fs->d, bl, TRAW, rc == 0 || rn == RBLOCK);
+		if(c == nil)
+			goto done;
+		if(rc == 0 && rn != RBLOCK)
+			memset(c->data, 0, sizeof(c->data));
+		memcpy(c->data + r, p, rn);
+		i += rn;
+		c->op |= i != e ? BWRIM : BDELWRI;
+		putbuf(c);
+		p += rn;
+	}
+done:
+	modified(ch, d);
+	e = off + (p - (uchar *) buf);
+	if(e > d->size)
+		d->size = e;
+	b->op |= BDELWRI;
+	putbuf(b);
+	chend(ch);
+	if(p == buf)
+		return -1;
+	return p - (uchar *) buf;
+}
+
+static int chandirread(Chan *, void *, ulong, uvlong);
+
+int
+chanread(Chan *ch, void *buf, ulong n, uvlong off)
+{
+	uvlong i, e, bl;
+	int r, rn, rc;
+	uchar *p;
+	Buf *b, *c;
+	Dentry *d;
+
+	chbegin(ch);
+	if((ch->loc->type & QTEXCL) != 0 && checklock(ch) < 0){
+		chend(ch);
+		return -1;
+	}
+	if((ch->open & CHREAD) == 0){
+		werrstr(Einval);
+		chend(ch);
+		return -1;
+	}
+	if((ch->loc->Qid.type & QTDIR) != 0)
+		return chandirread(ch, buf, n, off);
+	b = getbuf(ch->fs->d, ch->loc->blk, TDENTRY, 0);
+	if(b == nil){
+		chend(ch);
+		return -1;
+	}
+	d = &b->de[ch->loc->deind];
+	if(off >= d->size)
+		n = 0;
+	else if(off + n > d->size)
+		n = d->size - off;
+	if(n == 0){
+		putbuf(b);
+		chend(ch);
+		return 0;
+	}
+	e = off + n;
+	i = off;
+	p = buf;
+	while(i < e){
+		bl = i / RBLOCK;
+		r = i % RBLOCK;
+		rn = RBLOCK - r;
+		if(i + rn > e)
+			rn = e - i;
+		rc = getblk(ch->fs, ch->loc, b, bl, &bl, GBREAD);
+		if(rc < 0)
+			goto error;
+		if(rc == 0)
+			memset(p, 0, rn);
+		else{
+			c = getbuf(ch->fs->d, bl, TRAW, 0);
+			if(c == nil)
+				goto error;
+			memcpy(p, c->data + r, rn);
+			putbuf(c);
+		}
+		i += rn;
+		p += rn;
+	}
+	putbuf(b);
+	chend(ch);
+	return n;
+	
+error:
+	putbuf(b);
+	chend(ch);
+	return -1;
+}
+
+static void
+statbuf(Fs *fs, Dentry *d, Dir *di)
+{
+	di->qid = d->Qid;
+	di->mode = (d->mode & 0777) | (d->Qid.type << 24);
+	di->mtime = d->mtime;
+	di->atime = d->atime;
+	di->length = d->size;
+	if(d->type & QTDIR)
+		di->length = 0;
+	di->name = strdup(d->name);
+	di->uid = uid2name(fs, d->uid);
+	di->gid = uid2name(fs, d->gid);
+	di->muid = uid2name(fs, d->muid);
+}
+
+int
+chanstat(Chan *ch, Dir *di)
+{
+	Buf *b;
+	
+	chbegin(ch);
+	b = getbuf(ch->fs->d, ch->loc->blk, TDENTRY, 0);
+	if(b == nil){
+		chend(ch);
+		return -1;
+	}
+	statbuf(ch->fs, &b->de[ch->loc->deind], di);
+	putbuf(b);
+	chend(ch);
+	return 0;
+}
+
+static int
+chandirread(Chan *ch, void *buf, ulong n, uvlong off)
+{
+	Buf *b, *c;
+	Dentry *d;
+	uvlong i, blk;
+	int j;
+	int rc;
+	ulong wr;
+	Dir di;
+
+	if(off == 0){
+		ch->dwloff = 0;
+		ch->dwblk = 0;
+		ch->dwind = 0;
+	}else if(ch->dwloff != off){
+		werrstr(Einval);
+		chend(ch);
+		return -1;
+	}
+	b = getbuf(ch->fs->d, ch->loc->blk, TDENTRY, 0);
+	if(b == nil){
+		chend(ch);
+		return -1;
+	}
+	d = &b->de[ch->loc->deind];
+	if(ch->dwblk >= d->size){
+		putbuf(b);
+		chend(ch);
+		return 0;
+	}
+	c = nil;
+	wr = 0;
+	i = ch->dwblk;
+	j = ch->dwind;
+	for(;;){
+		if(c == nil){
+			rc = getblk(ch->fs, ch->loc, b, i, &blk, GBREAD);
+			if(rc < 0)
+				goto error;
+			if(rc == 0){
+				j = 0;
+				if(++i >= d->size)
+					break;
+				continue;
+			}
+			c = getbuf(ch->fs->d, blk, TDENTRY, 0);
+			if(c == nil)
+				goto error;
+		}
+		if((c->de[j].mode & DALLOC) == 0)
+			goto next;
+		if((ch->flags & CHFDUMP) != 0 && (c->de[j].type & QTTMP) != 0)
+			goto next;
+		statbuf(ch->fs, &c->de[j], &di);
+		rc = convD2M(&di, (uchar *) buf + wr, n - wr);
+		free(di.uid);
+		free(di.gid);
+		free(di.muid);
+		if(rc <= BIT16SZ)
+			break;
+		wr += rc;
+	next:
+		if(++j >= DEPERBLK){
+			j = 0;
+			if(c != nil)
+				putbuf(c);
+			c = nil;
+			if(++i >= d->size)
+				break;
+		}
+	}
+	ch->dwblk = i;
+	ch->dwind = j;
+	ch->dwloff += wr;
+	if(c != nil)
+		putbuf(c);
+	putbuf(b);
+	chend(ch);
+	return wr;
+error:
+	putbuf(b);
+	chend(ch);
+	return -1;
+}
+
+int
+chanclunk(Chan *ch)
+{
+	Buf *b, *p;
+	int rc;
+	Dentry *d;
+
+	chbegin(ch);
+	rc = 1;
+	if(ch->open & CHRCLOSE){
+		if((ch->flags & CHFRO) != 0)
+			goto inval;
+		if(willmodify(ch->fs, ch->loc, ch->flags & CHFNOLOCK) < 0)
+			goto err;
+		if(ch->loc->next == nil){
+		inval:
+			werrstr(Einval);
+		err:
+			rc = -1;
+			goto done;
+		}
+		p = getbuf(ch->fs->d, ch->loc->next->blk, TDENTRY, 0);
+		if(p == nil)
+			goto err;
+		b = getbuf(ch->fs->d, ch->loc->blk, TDENTRY, 0);
+		if(b == nil){
+			putbuf(p);
+			goto err;
+		}
+		if(!permcheck(ch->fs, &p->de[ch->loc->next->deind], ch->uid, OWRITE)){
+			werrstr(Eperm);
+			putbuf(b);
+			putbuf(p);
+			goto err;
+		}
+		d = &b->de[ch->loc->deind];
+		if((d->type & QTDIR) != 0 && findentry(ch->fs, ch->loc, b, nil, nil, ch->flags & CHFDUMP) != 0){
+			putbuf(b);
+			putbuf(p);
+			goto inval;
+		}
+		if((d->mode & DGONE) != 0){
+			putbuf(b);
+			putbuf(p);
+			goto done;
+		}
+		qlock(&ch->fs->loctree);
+		if(ch->loc->ref > 1){
+			d->mode &= ~DALLOC;
+			d->mode |= DGONE; /* aaaaand it's gone */
+			ch->loc->flags |= LGONE;
+			qunlock(&ch->fs->loctree);
+		}else{
+			ch->loc->flags &= ~LGONE;
+			qunlock(&ch->fs->loctree);
+			rc = delete(ch->fs, ch->loc, b);
+		}
+		b->op |= BDELWRI;
+		putbuf(b);
+		putbuf(p);
+	}
+done:
+	if((ch->loc->type & QTEXCL) != 0){
+		qlock(&ch->loc->ex);
+		if(ch->loc->exlock == ch)
+			ch->loc->exlock = nil;
+		qunlock(&ch->loc->ex);
+	}
+	putloc(ch->fs, ch->loc, 1);
+	chend(ch);
+	free(ch);
+	return rc;
+}
+
+int
+chanwstat(Chan *ch, Dir *di)
+{
+	Buf *b, *pb;
+	Dentry *d;
+	int isdir, owner, rc;
+	short nuid, ngid;
+
+	if((ch->flags & CHFRO) != 0){
+		werrstr(Einval);
+		return -1;
+	}
+	chbegin(ch);
+	if(willmodify(ch->fs, ch->loc, ch->flags & CHFNOLOCK) < 0){
+		chend(ch);
+		return -1;
+	}
+	pb = nil;
+	if(*di->name){
+		if(!namevalid(di->name) || ch->loc->next == nil){
+			werrstr(Einval);
+			chend(ch);
+			return -1;
+		}
+		pb = getbuf(ch->fs->d, ch->loc->next->blk, TDENTRY, 0);
+		if(pb == nil){
+			chend(ch);
+			return -1;
+		}
+		if(!permcheck(ch->fs, &pb->de[ch->loc->next->deind], ch->uid, OWRITE)){
+			werrstr(Eperm);
+			putbuf(pb);
+			chend(ch);
+			return -1;
+		}
+		rc = findentry(ch->fs, ch->loc->next, pb, di->name, nil, ch->flags & CHFDUMP);
+		if(rc > 0)
+			werrstr(Eexists);
+		if(rc != 0){
+			putbuf(pb);
+			chend(ch);
+			return -1;
+		}
+	}
+	b = getbuf(ch->fs->d, ch->loc->blk, TDENTRY, 0);
+	if(b == nil){
+		chend(ch);
+		return -1;
+	}
+	d = &b->de[ch->loc->deind];
+	isdir = (d->type & QTDIR) != 0;
+	owner = ch->uid == d->uid || ingroup(ch->fs, ch->uid, d->gid, 1) || (ch->fs->flags & FSNOPERM) != 0;
+	if((uvlong)~di->length){
+		if(isdir && di->length != 0)
+			goto inval;
+		if(di->length != d->size && !permcheck(ch->fs, d, ch->uid, OWRITE))
+			goto perm;
+	}
+	if((ulong)~di->atime)
+		goto inval;
+	if((ulong)~di->mtime && !owner)
+		goto perm;
+	if((ulong)~di->mode && !owner)
+		goto perm;
+	nuid = NOUID;
+	ngid = NOUID;
+	if(*di->uid != 0 && name2uid(ch->fs, di->uid, &nuid) < 0)
+		goto inval;
+	if(*di->gid != 0 && name2uid(ch->fs, di->gid, &ngid) < 0)
+		goto inval;
+	if(nuid != NOUID && (ch->fs->flags & FSCHOWN) == 0)
+		goto inval;
+	if((nuid != NOUID || ngid != NOUID) && !owner)
+		goto perm;
+
+	if((uvlong)~di->length && !isdir){
+		trunc(ch->fs, ch->loc, b, di->length);
+		modified(ch, d);
+	}
+	if((ulong)~di->mtime)
+		d->mtime = di->mtime;
+	if((ulong)~di->mode){
+		d->mode = d->mode & ~0777 | di->mode & 0777;
+		ch->loc->type = d->type = di->mode >> 24;
+	}
+	if(*di->name){
+		memset(d->name, 0, NAMELEN);
+		strcpy(d->name, di->name);
+	}
+	if(nuid != NOUID)
+		d->uid = nuid;
+	if(ngid != NOUID)
+		d->gid = ngid;
+	b->op |= BDELWRI;
+	if(pb != nil)
+		putbuf(pb);
+	putbuf(b);
+	chend(ch);
+	return 1;
+
+inval:
+	werrstr(Einval);
+	goto error;
+perm:
+	werrstr(Eperm);
+error:
+	if(pb != nil)
+		putbuf(pb);
+	putbuf(b);
+	chend(ch);
+	return -1;
+}
+
+int
+chanremove(Chan *ch)
+{
+	ch->open |= CHRCLOSE;
+	return chanclunk(ch);
+}
--- /dev/null
+++ b/sys/src/cmd/hjfs/main.c
@@ -1,0 +1,125 @@
+#include <u.h>
+#include <libc.h>
+#include <libsec.h>
+#include <thread.h>
+#include "dat.h"
+#include "fns.h"
+
+char Eio[] = "i/o error";
+char Enotadir[] = "not a directory";
+char Enoent[] = "not found";
+char Einval[] = "invalid operation";
+char Eperm[] = "permission denied";
+char Eexists[] = "file exists";
+char Elocked[] = "file locked";
+
+void*
+emalloc(int c)
+{
+	void *v;
+	
+	v = mallocz(c, 1);
+	if(v == 0)
+		sysfatal("malloc: %r");
+	setmalloctag(v, getcallerpc(&c));
+	return v;
+}
+
+ThrData *
+getthrdata(void)
+{
+	ThrData **v;
+	
+	v = (ThrData **) threaddata();
+	if(*v == nil){
+		*v = emalloc(sizeof(**v));
+		(*v)->resp = chancreate(sizeof(void *), 0);
+	}
+	return *v;
+}
+
+Fs *fsmain;
+
+int
+dprint(char *fmt, ...)
+{
+	va_list va;
+	int rc;
+	
+	va_start(va, fmt);
+	rc = vfprint(2, fmt, va);
+	va_end(va);
+	return rc;
+}
+
+static void
+syncproc(void *)
+{
+	for(;;){
+		sync(0);
+		sleep(SYNCINTERVAL);
+	}
+}
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [-rsS] [-m mem] [-n service] -f dev\n", argv0);
+	exits("usage");
+}
+
+void
+threadmain(int argc, char **argv)
+{
+	Dev *d;
+	char *file, *service;
+	int doream, flags, stdio, nbuf;
+
+	doream = 0;
+	stdio = 0;
+	flags = FSNOAUTH;
+	service = "fs";
+	file = nil;
+	nbuf = 10;
+	ARGBEGIN {
+	case 'A': flags &= ~FSNOAUTH; break;
+	case 'r': doream++; break;
+	case 'S': flags |= FSNOPERM | FSCHOWN; break;
+	case 's': stdio++; break;
+	case 'f': file = strdup(EARGF(usage())); break;
+	case 'n': service = strdup(EARGF(usage())); break;
+	case 'm':
+		nbuf = muldiv(atoi(EARGF(usage())), 1048576, sizeof(Buf));
+		if(nbuf < 10)
+			nbuf = 10;
+		break;
+	default: usage();
+	} ARGEND;
+	rfork(RFNOTEG);
+	bufinit(nbuf);
+	if(file == nil)
+		sysfatal("no default file");
+	if(argc != 0)
+		usage();
+	d = newdev(file);
+	if(d == nil)
+		sysfatal("newdev: %r");
+	fsmain = initfs(d, doream, flags);
+	if(fsmain == nil)
+		sysfatal("fsinit: %r");
+	initcons(service);
+	proccreate(syncproc, nil, mainstacksize);
+	start9p(service, stdio);
+	threadexits(nil);
+}
+
+void
+shutdown(void)
+{
+	wlock(fsmain);
+	sync(1);
+	dprint("hjfs: ending\n");
+	sleep(1000);
+	sync(1);
+	threadexitsall(nil);
+}
--- /dev/null
+++ b/sys/src/cmd/hjfs/mkfile
@@ -1,0 +1,21 @@
+</$objtype/mkfile
+
+BIN=/$objtype/bin
+TARG=hjfs
+OFILES=\
+	main.$O\
+	buf.$O\
+	dev.$O\
+	conv.$O\
+	fs1.$O\
+	fs2.$O\
+	auth.$O\
+	9p.$O\
+	dump.$O\
+	cons.$O\
+
+HFILES=\
+	dat.h\
+	fns.h\
+
+</sys/src/cmd/mkone