/* * 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 #include #include #include #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(""); } /* 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); }