code: mafs

Download patch

ref: 2c149cc04dbca3a1f4d5bf6b2a8917dfe7ebb92c
parent: 5ef5a68f7eddfd864d5a3fabcd240abf210cee7e
author: 9ferno <gophone2015@gmail.com>
date: Sat Nov 12 10:05:36 EST 2022

updated user processing

--- a/9p.c
+++ b/9p.c
@@ -38,18 +38,18 @@
 		return;
 	}
 
-	if((uid = lookupid(req->ifcall.uname, 0)) <= 0){
+	if((uid = lookupid(req->ifcall.uname)) <= 0){
 		respond(req, "no such user");
 		return;
 	}
 
-	dbuf = getbufchk(Broot, Breadonly, Tdentry, Qproot);
+	dbuf = getbufchk(Bdroot, Breadonly, Tdentry, Qproot);
 	if(dbuf == nil){
 		respond(req, "phase error");
 		return;
 	}
 	d = &dbuf->io->d;
-	req->fid->aux = newaux(Broot, uid);
+	req->fid->aux = newaux(Bdroot, uid);
 	req->fid->qid = (Qid){Qproot, d->qid.version, QTDIR};
 	putbuf(dbuf);
 	req->ofcall.qid = req->fid->qid;
@@ -107,16 +107,16 @@
 		dir->length = 0;
 	if(buf == nil){
 		dir->name = estrdup9p(d->name);
-		dir->uid = username(d->uid, 0, dir->uid);
-		dir->gid = username(d->gid, 0, dir->gid);
-		dir->muid = username(d->muid, 0, dir->muid);
+		dir->uid = username(d->uid, dir->uid);
+		dir->gid = username(d->gid, dir->gid);
+		dir->muid = username(d->muid, dir->muid);
 	}else{
 		memset(buf, 0, Namelen + 3 * Userlen);
 		strncpy(buf, d->name, Namelen - 1);
 		dir->name = buf;
-		dir->uid = username(d->uid, 0, buf + Namelen);
-		dir->gid = username(d->gid, 0, buf + Namelen + Userlen);
-		dir->muid = username(d->muid, 0, buf + Namelen + 2 * Userlen);
+		dir->uid = username(d->uid, buf + Namelen);
+		dir->gid = username(d->gid, buf + Namelen + Userlen);
+		dir->muid = username(d->muid, buf + Namelen + 2 * Userlen);
 	}
 }
 
@@ -229,7 +229,7 @@
 
 		/* TODO gid setting needs more fine tuning to align with stat(5) */
 		if(emptystr(req->d.gid) == 0 &&
-			(gid = lookupid(req->d.gid, 0)) != 0){
+			(gid = lookupid(req->d.gid)) != 0){
 			d->gid = gid;
 		}
 	}
--- a/TODO
+++ b/TODO
@@ -1,4 +1,5 @@
 test exclusive use file access
+streamline the error codes
 mafswrite():
 	add user and remove user messages
 	users format has changed as per this http://9p.io/wiki/plan9/Setting_up_Fossil/index.html
--- a/all.h
+++ b/all.h
@@ -172,7 +172,7 @@
 int	ingroup(s16 uid, s16 gid, int locked);
 void	initconfig(u64 bno);
 int	leadgroup(s16 uid, s16 gid);
-s16	lookupid(char *name, int locked);
+s16	lookupid(char *name);
 u64	min(u64 a, u64 b);
 int	mkqidcmp(Qid*, Dentry*);
 void	mkqid9p1(Qid9p1*, Qid*);
@@ -191,7 +191,7 @@
 int	strtouid(char*);
 int	strtouid1(char*);
 void	sync(void);
-char	*username(s16 uid, int locked, char *username);
+char	*username(s16 uid, char *username);
 void	usersinit(void);
 void	writeconfig(u64 bno);
 
--- a/dat.c
+++ b/dat.c
@@ -60,8 +60,10 @@
 	[Eqidmode]	"wstat -- qid.type/dir.mode mismatch",
 	[Eronly]	"file system read only",
 	[Ersc]	"it's russ's fault.  bug him.",
-	[Eshutdown]	"mafs shutting down",
-	[Esystem]	"mafs system error",
+	[Eshutdown]	"m[]afs shutting down",
+	[Esystem]	"m[a]fs system error",
 	[Etoolong]	"name too long",
+	[Einvusers]	"invalid users contents",
+	[Enousers]	"no users",
 	[Ewalk]		"walk -- too many (system wide)",
 };
--- a/dat.h
+++ b/dat.h
@@ -15,39 +15,16 @@
 	GiB	= KiB*MiB,	/* Gibibytes */
 	TiB	= KiB*GiB,	/* Tibibytes */
 
-	Nusers	= 32,	/* max. number of users */
+	Ngroups	= 32,	/* maximum number of groups an user can belong to */
 	None	= 0,	/* user ID for "none" */
 	Noworld	= 9999,	/* conventional id for "noworld" group */
 	Userlen = 32,
+
 	Nsec	= 1000ULL*1000*1000,
 	Usec	= 1000ULL*1000,
 	Msec	= 1000ULL,
 	MAXRPC	= 8192ULL,
 	Nbkp	= 2,
-
-	/* Qpnone is the Tag.path of free blocks/extents(Tfree),
-		and zero'ed out dentry blocks */
-	Qpnone = 0,
-	Qpmagic	= 1,	/* magic block Tag.qpath */
-	Qpconfig = 2,	/* /adm/config block Tag.qpath */
-	Qpsuper	= 3,	/* /adm/super block Tag.qpath */
-
-	Qpadm	= 5,	/* /adm */
-	Qpusers	= 6,	/* /adm/users */
-	Qpbkp	= 7,	/* /adm/bkp block Tag.qpath */
-	Qpconfig0 = 8,	/* /adm/bkp/config.0 block Tag.qpath */
-	Qpsuper0 = 9,	/* /adm/bkp/super.0 block Tag.qpath */
-	Qproot0	= 10,	/* /adm/bkp/root.0 block Tag.qpath */
-	Qpconfig1 = 11,	/* /adm/bkp/config.1 block Tag.qpath */
-	Qpsuper1 = 12,	/* /adm/bkp/super.1 block Tag.qpath */
-	Qproot1	= 13,	/* /adm/bkp/root.1 block Tag.qpath */
-	Qpctl = 14,		/* /adm/ctl */
-	Qpfrees = 15,	/* /adm/frees block Tag.qpath */
-	Nqidgen	= 64,
-	Qproot	= Nqidgen-1, /* /, so fscreate() can disallow any create's below */
-
-	/* check qpath for overflows - TODO, should be 2^64 */
-	Maxqpath = 72057594037927936ULL, /* 2^56 */
 };
 
 /*
@@ -64,24 +41,55 @@
 	Bmagicb	= 0,	/* block number of first block. Bmagic conflicts with bio.h */
 	Bconfig	= 1,	/* block number of config contents */
 	Bsuper	= 2,	/* block number of super contents */
-	Broot	= 3,	/* block number of root directory */
-	Badm	= 4,	/* block number of /adm directory */
-	Bdconfig = 5,	/* block number of /adm/config dentry */
-	Bdsuper	= 6,	/* block number of /adm/super dentry */
-	Bdusers	= 7,	/* block number of /adm/users file */
-	Busers	= 8,	/* block number of /adm/users file contents */
-	Bbkp	= 9,	/* block number of /adm/bkp directory */
-	Bdconfig0 = 10,	/* block number of /adm/bkp/config.0 dentry */
-	Bdsuper0 = 11,	/* block number of /adm/bkp/super.0 dentry */
-	Bdroot0	= 12,	/* block number of /adm/bkp/root.0 dentry */
-	Bdconfig1 = 13,	/* block number of /adm/bkp/config.1 dentry */
-	Bdsuper1 = 14,	/* block number of /adm/bkp/super.1 dentry */
-	Bdroot1	= 15,	/* block number of /adm/bkp/root.1 dentry */
-	Bdctl	= 16,	/* block number of /adm/ctl dentry, empty contents, virtual file */
-	Bdfrees = 17,	/* block number of /adm/frees dentry, text file of free extents */
-	Nbused, /* blocks used up by default */
+	Bdadm	= 3,	/* block number of /adm directory */
+	Bdconfig= 4,	/* block number of /adm/config dentry */
+	Bdsuper	= 5,	/* block number of /adm/super dentry */
+	Bdbkp	= 6,	/* block number of /adm/bkp directory */
+	Bdconfig0 = 7,	/* block number of /adm/bkp/config.0 dentry */
+	Bdsuper0  = 8,	/* block number of /adm/bkp/super.0 dentry */
+	Bdroot0	  = 9,	/* block number of /adm/bkp/root.0 dentry */
+	Bdconfig1 = 10,	/* block number of /adm/bkp/config.1 dentry */
+	Bdsuper1  = 11,	/* block number of /adm/bkp/super.1 dentry */
+	Bdroot1	= 12,	/* block number of /adm/bkp/root.1 dentry */
+	Bdusers	= 13,	/* block number of /adm/users/ dentry */
+	Bdusersinuse = 14,	/* block number of /adm/users/inuse dentry */
+	Busersinuse	= 15,	/* block number of /adm/users/inuse file contents */
+	Bdfrees = 16,	/* block number of /adm/frees dentry, text file of free extents */
+
+	Bdctl	= 17,	/* block number of /adm/ctl dentry, empty contents, virtual file */
+	Bdusersstaging = 18,/* block number of /adm/users/staging dentry */
+	Bdroot	= 19,	/* block number of root directory */
+	Nbused,			/* blocks used up by default */
 	Nminblocks = Nbused+(Nbkp*3), /* number of blocks used by the above and the backup blocks */
 
+	/* Qpnone is the Tag.path of free blocks/extents(Tfree),
+		and zero'ed out dentry blocks */
+	Qpnone		= 0,
+	Qpmagic		= 15,		/* magic block Tag.qpath */
+	Qpconfig	= Bconfig,	/* /adm/config block Tag.qpath */
+	Qpsuper		= Bsuper,	/* /adm/super block Tag.qpath */
+
+	Qpadm		= Bdadm,	/* /adm */
+	Qpbkp		= Bdbkp,	/* /adm/bkp block Tag.qpath */
+	Qpconfig0	= Bdconfig0,/* /adm/bkp/config.0 block Tag.qpath */
+	Qpsuper0	= Bdsuper0,	/* /adm/bkp/super.0 block Tag.qpath */
+	Qproot0		= Bdroot0,	/* /adm/bkp/root.0 block Tag.qpath */
+	Qpconfig1	= Bdconfig1,/* /adm/bkp/config.1 block Tag.qpath */
+	Qpsuper1	= Bdsuper1,	/* /adm/bkp/super.1 block Tag.qpath */
+	Qproot1		= Bdroot1,	/* /adm/bkp/root.1 block Tag.qpath */
+	Qpusers		= Bdusers,	/* /adm/users block Tag.qpath */
+	Qpusersinuse	= Bdusersinuse,	/* /adm/users/inuse block Tag.qpath */
+	Qpfrees		= Bdfrees,	/* /adm/frees block Tag.qpath */
+
+	Qpctl		= Bdctl,	/* /adm/ctl */
+	Qpusersstaging	= Bdusersstaging,/* /adm/users/staging block Tag.qpath */
+	Qproot		= Bdroot,	/* /, so fscreate() can disallow any create's below */
+
+	Nqidgen	= 64,
+
+	/* check qpath for overflows - TODO, should be 2^64 */
+	Maxqpath = 72057594037927936ULL, /* 2^56 */
+
 	Nthdirty = 9,	/* dirty byte is the 9th byte in the Tag */
 };
 
@@ -214,14 +222,18 @@
 	/* u64 lastreadahead; TODO */
 };
 
+/*
+	every user is also a group with one user.
+ */
 struct User
 {
 	s16 id;		/* user id */
-	s16 lid;		/* id of the leader of group */
-	s16 members[Nusers]; /* group table, member id's */
-	int nmembers;		/* number of group entries */
+	s16 lid;	/* id of the leader of group */
 	char name[Userlen];	/* user name */
-	User *next;
+	s16 *members;/* list of member id's belonging to this group */
+	int nmembers;/* number of members in this group */
+	int nalloc;	/* allocated space members */
+	User *prev, *next;	/* linked list sorted by id */
 };
 
 struct Bkp
@@ -245,6 +257,7 @@
 	Nworks = 1024,	/* make this a parameter? */
 	Nprocs = 32,	/* make this a parameter? */
 	Nlru  = 32,
+	Nuserserrmsg = 64,
 };
 
 /*
@@ -296,7 +309,8 @@
 	Eshutdown,
 	Esystem,
 	Etoolong,
-	Etoou64,
+	Einvusers,
+	Enousers,
 	Ewalk,
 
 	MAXERR
@@ -364,3 +378,4 @@
 extern	char	*procname;
 
 extern Config config;
+extern char userserrmsg[Nuserserrmsg];
--- a/docs/mafs.ms
+++ b/docs/mafs.ms
@@ -1509,25 +1509,42 @@
 	ramfs -m /n/ramfs
 	touch /n/ramfs/file
 	cat /dev/zero | tput -p > /n/ramfs/file
-	172.49 MB/s
-	174.56 MB/s
-	163.50 MB/s
-	125.00 MB/s
-	102.99 MB/s
-	87.81 MB/s
-	77.78 MB/s
-	69.50 MB/s
-	63.71 MB/s
-	58.65 MB/s
-	54.72 MB/s
+		196.00 MB/s
+		198.76 MB/s
+		187.58 MB/s
+		176.96 MB/s
+		175.87 MB/s
+		180.42 MB/s
+		183.52 MB/s
+		185.99 MB/s
+		187.96 MB/s
+		189.54 MB/s
+		190.83 MB/s
+		191.89 MB/s
+		192.80 MB/s
 	dd -if /dev/zero -of /n/ramfs/file -count 700 -bs 1m
 
 	disk/mfs -r mfs_ramfs_file /n/ramfs/file
 	mount -c /srv/mfs_ramfs_file /n/mfs_ramfs_file
 	cat /dev/zero | tput -p > /n/mfs_ramfs_file/zeros.file
-	6.26 MB/s
-	5.99 MB/s
-	5.90 MB/s
+		7.51 MB/s
+		7.24 MB/s
+		7.15 MB/s
+		7.11 MB/s
+		7.07 MB/s
+		7.06 MB/s
+		7.04 MB/s
+		7.03 MB/s
+		7.02 MB/s
+		7.01 MB/s
+		7.01 MB/s
+		7.00 MB/s
+		7.00 MB/s
+		6.99 MB/s
+		6.99 MB/s
+		6.98 MB/s
+		6.95 MB/s
+		6.86 MB/s
 	echo halt >> /n/mfs_ramfs_file/adm/ctl; lc /srv
 	unmount /n/mfs_ramfs
 
@@ -1534,10 +1551,18 @@
 	disk/mafs -r mafs_ramafs_file /n/ramfs/file
 	mount -c /srv/mafs_ramafs_file /n/mafs_ramafs_file
 	cat /dev/zero | tput -p > /n/mafs_ramafs_file/zeros.file # throttles down to mfs speed
-	45.49 MB/s
-	31.52 MB/s
-	23.16 MB/s
-	24.54 MB/s
+		33.12 MB/s
+		33.17 MB/s
+		33.08 MB/s
+		32.34 MB/s
+		26.87 MB/s
+		23.22 MB/s
+		20.63 MB/s
+		18.67 MB/s
+		17.16 MB/s
+		15.94 MB/s
+		14.94 MB/s
+		14.11 MB/s
 	echo halt >> /n/mafs_ramafs_file/adm/ctl; lc /srv
 	unmount /n/ramfs
 .fi
--- a/find.c
+++ b/find.c
@@ -64,7 +64,7 @@
 	if(debug)
 		print("%s %llud bytes %llud blocks\n", devfile, size, size/Rawblocksize);
 
-	walkdentry(Broot, 0);
+	walkdentry(Bdroot, 0);
 	exits(0);
 }
 
--- a/sub.c
+++ b/sub.c
@@ -234,16 +234,18 @@
 {
 	Iobuf *b;
 	Dentry *d;
-	char users[] = "-1:adm:adm:\n"
+	char users[128+Userlen*3];
+	char *user;
+
+	user = getuser();
+	snprint(users, 128+Userlen*3, "-1:adm:adm:%s\n"
 					"0:none:adm:\n"		/* user ID for "none" */
 					"9999:noworld::\n"	/* conventional id for "noworld" group */
 					"10000:sys::\n"
-					"10001:upas:upas:\n"
-					"10002:bootes:bootes:\n"
-					"10006:glenda:glenda:\n"
-					"10007:manies::\n";
+					"10001:upas:upas:\n"	/* what is this for? */
+					"10006:%s:%s:\n", user, user, user);
 
-	b = getbuf(Badm, Bwritable, Bfreshalloc);
+	b = getbuf(Bdadm, Bwritable, Bfreshalloc);
 	memset(b->io, 0, Rawblocksize);
 	settag(b, Tdentry, Qpadm);
 	d = &b->io->d;
@@ -256,17 +258,17 @@
 	d->qid.path = Qpadm;
 	d->qid.version = 0;
 	d->mtime = nsec();
-	d->pdblkno = Broot;
+	d->pdblkno = Bdroot;
 	d->pqpath = Qproot;
-	d->dblocks[0] = Bbkp;
+	d->dblocks[0] = Bdbkp;
 	d->dblocks[1] = Bdconfig;
-	d->dblocks[2] = Bdctl;
-	d->dblocks[3] = Bdsuper;
-	d->dblocks[4] = Bdusers;
-	d->dblocks[5] = Bdfrees;
+	d->dblocks[2] = Bdsuper;
+	d->dblocks[3] = Bdusers;
+	d->dblocks[4] = Bdfrees;
+	d->dblocks[5] = Bdctl;
 	putbuf(b);
 
-	b = getbuf(Bbkp, Bwritable, Bfreshalloc);
+	b = getbuf(Bdbkp, Bwritable, Bfreshalloc);
 	memset(b->io, 0, Rawblocksize);
 	settag(b, Tdentry, Qpbkp);
 	d = &b->io->d;
@@ -280,7 +282,7 @@
 	d->qid.version = 0;
 	d->mtime = nsec();
 	d->size = strlen(users)+1;
-	d->pdblkno = Badm;
+	d->pdblkno = Bdadm;
 	d->pqpath = Qpadm;
 	d->dblocks[0] = Bdconfig0;
 	d->dblocks[1] = Bdsuper0;
@@ -290,32 +292,55 @@
 	d->dblocks[5] = Bdroot1;
 	putbuf(b);
 
-	reamfile(Bdconfig, Qpconfig, "config", 0, Badm, Qpadm, Bconfig);
-	reamfile(Bdctl, Qpctl, "ctl", 0, Badm, Qpadm, 0);
-	reamfile(Bdsuper, Qpsuper, "super", 0, Badm, Qpadm, Bsuper);
-	reamfile(Bdusers, Qpusers, "users",
-			 strlen(users)+1, Badm, Qpadm, Busers);
-	reamfile(Bdfrees, Qpfrees, "frees", 0, Badm, Qpadm, 0);
+	b = getbuf(Bdusers, Bwritable, Bfreshalloc);
+	memset(b->io, 0, Rawblocksize);
+	settag(b, Tdentry, Qpusers);
+	d = &b->io->d;
+	strncpy(d->name, "users", 4);
+	d->uid = d->muid = d->gid = -1;
+	d->mode = DMDIR |
+		((DMREAD|DMWRITE|DMEXEC) << 6) |
+		((DMREAD|DMWRITE|DMEXEC) << 3) |
+		((DMREAD|DMWRITE|DMEXEC) << 0);
+	d->qid.path = Qpusers;
+	d->qid.version = 0;
+	d->mtime = nsec();
+	d->size = strlen(users)+1;
+	d->pdblkno = Bdadm;
+	d->pqpath = Qpadm;
+	d->dblocks[0] = Bdusersinuse;
+	d->dblocks[1] = Bdusersstaging;
+	putbuf(b);
 
+	reamfile(Bdconfig, Qpconfig, "config", 0, Bdadm, Qpadm, Bconfig);
+	reamfile(Bdctl, Qpctl, "ctl", 0, Bdadm, Qpadm, 0);
+	reamfile(Bdsuper, Qpsuper, "super", 0, Bdadm, Qpadm, Bsuper);
+	reamfile(Bdfrees, Qpfrees, "frees", 0, Bdadm, Qpadm, 0);
+
 	reamfile(Bdconfig0, Qpconfig0, "config.0",
-			 Blocksize, Bbkp, Qpbkp, config.config.dest[0]);
+			 Blocksize, Bdbkp, Qpbkp, config.config.dest[0]);
 	reamfile(Bdsuper0, Qpsuper0, "super.0",
-			 Blocksize, Bbkp, Qpbkp, config.super.dest[0]);
+			 Blocksize, Bdbkp, Qpbkp, config.super.dest[0]);
 	reamfile(Bdroot0, Qproot0, "root.0",
-			 Blocksize, Bbkp, Qpbkp, config.root.dest[0]);
+			 Blocksize, Bdbkp, Qpbkp, config.root.dest[0]);
 
 	reamfile(Bdconfig1, Qpconfig1, "config.1",
-			 Blocksize, Bbkp, Qpbkp, config.config.dest[1]);
+			 Blocksize, Bdbkp, Qpbkp, config.config.dest[1]);
 	reamfile(Bdsuper1, Qpsuper1, "super.1",
-			 Blocksize, Bbkp, Qpbkp, config.super.dest[1]);
+			 Blocksize, Bdbkp, Qpbkp, config.super.dest[1]);
 	reamfile(Bdroot1, Qproot1, "root.1",
-			 Blocksize, Bbkp, Qpbkp, config.root.dest[1]);
+			 Blocksize, Bdbkp, Qpbkp, config.root.dest[1]);
 
-	b = getbuf(Busers, Bwritable, Bfreshalloc);
+	reamfile(Bdusersinuse, Qpusersinuse, "inuse",
+			 Blocksize, Bdusers, Qpusers, Busersinuse);
+	reamfile(Bdusersstaging, Qpusersstaging, "staging",
+			 Blocksize, Bdusers, Qpusers, 0);
+
+	b = getbuf(Busersinuse, Bwritable, Bfreshalloc);
 	if(b == nil)
 		panic("cannot get Busers");
 	memset(b->io, 0, Rawblocksize);
-	settag(b, Tdata, Qpusers);
+	settag(b, Tdata, Qpusersinuse);
 	strcpy((char*)b->io->buf, users);
 	putbuf(b);
 }
@@ -326,7 +351,7 @@
 	Iobuf *b;
 	Dentry *d;
 
-	b = getbuf(Broot, Bwritable, Bfreshalloc);
+	b = getbuf(Bdroot, Bwritable, Bfreshalloc);
 	if(b == nil)
 		panic("rootream b == nil");
 	memset(b->io, 0, Rawblocksize);
@@ -343,7 +368,7 @@
 	d->qid.path = Qproot;
 	d->qid.version = 0;
 	d->mtime = nsec();
-	d->dblocks[0] = Badm;
+	d->dblocks[0] = Bdadm;
 	putbuf(b);
 }
 
@@ -372,8 +397,8 @@
 	config.config.dest[1] = nbused+((nblocks-nbused)/2)-Bconfig;
 	config.super.dest[0] = nblocks-Bsuper;
 	config.super.dest[1] = nbused+((nblocks-nbused)/2)-Bsuper;
-	config.root.dest[0] = nblocks-Broot;
-	config.root.dest[1] = nbused+((nblocks-nbused)/2)-Broot;
+	config.root.dest[0] = nblocks-3;
+	config.root.dest[1] = nbused+((nblocks-nbused)/2)-3;
 	for(i=0; i<Nbkp; i++){
 		reamdata(config.config.dest[i], Qpconfig0+3*i);
 		reamdata(config.super.dest[i], Qpsuper0+3*i);
@@ -403,7 +428,7 @@
 	/* this will enable backups */
 	config.config.srcbno = Bconfig;
 	config.super.srcbno = Bsuper;
-	config.root.srcbno = Broot;
+	config.root.srcbno = Bdroot;
 	strncpy(config.service, service, Namelen);
 
 	fsok(0);
--- a/used.c
+++ b/used.c
@@ -52,7 +52,7 @@
 
 	initextents(&useds);
 	checkblock(Bmagicb, Tmagic, Qpmagic);
-	walkdentry(Broot);
+	walkdentry(Bdroot);
 	close(devfd);
 	showextents(1, "", &useds);
 	exits(0);
--- a/user.c
+++ b/user.c
@@ -1,8 +1,8 @@
 #include "all.h"
 
-static User	*users;
-static u16	nusers;
+static User	*lruuser;	/* least recently used user */
 static RWLock userslock;
+char userserrmsg[Nuserserrmsg];
 
 int
 checkname9p2(char* name)
@@ -38,37 +38,33 @@
 	return 0;
 }
 
-/* t should be rlock'ed */
 User *
-lookupuser(char *name, int locked)
+leastid(User *us)
 {
 	User *u;
 
-	if(locked == 0)
-		rlock(&userslock);
-	for(u = users; u != nil; u = u->next)
-		if(strcmp(u->name, name) == 0)
-			return u;
-	if(locked == 0)
-		chkrunlock(&userslock);
-	return nil;
+	if(us == nil)
+		return nil;
+	for(u = us; u->prev != nil; u = u->prev)
+		;
+	return u;
 }
 
 s16
-lookupid(char *name, int locked)
+lookupid(char *name)
 {
 	User *u;
 	s16 id;
 
-	if(locked == 0)
-		rlock(&userslock);
-	u = lookupuser(name, 1);
+	rlock(&userslock);
+	for(u = leastid(lruuser); u != nil; u = u->next)
+		if(strcmp(u->name, name) == 0)
+			break;
 	if(u == nil)
 		id = 0;
 	else
 		id = u->id;
-	if(locked == 0)
-		chkrunlock(&userslock);
+	chkrunlock(&userslock);
 	return id;
 }
 
@@ -77,15 +73,14 @@
 	Hence, always call this with an rlock(&userslock)
  */
 char *
-username(s16 uid, int locked, char *username)
+username(s16 uid, char *username)
 {
 	User *u;
 	char *ret;
 
 	ret = nil;
-	if(locked == 0)
-		rlock(&userslock);
-	for(u = users; u != nil; u = u->next){
+	rlock(&userslock);
+	for(u = leastid(lruuser); u != nil; u = u->next){
 		if(u->id == uid){
 			if(username == nil)
 				username = emalloc9p(Userlen); /* potential for a leak */
@@ -94,8 +89,7 @@
 			break;
 		}
 	}
-	if(locked == 0)
-		chkrunlock(&userslock);
+	chkrunlock(&userslock);
 	return ret;
 }
 
@@ -112,7 +106,7 @@
 		found = 1;
 		goto Found;
 	}
-	for(u = users; u != nil; u = u->next){
+	for(u = leastid(lruuser); u != nil; u = u->next){
 		if(u->id == gid){
 			for(i = 0; i < u->nmembers; i++){
 				if(u->members[i] == uid){
@@ -139,7 +133,7 @@
 	if(uid == gid)
 		return 1;
 	rlock(&userslock);
-	for(u = users; u != nil; u = u->next)
+	for(u = leastid(lruuser); u != nil; u = u->next)
 		if(u->id == gid){
 			if(u->lid == uid){
 				ret = 1;
@@ -158,20 +152,116 @@
 	return ret;
 }
 
-/* should be called with a locked t */
 void
-syncusers(Tree *)
+freeusers(User *us)
 {
-	/* TODO */
+	User *u, *tmp;
+
+	if(us == nil)
+		return;
+	u = leastid(us);
+	while(u != nil){
+		if(u->members != nil)
+			free(u->members);
+		tmp = u;
+		u = u->next;
+		free(tmp);
+	}
 }
 
+/* used by the parseusers(). No locking needed. */
+s16
+lookforid(User *us, char *name)
+{
+	User *u;
+	s16 id;
+
+	for(u = leastid(us); u != nil; u = u->next)
+		if(strcmp(u->name, name) == 0)
+			break;
+	if(u == nil)
+		id = 0;
+	else
+		id = u->id;
+	return id;
+}
+
+User *
+lookforuser(User *us, s16 uid)
+{
+	User *u;
+
+	for(u = leastid(us); u != nil; u = u->next)
+		if(u->id == uid)
+			return u;
+	return nil;
+}
+
+char *
+lookforname(User *us, s16 uid)
+{
+	User *u;
+
+	for(u = leastid(us); u != nil; u = u->next)
+		if(u->id == uid)
+			return u->name;
+	return nil;
+}
+
+int
+adduser(User *us, int id, char *name, User **up)
+{
+	User *u, *prevu, *new;
+
+	if(name == nil)
+		return snprint(userserrmsg, Nuserserrmsg-1, "invalid name");
+	if(baduname(name))
+		return snprint(userserrmsg, Nuserserrmsg-1, "invalid name");
+
+	for(u = leastid(us); u != nil; u = u->next){
+		if(u->id == id){
+			return snprint(userserrmsg, Nuserserrmsg-1,
+					"mafs: duplicate user ID %d (name %q)\n", id, u->name);
+		}else if(strcmp(u->name, name) == 0){
+			return snprint(userserrmsg, Nuserserrmsg-1,
+					"mafs: duplicate user name %q (id %d)\n", name, u->id);
+		}
+	}
+
+	new = malloc(sizeof(User));
+	new->id = id;
+	new->nmembers = 0;
+	strncpy(new->name, name, Userlen);
+
+	*up = new;
+	if(us == nil)
+		return 0;
+	for(u = leastid(us); u != nil && u->id < id; u = u->next){
+		prevu = u;
+	}
+	/* at the top/last */
+	if(u == nil){
+		prevu->next = new;
+		new->prev = prevu;
+		return 0;
+	}
+	if(u->prev != nil)
+		u->prev->next = new;
+	new->next = u;
+	new->prev = u->prev;
+	u->prev = new;
+	return 0;
+}
+
 void
-addmember(User *u, char *memname)
+addmember(User *us, User *u, char *memname)
 {
 	s16 x;
 	int i;
 
-	x = lookupid(memname, 1);
+	if(us == nil || u == nil || memname == nil)
+		return;
+	x = lookforid(us, memname);
 	if(x > 0){
 		/* check for duplicate members */
 		for(i=0; i<u->nmembers; i++)
@@ -178,129 +268,205 @@
 			if(x == u->members[i])
 				return;
 		if(i == u->nmembers){
+			if(u->members == nil){
+				u->nalloc = 1;
+				u->members = emalloc9p(u->nalloc*sizeof(s16));
+			}else if(u->nmembers == u->nalloc){
+				u->nalloc++;
+				u->members = erealloc9p(u->members, u->nalloc*sizeof(s16));
+			}
 			u->members[u->nmembers++] = x;
 		}
 	}
 }
 
-void
-adduser(int id, char *name, char *leader, char *members)
+int
+addmembers(User *us, User *u, char *members)
 {
 	char *p, *ep;
-	User *u;
 	char memname[Userlen];
 
-	if(name == nil)
-		panic("invalid name");
-	if(baduname(name))
-		panic("invalid name");
+	if(u == nil)
+		return snprint(userserrmsg, Nuserserrmsg-1, "invalid user");
 
-	for(u = users; u != nil; u = u->next){
-		if(u->id == id){
-			dprint("mafs: duplicate user ID %d (name %q)\n", id, u->name);
-			panic("mafs: duplicate user ID");
-			return;
-		}else if(strcmp(u->name, name) == 0){
-			dprint("mafs: duplicate user name %q (id %d)\n", name, u->id);
-			panic("mafs: duplicate user name");
-			return;
-		}
-	}
-
-	u = malloc(sizeof(User));
-	u->id = id;
-	u->nmembers = 0;
-	strcpy(u->name, name);
-	u->next = users;
-	users = u;
-	nusers++;
-
-	if(leader == nil || leader[0] == '\0' || baduname(leader))
-		u->lid = 0;
-	else if(strcmp(name, leader) == 0)
-		u->lid = id;
-	else
-		u->lid = lookupid(leader, 1);
-
 	p = members;
 	while((ep = strchr(p, ',')) != nil){
 		if(ep-p >= Userlen)
-			panic("invalid name");
+			return snprint(userserrmsg, Nuserserrmsg-1, "memname too long");
 		else if(ep-p > 0){
 			*ep = '\0';
 			strncpy(memname, p, ep-p);
 			memname[ep-p] = '\0';
-			if(memname != nil && baduname(memname) == 0)
-				addmember(u, memname);
+			if(memname != nil){
+				 if(baduname(memname))
+					return snprint(userserrmsg, Nuserserrmsg-1, "bad memname");
+				addmember(us, u, memname);
+			}
 		}
 		p = ep+1;
 	}
-	if(p != nil && p[0] != '\0' && baduname(p) == 0)
-		addmember(u, p);
+	if(p != nil && p[0] != '\0'){
+		if(baduname(p))
+			return snprint(userserrmsg, Nuserserrmsg-1, "last memname bad");
+		addmember(us, u, p);
+	}
+	return 0;
 }
 
-void
-parseusers(char *raw)
+int
+addleader(User *us, User *u, char *leader)
 {
-	char *p, *ep;
+	if(u == nil)
+		return snprint(userserrmsg, Nuserserrmsg-1, "invalid user");
+
+	if(leader == nil || leader[0] == '\0' || baduname(leader))
+		u->lid = 0;
+	else if(strcmp(u->name, leader) == 0)
+		u->lid = u->id;
+	else
+		u->lid = lookforid(us, leader);
+	return 0;
+}
+
+/*
+	(uid:name:leader:member1,member2,...\n)+
+	first parse are the names, then, parse again
+	to add the leader and members to the group.
+ */
+s32
+parseusers(User **usp, s8 *raw, u64 usize)
+{
+	char *p, *ep, *line;
 	char *flds[5];
-	User *u;
+	User *u, *us;
 	s32 i;
-	char name[Userlen];
+	s32 rv;
 
+	u = us = nil;
 	p = raw;
-	/* (uid:name:leader:member1,member2,...\n)+ */
-	while((ep = strchr(p, '\n')) != nil){
-		if(ep-p > 502){
-			p[100] = '\0';
-			panic("name line %s too long\n", p);
-		}else if(ep-p > 0){
-			*ep = '\0';
-			if(getfields(p, flds, 4, 0, ":") != 4){
+	while(p-raw <= usize && (ep = strchr(p, '\n')) != nil){
+		/* skip blank lines or empty lines of :::*/
+		if(ep-p > 4){
+			line = emalloc9p(ep-p+1);
+			memcpy(line, p, ep-p);
+			if(getfields(line, flds, 4, 0, ":") != 4){
+				free(line);
 				dprint("invalid user data");
-				goto Next;
+				freeusers(us);
+				return Einvusers;
 			}
-			wlock(&userslock);
-			adduser(atoi(flds[0]), flds[1], flds[2], flds[3]);
-			chkwunlock(&userslock);
+			if((rv = adduser(us, atoi(flds[0]), flds[1], &u)) != 0){
+				free(line);
+				dprint("invalid adduser");
+				freeusers(us);
+				return rv;
+			
+			}
+			if(us == nil && u != nil)
+				*usp = us = u;
+			free(line);
 		}
-Next:
 		p = ep+1;
 	}
 
-	if(chatty9p > 2)
-	for(u = users; u != nil; u = u->next){
-		dprint("%d:%s:%d(%s):", u->id, u->name, u->lid, username(u->lid, 1, name));
-		for(i = 0; i<u->nmembers; i++)
-			dprint("	%d(%s)", u->members[i], username(u->members[i], 1, name));
-		dprint("\n");
+	if(us == nil)
+		return Enousers;
+
+	p = raw;
+	while((ep = strchr(p, '\n')) != nil){
+		/* skip blank lines or empty lines of ::::*/
+		if(ep-p > 4){
+			line = emalloc9p(ep-p+1);
+			memcpy(line, p, ep-p);
+			if(getfields(line, flds, 4, 0, ":") != 4){
+				free(line);
+				dprint("invalid user data");
+				freeusers(us);
+				return Einvusers;
+			}
+			u = lookforuser(us, atoi(flds[0]));
+			if((rv = addleader(us, u, flds[2])) != 0){
+				free(line);
+				dprint("invalid addmembers");
+				freeusers(us);
+				return rv;
+			
+			}
+			if((rv = addmembers(us, u, flds[3])) != 0){
+				free(line);
+				dprint("invalid addmembers %s\n", Nuserserrmsg);
+				freeusers(us);
+				return rv;
+			
+			}
+			free(line);
+		}
+		p = ep+1;
 	}
+	if(chatty9p > 2)
+		for(u = leastid(us); u != nil; u = u->next){
+			dprint("%d:%s:%d(%s):", u->id, u->name, u->lid, lookforname(us, u->lid));
+			for(i = 0; i<u->nmembers; i++)
+				dprint("	%d(%s)", u->members[i], lookforname(us, u->lid));
+			dprint("\n");
+		}
+	*usp = us;
+	return 0;
 }
 
+void
+usersinit(void)
+{
+	Iobuf *ubuf;
+	s8 *buf;
+	u64 usize;
+	User *us;
+	int rv;
+
+	ubuf = getbufchk(Bdusersinuse, Breadonly, Tdentry, Qpusersinuse);
+	usize = ubuf->io->d.size+1;
+	buf = emalloc9p(usize);
+	putbuf(ubuf);
+
+	readfile(Bdusersinuse, Qpusersinuse, buf, usize, 0);
+	if((rv=parseusers(&us, buf, usize)) != 0)
+		panic("could not parse /adm/users/inuse\n");
+	free(buf);
+	lruuser = us;
+}
+
 /*
-	TODO not bothering when the users file > 502 bytes.
-	Can add that stuff later
+	read from /adm/user/staging
+	check that the contents are valid
+		check that -1 is the first entry TODO
+		check that 0 is the next entry TODO
+	write the contents to /adm/users/inuse
+	lruuser points to these new contents
  */
 void
-usersinit(void)
+syncusers(void)
 {
-	Iobuf *ubuf, *ucbuf;
-	char rem[Blocksize*2];
-	Dentry *du;
+	Iobuf *ubuf;
+	s8 *buf;
+	u64 usize;
+	User *us, *old;
 
-	ubuf = getbufchk(Bdusers, Breadonly, Tdentry, Qpusers);
-	du = &ubuf->io->d;
-	/* for(i = 0; i < Ndblock; i++){ */
-		if(du->dblocks[0] == 0){
-			putbuf(ubuf);
-			return;
-		}
-		ucbuf = getbufchk(du->dblocks[0], Breadonly, Tdata, Qpusers);
-		/* fill(rem, ucbuf->iobuf); */
-		strncpy(rem, (char*)ucbuf->io->buf, Blocksize);
-		parseusers(rem);
-		putbuf(ucbuf);
-	/* } */
+	ubuf = getbufchk(Bdusersstaging, Breadonly, Tdentry, Qpusersstaging);
+	usize = ubuf->io->d.size+1;
+	buf = emalloc9p(usize);
 	putbuf(ubuf);
+
+	readfile(Bdusersstaging, Qpusersstaging, buf, usize, 0);
+	if(parseusers(&us, buf, usize) != 0)
+		panic("could not parse /adm/users/staging\n");
+
+	wlock(&userslock);
+	old = lruuser;
+	writefile(Bdusersinuse, Qpusersinuse, -1, buf, usize, 0);
+	lruuser = us;
+	wunlock(&userslock);
+
+	freeusers(old);
+	free(buf);
 }