summaryrefslogtreecommitdiff
path: root/sys/src/cmd/cwfs/9p2.c
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/cwfs/9p2.c
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/cwfs/9p2.c')
-rwxr-xr-xsys/src/cmd/cwfs/9p2.c1861
1 files changed, 1861 insertions, 0 deletions
diff --git a/sys/src/cmd/cwfs/9p2.c b/sys/src/cmd/cwfs/9p2.c
new file mode 100755
index 000000000..c36c613eb
--- /dev/null
+++ b/sys/src/cmd/cwfs/9p2.c
@@ -0,0 +1,1861 @@
+#include "all.h"
+#include <fcall.h>
+
+enum { MSIZE = MAXDAT+MAXMSG };
+
+static int
+mkmode9p1(ulong mode9p2)
+{
+ int mode;
+
+ /*
+ * Assume this is for an allocated entry.
+ */
+ mode = DALLOC|(mode9p2 & 0777);
+ if(mode9p2 & DMEXCL)
+ mode |= DLOCK;
+ if(mode9p2 & DMAPPEND)
+ mode |= DAPND;
+ if(mode9p2 & DMDIR)
+ mode |= DDIR;
+
+ return mode;
+}
+
+void
+mkqid9p1(Qid9p1* qid9p1, Qid* qid)
+{
+ if(qid->path & 0xFFFFFFFF00000000LL)
+ panic("mkqid9p1: path %lluX", (Wideoff)qid->path);
+ qid9p1->path = qid->path & 0xFFFFFFFF;
+ if(qid->type & QTDIR)
+ qid9p1->path |= QPDIR;
+ qid9p1->version = qid->vers;
+}
+
+static int
+mktype9p2(int mode9p1)
+{
+ int type;
+
+ type = 0;
+ if(mode9p1 & DLOCK)
+ type |= QTEXCL;
+ if(mode9p1 & DAPND)
+ type |= QTAPPEND;
+ if(mode9p1 & DDIR)
+ type |= QTDIR;
+
+ return type;
+}
+
+static ulong
+mkmode9p2(int mode9p1)
+{
+ ulong mode;
+
+ mode = mode9p1 & 0777;
+ if(mode9p1 & DLOCK)
+ mode |= DMEXCL;
+ if(mode9p1 & DAPND)
+ mode |= DMAPPEND;
+ if(mode9p1 & DDIR)
+ mode |= DMDIR;
+
+ return mode;
+}
+
+void
+mkqid9p2(Qid* qid, Qid9p1* qid9p1, int mode9p1)
+{
+ qid->path = (ulong)(qid9p1->path & ~QPDIR);
+ qid->vers = qid9p1->version;
+ qid->type = mktype9p2(mode9p1);
+}
+
+static int
+mkdir9p2(Dir* dir, Dentry* dentry, void* strs)
+{
+ char *op, *p;
+
+ memset(dir, 0, sizeof(Dir));
+ mkqid(&dir->qid, dentry, 1);
+ dir->mode = mkmode9p2(dentry->mode);
+ dir->atime = dentry->atime;
+ dir->mtime = dentry->mtime;
+ dir->length = dentry->size;
+
+ op = p = strs;
+ dir->name = p;
+ p += sprint(p, "%s", dentry->name)+1;
+
+ dir->uid = p;
+ uidtostr(p, dentry->uid, 1);
+ p += strlen(p)+1;
+
+ dir->gid = p;
+ uidtostr(p, dentry->gid, 1);
+ p += strlen(p)+1;
+
+ dir->muid = p;
+ uidtostr(p, dentry->muid, 1);
+ p += strlen(p)+1;
+
+ return p-op;
+}
+
+static int
+checkname9p2(char* name)
+{
+ char *p;
+
+ /*
+ * Return error or 0 if OK.
+ */
+ if(name == nil || *name == 0)
+ return Ename;
+
+ for(p = name; *p != 0; p++){
+ if(p-name >= NAMELEN-1)
+ return Etoolong;
+ if((*p & 0xFF) <= 040)
+ return Ename;
+ }
+ if(strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
+ return Edot;
+
+ return 0;
+}
+
+static int
+version(Chan* chan, Fcall* f, Fcall* r)
+{
+ if(chan->protocol != nil)
+ return Eversion;
+
+ if(f->msize < MSIZE)
+ r->msize = f->msize;
+ else
+ r->msize = MSIZE;
+
+ /*
+ * Should check the '.' stuff here.
+ */
+ if(strcmp(f->version, VERSION9P) == 0){
+ r->version = VERSION9P;
+ chan->protocol = serve9p2;
+ chan->msize = r->msize;
+ } else
+ r->version = "unknown";
+
+ fileinit(chan);
+ return 0;
+}
+
+struct {
+ Lock;
+ ulong hi;
+} authpath;
+
+static int
+auth(Chan* chan, Fcall* f, Fcall* r)
+{
+ char *aname;
+ File *file;
+ Filsys *fs;
+ int error;
+
+ if(cons.flags & authdisableflag)
+ return Eauthdisabled;
+
+ error = 0;
+ aname = f->aname;
+
+ if(strcmp(f->uname, "none") == 0)
+ return Eauthnone;
+
+ if(!aname[0]) /* default */
+ aname = "main";
+ file = filep(chan, f->afid, 1);
+ if(file == nil){
+ error = Efidinuse;
+ goto out;
+ }
+ fs = fsstr(aname);
+ if(fs == nil){
+ error = Ebadspc;
+ goto out;
+ }
+ lock(&authpath);
+ file->qid.path = authpath.hi++;
+ unlock(&authpath);
+ file->qid.type = QTAUTH;
+ file->qid.vers = 0;
+ file->fs = fs;
+ file->open = FREAD+FWRITE;
+ freewp(file->wpath);
+ file->wpath = 0;
+ file->auth = authnew(f->uname, f->aname);
+ if(file->auth == nil){
+ error = Eauthfile;
+ goto out;
+ }
+ r->aqid = file->qid;
+
+out:
+ if((cons.flags & attachflag) && error)
+ print("9p2: auth %s %T SUCK EGGS --- %s\n",
+ f->uname, time(nil), errstr9p[error]);
+ if(file != nil){
+ qunlock(file);
+ if(error)
+ freefp(file);
+ }
+ return error;
+}
+
+int
+authorize(Chan* chan, Fcall* f)
+{
+ File* af;
+ int db, uid = -1;
+
+ db = cons.flags & authdebugflag;
+
+ if(strcmp(f->uname, "none") == 0){
+ uid = strtouid(f->uname);
+ if(db)
+ print("permission granted to none: uid %s = %d\n",
+ f->uname, uid);
+ return uid;
+ }
+
+ if(cons.flags & authdisableflag){
+ uid = strtouid(f->uname);
+ if(db)
+ print("permission granted by authdisable uid %s = %d\n",
+ f->uname, uid);
+ return uid;
+ }
+
+ af = filep(chan, f->afid, 0);
+ if(af == nil){
+ if(db)
+ print("authorize: af == nil\n");
+ return -1;
+ }
+ if(af->auth == nil){
+ if(db)
+ print("authorize: af->auth == nil\n");
+ goto out;
+ }
+ if(strcmp(f->uname, authuname(af->auth)) != 0){
+ if(db)
+ print("authorize: strcmp(f->uname, authuname(af->auth)) != 0\n");
+ goto out;
+ }
+ if(strcmp(f->aname, authaname(af->auth)) != 0){
+ if(db)
+ print("authorize: strcmp(f->aname, authaname(af->auth)) != 0\n");
+ goto out;
+ }
+ uid = authuid(af->auth);
+ if(db)
+ print("authorize: uid is %d\n", uid);
+out:
+ qunlock(af);
+ return uid;
+}
+
+static int
+attach(Chan* chan, Fcall* f, Fcall* r)
+{
+ char *aname;
+ Iobuf *p;
+ Dentry *d;
+ File *file;
+ Filsys *fs;
+ Off raddr;
+ int error, u;
+
+ aname = f->aname;
+ if(!aname[0]) /* default */
+ aname = "main";
+ p = nil;
+ error = 0;
+ file = filep(chan, f->fid, 1);
+ if(file == nil){
+ error = Efidinuse;
+ goto out;
+ }
+
+ u = -1;
+ if(chan != cons.chan){
+ if(noattach && strcmp(f->uname, "none")) {
+ error = Enoattach;
+ goto out;
+ }
+ u = authorize(chan, f);
+ if(u < 0){
+ error = Ebadu;
+ goto out;
+ }
+ }
+ file->uid = u;
+
+ fs = fsstr(aname);
+ if(fs == nil){
+ error = Ebadspc;
+ goto out;
+ }
+ raddr = getraddr(fs->dev);
+ p = getbuf(fs->dev, raddr, Brd);
+ if(p == nil || checktag(p, Tdir, QPROOT)){
+ error = Ealloc;
+ goto out;
+ }
+ d = getdir(p, 0);
+ if(d == nil || !(d->mode & DALLOC)){
+ error = Ealloc;
+ goto out;
+ }
+ if (iaccess(file, d, DEXEC) ||
+ file->uid == 0 && fs->dev->type == Devro) {
+ /*
+ * 'none' not allowed on dump
+ */
+ error = Eaccess;
+ goto out;
+ }
+ accessdir(p, d, FREAD, file->uid);
+ mkqid(&file->qid, d, 1);
+ file->fs = fs;
+ file->addr = raddr;
+ file->slot = 0;
+ file->open = 0;
+ freewp(file->wpath);
+ file->wpath = 0;
+
+ r->qid = file->qid;
+
+ strncpy(chan->whoname, f->uname, sizeof(chan->whoname));
+ chan->whotime = time(nil);
+ if(cons.flags & attachflag)
+ print("9p2: attach %s %T to \"%s\" C%d\n",
+ chan->whoname, chan->whotime, fs->name, chan->chan);
+
+out:
+ if((cons.flags & attachflag) && error)
+ print("9p2: attach %s %T SUCK EGGS --- %s\n",
+ f->uname, time(nil), errstr9p[error]);
+ if(p != nil)
+ putbuf(p);
+ if(file != nil){
+ qunlock(file);
+ if(error)
+ freefp(file);
+ }
+ return error;
+}
+
+static int
+flush(Chan* chan, Fcall*, Fcall*)
+{
+ runlock(&chan->reflock);
+ wlock(&chan->reflock);
+ wunlock(&chan->reflock);
+ rlock(&chan->reflock);
+
+ return 0;
+}
+
+static void
+clone(File* nfile, File* file)
+{
+ Wpath *wpath;
+
+ nfile->qid = file->qid;
+
+ lock(&wpathlock);
+ nfile->wpath = file->wpath;
+ for(wpath = nfile->wpath; wpath != nil; wpath = wpath->up)
+ wpath->refs++;
+ unlock(&wpathlock);
+
+ nfile->fs = file->fs;
+ nfile->addr = file->addr;
+ nfile->slot = file->slot;
+ nfile->uid = file->uid;
+ nfile->open = file->open & ~FREMOV;
+}
+
+static int
+walkname(File* file, char* wname, Qid* wqid)
+{
+ Wpath *w;
+ Iobuf *p, *p1;
+ Dentry *d, *d1;
+ int error, slot;
+ Off addr, qpath;
+
+ p = p1 = nil;
+
+ /*
+ * File must not have been opened for I/O by an open
+ * or create message and must represent a directory.
+ */
+ if(file->open != 0){
+ error = Emode;
+ goto out;
+ }
+
+ p = getbuf(file->fs->dev, file->addr, Brd);
+ if(p == nil || checktag(p, Tdir, QPNONE)){
+ error = Edir1;
+ goto out;
+ }
+ d = getdir(p, file->slot);
+ if(d == nil || !(d->mode & DALLOC)){
+ error = Ealloc;
+ goto out;
+ }
+ if(!(d->mode & DDIR)){
+ error = Edir1;
+ goto out;
+ }
+ if(error = mkqidcmp(&file->qid, d))
+ goto out;
+
+ /*
+ * For walked elements the implied user must
+ * have permission to search the directory.
+ */
+ if(file->cp != cons.chan && iaccess(file, d, DEXEC)){
+ error = Eaccess;
+ goto out;
+ }
+ accessdir(p, d, FREAD, file->uid);
+
+ if(strcmp(wname, ".") == 0){
+setdot:
+ if(wqid != nil)
+ *wqid = file->qid;
+ goto out;
+ }
+ if(strcmp(wname, "..") == 0){
+ if(file->wpath == 0)
+ goto setdot;
+ putbuf(p);
+ p = nil;
+ addr = file->wpath->addr;
+ slot = file->wpath->slot;
+ p1 = getbuf(file->fs->dev, addr, Brd);
+ if(p1 == nil || checktag(p1, Tdir, QPNONE)){
+ error = Edir1;
+ goto out;
+ }
+ d1 = getdir(p1, slot);
+ if(d == nil || !(d1->mode & DALLOC)){
+ error = Ephase;
+ goto out;
+ }
+ lock(&wpathlock);
+ file->wpath->refs--;
+ file->wpath = file->wpath->up;
+ unlock(&wpathlock);
+ goto found;
+ }
+
+ for(addr = 0; ; addr++){
+ if(p == nil){
+ p = getbuf(file->fs->dev, file->addr, Brd);
+ if(p == nil || checktag(p, Tdir, QPNONE)){
+ error = Ealloc;
+ goto out;
+ }
+ d = getdir(p, file->slot);
+ if(d == nil || !(d->mode & DALLOC)){
+ error = Ealloc;
+ goto out;
+ }
+ }
+ qpath = d->qid.path;
+ p1 = dnodebuf1(p, d, addr, 0, file->uid);
+ p = nil;
+ if(p1 == nil || checktag(p1, Tdir, qpath)){
+ error = Eentry;
+ goto out;
+ }
+ for(slot = 0; slot < DIRPERBUF; slot++){
+ d1 = getdir(p1, slot);
+ if (!(d1->mode & DALLOC) ||
+ strncmp(wname, d1->name, NAMELEN) != 0)
+ continue;
+ /*
+ * update walk path
+ */
+ if((w = newwp()) == nil){
+ error = Ewalk;
+ goto out;
+ }
+ w->addr = file->addr;
+ w->slot = file->slot;
+ w->up = file->wpath;
+ file->wpath = w;
+ slot += DIRPERBUF*addr;
+ goto found;
+ }
+ putbuf(p1);
+ p1 = nil;
+ }
+
+found:
+ file->addr = p1->addr;
+ mkqid(&file->qid, d1, 1);
+ putbuf(p1);
+ p1 = nil;
+ file->slot = slot;
+ if(wqid != nil)
+ *wqid = file->qid;
+
+out:
+ if(p1 != nil)
+ putbuf(p1);
+ if(p != nil)
+ putbuf(p);
+
+ return error;
+}
+
+static int
+walk(Chan* chan, Fcall* f, Fcall* r)
+{
+ int error, nwname;
+ File *file, *nfile, tfile;
+
+ /*
+ * The file identified by f->fid must be valid in the
+ * current session and must not have been opened for I/O
+ * by an open or create message.
+ */
+ if((file = filep(chan, f->fid, 0)) == nil)
+ return Efid;
+ if(file->open != 0){
+ qunlock(file);
+ return Emode;
+ }
+
+ /*
+ * If newfid is not the same as fid, allocate a new file;
+ * a side effect is checking newfid is not already in use (error);
+ * if there are no names to walk this will be equivalent to a
+ * simple 'clone' operation.
+ * Otherwise, fid and newfid are the same and if there are names
+ * to walk make a copy of 'file' to be used during the walk as
+ * 'file' must only be updated on success.
+ * Finally, it's a no-op if newfid is the same as fid and f->nwname
+ * is 0.
+ */
+ r->nwqid = 0;
+ if(f->newfid != f->fid){
+ if((nfile = filep(chan, f->newfid, 1)) == nil){
+ qunlock(file);
+ return Efidinuse;
+ }
+ } else if(f->nwname != 0){
+ nfile = &tfile;
+ memset(nfile, 0, sizeof(File));
+ nfile->cp = chan;
+ nfile->fid = ~0;
+ } else {
+ qunlock(file);
+ return 0;
+ }
+ clone(nfile, file);
+
+ /*
+ * Should check name is not too long.
+ */
+ error = 0;
+ for(nwname = 0; nwname < f->nwname; nwname++){
+ error = walkname(nfile, f->wname[nwname], &r->wqid[r->nwqid]);
+ if(error != 0 || ++r->nwqid >= MAXDAT/sizeof(Qid))
+ break;
+ }
+
+ if(f->nwname == 0){
+ /*
+ * Newfid must be different to fid (see above)
+ * so this is a simple 'clone' operation - there's
+ * nothing to do except unlock unless there's
+ * an error.
+ */
+ if(error){
+ freewp(nfile->wpath);
+ qunlock(nfile);
+ freefp(nfile);
+ } else
+ qunlock(nfile);
+ } else if(r->nwqid < f->nwname){
+ /*
+ * Didn't walk all elements, 'clunk' nfile
+ * and leave 'file' alone.
+ * Clear error if some of the elements were
+ * walked OK.
+ */
+ freewp(nfile->wpath);
+ if(nfile != &tfile){
+ qunlock(nfile);
+ freefp(nfile);
+ }
+ if(r->nwqid != 0)
+ error = 0;
+ } else {
+ /*
+ * Walked all elements. If newfid is the same
+ * as fid must update 'file' from the temporary
+ * copy used during the walk.
+ * Otherwise just unlock (when using tfile there's
+ * no need to unlock as it's a local).
+ */
+ if(nfile == &tfile){
+ file->qid = nfile->qid;
+ freewp(file->wpath);
+ file->wpath = nfile->wpath;
+ file->addr = nfile->addr;
+ file->slot = nfile->slot;
+ } else
+ qunlock(nfile);
+ }
+ qunlock(file);
+
+ return error;
+}
+
+static int
+fs_open(Chan* chan, Fcall* f, Fcall* r)
+{
+ Iobuf *p;
+ Dentry *d;
+ File *file;
+ Tlock *t;
+ Qid qid;
+ int error, ro, fmod, wok;
+
+ wok = 0;
+ p = nil;
+
+ if(chan == cons.chan || writeallow)
+ wok = 1;
+
+ if((file = filep(chan, f->fid, 0)) == nil){
+ error = Efid;
+ goto out;
+ }
+ if(file->open != 0){
+ error = Emode;
+ goto out;
+ }
+
+ /*
+ * if remove on close, check access here
+ */
+ ro = file->fs->dev->type == Devro;
+ if(f->mode & ORCLOSE){
+ if(ro){
+ error = Eronly;
+ goto out;
+ }
+ /*
+ * check on parent directory of file to be deleted
+ */
+ if(file->wpath == 0 || file->wpath->addr == file->addr){
+ error = Ephase;
+ goto out;
+ }
+ p = getbuf(file->fs->dev, file->wpath->addr, Brd);
+ if(p == nil || checktag(p, Tdir, QPNONE)){
+ error = Ephase;
+ goto out;
+ }
+ d = getdir(p, file->wpath->slot);
+ if(d == nil || !(d->mode & DALLOC)){
+ error = Ephase;
+ goto out;
+ }
+ if(iaccess(file, d, DWRITE)){
+ error = Eaccess;
+ goto out;
+ }
+ putbuf(p);
+ }
+ p = getbuf(file->fs->dev, file->addr, Brd);
+ if(p == nil || checktag(p, Tdir, QPNONE)){
+ error = Ealloc;
+ goto out;
+ }
+ d = getdir(p, file->slot);
+ if(d == nil || !(d->mode & DALLOC)){
+ error = Ealloc;
+ goto out;
+ }
+ if(error = mkqidcmp(&file->qid, d))
+ goto out;
+ mkqid(&qid, d, 1);
+ switch(f->mode & 7){
+
+ case OREAD:
+ if(iaccess(file, d, DREAD) && !wok)
+ goto badaccess;
+ fmod = FREAD;
+ break;
+
+ case OWRITE:
+ if((d->mode & DDIR) || (iaccess(file, d, DWRITE) && !wok))
+ goto badaccess;
+ if(ro){
+ error = Eronly;
+ goto out;
+ }
+ fmod = FWRITE;
+ break;
+
+ case ORDWR:
+ if((d->mode & DDIR)
+ || (iaccess(file, d, DREAD) && !wok)
+ || (iaccess(file, d, DWRITE) && !wok))
+ goto badaccess;
+ if(ro){
+ error = Eronly;
+ goto out;
+ }
+ fmod = FREAD+FWRITE;
+ break;
+
+ case OEXEC:
+ if((d->mode & DDIR) || (iaccess(file, d, DEXEC) && !wok))
+ goto badaccess;
+ fmod = FREAD;
+ break;
+
+ default:
+ error = Emode;
+ goto out;
+ }
+ if(f->mode & OTRUNC){
+ if((d->mode & DDIR) || (iaccess(file, d, DWRITE) && !wok))
+ goto badaccess;
+ if(ro){
+ error = Eronly;
+ goto out;
+ }
+ }
+ t = 0;
+ if(d->mode & DLOCK){
+ if((t = tlocked(p, d)) == nil){
+ error = Elocked;
+ goto out;
+ }
+ }
+ if(f->mode & ORCLOSE)
+ fmod |= FREMOV;
+ file->open = fmod;
+ if((f->mode & OTRUNC) && !(d->mode & DAPND)){
+ dtrunc(p, d, file->uid);
+ qid.vers = d->qid.version;
+ }
+ r->qid = qid;
+ file->tlock = t;
+ if(t != nil)
+ t->file = file;
+ file->lastra = 1;
+ goto out;
+
+badaccess:
+ error = Eaccess;
+ file->open = 0;
+
+out:
+ if(p != nil)
+ putbuf(p);
+ if(file != nil)
+ qunlock(file);
+
+ r->iounit = chan->msize-IOHDRSZ;
+
+ return error;
+}
+
+static int
+fs_create(Chan* chan, Fcall* f, Fcall* r)
+{
+ Iobuf *p, *p1;
+ Dentry *d, *d1;
+ File *file;
+ int error, slot, slot1, fmod, wok;
+ Off addr, addr1, path;
+ Tlock *t;
+ Wpath *w;
+
+ wok = 0;
+ p = nil;
+
+ if(chan == cons.chan || writeallow)
+ wok = 1;
+
+ if((file = filep(chan, f->fid, 0)) == nil){
+ error = Efid;
+ goto out;
+ }
+ if(file->fs->dev->type == Devro){
+ error = Eronly;
+ goto out;
+ }
+ if(file->qid.type & QTAUTH){
+ error = Emode;
+ goto out;
+ }
+
+ p = getbuf(file->fs->dev, file->addr, Brd);
+ if(p == nil || checktag(p, Tdir, QPNONE)){
+ error = Ealloc;
+ goto out;
+ }
+ d = getdir(p, file->slot);
+ if(d == nil || !(d->mode & DALLOC)){
+ error = Ealloc;
+ goto out;
+ }
+ if(error = mkqidcmp(&file->qid, d))
+ goto out;
+ if(!(d->mode & DDIR)){
+ error = Edir2;
+ goto out;
+ }
+ if(iaccess(file, d, DWRITE) && !wok) {
+ error = Eaccess;
+ goto out;
+ }
+ accessdir(p, d, FREAD, file->uid);
+
+ /*
+ * Check the name is valid (and will fit in an old
+ * directory entry for the moment).
+ */
+ if(error = checkname9p2(f->name))
+ goto out;
+
+ addr1 = 0;
+ slot1 = 0; /* set */
+ for(addr = 0; ; addr++){
+ if((p1 = dnodebuf(p, d, addr, 0, file->uid)) == nil){
+ if(addr1 != 0)
+ break;
+ p1 = dnodebuf(p, d, addr, Tdir, file->uid);
+ }
+ if(p1 == nil){
+ error = Efull;
+ goto out;
+ }
+ if(checktag(p1, Tdir, d->qid.path)){
+ putbuf(p1);
+ goto phase;
+ }
+ for(slot = 0; slot < DIRPERBUF; slot++){
+ d1 = getdir(p1, slot);
+ if(!(d1->mode & DALLOC)){
+ if(addr1 == 0){
+ addr1 = p1->addr;
+ slot1 = slot + addr*DIRPERBUF;
+ }
+ continue;
+ }
+ if(strncmp(f->name, d1->name, sizeof(d1->name)) == 0){
+ putbuf(p1);
+ error = Eexist;
+ goto out;
+ }
+ }
+ putbuf(p1);
+ }
+
+ switch(f->mode & 7){
+ case OEXEC:
+ case OREAD: /* seems only useful to make directories */
+ fmod = FREAD;
+ break;
+
+ case OWRITE:
+ fmod = FWRITE;
+ break;
+
+ case ORDWR:
+ fmod = FREAD+FWRITE;
+ break;
+
+ default:
+ error = Emode;
+ goto out;
+ }
+ if(f->perm & PDIR)
+ if((f->mode & OTRUNC) || (f->perm & PAPND) || (fmod & FWRITE))
+ goto badaccess;
+ /*
+ * do it
+ */
+ path = qidpathgen(file->fs->dev);
+ if((p1 = getbuf(file->fs->dev, addr1, Brd|Bimm|Bmod)) == nil)
+ goto phase;
+ d1 = getdir(p1, slot1);
+ if(d1 == nil || checktag(p1, Tdir, d->qid.path)) {
+ putbuf(p1);
+ goto phase;
+ }
+ if(d1->mode & DALLOC){
+ putbuf(p1);
+ goto phase;
+ }
+
+ strncpy(d1->name, f->name, sizeof(d1->name));
+ if(chan == cons.chan){
+ d1->uid = cons.uid;
+ d1->gid = cons.gid;
+ } else {
+ d1->uid = file->uid;
+ d1->gid = d->gid;
+ f->perm &= d->mode | ~0666;
+ if(f->perm & PDIR)
+ f->perm &= d->mode | ~0777;
+ }
+ d1->qid.path = path;
+ d1->qid.version = 0;
+ d1->mode = DALLOC | (f->perm & 0777);
+ if(f->perm & PDIR) {
+ d1->mode |= DDIR;
+ d1->qid.path |= QPDIR;
+ }
+ if(f->perm & PAPND)
+ d1->mode |= DAPND;
+ t = nil;
+ if(f->perm & PLOCK){
+ d1->mode |= DLOCK;
+ t = tlocked(p1, d1);
+ /* if nil, out of tlock structures */
+ }
+ accessdir(p1, d1, FWRITE, file->uid);
+ mkqid(&r->qid, d1, 0);
+ putbuf(p1);
+ accessdir(p, d, FWRITE, file->uid);
+
+ /*
+ * do a walk to new directory entry
+ */
+ if((w = newwp()) == nil){
+ error = Ewalk;
+ goto out;
+ }
+ w->addr = file->addr;
+ w->slot = file->slot;
+ w->up = file->wpath;
+ file->wpath = w;
+ file->qid = r->qid;
+ file->tlock = t;
+ if(t != nil)
+ t->file = file;
+ file->lastra = 1;
+ if(f->mode & ORCLOSE)
+ fmod |= FREMOV;
+ file->open = fmod;
+ file->addr = addr1;
+ file->slot = slot1;
+ goto out;
+
+badaccess:
+ error = Eaccess;
+ goto out;
+
+phase:
+ error = Ephase;
+
+out:
+ if(p != nil)
+ putbuf(p);
+ if(file != nil)
+ qunlock(file);
+
+ r->iounit = chan->msize-IOHDRSZ;
+
+ return error;
+}
+
+static int
+fs_read(Chan* chan, Fcall* f, Fcall* r, uchar* data)
+{
+ Iobuf *p, *p1;
+ File *file;
+ Dentry *d, *d1;
+ Tlock *t;
+ Off addr, offset, start;
+ Timet tim;
+ int error, iounit, nread, count, n, o, slot;
+ Msgbuf *dmb;
+ Dir dir;
+
+ p = nil;
+
+ error = 0;
+ count = f->count;
+ offset = f->offset;
+ nread = 0;
+ if((file = filep(chan, f->fid, 0)) == nil){
+ error = Efid;
+ goto out;
+ }
+ if(!(file->open & FREAD)){
+ error = Eopen;
+ goto out;
+ }
+ iounit = chan->msize-IOHDRSZ;
+ if(count < 0 || count > iounit){
+ error = Ecount;
+ goto out;
+ }
+ if(offset < 0){
+ error = Eoffset;
+ goto out;
+ }
+ if(file->qid.type & QTAUTH){
+ nread = authread(file, (uchar*)data, count);
+ if(nread < 0)
+ error = Eauth2;
+ goto out;
+ }
+ p = getbuf(file->fs->dev, file->addr, Brd);
+ if(p == nil || checktag(p, Tdir, QPNONE)){
+ error = Ealloc;
+ goto out;
+ }
+ d = getdir(p, file->slot);
+ if(d == nil || !(d->mode & DALLOC)){
+ error = Ealloc;
+ goto out;
+ }
+ if(error = mkqidcmp(&file->qid, d))
+ goto out;
+ if(t = file->tlock){
+ tim = toytime();
+ if(t->time < tim || t->file != file){
+ error = Ebroken;
+ goto out;
+ }
+ /* renew the lock */
+ t->time = tim + TLOCK;
+ }
+ accessdir(p, d, FREAD, file->uid);
+ if(d->mode & DDIR)
+ goto dread;
+ if(offset+count > d->size)
+ count = d->size - offset;
+ while(count > 0){
+ if(p == nil){
+ p = getbuf(file->fs->dev, file->addr, Brd);
+ if(p == nil || checktag(p, Tdir, QPNONE)){
+ error = Ealloc;
+ goto out;
+ }
+ d = getdir(p, file->slot);
+ if(d == nil || !(d->mode & DALLOC)){
+ error = Ealloc;
+ goto out;
+ }
+ }
+ addr = offset / BUFSIZE;
+ file->lastra = dbufread(p, d, addr, file->lastra, file->uid);
+ o = offset % BUFSIZE;
+ n = BUFSIZE - o;
+ if(n > count)
+ n = count;
+ p1 = dnodebuf1(p, d, addr, 0, file->uid);
+ p = nil;
+ if(p1 != nil){
+ if(checktag(p1, Tfile, QPNONE)){
+ error = Ephase;
+ putbuf(p1);
+ goto out;
+ }
+ memmove(data+nread, p1->iobuf+o, n);
+ putbuf(p1);
+ } else
+ memset(data+nread, 0, n);
+ count -= n;
+ nread += n;
+ offset += n;
+ }
+ goto out;
+
+dread:
+ /*
+ * Pick up where we left off last time if nothing has changed,
+ * otherwise must scan from the beginning.
+ */
+ if(offset == file->doffset /*&& file->qid.vers == file->dvers*/){
+ addr = file->dslot/DIRPERBUF;
+ slot = file->dslot%DIRPERBUF;
+ start = offset;
+ } else {
+ addr = 0;
+ slot = 0;
+ start = 0;
+ }
+
+ dmb = mballoc(iounit, chan, Mbreply1);
+ for (;;) {
+ if(p == nil){
+ /*
+ * This is just a check to ensure the entry hasn't
+ * gone away during the read of each directory block.
+ */
+ p = getbuf(file->fs->dev, file->addr, Brd);
+ if(p == nil || checktag(p, Tdir, QPNONE)){
+ error = Ealloc;
+ goto out1;
+ }
+ d = getdir(p, file->slot);
+ if(d == nil || !(d->mode & DALLOC)){
+ error = Ealloc;
+ goto out1;
+ }
+ }
+ p1 = dnodebuf1(p, d, addr, 0, file->uid);
+ p = nil;
+ if(p1 == nil)
+ goto out1;
+ if(checktag(p1, Tdir, QPNONE)){
+ error = Ephase;
+ putbuf(p1);
+ goto out1;
+ }
+
+ for(; slot < DIRPERBUF; slot++){
+ d1 = getdir(p1, slot);
+ if(!(d1->mode & DALLOC))
+ continue;
+ mkdir9p2(&dir, d1, dmb->data);
+ n = convD2M(&dir, data+nread, iounit - nread);
+ if(n <= BIT16SZ){
+ putbuf(p1);
+ goto out1;
+ }
+ start += n;
+ if(start < offset)
+ continue;
+ if(count < n){
+ putbuf(p1);
+ goto out1;
+ }
+ count -= n;
+ nread += n;
+ offset += n;
+ }
+ putbuf(p1);
+ slot = 0;
+ addr++;
+ }
+out1:
+ mbfree(dmb);
+ if(error == 0){
+ file->doffset = offset;
+ file->dvers = file->qid.vers;
+ file->dslot = slot+DIRPERBUF*addr;
+ }
+
+out:
+ /*
+ * Do we need this any more?
+ count = f->count - nread;
+ if(count > 0)
+ memset(data+nread, 0, count);
+ */
+ if(p != nil)
+ putbuf(p);
+ if(file != nil)
+ qunlock(file);
+ r->count = nread;
+ r->data = (char*)data;
+
+ return error;
+}
+
+static int
+fs_write(Chan* chan, Fcall* f, Fcall* r)
+{
+ Iobuf *p, *p1;
+ Dentry *d;
+ File *file;
+ Tlock *t;
+ Off offset, addr, qpath;
+ Timet tim;
+ int count, error, nwrite, o, n;
+
+ error = 0;
+ offset = f->offset;
+ count = f->count;
+
+ nwrite = 0;
+ p = nil;
+
+ if((file = filep(chan, f->fid, 0)) == nil){
+ error = Efid;
+ goto out;
+ }
+ if(!(file->open & FWRITE)){
+ error = Eopen;
+ goto out;
+ }
+ if(count < 0 || count > chan->msize-IOHDRSZ){
+ error = Ecount;
+ goto out;
+ }
+ if(offset < 0) {
+ error = Eoffset;
+ goto out;
+ }
+
+ if(file->qid.type & QTAUTH){
+ nwrite = authwrite(file, (uchar*)f->data, count);
+ if(nwrite < 0)
+ error = Eauth2;
+ goto out;
+ } else if(file->fs->dev->type == Devro){
+ error = Eronly;
+ goto out;
+ }
+
+ if ((p = getbuf(file->fs->dev, file->addr, Brd|Bmod)) == nil ||
+ (d = getdir(p, file->slot)) == nil || !(d->mode & DALLOC)) {
+ error = Ealloc;
+ goto out;
+ }
+ if(error = mkqidcmp(&file->qid, d))
+ goto out;
+ if(t = file->tlock) {
+ tim = toytime();
+ if(t->time < tim || t->file != file){
+ error = Ebroken;
+ goto out;
+ }
+ /* renew the lock */
+ t->time = tim + TLOCK;
+ }
+ accessdir(p, d, FWRITE, file->uid);
+ if(d->mode & DAPND)
+ offset = d->size;
+ if(offset+count > d->size)
+ d->size = offset+count;
+ while(count > 0){
+ if(p == nil){
+ p = getbuf(file->fs->dev, file->addr, Brd|Bmod);
+ if(p == nil){
+ error = Ealloc;
+ goto out;
+ }
+ d = getdir(p, file->slot);
+ if(d == nil || !(d->mode & DALLOC)){
+ error = Ealloc;
+ goto out;
+ }
+ }
+ addr = offset / BUFSIZE;
+ o = offset % BUFSIZE;
+ n = BUFSIZE - o;
+ if(n > count)
+ n = count;
+ qpath = d->qid.path;
+ p1 = dnodebuf1(p, d, addr, Tfile, file->uid);
+ p = nil;
+ if(p1 == nil) {
+ error = Efull;
+ goto out;
+ }
+ if(checktag(p1, Tfile, qpath)){
+ putbuf(p1);
+ error = Ephase;
+ goto out;
+ }
+ memmove(p1->iobuf+o, f->data+nwrite, n);
+ p1->flags |= Bmod;
+ putbuf(p1);
+ count -= n;
+ nwrite += n;
+ offset += n;
+ }
+
+out:
+ if(p != nil)
+ putbuf(p);
+ if(file != nil)
+ qunlock(file);
+ r->count = nwrite;
+
+ return error;
+}
+
+static int
+_clunk(File* file, int remove, int wok)
+{
+ Tlock *t;
+ int error;
+
+ error = 0;
+ if(t = file->tlock){
+ if(t->file == file)
+ t->time = 0; /* free the lock */
+ file->tlock = 0;
+ }
+ if(remove && (file->qid.type & QTAUTH) == 0)
+ error = doremove(file, wok);
+ file->open = 0;
+ freewp(file->wpath);
+ authfree(file->auth);
+ freefp(file);
+ qunlock(file);
+
+ return error;
+}
+
+static int
+clunk(Chan* chan, Fcall* f, Fcall*)
+{
+ File *file;
+
+ if((file = filep(chan, f->fid, 0)) == nil)
+ return Efid;
+
+ _clunk(file, file->open & FREMOV, 0);
+ return 0;
+}
+
+static int
+fs_remove(Chan* chan, Fcall* f, Fcall*)
+{
+ File *file;
+
+ if((file = filep(chan, f->fid, 0)) == nil)
+ return Efid;
+
+ return _clunk(file, 1, chan == cons.chan);
+}
+
+static int
+fs_stat(Chan* chan, Fcall* f, Fcall* r, uchar* data)
+{
+ Dir dir;
+ Iobuf *p;
+ Dentry *d, dentry;
+ File *file;
+ int error, len;
+
+ error = 0;
+ p = nil;
+ if((file = filep(chan, f->fid, 0)) == nil)
+ return Efid;
+ if(file->qid.type & QTAUTH){
+ memset(&dentry, 0, sizeof dentry);
+ d = &dentry;
+ mkqid9p1(&d->qid, &file->qid);
+ strcpy(d->name, "#¿");
+ d->uid = authuid(file->auth);
+ d->gid = d->uid;
+ d->muid = d->uid;
+ d->atime = time(nil);
+ d->mtime = d->atime;
+ d->size = 0;
+ } else {
+ p = getbuf(file->fs->dev, file->addr, Brd);
+ if(p == nil || checktag(p, Tdir, QPNONE)){
+ error = Edir1;
+ goto out;
+ }
+ d = getdir(p, file->slot);
+ if(d == nil || !(d->mode & DALLOC)){
+ error = Ealloc;
+ goto out;
+ }
+ if(error = mkqidcmp(&file->qid, d))
+ goto out;
+
+ if(d->qid.path == QPROOT) /* stat of root gives time */
+ d->atime = time(nil);
+ }
+ len = mkdir9p2(&dir, d, data);
+ data += len;
+
+ if((r->nstat = convD2M(&dir, data, chan->msize - len)) == 0)
+ error = Eedge;
+ r->stat = data;
+
+out:
+ if(p != nil)
+ putbuf(p);
+ if(file != nil)
+ qunlock(file);
+
+ return error;
+}
+
+static int
+fs_wstat(Chan* chan, Fcall* f, Fcall*, char* strs)
+{
+ Iobuf *p, *p1;
+ Dentry *d, *d1;
+ File *file;
+ int error, err, gid, gl, muid, op, slot, tsync, uid;
+ long addr;
+ Dir dir;
+
+ if(convM2D(f->stat, f->nstat, &dir, strs) == 0)
+ return Econvert;
+
+ /*
+ * Get the file.
+ * If user 'none' (uid == 0), can't do anything;
+ * if filesystem is read-only, can't change anything.
+ */
+ if((file = filep(chan, f->fid, 0)) == nil)
+ return Efid;
+ p = p1 = nil;
+ if(file->uid == 0){
+ error = Eaccess;
+ goto out;
+ }
+ if(file->fs->dev->type == Devro){
+ error = Eronly;
+ goto out;
+ }
+ if(file->qid.type & QTAUTH){
+ error = Emode;
+ goto out;
+ }
+
+ /*
+ * Get the current entry and check it is still valid.
+ */
+ p = getbuf(file->fs->dev, file->addr, Brd);
+ if(p == nil || checktag(p, Tdir, QPNONE)){
+ error = Ealloc;
+ goto out;
+ }
+ d = getdir(p, file->slot);
+ if(d == nil || !(d->mode & DALLOC)){
+ error = Ealloc;
+ goto out;
+ }
+ if(error = mkqidcmp(&file->qid, d))
+ goto out;
+
+ /*
+ * Run through each of the (sub-)fields in the provided Dir
+ * checking for validity and whether it's a default:
+ * .type, .dev and .atime are completely ignored and not checked;
+ * .qid.path, .qid.vers and .muid are checked for validity but
+ * any attempt to change them is an error.
+ * .qid.type/.mode, .mtime, .name, .length, .uid and .gid can
+ * possibly be changed (and .muid iff wstatallow).
+ *
+ * 'Op' flags there are changed fields, i.e. it's not a no-op.
+ * 'Tsync' flags all fields are defaulted.
+ *
+ * Wstatallow and writeallow are set to allow changes during the
+ * fileserver bootstrap phase.
+ */
+ tsync = 1;
+ if(dir.qid.path != ~0){
+ if(dir.qid.path != file->qid.path){
+ error = Ewstatp;
+ goto out;
+ }
+ tsync = 0;
+ }
+ if(dir.qid.vers != ~0){
+ if(dir.qid.vers != file->qid.vers){
+ error = Ewstatv;
+ goto out;
+ }
+ tsync = 0;
+ }
+
+ /*
+ * .qid.type and .mode have some bits in common. Only .mode
+ * is currently needed for comparisons with the old mode but
+ * if there are changes to the bits also encoded in .qid.type
+ * then file->qid must be updated appropriately later.
+ */
+ if(dir.qid.type == (uchar)~0){
+ if(dir.mode == ~0)
+ dir.qid.type = mktype9p2(d->mode);
+ else
+ dir.qid.type = dir.mode>>24;
+ } else
+ tsync = 0;
+ if(dir.mode == ~0)
+ dir.mode = mkmode9p2(d->mode);
+ else
+ tsync = 0;
+
+ /*
+ * Check dir.qid.type and dir.mode agree, check for any unknown
+ * type/mode bits, check for an attempt to change the directory bit.
+ */
+ if(dir.qid.type != ((dir.mode>>24) & 0xFF)){
+ error = Ewstatq;
+ goto out;
+ }
+ if(dir.mode & ~(DMDIR|DMAPPEND|DMEXCL|0777)){
+ error = Ewstatb;
+ goto out;
+ }
+
+ op = dir.mode^mkmode9p2(d->mode);
+ if(op & DMDIR){
+ error = Ewstatd;
+ goto out;
+ }
+
+ if(dir.mtime != ~0){
+ if(dir.mtime != d->mtime)
+ op = 1;
+ tsync = 0;
+ } else
+ dir.mtime = d->mtime;
+
+ if(dir.length == ~(Off)0)
+ dir.length = d->size;
+ else {
+ if (dir.length < 0) {
+ error = Ewstatl;
+ goto out;
+ } else if(dir.length != d->size)
+ op = 1;
+ tsync = 0;
+ }
+
+ /*
+ * Check for permission to change .mode, .mtime or .length,
+ * must be owner or leader of either group, for which test gid
+ * is needed; permission checks on gid will be done later.
+ * 'Gl' counts whether neither, one or both groups are led.
+ */
+ if(dir.gid != nil && *dir.gid != '\0'){
+ gid = strtouid(dir.gid);
+ tsync = 0;
+ } else
+ gid = d->gid;
+ gl = leadgroup(file->uid, gid) != 0;
+ gl += leadgroup(file->uid, d->gid) != 0;
+
+ if(op && !wstatallow && d->uid != file->uid && !gl){
+ error = Ewstato;
+ goto out;
+ }
+
+ /*
+ * Rename.
+ * Check .name is valid and different to the current.
+ */
+ if(dir.name != nil && *dir.name != '\0'){
+ if(error = checkname9p2(dir.name))
+ goto out;
+ if(strncmp(dir.name, d->name, NAMELEN))
+ op = 1;
+ else
+ dir.name = d->name;
+ tsync = 0;
+ } else
+ dir.name = d->name;
+
+ /*
+ * If the name is really to be changed check it's unique
+ * and there is write permission in the parent.
+ */
+ if(dir.name != d->name){
+ /*
+ * First get parent.
+ * Must drop current entry to prevent
+ * deadlock when searching that new name
+ * already exists below.
+ */
+ putbuf(p);
+ p = nil;
+
+ if(file->wpath == nil){
+ error = Ephase;
+ goto out;
+ }
+ p1 = getbuf(file->fs->dev, file->wpath->addr, Brd);
+ if(p1 == nil || checktag(p1, Tdir, QPNONE)){
+ error = Ephase;
+ goto out;
+ }
+ d1 = getdir(p1, file->wpath->slot);
+ if(d1 == nil || !(d1->mode & DALLOC)){
+ error = Ephase;
+ goto out;
+ }
+
+ /*
+ * Check entries in parent for new name.
+ */
+ for(addr = 0; ; addr++){
+ if((p = dnodebuf(p1, d1, addr, 0, file->uid)) == nil)
+ break;
+ if(checktag(p, Tdir, d1->qid.path)){
+ putbuf(p);
+ continue;
+ }
+ for(slot = 0; slot < DIRPERBUF; slot++){
+ d = getdir(p, slot);
+ if(!(d->mode & DALLOC) ||
+ strncmp(dir.name, d->name, sizeof d->name))
+ continue;
+ error = Eexist;
+ goto out;
+ }
+ putbuf(p);
+ }
+
+ /*
+ * Reacquire entry and check it's still OK.
+ */
+ p = getbuf(file->fs->dev, file->addr, Brd);
+ if(p == nil || checktag(p, Tdir, QPNONE)){
+ error = Ephase;
+ goto out;
+ }
+ d = getdir(p, file->slot);
+ if(d == nil || !(d->mode & DALLOC)){
+ error = Ephase;
+ goto out;
+ }
+
+ /*
+ * Check write permission in the parent.
+ */
+ if(!wstatallow && !writeallow && iaccess(file, d1, DWRITE)){
+ error = Eaccess;
+ goto out;
+ }
+ }
+
+ /*
+ * Check for permission to change owner - must be god.
+ */
+ if(dir.uid != nil && *dir.uid != '\0'){
+ uid = strtouid(dir.uid);
+ if(uid != d->uid){
+ if(!wstatallow){
+ error = Ewstatu;
+ goto out;
+ }
+ op = 1;
+ }
+ tsync = 0;
+ } else
+ uid = d->uid;
+ if(dir.muid != nil && *dir.muid != '\0'){
+ muid = strtouid(dir.muid);
+ if(muid != d->muid){
+ if(!wstatallow){
+ error = Ewstatm;
+ goto out;
+ }
+ op = 1;
+ }
+ tsync = 0;
+ } else
+ muid = d->muid;
+
+ /*
+ * Check for permission to change group, must be
+ * either owner and in new group or leader of both groups.
+ */
+ if(gid != d->gid){
+ if(!(wstatallow || writeallow)
+ && !(d->uid == file->uid && ingroup(file->uid, gid))
+ && !(gl == 2)){
+ error = Ewstatg;
+ goto out;
+ }
+ op = 1;
+ }
+
+ /*
+ * Checks all done, update if necessary.
+ */
+ if(op){
+ d->mode = mkmode9p1(dir.mode);
+ file->qid.type = mktype9p2(d->mode);
+ d->mtime = dir.mtime;
+ if (dir.length < d->size) {
+ err = dtrunclen(p, d, dir.length, uid);
+ if (error == 0)
+ error = err;
+ }
+ d->size = dir.length;
+ if(dir.name != d->name)
+ strncpy(d->name, dir.name, sizeof(d->name));
+ d->uid = uid;
+ d->gid = gid;
+ d->muid = muid;
+ }
+ if(!tsync)
+ accessdir(p, d, FREAD, file->uid);
+
+out:
+ if(p != nil)
+ putbuf(p);
+ if(p1 != nil)
+ putbuf(p1);
+ qunlock(file);
+
+ return error;
+}
+
+int
+serve9p2(Msgbuf* mb)
+{
+ Chan *chan;
+ Fcall f, r;
+ Msgbuf *data, *rmb;
+ char ename[64];
+ int error, n, type;
+ static int once;
+
+ if(once == 0){
+ fmtinstall('F', fcallfmt);
+ once = 1;
+ }
+
+ /*
+ * 0 return means i don't understand this message,
+ * 1 return means i dealt with it, including error
+ * replies.
+ */
+ if(convM2S(mb->data, mb->count, &f) != mb->count)
+{
+print("didn't like %d byte message\n", mb->count);
+ return 0;
+}
+ type = f.type;
+ if(type < Tversion || type >= Tmax || (type & 1) || type == Terror)
+ return 0;
+
+ chan = mb->chan;
+ if(CHAT(chan))
+ print("9p2: f %F\n", &f);
+ r.type = type+1;
+ r.tag = f.tag;
+ error = 0;
+ data = nil;
+
+ switch(type){
+ default:
+ r.type = Rerror;
+ snprint(ename, sizeof(ename), "unknown message: %F", &f);
+ r.ename = ename;
+ break;
+ case Tversion:
+ error = version(chan, &f, &r);
+ break;
+ case Tauth:
+ error = auth(chan, &f, &r);
+ break;
+ case Tattach:
+ error = attach(chan, &f, &r);
+ break;
+ case Tflush:
+ error = flush(chan, &f, &r);
+ break;
+ case Twalk:
+ error = walk(chan, &f, &r);
+ break;
+ case Topen:
+ error = fs_open(chan, &f, &r);
+ break;
+ case Tcreate:
+ error = fs_create(chan, &f, &r);
+ break;
+ case Tread:
+ data = mballoc(chan->msize, chan, Mbreply1);
+ error = fs_read(chan, &f, &r, data->data);
+ break;
+ case Twrite:
+ error = fs_write(chan, &f, &r);
+ break;
+ case Tclunk:
+ error = clunk(chan, &f, &r);
+ break;
+ case Tremove:
+ error = fs_remove(chan, &f, &r);
+ break;
+ case Tstat:
+ data = mballoc(chan->msize, chan, Mbreply1);
+ error = fs_stat(chan, &f, &r, data->data);
+ break;
+ case Twstat:
+ data = mballoc(chan->msize, chan, Mbreply1);
+ error = fs_wstat(chan, &f, &r, (char*)data->data);
+ break;
+ }
+
+ if(error != 0){
+ r.type = Rerror;
+ if(error >= MAXERR){
+ snprint(ename, sizeof(ename), "error %d", error);
+ r.ename = ename;
+ } else
+ r.ename = errstr9p[error];
+ }
+ if(CHAT(chan))
+ print("9p2: r %F\n", &r);
+
+ rmb = mballoc(chan->msize, chan, Mbreply2);
+ n = convS2M(&r, rmb->data, chan->msize);
+ if(data != nil)
+ mbfree(data);
+ if(n == 0){
+ type = r.type;
+ r.type = Rerror;
+
+ /*
+ * If a Tversion has not been seen on the chan then
+ * chan->msize will be 0. In that case craft a special
+ * Rerror message. It's fortunate that the mballoc above
+ * for rmb will have returned a Msgbuf of MAXMSG size
+ * when given a request with count of 0...
+ */
+ if(chan->msize == 0){
+ r.ename = "Tversion not seen";
+ n = convS2M(&r, rmb->data, MAXMSG);
+ } else {
+ snprint(ename, sizeof(ename), "9p2: convS2M: type %d",
+ type);
+ r.ename = ename;
+ n = convS2M(&r, rmb->data, chan->msize);
+ }
+ print("%s\n", r.ename);
+ if(n == 0){
+ /*
+ * What to do here, the failure notification failed?
+ */
+ mbfree(rmb);
+ return 1;
+ }
+ }
+ rmb->count = n;
+ rmb->param = mb->param;
+
+ /* done 9P processing, write reply to network */
+ fs_send(chan->reply, rmb);
+
+ return 1;
+}