#include #include #include #include #include "cformat.h" #include "lru.h" #include "bcache.h" #include "disk.h" #include "inode.h" #include "file.h" #include "stats.h" enum { Nfid= 10240, }; /* maximum length of a file */ enum { MAXLEN = ~0ULL >> 1 }; typedef struct Mfile Mfile; typedef struct Ram Ram; typedef struct P9fs P9fs; struct Mfile { Qid qid; char busy; }; Mfile mfile[Nfid]; Icache ic; int debug, statson, noauth, openserver; struct P9fs { int fd[2]; Fcall rhdr; Fcall thdr; long len; char *name; }; P9fs c; /* client conversation */ P9fs s; /* server conversation */ struct Cfsstat cfsstat, cfsprev; char statbuf[2048]; int statlen; #define MAXFDATA 8192 /* i/o size for read/write */ int messagesize = MAXFDATA+IOHDRSZ; uchar datasnd[MAXFDATA + IOHDRSZ]; uchar datarcv[MAXFDATA + IOHDRSZ]; Qid rootqid; Qid ctlqid = {0x5555555555555555LL, 0, 0}; void rversion(void); void rauth(Mfile*); void rflush(void); void rattach(Mfile*); void rwalk(Mfile*); void ropen(Mfile*); void rcreate(Mfile*); void rread(Mfile*); void rwrite(Mfile*); void rclunk(Mfile*); void rremove(Mfile*); void rstat(Mfile*); void rwstat(Mfile*); void error(char*, ...); void warning(char*); void mountinit(char*, char*); void io(void); void sendreply(char*); void sendmsg(P9fs*, Fcall*); void rcvmsg(P9fs*, Fcall*); int delegate(void); int askserver(void); void cachesetup(int, char*, char*); int ctltest(Mfile*); void genstats(void); 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 usage(void) { fprint(2, "usage:\tcfs -s [-dknrS] [-f partition]\n"); fprint(2, "\tcfs [-a netaddr | -F srv] [-dknrS] [-f partition] [mntpt]\n"); exits("usage"); } void main(int argc, char *argv[]) { int std, format, chkid; char *part, *server, *mtpt; NetConnInfo *snci; std = 0; format = 0; chkid = 1; part = "/dev/sdC0/cache"; server = "tcp!fs"; mtpt = "/tmp"; ARGBEGIN{ case 'a': server = EARGF(usage()); break; case 'd': debug = 1; break; case 'f': part = EARGF(usage()); break; case 'F': server = EARGF(usage()); openserver = 1; break; case 'k': chkid = 0; break; case 'n': noauth = 1; break; case 'r': format = 1; break; case 'S': statson = 1; break; case 's': std = 1; break; default: usage(); }ARGEND if(argc && *argv) mtpt = *argv; if(debug) fmtinstall('F', fcallfmt); 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); if(chkid){ if((snci = getnetconninfo(nil, s.fd[0])) == nil) /* Failed to lookup information; format */ cachesetup(1, nil, part); else /* Do partition check */ cachesetup(0, snci->raddr, part); }else /* Obey -f w/o regard to cache vs. remote server */ cachesetup(format, nil, part); switch(fork()){ case 0: io(); exits(""); case -1: error("fork"); default: exits(""); } } void cachesetup(int format, char *name, char *partition) { int f; int secsize; int inodes; int blocksize; secsize = 512; inodes = 1024; blocksize = 4*1024; f = open(partition, ORDWR); if(f < 0) error("opening partition"); if(format || iinit(&ic, f, secsize, name) < 0){ /* * If we need to format and don't have a name, fall * back to our old behavior of using "bootes" */ name = (name == nil? "bootes": name); if(iformat(&ic, f, inodes, name, blocksize, secsize) < 0) error("formatting failed"); } } 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); else s.fd[0] = dial(netmkaddr(server, 0, "9fs"), 0, 0, 0); if(s.fd[0] < 0) error("opening data: %r"); s.fd[1] = s.fd[0]; /* * mount onto name space */ if(pipe(p) < 0) error("pipe failed"); switch(fork()){ case 0: break; default: if (noauth) err = mount(p[1], -1, mountpoint, MREPL|MCREATE, ""); else err = amount(p[1], mountpoint, MREPL|MCREATE, ""); if (err < 0) error("mount failed: %r"); exits(0); case -1: error("fork failed\n"); /*BUG: no wait!*/ } c.fd[0] = c.fd[1] = p[0]; } void io(void) { int type; Mfile *mf; loop: rcvmsg(&c, &c.thdr); type = c.thdr.type; if(statson){ cfsstat.cm[type].n++; cfsstat.cm[type].s = nsec(); } mf = &mfile[c.thdr.fid]; switch(type){ default: error("type"); break; case Tversion: rversion(); break; case Tauth: mf = &mfile[c.thdr.afid]; rauth(mf); break; case Tflush: rflush(); break; case Tattach: rattach(mf); break; case Twalk: rwalk(mf); break; case Topen: ropen(mf); break; case Tcreate: rcreate(mf); break; case Tread: rread(mf); break; case Twrite: rwrite(mf); break; case Tclunk: rclunk(mf); break; case Tremove: rremove(mf); break; case Tstat: rstat(mf); break; case Twstat: rwstat(mf); break; } if(statson){ cfsstat.cm[type].t += nsec() -cfsstat.cm[type].s; } goto loop; } void rversion(void) { if(messagesize > c.thdr.msize) messagesize = c.thdr.msize; c.thdr.msize = messagesize; /* set downstream size */ delegate(); } void rauth(Mfile *mf) { if(mf->busy) error("auth to used channel"); if(delegate() == 0){ mf->qid = s.rhdr.aqid; mf->busy = 1; } } void rflush(void) /* synchronous so easy */ { sendreply(0); } void rattach(Mfile *mf) { if(delegate() == 0){ mf->qid = s.rhdr.qid; mf->busy = 1; if (statson == 1){ statson++; rootqid = mf->qid; } } } void rwalk(Mfile *mf) { Mfile *nmf; nmf = nil; if(statson && mf->qid.type == rootqid.type && mf->qid.path == rootqid.path && c.thdr.nwname == 1 && strcmp(c.thdr.wname[0], "cfsctl") == 0){ /* This is the ctl file */ nmf = &mfile[c.thdr.newfid]; if(c.thdr.newfid != c.thdr.fid && nmf->busy) error("clone to used channel"); nmf = &mfile[c.thdr.newfid]; nmf->qid = ctlqid; nmf->busy = 1; c.rhdr.nwqid = 1; c.rhdr.wqid[0] = ctlqid; sendreply(0); return; } if(c.thdr.newfid != c.thdr.fid){ if(c.thdr.newfid >= Nfid) error("clone nfid out of range"); nmf = &mfile[c.thdr.newfid]; if(nmf->busy) error("clone to used channel"); nmf = &mfile[c.thdr.newfid]; nmf->qid = mf->qid; nmf->busy = 1; mf = nmf; /* Walk mf */ } if(delegate() < 0){ /* complete failure */ if(nmf) nmf->busy = 0; return; } if(s.rhdr.nwqid == c.thdr.nwname){ /* complete success */ if(s.rhdr.nwqid > 0) mf->qid = s.rhdr.wqid[s.rhdr.nwqid-1]; return; } /* partial success; release fid */ if(nmf) nmf->busy = 0; } void ropen(Mfile *mf) { if(statson && ctltest(mf)){ /* Opening ctl file */ if(c.thdr.mode != OREAD){ sendreply("does not exist"); return; } c.rhdr.qid = ctlqid; c.rhdr.iounit = 0; sendreply(0); genstats(); return; } if(delegate() == 0){ mf->qid = s.rhdr.qid; if(c.thdr.mode & OTRUNC) iget(&ic, mf->qid); } } void rcreate(Mfile *mf) { if(statson && ctltest(mf)){ sendreply("exists"); return; } if(delegate() == 0){ mf->qid = s.rhdr.qid; mf->qid.vers++; } } void rclunk(Mfile *mf) { if(!mf->busy){ sendreply(0); return; } mf->busy = 0; delegate(); } void rremove(Mfile *mf) { if(statson && ctltest(mf)){ sendreply("not removed"); return; } mf->busy = 0; delegate(); } void rread(Mfile *mf) { int cnt, done; long n; vlong off, first; char *cp; char data[MAXFDATA]; Ibuf *b; off = c.thdr.offset; first = off; cnt = c.thdr.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){ sendreply("eof"); return; } c.rhdr.data = statbuf + off; sendreply(0); return; } if(mf->qid.type & (QTDIR|QTAUTH)){ delegate(); if (statson) { cfsstat.ndirread++; if(c.rhdr.count > 0){ cfsstat.bytesread += c.rhdr.count; cfsstat.bytesfromdirs += c.rhdr.count; } } return; } b = iget(&ic, mf->qid); if(b == 0){ DPRINT(2, "delegating read\n"); delegate(); if (statson){ cfsstat.ndelegateread++; if(c.rhdr.count > 0){ cfsstat.bytesread += c.rhdr.count; cfsstat.bytesfromserver += c.rhdr.count; } } return; } cp = data; done = 0; while(cnt>0 && !done){ if(off >= b->inode.length){ DPRINT(2, "offset %lld greater than length %lld\n", off, b->inode.length); break; } n = fread(&ic, b, 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.type = c.thdr.type; s.thdr.fid = c.thdr.fid; s.thdr.tag = c.thdr.tag; s.thdr.offset = off; s.thdr.count = n; if(statson) cfsstat.ndelegateread++; if(askserver() < 0){ sendreply(s.rhdr.ename); return; } if(s.rhdr.count != n) done = 1; n = s.rhdr.count; if(n == 0){ /* end of file */ if(b->inode.length > off){ DPRINT(2, "file %llud.%ld, length %lld\n", b->inode.qid.path, b->inode.qid.vers, off); b->inode.length = off; } break; } memmove(cp, s.rhdr.data, n); fwrite(&ic, b, 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(Mfile *mf) { Ibuf *b; char buf[MAXFDATA]; if(statson && ctltest(mf)){ sendreply("read only"); return; } if(mf->qid.type & (QTDIR|QTAUTH)){ delegate(); if(statson && c.rhdr.count > 0) cfsstat.byteswritten += c.rhdr.count; return; } memmove(buf, c.thdr.data, c.thdr.count); if(delegate() < 0) return; if(s.rhdr.count > 0) cfsstat.byteswritten += s.rhdr.count; /* don't modify our cache for append-only data; always read from server*/ if(mf->qid.type & QTAPPEND) return; b = iget(&ic, mf->qid); if(b == 0) return; if (b->inode.length < c.thdr.offset + s.rhdr.count) b->inode.length = c.thdr.offset + s.rhdr.count; mf->qid.vers++; if (s.rhdr.count != c.thdr.count) syslog(0, "cfslog", "rhdr.count %ud, thdr.count %ud\n", s.rhdr.count, c.thdr.count); if(fwrite(&ic, b, buf, c.thdr.offset, s.rhdr.count) == s.rhdr.count){ iinc(&ic, b); if(statson) cfsstat.bytestocache += s.rhdr.count; } } void rstat(Mfile *mf) { Dir d; if(statson && ctltest(mf)){ genstats(); d.qid = ctlqid; d.mode = 0444; d.length = statlen; /* would be nice to do better */ d.name = "cfsctl"; d.uid = "none"; d.gid = "none"; d.muid = "none"; d.atime = time(nil); d.mtime = d.atime; c.rhdr.nstat = convD2M(&d, c.rhdr.stat, sizeof c.rhdr - (c.rhdr.stat - (uchar*)&c.rhdr)); sendreply(0); return; } if(delegate() == 0){ Ibuf *b; convM2D(s.rhdr.stat, s.rhdr.nstat , &d, nil); mf->qid = d.qid; b = iget(&ic, mf->qid); if(b) b->inode.length = d.length; } } void rwstat(Mfile *mf) { Ibuf *b; if(statson && ctltest(mf)){ sendreply("read only"); return; } delegate(); if(b = iget(&ic, mf->qid)) b->inode.length = MAXLEN; } 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); exits("error"); } void warning(char *s) { fprint(2, "cfs: %s: %r\n", 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); } /* * send a request to the server, get the reply, and send that to * the client */ int delegate(void) { int type; type = c.thdr.type; if(statson){ cfsstat.sm[type].n++; cfsstat.sm[type].s = nsec(); } sendmsg(&s, &c.thdr); rcvmsg(&s, &s.rhdr); if(statson) cfsstat.sm[type].t += nsec() - cfsstat.sm[type].s; sendmsg(&c, &s.rhdr); return c.thdr.type+1 == s.rhdr.type ? 0 : -1; } /* * send a request to the server and get a reply */ int askserver(void) { int type; s.thdr.tag = c.thdr.tag; type = s.thdr.type; if(statson){ cfsstat.sm[type].n++; cfsstat.sm[type].s = nsec(); } 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, datasnd, messagesize); if(p->len <= 0) error("convS2M"); if(write(p->fd[1], datasnd, p->len)!=p->len) error("sendmsg"); } void dump(uchar *p, int len) { fprint(2, "%d bytes", len); while(len-- > 0) fprint(2, " %.2ux", *p++); fprint(2, "\n"); } void rcvmsg(P9fs *p, Fcall *f) { int olen, rlen; char buf[128]; olen = p->len; p->len = read9pmsg(p->fd[0], datarcv, sizeof(datarcv)); if(p->len <= 0){ snprint(buf, sizeof buf, "read9pmsg(%d)->%ld: %r", p->fd[0], p->len); error(buf); } if((rlen = convM2S(datarcv, p->len, f)) != p->len) error("rcvmsg format error, expected length %d, got %d", rlen, p->len); if(f->fid >= Nfid){ fprint(2, "<-%s: %d %s on %d\n", p->name, f->type, mname[f->type]? mname[f->type]: "mystery", f->fid); dump((uchar*)datasnd, olen); dump((uchar*)datarcv, p->len); error("rcvmsg fid out of range"); } DPRINT(2, "<-%s: %F\n", p->name, f); } int ctltest(Mfile *mf) { return mf->busy && mf->qid.type == ctlqid.type && mf->qid.path == ctlqid.path; } 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; }