code: plan9front

Download patch

ref: e483c61242937b4258f60508292467050b56f5e9
parent: 87fa1a78e323cd68189519e3413403f4423491a4
author: Jacob Moody <moody@posixcafe.org>
date: Fri Dec 2 02:16:10 EST 2022

kernel: private srv boards

--- a/sys/man/3/srv
+++ b/sys/man/3/srv
@@ -5,6 +5,8 @@
 .nf
 .B bind #s /srv
 
+.BI #s/ clone
+.BI #s/ n
 .BI #s/ service1
 .BI #s/ service2
  ...
@@ -12,7 +14,7 @@
 .SH DESCRIPTION
 The
 .I srv
-device provides a one-level directory holding
+device provides a tree of directories holding
 already-open channels to services.
 In effect,
 .I srv
@@ -40,6 +42,18 @@
 .PP
 It is an error to write more than one number into a server file,
 or to create a file with a name that is already being used.
+.PP
+Opening the
+.I clone
+file allocates a new service directory. Reading
+.I clone
+returns the id of the new directory. This new service
+directory can then be accessed at
+.BR /srv/id .
+Directories are recursable; each new service directory
+contains its own
+.I clone
+file.
 .SH EXAMPLE
 To drop one end of a pipe into
 .BR /srv ,
--- a/sys/src/9/port/devsrv.c
+++ b/sys/src/9/port/devsrv.c
@@ -5,61 +5,209 @@
 #include	"fns.h"
 #include	"../port/error.h"
 
+#define SRVTYPE(x)	(((uvlong)x)&0x3)
+#define SRVPATH(x)	(((uvlong)x)>>2)
+#define SRVQID(x, t)	((((uvlong)x)<<2)|((t)&0x3))
 
-typedef struct Srv Srv;
-struct Srv
+typedef struct Link Link;
+struct Link
 {
-	char	*name;
+	void 	*link;
+	char 	*name;
+	ulong 	path;
 	char	*owner;
 	ulong	perm;
+};
+
+typedef struct Srv Srv;
+struct Srv
+{
+	Link;
 	Chan	*chan;
-	Srv	*link;
-	ulong	path;
 };
 
-static QLock	srvlk;
-static Srv	*srv;
-static int	qidpath;
+typedef struct Board Board;
+struct Board
+{
+	Link;
+	Ref;
 
-static Srv*
-srvlookup(char *name, ulong qidpath)
+	Board 	*parent;
+	Board 	*child;
+	Srv 	*srv;
+	int 	closed;	
+};
+
+enum{
+	Qsrv,
+	Qclone,
+	Qlease,
+};
+
+Board root;
+RWlock srvlk;
+ulong srvpath;
+
+static char Eexpired[] = "expired lease";
+
+static void*
+lookup(Link *l, char *name, ulong qidpath)
 {
-	Srv *sp;
+	Link *lp;
 
-	for(sp = srv; sp != nil; sp = sp->link) {
-		if(sp->path == qidpath || (name != nil && strcmp(sp->name, name) == 0))
-			return sp;
+	if(qidpath != ~0UL){
+		assert(SRVTYPE(qidpath) == Qsrv);
+		qidpath = SRVPATH(qidpath);
 	}
+	for(lp = l; lp != nil; lp = lp->link){
+		if(qidpath != ~0UL && lp->path == qidpath)
+			return lp;
+		if(name != nil && strcmp(lp->name, name) == 0)
+			return lp;
+	}
 	return nil;
 }
 
+static void*
+remove(Link **l, char *name, ulong qidpath)
+{
+	Link *lp;
+	Link **last;
+
+	if(qidpath != ~0UL){
+		assert(SRVTYPE(qidpath) == Qsrv);
+		qidpath = SRVPATH(qidpath);
+	}
+	last = l;
+	for(lp = *l; lp != nil; lp = lp->link){
+		if(qidpath != ~0UL && lp->path == qidpath)
+			break;
+		if(name != nil && strcmp(lp->name, name) == 0)
+			break;
+		last = &lp->link;
+	}
+	if(lp == nil)
+		return nil;
+
+	*last = lp->link;
+	lp->link = nil;
+	return lp;
+}
+
+static void
+boardclunk(Board *b, int close)
+{
+	Srv *sp, *prv;
+	Board *ch;
+	long ref;
+
+	if(b == &root)
+		return;
+
+	if(close){
+		assert(b->closed == 0);
+		b->closed++;
+		for(sp = b->srv; sp != nil; sp = prv){
+			prv = sp->link;
+			free(sp->owner);
+			free(sp->name);
+			if(sp->chan != nil)
+				cclose(sp->chan);
+			free(sp);
+		}
+		b->srv = nil;
+	}
+	ref = decref(b);
+
+	/*
+	 * All boards must be walkable from root. So a board
+	 * is allowed to sit at zero references as long as it
+	 * still has active children. For leaf nodes we then
+	 * have to walk up the tree to clear now empty parents.
+	 */
+	while(b->closed && b->child == nil && ref == 0){
+		//Root should never be closed
+		assert(b->parent != nil);
+		ch = remove((Link**)&b->parent->child, b->name, ~0UL);
+		assert(ch == b);
+
+		b = ch->parent;
+		free(ch->name);
+		free(ch->owner);
+		free(ch);
+	}
+}
+
 static int
 srvgen(Chan *c, char *name, Dirtab*, int, int s, Dir *dp)
 {
 	Srv *sp;
+	Board *b, *ch;
 	Qid q;
 
-	if(s == DEVDOTDOT){
-		devdir(c, c->qid, "#s", 0, eve, 0555, dp);
-		return 1;
+	if(name != nil && strlen(name) >= sizeof(up->genbuf))
+		return -1;
+
+	b = c->aux;
+	ch = nil;
+	mkqid(&q, ~0L, 0, QTFILE);
+	rlock(&srvlk);
+	if(waserror()){
+		runlock(&srvlk);
+		nexterror();
 	}
+	switch(s){
+	case -2: /* dot */
+		ch = b;
+		goto Child;
+	case DEVDOTDOT:
+		ch = b->parent;
+		if(ch == nil)
+			ch = &root;
+		goto Child;
+	}
+	if(name != nil){
+		if(strcmp("clone", name) == 0)
+			goto Clone;
 
-	qlock(&srvlk);
-	if(name != nil)
-		sp = srvlookup(name, -1);
-	else {
-		for(sp = srv; sp != nil && s > 0; sp = sp->link)
+		sp = lookup(b->srv, name, ~0UL);
+		if(sp == nil)
+			ch = lookup(b->child, name, ~0UL);
+	} else {
+		if(s == 0)
+			goto Clone;
+		s--;
+		for(sp = b->srv; sp != nil && s > 0; sp = sp->link)
 			s--;
+		for(ch = b->child; ch != nil && s > 0; ch = ch->link)
+			s--;
 	}
-	if(sp == nil || (name != nil && (strlen(sp->name) >= sizeof(up->genbuf)))) {
-		qunlock(&srvlk);
+	if(sp != nil){
+		kstrcpy(up->genbuf, sp->name, sizeof up->genbuf);
+		q.path = SRVQID(sp->path, Qsrv);
+		devdir(c, q, up->genbuf, 0, sp->owner, sp->perm, dp);
+	} else if(ch != nil){
+Child:
+		if(name != nil || s == DEVDOTDOT){
+			devpermcheck(ch->owner, ch->perm, OEXEC);
+			c->aux = ch;
+		}
+		kstrcpy(up->genbuf, ch->name, sizeof up->genbuf);
+		q.path = SRVQID(ch->path, Qsrv);
+		q.type = QTDIR;
+		devdir(c, q, up->genbuf, 0, ch->owner, ch->perm|DMDIR, dp);
+	} else if(0){
+Clone:
+		q.path = SRVQID(SRVPATH(c->qid.path), Qclone);
+		devdir(c, q, "clone", 0, eve, 0444, dp);
+	} else {
+		runlock(&srvlk);
+		poperror();
 		return -1;
 	}
-	mkqid(&q, sp->path, 0, QTFILE);
-	/* make sure name string continues to exist after we release lock */
-	kstrcpy(up->genbuf, sp->name, sizeof up->genbuf);
-	devdir(c, q, up->genbuf, 0, sp->owner, sp->perm, dp);
-	qunlock(&srvlk);
+
+	runlock(&srvlk);
+	poperror();
 	return 1;
 }
 
@@ -66,24 +214,55 @@
 static void
 srvinit(void)
 {
-	qidpath = 1;
+	srvpath = 0;
+	root.path = srvpath++;
+	root.name = "#s";
+	root.perm = 0777;
+	kstrdup(&root.owner, eve);
 }
 
 static Chan*
 srvattach(char *spec)
 {
-	return devattach('s', spec);
+	Chan *c;
+
+	c = devattach('s', spec);
+	c->aux = &root;
+	return c;
 }
 
 static Walkqid*
 srvwalk(Chan *c, Chan *nc, char **name, int nname)
 {
-	return devwalk(c, nc, name, nname, 0, 0, srvgen);
+	Board *b;
+	Walkqid *wq;
+
+	wq = devwalk(c, nc, name, nname, 0, 0, srvgen);
+	if(wq == nil || wq->clone == nil)
+		return wq;
+
+	b = wq->clone->aux;
+	if(b == &root)
+		return wq;
+
+	incref(b);
+	return wq;
 }
 
 static int
 srvstat(Chan *c, uchar *db, int n)
 {
+	Dir d;
+
+	/* devstat cheats for dir stats, we care about our dir perms */
+	if(c->qid.type == QTDIR){
+		srvgen(c, nil, nil, 0, -2, &d);
+		n = convD2M(&d, db, n);
+		if(n == 0)
+			error(Ebadarg);
+		return n;
+	}
+
 	return devstat(c, db, n, 0, 0, srvgen);
 }
 
@@ -90,12 +269,14 @@
 char*
 srvname(Chan *c)
 {
+	Board *b;
 	Srv *sp;
 	char *s;
 
 	s = nil;
-	qlock(&srvlk);
-	for(sp = srv; sp != nil; sp = sp->link) {
+	b = &root;
+	rlock(&srvlk);
+	for(sp = b->srv; sp != nil; sp = sp->link) {
 		if(sp->chan == c){
 			s = malloc(3+strlen(sp->name)+1);
 			if(s != nil)
@@ -103,7 +284,7 @@
 			break;
 		}
 	}
-	qunlock(&srvlk);
+	runlock(&srvlk);
 	return s;
 }
 
@@ -110,33 +291,74 @@
 static Chan*
 srvopen(Chan *c, int omode)
 {
+	Board *b, *ch;
 	Srv *sp;
 	Chan *nc;
+	char buf[64];
 
+	if(omode&OTRUNC)
+		error(Eexist);
+	if(omode&ORCLOSE)
+		error(Eperm);
+
+	b = c->aux;
+	if(SRVTYPE(c->qid.path) == Qclone){;
+		wlock(&srvlk);
+		if(waserror()){
+			wunlock(&srvlk);
+			nexterror();
+		}
+		if(b->closed)
+			error(Eexpired);
+
+		ch = smalloc(sizeof *ch);
+		ch->ref = 1;
+		ch->perm = 0770;
+		kstrdup(&ch->owner, up->user);
+		do {
+			ch->path = srvpath++;
+			snprint(buf, sizeof buf, "%ld", ch->path);
+		} while(lookup(b->srv, buf, ~0UL) != nil);
+
+		ch->parent = b;
+		kstrdup(&ch->name, buf);
+
+		ch->link = b->child;
+		b->child = ch;
+		c->aux = ch;
+		c->qid.path = SRVQID(ch->path, Qlease);
+		c->mode = openmode(omode);
+		boardclunk(b, 0);
+		wunlock(&srvlk);
+		poperror();
+		return c;
+	}
+
+	rlock(&srvlk);
+	if(waserror()){
+		runlock(&srvlk);
+		nexterror();
+	}
 	if(c->qid.type == QTDIR){
 		if(omode & ORCLOSE)
 			error(Eperm);
 		if(omode != OREAD)
 			error(Eisdir);
-		c->mode = omode;
+		devpermcheck(b->owner, b->perm, omode);
+		c->mode = openmode(omode);
 		c->flag |= COPEN;
 		c->offset = 0;
+		runlock(&srvlk);
+		poperror();
 		return c;
 	}
-	qlock(&srvlk);
-	if(waserror()){
-		qunlock(&srvlk);
-		nexterror();
-	}
+	if(b->closed)
+		error(Eexpired);
 
-	sp = srvlookup(nil, c->qid.path);
+	sp = lookup(b->srv, nil, c->qid.path);
 	if(sp == nil || sp->chan == nil)
 		error(Eshutdown);
 
-	if(omode&OTRUNC)
-		error(Eexist);
-	if(omode&ORCLOSE)
-		error(Eperm);
 	if(openmode(omode)!=sp->chan->mode && sp->chan->mode!=ORDWR)
 		error(Eperm);
 	devpermcheck(sp->owner, sp->perm, omode);
@@ -144,7 +366,7 @@
 	nc = sp->chan;
 	incref(nc);
 
-	qunlock(&srvlk);
+	runlock(&srvlk);
 	poperror();
 
 	cclose(c);
@@ -154,6 +376,7 @@
 static Chan*
 srvcreate(Chan *c, char *name, int omode, ulong perm)
 {
+	Board *b;
 	Srv *sp;
 
 	if(openmode(omode) != OWRITE)
@@ -162,31 +385,40 @@
 	if(strlen(name) >= sizeof(up->genbuf))
 		error(Etoolong);
 
+	if(strcmp("clone", name) == 0)
+		error("reserved name");
+
 	sp = smalloc(sizeof *sp);
 	kstrdup(&sp->name, name);
 	kstrdup(&sp->owner, up->user);
 
-	qlock(&srvlk);
+	b = c->aux;
+	wlock(&srvlk);
 	if(waserror()){
-		qunlock(&srvlk);
+		wunlock(&srvlk);
 		free(sp->owner);
 		free(sp->name);
 		free(sp);
 		nexterror();
 	}
-	if(srvlookup(name, -1) != nil)
+	if(b->closed)
+		error(Eexpired);
+	devpermcheck(b->owner, b->perm, OWRITE);
+	if(lookup(b->srv, name, ~0UL) != nil)
 		error(Eexist);
+	if(lookup(b->child, name, ~0UL) != nil)
+		error(Eexist);
 
 	sp->perm = perm&0777;
-	sp->path = qidpath++;
+	sp->path = srvpath++;
 
-	c->qid.path = sp->path;
+	c->qid.path = SRVQID(sp->path, Qsrv);
 	c->qid.type = QTFILE;
 
-	sp->link = srv;
-	srv = sp;
+	sp->link = b->srv;
+	b->srv = sp;
 
-	qunlock(&srvlk);
+	wunlock(&srvlk);
 	poperror();
 
 	c->flag |= COPEN;
@@ -198,41 +430,35 @@
 static void
 srvremove(Chan *c)
 {
-	Srv *sp, **l;
+	Board *b;
+	Srv *sp;
 
-	if(c->qid.type == QTDIR)
-		error(Eperm);
-
-	qlock(&srvlk);
+	b = c->aux;
+	wlock(&srvlk);
 	if(waserror()){
-		qunlock(&srvlk);
+		boardclunk(b, 0);
+		wunlock(&srvlk);
 		nexterror();
 	}
-	l = &srv;
-	for(sp = *l; sp != nil; sp = *l) {
-		if(sp->path == c->qid.path)
-			break;
-		l = &sp->link;
+	if(c->qid.type == QTDIR)
+		error(Eperm);
+	switch(SRVTYPE(c->qid.path)){
+	case Qlease:
+	case Qclone:
+		error(Eperm);
 	}
+
+	sp = lookup(b->srv, nil, c->qid.path);
 	if(sp == nil)
 		error(Enonexist);
 
-	/*
-	 * Only eve can remove system services.
-	 */
-	if(strcmp(sp->owner, eve) == 0 && !iseve())
+	if(strcmp(sp->owner, up->user) != 0 && !iseve())
 		error(Eperm);
 
-	/*
-	 * No removing personal services.
-	 */
-	if((sp->perm&7) != 7 && strcmp(sp->owner, up->user) && !iseve())
-		error(Eperm);
+	remove((Link**)&b->srv, nil, c->qid.path);
 
-	*l = sp->link;
-	sp->link = nil;
-
-	qunlock(&srvlk);
+	boardclunk(b, 0);
+	wunlock(&srvlk);
 	poperror();
 
 	if(sp->chan != nil)
@@ -245,12 +471,18 @@
 static int
 srvwstat(Chan *c, uchar *dp, int n)
 {
+	Board *b, *s;
 	char *strs;
-	Srv *sp;
 	Dir d;
+	Link *lp;
 
-	if(c->qid.type & QTDIR)
+	switch(SRVTYPE(c->qid.path)){
+	case Qlease:
+	case Qclone:
 		error(Eperm);
+	}
+	if(c->qid.type == QTDIR && c->aux == &root)
+		error(Eperm);
 
 	strs = smalloc(n);
 	if(waserror()){
@@ -261,32 +493,48 @@
 	if(n == 0)
 		error(Eshortstat);
 
-	qlock(&srvlk);
+	b = c->aux;
+	wlock(&srvlk);
 	if(waserror()){
-		qunlock(&srvlk);
+		wunlock(&srvlk);
 		nexterror();
 	}
+	if(b->closed)
+		error(Eexpired);
 
-	sp = srvlookup(nil, c->qid.path);
-	if(sp == nil)
+	if(c->qid.type == QTDIR)
+		lp = b;
+	else
+		lp = lookup(b->srv, nil, c->qid.path);
+	if(lp == nil)
 		error(Enonexist);
 
-	if(strcmp(sp->owner, up->user) != 0 && !iseve())
+	if(strcmp(lp->owner, up->user) != 0 && !iseve())
 		error(Eperm);
 
-	if(d.name != nil && *d.name && strcmp(sp->name, d.name) != 0) {
+	if(d.name != nil && *d.name && strcmp(lp->name, d.name) != 0) {
 		if(strchr(d.name, '/') != nil)
 			error(Ebadchar);
 		if(strlen(d.name) >= sizeof(up->genbuf))
 			error(Etoolong);
-		kstrdup(&sp->name, d.name);
+
+		//Ensure new name doesn't conflict with old names
+		if(c->qid.type == QTDIR)
+			s = b->parent;
+		else
+			s = b;
+		if(lookup(s->srv, d.name, ~0UL) != nil)
+			error(Eexist);
+		if(lookup(s->child, d.name, ~0UL) != nil)
+			error(Eexist);
+		kstrdup(&lp->name, d.name);
 	}
 	if(d.uid != nil && *d.uid)
-		kstrdup(&sp->owner, d.uid);
+		kstrdup(&lp->owner, d.uid);
 	if(d.mode != ~0UL)
-		sp->perm = d.mode & 0777;
+		lp->perm = d.mode & 0777;
 
-	qunlock(&srvlk);
+	wunlock(&srvlk);
 	poperror();
 
 	free(strs);
@@ -298,22 +546,48 @@
 static void
 srvclose(Chan *c)
 {
-	/*
-	 * in theory we need to override any changes in removability
-	 * since open, but since all that's checked is the owner,
-	 * which is immutable, all is well.
-	 */
-	if(c->flag & CRCLOSE){
+	Board *b;
+	int expired;
+
+	expired = 0;
+	if(SRVTYPE(c->qid.path) == Qlease)
+		expired++;
+	else if(c->flag & CRCLOSE){
+		/*
+		 * in theory we need to override any changes in removability
+		 * since open, but since all that's checked is the owner,
+	 	 * which is immutable, all is well.
+	 	 */
 		if(waserror())
 			return;
 		srvremove(c);
 		poperror();
+		return;
 	}
+
+	b = c->aux;
+	wlock(&srvlk);
+	boardclunk(b, expired);
+	wunlock(&srvlk);
 }
 
 static long
-srvread(Chan *c, void *va, long n, vlong)
+srvread(Chan *c, void *va, long n, vlong off)
 {
+	Board *b;
+
+	if(SRVTYPE(c->qid.path) == Qlease){
+		b = c->aux;
+		rlock(&srvlk);
+		if(waserror()){
+			runlock(&srvlk);
+			nexterror();
+		}
+		n = readstr((ulong)off, va, n, b->name);
+		runlock(&srvlk);
+		poperror();
+		return n;
+	}
 	isdir(c);
 	return devdirread(c, va, n, 0, 0, srvgen);
 }
@@ -321,11 +595,15 @@
 static long
 srvwrite(Chan *c, void *va, long n, vlong)
 {
+	Board *b;
 	Srv *sp;
 	Chan *c1;
 	int fd;
 	char buf[32];
 
+	if(SRVTYPE(c->qid.path) == Qlease)
+		error(Eperm);
+
 	if(n >= sizeof buf)
 		error(Etoobig);
 	memmove(buf, va, n);	/* so we can NUL-terminate */
@@ -334,15 +612,18 @@
 
 	c1 = fdtochan(fd, -1, 0, 1);	/* error check and inc ref */
 
-	qlock(&srvlk);
+	b = c->aux;
+	wlock(&srvlk);
 	if(waserror()) {
-		qunlock(&srvlk);
+		wunlock(&srvlk);
 		cclose(c1);
 		nexterror();
 	}
+	if(b->closed)
+		error(Eexpired);
 	if(c1->qid.type & QTAUTH)
 		error("cannot post auth file in srv");
-	sp = srvlookup(nil, c->qid.path);
+	sp = lookup(b->srv, nil, c->qid.path);
 	if(sp == nil)
 		error(Enonexist);
 
@@ -351,7 +632,7 @@
 
 	sp->chan = c1;
 
-	qunlock(&srvlk);
+	wunlock(&srvlk);
 	poperror();
 	return n;
 }
@@ -380,12 +661,15 @@
 void
 srvrenameuser(char *old, char *new)
 {
+	Board *b;
 	Srv *sp;
 
-	qlock(&srvlk);
-	for(sp = srv; sp != nil; sp = sp->link) {
+	b = &root;
+	wlock(&srvlk);
+	kstrdup(&b->owner, new);
+	for(sp = b->srv; sp != nil; sp = sp->link) {
 		if(sp->owner != nil && strcmp(old, sp->owner) == 0)
 			kstrdup(&sp->owner, new);
 	}
-	qunlock(&srvlk);
+	wunlock(&srvlk);
 }