ref: 1b7ddc17b497c25e764341fda54d7c0a97b6b5a8
dir: /sys/src/9/port/devsrv.c/
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "../port/error.h"
#define SRVTYPE(x) (((uint)x)&0x3)
#define SRVPATH(x) (((uvlong)x)>>2)
#define SRVQID(x, t) ((((uvlong)x)<<2)|((t)&0x3))
typedef struct Link Link;
struct Link
{
void *link;
char *name;
char *owner;
ulong perm;
ulong path;
};
typedef struct Srv Srv;
struct Srv
{
Link;
Chan *chan;
};
typedef struct Board Board;
struct Board
{
Link;
Ref;
int closed;
Srv *srv;
/* tree linkage */
Board *parent;
Board *child;
/* all boards list */
Board *prev;
Board *next;
};
enum{
Qsrv,
Qclone,
Qlease,
};
static char clone[] = "clone";
static RWlock srvlk;
static ulong srvpath;
static Board srvroot;
static void*
lookup(Link *l, char *name, ulong qidpath)
{
Link *lp;
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
freelink(Link *l)
{
free(l->name);
free(l->owner);
free(l);
}
/* always called with srvlock wlock'ed */
static void
boardclunk(Board *b)
{
Board *ch;
/* srvroot is not ref-counted nor clunkable */
if(b == &srvroot)
return;
if(decref(b))
return;
/*
* 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){
assert(b->srv == nil);
assert(b->parent != nil);
/* unlink from parent board */
ch = remove((Link**)&b->parent->child, b->name, ~0UL);
assert(ch == b);
/* unlink from all boards list */
ch->prev->next = ch->next;
ch->next->prev = ch->prev;
b = ch->parent;
freelink(ch);
}
}
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(&srvlk);
if(waserror()){
runlock(&srvlk);
nexterror();
}
switch(s){
case -2: /* dot */
ch = b;
goto Child;
case DEVDOTDOT:
ch = b->parent;
if(ch == nil)
ch = &srvroot;
goto Child;
}
if(name != nil){
if(strcmp(name, clone) == 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.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;
}
runlock(&srvlk);
poperror();
return 1;
}
static void
srvinit(void)
{
srvroot.next = &srvroot;
srvroot.prev = &srvroot;
srvroot.path = srvpath++;
srvroot.name = "#s";
srvroot.perm = 0777;
kstrdup(&srvroot.owner, eve);
}
static Chan*
srvattach(char *spec)
{
Chan *c;
c = devattach('s', spec);
c->aux = &srvroot;
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 == &srvroot)
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);
}
static Chan*
srvopen(Chan *c, int omode)
{
Board *b, *ch;
Srv *sp;
Chan *nc;
char buf[64];
int mode;
if(omode&OTRUNC)
error(Eexist);
if(omode&ORCLOSE)
error(Eperm);
mode = openmode(omode);
b = c->aux;
if(SRVTYPE(c->qid.path) == Qclone){
wlock(&srvlk);
if(waserror()){
wunlock(&srvlk);
nexterror();
}
if(b->closed)
error(Eshutdown);
ch = smalloc(sizeof *ch);
ch->ref = 1;
ch->perm = 0770;
do {
ch->path = srvpath++;
snprint(buf, sizeof buf, "%ld", ch->path);
} while(lookup(b->srv, buf, ~0UL) != nil);
kstrdup(&ch->name, buf);
kstrdup(&ch->owner, up->user);
/* link to all boards list */
ch->next = &srvroot;
ch->prev = srvroot.prev;
ch->next->prev = ch;
ch->prev->next = ch;
/* link to parent board */
ch->parent = b;
ch->link = b->child;
b->child = ch;
c->aux = ch;
c->qid.path = SRVQID(ch->path, Qlease);
c->mode = mode;
boardclunk(b);
wunlock(&srvlk);
poperror();
return c;
}
rlock(&srvlk);
if(waserror()){
runlock(&srvlk);
nexterror();
}
if(c->qid.type == QTDIR){
if(omode != OREAD)
error(Eisdir);
devpermcheck(b->owner, b->perm, omode);
c->mode = mode;
c->flag |= COPEN;
c->offset = 0;
runlock(&srvlk);
poperror();
return c;
}
if(b->closed)
error(Eshutdown);
sp = lookup(b->srv, nil, c->qid.path);
if(sp == nil)
error(Eshutdown);
nc = sp->chan;
if(nc == nil)
error(Eshutdown);
if(mode != nc->mode && nc->mode != ORDWR)
error(Eperm);
devpermcheck(sp->owner, sp->perm, omode);
incref(nc);
runlock(&srvlk);
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(name, clone) == 0)
error(Eexist);
sp = smalloc(sizeof *sp);
kstrdup(&sp->name, name);
kstrdup(&sp->owner, up->user);
b = c->aux;
wlock(&srvlk);
if(waserror()){
wunlock(&srvlk);
freelink(sp);
nexterror();
}
if(b->closed)
error(Eshutdown);
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 = srvpath++;
c->qid.path = SRVQID(sp->path, Qsrv);
c->qid.type = QTFILE;
sp->link = b->srv;
b->srv = sp;
wunlock(&srvlk);
poperror();
c->flag |= COPEN;
c->mode = OWRITE;
return c;
}
static void
srvremove(Chan *c)
{
Board *b;
Srv *sp;
b = c->aux;
wlock(&srvlk);
if(waserror()){
boardclunk(b);
wunlock(&srvlk);
nexterror();
}
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);
if(strcmp(sp->owner, up->user) != 0 && !iseve())
error(Eperm);
remove((Link**)&b->srv, nil, c->qid.path);
boardclunk(b);
wunlock(&srvlk);
poperror();
if(sp->chan != nil)
cclose(sp->chan);
freelink(sp);
}
static int
srvwstat(Chan *c, uchar *dp, int n)
{
Board *b, *s;
char *strs;
Dir d;
Link *lp;
switch(SRVTYPE(c->qid.path)){
case Qlease:
case Qclone:
error(Eperm);
}
if(c->qid.type == QTDIR && c->aux == &srvroot)
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(&srvlk);
if(waserror()){
wunlock(&srvlk);
nexterror();
}
if(b->closed)
error(Eshutdown);
if(c->qid.type == QTDIR)
lp = b;
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(strcmp(d.name, clone) == 0)
error(Eexist);
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;
wunlock(&srvlk);
poperror();
free(strs);
poperror();
return n;
}
static void
srvclose(Chan *c)
{
Srv *sp, *link;
Board *b;
if((c->flag & CRCLOSE) != 0 && SRVTYPE(c->qid.path) != Qlease){
/*
* 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;
if(b == &srvroot)
return;
wlock(&srvlk);
if(SRVTYPE(c->qid.path) != Qlease){
boardclunk(b);
wunlock(&srvlk);
return;
}
/* free later after releasing srvlk */
sp = b->srv;
b->srv = nil;
b->closed++;
boardclunk(b);
wunlock(&srvlk);
for(; sp != nil; sp = link){
link = sp->link;
if(sp->chan != nil)
ccloseq(sp->chan);
freelink(sp);
}
}
static long
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);
}
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 */
buf[n] = 0;
fd = strtoul(buf, 0, 0);
c1 = fdtochan(fd, -1, 0, 1); /* error check and inc ref */
b = c->aux;
wlock(&srvlk);
if(waserror()) {
wunlock(&srvlk);
cclose(c1);
nexterror();
}
if(b->closed)
error(Eshutdown);
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;
if(c1->srvname == nil)
kstrdup(&c1->srvname, c->path->s);
wunlock(&srvlk);
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;
wlock(&srvlk);
b = &srvroot;
do {
if(strcmp(b->owner, old) == 0)
kstrdup(&b->owner, new);
for(sp = b->srv; sp != nil; sp = sp->link)
if(strcmp(sp->owner, old) == 0)
kstrdup(&sp->owner, new);
b = b->next;
} while(b != &srvroot);
wunlock(&srvlk);
}