summaryrefslogtreecommitdiff
path: root/sys/src/cmd/nfs.c
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/nfs.c
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/nfs.c')
-rwxr-xr-xsys/src/cmd/nfs.c1632
1 files changed, 1632 insertions, 0 deletions
diff --git a/sys/src/cmd/nfs.c b/sys/src/cmd/nfs.c
new file mode 100755
index 000000000..03bcc1f47
--- /dev/null
+++ b/sys/src/cmd/nfs.c
@@ -0,0 +1,1632 @@
+/* Copyright © 2003 Russ Cox, MIT; see /sys/src/libsunrpc/COPYING */
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include <sunrpc.h>
+#include <nfs3.h>
+
+SunClient *nfscli;
+SunClient *mntcli;
+char *defaultpath = "/";
+Channel *fschan;
+char *sys;
+int verbose;
+int readplus = 0;
+
+
+typedef struct Auth Auth;
+struct Auth
+{
+ int ref;
+ uchar *data;
+ int ndata;
+};
+
+typedef struct FidAux FidAux;
+struct FidAux
+{
+ Nfs3Handle handle;
+
+ u64int cookie; /* for continuing directory reads */
+ char *name; /* botch: for remove and rename */
+ Nfs3Handle parent; /* botch: for remove and rename */
+ char err[ERRMAX]; /* for walk1 */
+ Auth *auth;
+};
+
+/*
+ * various RPCs. here is where we'd insert support for NFS v2
+ */
+
+void
+portCall(SunCall *c, PortCallType type)
+{
+ c->rpc.prog = PortProgram;
+ c->rpc.vers = PortVersion;
+ c->rpc.proc = type>>1;
+ c->rpc.iscall = !(type&1);
+ c->type = type;
+}
+
+int
+getport(SunClient *client, uint prog, uint vers, uint prot, uint *port)
+{
+ PortTGetport tx;
+ PortRGetport rx;
+
+ memset(&tx, 0, sizeof tx);
+ portCall(&tx.call, PortCallTGetport);
+ tx.map.prog = prog;
+ tx.map.vers = vers;
+ tx.map.prot = prot;
+
+ memset(&rx, 0, sizeof rx);
+ portCall(&rx.call, PortCallRGetport);
+
+ if(sunClientRpc(client, 0, &tx.call, &rx.call, nil) < 0)
+ return -1;
+ *port = rx.port;
+ return 0;
+}
+
+void
+mountCall(Auth *a, SunCall *c, NfsMount3CallType type)
+{
+ c->rpc.iscall = !(type&1);
+ c->rpc.proc = type>>1;
+ c->rpc.prog = NfsMount3Program;
+ c->rpc.vers = NfsMount3Version;
+ if(c->rpc.iscall && a){
+ c->rpc.cred.flavor = SunAuthSys;
+ c->rpc.cred.data = a->data;
+ c->rpc.cred.ndata = a->ndata;
+ }
+ c->type = type;
+}
+
+int
+mountNull(ulong tag)
+{
+ NfsMount3TNull tx;
+ NfsMount3RNull rx;
+
+ memset(&tx, 0, sizeof tx);
+ mountCall(nil, &tx.call, NfsMount3CallTNull);
+
+ memset(&rx, 0, sizeof rx);
+ mountCall(nil, &rx.call, NfsMount3CallTNull);
+
+ return sunClientRpc(mntcli, tag, &tx.call, &rx.call, nil);
+}
+
+int
+mountMnt(Auth *a, ulong tag, char *path, Nfs3Handle *h)
+{
+ uchar *freeme;
+ NfsMount3TMnt tx;
+ NfsMount3RMnt rx;
+
+ memset(&tx, 0, sizeof tx);
+ mountCall(a, &tx.call, NfsMount3CallTMnt);
+ tx.path = path;
+
+ memset(&rx, 0, sizeof rx);
+ mountCall(a, &rx.call, NfsMount3CallRMnt);
+ if(sunClientRpc(mntcli, tag, &tx.call, &rx.call, &freeme) < 0)
+ return -1;
+ if(rx.status != Nfs3Ok){
+ nfs3Errstr(rx.status);
+ return -1;
+ }
+if(verbose)print("handle %.*H\n", rx.len, rx.handle);
+ if(rx.len >= Nfs3MaxHandleSize){
+ free(freeme);
+ werrstr("server-returned handle too long");
+ return -1;
+ }
+ memmove(h->h, rx.handle, rx.len);
+ h->len = rx.len;
+ free(freeme);
+ return 0;
+}
+
+void
+nfs3Call(Auth *a, SunCall *c, Nfs3CallType type)
+{
+ c->rpc.iscall = !(type&1);
+ c->rpc.proc = type>>1;
+ c->rpc.prog = Nfs3Program;
+ c->rpc.vers = Nfs3Version;
+ if(c->rpc.iscall && a){
+ c->rpc.cred.flavor = SunAuthSys;
+ c->rpc.cred.data = a->data;
+ c->rpc.cred.ndata = a->ndata;
+ }
+ c->type = type;
+}
+
+int
+nfsNull(ulong tag)
+{
+ Nfs3TNull tx;
+ Nfs3RNull rx;
+
+ memset(&tx, 0, sizeof tx);
+ nfs3Call(nil, &tx.call, Nfs3CallTNull);
+
+ memset(&rx, 0, sizeof rx);
+ nfs3Call(nil, &rx.call, Nfs3CallTNull);
+
+ return sunClientRpc(nfscli, tag, &tx.call, &rx.call, nil);
+}
+
+int
+nfsGetattr(Auth *a, ulong tag, Nfs3Handle *h, Nfs3Attr *attr)
+{
+ Nfs3TGetattr tx;
+ Nfs3RGetattr rx;
+
+ memset(&tx, 0, sizeof tx);
+ nfs3Call(a, &tx.call, Nfs3CallTGetattr);
+ tx.handle = *h;
+
+ memset(&rx, 0, sizeof rx);
+ nfs3Call(a, &rx.call, Nfs3CallRGetattr);
+
+ if(sunClientRpc(nfscli, tag, &tx.call, &rx.call, nil) < 0)
+ return -1;
+ if(rx.status != Nfs3Ok){
+ nfs3Errstr(rx.status);
+ return -1;
+ }
+
+ *attr = rx.attr;
+ return 0;
+}
+
+int
+nfsAccess(Auth *a, ulong tag, Nfs3Handle *h, ulong want, ulong *got, u1int *have, Nfs3Attr *attr)
+{
+ Nfs3TAccess tx;
+ Nfs3RAccess rx;
+
+ memset(&tx, 0, sizeof tx);
+ nfs3Call(a, &tx.call, Nfs3CallTAccess);
+ tx.handle = *h;
+ tx.access = want;
+
+ memset(&rx, 0, sizeof rx);
+ nfs3Call(a, &rx.call, Nfs3CallRAccess);
+
+ if(sunClientRpc(nfscli, tag, &tx.call, &rx.call, nil) < 0)
+ return -1;
+ if(rx.status != Nfs3Ok){
+ nfs3Errstr(rx.status);
+ return -1;
+ }
+
+ *got = rx.access;
+
+ *have = rx.haveAttr;
+ if(rx.haveAttr)
+ *attr = rx.attr;
+ return 0;
+}
+
+int
+nfsMkdir(Auth *a, ulong tag, Nfs3Handle *h, char *name, Nfs3Handle *nh, ulong mode, uint gid,
+ u1int *have, Nfs3Attr *attr)
+{
+ Nfs3TMkdir tx;
+ Nfs3RMkdir rx;
+
+ memset(&tx, 0, sizeof tx);
+ nfs3Call(a, &tx.call, Nfs3CallTMkdir);
+ tx.handle = *h;
+ tx.name = name;
+ tx.attr.setMode = 1;
+ tx.attr.mode = mode;
+ tx.attr.setGid = 1;
+ tx.attr.gid = gid;
+
+ memset(&rx, 0, sizeof rx);
+ nfs3Call(a, &rx.call, Nfs3CallRMkdir);
+
+ if(sunClientRpc(nfscli, tag, &tx.call, &rx.call, nil) < 0)
+ return -1;
+ if(rx.status != Nfs3Ok){
+ nfs3Errstr(rx.status);
+ return -1;
+ }
+
+ if(!rx.haveHandle){
+ werrstr("nfs mkdir did not return handle");
+ return -1;
+ }
+ *nh = rx.handle;
+
+ *have = rx.haveAttr;
+ if(rx.haveAttr)
+ *attr = rx.attr;
+ return 0;
+}
+
+int
+nfsCreate(Auth *a, ulong tag, Nfs3Handle *h, char *name, Nfs3Handle *nh, ulong mode, uint gid,
+ u1int *have, Nfs3Attr *attr)
+{
+ Nfs3TCreate tx;
+ Nfs3RCreate rx;
+
+ memset(&tx, 0, sizeof tx);
+ nfs3Call(a, &tx.call, Nfs3CallTCreate);
+ tx.handle = *h;
+ tx.name = name;
+ tx.attr.setMode = 1;
+ tx.attr.mode = mode;
+ tx.attr.setGid = 1;
+ tx.attr.gid = gid;
+
+ memset(&rx, 0, sizeof rx);
+ nfs3Call(a, &rx.call, Nfs3CallRCreate);
+
+ if(sunClientRpc(nfscli, tag, &tx.call, &rx.call, nil) < 0)
+ return -1;
+ if(rx.status != Nfs3Ok){
+ nfs3Errstr(rx.status);
+ return -1;
+ }
+
+ if(!rx.haveHandle){
+ werrstr("nfs create did not return handle");
+ return -1;
+ }
+ *nh = rx.handle;
+
+ *have = rx.haveAttr;
+ if(rx.haveAttr)
+ *attr = rx.attr;
+ return 0;
+}
+
+int
+nfsRead(Auth *a, ulong tag, Nfs3Handle *h, u32int count, u64int offset,
+ uchar **pp, u32int *pcount, uchar **pfreeme)
+{
+ uchar *freeme;
+ Nfs3TRead tx;
+ Nfs3RRead rx;
+
+ memset(&tx, 0, sizeof tx);
+ nfs3Call(a, &tx.call, Nfs3CallTRead);
+ tx.handle = *h;
+ tx.count = count;
+ tx.offset = offset;
+
+ memset(&rx, 0, sizeof rx);
+ nfs3Call(a, &rx.call, Nfs3CallRRead);
+
+ if(sunClientRpc(nfscli, tag, &tx.call, &rx.call, &freeme) < 0)
+ return -1;
+ if(rx.status != Nfs3Ok){
+ nfs3Errstr(rx.status);
+ return -1;
+ }
+ if(rx.count != rx.ndata){
+ werrstr("nfs read returned count=%ud ndata=%ud", (uint)rx.count, (uint)rx.ndata);
+ free(freeme);
+ return -1;
+ }
+ *pfreeme = freeme;
+ *pcount = rx.count;
+ *pp = rx.data;
+ return 0;
+}
+
+int
+nfsWrite(Auth *a, ulong tag, Nfs3Handle *h, uchar *data, u32int count, u64int offset, u32int *pcount)
+{
+ Nfs3TWrite tx;
+ Nfs3RWrite rx;
+
+ memset(&tx, 0, sizeof tx);
+ nfs3Call(a, &tx.call, Nfs3CallTWrite);
+ tx.handle = *h;
+ tx.count = count;
+ tx.offset = offset;
+ tx.data = data;
+ tx.ndata = count;
+
+ memset(&rx, 0, sizeof rx);
+ nfs3Call(a, &rx.call, Nfs3CallRWrite);
+
+ if(sunClientRpc(nfscli, tag, &tx.call, &rx.call, nil) < 0)
+ return -1;
+ if(rx.status != Nfs3Ok){
+ nfs3Errstr(rx.status);
+ return -1;
+ }
+
+ *pcount = rx.count;
+ return 0;
+}
+
+int
+nfsRmdir(Auth *a, ulong tag, Nfs3Handle *h, char *name)
+{
+ Nfs3TRmdir tx;
+ Nfs3RRmdir rx;
+
+ memset(&tx, 0, sizeof tx);
+ nfs3Call(a, &tx.call, Nfs3CallTRmdir);
+ tx.handle = *h;
+ tx.name = name;
+
+ memset(&rx, 0, sizeof rx);
+ nfs3Call(a, &rx.call, Nfs3CallRRmdir);
+
+ if(sunClientRpc(nfscli, tag, &tx.call, &rx.call, nil) < 0)
+ return -1;
+ if(rx.status != Nfs3Ok){
+ nfs3Errstr(rx.status);
+ return -1;
+ }
+ return 0;
+}
+
+int
+nfsRemove(Auth *a, ulong tag, Nfs3Handle *h, char *name)
+{
+ Nfs3TRemove tx;
+ Nfs3RRemove rx;
+
+ memset(&tx, 0, sizeof tx);
+ nfs3Call(a, &tx.call, Nfs3CallTRemove);
+ tx.handle = *h;
+ tx.name = name;
+
+ memset(&rx, 0, sizeof rx);
+ nfs3Call(a, &rx.call, Nfs3CallRRemove);
+
+ if(sunClientRpc(nfscli, tag, &tx.call, &rx.call, nil) < 0)
+ return -1;
+ if(rx.status != Nfs3Ok){
+ nfs3Errstr(rx.status);
+ return -1;
+ }
+ return 0;
+}
+
+int
+nfsRename(Auth *a, ulong tag, Nfs3Handle *h, char *name, Nfs3Handle *th, char *tname)
+{
+ Nfs3TRename tx;
+ Nfs3RRename rx;
+
+ memset(&tx, 0, sizeof tx);
+ nfs3Call(a, &tx.call, Nfs3CallTRename);
+ tx.from.handle = *h;
+ tx.from.name = name;
+ tx.to.handle = *th;
+ tx.to.name = tname;
+
+ memset(&rx, 0, sizeof rx);
+ nfs3Call(a, &rx.call, Nfs3CallRRename);
+
+ if(sunClientRpc(nfscli, tag, &tx.call, &rx.call, nil) < 0)
+ return -1;
+ if(rx.status != Nfs3Ok){
+ nfs3Errstr(rx.status);
+ return -1;
+ }
+ return 0;
+}
+
+int
+nfsSetattr(Auth *a, ulong tag, Nfs3Handle *h, Nfs3SetAttr *attr)
+{
+ Nfs3TSetattr tx;
+ Nfs3RSetattr rx;
+
+ memset(&tx, 0, sizeof tx);
+ nfs3Call(a, &tx.call, Nfs3CallTSetattr);
+ tx.handle = *h;
+ tx.attr = *attr;
+
+ memset(&rx, 0, sizeof rx);
+ nfs3Call(a, &rx.call, Nfs3CallRSetattr);
+
+ if(sunClientRpc(nfscli, tag, &tx.call, &rx.call, nil) < 0)
+ return -1;
+ if(rx.status != Nfs3Ok){
+ nfs3Errstr(rx.status);
+ return -1;
+ }
+ return 0;
+}
+
+int
+nfsCommit(Auth *a, ulong tag, Nfs3Handle *h)
+{
+ Nfs3TCommit tx;
+ Nfs3RCommit rx;
+
+ memset(&tx, 0, sizeof tx);
+ nfs3Call(a, &tx.call, Nfs3CallTCommit);
+ tx.handle = *h;
+
+ memset(&rx, 0, sizeof rx);
+ nfs3Call(a, &rx.call, Nfs3CallRCommit);
+
+ if(sunClientRpc(nfscli, tag, &tx.call, &rx.call, nil) < 0)
+ return -1;
+
+ if(rx.status != Nfs3Ok){
+ nfs3Errstr(rx.status);
+ return -1;
+ }
+ return 0;
+}
+
+int
+nfsLookup(Auth *a, ulong tag, Nfs3Handle *h, char *name, Nfs3Handle *nh, u1int *have, Nfs3Attr *attr)
+{
+ Nfs3TLookup tx;
+ Nfs3RLookup rx;
+
+ memset(&tx, 0, sizeof tx);
+ nfs3Call(a, &tx.call, Nfs3CallTLookup);
+ tx.handle = *h;
+ tx.name = name;
+
+ memset(&rx, 0, sizeof rx);
+ nfs3Call(a, &rx.call, Nfs3CallRLookup);
+
+ if(sunClientRpc(nfscli, tag, &tx.call, &rx.call, nil) < 0)
+ return -1;
+
+ if(rx.status != Nfs3Ok){
+ nfs3Errstr(rx.status);
+ return -1;
+ }
+ *nh = rx.handle;
+ *have = rx.haveAttr;
+ if(rx.haveAttr)
+ *attr = rx.attr;
+ return 0;
+}
+
+int
+nfsReadDirPlus(Auth *a, ulong tag, Nfs3Handle *h, u32int count, u64int cookie, uchar **pp,
+ u32int *pcount, int (**unpack)(uchar*, uchar*, uchar**, Nfs3Entry*), uchar **pfreeme)
+{
+ Nfs3TReadDirPlus tx;
+ Nfs3RReadDirPlus rx;
+
+ memset(&tx, 0, sizeof tx);
+ nfs3Call(a, &tx.call, Nfs3CallTReadDirPlus);
+ tx.handle = *h;
+ tx.maxCount = count;
+ tx.dirCount = 1000;
+ tx.cookie = cookie;
+
+ memset(&rx, 0, sizeof rx);
+ nfs3Call(a, &rx.call, Nfs3CallRReadDirPlus);
+
+ if(sunClientRpc(nfscli, tag, &tx.call, &rx.call, pfreeme) < 0)
+ return -1;
+ if(rx.status != Nfs3Ok){
+ free(*pfreeme);
+ *pfreeme = 0;
+ nfs3Errstr(rx.status);
+ return -1;
+ }
+
+ *unpack = nfs3EntryPlusUnpack;
+ *pcount = rx.count;
+ *pp = rx.data;
+ return 0;
+}
+
+int
+nfsReadDir(Auth *a, ulong tag, Nfs3Handle *h, u32int count, u64int cookie, uchar **pp,
+ u32int *pcount, int (**unpack)(uchar*, uchar*, uchar**, Nfs3Entry*), uchar **pfreeme)
+{
+ /* BUG: try readdirplus */
+ char e[ERRMAX];
+ Nfs3TReadDir tx;
+ Nfs3RReadDir rx;
+
+ if(readplus!=-1){
+ if(nfsReadDirPlus(a, tag, h, count, cookie, pp, pcount, unpack, pfreeme) == 0){
+ readplus = 1;
+ return 0;
+ }
+ if(readplus == 0){
+ rerrstr(e, sizeof e);
+ if(strstr(e, "procedure unavailable") || strstr(e, "not supported"))
+ readplus = -1;
+ }
+ if(readplus == 0)
+ fprint(2, "readdirplus: %r\n");
+ }
+ if(readplus == 1)
+ return -1;
+
+ memset(&tx, 0, sizeof tx);
+ nfs3Call(a, &tx.call, Nfs3CallTReadDir);
+ tx.handle = *h;
+ tx.count = count;
+ tx.cookie = cookie;
+
+ memset(&rx, 0, sizeof rx);
+ nfs3Call(a, &rx.call, Nfs3CallRReadDir);
+
+ if(sunClientRpc(nfscli, tag, &tx.call, &rx.call, pfreeme) < 0)
+ return -1;
+ if(rx.status != Nfs3Ok){
+ free(*pfreeme);
+ *pfreeme = 0;
+ nfs3Errstr(rx.status);
+ return -1;
+ }
+
+ /* readplus failed but read succeeded */
+ readplus = -1;
+
+ *unpack = nfs3EntryUnpack;
+ *pcount = rx.count;
+ *pp = rx.data;
+ return 0;
+}
+
+/*
+ * name <-> int translation
+ */
+typedef struct Map Map;
+typedef struct User User;
+typedef struct Group Group;
+
+Map *map;
+Map emptymap;
+
+struct User
+{
+ char *name;
+ uint uid;
+ uint gid;
+ uint g[16];
+ uint ng;
+ uchar *auth;
+ int nauth;
+};
+
+struct Group
+{
+ char *name; /* same pos as in User struct */
+ uint gid; /* same pos as in User struct */
+};
+
+struct Map
+{
+ int nuser;
+ int ngroup;
+ User *user;
+ User **ubyname;
+ User **ubyid;
+ Group *group;
+ Group **gbyname;
+ Group **gbyid;
+};
+
+User*
+finduser(User **u, int nu, char *s)
+{
+ int lo, hi, mid, n;
+
+ hi = nu;
+ lo = 0;
+ while(hi > lo){
+ mid = (lo+hi)/2;
+ n = strcmp(u[mid]->name, s);
+ if(n == 0)
+ return u[mid];
+ if(n < 0)
+ lo = mid+1;
+ else
+ hi = mid;
+ }
+ return nil;
+}
+
+int
+strtoid(User **u, int nu, char *s, u32int *id)
+{
+ u32int x;
+ char *p;
+ User *uu;
+
+ x = strtoul(s, &p, 10);
+ if(*s != 0 && *p == 0){
+ *id = x;
+ return 0;
+ }
+
+ uu = finduser(u, nu, s);
+ if(uu == nil)
+ return -1;
+ *id = uu->uid;
+ return 0;
+}
+
+char*
+idtostr(User **u, int nu, u32int id)
+{
+ char buf[32];
+ int lo, hi, mid;
+
+ hi = nu;
+ lo = 0;
+ while(hi > lo){
+ mid = (lo+hi)/2;
+ if(u[mid]->uid == id)
+ return estrdup9p(u[mid]->name);
+ if(u[mid]->uid < id)
+ lo = mid+1;
+ else
+ hi = mid;
+ }
+ snprint(buf, sizeof buf, "%ud", id);
+ return estrdup9p(buf);
+}
+char*
+uidtostr(u32int uid)
+{
+ return idtostr(map->ubyid, map->nuser, uid);
+}
+
+char*
+gidtostr(u32int gid)
+{
+ return idtostr((User**)map->gbyid, map->ngroup, gid);
+}
+
+int
+strtouid(char *s, u32int *id)
+{
+ return strtoid(map->ubyname, map->nuser, s, id);
+}
+
+int
+strtogid(char *s, u32int *id)
+{
+ return strtoid((User**)map->gbyid, map->ngroup, s, id);
+}
+
+
+int
+idcmp(const void *va, const void *vb)
+{
+ User **a, **b;
+
+ a = (User**)va;
+ b = (User**)vb;
+ return (*a)->uid - (*b)->uid;
+}
+
+int
+namecmp(const void *va, const void *vb)
+{
+ User **a, **b;
+
+ a = (User**)va;
+ b = (User**)vb;
+ return strcmp((*a)->name, (*b)->name);
+}
+
+void
+closemap(Map *m)
+{
+ int i;
+
+ for(i=0; i<m->nuser; i++){
+ free(m->user[i].name);
+ free(m->user[i].auth);
+ }
+ for(i=0; i<m->ngroup; i++)
+ free(m->group[i].name);
+ free(m->user);
+ free(m->group);
+ free(m->ubyid);
+ free(m->ubyname);
+ free(m->gbyid);
+ free(m->gbyname);
+ free(m);
+}
+
+Map*
+readmap(char *passwd, char *group)
+{
+ char *s, *f[10], *p, *nextp, *name;
+ uchar *q, *eq;
+ int i, n, nf, line, uid, gid;
+ Biobuf *b;
+ Map *m;
+ User *u;
+ Group *g;
+ SunAuthUnix au;
+
+ m = emalloc(sizeof(Map));
+
+ if((b = Bopen(passwd, OREAD)) == nil){
+ free(m);
+ return nil;
+ }
+ line = 0;
+ for(; (s = Brdstr(b, '\n', 1)) != nil; free(s)){
+ line++;
+ if(s[0] == '#')
+ continue;
+ nf = getfields(s, f, nelem(f), 0, ":");
+ if(nf < 4)
+ continue;
+ name = f[0];
+ uid = strtol(f[2], &p, 10);
+ if(f[2][0] == 0 || *p != 0){
+ fprint(2, "%s:%d: non-numeric id in third field\n", passwd, line);
+ continue;
+ }
+ gid = strtol(f[3], &p, 10);
+ if(f[3][0] == 0 || *p != 0){
+ fprint(2, "%s:%d: non-numeric id in fourth field\n", passwd, line);
+ continue;
+ }
+ if(m->nuser%32 == 0)
+ m->user = erealloc(m->user, (m->nuser+32)*sizeof(m->user[0]));
+ u = &m->user[m->nuser++];
+ u->name = estrdup9p(name);
+ u->uid = uid;
+ u->gid = gid;
+ u->ng = 0;
+ u->auth = 0;
+ u->nauth = 0;
+ }
+ Bterm(b);
+ m->ubyname = emalloc(m->nuser*sizeof(User*));
+ m->ubyid = emalloc(m->nuser*sizeof(User*));
+ for(i=0; i<m->nuser; i++){
+ m->ubyname[i] = &m->user[i];
+ m->ubyid[i] = &m->user[i];
+ }
+ qsort(m->ubyname, m->nuser, sizeof(m->ubyname[0]), namecmp);
+ qsort(m->ubyid, m->nuser, sizeof(m->ubyid[0]), idcmp);
+
+ if((b = Bopen(group, OREAD)) == nil){
+ closemap(m);
+ return nil;
+ }
+ line = 0;
+ for(; (s = Brdstr(b, '\n', 1)) != nil; free(s)){
+ line++;
+ if(s[0] == '#')
+ continue;
+ nf = getfields(s, f, nelem(f), 0, ":");
+ if(nf < 4)
+ continue;
+ name = f[0];
+ gid = strtol(f[2], &p, 10);
+ if(f[2][0] == 0 || *p != 0){
+ fprint(2, "%s:%d: non-numeric id in third field\n", group, line);
+ continue;
+ }
+ if(m->ngroup%32 == 0)
+ m->group = erealloc(m->group, (m->ngroup+32)*sizeof(m->group[0]));
+ g = &m->group[m->ngroup++];
+ g->name = estrdup9p(name);
+ g->gid = gid;
+
+ for(p=f[3]; *p; p=nextp){
+ if((nextp = strchr(p, ',')) != nil)
+ *nextp++ = 0;
+ else
+ nextp = p+strlen(p);
+ u = finduser(m->ubyname, m->nuser, p);
+ if(u == nil){
+ if(verbose)
+ fprint(2, "%s:%d: unknown user %s\n", group, line, p);
+ continue;
+ }
+ if(u->ng >= nelem(u->g)){
+ fprint(2, "%s:%d: user %s is in too many groups; ignoring %s\n", group, line, p, name);
+ continue;
+ }
+ u->g[u->ng++] = gid;
+ }
+ }
+ Bterm(b);
+ m->gbyname = emalloc(m->ngroup*sizeof(Group*));
+ m->gbyid = emalloc(m->ngroup*sizeof(Group*));
+ for(i=0; i<m->ngroup; i++){
+ m->gbyname[i] = &m->group[i];
+ m->gbyid[i] = &m->group[i];
+ }
+ qsort(m->gbyname, m->ngroup, sizeof(m->gbyname[0]), namecmp);
+ qsort(m->gbyid, m->ngroup, sizeof(m->gbyid[0]), idcmp);
+
+ for(i=0; i<m->nuser; i++){
+ au.stamp = 0;
+ au.sysname = sys;
+ au.uid = m->user[i].uid;
+ au.gid = m->user[i].gid;
+ memmove(au.g, m->user[i].g, sizeof au.g);
+ au.ng = m->user[i].ng;
+ n = sunAuthUnixSize(&au);
+ q = emalloc(n);
+ eq = q+n;
+ m->user[i].auth = q;
+ m->user[i].nauth = n;
+ if(sunAuthUnixPack(q, eq, &q, &au) < 0 || q != eq){
+ fprint(2, "sunAuthUnixPack failed for %s\n", m->user[i].name);
+ free(m->user[i].auth);
+ m->user[i].auth = 0;
+ m->user[i].nauth = 0;
+ }
+ }
+
+ return m;
+}
+
+Auth*
+mkauth(char *user)
+{
+ Auth *a;
+ uchar *p;
+ int n;
+ SunAuthUnix au;
+ User *u;
+
+ u = finduser(map->ubyname, map->nuser, user);
+ if(u == nil || u->nauth == 0){
+ /* nobody */
+ au.stamp = 0;
+ au.uid = -1;
+ au.gid = -1;
+ au.ng = 0;
+ au.sysname = sys;
+ n = sunAuthUnixSize(&au);
+ a = emalloc(sizeof(Auth)+n);
+ a->data = (uchar*)&a[1];
+ a->ndata = n;
+ if(sunAuthUnixPack(a->data, a->data+a->ndata, &p, &au) < 0
+ || p != a->data+a->ndata){
+ free(a);
+ return nil;
+ }
+ a->ref = 1;
+if(verbose)print("creds for %s: %.*H\n", user, a->ndata, a->data);
+ return a;
+ }
+
+ a = emalloc(sizeof(Auth)+u->nauth);
+ a->data = (uchar*)&a[1];
+ a->ndata = u->nauth;
+ memmove(a->data, u->auth, a->ndata);
+ a->ref = 1;
+if(verbose)print("creds for %s: %.*H\n", user, a->ndata, a->data);
+ return a;
+}
+
+void
+freeauth(Auth *a)
+{
+ if(--a->ref > 0)
+ return;
+ free(a);
+}
+
+/*
+ * 9P server
+ */
+void
+responderrstr(Req *r)
+{
+ char e[ERRMAX];
+
+ rerrstr(e, sizeof e);
+ respond(r, e);
+}
+
+void
+fsdestroyfid(Fid *fid)
+{
+ FidAux *aux;
+
+ aux = fid->aux;
+ if(aux == nil)
+ return;
+ freeauth(aux->auth);
+ free(aux->name);
+ free(aux);
+}
+
+void
+attrToQid(Nfs3Attr *attr, Qid *qid)
+{
+ qid->path = attr->fileid;
+ qid->vers = attr->mtime.sec;
+ qid->type = 0;
+ if(attr->type == Nfs3FileDir)
+ qid->type |= QTDIR;
+}
+
+void
+attrToDir(Nfs3Attr *attr, Dir *d)
+{
+ d->mode = attr->mode & 0777;
+ if(attr->type == Nfs3FileDir)
+ d->mode |= DMDIR;
+ d->uid = uidtostr(attr->uid);
+ d->gid = gidtostr(attr->gid);
+ d->length = attr->size;
+ attrToQid(attr, &d->qid);
+ d->mtime = attr->mtime.sec;
+ d->atime = attr->atime.sec;
+ d->muid = nil;
+}
+
+void
+fsattach(Req *r)
+{
+ char *path;
+ Auth *auth;
+ FidAux *aux;
+ Nfs3Attr attr;
+ Nfs3Handle h;
+
+ path = r->ifcall.aname;
+ if(path==nil || path[0]==0)
+ path = defaultpath;
+
+ auth = mkauth(r->ifcall.uname);
+
+ if(mountMnt(auth, r->tag, path, &h) < 0
+ || nfsGetattr(auth, r->tag, &h, &attr) < 0){
+ freeauth(auth);
+ responderrstr(r);
+ return;
+ }
+
+ aux = emalloc(sizeof(FidAux));
+ aux->auth = auth;
+ aux->handle = h;
+ aux->cookie = 0;
+ aux->name = nil;
+ memset(&aux->parent, 0, sizeof aux->parent);
+ r->fid->aux = aux;
+ attrToQid(&attr, &r->fid->qid);
+ r->ofcall.qid = r->fid->qid;
+ respond(r, nil);
+}
+
+void
+fsopen(Req *r)
+{
+ FidAux *aux;
+ Nfs3Attr attr;
+ Nfs3SetAttr sa;
+ u1int have;
+ ulong a, b;
+
+ aux = r->fid->aux;
+ a = 0;
+ switch(r->ifcall.mode&OMASK){
+ case OREAD:
+ a = 0x0001;
+ break;
+ case OWRITE:
+ a = 0x0004;
+ break;
+ case ORDWR:
+ a = 0x0001|0x0004;
+ break;
+ case OEXEC:
+ a = 0x20;
+ break;
+ }
+ if(r->ifcall.mode&OTRUNC)
+ a |= 0x0004;
+
+ if(nfsAccess(aux->auth, r->tag, &aux->handle, a, &b, &have, &attr) < 0
+ || (!have && nfsGetattr(aux->auth, r->tag, &aux->handle, &attr) < 0)){
+ Error:
+ responderrstr(r);
+ return;
+ }
+ if(a != b){
+ respond(r, "permission denied");
+ return;
+ }
+ if(r->ifcall.mode&OTRUNC){
+ memset(&sa, 0, sizeof sa);
+ sa.setSize = 1;
+ if(nfsSetattr(aux->auth, r->tag, &aux->handle, &sa) < 0)
+ goto Error;
+ }
+ attrToQid(&attr, &r->fid->qid);
+ r->ofcall.qid = r->fid->qid;
+ respond(r, nil);
+}
+
+void
+fscreate(Req *r)
+{
+ FidAux *aux;
+ u1int have;
+ Nfs3Attr attr;
+ Nfs3Handle h;
+ ulong mode;
+ uint gid;
+ int (*mk)(Auth*, ulong, Nfs3Handle*, char*, Nfs3Handle*, ulong, uint, u1int*, Nfs3Attr*);
+
+ aux = r->fid->aux;
+
+ /*
+ * Plan 9 has no umask, so let's use the
+ * parent directory bits like Plan 9 does.
+ * What the heck, let's inherit the group too.
+ * (Unix will let us set the group to anything
+ * since we're the owner!)
+ */
+ if(nfsGetattr(aux->auth, r->tag, &aux->handle, &attr) < 0){
+ responderrstr(r);
+ return;
+ }
+ mode = r->ifcall.perm&0777;
+ if(r->ifcall.perm&DMDIR)
+ mode &= (attr.mode&0666) | ~0666;
+ else
+ mode &= (attr.mode&0777) | ~0777;
+ gid = attr.gid;
+
+ if(r->ifcall.perm&DMDIR)
+ mk = nfsMkdir;
+ else
+ mk = nfsCreate;
+
+ if((*mk)(aux->auth, r->tag, &aux->handle, r->ifcall.name, &h, mode, gid, &have, &attr) < 0
+ || (!have && nfsGetattr(aux->auth, r->tag, &h, &attr) < 0)){
+ responderrstr(r);
+ return;
+ }
+ attrToQid(&attr, &r->fid->qid);
+ aux->parent = aux->handle;
+ aux->handle = h;
+ free(aux->name);
+ aux->name = estrdup9p(r->ifcall.name);
+ r->ofcall.qid = r->fid->qid;
+ respond(r, nil);
+}
+
+void
+fsreaddir(Req *r)
+{
+ FidAux *aux;
+ uchar *p, *freeme, *ep, *p9, *ep9;
+ char *s;
+ uint count;
+ int n, (*unpack)(uchar*, uchar*, uchar**, Nfs3Entry*);
+ Nfs3Entry e;
+ u64int cookie;
+ Dir d;
+
+ aux = r->fid->aux;
+ /*
+ * r->ifcall.count seems a reasonable estimate to
+ * how much NFS entry data we want. is it?
+ */
+ if(r->ifcall.offset)
+ cookie = aux->cookie;
+ else
+ cookie = 0;
+ if(nfsReadDir(aux->auth, r->tag, &aux->handle, r->ifcall.count, cookie,
+ &p, &count, &unpack, &freeme) < 0){
+ responderrstr(r);
+ return;
+ }
+ ep = p+count;
+
+ p9 = (uchar*)r->ofcall.data;
+ ep9 = p9+r->ifcall.count;
+
+ /*
+ * BUG: Issue all of the stat requests in parallel.
+ */
+ while(p < ep && p9 < ep9){
+ if((*unpack)(p, ep, &p, &e) < 0)
+ break;
+ aux->cookie = e.cookie;
+ if(strcmp(e.name, ".") == 0 || strcmp(e.name, "..") == 0)
+ continue;
+ for(s=e.name; (uchar)*s >= ' '; s++)
+ ;
+ if(*s != 0) /* bad character in name */
+ continue;
+ if(!e.haveAttr && !e.haveHandle)
+ if(nfsLookup(aux->auth, r->tag, &aux->handle, e.name, &e.handle, &e.haveAttr, &e.attr) < 0)
+ continue;
+ if(!e.haveAttr)
+ if(nfsGetattr(aux->auth, r->tag, &e.handle, &e.attr) < 0)
+ continue;
+ memset(&d, 0, sizeof d);
+ attrToDir(&e.attr, &d);
+ d.name = e.name;
+ if((n = convD2M(&d, p9, ep9-p9)) <= BIT16SZ)
+ break;
+ p9 += n;
+ }
+ free(freeme);
+ r->ofcall.count = p9 - (uchar*)r->ofcall.data;
+ respond(r, nil);
+}
+
+void
+fsread(Req *r)
+{
+ uchar *p, *freeme;
+ uint count;
+ FidAux *aux;
+
+ if(r->fid->qid.type&QTDIR){
+ fsreaddir(r);
+ return;
+ }
+
+ aux = r->fid->aux;
+ if(nfsRead(aux->auth, r->tag, &aux->handle, r->ifcall.count, r->ifcall.offset, &p, &count, &freeme) < 0){
+ responderrstr(r);
+ return;
+ }
+ r->ofcall.data = (char*)p;
+ r->ofcall.count = count;
+ respond(r, nil);
+ free(freeme);
+}
+
+void
+fswrite(Req *r)
+{
+ uint count;
+ FidAux *aux;
+
+ aux = r->fid->aux;
+ if(nfsWrite(aux->auth, r->tag, &aux->handle, (uchar*)r->ifcall.data, r->ifcall.count, r->ifcall.offset, &count) < 0){
+ responderrstr(r);
+ return;
+ }
+ r->ofcall.count = count;
+ respond(r, nil);
+}
+
+void
+fsremove(Req *r)
+{
+ int n;
+ FidAux *aux;
+
+ aux = r->fid->aux;
+ if(aux->name == nil){
+ respond(r, "nfs3client botch -- don't know parent handle in remove");
+ return;
+ }
+ if(r->fid->qid.type&QTDIR)
+ n = nfsRmdir(aux->auth, r->tag, &aux->parent, aux->name);
+ else
+ n = nfsRemove(aux->auth, r->tag, &aux->parent, aux->name);
+ if(n < 0){
+ responderrstr(r);
+ return;
+ }
+ respond(r, nil);
+}
+
+void
+fsstat(Req *r)
+{
+ FidAux *aux;
+ Nfs3Attr attr;
+
+ aux = r->fid->aux;
+ if(nfsGetattr(aux->auth, r->tag, &aux->handle, &attr) < 0){
+ responderrstr(r);
+ return;
+ }
+ memset(&r->d, 0, sizeof r->d);
+ attrToDir(&attr, &r->d);
+ r->d.name = estrdup9p(aux->name ? aux->name : "???");
+ respond(r, nil);
+}
+
+void
+fswstat(Req *r)
+{
+ int op, sync;
+ FidAux *aux;
+ Nfs3SetAttr attr;
+
+ memset(&attr, 0, sizeof attr);
+ aux = r->fid->aux;
+
+ /* Fill out stat first to catch errors */
+ op = 0;
+ sync = 1;
+ if(~r->d.mode){
+ if(r->d.mode&(DMAPPEND|DMEXCL)){
+ respond(r, "wstat -- DMAPPEND and DMEXCL bits not supported");
+ return;
+ }
+ op = 1;
+ sync = 0;
+ attr.setMode = 1;
+ attr.mode = r->d.mode & 0777;
+ }
+ if(r->d.uid && r->d.uid[0]){
+ attr.setUid = 1;
+ if(strtouid(r->d.uid, &attr.uid) < 0){
+ respond(r, "wstat -- unknown uid");
+ return;
+ }
+ op = 1;
+ sync = 0;
+ }
+ if(r->d.gid && r->d.gid[0]){
+ attr.setGid = 1;
+ if(strtogid(r->d.gid, &attr.gid) < 0){
+ respond(r, "wstat -- unknown gid");
+ return;
+ }
+ op = 1;
+ sync = 0;
+ }
+ if(~r->d.length){
+ attr.setSize = 1;
+ attr.size = r->d.length;
+ op = 1;
+ sync = 0;
+ }
+ if(~r->d.mtime){
+ attr.setMtime = Nfs3SetTimeClient;
+ attr.mtime.sec = r->d.mtime;
+ op = 1;
+ sync = 0;
+ }
+ if(~r->d.atime){
+ attr.setAtime = Nfs3SetTimeClient;
+ attr.atime.sec = r->d.atime;
+ op = 1;
+ sync = 0;
+ }
+
+ /* Try rename first because it's more likely to fail (?) */
+ if(r->d.name && r->d.name[0]){
+ if(aux->name == nil){
+ respond(r, "nfsclient botch -- don't know parent handle in rename");
+ return;
+ }
+ if(nfsRename(aux->auth, r->tag, &aux->parent, aux->name, &aux->parent, r->d.name) < 0){
+ responderrstr(r);
+ return;
+ }
+ free(aux->name);
+ aux->name = estrdup9p(r->d.name);
+ sync = 0;
+ }
+
+ /*
+ * Now we have a problem. The rename succeeded
+ * but the setattr could fail. Sic transit atomicity.
+ */
+ if(op){
+ if(nfsSetattr(aux->auth, r->tag, &aux->handle, &attr) < 0){
+ responderrstr(r);
+ return;
+ }
+ }
+
+ if(sync){
+ /* NFS commit */
+ if(nfsCommit(aux->auth, r->tag, &aux->handle) < 0){
+ responderrstr(r);
+ return;
+ }
+ }
+
+ respond(r, nil);
+}
+
+char*
+fswalk1(Fid *fid, char *name, void *v)
+{
+ u1int have;
+ ulong tag;
+ FidAux *aux;
+ Nfs3Attr attr;
+ Nfs3Handle h;
+
+ tag = *(ulong*)v;
+ aux = fid->aux;
+
+ if(nfsLookup(aux->auth, tag, &aux->handle, name, &h, &have, &attr) < 0
+ || (!have && nfsGetattr(aux->auth, tag, &h, &attr) < 0)){
+ rerrstr(aux->err, sizeof aux->err);
+ return aux->err;
+ }
+
+ aux->parent = aux->handle;
+ aux->handle = h;
+ free(aux->name);
+ if(strcmp(name, "..") == 0)
+ aux->name = nil;
+ else
+ aux->name = estrdup9p(name);
+ attrToQid(&attr, &fid->qid);
+ return nil;
+}
+
+char*
+fsclone(Fid *fid, Fid *newfid, void*)
+{
+ FidAux *a, *na;
+
+ a = fid->aux;
+ na = emalloc9p(sizeof(FidAux));
+ *na = *a;
+ if(na->name)
+ na->name = estrdup9p(na->name);
+ newfid->aux = na;
+ if(na->auth)
+ na->auth->ref++;
+ return nil;
+}
+
+void
+fswalk(Req *r)
+{
+ walkandclone(r, fswalk1, fsclone, &r->tag);
+}
+
+void
+fsflush(Req *r)
+{
+ Req *or;
+
+ /*
+ * Send on the flush channel(s).
+ * The library will make sure the response
+ * is delayed as necessary.
+ */
+ or = r->oldreq;
+ if(nfscli)
+ sendul(nfscli->flushchan, (ulong)or->tag);
+ if(mntcli)
+ sendul(mntcli->flushchan, (ulong)or->tag);
+ respond(r, nil);
+}
+
+void
+fsdispatch(void *v)
+{
+ Req *r;
+
+ r = v;
+ switch(r->ifcall.type){
+ default: respond(r, "unknown type"); break;
+ case Tattach: fsattach(r); break;
+ case Topen: fsopen(r); break;
+ case Tcreate: fscreate(r); break;
+ case Tread: fsread(r); break;
+ case Twrite: fswrite(r); break;
+ case Tremove: fsremove(r); break;
+ case Tflush: fsflush(r); break;
+ case Tstat: fsstat(r); break;
+ case Twstat: fswstat(r); break;
+ case Twalk: fswalk(r); break;
+ }
+}
+
+void
+fsthread(void*)
+{
+ Req *r;
+
+ while((r = recvp(fschan)) != nil)
+ threadcreate(fsdispatch, r, SunStackSize);
+}
+
+void
+fssend(Req *r)
+{
+ sendp(fschan, r);
+}
+
+void
+fsdie(Srv*)
+{
+ threadexitsall(nil);
+}
+
+Srv fs =
+{
+.destroyfid = fsdestroyfid,
+.attach= fssend,
+.open= fssend,
+.create= fssend,
+.read= fssend,
+.write= fssend,
+.remove= fssend,
+.flush= fssend,
+.stat= fssend,
+.wstat= fssend,
+.walk= fssend,
+.end= fsdie
+};
+
+void
+usage(void)
+{
+ fprint(2, "usage: nfs [-DRv] [-p perm] [-s srvname] [-u passwd group] addr [addr]\n");
+ fprint(2, "\taddr - address of portmapper server\n");
+ fprint(2, "\taddr addr - addresses of mount server and nfs server\n");
+ exits("usage");
+}
+
+char*
+netchangeport(char *addr, uint port, char *buf, uint nbuf)
+{
+ char *r;
+
+ strecpy(buf, buf+nbuf, addr);
+ r = strrchr(buf, '!');
+ if(r == nil)
+ return nil;
+ r++;
+ seprint(r, buf+nbuf, "%ud", port);
+ return buf;
+}
+
+char mbuf[256], nbuf[256];
+char *mountaddr, *nfsaddr;
+Channel *csync;
+int chattyrpc;
+void dialproc(void*);
+
+void
+threadmain(int argc, char **argv)
+{
+ char *srvname, *passwd, *group, *addr, *p;
+ SunClient *cli;
+ int proto;
+ uint mport, nport;
+ ulong perm;
+ Dir d;
+
+ perm = 0600;
+ passwd = nil;
+ group = nil;
+ srvname = nil;
+ sys = sysname();
+ if(sys == nil)
+ sys = "plan9";
+ ARGBEGIN{
+ default:
+ usage();
+ case 'D':
+ chatty9p++;
+ break;
+ case 'R':
+ chattyrpc++;
+ break;
+ case 'p':
+ perm = strtol(EARGF(usage()), &p, 8);
+ if(perm==0 || *p != 0)
+ usage();
+ break;
+ case 's':
+ srvname = EARGF(usage());
+ break;
+ case 'u':
+ passwd = EARGF(usage());
+ group = EARGF(usage());
+ break;
+ case 'v':
+ verbose++;
+ break;
+ }ARGEND
+
+ if(argc != 1 && argc != 2)
+ usage();
+
+ if(srvname == nil)
+ srvname = argv[0];
+
+ fmtinstall('B', sunRpcFmt);
+ fmtinstall('C', sunCallFmt);
+ fmtinstall('H', encodefmt);
+ sunFmtInstall(&portProg);
+ sunFmtInstall(&nfs3Prog);
+ sunFmtInstall(&nfsMount3Prog);
+
+ if(passwd && (map = readmap(passwd, group)) == nil)
+ fprint(2, "warning: reading %s and %s: %r\n", passwd, group);
+
+ if(map == nil)
+ map = &emptymap;
+
+ if(argc == 1){
+ addr = netmkaddr(argv[0], "udp", "portmap");
+ if((cli = sunDial(addr)) == nil)
+ sysfatal("dial %s: %r", addr);
+ cli->chatty = chattyrpc;
+ sunClientProg(cli, &portProg);
+ if(strstr(addr, "udp!"))
+ proto = PortProtoUdp;
+ else
+ proto = PortProtoTcp;
+ if(getport(cli, NfsMount3Program, NfsMount3Version, proto, &mport) < 0)
+ sysfatal("lookup mount program port: %r");
+ if(getport(cli, Nfs3Program, Nfs3Version, proto, &nport) < 0)
+ sysfatal("lookup nfs program port: %r");
+ sunClientClose(cli);
+ mountaddr = netchangeport(addr, mport, mbuf, sizeof mbuf);
+ nfsaddr = netchangeport(addr, nport, nbuf, sizeof nbuf);
+ strcat(mountaddr, "!r");
+ strcat(nfsaddr, "!r");
+ if(verbose)
+ fprint(2, "nfs %s %s\n", mountaddr, nfsaddr);
+ }else{
+ mountaddr = argv[0];
+ nfsaddr = argv[1];
+ }
+
+ /* have to dial in another proc because it creates threads */
+ csync = chancreate(sizeof(void*), 0);
+ proccreate(dialproc, nil, SunStackSize);
+ recvp(csync);
+
+ threadpostmountsrv(&fs, srvname, nil, 0);
+ if(perm != 0600){
+ p = smprint("/srv/%s", srvname);
+ if(p){
+ nulldir(&d);
+ d.mode = perm;
+ dirwstat(p, &d);
+ }
+ }
+ threadexits(nil);
+}
+
+void
+dialproc(void*)
+{
+ rfork(RFNAMEG);
+ rfork(RFNOTEG);
+ if((mntcli = sunDial(mountaddr)) == nil)
+ sysfatal("dial mount program at %s: %r", mountaddr);
+ mntcli->chatty = chattyrpc;
+ sunClientProg(mntcli, &nfsMount3Prog);
+ if(mountNull(0) < 0)
+ sysfatal("execute nop with mnt server at %s: %r", mountaddr);
+
+ if((nfscli = sunDial(nfsaddr)) == nil)
+ sysfatal("dial nfs program at %s: %r", nfsaddr);
+ nfscli->chatty = chattyrpc;
+ sunClientProg(nfscli, &nfs3Prog);
+ if(nfsNull(0) < 0)
+ sysfatal("execute nop with nfs server at %s: %r", nfsaddr);
+
+ fschan = chancreate(sizeof(Req*), 0);
+ threadcreate(fsthread, nil, SunStackSize);
+ sendp(csync, 0);
+}