summaryrefslogtreecommitdiff
path: root/sys/src/cmd/exportfs
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/exportfs
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/exportfs')
-rwxr-xr-xsys/src/cmd/exportfs/exportfs.c937
-rwxr-xr-xsys/src/cmd/exportfs/exportfs.h144
-rwxr-xr-xsys/src/cmd/exportfs/exportsrv.c764
-rwxr-xr-xsys/src/cmd/exportfs/mkfile18
-rwxr-xr-xsys/src/cmd/exportfs/pattern.c154
5 files changed, 2017 insertions, 0 deletions
diff --git a/sys/src/cmd/exportfs/exportfs.c b/sys/src/cmd/exportfs/exportfs.c
new file mode 100755
index 000000000..6022e78b7
--- /dev/null
+++ b/sys/src/cmd/exportfs/exportfs.c
@@ -0,0 +1,937 @@
+/*
+ * exportfs - Export a plan 9 name space across a network
+ */
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <libsec.h>
+#define Extern
+#include "exportfs.h"
+
+#define QIDPATH ((1LL<<48)-1)
+vlong newqid = 0;
+
+enum {
+ Encnone,
+ Encssl,
+ Enctls,
+};
+
+void (*fcalls[])(Fsrpc*) =
+{
+ [Tversion] Xversion,
+ [Tauth] Xauth,
+ [Tflush] Xflush,
+ [Tattach] Xattach,
+ [Twalk] Xwalk,
+ [Topen] slave,
+ [Tcreate] Xcreate,
+ [Tclunk] Xclunk,
+ [Tread] slave,
+ [Twrite] slave,
+ [Tremove] Xremove,
+ [Tstat] Xstat,
+ [Twstat] Xwstat,
+};
+
+/* accounting and debugging counters */
+int filecnt;
+int freecnt;
+int qidcnt;
+int qfreecnt;
+int ncollision;
+
+int netfd; /* initially stdin */
+int srvfd = -1;
+int nonone = 1;
+char *filterp;
+char *ealgs = "rc4_256 sha1";
+char *aanfilter = "/bin/aan";
+int encproto = Encnone;
+int readonly;
+
+static void mksecret(char *, uchar *);
+static int localread9pmsg(int, void *, uint, ulong *);
+static char *anstring = "tcp!*!0";
+
+char *netdir = "", *local = "", *remote = "";
+
+int filter(int, char *);
+
+void
+usage(void)
+{
+ fprint(2, "usage: %s [-adnsR] [-f dbgfile] [-m msize] [-r root] "
+ "[-S srvfile] [-e 'crypt hash'] [-P exclusion-file] "
+ "[-A announce-string] [-B address]\n", argv0);
+ fatal("usage");
+}
+
+static void
+noteconn(int fd)
+{
+ NetConnInfo *nci;
+
+ nci = getnetconninfo(nil, fd);
+ if (nci == nil)
+ return;
+ netdir = strdup(nci->dir);
+ local = strdup(nci->lsys);
+ remote = strdup(nci->rsys);
+ freenetconninfo(nci);
+}
+
+void
+main(int argc, char **argv)
+{
+ char buf[ERRMAX], ebuf[ERRMAX], *srvfdfile;
+ Fsrpc *r;
+ int doauth, n, fd;
+ char *dbfile, *srv, *na, *nsfile, *keyspec;
+ AuthInfo *ai;
+ ulong initial;
+
+ dbfile = "/tmp/exportdb";
+ srv = nil;
+ srvfd = -1;
+ srvfdfile = nil;
+ na = nil;
+ nsfile = nil;
+ keyspec = "";
+ doauth = 0;
+
+ ai = nil;
+ ARGBEGIN{
+ case 'a':
+ doauth = 1;
+ break;
+
+ case 'd':
+ dbg++;
+ break;
+
+ case 'e':
+ ealgs = EARGF(usage());
+ if(*ealgs == 0 || strcmp(ealgs, "clear") == 0)
+ ealgs = nil;
+ break;
+
+ case 'f':
+ dbfile = EARGF(usage());
+ break;
+
+ case 'k':
+ keyspec = EARGF(usage());
+ break;
+
+ case 'm':
+ messagesize = strtoul(EARGF(usage()), nil, 0);
+ break;
+
+ case 'n':
+ nonone = 0;
+ break;
+
+ case 'r':
+ srv = EARGF(usage());
+ break;
+
+ case 's':
+ srv = "/";
+ break;
+
+ case 'A':
+ anstring = EARGF(usage());
+ break;
+
+ case 'B':
+ na = EARGF(usage());
+ break;
+
+ case 'F':
+ /* accepted but ignored, for backwards compatibility */
+ break;
+
+ case 'N':
+ nsfile = EARGF(usage());
+ break;
+
+ case 'P':
+ patternfile = EARGF(usage());
+ break;
+
+ case 'R':
+ readonly = 1;
+ break;
+
+ case 'S':
+ if(srvfdfile)
+ usage();
+ srvfdfile = EARGF(usage());
+ break;
+
+ default:
+ usage();
+ }ARGEND
+ USED(argc, argv);
+
+ if(doauth){
+ /*
+ * We use p9any so we don't have to visit this code again, with the
+ * cost that this code is incompatible with the old world, which
+ * requires p9sk2. (The two differ in who talks first, so compatibility
+ * is awkward.)
+ */
+ ai = auth_proxy(0, auth_getkey, "proto=p9any role=server %s", keyspec);
+ if(ai == nil)
+ fatal("auth_proxy: %r");
+ if(nonone && strcmp(ai->cuid, "none") == 0)
+ fatal("exportfs by none disallowed");
+ if(auth_chuid(ai, nsfile) < 0)
+ fatal("auth_chuid: %r");
+ putenv("service", "exportfs");
+ }
+
+ if(srvfdfile){
+ if((srvfd = open(srvfdfile, ORDWR)) < 0)
+ sysfatal("open '%s': %r", srvfdfile);
+ }
+
+ if(na){
+ if(srv == nil)
+ sysfatal("-B requires -s");
+
+ local = "me";
+ remote = na;
+ if((fd = dial(netmkaddr(na, 0, "importfs"), 0, 0, 0)) < 0)
+ sysfatal("can't dial %s: %r", na);
+
+ ai = auth_proxy(fd, auth_getkey, "proto=p9any role=client %s", keyspec);
+ if(ai == nil)
+ sysfatal("%r: %s", na);
+
+ dup(fd, 0);
+ dup(fd, 1);
+ close(fd);
+ }
+
+ exclusions();
+
+ if(dbg) {
+ n = create(dbfile, OWRITE|OTRUNC, 0666);
+ dup(n, DFD);
+ close(n);
+ }
+
+ if(srvfd >= 0 && srv){
+ fprint(2, "exportfs: -S cannot be used with -r or -s\n");
+ usage();
+ }
+
+ DEBUG(DFD, "exportfs: started\n");
+
+ rfork(RFNOTEG);
+
+ if(messagesize == 0){
+ messagesize = iounit(netfd);
+ if(messagesize == 0)
+ messagesize = 8192+IOHDRSZ;
+ }
+
+ Workq = emallocz(sizeof(Fsrpc)*Nr_workbufs);
+// for(i=0; i<Nr_workbufs; i++)
+// Workq[i].buf = emallocz(messagesize);
+ fhash = emallocz(sizeof(Fid*)*FHASHSIZE);
+
+ fmtinstall('F', fcallfmt);
+
+ /*
+ * Get tree to serve from network connection,
+ * check we can get there and ack the connection
+ */
+ if(srvfd != -1) {
+ /* do nothing */
+ }
+ else if(srv) {
+ chdir(srv);
+ DEBUG(DFD, "invoked as server for %s", srv);
+ strncpy(buf, srv, sizeof buf);
+ }
+ else {
+ noteconn(netfd);
+ buf[0] = 0;
+ n = read(0, buf, sizeof(buf)-1);
+ if(n < 0) {
+ errstr(buf, sizeof buf);
+ fprint(0, "read(0): %s", buf);
+ DEBUG(DFD, "read(0): %s", buf);
+ exits(buf);
+ }
+ buf[n] = 0;
+ if(chdir(buf) < 0) {
+ errstr(ebuf, sizeof ebuf);
+ fprint(0, "chdir(%d:\"%s\"): %s", n, buf, ebuf);
+ DEBUG(DFD, "chdir(%d:\"%s\"): %s", n, buf, ebuf);
+ exits(ebuf);
+ }
+ }
+
+ DEBUG(DFD, "\niniting root\n");
+ initroot();
+
+ DEBUG(DFD, "exportfs: %s\n", buf);
+
+ if(srv == nil && srvfd == -1 && write(0, "OK", 2) != 2)
+ fatal("open ack write");
+
+ if (readn(netfd, &initial, sizeof(ulong)) < sizeof(ulong))
+ fatal("can't read initial string: %r\n");
+
+ if (strncmp((char *)&initial, "impo", sizeof(ulong)) == 0) {
+ char buf[128], *p, *args[3];
+
+ /* New import. Read import's parameters... */
+ initial = 0;
+
+ p = buf;
+ while (p - buf < sizeof buf) {
+ if ((n = read(netfd, p, 1)) < 0)
+ fatal("can't read impo arguments: %r\n");
+
+ if (n == 0)
+ fatal("connection closed while reading arguments\n");
+
+ if (*p == '\n')
+ *p = '\0';
+ if (*p++ == '\0')
+ break;
+ }
+
+ if (tokenize(buf, args, nelem(args)) != 2)
+ fatal("impo arguments invalid: impo%s...\n", buf);
+
+ if (strcmp(args[0], "aan") == 0)
+ filterp = aanfilter;
+ else if (strcmp(args[0], "nofilter") != 0)
+ fatal("import filter argument unsupported: %s\n", args[0]);
+
+ if (strcmp(args[1], "ssl") == 0)
+ encproto = Encssl;
+ else if (strcmp(args[1], "tls") == 0)
+ encproto = Enctls;
+ else if (strcmp(args[1], "clear") != 0)
+ fatal("import encryption proto unsupported: %s\n", args[1]);
+
+ if (encproto == Enctls)
+ sysfatal("%s: tls has not yet been implemented", argv[0]);
+ }
+
+ if (encproto != Encnone && ealgs && ai) {
+ uchar key[16];
+ uchar digest[SHA1dlen];
+ char fromclientsecret[21];
+ char fromserversecret[21];
+ int i;
+
+ memmove(key+4, ai->secret, ai->nsecret);
+
+ /* exchange random numbers */
+ srand(truerand());
+ for(i = 0; i < 4; i++)
+ key[i+12] = rand();
+
+ if (initial)
+ fatal("Protocol botch: old import\n");
+ if(readn(netfd, key, 4) != 4)
+ fatal("can't read key part; %r\n");
+
+ if(write(netfd, key+12, 4) != 4)
+ fatal("can't write key part; %r\n");
+
+ /* scramble into two secrets */
+ sha1(key, sizeof(key), digest, nil);
+ mksecret(fromclientsecret, digest);
+ mksecret(fromserversecret, digest+10);
+
+ if (filterp)
+ netfd = filter(netfd, filterp);
+
+ switch (encproto) {
+ case Encssl:
+ netfd = pushssl(netfd, ealgs, fromserversecret,
+ fromclientsecret, nil);
+ break;
+ case Enctls:
+ default:
+ fatal("Unsupported encryption protocol\n");
+ }
+
+ if(netfd < 0)
+ fatal("can't establish ssl connection: %r");
+ }
+ else if (filterp) {
+ if (initial)
+ fatal("Protocol botch: don't know how to deal with this\n");
+ netfd = filter(netfd, filterp);
+ }
+
+ /*
+ * Start serving file requests from the network
+ */
+ for(;;) {
+ r = getsbuf();
+ if(r == 0)
+ fatal("Out of service buffers");
+
+ n = localread9pmsg(netfd, r->buf, messagesize, &initial);
+ if(n <= 0)
+ fatal(nil);
+
+ if(convM2S(r->buf, n, &r->work) == 0)
+ fatal("convM2S format error");
+
+ DEBUG(DFD, "%F\n", &r->work);
+ (fcalls[r->work.type])(r);
+ }
+}
+
+/*
+ * WARNING: Replace this with the original version as soon as all
+ * _old_ imports have been replaced with negotiating imports. Also
+ * cpu relies on this (which needs to be fixed!) -- pb.
+ */
+static int
+localread9pmsg(int fd, void *abuf, uint n, ulong *initial)
+{
+ int m, len;
+ uchar *buf;
+
+ buf = abuf;
+
+ /* read count */
+ assert(BIT32SZ == sizeof(ulong));
+ if (*initial) {
+ memcpy(buf, initial, BIT32SZ);
+ *initial = 0;
+ }
+ else {
+ m = readn(fd, buf, BIT32SZ);
+ if(m != BIT32SZ){
+ if(m < 0)
+ return -1;
+ return 0;
+ }
+ }
+
+ len = GBIT32(buf);
+ if(len <= BIT32SZ || len > n){
+ werrstr("bad length in 9P2000 message header");
+ return -1;
+ }
+ len -= BIT32SZ;
+ m = readn(fd, buf+BIT32SZ, len);
+ if(m < len)
+ return 0;
+ return BIT32SZ+m;
+}
+void
+reply(Fcall *r, Fcall *t, char *err)
+{
+ uchar *data;
+ int n;
+
+ t->tag = r->tag;
+ t->fid = r->fid;
+ if(err) {
+ t->type = Rerror;
+ t->ename = err;
+ }
+ else
+ t->type = r->type + 1;
+
+ DEBUG(DFD, "\t%F\n", t);
+
+ data = malloc(messagesize); /* not mallocz; no need to clear */
+ if(data == nil)
+ fatal(Enomem);
+ n = convS2M(t, data, messagesize);
+ if(write(netfd, data, n)!=n)
+{syslog(0, "exportfs", "short write: %r");
+ fatal("mount write");
+}
+ free(data);
+}
+
+Fid *
+getfid(int nr)
+{
+ Fid *f;
+
+ for(f = fidhash(nr); f; f = f->next)
+ if(f->nr == nr)
+ return f;
+
+ return 0;
+}
+
+int
+freefid(int nr)
+{
+ Fid *f, **l;
+ char buf[128];
+
+ l = &fidhash(nr);
+ for(f = *l; f; f = f->next) {
+ if(f->nr == nr) {
+ if(f->mid) {
+ sprint(buf, "/mnt/exportfs/%d", f->mid);
+ unmount(0, buf);
+ psmap[f->mid] = 0;
+ }
+ if(f->f) {
+ freefile(f->f);
+ f->f = nil;
+ }
+ if(f->dir){
+ free(f->dir);
+ f->dir = nil;
+ }
+ *l = f->next;
+ f->next = fidfree;
+ fidfree = f;
+ return 1;
+ }
+ l = &f->next;
+ }
+
+ return 0;
+}
+
+Fid *
+newfid(int nr)
+{
+ Fid *new, **l;
+ int i;
+
+ l = &fidhash(nr);
+ for(new = *l; new; new = new->next)
+ if(new->nr == nr)
+ return 0;
+
+ if(fidfree == 0) {
+ fidfree = emallocz(sizeof(Fid) * Fidchunk);
+
+ for(i = 0; i < Fidchunk-1; i++)
+ fidfree[i].next = &fidfree[i+1];
+
+ fidfree[Fidchunk-1].next = 0;
+ }
+
+ new = fidfree;
+ fidfree = new->next;
+
+ memset(new, 0, sizeof(Fid));
+ new->next = *l;
+ *l = new;
+ new->nr = nr;
+ new->fid = -1;
+ new->mid = 0;
+
+ return new;
+}
+
+Fsrpc *
+getsbuf(void)
+{
+ static int ap;
+ int look, rounds;
+ Fsrpc *wb;
+ int small_instead_of_fast = 1;
+
+ if(small_instead_of_fast)
+ ap = 0; /* so we always start looking at the beginning and reuse buffers */
+
+ for(rounds = 0; rounds < 10; rounds++) {
+ for(look = 0; look < Nr_workbufs; look++) {
+ if(++ap == Nr_workbufs)
+ ap = 0;
+ if(Workq[ap].busy == 0)
+ break;
+ }
+
+ if(look == Nr_workbufs){
+ sleep(10 * rounds);
+ continue;
+ }
+
+ wb = &Workq[ap];
+ wb->pid = 0;
+ wb->canint = 0;
+ wb->flushtag = NOTAG;
+ wb->busy = 1;
+ if(wb->buf == nil) /* allocate buffers dynamically to keep size down */
+ wb->buf = emallocz(messagesize);
+ return wb;
+ }
+ fatal("No more work buffers");
+ return nil;
+}
+
+void
+freefile(File *f)
+{
+ File *parent, *child;
+
+Loop:
+ f->ref--;
+ if(f->ref > 0)
+ return;
+ freecnt++;
+ if(f->ref < 0) abort();
+ DEBUG(DFD, "free %s\n", f->name);
+ /* delete from parent */
+ parent = f->parent;
+ if(parent->child == f)
+ parent->child = f->childlist;
+ else{
+ for(child=parent->child; child->childlist!=f; child=child->childlist)
+ if(child->childlist == nil)
+ fatal("bad child list");
+ child->childlist = f->childlist;
+ }
+ freeqid(f->qidt);
+ free(f->name);
+ f->name = nil;
+ free(f);
+ f = parent;
+ if(f != nil)
+ goto Loop;
+}
+
+File *
+file(File *parent, char *name)
+{
+ Dir *dir;
+ char *path;
+ File *f;
+
+ DEBUG(DFD, "\tfile: 0x%p %s name %s\n", parent, parent->name, name);
+
+ path = makepath(parent, name);
+ if(patternfile != nil && excludefile(path)){
+ free(path);
+ return nil;
+ }
+ dir = dirstat(path);
+ free(path);
+ if(dir == nil)
+ return nil;
+
+ for(f = parent->child; f; f = f->childlist)
+ if(strcmp(name, f->name) == 0)
+ break;
+
+ if(f == nil){
+ f = emallocz(sizeof(File));
+ f->name = estrdup(name);
+
+ f->parent = parent;
+ f->childlist = parent->child;
+ parent->child = f;
+ parent->ref++;
+ f->ref = 0;
+ filecnt++;
+ }
+ f->ref++;
+ f->qid.type = dir->qid.type;
+ f->qid.vers = dir->qid.vers;
+ f->qidt = uniqueqid(dir);
+ f->qid.path = f->qidt->uniqpath;
+
+ f->inval = 0;
+
+ free(dir);
+
+ return f;
+}
+
+void
+initroot(void)
+{
+ Dir *dir;
+
+ root = emallocz(sizeof(File));
+ root->name = estrdup(".");
+
+ dir = dirstat(root->name);
+ if(dir == nil)
+ fatal("root stat");
+
+ root->ref = 1;
+ root->qid.vers = dir->qid.vers;
+ root->qidt = uniqueqid(dir);
+ root->qid.path = root->qidt->uniqpath;
+ root->qid.type = QTDIR;
+ free(dir);
+
+ psmpt = emallocz(sizeof(File));
+ psmpt->name = estrdup("/");
+
+ dir = dirstat(psmpt->name);
+ if(dir == nil)
+ return;
+
+ psmpt->ref = 1;
+ psmpt->qid.vers = dir->qid.vers;
+ psmpt->qidt = uniqueqid(dir);
+ psmpt->qid.path = psmpt->qidt->uniqpath;
+ free(dir);
+
+ psmpt = file(psmpt, "mnt");
+ if(psmpt == 0)
+ return;
+ psmpt = file(psmpt, "exportfs");
+}
+
+char*
+makepath(File *p, char *name)
+{
+ int i, n;
+ char *c, *s, *path, *seg[256];
+
+ seg[0] = name;
+ n = strlen(name)+2;
+ for(i = 1; i < 256 && p; i++, p = p->parent){
+ seg[i] = p->name;
+ n += strlen(p->name)+1;
+ }
+ path = malloc(n);
+ if(path == nil)
+ fatal("out of memory");
+ s = path;
+
+ while(i--) {
+ for(c = seg[i]; *c; c++)
+ *s++ = *c;
+ *s++ = '/';
+ }
+ while(s[-1] == '/')
+ s--;
+ *s = '\0';
+
+ return path;
+}
+
+int
+qidhash(vlong path)
+{
+ int h, n;
+
+ h = 0;
+ for(n=0; n<64; n+=Nqidbits){
+ h ^= path;
+ path >>= Nqidbits;
+ }
+ return h & (Nqidtab-1);
+}
+
+void
+freeqid(Qidtab *q)
+{
+ ulong h;
+ Qidtab *l;
+
+ q->ref--;
+ if(q->ref > 0)
+ return;
+ qfreecnt++;
+ h = qidhash(q->path);
+ if(qidtab[h] == q)
+ qidtab[h] = q->next;
+ else{
+ for(l=qidtab[h]; l->next!=q; l=l->next)
+ if(l->next == nil)
+ fatal("bad qid list");
+ l->next = q->next;
+ }
+ free(q);
+}
+
+Qidtab*
+qidlookup(Dir *d)
+{
+ ulong h;
+ Qidtab *q;
+
+ h = qidhash(d->qid.path);
+ for(q=qidtab[h]; q!=nil; q=q->next)
+ if(q->type==d->type && q->dev==d->dev && q->path==d->qid.path)
+ return q;
+ return nil;
+}
+
+int
+qidexists(vlong path)
+{
+ int h;
+ Qidtab *q;
+
+ for(h=0; h<Nqidtab; h++)
+ for(q=qidtab[h]; q!=nil; q=q->next)
+ if(q->uniqpath == path)
+ return 1;
+ return 0;
+}
+
+Qidtab*
+uniqueqid(Dir *d)
+{
+ ulong h;
+ vlong path;
+ Qidtab *q;
+
+ q = qidlookup(d);
+ if(q != nil){
+ q->ref++;
+ return q;
+ }
+ path = d->qid.path;
+ while(qidexists(path)){
+ DEBUG(DFD, "collision on %s\n", d->name);
+ /* collision: find a new one */
+ ncollision++;
+ path &= QIDPATH;
+ ++newqid;
+ if(newqid >= (1<<16)){
+ DEBUG(DFD, "collision wraparound\n");
+ newqid = 1;
+ }
+ path |= newqid<<48;
+ DEBUG(DFD, "assign qid %.16llux\n", path);
+ }
+ q = mallocz(sizeof(Qidtab), 1);
+ if(q == nil)
+ fatal("no memory for qid table");
+ qidcnt++;
+ q->ref = 1;
+ q->type = d->type;
+ q->dev = d->dev;
+ q->path = d->qid.path;
+ q->uniqpath = path;
+ h = qidhash(d->qid.path);
+ q->next = qidtab[h];
+ qidtab[h] = q;
+ return q;
+}
+
+void
+fatal(char *s, ...)
+{
+ char buf[ERRMAX];
+ va_list arg;
+ Proc *m;
+
+ if (s) {
+ va_start(arg, s);
+ vsnprint(buf, ERRMAX, s, arg);
+ va_end(arg);
+ }
+
+ /* Clear away the slave children */
+ for(m = Proclist; m; m = m->next)
+ postnote(PNPROC, m->pid, "kill");
+
+ DEBUG(DFD, "%s\n", buf);
+ if (s)
+ sysfatal("%s", buf); /* caution: buf could contain '%' */
+ else
+ exits(nil);
+}
+
+void*
+emallocz(uint n)
+{
+ void *p;
+
+ p = mallocz(n, 1);
+ if(p == nil)
+ fatal(Enomem);
+ return p;
+}
+
+char*
+estrdup(char *s)
+{
+ char *t;
+
+ t = strdup(s);
+ if(t == nil)
+ fatal(Enomem);
+ return t;
+}
+
+/* Network on fd1, mount driver on fd0 */
+int
+filter(int fd, char *cmd)
+{
+ int p[2], lfd, len, nb, argc;
+ char newport[128], buf[128], devdir[40], *s, *file, *argv[16];
+
+ /* Get a free port and post it to the client. */
+ if (announce(anstring, devdir) < 0)
+ sysfatal("filter: Cannot announce %s: %r", anstring);
+
+ snprint(buf, sizeof(buf), "%s/local", devdir);
+ buf[sizeof buf - 1] = '\0';
+ if ((lfd = open(buf, OREAD)) < 0)
+ sysfatal("filter: Cannot open %s: %r", buf);
+ if ((len = read(lfd, newport, sizeof newport - 1)) < 0)
+ sysfatal("filter: Cannot read %s: %r", buf);
+ close(lfd);
+ newport[len] = '\0';
+
+ if ((s = strchr(newport, '\n')) != nil)
+ *s = '\0';
+
+ if ((nb = write(fd, newport, len)) < 0)
+ sysfatal("getport; cannot write port; %r");
+ assert(nb == len);
+
+ argc = tokenize(cmd, argv, nelem(argv)-2);
+ if (argc == 0)
+ sysfatal("filter: empty command");
+ argv[argc++] = buf;
+ argv[argc] = nil;
+ file = argv[0];
+ if (s = strrchr(argv[0], '/'))
+ argv[0] = s+1;
+
+ if(pipe(p) < 0)
+ fatal("pipe");
+
+ switch(rfork(RFNOWAIT|RFPROC|RFFDG)) {
+ case -1:
+ fatal("rfork record module");
+ case 0:
+ if (dup(p[0], 1) < 0)
+ fatal("filter: Cannot dup to 1; %r\n");
+ if (dup(p[0], 0) < 0)
+ fatal("filter: Cannot dup to 0; %r\n");
+ close(p[0]);
+ close(p[1]);
+ exec(file, argv);
+ fatal("exec record module");
+ default:
+ close(fd);
+ close(p[0]);
+ }
+ return p[1];
+}
+
+static void
+mksecret(char *t, uchar *f)
+{
+ sprint(t, "%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux",
+ f[0], f[1], f[2], f[3], f[4], f[5], f[6], f[7], f[8], f[9]);
+}
diff --git a/sys/src/cmd/exportfs/exportfs.h b/sys/src/cmd/exportfs/exportfs.h
new file mode 100755
index 000000000..0f7ba3e71
--- /dev/null
+++ b/sys/src/cmd/exportfs/exportfs.h
@@ -0,0 +1,144 @@
+/*
+ * exportfs.h - definitions for exporting file server
+ */
+
+#define DEBUG if(!dbg){}else fprint
+#define DFD 9
+#define fidhash(s) fhash[s%FHASHSIZE]
+
+typedef struct Fsrpc Fsrpc;
+typedef struct Fid Fid;
+typedef struct File File;
+typedef struct Proc Proc;
+typedef struct Qidtab Qidtab;
+
+struct Fsrpc
+{
+ int busy; /* Work buffer has pending rpc to service */
+ uintptr pid; /* Pid of slave process executing the rpc */
+ int canint; /* Interrupt gate */
+ int flushtag; /* Tag on which to reply to flush */
+ Fcall work; /* Plan 9 incoming Fcall */
+ uchar *buf; /* Data buffer */
+};
+
+struct Fid
+{
+ int fid; /* system fd for i/o */
+ File *f; /* File attached to this fid */
+ int mode;
+ int nr; /* fid number */
+ int mid; /* Mount id */
+ Fid *next; /* hash link */
+
+ /* for preaddir -- ARRGH! */
+ Dir *dir; /* buffer for reading directories */
+ int ndir; /* number of entries in dir */
+ int cdir; /* number of consumed entries in dir */
+ int gdir; /* glue index */
+ vlong offset; /* offset in virtual directory */
+};
+
+struct File
+{
+ char *name;
+ int ref;
+ Qid qid;
+ Qidtab *qidt;
+ int inval;
+ File *parent;
+ File *child;
+ File *childlist;
+};
+
+struct Proc
+{
+ uintptr pid;
+ int busy;
+ Proc *next;
+};
+
+struct Qidtab
+{
+ int ref;
+ int type;
+ int dev;
+ vlong path;
+ vlong uniqpath;
+ Qidtab *next;
+};
+
+enum
+{
+ MAXPROC = 50,
+ FHASHSIZE = 64,
+ Nr_workbufs = 50,
+ Fidchunk = 1000,
+ Npsmpt = 32,
+ Nqidbits = 5,
+ Nqidtab = (1<<Nqidbits),
+};
+
+char Ebadfid[];
+char Enotdir[];
+char Edupfid[];
+char Eopen[];
+char Exmnt[];
+char Enomem[];
+char Emip[];
+char Enopsmt[];
+
+Extern Fsrpc *Workq;
+Extern int dbg;
+Extern File *root;
+Extern File *psmpt;
+Extern Fid **fhash;
+Extern Fid *fidfree;
+Extern Proc *Proclist;
+Extern char psmap[Npsmpt];
+Extern Qidtab *qidtab[Nqidtab];
+Extern ulong messagesize;
+Extern char Enomem[];
+Extern int srvfd;
+Extern char* patternfile;
+
+/* File system protocol service procedures */
+void Xattach(Fsrpc*);
+void Xauth(Fsrpc*);
+void Xclunk(Fsrpc*);
+void Xcreate(Fsrpc*);
+void Xflush(Fsrpc*);
+void Xnop(Fsrpc*);
+void Xremove(Fsrpc*);
+void Xstat(Fsrpc*);
+void Xversion(Fsrpc*);
+void Xwalk(Fsrpc*);
+void Xwstat(Fsrpc*);
+void slave(Fsrpc*);
+
+void reply(Fcall*, Fcall*, char*);
+Fid *getfid(int);
+int freefid(int);
+Fid *newfid(int);
+Fsrpc *getsbuf(void);
+void initroot(void);
+void fatal(char*, ...);
+char* makepath(File*, char*);
+File *file(File*, char*);
+void freefile(File*);
+void slaveopen(Fsrpc*);
+void slaveread(Fsrpc*);
+void slavewrite(Fsrpc*);
+void blockingslave(void);
+void reopen(Fid *f);
+void noteproc(int, char*);
+void flushaction(void*, char*);
+void pushfcall(char*);
+Qidtab* uniqueqid(Dir*);
+void freeqid(Qidtab*);
+char* estrdup(char*);
+void* emallocz(uint);
+int readmessage(int, char*, int);
+void exclusions(void);
+int excludefile(char*);
+int preaddir(Fid*, uchar*, int, vlong);
diff --git a/sys/src/cmd/exportfs/exportsrv.c b/sys/src/cmd/exportfs/exportsrv.c
new file mode 100755
index 000000000..842880529
--- /dev/null
+++ b/sys/src/cmd/exportfs/exportsrv.c
@@ -0,0 +1,764 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#define Extern extern
+#include "exportfs.h"
+
+extern char *netdir, *local, *remote;
+
+char Ebadfid[] = "Bad fid";
+char Enotdir[] = "Not a directory";
+char Edupfid[] = "Fid already in use";
+char Eopen[] = "Fid already opened";
+char Exmnt[] = "Cannot .. past mount point";
+char Emip[] = "Mount in progress";
+char Enopsmt[] = "Out of pseudo mount points";
+char Enomem[] = "No memory";
+char Eversion[] = "Bad 9P2000 version";
+char Ereadonly[] = "File system read only";
+
+ulong messagesize;
+int readonly;
+
+void
+Xversion(Fsrpc *t)
+{
+ Fcall rhdr;
+
+ if(t->work.msize > messagesize)
+ t->work.msize = messagesize;
+ messagesize = t->work.msize;
+ if(strncmp(t->work.version, "9P2000", 6) != 0){
+ reply(&t->work, &rhdr, Eversion);
+ return;
+ }
+ rhdr.version = "9P2000";
+ rhdr.msize = t->work.msize;
+ reply(&t->work, &rhdr, 0);
+ t->busy = 0;
+}
+
+void
+Xauth(Fsrpc *t)
+{
+ Fcall rhdr;
+
+ reply(&t->work, &rhdr, "exportfs: authentication not required");
+ t->busy = 0;
+}
+
+void
+Xflush(Fsrpc *t)
+{
+ Fsrpc *w, *e;
+ Fcall rhdr;
+
+ e = &Workq[Nr_workbufs];
+
+ for(w = Workq; w < e; w++) {
+ if(w->work.tag == t->work.oldtag) {
+ DEBUG(DFD, "\tQ busy %d pid %p can %d\n", w->busy, w->pid, w->canint);
+ if(w->busy && w->pid) {
+ w->flushtag = t->work.tag;
+ DEBUG(DFD, "\tset flushtag %d\n", t->work.tag);
+ if(w->canint)
+ postnote(PNPROC, w->pid, "flush");
+ t->busy = 0;
+ return;
+ }
+ }
+ }
+
+ reply(&t->work, &rhdr, 0);
+ DEBUG(DFD, "\tflush reply\n");
+ t->busy = 0;
+}
+
+void
+Xattach(Fsrpc *t)
+{
+ int i, nfd;
+ Fcall rhdr;
+ Fid *f;
+ char buf[128];
+
+ f = newfid(t->work.fid);
+ if(f == 0) {
+ reply(&t->work, &rhdr, Ebadfid);
+ t->busy = 0;
+ return;
+ }
+
+ if(srvfd >= 0){
+ if(psmpt == 0){
+ Nomount:
+ reply(&t->work, &rhdr, Enopsmt);
+ t->busy = 0;
+ freefid(t->work.fid);
+ return;
+ }
+ for(i=0; i<Npsmpt; i++)
+ if(psmap[i] == 0)
+ break;
+ if(i >= Npsmpt)
+ goto Nomount;
+ sprint(buf, "%d", i);
+ f->f = file(psmpt, buf);
+ if(f->f == nil)
+ goto Nomount;
+ sprint(buf, "/mnt/exportfs/%d", i);
+ nfd = dup(srvfd, -1);
+ if(amount(nfd, buf, MREPL|MCREATE, t->work.aname) < 0){
+ errstr(buf, sizeof buf);
+ reply(&t->work, &rhdr, buf);
+ t->busy = 0;
+ freefid(t->work.fid);
+ close(nfd);
+ return;
+ }
+ psmap[i] = 1;
+ f->mid = i;
+ }else{
+ f->f = root;
+ f->f->ref++;
+ }
+
+ rhdr.qid = f->f->qid;
+ reply(&t->work, &rhdr, 0);
+ t->busy = 0;
+}
+
+Fid*
+clonefid(Fid *f, int new)
+{
+ Fid *n;
+
+ n = newfid(new);
+ if(n == 0) {
+ n = getfid(new);
+ if(n == 0)
+ fatal("inconsistent fids");
+ if(n->fid >= 0)
+ close(n->fid);
+ freefid(new);
+ n = newfid(new);
+ if(n == 0)
+ fatal("inconsistent fids2");
+ }
+ n->f = f->f;
+ n->f->ref++;
+ return n;
+}
+
+void
+Xwalk(Fsrpc *t)
+{
+ char err[ERRMAX], *e;
+ Fcall rhdr;
+ Fid *f, *nf;
+ File *wf;
+ int i;
+
+ f = getfid(t->work.fid);
+ if(f == 0) {
+ reply(&t->work, &rhdr, Ebadfid);
+ t->busy = 0;
+ return;
+ }
+
+ nf = nil;
+ if(t->work.newfid != t->work.fid){
+ nf = clonefid(f, t->work.newfid);
+ f = nf;
+ }
+
+ rhdr.nwqid = 0;
+ e = nil;
+ for(i=0; i<t->work.nwname; i++){
+ if(i == MAXWELEM){
+ e = "Too many path elements";
+ break;
+ }
+
+ if(strcmp(t->work.wname[i], "..") == 0) {
+ if(f->f->parent == nil) {
+ e = Exmnt;
+ break;
+ }
+ wf = f->f->parent;
+ wf->ref++;
+ goto Accept;
+ }
+
+ wf = file(f->f, t->work.wname[i]);
+ if(wf == 0){
+ errstr(err, sizeof err);
+ e = err;
+ break;
+ }
+ Accept:
+ freefile(f->f);
+ rhdr.wqid[rhdr.nwqid++] = wf->qid;
+ f->f = wf;
+ continue;
+ }
+
+ if(nf!=nil && (e!=nil || rhdr.nwqid!=t->work.nwname))
+ freefid(t->work.newfid);
+ if(rhdr.nwqid > 0)
+ e = nil;
+ reply(&t->work, &rhdr, e);
+ t->busy = 0;
+}
+
+void
+Xclunk(Fsrpc *t)
+{
+ Fcall rhdr;
+ Fid *f;
+
+ f = getfid(t->work.fid);
+ if(f == 0) {
+ reply(&t->work, &rhdr, Ebadfid);
+ t->busy = 0;
+ return;
+ }
+
+ if(f->fid >= 0)
+ close(f->fid);
+
+ freefid(t->work.fid);
+ reply(&t->work, &rhdr, 0);
+ t->busy = 0;
+}
+
+void
+Xstat(Fsrpc *t)
+{
+ char err[ERRMAX], *path;
+ Fcall rhdr;
+ Fid *f;
+ Dir *d;
+ int s;
+ uchar *statbuf;
+
+ f = getfid(t->work.fid);
+ if(f == 0) {
+ reply(&t->work, &rhdr, Ebadfid);
+ t->busy = 0;
+ return;
+ }
+ if(f->fid >= 0)
+ d = dirfstat(f->fid);
+ else {
+ path = makepath(f->f, "");
+ d = dirstat(path);
+ free(path);
+ }
+
+ if(d == nil) {
+ errstr(err, sizeof err);
+ reply(&t->work, &rhdr, err);
+ t->busy = 0;
+ return;
+ }
+
+ d->qid.path = f->f->qidt->uniqpath;
+ s = sizeD2M(d);
+ statbuf = emallocz(s);
+ s = convD2M(d, statbuf, s);
+ free(d);
+ rhdr.nstat = s;
+ rhdr.stat = statbuf;
+ reply(&t->work, &rhdr, 0);
+ free(statbuf);
+ t->busy = 0;
+}
+
+static int
+getiounit(int fd)
+{
+ int n;
+
+ n = iounit(fd);
+ if(n > messagesize-IOHDRSZ)
+ n = messagesize-IOHDRSZ;
+ return n;
+}
+
+void
+Xcreate(Fsrpc *t)
+{
+ char err[ERRMAX], *path;
+ Fcall rhdr;
+ Fid *f;
+ File *nf;
+
+ if(readonly) {
+ reply(&t->work, &rhdr, Ereadonly);
+ t->busy = 0;
+ return;
+ }
+ f = getfid(t->work.fid);
+ if(f == 0) {
+ reply(&t->work, &rhdr, Ebadfid);
+ t->busy = 0;
+ return;
+ }
+
+
+ path = makepath(f->f, t->work.name);
+ f->fid = create(path, t->work.mode, t->work.perm);
+ free(path);
+ if(f->fid < 0) {
+ errstr(err, sizeof err);
+ reply(&t->work, &rhdr, err);
+ t->busy = 0;
+ return;
+ }
+
+ nf = file(f->f, t->work.name);
+ if(nf == 0) {
+ errstr(err, sizeof err);
+ reply(&t->work, &rhdr, err);
+ t->busy = 0;
+ return;
+ }
+
+ f->mode = t->work.mode;
+ freefile(f->f);
+ f->f = nf;
+ rhdr.qid = f->f->qid;
+ rhdr.iounit = getiounit(f->fid);
+ reply(&t->work, &rhdr, 0);
+ t->busy = 0;
+}
+
+void
+Xremove(Fsrpc *t)
+{
+ char err[ERRMAX], *path;
+ Fcall rhdr;
+ Fid *f;
+
+ if(readonly) {
+ reply(&t->work, &rhdr, Ereadonly);
+ t->busy = 0;
+ return;
+ }
+ f = getfid(t->work.fid);
+ if(f == 0) {
+ reply(&t->work, &rhdr, Ebadfid);
+ t->busy = 0;
+ return;
+ }
+
+ path = makepath(f->f, "");
+ DEBUG(DFD, "\tremove: %s\n", path);
+ if(remove(path) < 0) {
+ free(path);
+ errstr(err, sizeof err);
+ reply(&t->work, &rhdr, err);
+ t->busy = 0;
+ return;
+ }
+ free(path);
+
+ f->f->inval = 1;
+ if(f->fid >= 0)
+ close(f->fid);
+ freefid(t->work.fid);
+
+ reply(&t->work, &rhdr, 0);
+ t->busy = 0;
+}
+
+void
+Xwstat(Fsrpc *t)
+{
+ char err[ERRMAX], *path;
+ Fcall rhdr;
+ Fid *f;
+ int s;
+ char *strings;
+ Dir d;
+
+ if(readonly) {
+ reply(&t->work, &rhdr, Ereadonly);
+ t->busy = 0;
+ return;
+ }
+ f = getfid(t->work.fid);
+ if(f == 0) {
+ reply(&t->work, &rhdr, Ebadfid);
+ t->busy = 0;
+ return;
+ }
+ strings = emallocz(t->work.nstat); /* ample */
+ if(convM2D(t->work.stat, t->work.nstat, &d, strings) <= BIT16SZ){
+ rerrstr(err, sizeof err);
+ reply(&t->work, &rhdr, err);
+ t->busy = 0;
+ free(strings);
+ return;
+ }
+
+ if(f->fid >= 0)
+ s = dirfwstat(f->fid, &d);
+ else {
+ path = makepath(f->f, "");
+ s = dirwstat(path, &d);
+ free(path);
+ }
+ if(s < 0) {
+ rerrstr(err, sizeof err);
+ reply(&t->work, &rhdr, err);
+ }
+ else {
+ /* wstat may really be rename */
+ if(strcmp(d.name, f->f->name)!=0 && strcmp(d.name, "")!=0){
+ free(f->f->name);
+ f->f->name = estrdup(d.name);
+ }
+ reply(&t->work, &rhdr, 0);
+ }
+ free(strings);
+ t->busy = 0;
+}
+
+/*
+ * based on libthread's threadsetname, but drags in less library code.
+ * actually just sets the arguments displayed.
+ */
+void
+procsetname(char *fmt, ...)
+{
+ int fd;
+ char *cmdname;
+ char buf[128];
+ va_list arg;
+
+ va_start(arg, fmt);
+ cmdname = vsmprint(fmt, arg);
+ va_end(arg);
+ if (cmdname == nil)
+ return;
+ snprint(buf, sizeof buf, "#p/%d/args", getpid());
+ if((fd = open(buf, OWRITE)) >= 0){
+ write(fd, cmdname, strlen(cmdname)+1);
+ close(fd);
+ }
+ free(cmdname);
+}
+
+void
+slave(Fsrpc *f)
+{
+ Proc *p;
+ uintptr pid;
+ Fcall rhdr;
+ static int nproc;
+
+ if(readonly){
+ switch(f->work.type){
+ case Twrite:
+ reply(&f->work, &rhdr, Ereadonly);
+ f->busy = 0;
+ return;
+ case Topen:
+ if((f->work.mode&3) == OWRITE || (f->work.mode&OTRUNC)){
+ reply(&f->work, &rhdr, Ereadonly);
+ f->busy = 0;
+ return;
+ }
+ }
+ }
+ for(;;) {
+ for(p = Proclist; p; p = p->next) {
+ if(p->busy == 0) {
+ f->pid = p->pid;
+ p->busy = 1;
+ pid = (uintptr)rendezvous((void*)p->pid, f);
+ if(pid != p->pid)
+ fatal("rendezvous sync fail");
+ return;
+ }
+ }
+
+ if(++nproc > MAXPROC)
+ fatal("too many procs");
+
+ pid = rfork(RFPROC|RFMEM);
+ switch(pid) {
+ case -1:
+ fatal("rfork");
+
+ case 0:
+ if (local[0] != '\0')
+ if (netdir[0] != '\0')
+ procsetname("%s: %s -> %s", netdir,
+ local, remote);
+ else
+ procsetname("%s -> %s", local, remote);
+ blockingslave();
+ fatal("slave");
+
+ default:
+ p = malloc(sizeof(Proc));
+ if(p == 0)
+ fatal("out of memory");
+
+ p->busy = 0;
+ p->pid = pid;
+ p->next = Proclist;
+ Proclist = p;
+
+ rendezvous((void*)pid, p);
+ }
+ }
+}
+
+void
+blockingslave(void)
+{
+ Fsrpc *p;
+ Fcall rhdr;
+ Proc *m;
+ uintptr pid;
+
+ notify(flushaction);
+
+ pid = getpid();
+
+ m = rendezvous((void*)pid, 0);
+
+ for(;;) {
+ p = rendezvous((void*)pid, (void*)pid);
+ if(p == (void*)~0) /* Interrupted */
+ continue;
+
+ DEBUG(DFD, "\tslave: %p %F b %d p %p\n", pid, &p->work, p->busy, p->pid);
+ if(p->flushtag != NOTAG)
+ goto flushme;
+
+ switch(p->work.type) {
+ case Tread:
+ slaveread(p);
+ break;
+
+ case Twrite:
+ slavewrite(p);
+ break;
+
+ case Topen:
+ slaveopen(p);
+ break;
+
+ default:
+ reply(&p->work, &rhdr, "exportfs: slave type error");
+ }
+ if(p->flushtag != NOTAG) {
+flushme:
+ p->work.type = Tflush;
+ p->work.tag = p->flushtag;
+ reply(&p->work, &rhdr, 0);
+ }
+ p->busy = 0;
+ m->busy = 0;
+ }
+}
+
+int
+openmount(int sfd)
+{
+ int p[2];
+ char *arg[10], fdbuf[20], mbuf[20];
+
+ if(pipe(p) < 0)
+ return -1;
+
+ switch(rfork(RFPROC|RFMEM|RFNOWAIT|RFNAMEG|RFFDG)){
+ case -1:
+ return -1;
+
+ default:
+ close(sfd);
+ close(p[0]);
+ return p[1];
+
+ case 0:
+ break;
+ }
+
+ close(p[1]);
+
+ arg[0] = "exportfs";
+ snprint(fdbuf, sizeof fdbuf, "-S/fd/%d", sfd);
+ arg[1] = fdbuf;
+ snprint(mbuf, sizeof mbuf, "-m%lud", messagesize-IOHDRSZ);
+ arg[2] = mbuf;
+ arg[3] = nil;
+
+ close(0);
+ close(1);
+ dup(p[0], 0);
+ dup(p[0], 1);
+ exec("/bin/exportfs", arg);
+ _exits("whoops: exec failed");
+ return -1;
+}
+
+void
+slaveopen(Fsrpc *p)
+{
+ char err[ERRMAX], *path;
+ Fcall *work, rhdr;
+ Fid *f;
+ Dir *d;
+
+ work = &p->work;
+
+ f = getfid(work->fid);
+ if(f == 0) {
+ reply(work, &rhdr, Ebadfid);
+ return;
+ }
+ if(f->fid >= 0) {
+ close(f->fid);
+ f->fid = -1;
+ }
+
+ path = makepath(f->f, "");
+ DEBUG(DFD, "\topen: %s %d\n", path, work->mode);
+
+ p->canint = 1;
+ if(p->flushtag != NOTAG){
+ free(path);
+ return;
+ }
+ /* There is a race here I ignore because there are no locks */
+ f->fid = open(path, work->mode);
+ free(path);
+ p->canint = 0;
+ if(f->fid < 0 || (d = dirfstat(f->fid)) == nil) {
+ Error:
+ errstr(err, sizeof err);
+ reply(work, &rhdr, err);
+ return;
+ }
+ f->f->qid = d->qid;
+ free(d);
+ if(f->f->qid.type & QTMOUNT){ /* fork new exportfs for this */
+ f->fid = openmount(f->fid);
+ if(f->fid < 0)
+ goto Error;
+ }
+
+ DEBUG(DFD, "\topen: fd %d\n", f->fid);
+ f->mode = work->mode;
+ f->offset = 0;
+ rhdr.iounit = getiounit(f->fid);
+ rhdr.qid = f->f->qid;
+ reply(work, &rhdr, 0);
+}
+
+void
+slaveread(Fsrpc *p)
+{
+ Fid *f;
+ int n, r;
+ Fcall *work, rhdr;
+ char *data, err[ERRMAX];
+
+ work = &p->work;
+
+ f = getfid(work->fid);
+ if(f == 0) {
+ reply(work, &rhdr, Ebadfid);
+ return;
+ }
+
+ n = (work->count > messagesize-IOHDRSZ) ? messagesize-IOHDRSZ : work->count;
+ p->canint = 1;
+ if(p->flushtag != NOTAG)
+ return;
+ data = malloc(n);
+ if(data == nil)
+ fatal(Enomem);
+
+ /* can't just call pread, since directories must update the offset */
+ if(patternfile != nil && (f->f->qid.type&QTDIR))
+ r = preaddir(f, (uchar*)data, n, work->offset);
+ else
+ r = pread(f->fid, data, n, work->offset);
+ p->canint = 0;
+ if(r < 0) {
+ free(data);
+ errstr(err, sizeof err);
+ reply(work, &rhdr, err);
+ return;
+ }
+
+ DEBUG(DFD, "\tread: fd=%d %d bytes\n", f->fid, r);
+
+ rhdr.data = data;
+ rhdr.count = r;
+ reply(work, &rhdr, 0);
+ free(data);
+}
+
+void
+slavewrite(Fsrpc *p)
+{
+ char err[ERRMAX];
+ Fcall *work, rhdr;
+ Fid *f;
+ int n;
+
+ work = &p->work;
+
+ f = getfid(work->fid);
+ if(f == 0) {
+ reply(work, &rhdr, Ebadfid);
+ return;
+ }
+
+ n = (work->count > messagesize-IOHDRSZ) ? messagesize-IOHDRSZ : work->count;
+ p->canint = 1;
+ if(p->flushtag != NOTAG)
+ return;
+ n = pwrite(f->fid, work->data, n, work->offset);
+ p->canint = 0;
+ if(n < 0) {
+ errstr(err, sizeof err);
+ reply(work, &rhdr, err);
+ return;
+ }
+
+ DEBUG(DFD, "\twrite: %d bytes fd=%d\n", n, f->fid);
+
+ rhdr.count = n;
+ reply(work, &rhdr, 0);
+}
+
+void
+reopen(Fid *f)
+{
+ USED(f);
+ fatal("reopen");
+}
+
+void
+flushaction(void *a, char *cause)
+{
+ USED(a);
+ if(strncmp(cause, "sys:", 4) == 0 && !strstr(cause, "pipe")) {
+ fprint(2, "exportsrv: note: %s\n", cause);
+ exits("noted");
+ }
+ if(strncmp(cause, "kill", 4) == 0)
+ noted(NDFLT);
+
+ noted(NCONT);
+}
diff --git a/sys/src/cmd/exportfs/mkfile b/sys/src/cmd/exportfs/mkfile
new file mode 100755
index 000000000..cf1f51451
--- /dev/null
+++ b/sys/src/cmd/exportfs/mkfile
@@ -0,0 +1,18 @@
+</$objtype/mkfile
+
+TARG=exportfs
+OFILES=\
+ exportfs.$O\
+ exportsrv.$O\
+ pattern.$O\
+
+HFILES=exportfs.h\
+
+BIN=/$objtype/bin
+
+UPDATE=\
+ mkfile\
+ $HFILES\
+ ${OFILES:%.$O=%.c}\
+
+</sys/src/cmd/mkone
diff --git a/sys/src/cmd/exportfs/pattern.c b/sys/src/cmd/exportfs/pattern.c
new file mode 100755
index 000000000..0dcbc9380
--- /dev/null
+++ b/sys/src/cmd/exportfs/pattern.c
@@ -0,0 +1,154 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <bio.h>
+#include <regexp.h>
+#define Extern
+#include "exportfs.h"
+
+Reprog **exclude, **include;
+char *patternfile;
+
+void
+exclusions(void)
+{
+ Biobuf *f;
+ int ni, nmaxi, ne, nmaxe;
+ char *line;
+
+ if(patternfile == nil)
+ return;
+
+ f = Bopen(patternfile, OREAD);
+ if(f == nil)
+ fatal("cannot open patternfile");
+ ni = 0;
+ nmaxi = 100;
+ include = malloc(nmaxi*sizeof(*include));
+ if(include == nil)
+ fatal("out of memory");
+ include[0] = nil;
+ ne = 0;
+ nmaxe = 100;
+ exclude = malloc(nmaxe*sizeof(*exclude));
+ if(exclude == nil)
+ fatal("out of memory");
+ exclude[0] = nil;
+ while(line = Brdline(f, '\n')){
+ line[Blinelen(f) - 1] = 0;
+ if(strlen(line) < 2 || line[1] != ' ')
+ continue;
+ switch(line[0]){
+ case '+':
+ if(ni+1 >= nmaxi){
+ nmaxi = 2*nmaxi;
+ include = realloc(include, nmaxi*sizeof(*include));
+ if(include == nil)
+ fatal("out of memory");
+ }
+ DEBUG(DFD, "\tinclude %s\n", line+2);
+ include[ni] = regcomp(line+2);
+ include[++ni] = nil;
+ break;
+ case '-':
+ if(ne+1 >= nmaxe){
+ nmaxe = 2*nmaxe;
+ exclude = realloc(exclude, nmaxe*sizeof(*exclude));
+ if(exclude == nil)
+ fatal("out of memory");
+ }
+ DEBUG(DFD, "\texclude %s\n", line+2);
+ exclude[ne] = regcomp(line+2);
+ exclude[++ne] = nil;
+ break;
+ default:
+ DEBUG(DFD, "ignoring pattern %s\n", line);
+ break;
+ }
+ }
+ Bterm(f);
+}
+
+int
+excludefile(char *path)
+{
+ Reprog **re;
+ char *p;
+
+ if(*(path+1) == 0)
+ p = "/";
+ else
+ p = path+1;
+
+ DEBUG(DFD, "checking %s\n", path);
+ for(re = include; *re != nil; re++){
+ if(regexec(*re, p, nil, 0) != 1){
+ DEBUG(DFD, "excluded+ %s\n", path);
+ return -1;
+ }
+ }
+ for(re = exclude; *re != nil; re++){
+ if(regexec(*re, p, nil, 0) == 1){
+ DEBUG(DFD, "excluded- %s\n", path);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+int
+preaddir(Fid *f, uchar *data, int n, vlong offset)
+{
+ int r = 0, m;
+ Dir *d;
+
+ DEBUG(DFD, "\tpreaddir n=%d wo=%lld fo=%lld\n", n, offset, f->offset);
+ if(offset == 0 && f->offset != 0){
+ if(seek(f->fid, 0, 0) != 0)
+ return -1;
+ f->offset = f->cdir = f->ndir = 0;
+ free(f->dir);
+ f->dir = nil;
+ }else if(offset != f->offset){
+ werrstr("can't seek dir %lld to %lld", f->offset, offset);
+ return -1;
+ }
+
+ while(n > 0){
+ if(f->dir == nil){
+ f->ndir = dirread(f->fid, &f->dir);
+ if(f->ndir < 0)
+ return f->ndir;
+ if(f->ndir == 0)
+ return r;
+ }
+ d = &f->dir[f->cdir++];
+ if(exclude){
+ char *p = makepath(f->f, d->name);
+ if(excludefile(p)){
+ free(p);
+ goto skipentry;
+ }
+ free(p);
+ }
+ m = convD2M(d, data, n);
+ DEBUG(DFD, "\t\tconvD2M %d\n", m);
+ if(m <= BIT16SZ){
+ DEBUG(DFD, "\t\t\tneeded %d\n", GBIT16(data));
+ /* not enough room for full entry; leave for next time */
+ f->cdir--;
+ return r;
+ }else{
+ data += m;
+ n -= m;
+ r += m;
+ f->offset += m;
+ }
+skipentry: if(f->cdir >= f->ndir){
+ f->cdir = f->ndir = 0;
+ free(f->dir);
+ f->dir = nil;
+ }
+ }
+ return r;
+}