diff --git a/sys/src/cmd/fscfs/cfs.c b/sys/src/cmd/fscfs/cfs.c new file mode 100644 index 00000000..57b8a914 --- /dev/null +++ b/sys/src/cmd/fscfs/cfs.c @@ -0,0 +1,2043 @@ +#include +#include +#include +#include +#include + +/* + * to do: + * - concurrency? + * - only worthwhile with several connections, perhaps to several file servers + * - cache directories + * - message size (dynamic buffers) + * - more useful stats + * - notes + * - lru for sfids in paths (also correct ref counts for paths) + */ + +#include "dat.h" +#include "fns.h" +#include "stats.h" + +#define DPRINT if(debug)fprint + +/* maximum length of a file (used for unknown lengths) */ +enum { MAXLEN = ((uvlong)1<<63)-1 }; + +int debug, statson, noauth, openserver; + +#define MAXFDATA 8192 /* i/o size for read/write */ + +struct P9fs +{ + int fd[2]; + Fcall thdr; + Fcall rhdr; + uchar sndbuf[MAXFDATA+IOHDRSZ]; /* TO DO: dynamic */ + uchar rcvbuf[2][MAXFDATA + IOHDRSZ]; /* extra buffer to protect client data over server calls */ + int cb; /* current buffer */ + long len; + char *name; +}; + +P9fs c; /* client conversation */ +P9fs s; /* server conversation */ + +Host host[1]; /* clients, just the one for now */ + +Cfsstat cfsstat, cfsprev; +char statbuf[2048]; +int statlen; + +int messagesize = MAXFDATA+IOHDRSZ; /* must be the same for all connections */ + +Qid rootqid; +Qid ctlqid = {0x5555555555555555LL, 0, 0}; + +uint cachesize = 16 * 1024 * 1024; + +/* + * clients directly access directories, auth files, append-only files, exclusive-use files, and mount points + */ +#define isdirect(qid) (((qid).type & ~QTTMP) != QTFILE) + +static char Efidinuse[] = "fid in use"; +static char Ebadfid[] = "invalid fid"; +static char Enotdir[] = "not a directory"; +static char Enonexist[] = "file does not exist"; +static char Eexist[] = "file already exists"; +static char Eopened[] = "opened for I/O"; +static char Emode[] = "bad open mode"; +static char Eremoved[] = "file has been removed"; + +static SFid* freefids; +static u32int fidgen; + +static Data datalist; + +static Auth* authlist; + +void +usage(void) +{ + fprint(2, "usage: %s [-a netaddr | -F srv] [-dknS] [mntpt]\n", argv0); + threadexitsall("usage"); +} + +void +threadmain(int argc, char *argv[]) +{ + int std; + char *server, *mtpt; + + std = 0; + server = "net!$server"; + mtpt = "/n/remote"; + + ARGBEGIN{ + case 'a': + server = EARGF(usage()); + break; + case 'd': + debug = 1; + break; + case 'F': + server = EARGF(usage()); + openserver = 1; + break; + case 'm': + cachesize = atoi(EARGF(usage())) * 1024 * 1024; + if(cachesize < 8 * 1024 * 1024 || + cachesize > 3750UL * 1024 * 1024) + sysfatal("implausible cache size %ud", cachesize); + break; + case 'n': + noauth = 1; + break; + case 'S': + statson = 1; + break; + case 's': + std = 1; + break; + default: + usage(); + }ARGEND + if(argc && *argv) + mtpt = *argv; + + quotefmtinstall(); + if(debug){ + fmtinstall('F', fcallfmt); + fmtinstall('D', dirfmt); + fmtinstall('M', dirmodefmt); + } + + c.name = "client"; + s.name = "server"; + if(std){ + c.fd[0] = c.fd[1] = 1; + s.fd[0] = s.fd[1] = 0; + }else + mountinit(server, mtpt); + + switch(fork()){ + case 0: + io(); + threadexits(""); + case -1: + error("fork"); + default: + _exits(""); + } +} + +/* + * TO DO: need to use libthread variants + */ +void +mountinit(char *server, char *mountpoint) +{ + int err; + int p[2]; + + /* + * grab a channel and call up the file server + */ + if(openserver){ + s.fd[0] = open(server, ORDWR); + if(s.fd[0] < 0) + error("opening srv file %s: %r", server); + } else { + s.fd[0] = dial(netmkaddr(server, 0, "9fs"), 0, 0, 0); + if(s.fd[0] < 0) + error("dialing %s: %r", server); + } + s.fd[1] = s.fd[0]; + + /* + * mount onto name space + */ + if(pipe(p) < 0) + error("pipe failed"); + switch(fork()){ + case 0: + break; + default: + rfork(RFFDG); + close(p[0]); + if(noauth) + err = mount(p[1], -1, mountpoint, MREPL|MCREATE|MCACHE, ""); + else + err = amount(p[1], mountpoint, MREPL|MCREATE|MCACHE, ""); + if(err < 0) + error("mount failed: %r"); + _exits(0); + case -1: + error("fork failed\n"); +/*BUG: no wait!*/ + } + close(p[1]); + c.fd[0] = c.fd[1] = p[0]; +} + +void +io(void) +{ + Fcall *t; + + datainit(); + t = &c.thdr; + + loop: + rcvmsg(&c, t); + + if(statson){ + cfsstat.cm[t->type].n++; + cfsstat.cm[t->type].s = nsec(); + } + switch(t->type){ + default: + sendreply("invalid 9p operation"); + break; + case Tversion: + rversion(t); + break; + case Tauth: + rauth(t); + break; + case Tflush: + rflush(t); + break; + case Tattach: + rattach(t); + break; + case Twalk: + rwalk(t); + break; + case Topen: + ropen(t); + break; + case Tcreate: + rcreate(t); + break; + case Tread: + rread(t); + break; + case Twrite: + rwrite(t); + break; + case Tclunk: + rclunk(t); + break; + case Tremove: + rremove(t); + break; + case Tstat: + rstat(t); + break; + case Twstat: + rwstat(t); + break; + } + if(statson){ + cfsstat.cm[t->type].t += nsec() -cfsstat.cm[t->type].s; + } + goto loop; +} + +void +rversion(Fcall *t) +{ + if(messagesize > t->msize) + messagesize = t->msize; + t->msize = messagesize; /* set downstream size */ + delegate(t, nil, nil); +} + +void +rauth(Fcall *t) +{ + Fid *mf; + + mf = findfid(host, t->afid, 1); + if(mf == nil) + return; + mf->path = newpath(nil, "#auth", (Qid){0, 0, 0}); /* path name is never used */ + mf->opened = allocsfid(); /* newly allocated */ + if(delegate(t, mf, nil) == 0){ + mf->qid = s.rhdr.aqid; + mf->mode = ORDWR; + }else{ + freesfid(mf->opened); /* free, don't clunk: failed to establish */ + mf->opened = nil; + putfid(mf); + } +} + +void +rflush(Fcall*) /* synchronous so easy */ +{ + /*TO DO: need a tag hash to find requests, once auth is asynchronous */ + sendreply(0); +} + +void +rattach(Fcall *t) +{ + Fid *mf, *afid; + SFid *sfid; + + mf = findfid(host, t->fid, 1); + if(mf == nil) + return; + sfid = nil; + if(t->afid != NOFID){ + afid = findfid(host, t->afid, 0); + if(afid == nil) + return; + sfid = afid->opened; + if((afid->qid.type & QTAUTH) == 0 || sfid == nil){ + sendreply("bad auth file"); + return; + } + } + mf->path = newpath(nil, "", (Qid){0, 0, 0}); + mf->path->sfid = allocsfid(); /* newly allocated */ + if(delegate(t, mf, sfid) == 0){ + mf->qid = s.rhdr.qid; + mf->path->qid = mf->qid; + if(statson == 1){ + statson++; + rootqid = mf->qid; + } + }else{ + freesfid(mf->path->sfid); /* free, don't clunk: failed to establish */ + mf->path->sfid = nil; + putfid(mf); + } +} + +void +rwalk(Fcall *t) +{ + Fid *mf, *nmf; + SFid *sfid; + Path *p; + char *n; + int i; + + mf = findfid(host, t->fid, 0); + if(mf == nil) + return; + if(mf->path){ + DPRINT(2, "walk from %p %q + %d\n", mf, pathstr(mf->path), t->nwname); + } + nmf = nil; + if(t->newfid != t->fid){ + nmf = findfid(host, t->newfid, 1); + if(nmf == nil) + return; + nmf->qid = mf->qid; + if(nmf->path != nil) + freepath(nmf->path); + mf->path->ref++; + nmf->path = mf->path; + mf = nmf; /* Walk mf */ + } + + if(statson && + mf->qid.type == rootqid.type && mf->qid.path == rootqid.path && + t->nwname == 1 && strcmp(t->wname[0], "cfsctl") == 0){ + /* This is the ctl file */ + mf->qid = ctlqid; + c.rhdr.nwqid = 1; + c.rhdr.wqid[0] = ctlqid; + sendreply(0); + return; + } + + /* + * need this as an invariant, and it should always be true, because + * Twalk.fid should exist and thus have sfid. could compensate by + * rewalking from nearest parent with sfid (in the limit, root from attach) + */ + assert(mf->path->sfid != nil); + + if(t->nwname == 0){ + /* new local fid (if any) refers to existing path, shares its sfid via that path */ + c.rhdr.nwqid = 0; + sendreply(0); + return; + } + + /* t->nwname != 0 */ + + if(localwalk(mf, &c.thdr, &c.rhdr, &p)){ + /* it all worked or is known to fail (in all or in part) */ + if(c.rhdr.type == Rerror){ + DPRINT(2, "walk error: %q\n", c.rhdr.ename); + sendreply(c.rhdr.ename); + if(nmf != nil) + putfid(nmf); + return; + } + if(c.rhdr.nwqid != t->nwname){ + DPRINT(2, "partial walk, hit error\n"); + sendreply(0); + if(nmf != nil) + putfid(nmf); + return; + } + DPRINT(2, "seen it: %q %q\n", pathstr(p), p->inval); + if(p->sfid != nil){ + p->ref++; + freepath(mf->path); + mf->path = p; + if(c.rhdr.nwqid > 0) + mf->qid = c.rhdr.wqid[c.rhdr.nwqid-1]; + sendreply(0); + return; + } + /* know the path, but need a fid on the server */ + } + + /* walk to a new server fid (ie, server always sees fid != newfid) */ + sfid = allocsfid(); /* must release it, if walk doesn't work */ + + if(delegate(t, mf, sfid) < 0){ /* complete failure */ + if(t->nwname > 0){ /* cache first item's failure */ + DPRINT(2, "bad path: %q + %q -> %q\n", pathstr(mf->path), t->wname[0], s.rhdr.ename); + badpath(mf->path, t->wname[0], s.rhdr.ename); + } + if(nmf != nil) + putfid(nmf); + freesfid(sfid); /* no need to clunk it, since it hasn't been assigned */ + return; + } + + if(s.rhdr.nwqid == t->nwname){ /* complete success */ + for(i = 0; i < t->nwname; i++){ + n = t->wname[i]; + if(strcmp(n, "..") == 0){ + p = mf->path->parent; + if(p != nil){ /* otherwise at root, no change */ + p->ref++; + freepath(mf->path); + mf->path = p; + } + }else + mf->path = newpath(mf->path, n, s.rhdr.wqid[i]); + } + mf->qid = s.rhdr.wqid[s.rhdr.nwqid-1]; /* nwname != 0 (above) */ + if(mf->path->sfid != nil){ + /* shouldn't happen (otherwise we wouldn't walk, because localwalk would find it) */ + sysfatal("rwalk: unexpected sfid: %q -> %ud\n", pathstr(mf->path), mf->path->sfid->fid); + } + mf->path->sfid = sfid; + return; + } + + /* partial success; note success and failure, and release fid */ + p = mf->path; + for(i = 0; i < s.rhdr.nwqid; i++){ + n = t->wname[i]; + if(strcmp(n, "..") == 0){ + if(p->parent != nil) + p = p->parent; + }else + p = newpath(p, n, s.rhdr.wqid[i]); + } + n = t->wname[i]; + if(i == 0 || s.rhdr.wqid[i-1].type & QTDIR) + badpath(p, n, Enonexist); + if(nmf != nil) + putfid(nmf); + freesfid(sfid); +} + +int +localwalk(Fid *mf, Fcall *t, Fcall *r, Path **pp) +{ + Path *p, *q; + char *n, *err; + int i; + + *pp = nil; + if(t->nwname == 0) + return 0; /* clone */ + if(mf->path == nil) + return 0; + r->type = Rwalk; + r->nwqid = 0; + p = mf->path; + for(i = 0; i < t->nwname; i++){ + n = t->wname[i]; + if((err = p->inval) != nil) + goto Err; + if((p->qid.type & QTDIR) == 0){ + err = Enotdir; + goto Err; + } + if(strcmp(n, "..") == 0){ + if((q = p->parent) == nil) + q = p; + }else{ + for(q = p->child; q != nil; q = q->next){ + if(strcmp(n, q->name) == 0){ + if((err = q->inval) != nil) + goto Err; + break; + } + } + if(q == nil) + return 0; /* not found in cache, must try server */ + } + r->wqid[r->nwqid++] = q->qid; + p = q; + } + /* found them all: if p's not locally NOFID, link incoming fid to it as local value */ + *pp = p; + return 1; + +Err: + if(r->nwqid == 0){ + r->type = Rerror; + r->ename = err; + } + return 1; +} + +void +ropen(Fcall *t) +{ + Fid *mf; + SFid *sfid; + int omode; + File *file; + + mf = findfid(host, t->fid, 0); + if(mf == nil) + return; + if(mf->opened != nil){ + sendreply(Eopened); + return; + } + omode = openmode(t->mode); + if(omode < 0){ + sendreply(Emode); + return; + } + if(statson && ctltest(mf)){ + /* Opening ctl file */ + if(t->mode != OREAD){ + sendreply("permission denied"); + return; + } + mf->mode = OREAD; + c.rhdr.qid = ctlqid; + c.rhdr.iounit = 0; + sendreply(0); + genstats(); + return; + } + if(t->mode & (ORCLOSE|OTRUNC) || isdirect(mf->qid)){ + /* must have private sfid */ + DPRINT(2, "open dir/auth: %q\n", pathstr(mf->path)); + sfid = sfclone(mf->path->sfid); + if(delegate(t, mf, sfid) < 0){ + sfclunk(sfid); + return; + } + mf->qid = s.rhdr.qid; + /* don't currently need iounit */ + }else if((sfid = alreadyopen(mf, omode)) == nil){ + sfid = sfclone(mf->path->sfid); + DPRINT(2, "new open %q -> %ud\n", pathstr(mf->path), sfid->fid); + if(delegate(t, mf, sfid) < 0){ + sfclunk(sfid); + return; + } + if(mf->qid.type != s.rhdr.qid.type || mf->qid.path != s.rhdr.qid.path){ + /* file changed type or naming is volatile */ + print("file changed underfoot: %q %#ux %llud -> %#ux %llud\n", + pathstr(mf->path), mf->qid.type, mf->qid.path, s.rhdr.qid.type, s.rhdr.qid.path); + mf->path->qid = mf->qid; + fileinval(mf->path); + } + mf->qid = s.rhdr.qid; /* picks up vers */ + openfile(mf, omode, s.rhdr.iounit, sfid); + }else{ + DPRINT(2, "cached open %q -> %ud\n", pathstr(mf->path), sfid->fid); + c.rhdr.qid = mf->path->file->qid; + c.rhdr.iounit = mf->path->file->iounit; + sendreply(0); + } + mf->opened = sfid; + mf->mode = omode; + if(t->mode & OTRUNC && !(mf->qid.type & QTAPPEND)){ + file = mf->path->file; + if(file != nil){ + cacheinval(file); /* discard cached data */ + file->length = 0; + } + } +} + +void +rcreate(Fcall *t) +{ + Fid *mf; + SFid *sfid; + int omode; + + mf = findfid(host, t->fid, 0); + if(mf == nil) + return; + if(statson && ctltest(mf)){ + sendreply(Eexist); + return; + } + omode = openmode(t->mode); + if(omode < 0){ + sendreply(Emode); + return; + } + /* always write-through, and by definition can't exist and be open */ + sfid = sfclone(mf->path->sfid); + if(delegate(t, mf, sfid) == 0){ + mf->opened = sfid; + mf->mode = omode; + mf->qid = s.rhdr.qid; + mf->path = newpath(mf->path, t->name, mf->qid); + if(!(t->mode & ORCLOSE || isdirect(mf->qid))){ + /* can cache (if it's volatile, find out on subsequent open) */ + openfile(mf, omode, s.rhdr.iounit, sfid); + } + //mf->iounit = s.rhdr.iounit; + }else + sfclunk(sfid); +} + +void +rclunk(Fcall *t) +{ + Fid *mf; + + mf = findfid(host, t->fid, 0); + if(mf == nil) + return; + if(mf->opened != nil) + closefile(mf, 1, 0); + /* local clunk, not delegated */ + sendreply(0); + putfid(mf); +} + +void +rremove(Fcall *t) +{ + Fid *mf; + Path *p; + SFid *sfid; + File *file; + + mf = findfid(host, t->fid, 0); + if(mf == nil) + return; + /* invalidate path entry; discard file; discard any local fids in use */ + if(statson && ctltest(mf)){ + sendreply("not removed"); + return; + } + file = nil; + p = mf->path; + if(delegate(t, mf, nil) == 0){ + setinval(p, Eremoved); + file = p->file; + p->file = nil; + } + /* in any case, the fid was clunked: free the sfid */ + if(mf->opened == nil){ + sfid = p->sfid; + p->sfid = nil; + freesfid(sfid); + }else + closefile(mf, 0, 1); + putfid(mf); + if(file != nil) + putfile(file); +} + +void +rread(Fcall *t) +{ + Fid *mf; + int cnt, done; + long n; + vlong off, first; + char *cp; + File *file; + char data[MAXFDATA]; + + mf = findfid(host, t->fid, 0); + if(mf == nil) + return; + + off = t->offset; + cnt = t->count; + + if(statson && ctltest(mf)){ + if(cnt > statlen-off) + c.rhdr.count = statlen-off; + else + c.rhdr.count = cnt; + if((int)c.rhdr.count < 0){ + c.rhdr.count = 0; + sendreply(0); + return; + } + c.rhdr.data = statbuf + off; + sendreply(0); + return; + } + + if(mf->opened == nil || mf->mode == OWRITE){ + sendreply("not open for reading"); + return; + } + + file = mf->path->file; + if(isdirect(mf->qid) || file == nil){ + DPRINT(2, "delegating read\n"); + delegate(t, mf, nil); + if(statson){ + if(mf->qid.type & QTDIR) + cfsstat.ndirread++; + else + cfsstat.ndelegateread++; + if(s.rhdr.count > 0){ + cfsstat.bytesread += s.rhdr.count; + if(mf->qid.type & QTDIR) + cfsstat.bytesfromdirs += s.rhdr.count; + else + cfsstat.bytesfromserver += s.rhdr.count; + } + } + return; + } + + first = off; + cp = data; + done = 0; + while(cnt>0 && !done){ + if(off >= file->clength){ + DPRINT(2, "offset %lld >= length %lld\n", off, file->clength); + break; + } + n = cacheread(file, cp, off, cnt); + if(n <= 0){ + n = -n; + if(n==0 || n>cnt) + n = cnt; + DPRINT(2, "fetch %ld bytes of data from server at offset %lld\n", n, off); + s.thdr.tag = t->tag; + s.thdr.type = t->type; + s.thdr.fid = t->fid; + s.thdr.offset = off; + s.thdr.count = n; + if(statson) + cfsstat.ndelegateread++; + if(askserver(t, mf) < 0){ + sendreply(s.rhdr.ename); + return; + } + if(s.rhdr.count != n) + done = 1; + n = s.rhdr.count; + if(n == 0){ + /* end of file */ + if(file->clength > off){ + DPRINT(2, "file %llud.%ld, length %lld\n", + file->qid.path, + file->qid.vers, off); + file->clength = off; + } + break; + } + memmove(cp, s.rhdr.data, n); + cachewrite(file, cp, off, n); + if(statson){ + cfsstat.bytestocache += n; + cfsstat.bytesfromserver += n; + } + }else{ + DPRINT(2, "fetched %ld bytes from cache\n", n); + if(statson) + cfsstat.bytesfromcache += n; + } + cnt -= n; + off += n; + cp += n; + } + c.rhdr.data = data; + c.rhdr.count = off - first; + if(statson) + cfsstat.bytesread += c.rhdr.count; + sendreply(0); +} + +void +rwrite(Fcall *t) +{ + Fid *mf; + File *file; + u32int v1, v2, count; + + mf = findfid(host, t->fid, 0); + if(mf == nil) + return; + if(statson && ctltest(mf)){ + sendreply("read only"); + return; + } + if(mf->opened == nil || mf->mode == OREAD){ + sendreply("not open for writing"); + return; + } + file = mf->path->file; + if(isdirect(mf->qid) || file == nil){ + delegate(t, mf, nil); + if(statson && s.rhdr.count > 0) + cfsstat.byteswritten += s.rhdr.count; + return; + } + + /* add to local cache as write through */ + if(delegate(t, mf, nil) < 0) + return; + + count = s.rhdr.count; + cfsstat.byteswritten += count; + + v1 = mf->qid.vers + 1; + v2 = mf->path->qid.vers + 1; + if(v1 > v2) + v1 = v2; + mf->qid.vers = v1; + mf->path->qid.vers = v1; + file->qid.vers = v1; + if(file->clength < t->offset + count) + file->clength = t->offset + count; + cachewrite(file, t->data, t->offset, count); +} + +void +rstat(Fcall *t) +{ + Fid *mf; + Dir d; + uchar statbuf[256]; + + mf = findfid(host, t->fid, 0); + if(mf == nil) + return; + if(statson && ctltest(mf)){ + genstats(); + d.qid = ctlqid; + d.mode = 0444; + d.length = statlen; + d.name = "cfsctl"; + d.uid = "none"; + d.gid = "none"; + d.muid = "none"; + d.atime = time(nil); + d.mtime = d.atime; + c.rhdr.stat = statbuf; + c.rhdr.nstat = convD2M(&d, statbuf, sizeof(statbuf)); + sendreply(0); + return; + } + if(delegate(t, mf, nil) == 0){ + convM2D(s.rhdr.stat, s.rhdr.nstat , &d, nil); + //mf->qid = d.qid; + if(mf->path->file != nil) + copystat(mf->path->file, &d, 0); + } +} + +void +rwstat(Fcall *t) +{ + Fid *mf; + Path *p; + Dir *d; + int qt; + + mf = findfid(host, t->fid, 0); + if(mf == nil) + return; + if(statson && ctltest(mf)){ + sendreply("read only"); + return; + } + if(delegate(t, mf, nil) == 0){ + d = malloc(sizeof(*d)+t->nstat); + if(convM2D(t->stat, t->nstat, d, (char*)(d+1)) != 0){ + if(*d->name){ /* file renamed */ + p = mf->path; + free(p->name); + p->name = strdup(d->name); + } + if(d->mode != ~0){ + qt = d->mode>>24; + mf->qid.type = qt; + mf->path->qid.type = qt; + } + if(mf->path->file != nil) + copystat(mf->path->file, d, 1); + } + free(d); + } +} + +int +openmode(uint o) +{ + uint m; + + m = o & ~(OTRUNC|OCEXEC|ORCLOSE); + if(m > OEXEC) + return -1; + if(m == OEXEC) + m = OREAD; + if(o&OTRUNC && m == OREAD) + return -1; + return m; +} + +void +error(char *fmt, ...) +{ + va_list arg; + static char buf[2048]; + + va_start(arg, fmt); + vseprint(buf, buf+sizeof(buf), fmt, arg); + va_end(arg); + fprint(2, "%s: %s\n", argv0, buf); + threadexitsall("error"); +} + +void +warning(char *s) +{ + fprint(2, "%s: %s: %r\n", argv0, s); +} + +/* + * send a reply to the client + */ +void +sendreply(char *err) +{ + if(err){ + c.rhdr.type = Rerror; + c.rhdr.ename = err; + }else{ + c.rhdr.type = c.thdr.type+1; + c.rhdr.fid = c.thdr.fid; + } + c.rhdr.tag = c.thdr.tag; + sendmsg(&c, &c.rhdr); +} + +/* + * local fids + */ +Fid* +findfid(Host *host, u32int fid, int mk) +{ + Fid *f, **l; + + if(fid == NOFID){ + sendreply(Ebadfid); + return nil; + } + l = &host->fids[fid & (FidHash-1)]; + for(f = *l; f != nil; f = f->next){ + if(f->fid == fid){ + if(mk){ + sendreply(Efidinuse); + return nil; + } + return f; + } + } + if(!mk){ + sendreply(Ebadfid); + return nil; + } + f = mallocz(sizeof(*f), 1); + f->fid = fid; + f->next = *l; + *l = f; + return f; +} + +void +putfid(Fid *f) +{ + Fid **l; + static Qid zeroqid; + + for(l = &host->fids[f->fid & (FidHash-1)]; *l != nil; l = &(*l)->next){ + if(*l == f){ + *l = f->next; + break; + } + } + if(f->opened != nil){ + sfclunk(f->opened); + f->opened = nil; + } + freepath(f->path); + /* poison values just in case */ + f->path = nil; + f->fid = NOFID; + f->qid = zeroqid; + free(f); +} + +/* + * return server fid for local fid + */ +u32int +mapfid(Fid *f) +{ + if(f->opened != nil){ + DPRINT(2, "mapfid: use open fid %ud -> %ud\n", f->fid, f->opened->fid); + return f->opened->fid; + } + if(f->path->sfid == nil) + sysfatal("mapfid: missing sfid for path %q\n", pathstr(f->path)); + return f->path->sfid->fid; +} + +/* + * send a request to the server, get the reply, + * and send that to the client + */ +int +delegate(Fcall *t, Fid *f1, SFid *f2) +{ + int type; + + type = t->type; + if(statson){ + cfsstat.sm[type].n++; + cfsstat.sm[type].s = nsec(); + } + + switch(type){ + case Tversion: + case Tflush: /* no fid */ + break; + case Tauth: /* afid */ + t->afid = mapfid(f1); + break; + case Tattach: /* fid, afid */ + t->fid = mapfid(f1); + if(f2 != nil) + t->afid = f2->fid; + else + t->afid = NOFID; + break; + case Twalk: /* fid, newfid */ + t->fid = mapfid(f1); + t->newfid = f2->fid; + break; + default: /* fid */ + if(f2 != nil) + t->fid = f2->fid; + else + t->fid = mapfid(f1); + break; + } + + sendmsg(&s, t); + rcvmsg(&s, &s.rhdr); + + if(statson) + cfsstat.sm[type].t += nsec() - cfsstat.sm[type].s; + + sendmsg(&c, &s.rhdr); + return t->type+1 == s.rhdr.type? 0: -1; +} + +/* + * send an i/o request to the server and get a reply + */ +int +askserver(Fcall *t, Fid *f) +{ + int type; + + s.thdr.tag = t->tag; + + type = s.thdr.type; + if(statson){ + cfsstat.sm[type].n++; + cfsstat.sm[type].s = nsec(); + } + + s.thdr.fid = mapfid(f); + sendmsg(&s, &s.thdr); + rcvmsg(&s, &s.rhdr); + + if(statson) + cfsstat.sm[type].t += nsec() - cfsstat.sm[type].s; + + return s.thdr.type+1 == s.rhdr.type ? 0 : -1; +} + +/* + * send/receive messages with logging + */ + +void +sendmsg(P9fs *p, Fcall *f) +{ + DPRINT(2, "->%s: %F\n", p->name, f); + + p->len = convS2M(f, p->sndbuf, messagesize); + if(p->len <= 0) + error("convS2M"); + if(write(p->fd[1], p->sndbuf, p->len)!=p->len) + error("sendmsg"); +} + +void +rcvmsg(P9fs *p, Fcall *f) +{ + int rlen; + char buf[128]; + + p->len = read9pmsg(p->fd[0], p->rcvbuf[p->cb], sizeof(p->rcvbuf[0])); + if(p->len <= 0){ + snprint(buf, sizeof buf, "read9pmsg(%d)->%ld: %r", + p->fd[0], p->len); + error(buf); + } + + if((rlen = convM2S(p->rcvbuf[p->cb], p->len, f)) != p->len) + error("rcvmsg format error, expected length %d, got %d", + rlen, p->len); + DPRINT(2, "<-%s: %F\n", p->name, f); + p->cb = (p->cb+1)%nelem(p->rcvbuf); +} + +void +dump(void *a, int len) +{ + uchar *p; + + p = a; + fprint(2, "%d bytes", len); + while(len-- > 0) + fprint(2, " %.2ux", *p++); + fprint(2, "\n"); +} + +/* + * requests (later, unused now) + */ + +void +readtmsgproc(void *a) +{ + //uint messagesize; + int n; + Req *r; + Channel *reqs; + + reqs = a; + for(;;){ + r = malloc(sizeof(*r)+messagesize); + r->msize = messagesize; + r->buf = (uchar*)(r+1); + n = read9pmsg(c.fd[0], r->buf, r->msize); + if(n <= 0){ + free(r); + sendp(reqs, nil); + threadexits(nil); + } + if(convM2S(r->buf, n, &r->t) != n){ + free(r); + error("convM2S error in readtmsgproc"); + } + sendp(reqs, r); + } +} + +void +readrmsgproc(void *a) +{ + //uint messagesize; + int n; + Fcall *r; + uchar *buf; + Channel *reps; + + reps = a; + for(;;){ + r = malloc(sizeof(*r)+messagesize); + buf = (uchar*)(r+1); + n = read9pmsg(c.fd[0], buf, messagesize); + if(n <= 0){ + free(r); + sendp(reps, nil); + threadexits(nil); + } + if(convM2S(buf, n, r) != n){ + free(r); + error("convM2S error in readtmsgproc"); + } + sendp(reps, r); + } +} + +void +freereq(Req *r) +{ + free(r); +} + +/* + * server fids +*/ + +SFid* +allocsfid(void) +{ + SFid *sf; + + sf = freefids; + if(sf != nil){ + freefids = sf->next; + sf->ref = 1; + return sf; + } + sf = mallocz(sizeof(*sf), 1); + sf->ref = 1; + sf->fid = ++fidgen; + return sf; +} + +void +freesfid(SFid *sf) +{ + if(--sf->ref != 0) + return; + /* leave sf->fid alone */ + sf->next = freefids; + freefids = sf; +} + +SFid* +sfclone(SFid *sf) +{ + Fcall t, r; + SFid *cf; + + cf = allocsfid(); + t.tag = c.thdr.tag; + t.type = Twalk; + t.fid = sf->fid; + t.newfid = cf->fid; + t.nwname = 0; + sendmsg(&s, &t); + rcvmsg(&s, &r); + if(r.type == Rerror){ + werrstr("%s", r.ename); + return nil; + } + assert(r.type == Rwalk); /* TO DO: out of order */ + return cf; +} + +Dir* +sfstat(Path *p, u32int tag) +{ + Fcall t, r; + Dir *d; + + assert(p->sfid != nil); + t.tag = tag; + t.type = Tstat; + t.fid = p->sfid->fid; + sendmsg(&s, &t); + rcvmsg(&s, &r); + if(r.type == Rerror){ + werrstr("%s", r.ename); + return nil; + } + d = malloc(sizeof(*d)+r.nstat); + if(convM2D(r.stat, r.nstat, d, (char*)(d+1)) == 0){ + free(d); + werrstr("invalid stat data"); + return nil; + } + return d; +} + +void +sfclunk(SFid *sf) +{ + Fcall t, r; + + if(sf->ref > 1) + return; + t.tag = c.thdr.tag; + t.type = Tclunk; + t.fid = sf->fid; + sendmsg(&s, &t); + rcvmsg(&s, &r); + /* don't care about result */ +} + +void +printpath(Path *p, int level) +{ + Path *q; + + for(int i = 0; i < level; i++) + print("\t"); + print("%q [%lud] %q\n", p->name, p->ref, p->inval?p->inval:""); + for(q = p->child; q != nil; q = q->next) + printpath(q, level+1); +} + +int +ctltest(Fid *mf) +{ + return mf->qid.type == ctlqid.type && mf->qid.path == ctlqid.path; +} + +char *mname[]={ + [Tversion] "Tversion", + [Tauth] "Tauth", + [Tflush] "Tflush", + [Tattach] "Tattach", + [Twalk] "Twalk", + [Topen] "Topen", + [Tcreate] "Tcreate", + [Tclunk] "Tclunk", + [Tread] "Tread", + [Twrite] "Twrite", + [Tremove] "Tremove", + [Tstat] "Tstat", + [Twstat] "Twstat", + [Rversion] "Rversion", + [Rauth] "Rauth", + [Rerror] "Rerror", + [Rflush] "Rflush", + [Rattach] "Rattach", + [Rwalk] "Rwalk", + [Ropen] "Ropen", + [Rcreate] "Rcreate", + [Rclunk] "Rclunk", + [Rread] "Rread", + [Rwrite] "Rwrite", + [Rremove] "Rremove", + [Rstat] "Rstat", + [Rwstat] "Rwstat", + 0, +}; + +void +genstats(void) +{ + int i; + char *p; + + p = statbuf; + + p += snprint(p, sizeof statbuf+statbuf-p, + " Client Server\n"); + p += snprint(p, sizeof statbuf+statbuf-p, + " #calls Δ ms/call Δ #calls Δ ms/call Δ\n"); + for(i = 0; i < nelem(cfsstat.cm); i++) + if(cfsstat.cm[i].n || cfsstat.sm[i].n){ + p += snprint(p, sizeof statbuf+statbuf-p, + "%7lud %7lud ", cfsstat.cm[i].n, + cfsstat.cm[i].n - cfsprev.cm[i].n); + if(cfsstat.cm[i].n) + p += snprint(p, sizeof statbuf+statbuf-p, + "%7.3f ", 0.000001*cfsstat.cm[i].t/ + cfsstat.cm[i].n); + else + p += snprint(p, sizeof statbuf+statbuf-p, + " "); + if(cfsstat.cm[i].n - cfsprev.cm[i].n) + p += snprint(p, sizeof statbuf+statbuf-p, + "%7.3f ", 0.000001* + (cfsstat.cm[i].t - cfsprev.cm[i].t)/ + (cfsstat.cm[i].n - cfsprev.cm[i].n)); + else + p += snprint(p, sizeof statbuf+statbuf-p, + " "); + p += snprint(p, sizeof statbuf+statbuf-p, + "%7lud %7lud ", cfsstat.sm[i].n, + cfsstat.sm[i].n - cfsprev.sm[i].n); + if(cfsstat.sm[i].n) + p += snprint(p, sizeof statbuf+statbuf-p, + "%7.3f ", 0.000001*cfsstat.sm[i].t/ + cfsstat.sm[i].n); + else + p += snprint(p, sizeof statbuf+statbuf-p, + " "); + if(cfsstat.sm[i].n - cfsprev.sm[i].n) + p += snprint(p, sizeof statbuf+statbuf-p, + "%7.3f ", 0.000001* + (cfsstat.sm[i].t - cfsprev.sm[i].t)/ + (cfsstat.sm[i].n - cfsprev.sm[i].n)); + else + p += snprint(p, sizeof statbuf+statbuf-p, + " "); + p += snprint(p, sizeof statbuf+statbuf-p, "%s\n", + mname[i]); + } + p += snprint(p, sizeof statbuf+statbuf-p, "%7lud %7lud ndirread\n", + cfsstat.ndirread, cfsstat.ndirread - cfsprev.ndirread); + p += snprint(p, sizeof statbuf+statbuf-p, "%7lud %7lud ndelegateread\n", + cfsstat.ndelegateread, cfsstat.ndelegateread - + cfsprev.ndelegateread); + p += snprint(p, sizeof statbuf+statbuf-p, "%7lud %7lud ninsert\n", + cfsstat.ninsert, cfsstat.ninsert - cfsprev.ninsert); + p += snprint(p, sizeof statbuf+statbuf-p, "%7lud %7lud ndelete\n", + cfsstat.ndelete, cfsstat.ndelete - cfsprev.ndelete); + p += snprint(p, sizeof statbuf+statbuf-p, "%7lud %7lud nupdate\n", + cfsstat.nupdate, cfsstat.nupdate - cfsprev.nupdate); + + p += snprint(p, sizeof statbuf+statbuf-p, "%7llud %7llud bytesread\n", + cfsstat.bytesread, cfsstat.bytesread - cfsprev.bytesread); + p += snprint(p, sizeof statbuf+statbuf-p, "%7llud %7llud byteswritten\n", + cfsstat.byteswritten, cfsstat.byteswritten - + cfsprev.byteswritten); + p += snprint(p, sizeof statbuf+statbuf-p, "%7llud %7llud bytesfromserver\n", + cfsstat.bytesfromserver, cfsstat.bytesfromserver - + cfsprev.bytesfromserver); + p += snprint(p, sizeof statbuf+statbuf-p, "%7llud %7llud bytesfromdirs\n", + cfsstat.bytesfromdirs, cfsstat.bytesfromdirs - + cfsprev.bytesfromdirs); + p += snprint(p, sizeof statbuf+statbuf-p, "%7llud %7llud bytesfromcache\n", + cfsstat.bytesfromcache, cfsstat.bytesfromcache - + cfsprev.bytesfromcache); + p += snprint(p, sizeof statbuf+statbuf-p, "%7llud %7llud bytestocache\n", + cfsstat.bytestocache, cfsstat.bytestocache - + cfsprev.bytestocache); + statlen = p - statbuf; + cfsprev = cfsstat; +} + +/* + * paths + */ + +Path* +newpath(Path *parent, char *name, Qid qid) +{ + Path *p; + + if(parent != nil){ + for(p = parent->child; p != nil; p = p->next){ + if(strcmp(p->name, name) == 0){ + if(p->inval != nil){ + free(p->inval); + p->inval = nil; + } + p->qid = qid; + p->ref++; + return p; + } + } + } + p = mallocz(sizeof(*p), 1); + p->ref = 2; + if(name != nil) + p->name = strdup(name); + p->qid = qid; + p->parent = parent; + if(parent != nil){ + parent->ref++; + p->next = parent->child; + parent->child = p; + } + return p; +} + +void +setinval(Path *p, char *err) +{ + if(p->inval != nil){ + free(p->inval); + p->inval = nil; + } + if(err != nil) + p->inval = strdup(err); +} + +void +badpath(Path *parent, char *name, char *err) +{ + Path *p; + + for(p = parent->child; p != nil; p = p->next){ + if(strcmp(p->name, name) == 0){ + setinval(p, err); + return; + } + } + p = mallocz(sizeof(*p), 1); + p->ref = 2; + if(name != nil) + p->name = strdup(name); + p->inval = strdup(err); + p->parent = parent; + if(parent != nil){ + parent->ref++; + p->next = parent->child; + parent->child = p; + } +} + +void +fileinval(Path *p) +{ + if(p->file != nil){ + putfile(p->file); + p->file = nil; + } +} + +void +freepath(Path *p) +{ + Path *q, **r; + + while(p != nil && --p->ref == 0){ + if(p->child != nil) + error("freepath child"); + q = p->parent; + if(q != nil){ + for(r = &q->child; *r != nil; r = &(*r)->next){ + if(*r == p){ + *r = p->next; + break; + } + } + } + if(p->sfid != nil){ + /* TO DO: could queue these for a great clunking proc */ + sfclunk(p->sfid); + p->sfid = nil; + } + if(p->inval != nil) + free(p->inval); + if(p->file != nil) + putfile(p->file); + free(p->name); + free(p); + p = q; + } +} + +static char* +pathstr1(Path *p, char *ep) +{ + ep -= strlen(p->name); + memmove(ep, p->name, strlen(p->name)); + if(p->parent != nil){ + *--ep = '/'; + ep = pathstr1(p->parent, ep); + } + return ep; +} + +char* +pathstr(Path *p) +{ + static char buf[1000]; + return pathstr1(p, buf+sizeof(buf)-1); +} + +/* + * files + */ + +void +openfile(Fid *mf, int omode, u32int iounit, SFid *sfid) +{ + /* open mf and cache open File at p */ + Path *p; + File *file; + SFid *osfid; + + p = mf->path; + file = p->file; + if(file == nil){ + file = mallocz(sizeof(*file), 1); + file->ref = 1; + file->qid = mf->qid; /* TO DO: check for clone files */ + file->clength = MAXLEN; + p->file = file; + } + osfid = file->open[omode]; + if(osfid != nil){ + DPRINT(2, "openfile: existing sfid %ud open %d\n", osfid->fid, omode); + return; + } + DPRINT(2, "openfile: cached %ud for %q mode %d\n", sfid->fid, pathstr(p), omode); + sfid->ref++; + file->open[omode] = sfid; + if(file->iounit == 0) + file->iounit = iounit; /* BUG: might vary */ +} + +void +closefile(Fid *mf, int mustclunk, int removed) +{ + Path *p; + File *file; + SFid *sfid, *osfid; + + if((sfid = mf->opened) == nil) + return; + p = mf->path; + file = p->file; + if(file == nil){ /* TO DO: messy */ + mf->opened = nil; + if(sfid->ref == 1 && mustclunk) + sfclunk(sfid); + freesfid(sfid); + return; + } + osfid = file->open[mf->mode]; + if(osfid == sfid){ + if(removed || sfid->ref == 1) + file->open[mf->mode] = nil; + if(sfid->ref == 1){ + if(mustclunk) + sfclunk(sfid); + freesfid(sfid); + }else + sfid->ref--; + } + mf->opened = nil; +} + +SFid* +alreadyopen(Fid *mf, uint mode) +{ + File *file; + SFid *sfid; + + file = mf->path->file; + if(file == nil) + return nil; + sfid = file->open[mode&3]; + if(sfid == nil) + return nil; + DPRINT(2, "openfile: existing sfid %ud open %d\n", sfid->fid, mode&3); + sfid->ref++; + return sfid; +} + +void +copystat(File *file, Dir *d, int iswstat) +{ + if(d->length != ~(vlong)0){ + /* length change: discard cached data */ + if(iswstat && file->length < file->clength) + cacheinval(file); + file->length = d->length; + } + if(d->mode != ~0){ + file->mode = d->mode; + file->qid.type = d->mode>>24; + } + if(d->atime != ~0) + file->atime = d->atime; + if(d->mtime != ~0) + file->mtime = d->mtime; + if(*d->uid){ + freestr(file->uid); + file->uid = newstr(d->uid); + } + if(*d->gid){ + freestr(file->gid); + file->gid = newstr(d->gid); + } + if(*d->muid){ + freestr(file->muid); + file->muid = newstr(d->muid); + } +} + +void +putfile(File *f) +{ + int i; + SFid *sfid; + + if(--f->ref != 0) + return; + for(i = 0; i < nelem(f->open); i++){ + sfid = f->open[i]; + if(sfid != nil){ + f->open[i] = nil; + sfclunk(sfid); + freesfid(sfid); + } + } + freestr(f->uid); + freestr(f->gid); + freestr(f->muid); + cacheinval(f); + free(f->cached); + free(f); +} + +/* + * data + */ + +static int radix[] = {10, 12, 14}; +static uint range[] = {8, 32, 0}; +static uint cacheused; + +void +datainit(void) +{ + datalist.forw = datalist.back = &datalist; +} + +Data* +allocdata(File *file, uint n, uint nbytes) +{ + Data *d; + + while(cacheused+nbytes > cachesize && datalist.forw != datalist.back) + freedata(datalist.forw); + cacheused += nbytes; + d = mallocz(sizeof(*d), 0); + d->owner = file; + d->n = n; + d->base = malloc(nbytes); + d->size = nbytes; + d->min = 0; + d->max = 0; + d->forw = &datalist; + d->back = datalist.back; + d->back->forw = d; + datalist.back = d; + return d; +} + +void +freedata(Data *d) +{ + d->forw->back = d->back; + d->back->forw = d->forw; + cacheused -= d->size; + if(d->owner != nil) + d->owner->cached[d->n] = nil; + free(d->base); + free(d); +} + +/* + * move recently-used data to end + */ +void +usedata(Data *d) +{ + if(datalist.back == d) + return; /* already at end */ + + d->forw->back = d->back; + d->back->forw = d->forw; + + d->forw = &datalist; + d->back = datalist.back; + d->back->forw = d; + datalist.back = d; +} + +/* + * data cache + */ + +int +cacheread(File *file, void *buf, vlong offset, int nbytes) +{ + char *p; + Data *d; + int n, o; + + DPRINT(2, "file %lld length %lld\n", file->qid.path, file->clength); + p = buf; + while(nbytes > 0){ + d = finddata(file, offset, &o); + if(d == nil) + break; + if(o < d->min){ + if(p == (char*)buf) + return -(d->min-o); /* fill the gap */ + break; + }else if(o >= d->max) + break; + usedata(d); + /* o >= d->min && o < d->max */ + n = nbytes; + if(n > d->max-o) + n = d->max-o; + memmove(p, d->base+o, n); + p += n; + nbytes -= n; + offset += n; + } + return p-(char*)buf; +} + +void +cachewrite(File *file, void *buf, vlong offset, int nbytes) +{ + char *p; + Data *d; + int n, o; + + p = buf; + while(nbytes > 0){ + d = storedata(file, offset, &o); + if(d == nil) + break; + n = nbytes; + if(n > d->size-o) + n = d->size-o; + cachemerge(d, p, o, n); + p += n; + offset += n; + nbytes -= n; + } + if(offset > file->clength) + file->clength = offset; +} + +/* + * merge data in if it overlaps existing contents, + * or simply replace existing contents otherwise + */ +void +cachemerge(Data *p, char *from, int start, int len) +{ + int end; + + end = start + len; + memmove(p->base+start, from, len); + + if(start > p->max || p->min > end){ + p->min = start; + p->max = end; + }else{ + if(start < p->min) + p->min = start; + if(end > p->max) + p->max = end; + } +} + +void +cacheinval(File *file) +{ + Data *d; + int i; + + if(file->cached != nil){ + for(i = 0; i < file->ndata; i++){ + d = file->cached[i]; + if(d != nil){ + file->cached[i] = nil; + d->owner = nil; + freedata(d); + } + } + /* leave the array */ + } + file->clength = 0; +} + +Data* +finddata(File *file, uvlong offset, int *blkoff) +{ + int r, x; + uvlong base, o; + + x = 0; + base = 0; + for(r = 0; r < nelem(radix); r++){ + o = (offset - base) >> radix[r]; + DPRINT(2, "file %llud offset %llud x %d base %llud o %llud\n", file->qid.path, offset, x, base, o); + if(range[r] == 0 || o < range[r]){ + o += x; + if(o >= file->ndata) + break; + *blkoff = (offset-base) & ((1<cached[(int)o]; + } + base += range[r]<cached == nil){ + file->cached = mallocz(16*sizeof(*file->cached), 1); + file->ndata = 16; + } + x = 0; + base = 0; + for(r = 0; r < nelem(radix); r++){ + o = (offset - base) >> radix[r]; + DPRINT(2, "store file %llud offset %llud x %d base %llud o %llud\n", file->qid.path, offset, x, base, o); + if(range[r] == 0 || o < range[r]){ + o += x; + if(o >= file->ndata){ + if(o >= 512) + return nil; /* won't fit */ + v = (o+32+31)&~31; + file->cached = realloc(file->cached, v*sizeof(*file->cached)); + memset(file->cached+file->ndata, 0, (v-file->ndata)*sizeof(*file->cached)); + file->ndata = v; + } + size = 1 << radix[r]; + DPRINT(2, " -> %d %d\n", (int)o, size); + *blkoff = (offset-base) & (size-1); + p = &file->cached[(int)o]; + if(*p == nil) + *p = allocdata(file, (int)o, size); + else + usedata(*p); + return *p; + } + base += range[r]<>24) & 0xFF) | g; + } + return &stralloc.htab[h & Strhashmask]; +} + +String* +newstr(char *val) +{ + String *s, **l; + + //qlock(&stralloc); + for(l = hashslot(val); (s = *l) != nil; l = &s->next){ + if(strcmp(s->s, val) == 0){ + s->ref++; + //qunlock(&stralloc); + return s; + } + } + s = malloc(sizeof(*s)); + s->s = strdup(val); + s->len = strlen(s->s); + s->ref = 1; + s->next = *l; + *l = s; + //qunlock(&stralloc); + return s; +} + +String* +dupstr(String *s) +{ + s->ref++; + return s; +} + +void +freestr(String *s) +{ + String **l; + + if(s != nil && --s->ref == 0){ + //qlock(&stralloc); + for(l = hashslot(s->s); *l != nil; l = &(*l)->next){ + if(*l == s){ + *l = s->next; + break; + } + } + //qunlock(&stralloc); + free(s->s); + free(s); + } +} diff --git a/sys/src/cmd/fscfs/dat.h b/sys/src/cmd/fscfs/dat.h new file mode 100644 index 00000000..aa296440 --- /dev/null +++ b/sys/src/cmd/fscfs/dat.h @@ -0,0 +1,162 @@ +/* + * fscfs + */ + +typedef struct Attach Attach; +typedef struct Auth Auth; +typedef struct Data Data; +typedef struct Dref Dref; +typedef struct Fid Fid; +typedef struct File File; +typedef struct Host Host; +typedef struct Path Path; +typedef struct P9fs P9fs; +typedef struct Req Req; +typedef struct SFid SFid; +typedef struct String String; + +#pragma incomplete P9fs + +typedef u32int Tag; + +struct String +{ + Ref; + int len; + char* s; + String* next; /* hash chain */ +}; + +struct Dref +{ + int inuse; /* reference count locally */ + int faruse; /* remote references */ + Tag tag; /* remote object tag */ + Host* loc; /* location of object (nil if here) */ + Host* src; /* source of object reference (nil if here) */ + int depth; /* 0 if here or loc == src */ + int weight; /* proxy weight */ +}; + +enum{ + FidHash= 1<<8, +}; + +struct Attach +{ + /* parameters and result of a Tattach */ + u32int fid; + char* uname; + char* aname; + Path* root; /* qid from attach is root->qid, root->sfid is server fid */ + Attach* next; +}; + +struct Auth +{ + /* could probably use a Ref */ + SFid* afid; + char* uname; + char* aname; + char* error; + Auth* next; + int active; /* active until attached, with or without success */ + Req* pending; /* later Auth requests with same parameters, and flush requests */ +}; + +struct Host +{ + Ref; + + QLock; + int fd; /* link to host */ + char* name; /* symbolic name (mainly for diagnostics) */ + Fid* fids[FidHash]; /* fids active for this host */ + /* might need per-host auth/attach fid/afid for authentication? */ + /* implies separation of fid spaces, and thus separate Fids but not Files and Paths */ +}; + +struct Path +{ + Ref; + char* name; + Qid qid; + Path* parent; + Path* child; + Path* next; /* sibling */ + uint nxtime; /* zero (if exists) or last time we checked */ + char* inval; /* walk error if invalid */ + File* file; /* file data, if open */ + SFid* sfid; /* walked to this fid on server */ +}; + +struct Data +{ + /* cached portion of a file */ + uint min; /* offsets */ + uint max; + uint size; /* size of buffer (power of 2) */ + uchar* base; + + /* LRU stuff */ + Data* forw; + Data* back; + File* owner; + uint n; /* index in owner's cache */ +}; + +struct SFid +{ + Ref; /* by client fids */ + u32int fid; /* fid on server */ + SFid* next; /* on free or LRU list */ +}; + +struct Fid +{ + u32int fid; /* fid on Host */ + Qid qid; + Path* path; /* shared data about file */ + SFid* opened; /* server fid once opened */ + uint mode; /* open mode (OREAD, OWRITE, ORDWR) */ + Fid* next; /* in fid hash list */ +}; + +struct File +{ + Ref; + + SFid* open[3]; /* cached sfids: OREAD, OWRITE, ORDWR */ + + /* data from Dir */ + uint mode; /* permissions */ + uint atime; /* last read time */ + uint mtime; /* last write time */ + u64int length; /* file length from stat or 0 => use clength */ + String* uid; /* owner name */ + String* gid; /* group name */ + String* muid; /* last modifier name */ + + Qid qid; + u32int iounit; + u64int clength; /* known length in cache */ + uint ndata; /* size of cache array */ + Data** cached; + + /* Dref for local and remote references */ + /* possibly put expired ones on LRU? */ + File* next; +}; + +struct Req +{ + u32int tag; + Fcall t; + SFid* fid; /* also afid in Tauth */ + SFid* newfid; /* also afid in Tattach */ + Fcall r; + uchar* buf; + uint msize; + Req* flush; + Req* next; /* in tag list, or flush list of another Req */ +}; diff --git a/sys/src/cmd/fscfs/fns.h b/sys/src/cmd/fscfs/fns.h new file mode 100644 index 00000000..968f7e75 --- /dev/null +++ b/sys/src/cmd/fscfs/fns.h @@ -0,0 +1,57 @@ +Data* allocdata(File*, uint, uint); +SFid* allocsfid(void); +SFid* alreadyopen(Fid*, uint); +int askserver(Fcall*, Fid*); +void badpath(Path*, char*, char*); +void cacheinval(File*); +void closefile(Fid*, int, int); +void cachemerge(Data*, char*, int, int); +void copystat(File*, Dir*, int); +int cacheread(File*, void*, vlong, int); +int ctltest(Fid*); +void cachewrite(File*, void*, vlong, int); +void datainit(void); +int delegate(Fcall*, Fid*, SFid*); +void dumpdata(Data*, int); +String* dupstr(String*); +void error(char*, ...); +void fileinval(Path*); +Data* finddata(File*, uvlong, int*); +Fid* findfid(Host*, u32int, int); +void freedata(Data*); +void freepath(Path*); +void freesfid(SFid*); +void freestr(String*); +void genstats(void); +void io(void); +int localwalk(Fid*, Fcall*, Fcall*, Path**); +void mountinit(char*, char*); +Path* newpath(Path*, char*, Qid); +String* newstr(char*); +void openfile(Fid*, int, u32int, SFid*); +int openmode(uint); +char* pathstr(Path*); +void printpath(Path*, int); +void putfid(Fid*); +void putfile(File*); +void rattach(Fcall*); +void rauth(Fcall*); +void rclunk(Fcall*); +void rcreate(Fcall*); +void rcvmsg(P9fs*, Fcall*); +void rflush(Fcall*); +void ropen(Fcall*); +void rread(Fcall*); +void rremove(Fcall*); +void rstat(Fcall*); +void rversion(Fcall*); +void rwalk(Fcall*); +void rwrite(Fcall*); +void rwstat(Fcall*); +void sendmsg(P9fs*, Fcall*); +void sendreply(char*); +void setinval(Path*, char*); +SFid* sfclone(SFid*); +void sfclunk(SFid*); +Data* storedata(File*, uvlong, int*); +void warning(char*); diff --git a/sys/src/cmd/fscfs/messages b/sys/src/cmd/fscfs/messages new file mode 100644 index 00000000..e92dae17 --- /dev/null +++ b/sys/src/cmd/fscfs/messages @@ -0,0 +1,40 @@ + size[4] Tversion tag[2] msize[4] version[s] + size[4] Rversion tag[2] msize[4] version[s] + + size[4] Tauth tag[2] afid[4] uname[s] aname[s] + size[4] Rauth tag[2] aqid[13] + + size[4] Rerror tag[2] ename[s] + + size[4] Tflush tag[2] oldtag[2] + size[4] Rflush tag[2] + + size[4] Tattach tag[2] fid[4] afid[4] uname[s] aname[s] + size[4] Rattach tag[2] qid[13] + + size[4] Twalk tag[2] fid[4] newfid[4] nwname[2] nwname*(wname[s]) + size[4] Rwalk tag[2] nwqid[2] nwqid*(wqid[13]) + + size[4] Topen tag[2] fid[4] mode[1] + size[4] Ropen tag[2] qid[13] iounit[4] + + size[4] Tcreate tag[2] fid[4] name[s] perm[4] mode[1] + size[4] Rcreate tag[2] qid[13] iounit[4] + + size[4] Tread tag[2] fid[4] offset[8] count[4] + size[4] Rread tag[2] count[4] data[count] + + size[4] Twrite tag[2] fid[4] offset[8] count[4] data[count] + size[4] Rwrite tag[2] count[4] + + size[4] Tclunk tag[2] fid[4] + size[4] Rclunk tag[2] + + size[4] Tremove tag[2] fid[4] + size[4] Rremove tag[2] + + size[4] Tstat tag[2] fid[4] + size[4] Rstat tag[2] stat[n] + + size[4] Twstat tag[2] fid[4] stat[n] + size[4] Rwstat tag[2] diff --git a/sys/src/cmd/fscfs/mkfile b/sys/src/cmd/fscfs/mkfile new file mode 100644 index 00000000..3b6fac2a --- /dev/null +++ b/sys/src/cmd/fscfs/mkfile @@ -0,0 +1,26 @@ + R in order + +- could re-use tag of incoming op (probably assumes in order, or put client id in high-order bits) + +x String hash table + +- auth interaction, and attach + +- fid management different for input from clients and output to server + +- if any client has fid to file, server has fid to file +- if no client has fid to file, server might have fid to file (LRU) +- if any client has fid open, server has fid open (BUT: modes, auth) +- if no client has fid open, server will not have fid open (for now) + - OREAD, OWRITE, ORDWR, OEXEC(!) + - OTRUNC [if not QTAPPEND] + - OEXCL + - ORCLOSE + - OCEXEC (not in protocol) +- initially it's probably adequate to cache only OREAD + +fid walk +- Path has optional SFid +- root has SFid from Tattach + - must not be lost, in case of later attaches +- all local attach fids are bound to root.sfid +- if not localwalk, allocate SFid and walk that + - if walks to Path with SFid, clunk and substitute + (in non-concurrent system, can't happen? because localwalk would have returned it) + +attach and auth handling +- attach can't be done until successful Tauth +- Tauth just introduces a fid for Tread/Twrite[/Tclunk] + - need to assume that existing Attach with similar parameters is adequate authentication + (obviously wrong, because another incoming connection saying Tauth might not represent the + same principal. with one connection, it hardly matters, because if it's shared, + spoofing could occur by fid substitution if the connection can't be trusted. + also, in this particular application, it's usually safe to assume that every connection is either the + same principal or "none". trust lies in the authentication of the connection, and the associated "speaks for" + relationship.) + +- private sfid in ropen/rcreate for isdirect files; must clunk + +- Path's SFid is only walkable; Fid must have separate path to (possibly shared) SFid for IO + +- I/O fids should be shared (unless impossible) + - can't share QTEXCL, or QTDIR [offset clashes] + - for QTDIR, could read whole directory and cache that, with purely local fid, + or read and accumulate contents incrementally + - need a way to find the shared fid + - stored in File? as at present [one for each possible mode] + - map [hash] qid.path x (mode&3) x rclose -> SFid + - first open in given mode -> clone Path's SFid (as at present) + - have Fid.open: SFid -> shared fid once located? + - also private SFid when needed (eg, isdirect, ORCLOSE) + - Qid changes on open (eg, clone) + - might detect change of qid.path on first open and mark File as volatile + (enough to discard File reference from Path) diff --git a/sys/src/cmd/fscfs/stats.h b/sys/src/cmd/fscfs/stats.h new file mode 100644 index 00000000..e5a2e74a --- /dev/null +++ b/sys/src/cmd/fscfs/stats.h @@ -0,0 +1,29 @@ +typedef struct Cfsmsg Cfsmsg; +typedef struct Cfsstat Cfsstat; + +struct Cfsmsg { + ulong n; /* number of messages (of some type) */ + vlong t; /* time spent in these messages */ + vlong s; /* start time of last call */ +}; + +struct Cfsstat { + Cfsmsg cm[128]; /* client messages */ + Cfsmsg sm[128]; /* server messages */ + + ulong ndirread; /* # of directory read ops */ + ulong ndelegateread; /* # of read ops delegated */ + ulong ninsert; /* # of cache insert ops */ + ulong ndelete; /* # of cache delete ops */ + ulong nupdate; /* # of cache update ops */ + + uvlong bytesread; /* # of bytes read by client */ + uvlong byteswritten; /* # of bytes written by client */ + uvlong bytesfromserver; /* # of bytes read from server */ + uvlong bytesfromdirs; /* # of directory bytes read from server */ + uvlong bytesfromcache; /* # of bytes read from cache */ + uvlong bytestocache; /* # of bytes written to cache */ +}; + +extern Cfsstat cfsstat, cfsprev; +extern int statson;