shithub: gefs

Download patch

ref: 6c35d0b041df30eacf7601d50d187adb90542b4d
parent: 104aa29f9fe37221d10e4cbaea1d8a8dd6b0d2ca
author: Ori Bernstein <[email protected]>
date: Sat Jan 1 16:29:51 EST 2022

fs: start checking permissions

--- a/TODO
+++ b/TODO
@@ -1,14 +1,11 @@
 *** must have before usable for testing ***
 - correctly implement deadlists
 - auth against authservers
-- users and groups:
-	- permission checks
-	- user and group loading
-- complete wstat: implement missing operations
 - implement special file modes: ORCLOSE, OEXCL, etc
-- add missing management commands in console
 
 *** nice to have, can get testing without**
+- Reserve blocks for deletion
+- add missing management commands in console
 - transient exec snapshots
 - performance optimization:
 	- async page writeback: currently, every write to device is sync.
--- a/blk.c
+++ b/blk.c
@@ -251,8 +251,10 @@
 	PBIT64(p, (uvlong)LogEnd);
 	if(ol->head.addr == -1)
 		ol->head = lb->bp;
-	if(ol->tail != lb)
+	if(ol->tail != lb){
+		putblk(ol->tail);
 		ol->tail = lb;
+	}
 	return 0;
 }
 
@@ -476,13 +478,9 @@
 	 */
 	if((bp = blkalloc_lk(a)) == -1)
 		return -1;
-	if((b = mallocz(sizeof(Blk), 1)) == nil)
+	if((b = initblk(bp, Tlog)) == nil)
 		return -1;
 	setflag(b, Bdirty);
-	b->type = Tlog;
-	b->bp.addr = bp;
-	b->ref = 1;
-	b->data = b->buf + Hdrsz;
 	b->logsz = Loghdsz;
 
 	PBIT64(b->data+b->logsz, (uvlong)LogEnd);
--- a/cache.c
+++ b/cache.c
@@ -7,7 +7,7 @@
 #include "fns.h"
 
 void
-cachedel(vlong del)
+cachedel_lk(vlong del)
 {
 	Bucket *bkt;
 	Blk *b, **p;
@@ -42,6 +42,7 @@
 	b->cprev = nil;
 	clrflag(b, Bcached);
 	fs->ccount--;
+	putblk(b);
 }
 
 Blk*
@@ -87,14 +88,20 @@
 		fs->ccount++;
 		refblk(b);
 	}
-	for(c = fs->ctail; c != nil && fs->ccount >= fs->cmax; c = fs->ctail){
-		cachedel(c->bp.addr);
-		putblk(c);
-	}
+	for(c = fs->ctail; c != nil && fs->ccount >= fs->cmax; c = fs->ctail)
+		cachedel_lk(c->bp.addr);
 
 Cached:
 	unlock(&fs->lrulk);
 	return b;
+}
+
+void
+cachedel(vlong del)
+{
+	lock(&fs->lrulk);
+	cachedel_lk(del);
+	unlock(&fs->lrulk);
 }
 
 Blk*
--- a/cons.c
+++ b/cons.c
@@ -99,13 +99,13 @@
 	for(i = 0; i < fs->nusers; i++){
 		u = &fs->users[i];
 		fprint(fd, "%d:%s:", u->id, u->name);
-		if((v = uid2user(fs->users, fs->nusers, u->lead)) == nil)
+		if((v = uid2user(u->lead)) == nil)
 			fprint(fd, "???:");
 		else
 			fprint(fd, "%s:", v->name);
 		sep = "";
 		for(j = 0; j < u->nmemb; j++){
-			if((v = uid2user(fs->users, fs->nusers, u->memb[j])) == nil)
+			if((v = uid2user(u->memb[j])) == nil)
 				fprint(fd, "%s???", sep);
 			else
 				fprint(fd, "%s%s", sep, v->name);
--- a/dat.h
+++ b/dat.h
@@ -274,6 +274,9 @@
 	Owmode	= 1<<1,	/* [4]mode: update file mode */
 	Owmtime	= 1<<2, /* [8]mtime: update mtime, in nsec */
 	Owatime	= 1<<3, /* [8]atime: update atime, in nsec */
+	Owuid	= 1<<4,	/* [4]uid: set uid */
+	Owgid	= 1<<5,	/* [4]uid: set gid */
+	Owmuid	= 1<<6,	/* [4]uid: set muid */
 };
 
 /*
@@ -439,14 +442,14 @@
 struct Xdir {
 	/* file data */
 	Qid	qid;	/* unique id from server */
-	vlong	mode;	/* permissions */
-	vlong	atime;	/* last read time */
-	vlong	mtime;	/* last write time */
+	int	mode;	/* permissions */
+	vlong	atime;	/* last read time: nsec */
+	vlong	mtime;	/* last write time: nsec */
 	uvlong	length;	/* file length */
 	int	uid;	/* owner name */
 	int	gid;	/* group name */
 	int	muid;	/* last modifier name */
-	char	name[Keymax];	/* last element of path */
+	char	*name;	/* last element of path */
 };
 
 struct Dent {
@@ -462,7 +465,7 @@
 struct Mount {
 	long	ref;
 	vlong	gen;
-	char	*user;
+	int	uid;
 	char	*name;
 	Tree	*root;
 };
@@ -483,6 +486,10 @@
 	long	ref;
 	int	mode;
 	int	iounit;
+
+	int	duid;
+	int	dgid;
+	int	dmode;
 
 	Scan	*scan;	/* in progres scan */
 	Dent	*dent;	/* (pqid, name) ref, modified on rename */
--- a/dump.c
+++ b/dump.c
@@ -49,7 +49,7 @@
 	int n, ws;
 	char *p;
 	Tree t;
-	Dir d;
+	Xdir d;
 
 	n = 0;
 	if(flg){
@@ -76,7 +76,7 @@
 			if(kv2dir(v, &d) == -1)
 				n = fmtprint(fmt, "bad dir");
 			else
-				n = fmtprint(fmt, "[qid=(%lld,%lud,%d), %lo, t=%lud,%lud, l=%lld]",
+				n = fmtprint(fmt, "[qid=(%lld,%lud,%d), %o, t=%lld,%lld, l=%lld]",
 					d.qid.path, d.qid.vers, d.qid.type,
 					d.mode, d.atime, d.mtime, d.length);
 			break;
@@ -102,8 +102,20 @@
 				n += fmtprint(fmt, "mtime:%llx ", GBIT64(p));
 				p += 8;
 			}
+			if(ws & Owuid){
+				n += fmtprint(fmt, "uid:%d ", GBIT32(p));
+				p += 4;
+			}
+			if(ws & Owgid){
+				n += fmtprint(fmt, "gid:%d ", GBIT32(p));
+				p += 4;
+			}
+			if(ws & Owmuid){
+				n += fmtprint(fmt, "muid:%d ", GBIT32(p));
+				p += 4;
+			}
 			if(p != v->v + v->nv){
-				fprint(2, "v->nv: %d\n", v->nv);
+				fprint(2, "v->nv: %d, sz=%d\n", v->nv, (int)(p - v->v));
 				abort();
 			}
 			break;
--- a/fns.h
+++ b/fns.h
@@ -52,8 +52,8 @@
 void	setval(Blk*, int, Kvp*);
 
 char*	loadusers(int, Tree*);
-User*	uid2user(User*, int, int);
-User*	name2user(User*, int, char*);
+User*	uid2user(int);
+User*	name2user(char*);
 
 char*	btupsert(Tree*, Msg*, int);
 char*	btlookup(Tree*, Key*, Kvp*, char*, int);
@@ -102,9 +102,10 @@
 char*	unpack32(int*, char*, char*, void*);
 char*	unpack64(int*, char*, char*, void*);
 char*	unpackstr(int*, char*, char*, char**);
-int	dir2kv(vlong, Dir*, Kvp*, char*, int);
+int	dir2kv(vlong, Xdir*, Kvp*, char*, int);
 int	kv2statbuf(Kvp*, char*, int);
-int	kv2dir(Kvp*, Dir*);
+int	dir2statbuf(Xdir*, char*, int);
+int	kv2dir(Kvp*, Xdir*);
 int	kv2qid(Kvp*, Qid*);
 
 char*	packbp(char*, int, Bptr*);
@@ -112,6 +113,7 @@
 char*	packtree(char*, int, Tree*);
 Tree*	unpacktree(Tree*, char*, int);
 char*	packdkey(char*, int, vlong, char*);
+char*	packdval(char*, int, Xdir*);
 
 /* fmt */
 int	Bconv(Fmt*);
--- a/fs.c
+++ b/fs.c
@@ -9,7 +9,7 @@
 
 static char*	clearb(Fid*, vlong, vlong);
 
-// FIXME: hack.
+// FIXME: hack. We sync way too often.
 static char*
 updatesnap(Fid *f)
 {
@@ -278,7 +278,7 @@
 }
 
 static Dent*
-getdent(vlong pqid, Dir *d)
+getdent(vlong pqid, Xdir *d)
 {
 	Dent *de;
 	char *e;
@@ -296,6 +296,7 @@
 
 	if((de = mallocz(sizeof(Dent), 1)) == nil)
 		return nil;
+	de->Xdir = *d;
 	de->ref = 1;
 	de->qid = d->qid;
 	de->length = d->length;
@@ -497,6 +498,62 @@
 	respond(m, &r);
 }
 
+int
+ingroup(int uid, int gid)
+{
+	User *u, *g;
+	int i, in;
+
+	rlock(&fs->userlk);
+	in = 0;
+	u = uid2user(uid);
+	g = uid2user(gid);
+	if(u != nil && g != nil)
+		for(i = 0; i < g->nmemb; i++)
+			if(u->id == g->memb[i])
+				in = 1;
+	runlock(&fs->userlk);
+	return in;
+}
+
+int
+mode2bits(int req)
+{
+	int m;
+
+	m = 0;
+	switch(req&0xf){
+	case OREAD:	m = DMREAD;		break;
+	case OWRITE:	m = DMWRITE;		break;
+	case ORDWR:	m = DMREAD|DMWRITE;	break;
+	case OEXEC:	m = DMREAD|DMEXEC;	break;
+	}
+	if(req&OTRUNC)
+		m |= DMWRITE;
+	return m;
+}
+
+int
+fsaccess(Mount *mnt, int fmode, int fuid, int fgid, int m)
+{
+	/* uid none gets only other permissions */
+	if(mnt->uid != 0) {
+		if(mnt->uid == fuid)
+			if((m & (fmode>>6)) == m)
+				return 0;
+		if(ingroup(mnt->uid, fgid))
+			if((m & (fmode>>3)) == m)
+				return 0;
+	}
+	if(m & fmode) {
+		if((fmode & DMDIR) && (m == DMEXEC))
+			return 0;
+		if(!ingroup(mnt->uid, 9999))
+			return 0;
+	}
+	return -1;
+}
+
 static void
 fsattach(Fmsg *m, int iounit)
 {
@@ -503,11 +560,12 @@
 	char *e, *p, dbuf[Kvmax], kvbuf[Kvmax];
 	Mount *mnt;
 	Dent *de;
+	User *u;
 	Fcall r;
+	Xdir d;
 	Kvp kv;
 	Key dk;
 	Fid f;
-	Dir d;
 
 	if((mnt = mallocz(sizeof(Mount), 1)) == nil){
 		rerror(m, Emem);
@@ -517,10 +575,15 @@
 		rerror(m, Emem);
 		return;
 	}
-	if((mnt->user = strdup("glenda")) == nil){
-		rerror(m, Emem);
+	rlock(&fs->userlk);
+	if((u = name2user("glenda")) == nil){
+		rerror(m, Enouser);
+		runlock(&fs->userlk);
 		return;
 	}
+	mnt->uid = u->id;
+	runlock(&fs->userlk);
+
 	if((mnt->root = openlabel(m->aname)) == nil){
 		rerror(m, Enosnap);
 		return;
@@ -560,6 +623,9 @@
 	f.mode = -1;
 	f.iounit = iounit;
 	f.dent = de;
+	f.duid = mnt->uid;
+	f.dgid = d.gid;
+	f.dmode = d.mode;
 	if(dupfid(m->fid, &f) == nil){
 		rerror(m, Enomem);
 		return;
@@ -575,13 +641,14 @@
 fswalk(Fmsg *m)
 {
 	char *p, *e, kbuf[Maxent], kvbuf[Kvmax];
+	int duid, dgid, dmode;
 	vlong up, prev;
 	Fid *o, *f;
 	Dent *dent;
 	Fcall r;
+	Xdir d;
 	Kvp kv;
 	Key k;
-	Dir d;
 	int i;
 
 	if((o = getfid(m->fid)) == nil){
@@ -589,6 +656,7 @@
 		return;
 	}
 	if(o->mode != -1){
+print("use walk\n");
 		rerror(m, Einuse);
 		return;
 	}
@@ -595,6 +663,10 @@
 	e = nil;
 	up = o->qpath;
 	prev = o->qpath;
+	d = *o->dent;
+	duid = d.uid;
+	dgid = d.gid;
+	dmode = d.mode;
 	r.type = Rwalk;
 	for(i = 0; i < m->nwname; i++){
 		if((p = packdkey(kbuf, sizeof(kbuf), prev, m->wname[i])) == nil){
@@ -607,6 +679,9 @@
 		if((e = lookup(o, &k, &kv, kvbuf, sizeof(kvbuf), 0)) != nil){
 			break;
 		}
+		duid = d.uid;
+		dgid = d.gid;
+		dmode = d.mode;
 		if(kv2dir(&kv, &d) == -1){
 			rerror(m, Efs);
 			putfid(o);
@@ -643,6 +718,9 @@
 		if(i == m->nwname){
 			f->qpath = r.wqid[i-1].path;
 			f->dent = dent;
+			f->duid = duid;
+			f->dgid = dgid;
+			f->dmode = dmode;
 		}
 	}
 	respond(m, &r);
@@ -688,7 +766,8 @@
 	Fcall r;
 	Dent *de;
 	Msg mb[3];
-	Dir o, d;
+	Xdir o;
+	Dir d;
 	Fid *f;
 	Kvp kv;
 	Key k;
@@ -709,6 +788,12 @@
 	}
 	de = f->dent;
 	k = f->dent->Key;
+	rlock(de);
+	if(fsaccess(f->mnt, de->mode, de->uid, de->gid, DMWRITE) == -1){
+		rerror(m, Eperm);
+		runlock(de);
+		return;
+	}
 
 	/* A nop qid change is allowed. */
 	if(d.qid.path != ~0 || d.qid.vers != ~0){
@@ -737,7 +822,7 @@
 		mb[nm].op = Odelete;
 		mb[nm].Key = f->dent->Key;
 		nm++;
-		if((e = btlookup(f->mnt->root, f->dent, &kv, kvbuf, sizeof(kvbuf))) != nil){
+		if((e = btlookup(f->mnt->root, de, &kv, kvbuf, sizeof(kvbuf))) != nil){
 			rerror(m, e);
 			goto Out;
 		}
@@ -756,13 +841,17 @@
 		k = mb[nm].Key;
 		nm++;
 	}
+	runlock(de);
 
+	wlock(de);
 	p = opbuf+1;
 	op = 0;
 	mb[nm].Key = k;
 	mb[nm].op = Owstat;
+	de->qid.vers++;
 	if(d.length != ~0){
 		op |= Owsize;
+		de->length = d.length;
 		PBIT64(p, d.length);
 		p += 8;
 		sync = 0;
@@ -769,6 +858,7 @@
 	}
 	if(d.mode != ~0){
 		op |= Owmode;
+		de->mode = d.mode;
 		PBIT32(p, d.mode);
 		p += 4;
 		sync = 0;
@@ -775,14 +865,22 @@
 	}
 	if(d.mtime != ~0){
 		op |= Owmtime;
+		de->mtime = d.mtime;
 		PBIT64(p, (vlong)d.mtime*Nsec);
 		p += 8;
 		sync = 0;
 	}
+	op |= Owmuid;
+	de->muid = f->mnt->uid;
+	PBIT32(p, f->mnt->uid);
+	p += 4;
+	wunlock(de);
+
 	opbuf[0] = op;
 	mb[nm].v = opbuf;
 	mb[nm].nv = p - opbuf;
 	nm++;
+
 	if(sync){
 		rerror(m, Eimpl);
 	}else{
@@ -831,11 +929,11 @@
 fscreate(Fmsg *m)
 {
 	char *e, buf[Kvmax];
-	Dent *dent;
+	Dent *de;
 	Fcall r;
 	Msg mb;
 	Fid *f;
-	Dir d;
+	Xdir d;
 
 	if(okname(m->name) == -1){
 		rerror(m, Ename);
@@ -849,6 +947,15 @@
 		rerror(m, "unknown permission");
 		return;
 	}
+	de = f->dent;
+	rlock(de);
+	if(fsaccess(f->mnt, de->mode, de->uid, de->gid, DMWRITE) == -1){
+		rerror(m, Eperm);
+		runlock(de);
+		return;
+	}
+	runlock(de);
+
 	d.qid.type = 0;
 	if(m->perm & DMDIR)
 		d.qid.type |= QTDIR;
@@ -862,12 +969,13 @@
 	d.qid.vers = 0;
 	d.mode = m->perm;
 	d.name = m->name;
-	d.atime = (nsec() + Nsec/2)/Nsec;
+	d.atime = nsec();
 	d.mtime = d.atime;
 	d.length = 0;
-	d.uid = "glenda";
-	d.gid = "glenda";
-	d.muid = "glenda";
+	d.uid = f->mnt->uid;
+	d.gid = f->dgid;
+	d.muid = f->mnt->uid;
+
 	mb.op = Oinsert;
 	if(dir2kv(f->qpath, &d, &mb, buf, sizeof(buf)) == -1){
 		rerror(m, Efs);
@@ -879,8 +987,8 @@
 		putfid(f);
 		return;
 	}
-	dent = getdent(f->qpath, &d);
-	if(dent == nil){
+	de = getdent(f->qpath, &d);
+	if(de == nil){
 		if(m->fid != m->newfid)
 			clunkfid(f);
 		rerror(m, Enomem);
@@ -891,24 +999,25 @@
 	lock(f);
 	if(f->mode != -1){
 		unlock(f);
-		clunkdent(dent);
+		clunkdent(de);
+print("use create\n");
 		rerror(m, Einuse);
 		putfid(f);
 		return;
 	}
-	f->mode = m->mode;
+	f->mode = mode2bits(m->mode);
 	f->qpath = d.qid.path;
-	f->dent = dent;
-	wlock(f->dent);
-	if((e = clearb(f, 0, dent->length)) != nil){
+	f->dent = de;
+	wlock(de);
+	if((e = clearb(f, 0, de->length)) != nil){
 		unlock(f);
-		clunkdent(dent);
+		clunkdent(de);
 		rerror(m, e);
 		putfid(f);
 		return;
 	}
-	dent->length = 0;
-	wunlock(f->dent);
+	de->length = 0;
+	wunlock(de);
 	unlock(f);
 
 	r.type = Rcreate;
@@ -971,6 +1080,11 @@
 		clunkfid(f);
 		return;
 	}
+	if(fsaccess(f->mnt, f->dmode, f->duid, f->dgid, OWRITE) == -1){
+		rerror(m, Eperm);
+		runlock(f->dent);
+		return;
+	}
 	mb.op = Odelete;
 	mb.k = f->dent->k;
 	mb.nk = f->dent->nk;
@@ -1001,67 +1115,17 @@
 	clunkfid(f);
 }
 
-int
-ingroup(char *user, char *group)
-{
-	User *u, *g;
-	int i, in;
-
-	rlock(&fs->userlk);
-	in = 0;
-	u = name2user(fs->users, fs->nusers, user);
-	g = name2user(fs->users, fs->nusers, group);
-	if(u != nil && g != nil)
-		for(i = 0; i < g->nmemb; i++)
-			if(u->id == g->memb[i])
-				in = 1;
-	runlock(&fs->userlk);
-	return in;
-}
-
-int
-fsaccess(Mount *mnt, Dir *d, int req)
-{
-	int m;
-
-	m = 0;
-	switch(req&0xf){
-	case OREAD:	m = DMREAD;		break;
-	case OWRITE:	m = DMWRITE;		break;
-	case ORDWR:	m = DMREAD|DMWRITE;	break;
-	}
-	if(req&OEXEC)
-		m |= DMEXEC;
-	if(req&OTRUNC)
-		m |= DMWRITE;
-
-	/* uid none gets only other permissions */
-	if(strcmp(mnt->user, "none") != 0) {
-		if(strcmp(mnt->user, d->uid) == 0)
-			if((m<<6) & d->mode)
-				return 0;
-		if(ingroup(mnt->user, d->gid))
-			if((m<<3) & d->mode)
-				return 0;
-	}
-	if(m & d->mode) {
-		if((d->mode & DMDIR) && (m == DMEXEC))
-			return 0;
-		if(!ingroup(mnt->user, "noworld"))
-			return 0;
-	}
-	return -1;
-}
-
 void
 fsopen(Fmsg *m)
 {
 	char *e, buf[Kvmax];
 	Fcall r;
-	Dir d;
+	Xdir d;
 	Fid *f;
 	Kvp kv;
+	int mb;
 
+	mb = mode2bits(m->mode);
 	if((f = getfid(m->fid)) == nil){
 		rerror(m, Efid);
 		return;
@@ -1076,7 +1140,7 @@
 		putfid(f);
 		return;
 	}
-	if(fsaccess(f->mnt, &d, m->mode) == -1){
+	if(fsaccess(f->mnt, d.mode, d.uid, d.gid, mb) == -1){
 		rerror(m, Eperm);
 		putfid(f);
 		return;
@@ -1091,12 +1155,13 @@
 	lock(f);
 	if(f->mode != -1){
 		rerror(m, Einuse);
+print("in use open\n");
 		unlock(f);
 		putfid(f);
 		return;
 	}
-	f->mode = m->mode;
-//	if((f->mode & 0x7) == OEXEC){
+	f->mode = mode2bits(m->mode);
+//	if((f->mode & DMEXEC)){
 //		lock(&fs->root.lk);
 //		f->root = fs->root;
 //		refblk(fs->root.bp);
@@ -1119,7 +1184,6 @@
 	char pfx[Dpfxsz], *p, *e;
 	int n, ns, done;
 	Scan *s;
-	Dir d;
 
 	s = f->scan;
 	if(s != nil && s->offset != 0 && s->offset != m->offset)
@@ -1143,7 +1207,6 @@
 		unlock(f);
 	}
 	if(s->done){
-		fprint(2, "early done...\n");
 		r->count = 0;
 		return nil;
 	}
@@ -1150,8 +1213,7 @@
 	p = r->data;
 	n = m->count;
 	if(s->overflow){
-		kv2dir(&s->kv, &d);
-		if((ns = convD2M(&d, (uchar*)p, n)) <= BIT16SZ)
+		if((ns = kv2statbuf(&s->kv, p, n)) == -1)
 			return Edscan;
 		s->overflow = 0;
 		p += ns;
@@ -1162,8 +1224,7 @@
 			return e;
 		if(done)
 			break;
-		kv2dir(&s->kv, &d);
-		if((ns = convD2M(&d, (uchar*)p, n)) <= BIT16SZ){
+		if((ns = kv2statbuf(&s->kv, p, n)) == -1){
 			s->overflow = 1;
 			break;
 		}
@@ -1265,8 +1326,8 @@
 		rerror(m, Efid);
 		return;
 	}
-	if((f->mode&0x7) != OWRITE){
-		dprint("f->mode: %x\n", f->mode);
+	if(!(f->mode & DMWRITE)){
+print("f->mode: %x\n", f->mode);
 		rerror(m, Einuse);
 		putfid(f);
 		return;
@@ -1309,6 +1370,10 @@
 		p += 8;
 		f->dent->length = m->offset+m->count;
 	}
+	sbuf[0] |= Owmuid;
+	PBIT32(p, f->mnt->uid);
+	p += 4;
+
 	kv[i].v = sbuf;
 	kv[i].nv = p - sbuf;
 	if((e = btupsert(f->mnt->root, kv, i+1)) != nil){
--- a/pack.c
+++ b/pack.c
@@ -132,69 +132,68 @@
 }
 		
 int
-dir2kv(vlong up, Dir *d, Kvp *kv, char *buf, int nbuf)
+dir2kv(vlong up, Xdir *d, Kvp *kv, char *buf, int nbuf)
 {
-	char *k, *ek, *v, *ev, *eb;
-	int err;
+	char *ek, *ev, *eb;
 
-	err = 0;
-	k = buf;
-	ek = buf;
+	if((ek = packdkey(buf, nbuf, up, d->name)) == nil)
+		return -1;
+	kv->k = buf;
+	kv->nk = ek - buf;
 	eb = buf + nbuf;
-	ek = pack8(&err, ek, eb, Kent);
-	ek = pack64(&err, ek, eb, up);
-	ek = packstr(&err, ek, eb, d->name);
-
-	v = ek;
-	ev = ek;
-	ev = pack64(&err, ev, eb, d->qid.path);
-	ev = pack32(&err, ev, eb, d->qid.vers);
-	ev = pack8(&err, ev, eb, d->qid.type);
-	ev = pack32(&err, ev, eb, d->mode);
-	ev = pack64(&err, ev, eb, (vlong)d->atime*Nsec);
-	ev = pack64(&err, ev, eb, (vlong)d->mtime*Nsec);
-	ev = pack64(&err, ev, eb, d->length);
-	ev = packstr(&err, ev, eb, d->uid);
-	ev = packstr(&err, ev, eb, d->gid);
-	ev = packstr(&err, ev, eb, d->muid);
-	if(err){
-		werrstr("stat too big: %.*s...", 32, d->name);
+	if((ev = packdval(ek, eb - ek, d)) == nil)
 		return -1;
-	}
-	kv->k = k;
-	kv->nk = ek - k;
-	kv->v = v;
-	kv->nv = ev - v;
+	kv->v = ek;
+	kv->nv = ev - ek;
 	return 0;
 }
 
-int
-name2dkey(vlong up, char *name, Key *k, char *buf, int nbuf)
+char*
+packdkey(char *p, int sz, vlong up, char *name)
 {
-	char *ek, *eb;
+	char *ep;
 	int err;
 
 	err = 0;
-	ek = buf;
-	eb = buf + nbuf;
-	ek = pack8(&err, ek, eb, Kent);
-	ek = pack64(&err, ek, eb, up);
-	ek = packstr(&err, ek, eb, name);
+	ep = p + sz;
+	p = pack8(&err, p, ep, Kent);
+	p = pack64(&err, p, ep, up);
+	p = packstr(&err, p, ep, name);
 	if(err)
-		return -1;
-	k->k = buf;
-	k->nk = ek - buf;
-	return k->nk;
+		return nil;
+	return p;
 }
 
+char*
+packdval(char *p, int sz, Xdir *d)
+{
+	char *e;
+	int err;
+
+	err = 0;
+	e = p + sz;
+	p = pack64(&err, p, e, d->qid.path);
+	p = pack32(&err, p, e, d->qid.vers);
+	p = pack8(&err, p, e, d->qid.type);
+	p = pack32(&err, p, e, d->mode);
+	p = pack64(&err, p, e, d->atime);
+	p = pack64(&err, p, e, d->mtime);
+	p = pack64(&err, p, e, d->length);
+	p = pack32(&err, p, e, d->uid);
+	p = pack32(&err, p, e, d->gid);
+	p = pack32(&err, p, e, d->muid);
+	if(err)
+		abort();
+	return p;
+}
+
 int
-kv2dir(Kvp *kv, Dir *d)
+kv2dir(Kvp *kv, Xdir *d)
 {
 	char *k, *ek, *v, *ev;
-	vlong atime, mtime;
 	int err;
 
-	memset(d, 0, sizeof(Dir));
+	memset(d, 0, sizeof(Xdir));
 	err = 0;
 	k = kv->k + 9;
 	ek = kv->k + kv->nk;
@@ -210,12 +209,12 @@
 	v = unpack32(&err, v, ev, &d->qid.vers);
 	v = unpack8(&err, v, ev, &d->qid.type);
 	v = unpack32(&err, v, ev, &d->mode);
-	v = unpack64(&err, v, ev, &atime);
-	v = unpack64(&err, v, ev, &mtime);
+	v = unpack64(&err, v, ev, &d->atime);
+	v = unpack64(&err, v, ev, &d->mtime);
 	v = unpack64(&err, v, ev, &d->length);
-	v = unpackstr(&err, v, ev, &d->uid);
-	v = unpackstr(&err, v, ev, &d->gid);
-	v = unpackstr(&err, v, ev, &d->muid);
+	v = unpack32(&err, v, ev, &d->uid);
+	v = unpack32(&err, v, ev, &d->gid);
+	v = unpack32(&err, v, ev, &d->muid);
 	if(err){
 //		print("fucked: %P\n", kv);
 		werrstr("val too small [%s]", d->name);
@@ -229,8 +228,6 @@
 		werrstr("stat full of fuck");
 		return -1;
 	}
-	d->atime = (atime+Nsec/2)/Nsec;
-	d->mtime = (mtime+Nsec/2)/Nsec;
 	return 0;
 }
 
@@ -237,14 +234,59 @@
 int
 kv2statbuf(Kvp *kv, char *buf, int nbuf)
 {
-	Dir d;
-	int n;
+	int sz, nn, nu, ng, nm, ret;
+	vlong atime, mtime;
+	User *u, *g, *m;
+	char *p;
+	Xdir d;
 
 	if(kv2dir(kv, &d) == -1)
 		return -1;
-	if((n = convD2M(&d, (uchar*)buf, nbuf)) <= BIT16SZ)
-		return -1;
-	return n;	
+
+	ret = -1;
+	rlock(&fs->userlk);
+	if((u = uid2user(d.uid)) == nil)
+		goto Out;
+	if((g = uid2user(d.gid)) == nil)
+		goto Out;
+	if((m = uid2user(d.muid)) == nil)
+		goto Out;
+
+	p = buf;
+	nn = strlen(d.name);
+	nu = strlen(u->name);
+	ng = strlen(g->name);
+	nm = strlen(m->name);
+	atime = (d.atime+Nsec/2)/Nsec;
+	mtime = (d.mtime+Nsec/2)/Nsec;
+	sz = STATFIXLEN + nn + nu + ng + nm;
+	if(sz > nbuf)
+		goto Out;
+	
+	PBIT16(p, sz-2);		p += 2;
+	PBIT16(p, -1 /*type*/);		p += 2;
+	PBIT32(p, -1 /*dev*/);		p += 4;
+	PBIT8(p, d.qid.type);		p += 1;
+	PBIT32(p, d.qid.vers);		p += 4;
+	PBIT64(p, d.qid.path);		p += 8;
+	PBIT32(p, d.mode);		p += 4;
+	PBIT32(p, atime);		p += 4;
+	PBIT32(p, mtime);		p += 4;
+	PBIT64(p, d.length);		p += 8;
+
+	PBIT16(p, nn);			p += 2;
+	memcpy(p, d.name, nn);		p += nn;
+	PBIT16(p, nu);			p += 2;
+	memcpy(p, u->name, nu);		p += nu;
+	PBIT16(p, ng);			p += 2;
+	memcpy(p, g->name, ng);		p += ng;
+	PBIT16(p, nm);			p += 2;
+	memcpy(p, m->name, nm);		p += nm;
+	assert(p - buf == sz);
+	ret = sz;
+Out:
+	runlock(&fs->userlk);
+	return ret;	
 }
 
 int
@@ -286,22 +328,6 @@
 	bp.hash = GBIT64(p);	p += 8;
 	bp.gen = GBIT64(p);
 	return bp;
-}
-
-char*
-packdkey(char *p, int sz, vlong up, char *name)
-{
-	char *ep;
-	int err;
-
-	err = 0;
-	ep = p + sz;
-	p = pack8(&err, p, ep, Kent);
-	p = pack64(&err, p, ep, up);
-	p = packstr(&err, p, ep, name);
-	if(err)
-		return 0;
-	return p;
 }
 
 Tree*
--- a/ream.c
+++ b/ream.c
@@ -13,19 +13,19 @@
 {
 	char buf[512];
 	Kvp kv;
-	Dir d;
+	Xdir d;
 
 	/* nb: values must be inserted in key order */
 	memset(&d, 0, sizeof(Dir));
 	d.qid = (Qid){fs->nextqid++, 0, QTDIR};
-	d.mode = 0755;
+	d.mode = DMDIR|0755;
 	d.atime = 0;
 	d.mtime = 0;
 	d.length = 0;
 	d.name = "";
-	d.uid = "glenda";
-	d.gid = "glenda";
-	d.muid = "glenda";
+	d.uid = 2;
+	d.gid = 2;
+	d.muid = 2;
 	if(dir2kv(-1, &d, &kv, buf, sizeof(buf)) == -1)
 		sysfatal("ream: pack root: %r");
 	setval(r, 0, &kv);
@@ -101,7 +101,7 @@
 	b->bp.addr = addr;
 	b->logsz = 32;
 	b->data = b->buf + Hdrsz;
-	setflag(b, Bdirty);
+	b->flag |= Bdirty;
 
 	p = b->data+Loghdsz;
 	PBIT64(p+ 0, addr|LogFree);		/* addr */
--- a/tree.c
+++ b/tree.c
@@ -314,9 +314,9 @@
 void
 statupdate(Kvp *kv, Msg *m)
 {
-	int op, err;
-	char *p, *e;
-	Dir d;
+	int op;
+	char *p;
+	Xdir d;
 
 	p = m->v;
 	op = *p++;
@@ -339,26 +339,25 @@
 		d.atime = GBIT64(p);
 		p += 8;
 	}
+	if(op & Owuid){
+		d.uid = GBIT32(p);
+		p += 4;
+	}
+	if(op & Owgid){
+		d.gid = GBIT32(p);
+		p += 4;
+	}
+	if(op & Owmuid){
+		d.muid = GBIT32(p);
+		p += 4;
+	}
 	if(p != m->v + m->nv){
 		fprint(2, "kv=%P, m=%M\n", kv, m);
 		fprint(2, "malformed stat message (op=%x, len=%lld, sz=%d)\n", op, p - m->v, m->nv);
 		abort();
 	}
-	err = 0;
-	p = kv->v;
-	e = kv->v + kv->nv;
-	p = pack64(&err, p, e, d.qid.path);
-	p = pack32(&err, p, e, d.qid.vers);
-	p = pack8(&err, p, e, d.qid.type);
-	p = pack32(&err, p, e, d.mode);
-	p = pack64(&err, p, e, (vlong)d.atime*Nsec);
-	p = pack64(&err, p, e, (vlong)d.mtime*Nsec);
-	p = pack64(&err, p, e, d.length);
-	p = packstr(&err, p, e, d.uid);
-	p = packstr(&err, p, e, d.gid);
-	p = packstr(&err, p, e, d.muid);
-	if(err){
-		fprint(2, "wstat not fixed size(%llx != %d)\n", p - kv->v, kv->nv);
+	if(packdval(kv->v, kv->nv, &d) == nil){
+		fprint(2, "repacking dir failed");
 		abort();
 	}
 }
--- a/user.c
+++ b/user.c
@@ -14,7 +14,7 @@
 	"-1:adm::\n"
 	"0:none::\n"
 	"1:tor:tor:\n"
-	"1:glenda:glenda:\n"
+	"2:glenda:glenda:\n"
 	"10000:sys::\n"
 	"10001:map:map:\n"
 	"10002:doc::\n"
@@ -27,9 +27,9 @@
 {
 	char *p, kbuf[Keymax], rbuf[Kvmax];
 	int err;
+	Xdir d;
 	Kvp kv;
 	Key k;
-	Dir d;
 
 	err = 0;
 	if((p = packdkey(kbuf, sizeof(kbuf), up, name)) == nil)
@@ -112,24 +112,24 @@
 }
 
 User*
-name2user(User *users, int nusers, char *name)
+name2user(char *name)
 {
 	int i;
 
-	for(i = 0; i < nusers; i++)
-		if(strcmp(users[i].name, name) == 0)
-			return &users[i];
+	for(i = 0; i < fs->nusers; i++)
+		if(strcmp(fs->users[i].name, name) == 0)
+			return &fs->users[i];
 	return nil;
 }
 
 User*
-uid2user(User *users, int nusers, int id)
+uid2user(int id)
 {
 	int i;
 
-	for(i = 0; i < nusers; i++)
-		if(users[i].id == id)
-			return &users[i];
+	for(i = 0; i < fs->nusers; i++)
+		if(fs->users[i].id == id)
+			return &fs->users[i];
 	return nil;
 }
 
@@ -193,7 +193,11 @@
 		if((f = getfield(&p, ':')) == nil)
 			return Esyntax;
 		if(f[0] != '\0'){
-			if((u = name2user(users, nusers, f)) == nil){
+			u = nil;
+			for(i = 0; i < nusers; i++)
+				if(strcmp(users[i].name, f) == 0)
+					u = &users[i];
+			if(u == nil){
 				fprint(fd, "/adm/users:%d: leader %s does not exist\n", lnum, f);
 				err = Enouser;
 				goto Error;
@@ -207,7 +211,11 @@
 		while((m = getfield(&f, ',')) != nil){
 			if(m[0] == '\0')
 				continue;
-			if((u = name2user(users, nusers, m)) == nil){
+			u = nil;
+			for(i = 0; i < nusers; i++)
+				if(strcmp(users[i].name, m) == 0)
+					u = &users[i];
+			if(u == nil){
 				fprint(fd, "/adm/users:%d: user %s does not exist\n", lnum, m);
 				err = Enouser;
 				goto Error;