/* 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);
}