Plan 9 from Bell Labs’s /usr/web/sources/contrib/blstuart/θfs/fs.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


/*
 * Copyright (c) 2013, Coraid, Inc.
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Coraid nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL CORAID BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <u.h>
#include <libc.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#include "dat.h"

typedef struct Fdref Fdref;
typedef struct Fidaux Fidaux;
typedef struct Lmsg Lmsg;
typedef struct Srvaux Srvaux;
typedef struct Uglymap Uglymap;

struct Fdref {
	Ref ref;
	int fd;
};

struct Fidaux {
	char *path;
	char *uname;
	uvlong lsearch;
	int dirindex;
	Fdref *store;
};

struct Lmsg {
	int data;
	char *rsys;
};

struct Srvaux {
	Ioproc *io9p;
};

struct Uglymap {
	Srv *s;
	uchar *rbuf;
	Uglymap *next;
};

static void θattach(Req *);
static void θcreate(Req *);
static void θdestroyfid(Fid *);
static void θend(Srv *);
static void θflush(Req *);
static void θopen(Req *);
static void θread(Req *);
static void θremove(Req *);
static void θstat(Req *);
static void θwalk(Req *);
static void θwstat(Req *);
static void θwrite(Req *);
static void mylistenproc(void *);
static void srvstarter(void *);

Srv θsrv = {
	.attach = θattach,
	.auth = auth9p,
	.open = θopen,
	.create = θcreate,
	.read = θread,
	.write = θwrite,
	.remove = θremove,
	.flush = θflush,
	.stat = θstat,
	.wstat = θwstat,
	.walk = θwalk,
	.destroyfid = θdestroyfid,
	.start = θstart,
	.end = θend,
};

static char *dev;
static char *laddr;
static Uglymap *uhd;
static Channel *lchan;

char *ddir, *dname;
uvlong starttime;
int doatimes;
int shutdown;
int mainstacksize = 16384;

static void
usage(void)
{
	fprint(2, "Usage: %s [-anrsACD] [-m nblk] [-p port] device\n", argv0);
	threadexits("usage");
}

void
threadmain(int argc, char *argv[])
{
	Lmsg lmsg;
	char *lstr, *p;
	int doream, postcons, port, poststdin;
	int doaoe, donfs, maxcache;

	doream = 0;
	postcons = 0;
	poststdin = 0;
	doaoe = 1;
	donfs = 1;
	maxcache = 4000;
	port = 564;
	ARGBEGIN {
	case 'a':
		doaoe = 0;
		break;
	case 'm':
		maxcache = atoi(EARGF(usage()));
		break;
	case 'n':
		donfs = 0;
		break;
	case 'p':
		port = atoi(EARGF(usage()));
		break;
	case 'r':
		doream = 1;
		break;
	case 's':
		poststdin = 1;
		break;
	case 'A':
		doatimes = 1;
		break;
	case 'C':
		postcons = 1;
		break;
	case 'D':
		++chatty9p;
		break;
	default:
		usage();
	} ARGEND
	if(argc != 1)
		usage();
	dev = *argv;
	lstr = smprint("tcp!*!%d", port);
	starttime = nsec();
	p = strrchr(dev, '/');
	if(p == nil) {
		ddir = ".";
		dname = strdup(dev);
	}
	else {
		ddir = mallocz(p - dev + 1, 1);
		strncpy(ddir, dev, p - dev);
		dname = strdup(p+1);
	}
	initcache(dev, maxcache);
	if(doream)
		ream(dev);
	else
		loadsuper();
	inituid();
	if(doaoe)
		initaoe();
	if(donfs)
		initnfs();
	lchan = chancreate(sizeof(Lmsg), 4);
	laddr = lstr;
	threadcreate(srvstarter, nil, 8192);
	if(poststdin) {
		lmsg.data = 1;
		lmsg.rsys = estrdup9p("boot");
		send(lchan, &lmsg);
		postfd("θfs", 0);
	}
	/*
	 * Because the main in libthread runs the thread scheduler in
	 * the initial process, we can't daemonize in the usual way.
	 * The backgrounding is no big deal, but we want the parent
	 * to be able to wait until we're ready for an attach.  So we
	 * don't do the console until almost the end and the parent
	 * can wait until θfsctl appears in /srv.  It's not as elegant as
	 * letting the wait synchronize, but it's better than an arbitrary
	 * sleep.
	 */
	initcons(postcons);
	proccreate(mylistenproc, nil, 8192);
}

void
halt9p(void)
{
	Srvaux *sa;
	Uglymap *u;

/*	chanclose(lchan); */
	for(u = uhd; u; u = u->next) {
		close(u->s->infd);
		close(u->s->outfd);
		sa = u->s->aux;
		closeioproc(sa->io9p);
	}
}

static void
mysrvproc(void *a)
{
	Srv *s;
	int data;

	s = a;
	data = s->infd;
	srv(s);
	close(data);
	threadexits(nil);
}

static void
srvstarter(void *)
{
	Lmsg m;
	Srv *s;

	while(recv(lchan, &m)) {
		if(shutdown)
			break;
		s = emalloc9p(sizeof(Srv));
		*s = θsrv;
		s->addr = m.rsys;
		s->infd = s->outfd = m.data;
		s->fpool = nil;
		s->rpool = nil;
		s->rbuf = nil;
		s->wbuf = nil;
		threadcreate(mysrvproc, s, 32 * 1024);
	}
	threadexits(nil);
}

static char*
getremotesys(char *ndir)
{
	char buf[128], *serv, *sys;
	int fd, n;

	snprint(buf, sizeof buf, "%s/remote", ndir);
	sys = nil;
	fd = open(buf, OREAD);
	if(fd >= 0) {
		n = read(fd, buf, sizeof(buf)-1);
		if(n>0) {
			buf[n-1] = 0;
			serv = strchr(buf, '!');
			if(serv)
				*serv = 0;
			sys = estrdup9p(buf);
		}
		close(fd);
	}
	if(sys == nil)
		sys = estrdup9p("unknown");
	return sys;
}

static void
mylistenproc(void *)
{
	Lmsg m;
	char ndir[NETPATHLEN], dir[NETPATHLEN];
	int ctl, data, nctl;
	
	ctl = announce(laddr, dir);
	if(ctl < 0) {
		fprint(2, "%s: announce %s: %r", argv0, laddr);
		return;
	}

	for(;;){
		nctl = listen(dir, ndir);
		if(nctl < 0){
			fprint(2, "%s: listen %s: %r", argv0, laddr);
			break;
		}

		data = accept(ctl, ndir);
		if(data < 0){
			fprint(2, "%s: accept %s: %r\n", argv0, ndir);
			continue;
		}
		m.data = data;
		m.rsys = getremotesys(ndir);
		send(lchan, &m);
	}
}

int
read9pmsg(int fd, void *abuf, uint n)
{
	Srvaux *sa;
	Uglymap *um;
	Ioproc *io9p;
	int m, len;
	uchar *buf;

	buf = abuf;

	/*
	 * Grotesque, but this is research :)
	 */
	for(um = uhd; um && um->rbuf != buf; um = um->next) ;
	if(um == nil) {
		fprint(2, "no ugly mapping");
		return 0;
	}
	sa = um->s->aux;
	io9p = sa->io9p;

	/* read count */
	m = ioreadn(io9p, fd, buf, BIT32SZ);
	if(m != BIT32SZ){
		if(m < 0)
			return -1;
		return 0;
	}

	len = GBIT32(buf);
	if(len <= BIT32SZ || len > n){
		werrstr("bad length in 9P2000 message header");
		return -1;
	}
	len -= BIT32SZ;
	m = ioreadn(io9p, fd, buf+BIT32SZ, len);
	if(m < len)
		return 0;
	return BIT32SZ+m;
}

static int
θhasperm(int fd, uvlong meta, char *uid, int p)
{
	uvlong mode;
	char *fuser, *fgroup;
	int m;

	if(allow)
		return 1;
	if(getmetaint(fd, meta, "mode", &mode) == MTnone)
		return 1;
	m = mode & 7;	/* other */
	if((p & m) == p)
		return 1;

	if((fuser = getmetastr(fd, meta, "uid")) != nil) {
		if(strcmp(fuser, uid) == 0) {
			m |= (mode>>6) & 7;
			if((p & m) == p) {
				free(fuser);
				return 1;
			}
		}
		free(fuser);
	}

	if((fgroup = getmetastr(fd, meta, "gid")) != nil) {
		if(ingroup(uid, fgroup)) {
			m |= (mode>>3) & 7;
			if((p & m) == p) {
				free(fgroup);
				return 1;
			}
		}
		free(fgroup);
	}
	return 0;
}


static void
attacher(void *a)
{
	Req *r;
	Fidaux *fa;
	char *path;
	uvlong rmeta, x;

	r = a;
	if(r->ifcall.aname == nil || strlen(r->ifcall.aname) == 0)
		path = smprint("/");
	else
		path = smprint("/%s", r->ifcall.aname);
	rmeta = q2m(-1, p2q(-1, path, 0), 0);
	if(rmeta == 0)
		respond(r, "no root");
	else {
		getmetaint(-1, rmeta, "qpath", &x);
		r->fid->qid.path = x;
		getmetaint(-1, rmeta, "qvers", &x);
		r->fid->qid.vers = x;
		getmetaint(-1, rmeta, "qtype", &x);
		r->fid->qid.type = x;
		r->ofcall.qid = r->fid->qid;
		fa = malloc(sizeof(Fidaux));
		r->fid->aux = fa;
		fa->path = path;
		fa->uname = estrdup9p(r->ifcall.uname);
		fa->lsearch = 0;
		fa->store = θmalloc(sizeof(Fdref));
		incref(&fa->store->ref);
		fa->store->fd = -1;
		respond(r, nil);
	}
	threadexits(nil);
}

static void
θattach(Req *r)
{
	if(authattach(r) < 0)
		return;
	threadcreate(attacher, r, 8192);
}

static void
_θcreate(void *a)
{
	Req *r;
	Qid nqid;
	Fidaux *fa;
	char *npath;
	uvlong x;
//	uvlong meta, pmeta, dirblk, now;
	uvlong meta, pmeta, now;

	r = a;
	fa = r->fid->aux;
	pmeta = q2m(-1, r->fid->qid.path, 0);
	if(θhasperm(fa->store->fd, pmeta, fa->uname, AWRITE) == 0) {
		respond(r, "permission denied");
		threadexits(nil);
	}
	npath = smprint("%s/%s", fa->path, r->ifcall.name);
	nqid.path = p2q(-1, npath, 1);
	meta = q2m(-1, nqid.path, 1);
	if(meta == 0) {
		respond(r, "create failure");
		free(npath);
		threadexits(nil);
	}
	setmetastr(meta, "name", nil, r->ifcall.name, 0);
	setmetaint(meta, "parent", nil, r->fid->qid.path);
	nqid.vers = 0;
	nqid.type = 0;
	if(r->ifcall.perm & DMDIR)
		nqid.type |= QTDIR;
	if(r->ifcall.perm & DMAPPEND)
		nqid.type |= QTAPPEND;
	if(r->ifcall.perm & DMEXCL)
		nqid.type |= QTEXCL;
	if(r->ifcall.perm & DMTMP)
		nqid.type |= QTTMP;
	setmetaint(meta, "qpath", nil, nqid.path);
	setmetaint(meta, "qvers", nil, nqid.vers);
	setmetaint(meta, "qtype", nil, nqid.type);
	setmetaint(meta, "mode", nil, r->ifcall.perm);
	now = nsec();
	setmetaint(meta, "atime", nil, now);
	setmetaint(meta, "mtime", nil, now);
	setmetaint(meta, "length", nil, 0);
	setmetastr(meta, "uid", nil, fa->uname, 0);
	setmetastr(meta, "gid", nil, fa->uname, 0);
	setmetastr(meta, "muid", nil, fa->uname, 0);
	if(getmetaint(-1, pmeta, "child", &x) == MTint)
		setmetaint(meta, "sib", nil, x);
	else
		setmetaint(meta, "sib", nil, 0);
	if(r->ifcall.perm & DMDIR)
		setmetaint(meta, "child", nil, 0);
	else
		setmetaint(meta, "dblock", nil, 0);
	setmetaint(pmeta, "child", nil, nqid.path);
	if(getmetaint(-1, pmeta, "qvers", &x) != MTnone)
		setmetaint(pmeta, "qvers", nil, x+1);
	setmetaint(pmeta, "mtime", nil, now);
	setmetastr(pmeta, "muid", nil, fa->uname, 0);
	setqhash(nqid.path, meta);
	free(fa->path);
	fa->path = npath;
	fa->lsearch = 0;
	r->fid->qid = nqid;
	r->ofcall.qid = nqid;
	respond(r, nil);
	savesuper();
	threadexits(nil);
}

static void
θcreate(Req *r)
{
	threadcreate(_θcreate, r, 8192);
}

static void
θdestroyfid(Fid *fid)
{
	Fidaux *fa;
	uvlong meta;

	if(fid->qid.type & QTAUTH) {
		authdestroy(fid);
		return;
	}
	fa = fid->aux;
	if(fid->omode != -1 && (fid->omode & ORCLOSE)) {
		meta = q2m(fa->store->fd, fid->qid.path, 0);
		if(meta != 0) {
			freedata(meta);
			rmdlist(meta, fid->qid.path);
			rmq(fid->qid.path, meta);
			rmmlist(meta);
			if(fa)
				rmp(fa->path);
		}
	}
	if(fa == nil)
		return;
	if(fa->store && decref(&fa->store->ref) == 0) {
		if(fa->store->fd != -1)
			close(fa->store->fd);
		free(fa->store);
	}
	free(fa->path);
	free(fa->uname);
	free(fa);
}

static void
θend(Srv *s)
{
	Srvaux *sa;
	Uglymap *um, *u;

	resetmeta();
	csync();
	sa = s->aux;
	if(sa) {
		if(sa->io9p)
			closeioproc(sa->io9p);
		free(sa);
	}
	if(uhd == nil)
		return;
	if(uhd->s == s) {
		um = uhd;
		uhd = um->next;
		free(um);
		return;
	}
	for(um = uhd; um && um->next && um->next->s != s; um = um->next) ;
	if(um && um->next) {
		u = um->next;
		um->next = u->next;
		free(u);
	}
}

static void
θflush(Req *r)
{
	respond(r, nil);
}

static void
_θopen(void *a)
{
	Fidaux *fa;
	Req *r;
	Fid *fid;
	uvlong meta, x;
	ulong need;

	r = a;
	fid = r->fid;
	fa = fid->aux;
	meta = q2m(fa->store->fd, fid->qid.path, 0);
	if(meta == 0) {
		respond(r, "no file");
		threadexits(nil);
	}
	switch(r->ifcall.mode & 3) {
	case OREAD:
		need = AREAD;
		break;
	case OWRITE:
		need = AWRITE;
		break;
	case ORDWR:
		need = AREAD | AWRITE;
		break;
	case OEXEC:
		need = AEXEC;
		break;
	default:
		need = AREAD | AWRITE | AEXEC;
		break;
	}
	if(r->ifcall.mode & OTRUNC)
		need |= AWRITE;
	if(θhasperm(fa->store->fd, meta, fa->uname, need) == 0) {
		respond(r, "permission denied");
		threadexits(nil);
	}
	if(r->ifcall.mode & ORCLOSE) {
		/* check write permission on parent */
	}
	if(r->ifcall.mode & OTRUNC) {
		setmetaint(meta, "length", nil, 0LL);
		if(getmetaint(fa->store->fd, meta, "qvers", &x) != MTnone)
			setmetaint(meta, "qvers", nil, x+1);
	}
	respond(r, nil);
	threadexits(nil);
}

static void
θopen(Req *r)
{
	threadcreate(_θopen, r, 8192);
}

static int
lzstat(int fd, uvlong meta, Dir *d)
{
	uvlong x;

	memset(&d->qid, 0, sizeof(Qid));
	if(getmetaint(fd, meta, "qpath", &x) != MTnone)
		d->qid.path = x;
	if(getmetaint(fd, meta, "qvers", &x) != MTnone)
		d->qid.vers = x;
	if(getmetaint(fd, meta, "qtype", &x) != MTnone)
		d->qid.type = x;
	if(getmetaint(fd, meta, "mode", &x) != MTnone)
		d->mode = x;
	else
		d->mode = 0;
	if(getmetaint(fd, meta, "atime", &x) != MTnone)
		d->atime = x / 1000000000;
	else
		d->atime = 0;
	if(getmetaint(fd, meta, "mtime", &x) != MTnone)
		d->mtime = x / 1000000000;
	else
		d->mtime = 0;
	if(getmetaint(fd, meta, "length", &x) != MTnone)
		d->length = x;
	else
		d->length = 0;
	if((d->name = getmetastr(fd, meta, "name")) == nil) {
		fprint(2, "where the streets have no name\n");
		d->name = estrdup9p("<nil>");
	}
	/* If this is one of the roots, just call it '/' */
	if(d->name[0] == '/')
		d->name[1] = 0;
	if((d->uid = getmetastr(fd, meta,"uid")) == nil)
		d->uid = estrdup9p("none");
	if((d->gid = getmetastr(fd, meta, "gid")) == nil)
		d->gid = estrdup9p("none");
	if((d->muid = getmetastr(fd, meta, "muid")) == nil)
		d->muid = estrdup9p("none");
	return 0;
}

static int
θgen(int n, Dir *dir, void *a)
{
	Fidaux *fa;
	Fid *fid;
	uvlong meta, x;
	int i;

	fid = a;
	fa = fid->aux;
	if(n == fa->dirindex + 1 && fa->lsearch != 0) {
		if(getmetaint(fa->store->fd, fa->lsearch, "sib", &x) == MTint)
			meta = q2m(fa->store->fd, x, 0);
		else {
			meta = 0;
			fprint(2, "no sibling in mblock %ulld\n", fa->lsearch);
		}
	}
	else {
		meta = q2m(fa->store->fd, fid->qid.path, 0);
		if(meta == 0)
			return -1;
		if(getmetaint(fa->store->fd, meta, "child", &x) != MTint)
			return -1;
		meta = q2m(fa->store->fd, x, 0);
		for(i = 0; i < n && meta != 0; ++i) {
			getmetaint(fa->store->fd, meta, "sib", &x);
			meta = q2m(fa->store->fd, x, 0);
		}
	}
	fa->dirindex = n;
	fa->lsearch = meta;
	if(meta == 0)
		return -1;
	i = lzstat(fa->store->fd, meta, dir);
	return i;
}

static void
_θread(void *a)
{
	Fidaux *fa;
	Req *r;
	ulong tot;

	r = a;
	fa = r->fid->aux;
	fa->lsearch = 0;
	fa->dirindex = 0;
	if(r->fid->qid.type & QTDIR) {
		dirread9p(r, θgen, r->fid);
		respond(r, nil);
		threadexits(nil);
	}
	tot = θpread(fa->store->fd, r->fid->qid.path, r->ofcall.data, r->ifcall.count, r->ifcall.offset);
	if(tot == -1) {
		respond(r, "no metadata");
		threadexits(nil);
	}
	r->ofcall.count = tot;
	respond(r, nil);
	threadexits(nil);
}

static void
θauthread(void *a)
{
	Req *r;

	r = a;
	authread(r);
	threadexits(nil);
}

static void
θread(Req *r)
{
	if(r->fid->qid.type & QTAUTH) {
		proccreate(θauthread, r, 8192);
		return;
	}
	threadcreate(_θread, r, 8192);
}

static void
_θremove(void *a)
{
	static QLock rlock;
	Req *r;
	Fidaux *fa;
	uvlong meta, pmeta, qpath, now;

	/*
	 * This lock is ugly.  Its purpose is to serialize the removes so
	 * that we don't end up in the process of removing the same
	 * file more than once concurrently.  It comes up when doing
	 * a mk clean on the kernel.  I'm going to give some thought
	 * to better ways to handle this, but this should get around
	 * the issue for now.
	 */
	qlock(&rlock);
	r = a;
	fa = r->fid->aux;
	meta = q2m(-1, r->fid->qid.path, 0);
	if(meta == 0) {
		qunlock(&rlock);
		respond(r, nil);
		threadexits(nil);
	}
	pmeta = 0;
	/* check parent permission */
	if(getmetaint(-1, meta, "parent", &qpath) != MTnone && qpath != 0) {
		pmeta = q2m(-1, qpath, 0);
		if(pmeta != 0) {
			if(θhasperm(fa->store->fd, pmeta, fa->uname, AWRITE) == 0) {
				qunlock(&rlock);
				respond(r, "permission denied");
				threadexits(nil);
			}
		}
	}
	if(r->fid->qid.type & QTDIR) {
		if(getmetaint(-1, meta, "child", &qpath) != MTnone && qpath != 0) {
			qunlock(&rlock);
			respond(r, "not empty");
			threadexits(nil);
		}
	}
	now = nsec();
	rmq(r->fid->qid.path, meta);
	setmetaint(pmeta, "mtime", nil, now);
	setmetastr(pmeta, "muid", nil, fa->uname, 0);
	freedata(meta);
	rmdlist(meta, r->fid->qid.path);
	rmmlist(meta);
	rmp(fa->path);
	qunlock(&rlock);
	respond(r, nil);
	threadexits(nil);
}

static void
θremove(Req *r)
{
	threadcreate(_θremove, r, 8192);
}

void
θstart(Srv *s)
{
	Srvaux *sa;
	Uglymap *um;

	sa = malloc(sizeof(Srvaux));
	sa->io9p = ioproc();
	s->aux = sa;
	um = malloc(sizeof(Uglymap));
	um->s = s;
	um->rbuf = s->rbuf;
	um->next = uhd;
	uhd = um;
}

static void
_θstat(void *a)
{
	Req *r;
	Fidaux *fa;
	uvlong meta;
	int n;

	r = a;
	fa = r->fid->aux;
	meta = q2m(fa->store->fd, r->fid->qid.path, 0);
	if(meta == 0)
		respond(r, "no file");
	else {
		n = lzstat(fa->store->fd, meta, &r->d);
		if(n == 0)
			respond(r, nil);
		else
			respond(r, "errnt");
	}
	threadexits(nil);
}

static void
θstat(Req *r)
{
	threadcreate(_θstat, r, 8192);
}

static char *
θwalk1(Fid *fid, char *name, void *)
{
	Fidaux *fa;
	char *npath, *sname, *spath;
	uvlong meta, x;
	int fd;

	fa = (Fidaux *)(fid->aux);
	npath = smprint("%s/%s", fa->path, name);
	meta = q2m(fa->store->fd, p2q(fa->store->fd, npath, 0), 0);
	if(meta == 0)
		return "does not exit";
	sname = getmetastr(fa->store->fd, meta, "snap");
	if(sname == nil) {
		free(fa->path);
		fa->path = npath;
	}
	else {
		free(npath);
		spath = smprint("%s/%s", ddir, sname);
		free(sname);
		fd = open(spath, OREAD);
		if(fd < 0)
			return "snap open";
		free(fa->path);
		fa->path = estrdup9p("/");
		if(decref(&fa->store->ref) == 0) {
			if(fa->store->fd != -1)
				close(fa->store->fd);
			free(fa->store);
		}
		fa->store = θmalloc(sizeof(Fdref));
		incref(&fa->store->ref);
		fa->store->fd = fd;
		meta = q2m(fa->store->fd, p2q(fa->store->fd, "/", 0), 0);
		if(meta == 0)
			return "no root";
	}
	if(getmetaint(fa->store->fd, meta, "qpath", &x) != MTint)
		return "no qid";
	fid->qid.path = x;
	getmetaint(fa->store->fd, meta, "qvers", &x);
	fid->qid.vers = x;
	getmetaint(fa->store->fd, meta, "qtype", &x);
	fid->qid.type = x;
	return nil;
}

static char *
θclone(Fid *oldfid, Fid *newfid, void *)
{
	Fidaux *ofa, *nfa;

	ofa = (Fidaux *)(oldfid->aux);
	nfa = newfid->aux = θmalloc(sizeof(Fidaux));
	*nfa = *ofa;
	nfa->path = estrdup9p(ofa->path);
	nfa->uname = estrdup9p(ofa->uname);
	incref(&nfa->store->ref);
	return nil;
}

static void
_θwalk(void *a)
{
	Req *r;
	Fdref *store;
	Fidaux *fa;
	char *npath, *p, *e;
	uvlong qp, meta, x;
	int nlen;
	int i, fd;

	r = a;
	fa = r->fid->aux;
	store = fa->store;
	fd = store->fd;
	if(r->ifcall.nwname == 1 && strcmp(r->ifcall.wname[0], "..") == 0) {
		npath = estrdup9p(fa->path);
		p = strrchr(npath, '/');
		if(p && p != npath)
			*p = 0;
	}
	else {
		nlen = strlen(fa->path);
		for(i = 0; i < r->ifcall.nwname; ++i)
			nlen += strlen(r->ifcall.wname[i]) + 1;
		npath = θmalloc(nlen + 1);
		p = npath;
		e = npath + nlen + 1;
		p = seprint(p, e, "%s", fa->path);
		for(i = 0; i < r->ifcall.nwname; ++i)
			p = seprint(p, e, "/%s", r->ifcall.wname[i]);
	}
	/*
	 * If we can get there directly, do it, otherwise, fall
	 * back to the one step at a time using walkandclone
	 */
	meta = q2m(fd, p2q(fd, npath, 0), 0);
	if(meta == 0) {
		walkandclone(r, θwalk1, θclone, nil);
		free(npath);
		threadexits(nil);
	}
	if(p = getmetastr(fd, meta, "snap")) {
		free(p);
		walkandclone(r, θwalk1, θclone, nil);
		free(npath);
		threadexits(nil);
	}
	fa = r->newfid->aux;
	if(r->fid == r->newfid)
		free(fa->path);
	else {
		fa = r->newfid->aux = θmalloc(sizeof(Fidaux));
		fa->uname = estrdup9p(((Fidaux *)(r->fid->aux))->uname);
		fa->store = store;
		incref(&store->ref);
	}
	fa->path = npath;
	if(r->ifcall.nwname == 0) {
		respond(r, nil);
		threadexits(nil);
	}
	r->ofcall.nwqid = r->ifcall.nwname;
	for(i = r->ifcall.nwname - 1; i >= 0; --i) {
		if(getmetaint(fd, meta, "qpath", &x) == MTnone) {
			respond(r, "errnt");
			threadexits(nil);
		}
		r->ofcall.wqid[i].path = x;
		getmetaint(fd, meta, "qvers", &x);
		r->ofcall.wqid[i].vers = x;
		getmetaint(fd, meta, "qtype", &x);
		r->ofcall.wqid[i].type = x;
		getmetaint(fd, meta, "parent", &qp);
		meta = q2m(fd, qp, 0);
	}
	respond(r, nil);
	threadexits(nil);
}

static void
θwalk(Req *r)
{
	threadcreate(_θwalk, r, 8192);
}

static void
_θwrite(void *a)
{
	Req *r;
	ulong tot;

	r = a;
	if(r->fid->qid.type & QTAPPEND)
		tot = θpwrite(r->fid->qid.path, r->ifcall.data, r->ifcall.count, 0, 2);
	else
		tot = θpwrite(r->fid->qid.path, r->ifcall.data, r->ifcall.count, r->ifcall.offset, 1);
	if(tot == -1) {
		respond(r, "no metadata");
		threadexits(nil);
	}
	r->ofcall.count = tot;
	respond(r, nil);
	threadexits(nil);
}

static void
θauthwrite(void *a)
{
	Req *r;

	r = a;
	authwrite(r);
	threadexits(nil);
}

static void
θwrite(Req *r)
{
	if(r->fid->qid.type & QTAUTH) {
		proccreate(θauthwrite, r, 8192);
		return;
	}
	threadcreate(_θwrite, r, 8192);
}

static void
_θwstat(void *a)
{
	Req *r;
	Fidaux *fa;
	Qid nqid;
	char *p, *gid, *uid, *newpath;
	uvlong meta, pmeta, x, pqpath;

	r = a;
	fa = r->fid->aux;
	meta = q2m(-1, r->fid->qid.path, 0);
	if(meta == 0) {
		respond(r, "no metadata");
		threadexits(nil);
	}
	p = strrchr(fa->path, '/');
	if(p && fa->path)
		newpath = smprint("%.*s/%s", (int)(p - fa->path), fa->path, r->d.name);
	else
		newpath = estrdup9p(r->d.name);

	if(allow)
		goto skipperm;
	uid = getmetastr(-1, meta, "uid");
	gid = getmetastr(-1, meta, "gid");

	/* Becuase wstat is defined to be all or none, first check all the permissions */
	if(strlen(r->d.name) > 0) {
		if(getmetaint(-1, meta, "parent", &pqpath) != MTnone && pqpath != 0) {
			pmeta = q2m(-1, pqpath, 0);
			if(pmeta != 0) {
				if(θhasperm(-1, pmeta, fa->uname, AWRITE) == 0) {
					free(newpath);
					free(gid);
					free(uid);
					respond(r, "permission denied");
					threadexits(nil);
				}
			}
		}
		if(q2m(-1, p2q(-1, newpath, 0), 0) != 0) {
			free(gid);
			free(uid);
			respond(r, "file extists");
			threadexits(nil);
		}
			
	}
	if(r->d.length != 0xffffffffffffffffLL) {
		if((r->fid->qid.type & QTDIR) && r->d.length != 0) {
			free(newpath);
			free(gid);
			free(uid);
			respond(r, "non-zero size on directory");
			threadexits(nil);
		}
		if(θhasperm(-1, meta, fa->uname, AWRITE) == 0) {
			free(newpath);
			free(gid);
			free(uid);
			respond(r, "permission denied");
			threadexits(nil);
		}
	}
	if(r->d.mode != 0xffffffff || r->d.mtime != 0xffffffff) {
		if(!(strcmp(fa->uname, uid) == 0 || isleader(fa->uname, gid))) {
			free(gid);
			free(uid);
			free(newpath);
			respond(r, "not owner");
			threadexits(nil);
		}
	}
	if(strlen(r->d.gid) > 0) {
		if(!(strcmp(fa->uname, uid) == 0 && ingroup(fa->uname, gid) || isleader(fa->uname, gid))) {
			free(gid);
			free(newpath);
			respond(r, "not owner");
			threadexits(nil);
		}
	}
	free(gid);
	free(uid);

skipperm:
	/* Now the we know we have permission, make all the changes */
	if(r->d.mode != 0xffffffff) {
		getmetaint(-1, meta, "qpath", &x);
		nqid.path = x;
		getmetaint(-1, meta, "qvers", &x);
		nqid.vers = x;
		getmetaint(-1, meta, "qtype", &x);
		nqid.type = x;
		x = nqid.type & QTDIR;
		if(r->d.mode & DMAPPEND)
			x |= QTAPPEND;
		if(r->d.mode & DMEXCL)
			x |= QTEXCL;
		if(r->d.mode & DMTMP)
			x |= QTTMP;
		if(x != nqid.type)
			setmetaint(meta, "qtype", nil, x);
		setmetaint(meta, "mode", nil, r->d.mode);
		if(getmetaint(-1, meta, "unixmode", &x) != MTnone)
			setmetaint(meta, "unixmode", nil, x & ~0777 | r->d.mode & 0777);
	}
	if(r->d.mtime != 0xffffffff)
		setmetaint(meta, "mtime", nil, r->d.mtime * 1000000000LL);
	if(r->d.length != 0xffffffffffffffffLL)
		setmetaint(meta, "length", nil, r->d.length);
	if(strlen(r->d.name) > 0) {
		setmetastr(meta, "name", nil, r->d.name, 0);
		rehashpath(r->fid->qid.path, fa->path, newpath);
		free(fa->path);
		fa->path = newpath;
	}
	if(allow && strlen(r->d.uid) > 0)
		setmetastr(meta, "uid", nil, r->d.uid, 0);
	if(strlen(r->d.gid) > 0)
		setmetastr(meta, "gid", nil, r->d.gid, 0);
	respond(r, nil);
	threadexits(nil);
}

static void
θwstat(Req *r)
{
	threadcreate(_θwstat, r, 8192);
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.