9front - general discussion about 9front
 help / color / mirror / Atom feed
From: Jacob Moody <moody@mail.posixcafe.org>
To: 9front@9front.org
Subject: Re: [9front] [PATCH] Permissions for child boards in /srv
Date: Wed, 29 Jun 2022 11:50:42 -0600	[thread overview]
Message-ID: <88b93657-5d28-2309-4e43-0cba952fdf20@posixcafe.org> (raw)
In-Reply-To: <24C7FAF56B76BEDB07410AED72ADD298@felloff.net>

[-- Attachment #1: Type: text/plain, Size: 16315 bytes --]

On 6/29/22 11:20, cinap_lenrek@felloff.net wrote:
>> It's disheartening you feel this way.
> 
> sorry, but this code runs on all our computers.
> 

You're correct, I am being disrespectful to others.

>> I will begin the process of reverting all of my commits
>> from the past month or so.
> 
> no. its just devsrv.c.

reverted. I had incorrectly assumed you wanted
all the stuff I'd been working on teared out.

> 
> the reason i lost faith is that the patch changes
> alot more than you said in the message.

My apologies, the message was only intended to cover
the delta from the previous post. This wasn't clear
so I understand your lose of faith. The 'other bits'
of that code are doing what the subject, and first couple
of posts detail. Which is allowing permissions to be set
on to child service directories to restrict access.

Here's the patch in total now against the now reverted version
in tree, happy to provide more context. Since this is more or less
a full rewrite, I've included the full devsrv.c as an attachment if
that is easier to dig through.


Thanks,
moody

diff cac03f355caa29a6bd5cafd7d0d6ca362149cb27 uncommitted
--- 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,220 @@
 #include	"fns.h"
 #include	"../port/error.h"

+#include	"netif.h"

-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;
+	RWlock;
+	Ref;

-static Srv*
-srvlookup(char *name, ulong qidpath)
+	Board 	*parent;
+	Board 	*child;
+	Srv 	*srv;
+	long	id;
+	int	qidpath;
+	int 	closed;	
+};
+
+struct{
+	QLock;
+	long path;
+} boards;
+
+enum{
+	Qroot,
+	Qclone,
+	Qlease,
+
+	Qend,
+};
+
+Board root;
+
+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)
+		qidpath = NETTYPE(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)
+		qidpath = NETTYPE(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;
+
+	/* caller holds a wlock */
+	if(b == &root){
+		wunlock(b);
+		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);
+		wlock(b->parent);
+		ch = remove((Link**)&b->parent->child, b->name, b->path);
+		assert(ch == b);
+
+		b = ch->parent;
+		free(ch->name);
+		free(ch->owner);
+		wunlock(ch);
+		free(ch);
+	}
+	wunlock(b);
+}
+
 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(b);
+	if(waserror()){
+		runlock(b);
+		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.vers = NETID(c->qid.path);
+		q.path = NETQID(q.vers, sp->path);
+		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.vers = ch->id;
+		q.path = NETQID(q.vers, ch->path);
+		q.type = QTDIR;
+		devdir(c, q, up->genbuf, 0, ch->owner, ch->perm|DMDIR, dp);
+	} else if(0){
+Clone:
+		q.vers = NETID(c->qid.path);
+		q.path = NETQID(q.vers, Qclone);
+		devdir(c, q, "clone", 0, eve, 0444, dp);
+	} else {
+		runlock(b);
+		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(b);
+	poperror();
 	return 1;
 }

@@ -66,24 +225,54 @@
 static void
 srvinit(void)
 {
-	qidpath = 1;
+	root.qidpath = Qend;
+	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 +279,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(b);
+	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 +294,7 @@
 			break;
 		}
 	}
-	qunlock(&srvlk);
+	runlock(b);
 	return s;
 }

@@ -110,33 +301,73 @@
 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(NETTYPE(c->qid.path) == Qclone){;
+		wlock(b);
+		if(b->closed){
+			wunlock(b);
+			error(Eexpired);
+		}
+		ch = smalloc(sizeof *ch);
+		ch->qidpath = Qend;
+		ch->ref = 1;
+		ch->perm = 0770;
+		kstrdup(&ch->owner, up->user);
+		do {
+			qlock(&boards);
+			ch->id = ++boards.path;
+			qunlock(&boards);
+			snprint(buf, sizeof buf, "%ld", ch->id);
+		} while(lookup(b->srv, buf, ~0UL) != nil);
+
+		ch->parent = b;
+		ch->path = b->qidpath++;
+		kstrdup(&ch->name, buf);
+
+		ch->link = b->child;
+		b->child = ch;
+		c->aux = ch;
+		c->qid.vers = ch->id;
+		c->qid.path = NETQID(ch->id, Qlease);
+		boardclunk(b, 0); //unlock
+		return c;
+	}
+
+	rlock(b);
+	if(waserror()){
+		runlock(b);
+		nexterror();
+	}
 	if(c->qid.type == QTDIR){
 		if(omode & ORCLOSE)
 			error(Eperm);
 		if(omode != OREAD)
 			error(Eisdir);
+		devpermcheck(b->owner, b->perm, omode);
 		c->mode = omode;
 		c->flag |= COPEN;
 		c->offset = 0;
+		runlock(b);
+		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 +375,7 @@
 	nc = sp->chan;
 	incref(nc);

-	qunlock(&srvlk);
+	runlock(b);
 	poperror();

 	cclose(c);
@@ -154,6 +385,7 @@
 static Chan*
 srvcreate(Chan *c, char *name, int omode, ulong perm)
 {
+	Board *b;
 	Srv *sp;

 	if(openmode(omode) != OWRITE)
@@ -162,31 +394,41 @@
 	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(b);
 	if(waserror()){
-		qunlock(&srvlk);
+		wunlock(b);
 		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 = b->qidpath++;

-	c->qid.path = sp->path;
+	c->qid.path = NETQID(b->id, sp->path);
+	c->qid.vers = b->id;
 	c->qid.type = QTFILE;

-	sp->link = srv;
-	srv = sp;
+	sp->link = b->srv;
+	b->srv = sp;

-	qunlock(&srvlk);
+	wunlock(b);
 	poperror();

 	c->flag |= COPEN;
@@ -198,41 +440,33 @@
 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(b);
 	if(waserror()){
-		qunlock(&srvlk);
+		boardclunk(b, 0); //unlock
 		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(NETTYPE(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); //unlock
 	poperror();

 	if(sp->chan != nil)
@@ -245,12 +479,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(NETTYPE(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 +501,60 @@
 	if(n == 0)
 		error(Eshortstat);

-	qlock(&srvlk);
+	b = c->aux;
+	wlock(b);
 	if(waserror()){
-		qunlock(&srvlk);
+		wunlock(b);
 		nexterror();
 	}
+	if(b->closed)
+		error(Eexpired);

-	sp = srvlookup(nil, c->qid.path);
-	if(sp == nil)
+	if(c->qid.type == QTDIR){
+		lp = b;
+		/* we share ownership of our stats with our parent */
+		assert(b->parent != nil);
+		wlock(b->parent);
+		if(waserror()){
+			wunlock(b->parent);
+			nexterror();
+		}
+	} 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);
+	if(c->qid.type == QTDIR){
+		wunlock(b->parent);
+		poperror();
+	}
+
+	wunlock(b);
 	poperror();

 	free(strs);
@@ -298,22 +566,47 @@
 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(NETTYPE(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(b);
+	boardclunk(b, expired); //unlock
 }

 static long
-srvread(Chan *c, void *va, long n, vlong)
+srvread(Chan *c, void *va, long n, vlong off)
 {
+	Board *b;
+
+	if(NETTYPE(c->qid.path) == Qlease){
+		b = c->aux;
+		rlock(b);
+		if(waserror()){
+			runlock(b);
+			nexterror();
+		}
+		n = readstr((ulong)off, va, n, b->name);
+		runlock(b);
+		poperror();
+		return n;
+	}
 	isdir(c);
 	return devdirread(c, va, n, 0, 0, srvgen);
 }
@@ -321,11 +614,15 @@
 static long
 srvwrite(Chan *c, void *va, long n, vlong)
 {
+	Board *b;
 	Srv *sp;
 	Chan *c1;
 	int fd;
 	char buf[32];

+	if(NETTYPE(c->qid.path) == Qlease)
+		error(Eperm);
+
 	if(n >= sizeof buf)
 		error(Etoobig);
 	memmove(buf, va, n);	/* so we can NUL-terminate */
@@ -334,15 +631,18 @@

 	c1 = fdtochan(fd, -1, 0, 1);	/* error check and inc ref */

-	qlock(&srvlk);
+	b = c->aux;
+	wlock(b);
 	if(waserror()) {
-		qunlock(&srvlk);
+		wunlock(b);
 		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 +651,7 @@

 	sp->chan = c1;

-	qunlock(&srvlk);
+	wunlock(b);
 	poperror();
 	return n;
 }
@@ -380,12 +680,15 @@
 void
 srvrenameuser(char *old, char *new)
 {
+	Board *b;
 	Srv *sp;

-	qlock(&srvlk);
-	for(sp = srv; sp != nil; sp = sp->link) {
+	b = &root;
+	wlock(b);
+	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(b);
 }


[-- Attachment #2.1: Type: text/plain, Size: 342 bytes --]

from postmaster@9front:
The following attachment had content that we can't
prove to be harmless.  To avoid possible automatic
execution, we changed the content headers.
The original header was:

	Content-Type: text/x-csrc; charset=UTF-8; name="devsrv.c"
	Content-Disposition: attachment; filename="devsrv.c"
	Content-Transfer-Encoding: base64

[-- Attachment #2.2: devsrv.c.suspect --]
[-- Type: application/octet-stream, Size: 11288 bytes --]

#include	"u.h"
#include	"../port/lib.h"
#include	"mem.h"
#include	"dat.h"
#include	"fns.h"
#include	"../port/error.h"

#include	"netif.h"

typedef struct Link Link;
struct Link
{
	void 	*link;
	char 	*name;
	ulong 	path;
	char	*owner;
	ulong	perm;
};

typedef struct Srv Srv;
struct Srv
{
	Link;
	Chan	*chan;
};

typedef struct Board Board;
struct Board
{
	Link;
	RWlock;
	Ref;

	Board 	*parent;
	Board 	*child;
	Srv 	*srv;
	long	id;
	int	qidpath;
	int 	closed;	
};

struct{
	QLock;
	long path;
} boards;

enum{
	Qroot,
	Qclone,
	Qlease,

	Qend,
};

Board root;

static char Eexpired[] = "expired lease";

static void*
lookup(Link *l, char *name, ulong qidpath)
{
	Link *lp;

	if(qidpath != ~0UL)
		qidpath = NETTYPE(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)
		qidpath = NETTYPE(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;

	/* caller holds a wlock */
	if(b == &root){
		wunlock(b);
		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);
		wlock(b->parent);
		ch = remove((Link**)&b->parent->child, b->name, b->path);
		assert(ch == b);

		b = ch->parent;
		free(ch->name);
		free(ch->owner);
		wunlock(ch);
		free(ch);
	}
	wunlock(b);
}

static int
srvgen(Chan *c, char *name, Dirtab*, int, int s, Dir *dp)
{
	Srv *sp;
	Board *b, *ch;
	Qid q;

	if(name != nil && strlen(name) >= sizeof(up->genbuf))
		return -1;

	b = c->aux;
	ch = nil;
	mkqid(&q, ~0L, 0, QTFILE);
	rlock(b);
	if(waserror()){
		runlock(b);
		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;

		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){
		kstrcpy(up->genbuf, sp->name, sizeof up->genbuf);
		q.vers = NETID(c->qid.path);
		q.path = NETQID(q.vers, sp->path);
		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.vers = ch->id;
		q.path = NETQID(q.vers, ch->path);
		q.type = QTDIR;
		devdir(c, q, up->genbuf, 0, ch->owner, ch->perm|DMDIR, dp);
	} else if(0){
Clone:
		q.vers = NETID(c->qid.path);
		q.path = NETQID(q.vers, Qclone);
		devdir(c, q, "clone", 0, eve, 0444, dp);
	} else {
		runlock(b);
		poperror();
		return -1;
	}

	runlock(b);
	poperror();
	return 1;
}

static void
srvinit(void)
{
	root.qidpath = Qend;
	root.name = "#s";
	root.perm = 0777;
	kstrdup(&root.owner, eve);
}

static Chan*
srvattach(char *spec)
{
	Chan *c;

	c = devattach('s', spec);
	c->aux = &root;
	return c;
}

static Walkqid*
srvwalk(Chan *c, Chan *nc, char **name, int nname)
{
	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);
}

char*
srvname(Chan *c)
{
	Board *b;
	Srv *sp;
	char *s;

	s = nil;
	b = &root;
	rlock(b);
	for(sp = b->srv; sp != nil; sp = sp->link) {
		if(sp->chan == c){
			s = malloc(3+strlen(sp->name)+1);
			if(s != nil)
				sprint(s, "#s/%s", sp->name);
			break;
		}
	}
	runlock(b);
	return s;
}

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(NETTYPE(c->qid.path) == Qclone){;
		wlock(b);
		if(b->closed){
			wunlock(b);
			error(Eexpired);
		}
		ch = smalloc(sizeof *ch);
		ch->qidpath = Qend;
		ch->ref = 1;
		ch->perm = 0770;
		kstrdup(&ch->owner, up->user);
		do {
			qlock(&boards);
			ch->id = ++boards.path;
			qunlock(&boards);
			snprint(buf, sizeof buf, "%ld", ch->id);
		} while(lookup(b->srv, buf, ~0UL) != nil);

		ch->parent = b;
		ch->path = b->qidpath++;
		kstrdup(&ch->name, buf);

		ch->link = b->child;
		b->child = ch;
		c->aux = ch;
		c->qid.vers = ch->id;
		c->qid.path = NETQID(ch->id, Qlease);
		boardclunk(b, 0); //unlock
		return c;
	}

	rlock(b);
	if(waserror()){
		runlock(b);
		nexterror();
	}
	if(c->qid.type == QTDIR){
		if(omode & ORCLOSE)
			error(Eperm);
		if(omode != OREAD)
			error(Eisdir);
		devpermcheck(b->owner, b->perm, omode);
		c->mode = omode;
		c->flag |= COPEN;
		c->offset = 0;
		runlock(b);
		poperror();
		return c;
	}
	if(b->closed)
		error(Eexpired);

	sp = lookup(b->srv, nil, c->qid.path);
	if(sp == nil || sp->chan == nil)
		error(Eshutdown);

	if(openmode(omode)!=sp->chan->mode && sp->chan->mode!=ORDWR)
		error(Eperm);
	devpermcheck(sp->owner, sp->perm, omode);

	nc = sp->chan;
	incref(nc);

	runlock(b);
	poperror();

	cclose(c);
	return nc;
}

static Chan*
srvcreate(Chan *c, char *name, int omode, ulong perm)
{
	Board *b;
	Srv *sp;

	if(openmode(omode) != OWRITE)
		error(Eperm);

	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);

	b = c->aux;
	wlock(b);
	if(waserror()){
		wunlock(b);
		free(sp->owner);
		free(sp->name);
		free(sp);
		nexterror();
	}
	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 = b->qidpath++;

	c->qid.path = NETQID(b->id, sp->path);
	c->qid.vers = b->id;
	c->qid.type = QTFILE;

	sp->link = b->srv;
	b->srv = sp;

	wunlock(b);
	poperror();

	c->flag |= COPEN;
	c->mode = OWRITE;

	return c;
}

static void
srvremove(Chan *c)
{
	Board *b;
	Srv *sp;

	b = c->aux;
	wlock(b);
	if(waserror()){
		boardclunk(b, 0); //unlock
		nexterror();
	}
	if(c->qid.type == QTDIR)
		error(Eperm);
	switch(NETTYPE(c->qid.path)){
	case Qlease:
	case Qclone:
		error(Eperm);
	}

	sp = lookup(b->srv, nil, c->qid.path);
	if(sp == nil)
		error(Enonexist);

	if(strcmp(sp->owner, up->user) != 0 && !iseve())
		error(Eperm);

	remove((Link**)&b->srv, nil, c->qid.path);

	boardclunk(b, 0); //unlock
	poperror();

	if(sp->chan != nil)
		cclose(sp->chan);
	free(sp->owner);
	free(sp->name);
	free(sp);
}

static int
srvwstat(Chan *c, uchar *dp, int n)
{
	Board *b, *s;
	char *strs;
	Dir d;
	Link *lp;

	switch(NETTYPE(c->qid.path)){
	case Qlease:
	case Qclone:
		error(Eperm);
	}
	if(c->qid.type == QTDIR && c->aux == &root)
		error(Eperm);

	strs = smalloc(n);
	if(waserror()){
		free(strs);
		nexterror();
	}
	n = convM2D(dp, n, &d, strs);
	if(n == 0)
		error(Eshortstat);

	b = c->aux;
	wlock(b);
	if(waserror()){
		wunlock(b);
		nexterror();
	}
	if(b->closed)
		error(Eexpired);

	if(c->qid.type == QTDIR){
		lp = b;
		/* we share ownership of our stats with our parent */
		assert(b->parent != nil);
		wlock(b->parent);
		if(waserror()){
			wunlock(b->parent);
			nexterror();
		}
	} else
		lp = lookup(b->srv, nil, c->qid.path);
	if(lp == nil)
		error(Enonexist);

	if(strcmp(lp->owner, up->user) != 0 && !iseve())
		error(Eperm);

	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);

		//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(&lp->owner, d.uid);
	if(d.mode != ~0UL)
		lp->perm = d.mode & 0777;

	if(c->qid.type == QTDIR){
		wunlock(b->parent);
		poperror();
	}

	wunlock(b);
	poperror();

	free(strs);
	poperror();

	return n;
}

static void
srvclose(Chan *c)
{
	Board *b;
	int expired;

	expired = 0;
	if(NETTYPE(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(b);
	boardclunk(b, expired); //unlock
}

static long
srvread(Chan *c, void *va, long n, vlong off)
{
	Board *b;

	if(NETTYPE(c->qid.path) == Qlease){
		b = c->aux;
		rlock(b);
		if(waserror()){
			runlock(b);
			nexterror();
		}
		n = readstr((ulong)off, va, n, b->name);
		runlock(b);
		poperror();
		return n;
	}
	isdir(c);
	return devdirread(c, va, n, 0, 0, srvgen);
}

static long
srvwrite(Chan *c, void *va, long n, vlong)
{
	Board *b;
	Srv *sp;
	Chan *c1;
	int fd;
	char buf[32];

	if(NETTYPE(c->qid.path) == Qlease)
		error(Eperm);

	if(n >= sizeof buf)
		error(Etoobig);
	memmove(buf, va, n);	/* so we can NUL-terminate */
	buf[n] = 0;
	fd = strtoul(buf, 0, 0);

	c1 = fdtochan(fd, -1, 0, 1);	/* error check and inc ref */

	b = c->aux;
	wlock(b);
	if(waserror()) {
		wunlock(b);
		cclose(c1);
		nexterror();
	}
	if(b->closed)
		error(Eexpired);
	if(c1->qid.type & QTAUTH)
		error("cannot post auth file in srv");
	sp = lookup(b->srv, nil, c->qid.path);
	if(sp == nil)
		error(Enonexist);

	if(sp->chan != nil)
		error(Ebadusefd);

	sp->chan = c1;

	wunlock(b);
	poperror();
	return n;
}

Dev srvdevtab = {
	's',
	"srv",

	devreset,
	srvinit,	
	devshutdown,
	srvattach,
	srvwalk,
	srvstat,
	srvopen,
	srvcreate,
	srvclose,
	srvread,
	devbread,
	srvwrite,
	devbwrite,
	srvremove,
	srvwstat,
};

void
srvrenameuser(char *old, char *new)
{
	Board *b;
	Srv *sp;

	b = &root;
	wlock(b);
	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);
	}
	wunlock(b);
}

  reply	other threads:[~2022-06-29 17:53 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-06-24 14:34 Jacob Moody
2022-06-25  3:49 ` Jacob Moody
2022-06-29 14:53   ` Jacob Moody
2022-06-29 16:35     ` cinap_lenrek
2022-06-29 16:42       ` Jacob Moody
2022-06-29 16:46         ` ori
2022-06-29 17:12           ` hiro
2022-06-29 17:20         ` cinap_lenrek
2022-06-29 17:50           ` Jacob Moody [this message]
2022-07-02 21:25             ` ori

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=88b93657-5d28-2309-4e43-0cba952fdf20@posixcafe.org \
    --to=moody@mail.posixcafe.org \
    --cc=9front@9front.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).