Subversion Repositories planix.SVN

Rev

Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

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