summaryrefslogtreecommitdiff
path: root/sys/src/cmd/9nfs
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/9nfs
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/9nfs')
-rwxr-xr-xsys/src/cmd/9nfs/9auth.c88
-rwxr-xr-xsys/src/cmd/9nfs/9p.c184
-rwxr-xr-xsys/src/cmd/9nfs/all.h16
-rwxr-xr-xsys/src/cmd/9nfs/auth.c31
-rwxr-xr-xsys/src/cmd/9nfs/authhostowner.c239
-rwxr-xr-xsys/src/cmd/9nfs/chat.c131
-rwxr-xr-xsys/src/cmd/9nfs/dat.h273
-rwxr-xr-xsys/src/cmd/9nfs/fns.h66
-rwxr-xr-xsys/src/cmd/9nfs/listalloc.c19
-rwxr-xr-xsys/src/cmd/9nfs/mkfile52
-rwxr-xr-xsys/src/cmd/9nfs/mport.c185
-rwxr-xr-xsys/src/cmd/9nfs/nametest.c99
-rwxr-xr-xsys/src/cmd/9nfs/nfs.c471
-rwxr-xr-xsys/src/cmd/9nfs/nfs.h44
-rwxr-xr-xsys/src/cmd/9nfs/nfsmount.c333
-rwxr-xr-xsys/src/cmd/9nfs/nfsserver.c655
-rwxr-xr-xsys/src/cmd/9nfs/pcnfsd.c180
-rwxr-xr-xsys/src/cmd/9nfs/portmapper.c172
-rwxr-xr-xsys/src/cmd/9nfs/rpc.c314
-rwxr-xr-xsys/src/cmd/9nfs/rpc.h84
-rwxr-xr-xsys/src/cmd/9nfs/server.c618
-rwxr-xr-xsys/src/cmd/9nfs/string.c105
-rwxr-xr-xsys/src/cmd/9nfs/strparse.c32
-rwxr-xr-xsys/src/cmd/9nfs/system.c34
-rwxr-xr-xsys/src/cmd/9nfs/testit8
-rwxr-xr-xsys/src/cmd/9nfs/unixnames.c322
-rwxr-xr-xsys/src/cmd/9nfs/xfile.c106
27 files changed, 4861 insertions, 0 deletions
diff --git a/sys/src/cmd/9nfs/9auth.c b/sys/src/cmd/9nfs/9auth.c
new file mode 100755
index 000000000..588aca4cb
--- /dev/null
+++ b/sys/src/cmd/9nfs/9auth.c
@@ -0,0 +1,88 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+
+#define NETCHLEN 16 /* max network challenge length */
+
+char * argv0;
+int debug;
+int delete;
+
+char * root = "/n/emelie";
+char * user;
+char file[64];
+char challenge[NETCHLEN];
+char response[NETCHLEN];
+
+void
+usage(void)
+{
+ printf("usage: %s [-d] username\n", argv0);
+ exit(1);
+}
+
+void
+main(int argc, char **argv)
+{
+ int fd, n;
+
+ for(argv0=*argv++,--argc; argc>0; ++argv,--argc){
+ if(argv[0][0] != '-' || argv[0][1] == '-')
+ break;
+ switch(argv[0][1]){
+ case 'D':
+ ++debug;
+ break;
+ case 'd':
+ ++delete;
+ break;
+ case 'r':
+ root = argv[0][2] ? &argv[0][2] : (--argc, *++argv);
+ break;
+ default:
+ usage();
+ break;
+ }
+ }
+ if(argc != 1)
+ usage();
+ user = argv[0];
+ snprintf(file, sizeof file, "%s/#%s", root, user);
+ if(debug)
+ printf("debug=%d, file=%s\n", debug, file);
+ if(delete){
+ fd = creat(file, 0600);
+ if(fd < 0){
+ perror(file);
+ exit(1);
+ }
+ exit(0);
+ }
+ fd = open(file, 2);
+ if(fd < 0){
+ perror(file);
+ exit(1);
+ }
+ n = read(fd, challenge, NETCHLEN);
+ if(debug)
+ printf("read %d\n", n);
+ if(n <= 0){
+ printf("read %d: ", n);
+ perror("");
+ exit(1);
+ }
+ printf("challenge: %s\n", challenge);
+ write(1, "response: ", 10);
+ read(0, response, NETCHLEN-1);
+ lseek(fd, 0, 0);
+ n = write(fd, response, NETCHLEN);
+ if(debug)
+ printf("write %d\n", n);
+ if(n <= 0){
+ printf("write %d: ", n);
+ perror("");
+ exit(1);
+ }
+ exit(0);
+}
diff --git a/sys/src/cmd/9nfs/9p.c b/sys/src/cmd/9nfs/9p.c
new file mode 100755
index 000000000..27fc2b5a8
--- /dev/null
+++ b/sys/src/cmd/9nfs/9p.c
@@ -0,0 +1,184 @@
+#include "all.h"
+
+static char *tnames[] = {
+ [Tversion] "version",
+ [Tauth] "auth",
+ [Tattach] "attach",
+ [Tflush] "flush",
+ [Twalk] "walk",
+ [Topen] "open",
+ [Tcreate] "create",
+ [Tread] "read",
+ [Twrite] "write",
+ [Tclunk] "clunk",
+ [Tremove] "remove",
+ [Tstat] "stat",
+ [Twstat] "wstat",
+};
+
+int messagesize = IOHDRSZ+Maxfdata;
+
+int
+xmesg(Session *s, int t)
+{
+ int n;
+
+ if(chatty){
+ if(0 <= t && t < nelem(tnames) && tnames[t])
+ chat("T%s...", tnames[t]);
+ else
+ chat("T%d...", t);
+ }
+ s->f.type = t;
+ s->f.tag = ++s->tag;
+ if(p9debug)
+ fprint(2, "xmseg\tsend %F\n", &s->f);
+ n = convS2M(&s->f, s->data, messagesize);
+ if(niwrite(s->fd, s->data, n) != n){
+ clog("xmesg write error on %d: %r\n", s->fd);
+ return -1;
+ }
+again:
+ n = read9pmsg(s->fd, s->data, messagesize);
+ if(n < 0){
+ clog("xmesg read error: %r\n");
+ return -1;
+ }
+ if(convM2S(s->data, n, &s->f) <= 0){
+ clog("xmesg bad convM2S %d %.2x %.2x %.2x %.2x\n",
+ n, ((uchar*)s->data)[0], ((uchar*)s->data)[1],
+ ((uchar*)s->data)[2], ((uchar*)s->data)[3]);
+ return -1;
+ }
+ if(p9debug)
+ fprint(2, "\trecv %F\n", &s->f);
+ if(s->f.tag != s->tag){
+ clog("xmesg tag %d for %d\n", s->f.tag, s->tag);
+ goto again;
+ }
+ if(s->f.type == Rerror){
+ if(t == Tclunk)
+ clog("xmesg clunk: %s", s->f.ename);
+ chat("xmesg %d error %s...", t, s->f.ename);
+ return -1;
+ }
+ if(s->f.type != t+1){
+ clog("xmesg type mismatch: %d, expected %d\n", s->f.type, t+1);
+ return -1;
+ }
+ return 0;
+}
+
+int
+clunkfid(Session *s, Fid *f)
+{
+ putfid(s, f);
+ if(s == 0 || f == 0)
+ return 0;
+ s->f.fid = f - s->fids;
+ return xmesg(s, Tclunk);
+}
+
+#define UNLINK(p) ((p)->prev->next = (p)->next, (p)->next->prev = (p)->prev)
+
+#define LINK(h, p) ((p)->next = (h)->next, (p)->prev = (h), \
+ (h)->next->prev = (p), (h)->next = (p))
+
+#define TOFRONT(h, p) ((h)->next != (p) ? (UNLINK(p), LINK(h,p)) : 0)
+
+Fid *
+newfid(Session *s)
+{
+ Fid *f, *fN;
+
+ chat("newfid..");
+ if(s->list.prev == 0){
+ chat("init..");
+ s->list.prev = &s->list;
+ s->list.next = &s->list;
+ s->free = s->fids;
+ if(0 && chatty)
+ fN = &s->fids[25];
+ else
+ fN = &s->fids[nelem(s->fids)];
+ for(f=s->fids; f<fN; f++){
+ f->owner = 0;
+ f->prev = 0;
+ f->next = f+1;
+ }
+ (f-1)->next = 0;
+ }
+ if(s->free){
+ f = s->free;
+ s->free = f->next;
+ LINK(&s->list, f);
+ }else{
+ for(f=s->list.prev; f!=&s->list; f=f->prev)
+ if(f->owner)
+ break;
+ if(f == &s->list){
+ clog("fid leak");
+ return 0;
+ }
+ setfid(s, f);
+ if(xmesg(s, Tclunk) < 0){
+ clog("clunk failed, no fids?");
+ /*return 0;*/
+ }
+ *(f->owner) = 0;
+ f->owner = 0;
+ }
+ chat("%ld...", f - s->fids);
+ f->tstale = nfstime + staletime;
+ return f;
+}
+
+void
+setfid(Session *s, Fid *f)
+{
+ /*
+ * TOFRONT(&s->list, f);
+ */
+ if(s->list.next != f){
+ UNLINK(f);
+ LINK(&s->list, f);
+ }
+
+ f->tstale = nfstime + staletime;
+ s->f.fid = f - s->fids;
+}
+
+void
+putfid(Session *s, Fid *f)
+{
+ chat("putfid %ld...", f-s->fids);
+ if(s == 0 || f == 0){
+ clog("putfid(0x%p, 0x%p) %s", s, f, (s ? s->service : "?"));
+ return;
+ }
+ UNLINK(f);
+ if(f->owner)
+ *(f->owner) = 0;
+ f->owner = 0;
+ f->prev = 0;
+ f->next = s->free;
+ s->free = f;
+}
+
+void
+fidtimer(Session *s, long now)
+{
+ Fid *f, *t;
+ int n;
+
+ f = s->list.next;
+ n = 0;
+ while(f != &s->list){
+ t = f;
+ f = f->next;
+ if(t->owner && now >= t->tstale)
+ ++n, clunkfid(s, t);
+ }
+ if(n > 0)
+ chat("fidtimer %s\n", s->service);
+}
diff --git a/sys/src/cmd/9nfs/all.h b/sys/src/cmd/9nfs/all.h
new file mode 100755
index 000000000..a4d720ef7
--- /dev/null
+++ b/sys/src/cmd/9nfs/all.h
@@ -0,0 +1,16 @@
+#include <u.h>
+#include <libc.h>
+#include <ip.h>
+#include <bio.h>
+#include <auth.h>
+#include <authsrv.h>
+#include <fcall.h>
+#include <regexp.h>
+#include "dat.h"
+#include "fns.h"
+#include "rpc.h"
+#include "nfs.h"
+#pragma varargck type "I" ulong
+#pragma varargck argpos chat 1
+#pragma varargck argpos clog 1
+#pragma varargck argpos panic 1
diff --git a/sys/src/cmd/9nfs/auth.c b/sys/src/cmd/9nfs/auth.c
new file mode 100755
index 000000000..672308452
--- /dev/null
+++ b/sys/src/cmd/9nfs/auth.c
@@ -0,0 +1,31 @@
+#include "all.h"
+
+/* this is all stubbed out; the NFS authentication stuff is now disabled - rob */
+
+Xfid *
+xfauth(Xfile *, String *)
+{
+ return 0;
+}
+
+long
+xfauthread(Xfid *xf, long, uchar *, long)
+{
+
+ chat("xfauthread %s...", xf->uid);
+ return 0;
+}
+
+long
+xfauthwrite(Xfid *xf, long, uchar *, long)
+{
+ chat("xfauthwrite %s...", xf->uid);
+ return 0;
+}
+
+int
+xfauthremove(Xfid *, char *)
+{
+ chat("authremove...");
+ return -1;
+}
diff --git a/sys/src/cmd/9nfs/authhostowner.c b/sys/src/cmd/9nfs/authhostowner.c
new file mode 100755
index 000000000..e91f0030b
--- /dev/null
+++ b/sys/src/cmd/9nfs/authhostowner.c
@@ -0,0 +1,239 @@
+#include "all.h"
+
+enum {
+ ARgiveup = 100,
+};
+
+static uchar*
+gstring(uchar *p, uchar *ep, char **s)
+{
+ uint n;
+
+ if(p == nil)
+ return nil;
+ if(p+BIT16SZ > ep)
+ return nil;
+ n = GBIT16(p);
+ p += BIT16SZ;
+ if(p+n > ep)
+ return nil;
+ *s = malloc(n+1);
+ memmove((*s), p, n);
+ (*s)[n] = '\0';
+ p += n;
+ return p;
+}
+
+static uchar*
+gcarray(uchar *p, uchar *ep, uchar **s, int *np)
+{
+ uint n;
+
+ if(p == nil)
+ return nil;
+ if(p+BIT16SZ > ep)
+ return nil;
+ n = GBIT16(p);
+ p += BIT16SZ;
+ if(p+n > ep)
+ return nil;
+ *s = malloc(n);
+ if(*s == nil)
+ return nil;
+ memmove((*s), p, n);
+ *np = n;
+ p += n;
+ return p;
+}
+
+static uchar*
+convM2AI(uchar *p, int n, AuthInfo **aip)
+{
+ uchar *e = p+n;
+ AuthInfo *ai;
+
+ ai = mallocz(sizeof(*ai), 1);
+ if(ai == nil)
+ return nil;
+
+ p = gstring(p, e, &ai->cuid);
+ p = gstring(p, e, &ai->suid);
+ p = gstring(p, e, &ai->cap);
+ p = gcarray(p, e, &ai->secret, &ai->nsecret);
+ if(p == nil)
+ auth_freeAI(ai);
+ else
+ *aip = ai;
+ return p;
+}
+
+static int
+dorpc(AuthRpc *rpc, char *verb, char *val, int len, AuthGetkey *getkey)
+{
+ int ret;
+
+ for(;;){
+ if((ret = auth_rpc(rpc, verb, val, len)) != ARneedkey && ret != ARbadkey)
+ return ret;
+ if(getkey == nil)
+ return ARgiveup; /* don't know how */
+ if((*getkey)(rpc->arg) < 0)
+ return ARgiveup; /* user punted */
+ }
+}
+
+static int
+doread(Session *s, Fid *f, void *buf, int n)
+{
+ s->f.fid = f - s->fids;
+ s->f.offset = 0;
+ s->f.count = n;
+ if(xmesg(s, Tread) < 0)
+ return -1;
+ n = s->f.count;
+ memmove(buf, s->f.data, n);
+ return n;
+}
+
+static int
+dowrite(Session *s, Fid *f, void *buf, int n)
+{
+ s->f.fid = f - s->fids;
+ s->f.offset = 0;
+ s->f.count = n;
+ s->f.data = (char *)buf;
+ if(xmesg(s, Twrite) < 0)
+ return -1;
+ return n;
+}
+
+/*
+ * this just proxies what the factotum tells it to.
+ */
+AuthInfo*
+authproto(Session *s, Fid *f, AuthRpc *rpc, AuthGetkey *getkey, char *params)
+{
+ char *buf;
+ int m, n, ret;
+ AuthInfo *a;
+ char oerr[ERRMAX];
+
+ rerrstr(oerr, sizeof oerr);
+ werrstr("UNKNOWN AUTH ERROR");
+
+ if(dorpc(rpc, "start", params, strlen(params), getkey) != ARok){
+ werrstr("fauth_proxy start: %r");
+ return nil;
+ }
+
+ buf = malloc(AuthRpcMax);
+ if(buf == nil)
+ return nil;
+ for(;;){
+ switch(dorpc(rpc, "read", nil, 0, getkey)){
+ case ARdone:
+ free(buf);
+ a = auth_getinfo(rpc);
+ errstr(oerr, sizeof oerr); /* no error, restore whatever was there */
+ return a;
+ case ARok:
+ if(dowrite(s, f, rpc->arg, rpc->narg) != rpc->narg){
+ werrstr("auth_proxy write fd: %r");
+ goto Error;
+ }
+ break;
+ case ARphase:
+ n = 0;
+ memset(buf, 0, AuthRpcMax);
+ while((ret = dorpc(rpc, "write", buf, n, getkey)) == ARtoosmall){
+ if(atoi(rpc->arg) > AuthRpcMax)
+ break;
+ m = doread(s, f, buf+n, atoi(rpc->arg)-n);
+ if(m <= 0){
+ if(m == 0)
+ werrstr("auth_proxy short read: %s", buf);
+ goto Error;
+ }
+ n += m;
+ }
+ if(ret != ARok){
+ werrstr("auth_proxy rpc write: %s: %r", buf);
+ goto Error;
+ }
+ break;
+ default:
+ werrstr("auth_proxy rpc: %r");
+ goto Error;
+ }
+ }
+Error:
+ free(buf);
+ return nil;
+}
+
+/* returns 0 if auth succeeded (or unneeded), -1 otherwise */
+int
+authhostowner(Session *s)
+{
+ Fid *af, *f;
+ int rv = -1;
+ int afd;
+ AuthInfo *ai;
+ AuthRpc *rpc;
+
+ /* get a fid to authenticate over */
+ f = nil;
+ af = newfid(s);
+ s->f.afid = af - s->fids;
+ s->f.uname = getuser();
+ s->f.aname = s->spec;
+ if(xmesg(s, Tauth)){
+ /* not needed */
+ rv = 0;
+ goto out;
+ }
+
+ quotefmtinstall(); /* just in case */
+
+ afd = open("/mnt/factotum/rpc", ORDWR);
+ if(afd < 0){
+ werrstr("opening /mnt/factotum/rpc: %r");
+ goto out;
+ }
+
+ rpc = auth_allocrpc(afd);
+ if(rpc == nil)
+ goto out;
+
+ ai = authproto(s, af, rpc, auth_getkey, "proto=p9any role=client");
+ if(ai != nil){
+ rv = 0;
+ auth_freeAI(ai);
+ }
+ auth_freerpc(rpc);
+ close(afd);
+
+ /* try attaching with the afid */
+ chat("attaching as hostowner...");
+ f = newfid(s);
+ s->f.fid = f - s->fids;
+ s->f.afid = af - s->fids;;
+ s->f.uname = getuser();
+ s->f.aname = s->spec;
+ if(xmesg(s, Tattach) == 0)
+ rv = 0;
+out:
+ if(af != nil){
+ putfid(s, af);
+ s->f.fid = af - s->fids;
+ xmesg(s, Tclunk);
+ }
+ if(f != nil){
+ putfid(s, f);
+ s->f.fid = f - s->fids;
+ xmesg(s, Tclunk);
+ }
+
+ return rv;
+}
+
diff --git a/sys/src/cmd/9nfs/chat.c b/sys/src/cmd/9nfs/chat.c
new file mode 100755
index 000000000..79b5315b1
--- /dev/null
+++ b/sys/src/cmd/9nfs/chat.c
@@ -0,0 +1,131 @@
+#include "all.h"
+
+#define SIZE 1024
+
+int chatty;
+int conftime;
+
+#define NSIZE 128
+
+static char nbuf[NSIZE];
+static int chatpid;
+
+static void
+killchat(void)
+{
+ char buf[NSIZE];
+ int fd;
+
+ remove(nbuf);
+ snprint(buf, sizeof buf, "/proc/%d/note", chatpid);
+ fd = open(buf, OWRITE);
+ write(fd, "kill\n", 5);
+ close(fd);
+}
+
+void
+chatsrv(char *name)
+{
+ int n, sfd, pfd[2];
+ char *p, buf[256];
+
+ if(name && *name)
+ snprint(nbuf, sizeof nbuf, "/srv/%s", name);
+ else{
+ if(p = strrchr(argv0, '/')) /* assign = */
+ name = p+1;
+ else
+ name = argv0;
+ snprint(nbuf, sizeof nbuf, "/srv/%s.chat", name);
+ }
+ remove(nbuf);
+ if(pipe(pfd) < 0)
+ panic("chatsrv pipe");
+ sfd = create(nbuf, OWRITE, 0600);
+ if(sfd < 0)
+ panic("chatsrv create %s", nbuf);
+ chatpid = rfork(RFPROC|RFMEM);
+ switch(chatpid){
+ case -1:
+ panic("chatsrv fork");
+ case 0:
+ break;
+ default:
+ atexit(killchat);
+ return;
+ }
+ fprint(sfd, "%d", pfd[1]);
+ close(sfd);
+ close(pfd[1]);
+ for(;;){
+ n = read(pfd[0], buf, sizeof(buf)-1);
+ if(n < 0)
+ break;
+ if(n == 0)
+ continue;
+ buf[n] = 0;
+ if(buf[0] == 'c')
+ conftime = 999;
+ chatty = strtol(buf, 0, 0);
+ if(abs(chatty) < 2)
+ rpcdebug = 0;
+ else
+ rpcdebug = abs(chatty) - 1;
+ fprint(2, "%s: chatty=%d, rpcdebug=%d, conftime=%d\n",
+ nbuf, chatty, rpcdebug, conftime);
+ }
+ _exits(0);
+}
+
+void
+chat(char *fmt, ...)
+{
+ char buf[SIZE];
+ va_list arg;
+ Fmt f;
+
+ if(!chatty)
+ return;
+
+ fmtfdinit(&f, 2, buf, sizeof buf);
+ va_start(arg, fmt);
+ fmtvprint(&f, fmt, arg);
+ va_end(arg);
+ fmtfdflush(&f);
+}
+
+void
+clog(char *fmt, ...)
+{
+ char buf[SIZE];
+ va_list arg;
+ int n;
+
+ va_start(arg, fmt);
+ vseprint(buf, buf+SIZE, fmt, arg);
+ va_end(arg);
+ n = strlen(buf);
+ if(chatty || rpcdebug)
+ write(2, buf, n);
+ if(chatty <= 0){
+ if(n>0 && buf[n-1] == '\n')
+ buf[n-1] = 0;
+ syslog(0, "nfs", buf);
+ }
+}
+
+void
+panic(char *fmt, ...)
+{
+ char buf[SIZE];
+ va_list arg;
+
+ va_start(arg, fmt);
+ vseprint(buf, buf+SIZE, fmt, arg);
+ va_end(arg);
+ if(chatty || rpcdebug)
+ fprint(2, "%s %d: %s: %r\n", argv0, getpid(), buf);
+ if(chatty <= 0)
+ syslog(0, "nfs", buf);
+ exits("panic");
+}
diff --git a/sys/src/cmd/9nfs/dat.h b/sys/src/cmd/9nfs/dat.h
new file mode 100755
index 000000000..c9f6a8664
--- /dev/null
+++ b/sys/src/cmd/9nfs/dat.h
@@ -0,0 +1,273 @@
+enum
+{
+ FHSIZE = 32
+};
+
+typedef struct Accept Accept;
+typedef struct Auth Auth;
+typedef struct Authunix Authunix;
+typedef struct Chalstuff Chalstuff;
+typedef uchar Fhandle[FHSIZE];
+typedef struct Fid Fid;
+typedef struct Procmap Procmap;
+typedef struct Progmap Progmap;
+typedef struct Reject Reject;
+typedef struct Rpccall Rpccall;
+typedef struct Rpccache Rpccache;
+typedef struct Sattr Sattr;
+typedef struct Session Session;
+typedef struct String String;
+typedef struct Strnode Strnode;
+typedef struct Unixid Unixid;
+typedef struct Unixidmap Unixidmap;
+typedef struct Unixmap Unixmap;
+typedef struct Unixscmap Unixscmap;
+typedef struct Xfid Xfid;
+typedef struct Xfile Xfile;
+
+struct String
+{
+ ulong n;
+ char * s;
+};
+
+struct Progmap
+{
+ int progno;
+ int vers;
+ void (*init)(int, char**);
+ Procmap *pmap;
+};
+
+struct Procmap
+{
+ int procno;
+ int (*procp)(int, Rpccall*, Rpccall*);
+};
+
+struct Auth
+{
+ ulong flavor;
+ ulong count;
+ void * data;
+};
+
+struct Authunix
+{
+ ulong stamp;
+ String mach;
+ ulong uid;
+ ulong gid;
+ int gidlen;
+ ulong gids[10];
+};
+
+struct Accept
+{
+ Auth averf;
+ ulong astat;
+ union{
+ void * results; /* SUCCESS */
+ struct{ /* PROG_MISMATCH */
+ ulong plow; /* acceptable version numbers */
+ ulong phigh;
+ };
+ };
+};
+
+struct Reject
+{
+ ulong rstat;
+ union{
+ struct{ /* RPC_MISMATCH */
+ ulong rlow; /* acceptable rpc version numbers */
+ ulong rhigh;
+ };
+ ulong authstat; /* AUTH_ERROR */
+ };
+};
+
+struct Rpccall
+{
+ /* corresponds to Udphdr */
+ uchar prefix0[12];
+ ulong host; /* ipv4 subset: prefixed to RPC message */
+ uchar prefix1[12];
+ ulong lhost; /* ipv4 subset: prefixed to RPC message */
+ /* ignore ifcaddr */
+ ulong port; /* prefixed to RPC message */
+ ulong lport; /* prefixed to RPC message */
+
+ ulong xid; /* transaction id */
+ ulong mtype; /* CALL or REPLY */
+ union{
+ struct{ /* CALL */
+ ulong rpcvers; /* must be equal to two (2) */
+ ulong prog; /* program number */
+ ulong vers; /* program version */
+ ulong proc; /* procedure number */
+ Auth cred; /* authentication credentials */
+ Auth verf; /* authentication verifier */
+ Unixidmap *up;
+ char * user;
+ void * args; /* procedure-specific */
+ };
+ struct{ /* REPLY */
+ ulong stat; /* MSG_ACCEPTED or MSG_DENIED */
+ union{
+ Accept;
+ Reject;
+ };
+ };
+ };
+};
+
+struct Rpccache
+{
+ Rpccache *prev;
+ Rpccache *next;
+ ulong host;
+ ulong port;
+ ulong xid;
+ int n;
+ uchar data[4];
+};
+
+struct Sattr
+{
+ ulong mode;
+ ulong uid;
+ ulong gid;
+ ulong size;
+ ulong atime; /* sec's */
+ ulong ausec; /* microsec's */
+ ulong mtime;
+ ulong musec;
+};
+
+struct Strnode
+{
+ Strnode *next; /* in hash bucket */
+ char str[4];
+};
+
+struct Unixid
+{
+ Unixid *next;
+ char * name;
+ int id;
+};
+
+struct Unixmap
+{
+ char * file;
+ int style;
+ long timestamp;
+ Unixid *ids;
+};
+
+struct Unixidmap
+{
+ Unixidmap *next;
+ int flag;
+ char * server;
+ char * client;
+ Reprog *sexp;
+ Reprog *cexp;
+ Unixmap u;
+ Unixmap g;
+};
+
+struct Unixscmap
+{
+ Unixscmap *next;
+ char * server;
+ ulong clientip;
+ Unixidmap *map;
+};
+
+struct Xfile
+{
+ Xfile * next; /* hash chain */
+ Session *s;
+ Qid qid; /* from stat */
+ Xfile * parent;
+ Xfile * child; /* if directory */
+ Xfile * sib; /* siblings */
+ char * name; /* path element */
+ Xfid * users;
+};
+
+enum
+{
+ Oread = 1,
+ Owrite = 2,
+ Open = 3,
+ Trunc = 4
+};
+
+struct Xfid
+{
+ Xfid * next; /* Xfile's user list */
+ Xfile * xp;
+ char * uid;
+ Fid * urfid;
+ Fid * opfid;
+ ulong mode; /* open mode, if opfid is non-zero */
+ ulong offset;
+};
+
+struct Fid
+{
+ Fid ** owner; /* null for root fids */
+ Fid * prev;
+ Fid * next;
+ long tstale; /* auto-clunk */
+};
+
+enum
+{
+ Maxfdata = 8192,
+ Maxstatdata = 2048,
+};
+
+struct Session
+{
+ Session *next;
+ char * service; /* for dial */
+ int fd;
+#define CHALLEN 1
+ char cchal[CHALLEN]; /* client challenge */
+ char schal[CHALLEN]; /* server challenge */
+ char authid[ANAMELEN]; /* server encryption uid */
+ char authdom[DOMLEN]; /* server encryption domain */
+ char * spec; /* for attach */
+ Xfile * root; /* to answer mount rpc */
+ ushort tag;
+ Fcall f;
+ uchar data[IOHDRSZ+Maxfdata];
+ uchar statbuf[Maxstatdata];
+ Fid * free; /* available */
+ Fid list; /* active, most-recently-used order */
+ Fid fids[1000];
+ int noauth;
+};
+
+struct Chalstuff
+{
+ Chalstuff *next;
+ Xfid * xf;
+ long tstale;
+ Chalstate;
+};
+
+extern int rpcdebug;
+extern int p9debug;
+extern int chatty;
+extern void (*rpcalarm)(void);
+extern long starttime;
+extern long nfstime;
+extern char * config;
+extern int staletime;
+extern int messagesize;
+extern char * commonopts;
diff --git a/sys/src/cmd/9nfs/fns.h b/sys/src/cmd/9nfs/fns.h
new file mode 100755
index 000000000..784e18a27
--- /dev/null
+++ b/sys/src/cmd/9nfs/fns.h
@@ -0,0 +1,66 @@
+int argopt(int c);
+int auth2unix(Auth*, Authunix*);
+int authhostowner(Session*);
+int canlock(Lock*);
+void chat(char*, ...);
+void chatsrv(char*);
+int checkreply(Session*, char*);
+int checkunixmap(Unixmap*);
+void clog(char*, ...);
+int clunkfid(Session*, Fid*);
+int convM2sattr(void*, Sattr*);
+int dir2fattr(Unixidmap*, Dir*, void*);
+int error(Rpccall*, int);
+void fidtimer(Session*, long);
+int garbage(Rpccall*, char*);
+int getdom(ulong, char*, int);
+int getticket(Session*, char*);
+char* id2name(Unixid**, int);
+void idprint(int, Unixid*);
+void* listalloc(long, long);
+void lock(Lock*);
+void mnttimer(long);
+int name2id(Unixid**, char*);
+Fid* newfid(Session*);
+long niwrite(int, void*, long);
+Unixidmap* pair2idmap(char*, ulong);
+void panic(char*, ...);
+void putfid(Session*, Fid*);
+int readunixidmaps(char*);
+Unixid* readunixids(char*, int);
+Xfid* rpc2xfid(Rpccall*, Dir*);
+int rpcM2S(void*, Rpccall*, int);
+int rpcS2M(Rpccall*, int, void*);
+void rpcprint(int, Rpccall*);
+void server(int argc, char *argv[], int, Progmap*);
+void setfid(Session*, Fid*);
+Xfid* setuser(Xfile*, char*);
+void showauth(Auth*);
+void srvinit(int, char*, char*);
+char* strfind(char*);
+int string2S(void*, String*);
+int strparse(void*, int, char**);
+void strprint(int);
+char* strstore(char*);
+Waitmsg *system(char*, char**);
+Waitmsg *systeml(char*, ...);
+void unlock(Lock*);
+int xfattach(Session*, char*, int);
+Xfid* xfauth(Xfile*, String*);
+void xfauthclose(Xfid*);
+long xfauthread(Xfid*, long, uchar*, long);
+int xfauthremove(Xfid*, char*);
+long xfauthwrite(Xfid*, long, uchar*, long);
+void xfclear(Xfid*);
+void xfclose(Xfid*);
+Xfid* xfid(char*, Xfile*, int);
+Xfile* xfile(Qid*, void*, int);
+int xfopen(Xfid*, int);
+int xfpurgeuid(Session*, char*);
+Xfile* xfroot(char*, int);
+int xfstat(Xfid*, Dir*);
+Xfid* xfwalkcr(int, Xfid*, String*, long);
+int xfwstat(Xfid*, Dir*);
+int xmesg(Session*, int);
+int xp2fhandle(Xfile*, Fhandle);
+void xpclear(Xfile*);
diff --git a/sys/src/cmd/9nfs/listalloc.c b/sys/src/cmd/9nfs/listalloc.c
new file mode 100755
index 000000000..605b289bf
--- /dev/null
+++ b/sys/src/cmd/9nfs/listalloc.c
@@ -0,0 +1,19 @@
+#include <u.h>
+#include <libc.h>
+
+void* listalloc(long, long);
+
+void *
+listalloc(long n, long size)
+{
+ char *p, *base;
+
+ size = (size+sizeof(ulong)-1)/sizeof(ulong)*sizeof(ulong);
+ p = base = malloc(n*size);
+ while(--n > 0){
+ *(char**)p = p+size;
+ p += size;
+ }
+ *(char**)p = 0;
+ return base;
+}
diff --git a/sys/src/cmd/9nfs/mkfile b/sys/src/cmd/9nfs/mkfile
new file mode 100755
index 000000000..7a6fc720d
--- /dev/null
+++ b/sys/src/cmd/9nfs/mkfile
@@ -0,0 +1,52 @@
+</$objtype/mkfile
+CFLAGS=-FVw
+
+TARG=nfsserver\
+ portmapper\
+ pcnfsd\
+
+HFILES=rpc.h\
+ nfs.h\
+ dat.h\
+ fns.h\
+
+OFILES=chat.$O\
+ rpc.$O\
+ string.$O\
+ strparse.$O\
+ system.$O\
+ listalloc.$O\
+ server.$O\
+ unixnames.$O\
+
+NFSSERVER=9p.$O\
+ auth.$O\
+ authhostowner.$O\
+ nfs.$O\
+ nfsmount.$O\
+ xfile.$O
+
+BIN=/$objtype/bin/aux
+
+all:V:
+
+
+UPDATE=\
+ mkfile\
+ $HFILES\
+ ${OFILES:%.$O=%.c}\
+ ${NFSSERVER:%.$O=%.c}\
+ ${TARG:%=%.c}\
+
+</sys/src/cmd/mkmany
+
+$O.nfsserver: $NFSSERVER
+
+nametest: $O.nametest
+ cp $O.nametest $target
+
+mport: $O.mport
+ cp $O.mport $target
+
+clean nuke:V:
+ rm -f *.[$OS] y.tab.? y.debug y.output [$OS].* $TARG mport nametest pcnfsd
diff --git a/sys/src/cmd/9nfs/mport.c b/sys/src/cmd/9nfs/mport.c
new file mode 100755
index 000000000..553c2bede
--- /dev/null
+++ b/sys/src/cmd/9nfs/mport.c
@@ -0,0 +1,185 @@
+#include "all.h"
+
+typedef struct Rpcconn Rpcconn;
+
+struct Rpcconn
+{
+ int data;
+ int ctl;
+ Rpccall cmd;
+ Rpccall reply;
+ uchar rpcbuf[8192];
+ uchar argbuf[8192];
+};
+
+void putauth(char*, Auth*);
+int rpccall(Rpcconn*, int);
+
+int rpcdebug;
+
+Rpcconn r;
+char * mach;
+
+void
+main(int argc, char **argv)
+{
+ char addr[64], dir[64], name[128];
+ char buf[33], *p;
+ uchar *dataptr, *argptr;
+ int i, fd, n, remport;
+
+ ARGBEGIN{
+ case 'm':
+ mach = ARGF();
+ break;
+ case 'D':
+ ++rpcdebug;
+ break;
+ }ARGEND
+ if(argc != 1)
+ exits("usage");
+
+ snprint(addr, sizeof addr, "udp!%s!111", argv[0]);
+ r.data = dial(addr, 0, dir, &r.ctl);
+ if(r.data < 0){
+ fprint(2, "dial %s: %r\n", addr);
+ exits("dial");
+ }
+ if(rpcdebug)
+ fprint(2, "dial %s: dir=%s\n", addr, dir);
+ if(fprint(r.ctl, "headers") < 0){
+ fprint(2, "can't set header mode: %r\n");
+ exits("headers");
+ }
+ sprint(name, "%s/remote", dir);
+ fd = open(name, OREAD);
+ if(fd < 0){
+ fprint(2, "can't open %s: %r\n", name);
+ exits("remote");
+ }
+ n = read(fd, buf, sizeof buf-1);
+ if(n < 0){
+ fprint(2, "can't read %s: %r\n", name);
+ exits("remote");
+ }
+ close(fd);
+ buf[n] = 0;
+ p = buf;
+ r.cmd.host = 0;
+ for(i=0; i<4; i++, p++)
+ r.cmd.host = (r.cmd.host<<8)|strtol(p, &p, 10);
+ r.cmd.port = strtol(p, 0, 10);
+ fprint(2, "host=%ld.%ld.%ld.%ld, port=%lud\n",
+ (r.cmd.host>>24)&0xff, (r.cmd.host>>16)&0xff,
+ (r.cmd.host>>8)&0xff, r.cmd.host&0xff, r.cmd.port);
+ fprint(r.ctl, "disconnect");
+
+ r.cmd.xid = time(0);
+ r.cmd.mtype = CALL;
+ r.cmd.rpcvers = 2;
+ r.cmd.args = r.argbuf;
+ if(mach)
+ putauth(mach, &r.cmd.cred);
+
+ r.cmd.prog = 100000; /* portmapper */
+ r.cmd.vers = 2; /* vers */
+ r.cmd.proc = 3; /* getport */
+ dataptr = r.cmd.args;
+
+ PLONG(100005); /* mount */
+ PLONG(1); /* vers */
+ PLONG(IPPROTO_UDP);
+ PLONG(0);
+
+ i = rpccall(&r, dataptr-(uchar*)r.cmd.args);
+ if(i != 4)
+ exits("trouble");
+ argptr = r.reply.results;
+ remport = GLONG();
+ fprint(2, "remote port = %d\n", remport);
+
+ r.cmd.port = remport;
+ r.cmd.prog = 100005; /* mount */
+ r.cmd.vers = 1; /* vers */
+ r.cmd.proc = 0; /* null */
+ dataptr = r.cmd.args;
+
+ i = rpccall(&r, dataptr-(uchar*)r.cmd.args);
+ if(i != 0)
+ exits("trouble");
+ fprint(2, "OK ping mount\n");
+
+ r.cmd.prog = 100005; /* mount */
+ r.cmd.vers = 1; /* vers */
+ r.cmd.proc = 5; /* export */
+ dataptr = r.cmd.args;
+
+ i = rpccall(&r, dataptr-(uchar*)r.cmd.args);
+ fprint(2, "export: %d bytes returned\n", i);
+ argptr = r.reply.results;
+ while (GLONG() != 0) {
+ n = GLONG();
+ p = GPTR(n);
+ print("%.*s\n", utfnlen(p, n), p);
+ while (GLONG() != 0) {
+ n = GLONG();
+ p = GPTR(n);
+ print("\t%.*s\n", utfnlen(p, n), p);
+ }
+ }
+
+ exits(0);
+}
+
+void
+putauth(char *mach, Auth *a)
+{
+ uchar *dataptr;
+ long stamp = time(0);
+ int n = strlen(mach);
+
+ dataptr = realloc(a->data, 2*4+ROUNDUP(n)+4*4);
+ a->data = dataptr;
+ a->flavor = AUTH_UNIX;
+ PLONG(stamp);
+ PLONG(n);
+ PPTR(mach, n);
+ PLONG(0);
+ PLONG(1);
+ PLONG(1);
+ PLONG(0);
+ a->count = dataptr - (uchar*)a->data;
+}
+
+int
+rpccall(Rpcconn *r, int narg)
+{
+ int n;
+
+ r->cmd.xid++;
+ n = rpcS2M(&r->cmd, narg, r->rpcbuf);
+ if(rpcdebug)
+ rpcprint(2, &r->cmd);
+ if(write(r->data, r->rpcbuf, n) < 0){
+ fprint(2, "rpc write: %r\n");
+ exits("rpc");
+ }
+ n = read(r->data, r->rpcbuf, sizeof r->rpcbuf);
+ if(n < 0){
+ fprint(2, "rpc read: %r\n");
+ exits("rpc");
+ }
+ if(rpcM2S(r->rpcbuf, &r->reply, n) != 0){
+ fprint(2, "rpc reply format error\n");
+ exits("rpc");
+ }
+ if(rpcdebug)
+ rpcprint(2, &r->reply);
+ if(r->reply.mtype != REPLY || r->reply.stat != MSG_ACCEPTED ||
+ r->reply.astat != SUCCESS){
+ fprint(2, "rpc mtype, stat, astat = %ld, %ld, %ld\n",
+ r->reply.mtype, r->reply.stat, r->reply.astat);
+ exits("rpc");
+ }
+ return n - (((uchar *)r->reply.results) - r->rpcbuf);
+}
diff --git a/sys/src/cmd/9nfs/nametest.c b/sys/src/cmd/9nfs/nametest.c
new file mode 100755
index 000000000..cb605720c
--- /dev/null
+++ b/sys/src/cmd/9nfs/nametest.c
@@ -0,0 +1,99 @@
+#include "all.h"
+
+void mapinit(char*, char*);
+
+int debug;
+int rpcdebug;
+int style = 'u';
+Biobuf *in;
+Unixid *ids;
+Unixid **pids;
+Unixidmap *mp;
+
+void
+main(int argc, char **argv)
+{
+ int id, arc; char *arv[4];
+ char *l, *name;
+
+ chatty = 1;
+ ARGBEGIN{
+ case '9':
+ case 'u':
+ style = ARGC();
+ break;
+ case 'D':
+ ++debug;
+ break;
+ }ARGEND
+ if(argc <= 0){
+ ids = readunixids("/fd/0", style);
+ if(ids)
+ idprint(1, ids);
+ exits(ids ? 0 : "readunixids");
+ }
+ mapinit(argv[0], 0);
+ in = Bopen("/fd/0", OREAD);
+ while(l = Brdline(in, '\n')){ /* assign = */
+ l[Blinelen(in)-1] = 0;
+ arc = strparse(l, nelem(arv), arv);
+ if(arc <= 0)
+ continue;
+ switch(arv[0][0]){
+ case 'r':
+ if(arc < 2)
+ continue;
+ mapinit(arv[1], arv[2]);
+ break;
+ case 'i':
+ if(arc < 2)
+ continue;
+ id = strtol(arv[1], 0, 10);
+ name = id2name(pids, id);
+ print("%d -> %s\n", id, name);
+ break;
+ case 'n':
+ if(arc < 2)
+ continue;
+ name = arv[1];
+ id = name2id(pids, name);
+ print("%s -> %d\n", name, id);
+ break;
+ case 'p':
+ print("server=%s, client=%s\n", mp->server, mp->client);
+ break;
+ case 'P':
+ idprint(1, *pids);
+ break;
+ case 'u':
+ pids = &mp->u.ids;
+ print("users...\n");
+ break;
+ case 'g':
+ pids = &mp->g.ids;
+ print("groups...\n");
+ break;
+ }
+ }
+ exits(0);
+}
+
+void
+mapinit(char *file, char *client)
+{
+ if(file){
+ print("reading %s...\n", file);
+ if(readunixidmaps(file) < 0)
+ exits("readunixidmaps");
+ if(!client)
+ client = "nslocum.research.bell-labs.com";
+ }
+ print("client = %s...\n", client);
+ mp = pair2idmap("bootes", client, 0);
+ if(mp == 0){
+ fprint(2, "%s: pair2idmap failed\n", argv0);
+ exits("pair2idmap");
+ }
+ pids = &mp->u.ids;
+ print("[users...]\n");
+}
diff --git a/sys/src/cmd/9nfs/nfs.c b/sys/src/cmd/9nfs/nfs.c
new file mode 100755
index 000000000..c01536742
--- /dev/null
+++ b/sys/src/cmd/9nfs/nfs.c
@@ -0,0 +1,471 @@
+#include "all.h"
+
+extern uchar buf[];
+
+Xfid *
+rpc2xfid(Rpccall *cmd, Dir *dp)
+{
+ char *argptr = cmd->args;
+ Xfile *xp;
+ Xfid *xf;
+ Session *s;
+ char *service;
+ Authunix au;
+ Qid qid;
+ char client[256], *user;
+ Unixidmap *m;
+ int i;
+ uvlong x1, x2;
+
+ chat("rpc2xfid %.8lux %.8lux %p %p\n", *((ulong*)argptr), *((ulong*)argptr+1), buf, argptr);
+ if(argptr[0] == 0 && argptr[1] == 0){ /* root */
+ chat("root...");
+ xp = xfroot(&argptr[2], 0);
+ s = xp ? xp->s : 0;
+ }else{
+ ulong ul;
+ chat("noroot %.8lux...", *((ulong*)argptr));
+ if((ul=GLONG()) != starttime){
+ chat("bad tag %lux %lux...", ul, starttime);
+ return 0;
+ }
+ s = (Session *)GLONG();
+ x1 = GLONG();
+ x2 = GLONG();
+ qid.path = x1 | (x2<<32);
+ qid.vers = 0;
+ qid.type = GBYTE();
+ xp = xfile(&qid, s, 0);
+ }
+ if(xp == 0){
+ chat("no xfile...");
+ return 0;
+ }
+ if(auth2unix(&cmd->cred, &au) != 0){
+ chat("auth flavor=%ld, count=%ld\n",
+ cmd->cred.flavor, cmd->cred.count);
+ for(i=0; i<cmd->cred.count; i++)
+ chat(" %.2ux", ((uchar *)cmd->cred.data)[i]);
+ chat("...");
+ return 0;
+ }else{
+/* chat("auth: %d %.*s u=%d g=%d",
+ * au.stamp, utfnlen(au.mach.s, au.mach.n), au.mach.s, au.uid, au.gid);
+ * for(i=0; i<au.gidlen; i++)
+ * chat(", %d", au.gids[i]);
+ * chat("...");
+ */
+ char *p = memchr(au.mach.s, '.', au.mach.n);
+ chat("%ld@%.*s...", au.uid, utfnlen(au.mach.s, (p ? p-au.mach.s : au.mach.n)), au.mach.s);
+ }
+ if(au.mach.n >= sizeof client){
+ chat("client name too long...");
+ return 0;
+ }
+ memcpy(client, au.mach.s, au.mach.n);
+ client[au.mach.n] = 0;
+ service = xp->parent->s->service;
+ cmd->up = m = pair2idmap(service, cmd->host);
+ if(m == 0){
+ chat("no map for pair (%s,%s)...", service, client);
+ /*chat("getdom %d.%d.%d.%d", cmd->host&0xFF, (cmd->host>>8)&0xFF,
+ (cmd->host>>16)&0xFF, (cmd->host>>24)&0xFF);/**/
+ /*if(getdom(cmd->host, client, sizeof(client))<0)
+ return 0;/**/
+ return 0;
+ }
+ /*chat("map=(%s,%s)...", m->server, m->client);/**/
+ cmd->user = user = id2name(&m->u.ids, au.uid);
+ if(user == 0){
+ chat("no user for id %ld...", au.uid);
+ return 0;
+ }
+ chat("user=%s...", user);/**/
+ xf = 0;
+ if(s == xp->parent->s){
+ if(!s->noauth)
+ xf = setuser(xp, user);
+ if(xf == 0)
+ xf = setuser(xp, "none");
+ if(xf == 0)
+ chat("can't set user none...");
+ }else
+ xf = xp->users;
+ if(xf)
+ chat("uid=%s...", xf->uid);
+ if(xf && dp && xfstat(xf, dp) < 0){
+ chat("can't stat %s...", xp->name);
+ return 0;
+ }
+ return xf;
+}
+
+Xfid *
+setuser(Xfile *xp, char *user)
+{
+ Xfid *xf, *xpf;
+ Session *s;
+
+ xf = xfid(user, xp, 1);
+ if(xf->urfid)
+ return xf;
+ if(xp->parent==xp || !(xpf = setuser(xp->parent, user))) /* assign = */
+ return xfid(user, xp, -1);
+ s = xp->s;
+ xf->urfid = newfid(s);
+ xf->urfid->owner = &xf->urfid;
+ setfid(s, xpf->urfid);
+ s->f.newfid = xf->urfid - s->fids;
+ s->f.nwname = 1;
+ s->f.wname[0] = xp->name;
+ if(xmesg(s, Twalk) || s->f.nwqid != 1)
+ return xfid(user, xp, -1);
+ return xf;
+}
+
+int
+xfstat(Xfid *xf, Dir *dp)
+{
+ Xfile *xp;
+ Session *s;
+ char buf[128];
+
+ xp = xf->xp;
+ s = xp->s;
+ if(s != xp->parent->s){
+ seprint(buf, buf+sizeof buf, "#%s", xf->uid);
+ dp->name = strstore(buf);
+ dp->uid = xf->uid;
+ dp->gid = xf->uid;
+ dp->muid = xf->uid;
+ dp->qid.path = (uvlong)xf->uid;
+ dp->qid.type = QTFILE;
+ dp->qid.vers = 0;
+ dp->mode = 0666;
+ dp->atime = time(0);
+ dp->mtime = dp->atime;
+ dp->length = NETCHLEN;
+ dp->type = 0;
+ dp->type = 0;
+ return 0;
+ }
+ setfid(s, xf->urfid);
+ if(xmesg(s, Tstat) == 0){
+ convM2D(s->f.stat, s->f.nstat, dp, (char*)s->statbuf);
+ if(xp->qid.path == dp->qid.path){
+ xp->name = strstore(dp->name);
+ return 0;
+ }
+ /* not reached ? */
+ chat("xp->qid.path=0x%.16llux, dp->qid.path=0x%.16llux name=%s...",
+ xp->qid.path, dp->qid.path, dp->name);
+ }
+ if(xp != xp->parent)
+ xpclear(xp);
+ else
+ clog("can't stat root: %s",
+ s->f.type == Rerror ? s->f.ename : "??");
+ return -1;
+}
+
+int
+xfwstat(Xfid *xf, Dir *dp)
+{
+ Xfile *xp;
+ Session *s;
+
+ xp = xf->xp;
+ s = xp->s;
+
+ /*
+ * xf->urfid can be zero because some DOS NFS clients
+ * try to do wstat on the #user authentication files on close.
+ */
+ if(s == 0 || xf->urfid == 0)
+ return -1;
+ setfid(s, xf->urfid);
+ s->f.stat = s->statbuf;
+ convD2M(dp, s->f.stat, Maxstatdata);
+ if(xmesg(s, Twstat))
+ return -1;
+ xp->name = strstore(dp->name);
+ return 0;
+}
+
+int
+xfopen(Xfid *xf, int flag)
+{
+ static int modes[] = {
+ [Oread] OREAD, [Owrite] OWRITE, [Oread|Owrite] ORDWR,
+ };
+ Xfile *xp;
+ Session *s;
+ Fid *opfid;
+ int omode;
+
+ if(xf->opfid && (xf->mode & flag & Open) == flag)
+ return 0;
+ omode = modes[(xf->mode|flag) & Open];
+ if(flag & Trunc)
+ omode |= OTRUNC;
+ xp = xf->xp;
+ chat("open(\"%s\", %d)...", xp->name, omode);
+ s = xp->s;
+ opfid = newfid(s);
+ setfid(s, xf->urfid);
+ s->f.newfid = opfid - s->fids;
+ s->f.nwname = 0;
+ if(xmesg(s, Twalk)){
+ putfid(s, opfid);
+ return -1;
+ }
+ setfid(s, opfid);
+ s->f.mode = omode;
+ if(xmesg(s, Topen)){
+ clunkfid(s, opfid);
+ return -1;
+ }
+ if(xf->opfid)
+ clunkfid(s, xf->opfid);
+ xf->mode |= flag & Open;
+ xf->opfid = opfid;
+ opfid->owner = &xf->opfid;
+ xf->offset = 0;
+ return 0;
+}
+
+void
+xfclose(Xfid *xf)
+{
+ Xfile *xp;
+
+ if(xf->mode & Open){
+ xp = xf->xp;
+ chat("close(\"%s\")...", xp->name);
+ if(xf->opfid)
+ clunkfid(xp->s, xf->opfid);
+ xf->mode &= ~Open;
+ xf->opfid = 0;
+ }
+}
+
+void
+xfclear(Xfid *xf)
+{
+ Xfile *xp = xf->xp;
+
+ if(xf->opfid){
+ clunkfid(xp->s, xf->opfid);
+ xf->opfid = 0;
+ }
+ if(xf->urfid){
+ clunkfid(xp->s, xf->urfid);
+ xf->urfid = 0;
+ }
+ xfid(xf->uid, xp, -1);
+}
+
+Xfid *
+xfwalkcr(int type, Xfid *xf, String *elem, long perm)
+{
+ Session *s;
+ Xfile *xp, *newxp;
+ Xfid *newxf;
+ Fid *nfid;
+
+ chat("xf%s(\"%s\")...", type==Tcreate ? "create" : "walk", elem->s);
+ xp = xf->xp;
+ s = xp->s;
+ nfid = newfid(s);
+ setfid(s, xf->urfid);
+ s->f.newfid = nfid - s->fids;
+ if(type == Tcreate){
+ s->f.nwname = 0;
+ if(xmesg(s, Twalk)){
+ putfid(s, nfid);
+ return 0;
+ }
+ s->f.fid = nfid - s->fids;
+ }
+ if(type == Tcreate){
+ s->f.name = elem->s;
+ s->f.perm = perm;
+ s->f.mode = (perm&DMDIR) ? OREAD : ORDWR;
+ if(xmesg(s, type)){
+ clunkfid(s, nfid);
+ return 0;
+ }
+ }else{ /* Twalk */
+ s->f.nwname = 1;
+ s->f.wname[0] = elem->s;
+ if(xmesg(s, type) || s->f.nwqid!=1){
+ putfid(s, nfid);
+ return 0;
+ }
+ s->f.qid = s->f.wqid[0]; /* only one element */
+ }
+ chat("fid=%d,qid=0x%llux,%ld,%.2ux...", s->f.fid, s->f.qid.path, s->f.qid.vers, s->f.qid.type);
+ newxp = xfile(&s->f.qid, s, 1);
+ if(newxp->parent == 0){
+ chat("new xfile...");
+ newxp->parent = xp;
+ newxp->sib = xp->child;
+ xp->child = newxp;
+ }
+ newxf = xfid(xf->uid, newxp, 1);
+ if(type == Tcreate){
+ newxf->mode = (perm&DMDIR) ? Oread : (Oread|Owrite);
+ newxf->opfid = nfid;
+ nfid->owner = &newxf->opfid;
+ nfid = newfid(s);
+ setfid(s, xf->urfid);
+ s->f.newfid = nfid - s->fids;
+ s->f.nwname = 1;
+ s->f.wname[0] = elem->s;
+ if(xmesg(s, Twalk) || s->f.nwqid!=1){
+ putfid(s, nfid);
+ xpclear(newxp);
+ return 0;
+ }
+ newxf->urfid = nfid;
+ nfid->owner = &newxf->urfid;
+ }else if(newxf->urfid){
+ chat("old xfid %ld...", newxf->urfid-s->fids);
+ clunkfid(s, nfid);
+ }else{
+ newxf->urfid = nfid;
+ nfid->owner = &newxf->urfid;
+ }
+ newxp->name = strstore(elem->s);
+ return newxf;
+}
+
+void
+xpclear(Xfile *xp)
+{
+ Session *s;
+ Xfid *xf;
+ Xfile *xnp;
+
+ s = xp->s;
+ while(xf = xp->users) /* assign = */
+ xfclear(xf);
+ while(xnp = xp->child){ /* assign = */
+ xp->child = xnp->sib;
+ xnp->parent = 0;
+ xpclear(xnp);
+ xfile(&xnp->qid, s, -1);
+ }
+ if(xnp = xp->parent){ /* assign = */
+ if(xnp->child == xp)
+ xnp->child = xp->sib;
+ else{
+ xnp = xnp->child;
+ while(xnp->sib != xp)
+ xnp = xnp->sib;
+ xnp->sib = xp->sib;
+ }
+ xfile(&xp->qid, s, -1);
+ }
+}
+
+int
+xp2fhandle(Xfile *xp, Fhandle fh)
+{
+ uchar *dataptr = fh;
+ ulong x;
+ int n;
+
+ memset(fh, 0, FHSIZE);
+ if(xp == xp->parent){ /* root */
+ dataptr[0] = 0;
+ dataptr[1] = 0;
+ n = strlen(xp->s->service);
+ if(n > FHSIZE-3)
+ n = FHSIZE-3;
+ memmove(&dataptr[2], xp->s->service, n);
+ dataptr[2+n] = 0;
+ }else{
+ PLONG(starttime);
+ PLONG((u32int)(uintptr)xp->s);
+ x = xp->qid.path;
+ PLONG(x);
+ x = xp->qid.path>>32;
+ PLONG(x);
+ PBYTE(xp->qid.type);
+ USED(dataptr);
+ }
+ return FHSIZE;
+}
+
+int
+dir2fattr(Unixidmap *up, Dir *dp, void *mp)
+{
+ uchar *dataptr = mp;
+ long length;
+ int r;
+
+ r = dp->mode & 0777;
+ if (dp->mode & DMDIR)
+ length = 1024;
+ else
+ length = dp->length;
+ if((dp->mode & DMDIR) && dp->type == '/' && dp->dev == 0)
+ r |= 0555;
+ if(dp->mode & DMDIR){
+ PLONG(NFDIR); /* type */
+ r |= S_IFDIR;
+ PLONG(r); /* mode */
+ PLONG(3); /* nlink */
+ }else{
+ PLONG(NFREG); /* type */
+ r |= S_IFREG;
+ PLONG(r); /* mode */
+ PLONG(1); /* nlink */
+ }
+ r = name2id(&up->u.ids, dp->uid);
+ if(r < 0){
+ r = name2id(&up->u.ids, "daemon");
+ if(r < 0)
+ r = 1;
+ }
+ PLONG(r); /* uid */
+ r = name2id(&up->g.ids, dp->gid);
+ if(r < 0){
+ r = name2id(&up->g.ids, "user");
+ if(r < 0)
+ r = 1;
+ }
+ PLONG(r); /* gid */
+ PLONG(length); /* size */
+ PLONG(2048); /* blocksize */
+ PLONG(0); /* rdev */
+ r = (length+2047)/2048;
+ PLONG(r); /* blocks */
+ r = (dp->type<<16) | dp->dev;
+ PLONG(r); /* fsid */
+ PLONG(dp->qid.path); /* fileid */
+ PLONG(dp->atime); /* atime */
+ PLONG(0);
+ PLONG(dp->mtime); /* mtime */
+ PLONG(0);
+ PLONG(dp->mtime); /* ctime */
+ PLONG(0);
+ return dataptr - (uchar *)mp;
+}
+
+int
+convM2sattr(void *mp, Sattr *sp)
+{
+ uchar *argptr = mp;
+
+ sp->mode = GLONG();
+ sp->uid = GLONG();
+ sp->gid = GLONG();
+ sp->size = GLONG();
+ sp->atime = GLONG();
+ sp->ausec = GLONG();
+ sp->mtime = GLONG();
+ sp->musec = GLONG();
+ return argptr - (uchar *)mp;
+}
diff --git a/sys/src/cmd/9nfs/nfs.h b/sys/src/cmd/9nfs/nfs.h
new file mode 100755
index 000000000..869906e4a
--- /dev/null
+++ b/sys/src/cmd/9nfs/nfs.h
@@ -0,0 +1,44 @@
+/*
+ * Cf. /lib/rfc/rfc1094
+ */
+
+enum NFS_stat
+{
+ NFS_OK = 0,
+ NFSERR_PERM = 1,
+ NFSERR_NOENT = 2,
+ NFSERR_IO = 5,
+ NFSERR_NXIO = 6,
+ NFSERR_ACCES = 13,
+ NFSERR_EXIST = 17,
+ NFSERR_NODEV = 19,
+ NFSERR_NOTDIR = 20,
+ NFSERR_ISDIR = 21,
+ NFSERR_FBIG = 27,
+ NFSERR_NOSPC = 28,
+ NFSERR_ROFS = 30,
+ NFSERR_NAMETOOLONG = 63,
+ NFSERR_NOTEMPTY = 66,
+ NFSERR_DQUOT = 69,
+ NFSERR_STALE = 70,
+ NFSERR_WFLUSH = 99
+};
+
+enum NFS_ftype
+{
+ NFNON = 0,
+ NFREG = 1,
+ NFDIR = 2,
+ NFBLK = 3,
+ NFCHR = 4,
+ NFLNK = 5
+};
+
+enum NFS_mode
+{
+ S_IFMT = 0170000, /* mask */
+ S_IFDIR = 0040000, /* directory */
+ S_IFREG = 0100000 /* regular */
+};
+
+#define NOATTR 0xffffffff
diff --git a/sys/src/cmd/9nfs/nfsmount.c b/sys/src/cmd/9nfs/nfsmount.c
new file mode 100755
index 000000000..583669273
--- /dev/null
+++ b/sys/src/cmd/9nfs/nfsmount.c
@@ -0,0 +1,333 @@
+#include "all.h"
+
+/*
+ * Cf. /lib/rfc/rfc1094
+ */
+
+static int mntnull(int, Rpccall*, Rpccall*);
+static int mntmnt(int, Rpccall*, Rpccall*);
+static int mntdump(int, Rpccall*, Rpccall*);
+static int mntumnt(int, Rpccall*, Rpccall*);
+static int mntumntall(int, Rpccall*, Rpccall*);
+static int mntexport(int, Rpccall*, Rpccall*);
+
+Procmap mntproc[] = {
+ 0, mntnull,
+ 1, mntmnt,
+ 2, mntdump,
+ 3, mntumnt,
+ 4, mntumntall,
+ 5, mntexport,
+ 0, 0
+};
+
+long starttime;
+static int noauth;
+char * config;
+Session * head;
+Session * tail;
+int staletime = 10*60;
+
+void
+mnttimer(long now)
+{
+ Session *s;
+
+ for(s=head; s; s=s->next)
+ fidtimer(s, now);
+}
+
+static void
+usage(void)
+{
+ sysfatal("usage: %s %s [-ns] [-a dialstring] [-c uidmap] [-f srvfile] "
+ "[-T staletime]", argv0, commonopts);
+}
+
+void
+mntinit(int argc, char **argv)
+{
+ int tries;
+
+ config = "config";
+ starttime = time(0);
+ clog("nfs mount server init, starttime = %lud\n", starttime);
+ tries = 0;
+ ARGBEGIN{
+ case 'a':
+ ++tries;
+ srvinit(-1, 0, EARGF(usage()));
+ break;
+ case 'c':
+ config = EARGF(usage());
+ break;
+ case 'f':
+ ++tries;
+ srvinit(-1, EARGF(usage()), 0);
+ break;
+ case 'n':
+ ++noauth;
+ break;
+ case 's':
+ ++tries;
+ srvinit(1, 0, 0);
+ break;
+ case 'T':
+ staletime = atoi(EARGF(usage()));
+ break;
+ default:
+ if(argopt(ARGC()) < 0)
+ sysfatal("usage: %s %s [-ns] [-a dialstring] "
+ "[-c uidmap] [-f srvfile] [-T staletime]",
+ argv0, commonopts);
+ break;
+ }ARGEND
+noauth=1; /* ZZZ */
+ if(tries == 0 && head == 0)
+ srvinit(-1, 0, "tcp!fs");
+ if(head == 0)
+ panic("can't initialize services");
+ readunixidmaps(config);
+}
+
+void
+srvinit(int fd, char *file, char *addr)
+{
+ char fdservice[16], *naddr;
+ Session *s;
+ Xfile *xp;
+ Xfid *xf;
+ Fid *f;
+
+ s = calloc(1, sizeof(Session));
+ s->spec = "";
+ s->fd = -1;
+ if(fd >= 0){
+ s->fd = fd;
+ sprint(fdservice, "/fd/%d", s->fd);
+ s->service = strstore(fdservice);
+ chat("fd = %d\n", s->fd);
+ }else if(file){
+ chat("file = \"%s\"\n", file);
+ s->service = file;
+ s->fd = open(file, ORDWR);
+ if(s->fd < 0){
+ clog("can't open %s: %r\n", file);
+ goto error;
+ }
+ }else if(addr){
+ chat("addr = \"%s\"\n", addr);
+ naddr = netmkaddr(addr, 0, "9fs");
+ s->service = addr;
+ s->fd = dial(naddr, 0, 0, 0);
+ if(s->fd < 0){
+ clog("can't dial %s: %r\n", naddr);
+ goto error;
+ }
+ }
+
+ chat("version...");
+ s->tag = NOTAG-1;
+ s->f.msize = Maxfdata+IOHDRSZ;
+ s->f.version = "9P2000";
+ xmesg(s, Tversion);
+ messagesize = IOHDRSZ+s->f.msize;
+ chat("version spec %s size %d\n", s->f.version, s->f.msize);
+
+ s->tag = 0;
+
+ chat("authenticate...");
+ if(authhostowner(s) < 0){
+ clog("auth failed %r\n");
+ goto error;
+ }
+
+ chat("attach as none...");
+ f = newfid(s);
+ s->f.fid = f - s->fids;
+ s->f.afid = ~0x0UL;
+ s->f.uname = "none";
+ s->f.aname = s->spec;
+ if(xmesg(s, Tattach)){
+ clog("attach failed\n");
+ goto error;
+ }
+
+ xp = xfile(&s->f.qid, s, 1);
+ s->root = xp;
+ xp->parent = xp;
+ xp->name = "/";
+ xf = xfid("none", xp, 1);
+ xf->urfid = f;
+ clog("service=%s uid=%s fid=%ld\n",
+ s->service, xf->uid, xf->urfid - s->fids);
+ if(tail)
+ tail->next = s;
+ else
+ head = s;
+ tail = s;
+ return;
+
+error:
+ if(s->fd >= 0)
+ close(s->fd);
+ free(s);
+}
+
+static int
+mntnull(int n, Rpccall *cmd, Rpccall *reply)
+{
+ USED(n, cmd, reply);
+ chat("mntnull\n");
+ return 0;
+}
+
+static char*
+Str2str(String s, char *buf, int nbuf)
+{
+ int i;
+ i = s.n;
+ if(i >= nbuf)
+ i = nbuf-1;
+ memmove(buf, s.s, i);
+ buf[i] = 0;
+ return buf;
+}
+
+static int
+mntmnt(int n, Rpccall *cmd, Rpccall *reply)
+{
+ int i;
+ char dom[64];
+ uchar *argptr = cmd->args;
+ uchar *dataptr = reply->results;
+ Authunix au;
+ Xfile *xp;
+ String root;
+
+ chat("mntmnt...\n");
+ if(n < 8)
+ return garbage(reply, "n too small");
+ argptr += string2S(argptr, &root);
+ if(argptr != &((uchar *)cmd->args)[n])
+ return garbage(reply, "bad count");
+ clog("host=%I, port=%ld, root=\"%.*s\"...",
+ cmd->host, cmd->port, utfnlen(root.s, root.n), root.s);
+ if(auth2unix(&cmd->cred, &au) != 0){
+ chat("auth flavor=%ld, count=%ld\n",
+ cmd->cred.flavor, cmd->cred.count);
+ for(i=0; i<cmd->cred.count; i++)
+ chat(" %.2ux", ((uchar *)cmd->cred.data)[i]);
+ chat("\n");
+ clog("auth: bad credentials");
+ return error(reply, 1);
+ }
+ clog("auth: %ld %.*s u=%ld g=%ld",
+ au.stamp, utfnlen(au.mach.s, au.mach.n), au.mach.s, au.uid, au.gid);
+ for(i=0; i<au.gidlen; i++)
+ chat(", %ld", au.gids[i]);
+ chat("...");
+ if(getdom(cmd->host, dom, sizeof(dom))<0){
+ clog("auth: unknown ip address");
+ return error(reply, 1);
+ }
+ chat("dom=%s...", dom);
+ xp = xfroot(root.s, root.n);
+ if(xp == 0){
+ chat("xp=0...");
+ clog("mntmnt: no fs");
+ return error(reply, 3);
+ }
+
+ PLONG(0);
+ dataptr += xp2fhandle(xp, dataptr);
+ chat("OK\n");
+ return dataptr - (uchar *)reply->results;
+}
+
+static int
+mntdump(int n, Rpccall *cmd, Rpccall *reply)
+{
+ if(n != 0)
+ return garbage(reply, "mntdump");
+ USED(cmd);
+ chat("mntdump...");
+ return error(reply, FALSE);
+}
+
+static int
+mntumnt(int n, Rpccall *cmd, Rpccall *reply)
+{
+ if(n <= 0)
+ return garbage(reply, "mntumnt");
+ USED(cmd);
+ chat("mntumnt\n");
+ return 0;
+}
+
+static int
+mntumntall(int n, Rpccall *cmd, Rpccall *reply)
+{
+ if(n != 0)
+ return garbage(reply, "mntumntall");
+ USED(cmd);
+ chat("mntumntall\n");
+ return 0;
+}
+
+static int
+mntexport(int n, Rpccall *cmd, Rpccall *reply)
+{
+ uchar *dataptr = reply->results;
+ Authunix au;
+ int i;
+
+ chat("mntexport...");
+ if(n != 0)
+ return garbage(reply, "mntexport");
+ if(auth2unix(&cmd->cred, &au) != 0){
+ chat("auth flavor=%ld, count=%ld\n",
+ cmd->cred.flavor, cmd->cred.count);
+ for(i=0; i<cmd->cred.count; i++)
+ chat(" %.2ux", ((uchar *)cmd->cred.data)[i]);
+ chat("...");
+ au.mach.n = 0;
+ }else
+ chat("%ld@%.*s...", au.uid, utfnlen(au.mach.s, au.mach.n), au.mach.s);
+ PLONG(TRUE);
+ PLONG(1);
+ PPTR("/", 1);
+ if(au.mach.n > 0){
+ PLONG(TRUE);
+ PLONG(au.mach.n);
+ PPTR(au.mach.s, au.mach.n);
+ }
+ PLONG(FALSE);
+ PLONG(FALSE);
+ chat("OK\n");
+ return dataptr - (uchar *)reply->results;
+}
+
+Xfile *
+xfroot(char *name, int n)
+{
+ Session *s;
+ char *p;
+
+ if(n <= 0)
+ n = strlen(name);
+ chat("xfroot: %.*s...", utfnlen(name, n), name);
+ if(n == 1 && name[0] == '/')
+ return head->root;
+ for(s=head; s; s=s->next){
+ if(strncmp(name, s->service, n) == 0)
+ return s->root;
+ p = strrchr(s->service, '!'); /* for -a tcp!foo */
+ if(p && strncmp(name, p+1, n) == 0)
+ return s->root;
+ p = strrchr(s->service, '/'); /* for -f /srv/foo */
+ if(p && strncmp(name, p+1, n) == 0)
+ return s->root;
+ }
+ return 0;
+}
diff --git a/sys/src/cmd/9nfs/nfsserver.c b/sys/src/cmd/9nfs/nfsserver.c
new file mode 100755
index 000000000..6951dfeb1
--- /dev/null
+++ b/sys/src/cmd/9nfs/nfsserver.c
@@ -0,0 +1,655 @@
+#include "all.h"
+
+/*
+ * Cf. /lib/rfc/rfc1094
+ */
+
+static int nfsnull(int, Rpccall*, Rpccall*);
+static int nfsgetattr(int, Rpccall*, Rpccall*);
+static int nfssetattr(int, Rpccall*, Rpccall*);
+static int nfsroot(int, Rpccall*, Rpccall*);
+static int nfslookup(int, Rpccall*, Rpccall*);
+static int nfsreadlink(int, Rpccall*, Rpccall*);
+static int nfsread(int, Rpccall*, Rpccall*);
+static int nfswritecache(int, Rpccall*, Rpccall*);
+static int nfswrite(int, Rpccall*, Rpccall*);
+static int nfscreate(int, Rpccall*, Rpccall*);
+static int nfsremove(int, Rpccall*, Rpccall*);
+static int nfsrename(int, Rpccall*, Rpccall*);
+static int nfslink(int, Rpccall*, Rpccall*);
+static int nfssymlink(int, Rpccall*, Rpccall*);
+static int nfsmkdir(int, Rpccall*, Rpccall*);
+static int nfsrmdir(int, Rpccall*, Rpccall*);
+static int nfsreaddir(int, Rpccall*, Rpccall*);
+static int nfsstatfs(int, Rpccall*, Rpccall*);
+
+Procmap nfsproc[] = {
+ 0, nfsnull, /* void */
+ 1, nfsgetattr, /* Fhandle */
+ 2, nfssetattr, /* Fhandle, Sattr */
+ 3, nfsroot, /* void */
+ 4, nfslookup, /* Fhandle, String */
+ 5, nfsreadlink, /* Fhandle */
+ 6, nfsread, /* Fhandle, long, long, long */
+ 7, nfswritecache,/* void */
+ 8, nfswrite, /* Fhandle, long, long, long, String */
+ 9, nfscreate, /* Fhandle, String, Sattr */
+ 10, nfsremove, /* Fhandle, String */
+ 11, nfsrename, /* Fhandle, String, Fhandle, String */
+ 12, nfslink, /* Fhandle, Fhandle, String */
+ 13, nfssymlink, /* Fhandle, String, String, Sattr */
+ 14, nfsmkdir, /* Fhandle, String, Sattr */
+ 15, nfsrmdir, /* Fhandle, String */
+ 16, nfsreaddir, /* Fhandle, long, long */
+ 17, nfsstatfs, /* Fhandle */
+ 0, 0
+};
+
+void nfsinit(int, char**);
+extern void mntinit(int, char**);
+extern Procmap mntproc[];
+
+Progmap progmap[] = {
+ 100005, 1, mntinit, mntproc,
+ 100003, 2, nfsinit, nfsproc,
+ 0, 0, 0,
+};
+
+int myport = 2049;
+long nfstime;
+int conftime;
+
+void
+main(int argc, char *argv[])
+{
+ server(argc, argv, myport, progmap);
+}
+
+static void
+doalarm(void)
+{
+ nfstime = time(0);
+ mnttimer(nfstime);
+ if(conftime+5*60 < nfstime){
+ conftime = nfstime;
+ readunixidmaps(config);
+ }
+}
+
+void
+nfsinit(int argc, char **argv)
+{
+ /*
+ * mntinit will have already parsed our options.
+ */
+ USED(argc, argv);
+ clog("nfs file server init\n");
+ rpcalarm = doalarm;
+ nfstime = time(0);
+}
+
+static int
+nfsnull(int n, Rpccall *cmd, Rpccall *reply)
+{
+ USED(n, reply);
+ chat("nfsnull...");
+ showauth(&cmd->cred);
+ chat("OK\n");
+ return 0;
+}
+
+static int
+nfsgetattr(int n, Rpccall *cmd, Rpccall *reply)
+{
+ Xfid *xf;
+ Dir dir;
+ uchar *dataptr = reply->results;
+
+ chat("getattr...");
+ if(n != FHSIZE)
+ return garbage(reply, "bad count");
+ xf = rpc2xfid(cmd, &dir);
+ if(xf == 0)
+ return error(reply, NFSERR_STALE);
+ chat("%s...", xf->xp->name);
+ PLONG(NFS_OK);
+ dataptr += dir2fattr(cmd->up, &dir, dataptr);
+ chat("OK\n");
+ return dataptr - (uchar *)reply->results;
+}
+
+static int
+nfssetattr(int n, Rpccall *cmd, Rpccall *reply)
+{
+ Xfid *xf;
+ Dir dir, nd;
+ Sattr sattr;
+ int r;
+ uchar *argptr = cmd->args;
+ uchar *dataptr = reply->results;
+
+ chat("setattr...");
+ if(n <= FHSIZE)
+ return garbage(reply, "count too small");
+ xf = rpc2xfid(cmd, &dir);
+ argptr += FHSIZE;
+ argptr += convM2sattr(argptr, &sattr);
+ if(argptr != &((uchar *)cmd->args)[n])
+ return garbage(reply, "bad count");
+ chat("mode=0%lo,u=%ld,g=%ld,size=%ld,atime=%ld,mtime=%ld...",
+ sattr.mode, sattr.uid, sattr.gid, sattr.size,
+ sattr.atime, sattr.mtime);
+ if(xf == 0)
+ return error(reply, NFSERR_STALE);
+ if(sattr.uid != NOATTR || sattr.gid != NOATTR)
+ return error(reply, NFSERR_PERM);
+ if(sattr.size == 0){
+ if(xf->xp->s != xf->xp->parent->s){
+ if(xfauthremove(xf, cmd->user) < 0)
+ return error(reply, NFSERR_PERM);
+ }else if(dir.length && xfopen(xf, Trunc|Oread|Owrite) < 0)
+ return error(reply, NFSERR_PERM);
+ }else if(sattr.size != NOATTR)
+ return error(reply, NFSERR_PERM);
+ r = 0;
+ nulldir(&nd);
+ if(sattr.mode != NOATTR)
+ ++r, nd.mode = (dir.mode & ~0777) | (sattr.mode & 0777);
+ if(sattr.atime != NOATTR)
+ ++r, nd.atime = sattr.atime;
+ if(sattr.mtime != NOATTR)
+ ++r, nd.mtime = sattr.mtime;
+ chat("sattr.mode=%luo dir.mode=%luo nd.mode=%luo...", sattr.mode, dir.mode, nd.mode);
+ if(r){
+ r = xfwstat(xf, &nd);
+ if(r < 0)
+ return error(reply, NFSERR_PERM);
+ }
+ if(xfstat(xf, &dir) < 0)
+ return error(reply, NFSERR_STALE);
+ PLONG(NFS_OK);
+ dataptr += dir2fattr(cmd->up, &dir, dataptr);
+ chat("OK\n");
+ return dataptr - (uchar *)reply->results;
+}
+
+static int
+nfsroot(int n, Rpccall *cmd, Rpccall *reply)
+{
+ USED(n, reply);
+ chat("nfsroot...");
+ showauth(&cmd->cred);
+ chat("OK\n");
+ return 0;
+}
+
+static int
+nfslookup(int n, Rpccall *cmd, Rpccall *reply)
+{
+ Xfile *xp;
+ Xfid *xf, *newxf;
+ String elem;
+ Dir dir;
+ uchar *argptr = cmd->args;
+ uchar *dataptr = reply->results;
+
+ chat("lookup...");
+ if(n <= FHSIZE)
+ return garbage(reply, "count too small");
+ xf = rpc2xfid(cmd, 0);
+ argptr += FHSIZE;
+ argptr += string2S(argptr, &elem);
+ if(argptr != &((uchar *)cmd->args)[n])
+ return garbage(reply, "bad count");
+ if(xf == 0)
+ return error(reply, NFSERR_STALE);
+ xp = xf->xp;
+ if(!(xp->qid.type & QTDIR))
+ return error(reply, NFSERR_NOTDIR);
+ chat("%s -> \"%.*s\"...", xp->name, utfnlen(elem.s, elem.n), elem.s);
+ if(xp->s->noauth == 0 && xp->parent == xp && elem.s[0] == '#')
+ newxf = xfauth(xp, &elem);
+ else
+ newxf = xfwalkcr(Twalk, xf, &elem, 0);
+ if(newxf == 0)
+ return error(reply, NFSERR_NOENT);
+ if(xfstat(newxf, &dir) < 0)
+ return error(reply, NFSERR_IO);
+ PLONG(NFS_OK);
+ dataptr += xp2fhandle(newxf->xp, dataptr);
+ dataptr += dir2fattr(cmd->up, &dir, dataptr);
+ chat("OK\n");
+ return dataptr - (uchar *)reply->results;
+}
+
+static int
+nfsreadlink(int n, Rpccall *cmd, Rpccall *reply)
+{
+ USED(n, reply);
+ chat("readlink...");
+ showauth(&cmd->cred);
+ return error(reply, NFSERR_NOENT);
+}
+
+static int
+nfsread(int n, Rpccall *cmd, Rpccall *reply)
+{
+ Session *s;
+ Xfid *xf;
+ Dir dir;
+ int offset, count;
+ uchar *argptr = cmd->args;
+ uchar *dataptr = reply->results;
+ uchar *readptr = dataptr + 4 + 17*4 + 4;
+
+ chat("read...");
+ if(n != FHSIZE+12)
+ return garbage(reply, "bad count");
+ xf = rpc2xfid(cmd, 0);
+ argptr += FHSIZE;
+ offset = GLONG();
+ count = GLONG();
+ if(xf == 0)
+ return error(reply, NFSERR_STALE);
+ chat("%s %d %d...", xf->xp->name, offset, count);
+ if(xf->xp->s != xf->xp->parent->s){
+ count = xfauthread(xf, offset, readptr, count);
+ }else{
+ if(xfopen(xf, Oread) < 0)
+ return error(reply, NFSERR_PERM);
+ if(count > 8192)
+ count = 8192;
+ s = xf->xp->s;
+ setfid(s, xf->opfid);
+ xf->opfid->tstale = nfstime + 60;
+ s->f.offset = offset;
+ s->f.count = count;
+ if(xmesg(s, Tread) < 0)
+ return error(reply, NFSERR_IO);
+ count = s->f.count;
+ memmove(readptr, s->f.data, count);
+ }
+ if(xfstat(xf, &dir) < 0)
+ return error(reply, NFSERR_IO);
+ PLONG(NFS_OK);
+ dataptr += dir2fattr(cmd->up, &dir, dataptr);
+ PLONG(count);
+ dataptr += ROUNDUP(count);
+ chat("%d OK\n", count);
+ return dataptr - (uchar *)reply->results;
+}
+
+static int
+nfswritecache(int n, Rpccall *cmd, Rpccall *reply)
+{
+ USED(n, reply);
+ chat("writecache...");
+ showauth(&cmd->cred);
+ chat("OK\n");
+ return 0;
+}
+
+static int
+nfswrite(int n, Rpccall *cmd, Rpccall *reply)
+{
+ Session *s;
+ Xfid *xf;
+ Dir dir;
+ int offset, count;
+ uchar *argptr = cmd->args;
+ uchar *dataptr = reply->results;
+
+ chat("write...");
+ if(n < FHSIZE+16)
+ return garbage(reply, "count too small");
+ xf = rpc2xfid(cmd, 0);
+ argptr += FHSIZE + 4;
+ offset = GLONG();
+ argptr += 4;
+ count = GLONG();
+ if(xf == 0)
+ return error(reply, NFSERR_STALE);
+ chat("%s %d %d...", xf->xp->name, offset, count);
+ if(xf->xp->s != xf->xp->parent->s){
+ if(xfauthwrite(xf, offset, argptr, count) < 0)
+ return error(reply, NFSERR_IO);
+ }else{
+ if(xfopen(xf, Owrite) < 0)
+ return error(reply, NFSERR_PERM);
+ s = xf->xp->s;
+ setfid(s, xf->opfid);
+ xf->opfid->tstale = nfstime + 60;
+ s->f.offset = offset;
+ s->f.count = count;
+ s->f.data = (char *)argptr;
+ if(xmesg(s, Twrite) < 0)
+ return error(reply, NFSERR_IO);
+ }
+ if(xfstat(xf, &dir) < 0)
+ return error(reply, NFSERR_IO);
+ PLONG(NFS_OK);
+ dataptr += dir2fattr(cmd->up, &dir, dataptr);
+ chat("OK\n");
+ return dataptr - (uchar *)reply->results;
+}
+
+static int
+creat(int n, Rpccall *cmd, Rpccall *reply, int chdir)
+{
+ Xfid *xf, *newxf;
+ Xfile *xp;
+ String elem;
+ Dir dir; Sattr sattr;
+ uchar *argptr = cmd->args;
+ uchar *dataptr = reply->results;
+ int trunced;
+
+ if(n <= FHSIZE)
+ return garbage(reply, "count too small");
+ xf = rpc2xfid(cmd, 0);
+ argptr += FHSIZE;
+ argptr += string2S(argptr, &elem);
+ argptr += convM2sattr(argptr, &sattr);
+ if(argptr != &((uchar *)cmd->args)[n])
+ return garbage(reply, "bad count");
+ if(xf == 0)
+ return error(reply, NFSERR_STALE);
+ xp = xf->xp;
+ if(!(xp->qid.type & QTDIR))
+ return error(reply, NFSERR_NOTDIR);
+ chat("%s/%.*s...", xp->name, utfnlen(elem.s, elem.n), elem.s);
+ trunced = 0;
+ if(xp->parent == xp && elem.s[0] == '#'){
+ newxf = xfauth(xp, &elem);
+ if(newxf == 0)
+ return error(reply, NFSERR_PERM);
+ if(xfauthremove(newxf, cmd->user) < 0)
+ return error(reply, NFSERR_PERM);
+ trunced = 1;
+ }else
+ newxf = xfwalkcr(Twalk, xf, &elem, 0);
+ if(newxf == 0){
+ newxf = xfwalkcr(Tcreate, xf, &elem, chdir|(sattr.mode&0777));
+ if(newxf)
+ trunced = 1;
+ else
+ newxf = xfwalkcr(Twalk, xf, &elem, 0);
+ }
+ if(newxf == 0)
+ return error(reply, NFSERR_PERM);
+ if(!trunced && chdir)
+ return error(reply, NFSERR_EXIST);
+ if(!trunced && xfopen(newxf, Trunc|Oread|Owrite) < 0)
+ return error(reply, NFSERR_PERM);
+ if(xfstat(newxf, &dir) < 0)
+ return error(reply, NFSERR_IO);
+
+ PLONG(NFS_OK);
+ dataptr += xp2fhandle(newxf->xp, dataptr);
+ dataptr += dir2fattr(cmd->up, &dir, dataptr);
+ chat("OK\n");
+ return dataptr - (uchar *)reply->results;
+}
+
+static int
+nfscreate(int n, Rpccall *cmd, Rpccall *reply)
+{
+ chat("create...");
+ return creat(n, cmd, reply, 0);
+}
+
+static int
+remov(int n, Rpccall *cmd, Rpccall *reply)
+{
+ Session *s;
+ Xfile *xp;
+ Xfid *xf, *newxf;
+ String elem;
+ Fid *nfid;
+ uchar *argptr = cmd->args;
+ uchar *dataptr = reply->results;
+
+ if(n <= FHSIZE)
+ return garbage(reply, "count too small");
+ xf = rpc2xfid(cmd, 0);
+ argptr += FHSIZE;
+ argptr += string2S(argptr, &elem);
+ if(argptr != &((uchar *)cmd->args)[n])
+ return garbage(reply, "bad count");
+ if(xf == 0)
+ return error(reply, NFSERR_STALE);
+ xp = xf->xp;
+ if(!(xp->qid.type & QTDIR))
+ return error(reply, NFSERR_NOTDIR);
+ chat("%s/%.*s...", xp->name, utfnlen(elem.s, elem.n), elem.s);
+ if(xp->s->noauth == 0 && xp->parent == xp && elem.s[0] == '#')
+ return error(reply, NFSERR_PERM);
+ newxf = xfwalkcr(Twalk, xf, &elem, 0);
+ if(newxf == 0)
+ return error(reply, NFSERR_NOENT);
+ s = xp->s;
+ nfid = newfid(s);
+ setfid(s, newxf->urfid);
+ s->f.newfid = nfid - s->fids;
+ s->f.nwname = 0;
+ if(xmesg(s, Twalk) < 0){
+ putfid(s, nfid);
+ return error(reply, NFSERR_IO);
+ }
+ s->f.fid = nfid - s->fids;
+ if(xmesg(s, Tremove) < 0){
+ putfid(s, nfid);
+ return error(reply, NFSERR_PERM);
+ }
+ putfid(s, nfid);
+ xpclear(newxf->xp);
+ PLONG(NFS_OK);
+ chat("OK\n");
+ return dataptr - (uchar *)reply->results;
+}
+
+static int
+nfsremove(int n, Rpccall *cmd, Rpccall *reply)
+{
+ chat("remove...");
+ return remov(n, cmd, reply);
+}
+
+static int
+nfsrename(int n, Rpccall *cmd, Rpccall *reply)
+{
+ Xfid *xf, *newxf;
+ Xfile *xp;
+ uchar *fromdir, *todir;
+ String fromelem, toelem;
+ Dir dir;
+ uchar *argptr = cmd->args;
+ uchar *dataptr = reply->results;
+
+ chat("rename...");
+ if(n <= FHSIZE)
+ return garbage(reply, "count too small");
+ xf = rpc2xfid(cmd, 0);
+ fromdir = argptr;
+ argptr += FHSIZE;
+ argptr += string2S(argptr, &fromelem);
+ todir = argptr;
+ argptr += FHSIZE;
+ argptr += string2S(argptr, &toelem);
+ if(argptr != &((uchar *)cmd->args)[n])
+ return garbage(reply, "bad count");
+ if(xf == 0)
+ return error(reply, NFSERR_STALE);
+ xp = xf->xp;
+ if(!(xp->qid.type & QTDIR))
+ return error(reply, NFSERR_NOTDIR);
+ if(memcmp(fromdir, todir, FHSIZE) != 0)
+ return error(reply, NFSERR_NXIO);
+ newxf = xfwalkcr(Twalk, xf, &fromelem, 0);
+ if(newxf == 0)
+ return error(reply, NFSERR_NOENT);
+ if(xfstat(newxf, &dir) < 0)
+ return error(reply, NFSERR_IO);
+
+ if(xp->parent == xp && toelem.s[0] == '#')
+ return error(reply, NFSERR_PERM);
+ nulldir(&dir);
+ dir.name = toelem.s;
+ if(xfwstat(newxf, &dir) < 0)
+ return error(reply, NFSERR_PERM);
+ PLONG(NFS_OK);
+ chat("OK\n");
+ return dataptr - (uchar *)reply->results;
+}
+
+static int
+nfslink(int n, Rpccall *cmd, Rpccall *reply)
+{
+ USED(n, reply);
+ chat("link...");
+ showauth(&cmd->cred);
+ return error(reply, NFSERR_NOENT);
+}
+
+static int
+nfssymlink(int n, Rpccall *cmd, Rpccall *reply)
+{
+ USED(n, reply);
+ chat("symlink...");
+ showauth(&cmd->cred);
+ return error(reply, NFSERR_NOENT);
+}
+
+static int
+nfsmkdir(int n, Rpccall *cmd, Rpccall *reply)
+{
+ chat("mkdir...");
+ return creat(n, cmd, reply, DMDIR);
+}
+
+static int
+nfsrmdir(int n, Rpccall *cmd, Rpccall *reply)
+{
+ chat("rmdir...");
+ return remov(n, cmd, reply);
+}
+
+static int
+nfsreaddir(int n, Rpccall *cmd, Rpccall *reply)
+{
+ Session *s;
+ Xfid *xf;
+ Dir dir;
+ char *rdata;
+ int k, offset, count, sfcount, entries, dsize;
+ uchar *argptr = cmd->args;
+ uchar *dataptr = reply->results;
+
+ chat("readdir...");
+ if(n != FHSIZE+8)
+ return garbage(reply, "bad count");
+ xf = rpc2xfid(cmd, 0);
+ argptr += FHSIZE;
+ offset = GLONG();
+ count = GLONG();
+ if(xf == 0)
+ return error(reply, NFSERR_STALE);
+ chat("%s (%ld) %d %d...", xf->xp->name, xf->offset, offset, count);
+ s = xf->xp->s;
+ if((xf->mode & Open) && xf->offset > offset)
+ xfclose(xf);
+ if(xfopen(xf, Oread) < 0)
+ return error(reply, NFSERR_PERM);
+ while(xf->offset < offset){ /* if we reopened, xf->offset will be zero */
+ sfcount = offset - xf->offset;
+ if(sfcount > messagesize-IOHDRSZ)
+ sfcount = messagesize-IOHDRSZ;
+ setfid(s, xf->opfid);
+ s->f.offset = xf->offset;
+ s->f.count = sfcount;
+ if(xmesg(s, Tread) < 0){
+ xfclose(xf);
+ return error(reply, NFSERR_IO);
+ }
+ if(s->f.count <= BIT16SZ)
+ break;
+ xf->offset += s->f.count;
+ }
+ if(count > messagesize-IOHDRSZ)
+ count = messagesize-IOHDRSZ;
+ PLONG(NFS_OK);
+ entries = 0;
+ while(count > 16){ /* at least 16 bytes required; we don't know size of name */
+chat("top of loop\n");
+ setfid(s, xf->opfid);
+ s->f.offset = xf->offset;
+ s->f.count = count; /* as good a guess as any */
+ if(xmesg(s, Tread) < 0){
+ xfclose(xf);
+ return error(reply, NFSERR_IO);
+ }
+ sfcount = s->f.count;
+ if(sfcount <= BIT16SZ)
+ break;
+ xf->offset += sfcount;
+chat("count %d data 0x%p\n", s->f.count, s->f.data);
+ rdata = s->f.data;
+ /* now have a buffer of Plan 9 directories; unpack into NFS thingies */
+ while(sfcount >= 0){
+ dsize = convM2D((uchar*)rdata, sfcount, &dir, (char*)s->statbuf);
+ if(dsize <= BIT16SZ){
+ count = 0; /* force break from outer loop */
+ break;
+ }
+ offset += dsize;
+ k = strlen(dir.name);
+ if(count < 16+ROUNDUP(k)){
+ count = 0; /* force break from outer loop */
+ break;
+ }
+ PLONG(TRUE);
+ PLONG(dir.qid.path);
+ PLONG(k);
+ PPTR(dir.name, k);
+ PLONG(offset);
+ count -= 16+ROUNDUP(k);
+ rdata += dsize;
+ sfcount -= dsize;
+ }
+ }
+ PLONG(FALSE);
+ if(s->f.count <= 0){
+ xfclose(xf);
+ chat("eof...");
+ PLONG(TRUE);
+ }else
+ PLONG(FALSE);
+ chat("%d OK\n", entries);
+ return dataptr - (uchar *)reply->results;
+}
+
+static int
+nfsstatfs(int n, Rpccall *cmd, Rpccall *reply)
+{
+ uchar *dataptr = reply->results;
+ enum {
+ Xfersize = 2048,
+ Maxlong = (long)((1ULL<<31) - 1),
+ Maxfreeblks = Maxlong / Xfersize,
+ };
+
+ chat("statfs...");
+ showauth(&cmd->cred);
+ if(n != FHSIZE)
+ return garbage(reply, "bad count");
+ PLONG(NFS_OK);
+ PLONG(4096); /* tsize (fs block size) */
+ PLONG(Xfersize); /* bsize (optimal transfer size) */
+ PLONG(Maxfreeblks); /* blocks in fs */
+ PLONG(Maxfreeblks); /* bfree to root*/
+ PLONG(Maxfreeblks); /* bavail (free to mortals) */
+ chat("OK\n");
+ /*conftime = 0;
+ readunixidmaps(config);*/
+ return dataptr - (uchar *)reply->results;
+}
diff --git a/sys/src/cmd/9nfs/pcnfsd.c b/sys/src/cmd/9nfs/pcnfsd.c
new file mode 100755
index 000000000..cfb947dd6
--- /dev/null
+++ b/sys/src/cmd/9nfs/pcnfsd.c
@@ -0,0 +1,180 @@
+#include "all.h"
+
+static void pcinit(int, char**);
+static int pcnull(int, Rpccall*, Rpccall*);
+static int pcinfo(int, Rpccall*, Rpccall*);
+static int pcauth(int, Rpccall*, Rpccall*);
+static int pc1auth(int, Rpccall*, Rpccall*);
+static int pcfacilities[15];
+static char no_comment[] = "Trust me.";
+static char pc_vers[] = "@(#)pcnfsd_v2.c 1.6 - rpc.pcnfsd V2.0 (c) 1994 P9, GmbH";
+static char pc_home[] = "merrimack:/";
+
+static Procmap pcproc[] = { /* pcnfsd v2 */
+ 0, pcnull,
+ 1, pcinfo,
+ 13, pcauth,
+ 0, 0
+};
+
+static Procmap pc1proc[] = { /* pc-nfsd v1 */
+ 0, pcnull,
+ 1, pc1auth,
+ 0, 0
+};
+
+int myport = 1111;
+
+Progmap progmap[] = {
+ 150001, 2, pcinit, pcproc,
+ 150001, 1, 0, pc1proc,
+ 0, 0, 0,
+};
+
+void
+main(int argc, char *argv[])
+{
+ server(argc, argv, myport, progmap);
+}
+
+static void
+pcinit(int argc, char **argv)
+{
+ Procmap *p;
+ int i;
+ char *config = "config";
+
+ ARGBEGIN{
+ case 'c':
+ config = ARGF();
+ break;
+ default:
+ if(argopt(ARGC()) < 0)
+ sysfatal("usage: %s %s [-c config]", argv0, commonopts);
+ break;
+ }ARGEND;
+ clog("pc init\n");
+
+ for(i=0; i<nelem(pcfacilities); i++)
+ pcfacilities[i] = -1;
+ for(p=pcproc; p->procp; p++)
+ pcfacilities[p->procno] = 100;
+ readunixidmaps(config);
+}
+
+static int
+pcnull(int n, Rpccall *cmd, Rpccall *reply)
+{
+ USED(n, cmd, reply);
+ return 0;
+}
+
+static void
+scramble(String *x)
+{
+ int i;
+
+ for(i=0; i<x->n; i++)
+ x->s[i] = (x->s[i] ^ 0x5b) & 0x7f;
+}
+
+static int
+pcinfo(int n, Rpccall *cmd, Rpccall *reply)
+{
+ uchar *argptr = cmd->args;
+ uchar *dataptr = reply->results;
+ String vers, cm;
+ int i;
+
+ chat("host=%I, port=%ld: pcinfo...",
+ cmd->host, cmd->port);
+ if(n <= 16)
+ return garbage(reply, "count too small");
+ argptr += string2S(argptr, &vers);
+ argptr += string2S(argptr, &cm);
+ if(argptr != &((uchar *)cmd->args)[n])
+ return garbage(reply, "bad count");
+ chat("\"%.*s\",\"%.*s\"\n", utfnlen(vers.s, vers.n), vers.s, utfnlen(cm.s, cm.n), cm.s);
+ PLONG(sizeof(pc_vers)-1);
+ PPTR(pc_vers, sizeof(pc_vers)-1);
+ PLONG(sizeof(no_comment)-1);
+ PPTR(no_comment, sizeof(no_comment)-1);
+ PLONG(nelem(pcfacilities));
+ for(i=0; i<nelem(pcfacilities); i++)
+ PLONG(pcfacilities[i]);
+ return dataptr - (uchar *)reply->results;
+}
+
+static int
+pc1auth(int n, Rpccall *cmd, Rpccall *reply)
+{
+ uchar *argptr = cmd->args;
+ uchar *dataptr = reply->results;
+ String id, pw;
+ Unixidmap *m;
+ int uid;
+
+ chat("host=%I, port=%ld: pcauth...",
+ cmd->host, cmd->port);
+ if(n <= 8)
+ return garbage(reply, "count too small");
+ argptr += string2S(argptr, &id);
+ argptr += string2S(argptr, &pw);
+ if(argptr != &((uchar*)cmd->args)[n])
+ return garbage(reply, "bad count");
+ scramble(&id);
+ scramble(&pw);
+ m = pair2idmap("pcnfsd", cmd->host);
+ uid = -1;
+ if(m)
+ uid = name2id(&m->u.ids, id.s);
+ if(uid < 0)
+ uid = 1;
+ chat("\"%.*s\",\"%.*s\" uid=%d\n", utfnlen(id.s, id.n), id.s, utfnlen(pw.s, pw.n), pw.s, uid);
+ PLONG(0); /* status */
+ PLONG(uid); /* uid */
+ PLONG(uid); /* gid */
+ return dataptr - (uchar*)reply->results;
+}
+
+static int
+pcauth(int n, Rpccall *cmd, Rpccall *reply)
+{
+ uchar *argptr = cmd->args;
+ uchar *dataptr = reply->results;
+ String sys, id, pw, cm;
+ Unixidmap *m;
+ int uid;
+
+ chat("host=%I, port=%ld: pcauth...",
+ cmd->host, cmd->port);
+ if(n <= 16)
+ return garbage(reply, "count too small");
+ argptr += string2S(argptr, &sys);
+ argptr += string2S(argptr, &id);
+ argptr += string2S(argptr, &pw);
+ argptr += string2S(argptr, &cm);
+ if(argptr != &((uchar *)cmd->args)[n])
+ return garbage(reply, "bad count");
+ scramble(&id);
+ scramble(&pw);
+
+ m = pair2idmap("pcnfsd", cmd->host);
+ uid = -1;
+ if(m)
+ uid = name2id(&m->u.ids, id.s);
+ if(uid < 0)
+ uid = 1;
+ chat("\"%.*s\",\"%.*s\",\"%.*s\",\"%.*s\"\n", utfnlen(sys.s, sys.n), sys.s,
+ utfnlen(id.s, id.n), id.s, utfnlen(pw.s, pw.n), pw.s, utfnlen(cm.s, cm.n), cm.s);
+ PLONG(0); /* status - OK */
+ PLONG(uid);
+ PLONG(uid); /* gid */
+ PLONG(0); /* ngids */
+ PLONG(sizeof(pc_home)-1);
+ PPTR(pc_home, sizeof(pc_home)-1);
+ PLONG(0); /* umask */
+ PLONG(sizeof(no_comment)-1);
+ PPTR(no_comment, sizeof(no_comment)-1);
+ return dataptr - (uchar *)reply->results;
+}
diff --git a/sys/src/cmd/9nfs/portmapper.c b/sys/src/cmd/9nfs/portmapper.c
new file mode 100755
index 000000000..ef3d7cfdd
--- /dev/null
+++ b/sys/src/cmd/9nfs/portmapper.c
@@ -0,0 +1,172 @@
+/*
+ * sunrpc portmapper
+ */
+#include "all.h"
+
+typedef struct Portmap Portmap;
+struct Portmap
+{
+ int prog;
+ int vers;
+ int protocol;
+ int port;
+};
+
+Portmap map[] = {
+ 100003, 2, IPPROTO_UDP, 2049, /* nfs v2 */
+// 100003, 3, IPPROTO_UDP, 2049, /* nfs v3 */
+ 100005, 1, IPPROTO_UDP, 2049, /* mount */
+ 150001, 2, IPPROTO_UDP, 1111, /* pcnfsd v2 */
+ 150001, 1, IPPROTO_UDP, 1111, /* pcnfsd v1 */
+ 0, 0, 0, 0,
+};
+
+static void pmapinit(int, char**);
+static int pmapnull(int, Rpccall*, Rpccall*);
+static int pmapset(int, Rpccall*, Rpccall*);
+static int pmapunset(int, Rpccall*, Rpccall*);
+static int pmapgetport(int, Rpccall*, Rpccall*);
+static int pmapdump(int, Rpccall*, Rpccall*);
+static int pmapcallit(int, Rpccall*, Rpccall*);
+
+static Procmap pmapproc[] = {
+ 0, pmapnull,
+ 1, pmapset,
+ 2, pmapunset,
+ 3, pmapgetport,
+ 4, pmapdump,
+ 5, pmapcallit,
+ 0, 0
+};
+
+int myport = 111;
+
+Progmap progmap[] = {
+ 100000, 2, pmapinit, pmapproc,
+ 0, 0, 0,
+};
+
+void
+main(int argc, char *argv[])
+{
+ server(argc, argv, myport, progmap);
+}
+
+static
+void
+pmapinit(int argc, char **argv)
+{
+ ARGBEGIN{
+ default:
+ if(argopt(ARGC()) < 0)
+ sysfatal("usage: %s %s", argv0, commonopts);
+ break;
+ }ARGEND;
+ clog("portmapper init\n");
+}
+
+static int
+pmapnull(int n, Rpccall *cmd, Rpccall *reply)
+{
+ USED(n, cmd, reply);
+ return 0;
+}
+
+static int
+pmapset(int n, Rpccall *cmd, Rpccall *reply)
+{
+ uchar *dataptr = reply->results;
+
+ if(n != 16)
+ return garbage(reply, "bad count");
+ USED(cmd);
+ PLONG(FALSE);
+ return dataptr - (uchar *)reply->results;
+}
+
+static int
+pmapunset(int n, Rpccall *cmd, Rpccall *reply)
+{
+ uchar *dataptr = reply->results;
+
+ if(n != 16)
+ return garbage(reply, "bad count");
+ USED(cmd);
+ PLONG(TRUE);
+ return dataptr - (uchar *)reply->results;
+}
+
+static int
+pmapgetport(int n, Rpccall *cmd, Rpccall *reply)
+{
+ int prog, vers, prot;
+ uchar *argptr = cmd->args;
+ uchar *dataptr = reply->results;
+ Portmap *mp;
+
+ clog("get port\n");
+
+ if(n != 16)
+ return garbage(reply, "bad count");
+ prog = GLONG();
+ vers = GLONG();
+ prot = GLONG();
+ chat("host=%I, port=%ld: ", cmd->host, cmd->port);
+ chat("getport: %d, %d, %d...", prog, vers, prot);
+ for(mp=map; mp->prog>0; mp++)
+ if(prog == mp->prog && vers == mp->vers &&
+ prot == mp->protocol)
+ break;
+ chat("%d\n", mp->port);
+ PLONG(mp->port);
+ return dataptr - (uchar *)reply->results;
+}
+
+static int
+pmapdump(int n, Rpccall *cmd, Rpccall *reply)
+{
+ uchar *dataptr = reply->results;
+ Portmap *mp;
+
+ if(n != 0)
+ return garbage(reply, "bad count");
+ USED(cmd);
+ for(mp=map; mp->prog>0; mp++){
+ PLONG(1);
+ PLONG(mp->prog);
+ PLONG(mp->vers);
+ PLONG(mp->protocol);
+ PLONG(mp->port);
+ }
+ PLONG(0);
+ return dataptr - (uchar *)reply->results;
+}
+
+static int
+pmapcallit(int n, Rpccall *cmd, Rpccall *reply)
+{
+ int prog, vers, proc;
+ uchar *argptr = cmd->args;
+ uchar *dataptr = reply->results;
+ Portmap *mp;
+
+ if(n < 12)
+ return garbage(reply, "bad count");
+ prog = GLONG();
+ vers = GLONG();
+ proc = GLONG();
+ chat("host=%I, port=%ld: ", cmd->host, cmd->port);
+ chat("callit: %d, %d, %d...", prog, vers, proc);
+ for(mp=map; mp->prog>0; mp++)
+ if(prog == mp->prog && vers == mp->vers &&
+ proc == 0)
+ break;
+ if(mp->port == 0){
+ chat("ignored\n");
+ return -1;
+ }
+ chat("%d\n", mp->port);
+ PLONG(mp->port);
+ PLONG(0);
+ return dataptr - (uchar *)reply->results;
+}
diff --git a/sys/src/cmd/9nfs/rpc.c b/sys/src/cmd/9nfs/rpc.c
new file mode 100755
index 000000000..91086c91a
--- /dev/null
+++ b/sys/src/cmd/9nfs/rpc.c
@@ -0,0 +1,314 @@
+#include "all.h"
+
+#define SHORT(x) r->x = (p[1] | (p[0]<<8)); p += 2
+#define LONG(x) r->x = (p[3] | (p[2]<<8) |\
+ (p[1]<<16) | (p[0]<<24)); p += 4
+#define SKIPLONG p += 4
+#define PTR(x, n) r->x = (void *)(p); p += ROUNDUP(n)
+
+int
+rpcM2S(void *ap, Rpccall *r, int n)
+{
+ int k;
+ uchar *p;
+ Udphdr *up;
+
+ /* copy IPv4 header fields from Udphdr */
+ up = ap;
+ p = &up->raddr[IPaddrlen - IPv4addrlen];
+ LONG(host);
+ USED(p);
+ p = &up->laddr[IPaddrlen - IPv4addrlen];
+ LONG(lhost);
+ USED(p);
+ /* ignore up->ifcaddr */
+ p = up->rport;
+ SHORT(port);
+ SHORT(lport);
+
+ LONG(xid);
+ LONG(mtype);
+ switch(r->mtype){
+ case CALL:
+ LONG(rpcvers);
+ if(r->rpcvers != 2)
+ break;
+ LONG(prog);
+ LONG(vers);
+ LONG(proc);
+ LONG(cred.flavor);
+ LONG(cred.count);
+ PTR(cred.data, r->cred.count);
+ LONG(verf.flavor);
+ LONG(verf.count);
+ PTR(verf.data, r->verf.count);
+ r->up = 0;
+ k = n - (p - (uchar *)ap);
+ if(k < 0)
+ break;
+ PTR(args, k);
+ break;
+ case REPLY:
+ LONG(stat);
+ switch(r->stat){
+ case MSG_ACCEPTED:
+ LONG(averf.flavor);
+ LONG(averf.count);
+ PTR(averf.data, r->averf.count);
+ LONG(astat);
+ switch(r->astat){
+ case SUCCESS:
+ k = n - (p - (uchar *)ap);
+ if(k < 0)
+ break;
+ PTR(results, k);
+ break;
+ case PROG_MISMATCH:
+ LONG(plow);
+ LONG(phigh);
+ break;
+ }
+ break;
+ case MSG_DENIED:
+ LONG(rstat);
+ switch(r->rstat){
+ case RPC_MISMATCH:
+ LONG(rlow);
+ LONG(rhigh);
+ break;
+ case AUTH_ERROR:
+ LONG(authstat);
+ break;
+ }
+ break;
+ }
+ break;
+ }
+ n -= p - (uchar *)ap;
+ return n;
+}
+
+int
+auth2unix(Auth *arg, Authunix *r)
+{
+ int i, n;
+ uchar *p;
+
+ if(arg->flavor != AUTH_UNIX)
+ return -1;
+ p = arg->data;
+ LONG(stamp);
+ LONG(mach.n);
+ PTR(mach.s, r->mach.n);
+ LONG(uid);
+ LONG(gid);
+ LONG(gidlen);
+ n = r->gidlen;
+ for(i=0; i<n && i < nelem(r->gids); i++){
+ LONG(gids[i]);
+ }
+ for(; i<n; i++){
+ SKIPLONG;
+ }
+ return arg->count - (p - (uchar *)arg->data);
+}
+
+int
+string2S(void *arg, String *r)
+{
+ uchar *p;
+ char *s;
+
+ p = arg;
+ LONG(n);
+ PTR(s, r->n);
+ /* must NUL terminate */
+ s = malloc(r->n+1);
+ if(s == nil)
+ panic("malloc(%ld) failed in string2S\n", r->n+1);
+ memmove(s, r->s, r->n);
+ s[r->n] = '\0';
+ r->s = strstore(s);
+ free(s);
+ return p - (uchar *)arg;
+}
+
+#undef SHORT
+#undef LONG
+#undef PTR
+
+#define SHORT(x) p[1] = r->x; p[0] = r->x>>8; p += 2
+#define LONG(x) p[3] = r->x; p[2] = r->x>>8; p[1] = r->x>>16; p[0] = r->x>>24; p += 4
+
+#define PTR(x,n) memmove(p, r->x, n); p += ROUNDUP(n)
+
+int
+rpcS2M(Rpccall *r, int ndata, void *ap)
+{
+ uchar *p;
+ Udphdr *up;
+
+ /* copy header fields to Udphdr */
+ up = ap;
+ memmove(up->raddr, v4prefix, IPaddrlen);
+ p = &up->raddr[IPaddrlen - IPv4addrlen];
+ LONG(host);
+ USED(p);
+ memmove(up->laddr, v4prefix, IPaddrlen);
+ p = &up->laddr[IPaddrlen - IPv4addrlen];
+ LONG(lhost);
+ USED(p);
+ memmove(up->ifcaddr, IPnoaddr, sizeof up->ifcaddr);
+ p = up->rport;
+ SHORT(port);
+ SHORT(lport);
+
+ LONG(xid);
+ LONG(mtype);
+ switch(r->mtype){
+ case CALL:
+ LONG(rpcvers);
+ LONG(prog);
+ LONG(vers);
+ LONG(proc);
+ LONG(cred.flavor);
+ LONG(cred.count);
+ PTR(cred.data, r->cred.count);
+ LONG(verf.flavor);
+ LONG(verf.count);
+ PTR(verf.data, r->verf.count);
+ PTR(args, ndata);
+ break;
+ case REPLY:
+ LONG(stat);
+ switch(r->stat){
+ case MSG_ACCEPTED:
+ LONG(averf.flavor);
+ LONG(averf.count);
+ PTR(averf.data, r->averf.count);
+ LONG(astat);
+ switch(r->astat){
+ case SUCCESS:
+ PTR(results, ndata);
+ break;
+ case PROG_MISMATCH:
+ LONG(plow);
+ LONG(phigh);
+ break;
+ }
+ break;
+ case MSG_DENIED:
+ LONG(rstat);
+ switch(r->rstat){
+ case RPC_MISMATCH:
+ LONG(rlow);
+ LONG(rhigh);
+ break;
+ case AUTH_ERROR:
+ LONG(authstat);
+ break;
+ }
+ break;
+ }
+ break;
+ }
+ return p - (uchar *)ap;
+}
+
+#undef SHORT
+#undef LONG
+#undef PTR
+
+#define LONG(m, x) fprint(fd, "%s = %ld\n", m, r->x)
+
+#define PTR(m, count) fprint(fd, "%s [%ld]\n", m, count)
+
+void
+rpcprint(int fd, Rpccall *r)
+{
+ fprint(fd, "%s: host = %I, port = %ld\n",
+ argv0, r->host, r->port);
+ LONG("xid", xid);
+ LONG("mtype", mtype);
+ switch(r->mtype){
+ case CALL:
+ LONG("rpcvers", rpcvers);
+ LONG("prog", prog);
+ LONG("vers", vers);
+ LONG("proc", proc);
+ LONG("cred.flavor", cred.flavor);
+ PTR("cred.data", r->cred.count);
+ LONG("verf.flavor", verf.flavor);
+ PTR("verf.data", r->verf.count);
+ fprint(fd, "args...\n");
+ break;
+ case REPLY:
+ LONG("stat", stat);
+ switch(r->stat){
+ case MSG_ACCEPTED:
+ LONG("averf.flavor", averf.flavor);
+ PTR("averf.data", r->averf.count);
+ LONG("astat", astat);
+ switch(r->astat){
+ case SUCCESS:
+ fprint(fd, "results...\n");
+ break;
+ case PROG_MISMATCH:
+ LONG("plow", plow);
+ LONG("phigh", phigh);
+ break;
+ }
+ break;
+ case MSG_DENIED:
+ LONG("rstat", rstat);
+ switch(r->rstat){
+ case RPC_MISMATCH:
+ LONG("rlow", rlow);
+ LONG("rhigh", rhigh);
+ break;
+ case AUTH_ERROR:
+ LONG("authstat", authstat);
+ break;
+ }
+ break;
+ }
+ }
+}
+
+void
+showauth(Auth *ap)
+{
+ Authunix au;
+ int i;
+
+ if(auth2unix(ap, &au) != 0){
+ chat("auth flavor=%ld, count=%ld",
+ ap->flavor, ap->count);
+ for(i=0; i<ap->count; i++)
+ chat(" %.2ux", ((uchar *)ap->data)[i]);
+ }else{
+ chat("auth: %ld %.*s u=%ld g=%ld",
+ au.stamp, utfnlen(au.mach.s, au.mach.n), au.mach.s, au.uid, au.gid);
+ for(i=0; i<au.gidlen; i++)
+ chat(", %ld", au.gids[i]);
+ }
+ chat("...");
+}
+
+int
+garbage(Rpccall *reply, char *msg)
+{
+ chat("%s\n", msg ? msg : "garbage");
+ reply->astat = GARBAGE_ARGS;
+ return 0;
+}
+
+int
+error(Rpccall *reply, int errno)
+{
+ uchar *dataptr = reply->results;
+
+ chat("error %d\n", errno);
+ PLONG(errno);
+ return dataptr - (uchar *)reply->results;
+}
diff --git a/sys/src/cmd/9nfs/rpc.h b/sys/src/cmd/9nfs/rpc.h
new file mode 100755
index 000000000..fda304864
--- /dev/null
+++ b/sys/src/cmd/9nfs/rpc.h
@@ -0,0 +1,84 @@
+/*
+ * Cf. /lib/rfc/rfc1014, /lib/rfc/rfc1050
+ */
+
+enum Bool
+{
+ FALSE = 0,
+ TRUE = 1
+};
+
+enum Auth_flavor
+{
+ AUTH_NULL = 0,
+ AUTH_UNIX = 1,
+ AUTH_SHORT = 2,
+ AUTH_DES = 3
+};
+
+enum Msg_type
+{
+ CALL = 0,
+ REPLY = 1
+};
+
+/*
+ * A reply to a call message can take on two forms:
+ * The message was either accepted or rejected.
+ */
+
+enum Reply_stat
+{
+ MSG_ACCEPTED = 0,
+ MSG_DENIED = 1
+};
+
+/*
+ * Given that a call message was accepted, the following is the
+ * status of an attempt to call a remote procedure.
+ */
+enum Accept_stat
+{
+ SUCCESS = 0, /* RPC executed successfully */
+ PROG_UNAVAIL = 1, /* remote hasn't exported program */
+ PROG_MISMATCH = 2, /* remote can't support version # */
+ PROC_UNAVAIL = 3, /* program can't support procedure */
+ GARBAGE_ARGS = 4 /* procedure can't decode params */
+};
+
+/*
+ * Reasons why a call message was rejected:
+ */
+enum Reject_stat
+{
+ RPC_MISMATCH = 0, /* RPC version number != 2 */
+ AUTH_ERROR = 1 /* remote can't authenticate caller */
+};
+
+/*
+ * Why authentication failed:
+ */
+enum Auth_stat
+{
+ AUTH_BADCRED = 1, /* bad credentials (seal broken) */
+ AUTH_REJECTEDCRED = 2, /* client must begin new session */
+ AUTH_BADVERF = 3, /* bad verifier (seal broken) */
+ AUTH_REJECTEDVERF = 4, /* verifier expired or replayed */
+ AUTH_TOOWEAK = 5 /* rejected for security reasons */
+};
+
+enum
+{
+ IPPROTO_TCP = 6, /* protocol number for TCP/IP */
+ IPPROTO_UDP = 17 /* protocol number for UDP/IP */
+};
+
+#define ROUNDUP(n) ((n) + ((-(n))&3))
+
+#define PLONG(x) (dataptr[3] = ((ulong)(x)), dataptr[2] = ((ulong)(x))>>8, dataptr[1] = ((ulong)(x))>>16, dataptr[0] = ((ulong)(x))>>24, dataptr += 4)
+#define PPTR(x, n) (memmove(dataptr, (x), n), dataptr += ROUNDUP(n))
+#define PBYTE(x) (*dataptr++ = (x))
+
+#define GLONG() (argptr += 4, (((uchar*)argptr)[-1] | (((uchar*)argptr)[-2]<<8) | (((uchar*)argptr)[-3]<<16) | (((uchar*)argptr)[-4]<<24)))
+#define GPTR(n) (void *)(argptr); argptr += ROUNDUP(n)
+#define GBYTE() (argptr++, ((uchar*)argptr)[-1])
diff --git a/sys/src/cmd/9nfs/server.c b/sys/src/cmd/9nfs/server.c
new file mode 100755
index 000000000..fe70f1036
--- /dev/null
+++ b/sys/src/cmd/9nfs/server.c
@@ -0,0 +1,618 @@
+#include "all.h"
+#include <ndb.h>
+
+static int alarmflag;
+
+static int Iconv(Fmt*);
+static void openudp(int);
+static void cachereply(Rpccall*, void*, int);
+static int replycache(int, Rpccall*, long (*)(int, void*, long));
+static void udpserver(int, Progmap*);
+static void tcpserver(int, Progmap*);
+static void getendpoints(Udphdr*, char*);
+static long readtcp(int, void*, long);
+static long writetcp(int, void*, long);
+static int servemsg(int, long (*)(int, void*, long), long (*)(int, void*, long),
+ int, Progmap*);
+void (*rpcalarm)(void);
+int rpcdebug;
+int rejectall;
+int p9debug;
+
+int nocache;
+
+uchar buf[9000];
+uchar rbuf[9000];
+uchar resultbuf[9000];
+
+static int tcp;
+
+char *commonopts = "[-9CDrtv]"; /* for usage() messages */
+
+/*
+ * this recognises common, nominally rcp-related options.
+ * they may not take arguments.
+ */
+int
+argopt(int c)
+{
+ switch(c){
+ case '9':
+ ++p9debug;
+ return 0;
+ case 'C':
+ ++nocache;
+ return 0;
+ case 'D':
+ ++rpcdebug;
+ return 0;
+ case 'r':
+ ++rejectall;
+ return 0;
+ case 't':
+ tcp = 1;
+ return 0;
+ case 'v':
+ ++chatty;
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+/*
+ * all option parsing is now done in (*pg->init)(), which can call back
+ * here to argopt for common options.
+ */
+void
+server(int argc, char **argv, int myport, Progmap *progmap)
+{
+ Progmap *pg;
+
+ fmtinstall('I', Iconv);
+ fmtinstall('F', fcallfmt);
+ fmtinstall('D', dirfmt);
+
+ switch(rfork(RFNOWAIT|RFENVG|RFNAMEG|RFNOTEG|RFFDG|RFPROC)){
+ case -1:
+ panic("fork");
+ default:
+ _exits(0);
+ case 0:
+ break;
+ }
+
+ switch(rfork(RFMEM|RFPROC)){
+ case 0:
+ for(;;){
+ sleep(30*1000);
+ alarmflag = 1;
+ }
+ case -1:
+ sysfatal("rfork: %r");
+ }
+
+ for(pg=progmap; pg->init; pg++)
+ (*pg->init)(argc, argv);
+ if(tcp)
+ tcpserver(myport, progmap);
+ else
+ udpserver(myport, progmap);
+}
+
+static void
+udpserver(int myport, Progmap *progmap)
+{
+ char service[128];
+ char data[128];
+ char devdir[40];
+ int ctlfd, datafd;
+
+ snprint(service, sizeof service, "udp!*!%d", myport);
+ ctlfd = announce(service, devdir);
+ if(ctlfd < 0)
+ panic("can't announce %s: %r\n", service);
+ if(fprint(ctlfd, "headers") < 0)
+ panic("can't set header mode: %r\n");
+
+ snprint(data, sizeof data, "%s/data", devdir);
+ datafd = open(data, ORDWR);
+ if(datafd < 0)
+ panic("can't open udp data: %r\n");
+ close(ctlfd);
+
+ chatsrv(0);
+ clog("%s: listening to port %d\n", argv0, myport);
+ while (servemsg(datafd, read, write, myport, progmap) >= 0)
+ continue;
+ exits(0);
+}
+
+static void
+tcpserver(int myport, Progmap *progmap)
+{
+ char adir[40];
+ char ldir[40];
+ char ds[40];
+ int actl, lctl, data;
+
+ snprint(ds, sizeof ds, "tcp!*!%d", myport);
+ chatsrv(0);
+ actl = -1;
+ for(;;){
+ if(actl < 0){
+ actl = announce(ds, adir);
+ if(actl < 0){
+ clog("%s: listening to tcp port %d\n",
+ argv0, myport);
+ clog("announcing: %r");
+ break;
+ }
+ }
+ lctl = listen(adir, ldir);
+ if(lctl < 0){
+ close(actl);
+ actl = -1;
+ continue;
+ }
+ switch(fork()){
+ case -1:
+ clog("%s!%d: %r\n", argv0, myport);
+ /* fall through */
+ default:
+ close(lctl);
+ continue;
+ case 0:
+ close(actl);
+ data = accept(lctl, ldir);
+ close(lctl);
+ if(data < 0)
+ exits(0);
+
+ /* pretend it's udp; fill in Udphdr */
+ getendpoints((Udphdr*)buf, ldir);
+
+ while (servemsg(data, readtcp, writetcp, myport,
+ progmap) >= 0)
+ continue;
+ close(data);
+ exits(0);
+ }
+ }
+ exits(0);
+}
+
+static int
+servemsg(int fd, long (*readmsg)(int, void*, long), long (*writemsg)(int, void*, long),
+ int myport, Progmap * progmap)
+{
+ int i, n, nreply;
+ Rpccall rcall, rreply;
+ int vlo, vhi;
+ Progmap *pg;
+ Procmap *pp;
+ char errbuf[ERRMAX];
+
+ if(alarmflag){
+ alarmflag = 0;
+ if(rpcalarm)
+ (*rpcalarm)();
+ }
+ n = (*readmsg)(fd, buf, sizeof buf);
+ if(n < 0){
+ errstr(errbuf, sizeof errbuf);
+ if(strcmp(errbuf, "interrupted") == 0)
+ return 0;
+ clog("port %d: error: %s\n", myport, errbuf);
+ return -1;
+ }
+ if(n == 0){
+ clog("port %d: EOF\n", myport);
+ return -1;
+ }
+ if(rpcdebug == 1)
+ fprint(2, "%s: rpc from %d.%d.%d.%d/%d\n",
+ argv0, buf[12], buf[13], buf[14], buf[15],
+ (buf[32]<<8)|buf[33]);
+ i = rpcM2S(buf, &rcall, n);
+ if(i != 0){
+ clog("udp port %d: message format error %d\n",
+ myport, i);
+ return 0;
+ }
+ if(rpcdebug > 1)
+ rpcprint(2, &rcall);
+ if(rcall.mtype != CALL)
+ return 0;
+ if(replycache(fd, &rcall, writemsg))
+ return 0;
+ nreply = 0;
+ rreply.host = rcall.host;
+ rreply.port = rcall.port;
+ rreply.lhost = rcall.lhost;
+ rreply.lport = rcall.lport;
+ rreply.xid = rcall.xid;
+ rreply.mtype = REPLY;
+ if(rcall.rpcvers != 2){
+ rreply.stat = MSG_DENIED;
+ rreply.rstat = RPC_MISMATCH;
+ rreply.rlow = 2;
+ rreply.rhigh = 2;
+ goto send_reply;
+ }
+ if(rejectall){
+ rreply.stat = MSG_DENIED;
+ rreply.rstat = AUTH_ERROR;
+ rreply.authstat = AUTH_TOOWEAK;
+ goto send_reply;
+ }
+ i = n - (((uchar *)rcall.args) - buf);
+ if(rpcdebug > 1)
+ fprint(2, "arg size = %d\n", i);
+ rreply.stat = MSG_ACCEPTED;
+ rreply.averf.flavor = 0;
+ rreply.averf.count = 0;
+ rreply.results = resultbuf;
+ vlo = 0x7fffffff;
+ vhi = -1;
+ for(pg=progmap; pg->pmap; pg++){
+ if(pg->progno != rcall.prog)
+ continue;
+ if(pg->vers == rcall.vers)
+ break;
+ if(pg->vers < vlo)
+ vlo = pg->vers;
+ if(pg->vers > vhi)
+ vhi = pg->vers;
+ }
+ if(pg->pmap == 0){
+ if(vhi < 0)
+ rreply.astat = PROG_UNAVAIL;
+ else{
+ rreply.astat = PROG_MISMATCH;
+ rreply.plow = vlo;
+ rreply.phigh = vhi;
+ }
+ goto send_reply;
+ }
+ for(pp = pg->pmap; pp->procp; pp++)
+ if(rcall.proc == pp->procno){
+ if(rpcdebug > 1)
+ fprint(2, "process %d\n", pp->procno);
+ rreply.astat = SUCCESS;
+ nreply = (*pp->procp)(i, &rcall, &rreply);
+ goto send_reply;
+ }
+ rreply.astat = PROC_UNAVAIL;
+send_reply:
+ if(nreply >= 0){
+ i = rpcS2M(&rreply, nreply, rbuf);
+ if(rpcdebug > 1)
+ rpcprint(2, &rreply);
+ (*writemsg)(fd, rbuf, i);
+ cachereply(&rreply, rbuf, i);
+ }
+ return 0;
+}
+
+static void
+getendpoint(char *dir, char *file, uchar *addr, uchar *port)
+{
+ int fd, n;
+ char buf[128];
+ char *sys, *serv;
+
+ sys = serv = 0;
+
+ snprint(buf, sizeof buf, "%s/%s", dir, file);
+ fd = open(buf, OREAD);
+ if(fd >= 0){
+ n = read(fd, buf, sizeof(buf)-1);
+ if(n>0){
+ buf[n-1] = 0;
+ serv = strchr(buf, '!');
+ if(serv){
+ *serv++ = 0;
+ serv = strdup(serv);
+ }
+ sys = strdup(buf);
+ }
+ close(fd);
+ }
+ if(serv == 0)
+ serv = strdup("unknown");
+ if(sys == 0)
+ sys = strdup("unknown");
+ parseip(addr, sys);
+ n = atoi(serv);
+ hnputs(port, n);
+}
+
+/* set Udphdr values from protocol dir local & remote files */
+static void
+getendpoints(Udphdr *ep, char *dir)
+{
+ getendpoint(dir, "local", ep->laddr, ep->lport);
+ getendpoint(dir, "remote", ep->raddr, ep->rport);
+}
+
+static long
+readtcp(int fd, void *vbuf, long blen)
+{
+ uchar mk[4];
+ int n, m, sofar;
+ ulong done;
+ char *buf;
+
+ buf = vbuf;
+ buf += Udphdrsize;
+ blen -= Udphdrsize;
+
+ done = 0;
+ for(sofar = 0; !done; sofar += n){
+ m = readn(fd, mk, 4);
+ if(m < 4)
+ return 0;
+ done = (mk[0]<<24)|(mk[1]<<16)|(mk[2]<<8)|mk[3];
+ m = done & 0x7fffffff;
+ done &= 0x80000000;
+ if(m > blen-sofar)
+ return -1;
+ n = readn(fd, buf+sofar, m);
+ if(m != n)
+ return 0;
+ }
+ return sofar + Udphdrsize;
+}
+
+static long
+writetcp(int fd, void *vbuf, long len)
+{
+ char *buf;
+
+ buf = vbuf;
+ buf += Udphdrsize;
+ len -= Udphdrsize;
+
+ buf -= 4;
+ buf[0] = 0x80 | (len>>24);
+ buf[1] = len>>16;
+ buf[2] = len>>8;
+ buf[3] = len;
+ len += 4;
+ return write(fd, buf, len);
+}
+/*
+ *long
+ *niwrite(int fd, void *buf, long count)
+ *{
+ * char errbuf[ERRLEN];
+ * long n;
+ *
+ * for(;;){
+ * n = write(fd, buf, count);
+ * if(n < 0){
+ * errstr(errbuf);
+ * if(strcmp(errbuf, "interrupted") == 0)
+ * continue;
+ * clog("niwrite error: %s\n", errbuf);
+ * werrstr(errbuf);
+ * }
+ * break;
+ * }
+ * return n;
+ *}
+ */
+long
+niwrite(int fd, void *buf, long n)
+{
+// int savalarm;
+
+// savalarm = alarm(0);
+ n = write(fd, buf, n);
+// if(savalarm > 0)
+// alarm(savalarm);
+ return n;
+}
+
+typedef struct Namecache Namecache;
+struct Namecache {
+ char dom[256];
+ ulong ipaddr;
+ Namecache *next;
+};
+
+Namecache *dnscache;
+
+static Namecache*
+domlookupl(void *name, int len)
+{
+ Namecache *n, **ln;
+
+ if(len >= sizeof(n->dom))
+ return nil;
+
+ for(ln=&dnscache, n=*ln; n; ln=&(*ln)->next, n=*ln) {
+ if(strncmp(n->dom, name, len) == 0 && n->dom[len] == 0) {
+ *ln = n->next;
+ n->next = dnscache;
+ dnscache = n;
+ return n;
+ }
+ }
+ return nil;
+}
+
+static Namecache*
+domlookup(void *name)
+{
+ return domlookupl(name, strlen(name));
+}
+
+static Namecache*
+iplookup(ulong ip)
+{
+ Namecache *n, **ln;
+
+ for(ln=&dnscache, n=*ln; n; ln=&(*ln)->next, n=*ln) {
+ if(n->ipaddr == ip) {
+ *ln = n->next;
+ n->next = dnscache;
+ dnscache = n;
+ return n;
+ }
+ }
+ return nil;
+}
+
+static Namecache*
+addcacheentry(void *name, int len, ulong ip)
+{
+ Namecache *n;
+
+ if(len >= sizeof(n->dom))
+ return nil;
+
+ n = malloc(sizeof(*n));
+ if(n == nil)
+ return nil;
+ strncpy(n->dom, name, len);
+ n->dom[len] = 0;
+ n->ipaddr = ip;
+ n->next = dnscache;
+ dnscache = n;
+ return nil;
+}
+
+int
+getdnsdom(ulong ip, char *name, int len)
+{
+ char buf[128];
+ Namecache *nc;
+ char *p;
+
+ if(nc=iplookup(ip)) {
+ strncpy(name, nc->dom, len);
+ name[len-1] = 0;
+ return 0;
+ }
+ clog("getdnsdom: %I\n", ip);
+ snprint(buf, sizeof buf, "%I", ip);
+ p = csgetvalue("/net", "ip", buf, "dom", nil);
+ if(p == nil)
+ return -1;
+ strncpy(name, p, len-1);
+ name[len] = 0;
+ free(p);
+ addcacheentry(name, strlen(name), ip);
+ return 0;
+}
+
+int
+getdom(ulong ip, char *dom, int len)
+{
+ int i;
+ static char *prefix[] = { "", "gate-", "fddi-", "u-", 0 };
+ char **pr;
+
+ if(getdnsdom(ip, dom, len)<0)
+ return -1;
+
+ for(pr=prefix; *pr; pr++){
+ i = strlen(*pr);
+ if(strncmp(dom, *pr, i) == 0) {
+ memmove(dom, dom+i, len-i);
+ break;
+ }
+ }
+ return 0;
+}
+
+#define MAXCACHE 64
+
+static Rpccache *head, *tail;
+static int ncache;
+
+static void
+cachereply(Rpccall *rp, void *buf, int len)
+{
+ Rpccache *cp;
+
+ if(nocache)
+ return;
+
+ if(ncache >= MAXCACHE){
+ if(rpcdebug)
+ fprint(2, "%s: drop %I/%ld, xid %uld, len %d\n",
+ argv0, tail->host,
+ tail->port, tail->xid, tail->n);
+ tail = tail->prev;
+ free(tail->next);
+ tail->next = 0;
+ --ncache;
+ }
+ cp = malloc(sizeof(Rpccache)+len-4);
+ if(cp == 0){
+ clog("cachereply: malloc %d failed\n", len);
+ return;
+ }
+ ++ncache;
+ cp->prev = 0;
+ cp->next = head;
+ if(head)
+ head->prev = cp;
+ else
+ tail = cp;
+ head = cp;
+ cp->host = rp->host;
+ cp->port = rp->port;
+ cp->xid = rp->xid;
+ cp->n = len;
+ memmove(cp->data, buf, len);
+ if(rpcdebug)
+ fprint(2, "%s: cache %I/%ld, xid %uld, len %d\n",
+ argv0, cp->host, cp->port, cp->xid, cp->n);
+}
+
+static int
+replycache(int fd, Rpccall *rp, long (*writemsg)(int, void*, long))
+{
+ Rpccache *cp;
+
+ for(cp=head; cp; cp=cp->next)
+ if(cp->host == rp->host &&
+ cp->port == rp->port &&
+ cp->xid == rp->xid)
+ break;
+ if(cp == 0)
+ return 0;
+ if(cp->prev){ /* move to front */
+ cp->prev->next = cp->next;
+ if(cp->next)
+ cp->next->prev = cp->prev;
+ else
+ tail = cp->prev;
+ cp->prev = 0;
+ cp->next = head;
+ head->prev = cp;
+ head = cp;
+ }
+ (*writemsg)(fd, cp->data, cp->n);
+ if(rpcdebug)
+ fprint(2, "%s: reply %I/%ld, xid %uld, len %d\n",
+ argv0, cp->host, cp->port, cp->xid, cp->n);
+ return 1;
+}
+
+static int
+Iconv(Fmt *f)
+{
+ char buf[16];
+ ulong h;
+
+ h = va_arg(f->args, ulong);
+ snprint(buf, sizeof buf, "%ld.%ld.%ld.%ld",
+ (h>>24)&0xff, (h>>16)&0xff,
+ (h>>8)&0xff, h&0xff);
+ return fmtstrcpy(f, buf);
+}
diff --git a/sys/src/cmd/9nfs/string.c b/sys/src/cmd/9nfs/string.c
new file mode 100755
index 000000000..e71bb2e20
--- /dev/null
+++ b/sys/src/cmd/9nfs/string.c
@@ -0,0 +1,105 @@
+#include "all.h"
+
+#define STRHASH 509 /* prime */
+
+static Strnode * stab[STRHASH];
+
+static long hashfun(void*);
+static Strnode* nalloc(int);
+
+char *
+strfind(char *a)
+{
+ Strnode **bin, *x, *xp;
+
+ bin = &stab[hashfun(a) % STRHASH];
+ for(xp=0, x=*bin; x; xp=x, x=x->next)
+ if(x->str[0] == a[0] && strcmp(x->str, a) == 0)
+ break;
+ if(x == 0)
+ return 0;
+ if(xp){
+ xp->next = x->next;
+ x->next = *bin;
+ *bin = x;
+ }
+ return x->str;
+}
+
+char *
+strstore(char *a)
+{
+ Strnode **bin, *x, *xp;
+ int n;
+
+ bin = &stab[hashfun(a) % STRHASH];
+ for(xp=0, x=*bin; x; xp=x, x=x->next)
+ if(x->str[0] == a[0] && strcmp(x->str, a) == 0)
+ break;
+ if(x == 0){
+ n = strlen(a)+1;
+ x = nalloc(n);
+ memmove(x->str, a, n);
+ x->next = *bin;
+ *bin = x;
+ }else if(xp){
+ xp->next = x->next;
+ x->next = *bin;
+ *bin = x;
+ }
+ return x->str;
+}
+
+void
+strprint(int fd)
+{
+ Strnode **bin, *x;
+
+ for(bin = stab; bin < stab+STRHASH; bin++)
+ for(x=*bin; x; x=x->next)
+ fprint(fd, "%ld %s\n", bin-stab, x->str);
+}
+
+static long
+hashfun(void *v)
+{
+ ulong a = 0, b;
+ uchar *s = v;
+
+ while(*s){
+ a = (a << 4) + *s++;
+ if(b = a&0xf0000000){ /* assign = */
+ a ^= b >> 24;
+ a ^= b;
+ }
+ }
+ return a;
+}
+
+#define STRSIZE 1000
+
+static Strnode *
+nalloc(int n) /* get permanent storage for Strnode */
+{
+ static char *curstp;
+ static int nchleft;
+ int k;
+ char *p;
+
+ if(n < 4)
+ n = 4;
+ else if(k = n&3) /* assign = */
+ n += 4-k;
+ n += sizeof(Strnode)-4;
+ if(n > nchleft){
+ nchleft = STRSIZE;
+ while(nchleft < n)
+ nchleft *= 2;
+ if((curstp = malloc(nchleft)) == 0)
+ panic("malloc(%d) failed in nalloc\n", nchleft);
+ }
+ p = curstp;
+ curstp += n;
+ nchleft -= n;
+ return (Strnode*)p;
+}
diff --git a/sys/src/cmd/9nfs/strparse.c b/sys/src/cmd/9nfs/strparse.c
new file mode 100755
index 000000000..d89fae307
--- /dev/null
+++ b/sys/src/cmd/9nfs/strparse.c
@@ -0,0 +1,32 @@
+#include <u.h>
+#include <libc.h>
+
+int strcomment = '#';
+
+int
+strparse(char *p, int arsize, char **arv)
+{
+ int arc = 0;
+
+ /*print("parse: 0x%lux = \"%s\"\n", p, p);/**/
+ while(p){
+ while(*p == ' ' || *p == '\t')
+ p++;
+ if(*p == 0 || *p == strcomment)
+ break;
+ if(arc >= arsize-1)
+ break;
+ arv[arc++] = p;
+ while(*p && *p != ' ' && *p != '\t')
+ p++;
+ if(*p == 0)
+ break;
+ *p++ = 0;
+ }
+ arv[arc] = 0;
+ /*while(*arv){
+ print("\t0x%lux = \"%s\"\n", *arv, *arv);
+ ++arv;
+ }/**/
+ return arc;
+}
diff --git a/sys/src/cmd/9nfs/system.c b/sys/src/cmd/9nfs/system.c
new file mode 100755
index 000000000..5c1d654ff
--- /dev/null
+++ b/sys/src/cmd/9nfs/system.c
@@ -0,0 +1,34 @@
+#include <u.h>
+#include <libc.h>
+
+Waitmsg*
+system(char *name, char **argv)
+{
+ char err[ERRMAX];
+ Waitmsg *w;
+ int pid;
+
+ switch(pid = fork()){ /* assign = */
+ case -1:
+ return nil;
+ case 0:
+ exec(name, argv);
+ errstr(err, sizeof err);
+ _exits(err);
+ }
+ for(;;){
+ w = wait();
+ if(w == nil)
+ break;
+ if(w->pid == pid)
+ return w;
+ free(w);
+ }
+ return nil;
+}
+
+Waitmsg*
+systeml(char *name, ...)
+{
+ return system(name, &name+1);
+}
diff --git a/sys/src/cmd/9nfs/testit b/sys/src/cmd/9nfs/testit
new file mode 100755
index 000000000..c08d74093
--- /dev/null
+++ b/sys/src/cmd/9nfs/testit
@@ -0,0 +1,8 @@
+#!/bin/rc
+9fs nslocum
+Kill 8.portmapper|rc
+Kill 8.nfsserver|rc
+rm -f /srv/nfsserver.chat /srv/portmapper.chat
+8.nfsserver -a il!emelie -c /lib/ndb/nfs >[2] /tmp/nfsserver
+8.portmapper >[2] /tmp/portmapper
+# on unix: mount -t nfs -o nfsv2 edith:/ /mnt
diff --git a/sys/src/cmd/9nfs/unixnames.c b/sys/src/cmd/9nfs/unixnames.c
new file mode 100755
index 000000000..7b12fbb30
--- /dev/null
+++ b/sys/src/cmd/9nfs/unixnames.c
@@ -0,0 +1,322 @@
+#include "all.h"
+
+static void uxfree(Unixid*);
+
+static Unixid * xfree;
+
+Unixidmap *idhead, *idtail;
+
+Unixscmap *scmap;
+
+#define UNUSED 0x7FFFFFFF
+
+/*
+ * Sadly we have to use the IP address, since some systems (FreeBSD in particular)
+ * do not believe it to be safe to depend on the hostname and so refuse to send it.
+ * I dislike making this IP-centric, but so be it.
+ * We keep a cache of host names in getdom.
+ */
+Unixidmap *
+pair2idmap(char *server, ulong clientip)
+{
+ Resub match;
+ Unixscmap *m, *mp;
+ Unixidmap *r;
+ char dom[256];
+
+ for(mp=0,m=scmap; m; mp=m,m=m->next){
+ if(m->server[0] != server[0])
+ continue;
+ if(strcmp(m->server, server))
+ continue;
+ if(m->clientip != clientip)
+ continue;
+ if(mp){
+ mp->next = m->next;
+ m->next = scmap;
+ scmap = m;
+ }
+ r = m->map;
+ if(r->u.timestamp != 0 && r->g.timestamp != 0)
+ return r;
+ scmap = m->next;
+ free(m);
+ break;
+ }
+ if(rpcdebug)
+ fprint(2, "looking for %lux\n", clientip);
+ if(getdom(clientip, dom, sizeof dom)<0){
+ clog("auth: unknown ip address");
+ return nil;
+ }
+ if(rpcdebug)
+ fprint(2, "dom is %s\n", dom);
+ for(r=idhead; r; r=r->next){
+ if(r->u.timestamp == 0 || r->g.timestamp == 0)
+ continue;
+ match.sp = match.ep = 0;
+ if(regexec(r->sexp, server, &match, 1) == 0)
+ continue;
+ if(match.sp != server || match.ep <= match.sp || *match.ep)
+ continue;
+ match.sp = match.ep = 0;
+ if(regexec(r->cexp, dom, &match, 1) == 0)
+ continue;
+ if(match.sp != dom || match.ep <= match.sp || *match.ep)
+ continue;
+ m = malloc(sizeof(Unixscmap));
+ m->next = scmap;
+ scmap = m;
+ m->server = strstore(server);
+ m->clientip = clientip;
+ m->map = r;
+ break;
+ }
+ return r;
+}
+
+int
+readunixidmaps(char *file)
+{
+ Waitmsg *w;
+ Biobuf *in;
+ Unixidmap *m;
+ int i, arc; char *arv[16], buf[256];
+ char *l;
+// long savalarm;
+
+// savalarm = alarm(0);
+ in = Bopen(file, OREAD);
+ if(in == 0){
+ clog("readunixidmaps can't open %s: %r\n", file);
+// alarm(savalarm);
+ return -1;
+ }
+ for(m=idhead; m; m=m->next)
+ m->flag = 0;
+ while(l = Brdline(in, '\n')){ /* assign = */
+ l[Blinelen(in)-1] = 0;
+ arc = strparse(l, nelem(arv), arv);
+ if(arc > 0 && arv[0][0] == '!'){
+ ++arv[0];
+ snprint(buf, sizeof buf, "/bin/%s", arv[0]);
+ if(chatty){
+ chat("!");
+ for(i=0; i<arc; i++)
+ chat(" %s", arv[i]);
+ chat("...");
+ }
+ w = system(buf, arv);
+ if(w == nil)
+ chat("err: %r\n");
+ else if(w->msg && w->msg[0])
+ chat("status: %s\n", w->msg);
+ else
+ chat("OK\n");
+ free(w);
+ continue;
+ }
+ if(arc != 4)
+ continue;
+ for(m=idhead; m; m=m->next)
+ if(strcmp(arv[0], m->server) == 0 &&
+ strcmp(arv[1], m->client) == 0)
+ break;
+ if(m == 0){
+ m = malloc(sizeof(Unixidmap));
+ if(idtail)
+ idtail->next = m;
+ else
+ idhead = m;
+ idtail = m;
+ m->next = 0;
+ m->server = strstore(arv[0]);
+ m->client = strstore(arv[1]);
+ m->sexp = regcomp(m->server);
+ m->cexp = regcomp(m->client);
+ m->u.file = strstore(arv[2]);
+ m->u.style = 'u';
+ m->u.timestamp = 0;
+ m->u.ids = 0;
+ m->g.file = strstore(arv[3]);
+ m->g.style = 'u';
+ m->g.timestamp = 0;
+ m->g.ids = 0;
+ }else{
+ if(!m->u.file || strcmp(m->u.file, arv[2]) != 0){
+ m->u.file = strstore(arv[2]);
+ m->u.timestamp = 0;
+ }
+ if(!m->g.file || strcmp(m->g.file, arv[3]) != 0){
+ m->g.file = strstore(arv[3]);
+ m->g.timestamp = 0;
+ }
+ }
+ m->flag = 1;
+ checkunixmap(&m->u);
+ checkunixmap(&m->g);
+ }
+ Bterm(in);
+ for(m=idhead; m; m=m->next)
+ if(m->flag == 0){
+ m->u.file = 0;
+ m->u.timestamp = 0;
+ uxfree(m->u.ids);
+ m->u.ids = 0;
+ m->g.file = 0;
+ m->g.timestamp = 0;
+ uxfree(m->g.ids);
+ m->g.ids = 0;
+ }
+// alarm(savalarm);
+ return 0;
+}
+
+static void
+uxfree(Unixid *x)
+{
+ Unixid *tail;
+ int count=0;
+
+ if(x){
+ tail = x;
+ if(tail->id < 0)
+ abort();
+ tail->id = UNUSED;
+ while(tail->next){
+ tail = tail->next;
+ ++count;
+ if(tail->id == UNUSED)
+ abort();
+ tail->id = UNUSED;
+ }
+ tail->next = xfree;
+ xfree = x;
+ }
+}
+
+int
+checkunixmap(Unixmap *u)
+{
+ Dir *dir;
+
+ dir = dirstat(u->file);
+ if(dir == nil){
+ clog("checkunixmap can't stat %s: %r\n", u->file);
+ return -1;
+ }
+ if(u->timestamp > dir->mtime){
+ free(dir);
+ return 0;
+ }
+ uxfree(u->ids);
+ u->ids = readunixids(u->file, u->style);
+ u->timestamp = time(0);
+ free(dir);
+ return 1;
+}
+
+int
+name2id(Unixid **list, char *name)
+{
+ Unixid *x, *xp;
+
+ for(xp=0,x=*list; x; xp=x,x=x->next){
+ if(x->name[0] == name[0] && strcmp(x->name, name) == 0){
+ if(xp){
+ xp->next = x->next;
+ x->next = *list;
+ *list = x;
+ }
+ return x->id;
+ }
+ }
+ return -1;
+}
+
+char *
+id2name(Unixid **list, int id)
+{
+ Unixid *x, *xp;
+
+ for(xp=0,x=*list; x; xp=x,x=x->next){
+ if(x->id == id){
+ if(xp){
+ xp->next = x->next;
+ x->next = *list;
+ *list = x;
+ }
+ return x->name;
+ }
+ }
+ return "none";
+}
+
+void
+idprint(int fd, Unixid *xp)
+{
+ while(xp){
+ fprint(fd, "%d\t%s\n", xp->id, xp->name);
+ xp = xp->next;
+ }
+}
+
+/*
+ * style '9': 3:tom:tom:
+ * style 'u': sysadm:*:0:0:System-Administrator:/usr/admin:/bin/sh
+ */
+
+Unixid *
+readunixids(char *file, int style)
+{
+ Biobuf *in;
+ char *l, *name = 0;
+ Unixid *x, *xp = 0;
+ int id = 0;
+
+ in = Bopen(file, OREAD);
+ if(in == 0){
+ clog("readunixids can't open %s: %r\n", file);
+ return 0;
+ }
+ while(l = Brdline(in, '\n')){ /* assign = */
+ l[Blinelen(in)-1] = 0;
+ switch(style){
+ case '9':
+ id = strtol(l, &l, 10);
+ if(*l != ':')
+ continue;
+ name = ++l;
+ l = strchr(l, ':');
+ if(l == 0)
+ continue;
+ *l = 0;
+ break;
+ case 'u':
+ name = l;
+ l = strchr(l, ':');
+ if(l == 0)
+ continue;
+ *l++ = 0;
+ /* skip password */
+ l = strchr(l, ':');
+ if(l == 0)
+ continue;
+ id = strtol(l+1, 0, 10);
+ break;
+ default:
+ panic("unknown unixid style %d\n", style);
+ }
+ if(id == UNUSED)
+ id = -1; /* any value will do */
+ if(!(x = xfree)) /* assign = */
+ x = listalloc(1024/sizeof(Unixid), sizeof(Unixid));
+ xfree = x->next;
+ x->id = id;
+ x->name = strstore(name);
+ x->next = xp;
+ xp = x;
+ }
+ Bterm(in);
+ return xp;
+}
diff --git a/sys/src/cmd/9nfs/xfile.c b/sys/src/cmd/9nfs/xfile.c
new file mode 100755
index 000000000..6f294d6d9
--- /dev/null
+++ b/sys/src/cmd/9nfs/xfile.c
@@ -0,0 +1,106 @@
+#include "all.h"
+
+static Xfile * xflfree;
+static Xfid * xfdfree;
+
+#define FIDMOD 127 /* prime */
+
+static Xfile *xfiles[FIDMOD];
+static Lock xlocks[FIDMOD];
+
+Xfile *
+xfile(Qid *qid, void *s, int new)
+{
+ int k;
+ Xfile **hp, *f, *pf;
+
+ k = ((ulong)qid->path ^ (((u32int)(uintptr)s)<<24))%FIDMOD;
+ hp = &xfiles[k];
+
+ lock(&xlocks[k]);
+ for(f=*hp, pf=0; f; pf=f, f=f->next)
+ if(f->qid.path == qid->path
+ && (u32int)(uintptr)f->s == (u32int)(uintptr)s)
+ break;
+ if(f && pf){
+ pf->next = f->next;
+ f->next = *hp;
+ *hp = f;
+ }
+ if(new < 0 && f){
+ *hp = f->next;
+ f->next = xflfree;
+ xflfree = f;
+ f = 0;
+ }
+ if(new > 0 && !f){
+ if(!(f = xflfree)) /* assign = */
+ f = listalloc(1024/sizeof(Xfile), sizeof(Xfile));
+ xflfree = f->next;
+ memset(f, 0, sizeof(Xfile));
+ f->next = *hp;
+ *hp = f;
+ f->qid = *qid;
+ f->s = s;
+ }
+ unlock(&xlocks[k]);
+ return f;
+}
+
+Xfid *
+xfid(char *uid, Xfile *xp, int new)
+{
+ Xfid *f, *pf;
+
+ for(f=xp->users, pf=0; f; pf=f, f=f->next)
+ if(f->uid[0] == uid[0] && strcmp(f->uid, uid) == 0)
+ break;
+ if(f && pf){
+ pf->next = f->next;
+ f->next = xp->users;
+ xp->users = f;
+ }
+ if(new < 0 && f){
+ if(f->urfid)
+ clunkfid(xp->s, f->urfid);
+ if(f->opfid)
+ clunkfid(xp->s, f->opfid);
+ xp->users = f->next;
+ f->next = xfdfree;
+ xfdfree = f;
+ f = 0;
+ }
+ if(new > 0 && !f){
+ if(!(f = xfdfree)) /* assign = */
+ f = listalloc(1024/sizeof(Xfid), sizeof(Xfid));
+ xfdfree = f->next;
+ memset(f, 0, sizeof(Xfid));
+ f->next = xp->users;
+ xp->users = f;
+ f->xp = xp;
+ f->uid = strstore(uid);
+ f->urfid = 0;
+ f->opfid = 0;
+ }
+ return f;
+}
+
+int
+xfpurgeuid(Session *s, char *uid)
+{
+ Xfile **hp, *f;
+ Xfid *xf;
+ int k, count=0;
+
+ for(k=0; k<FIDMOD; k++){
+ lock(&xlocks[k]);
+ hp=&xfiles[k];
+ for(f=*hp; f; f=f->next)
+ if(f->s == s && (xf = xfid(uid, f, 0))){ /* assign = */
+ xfclear(xf);
+ ++count;
+ }
+ unlock(&xlocks[k]);
+ }
+ return count;
+}