]> git.saurik.com Git - apple/xnu.git/blobdiff - bsd/nfs/nfs4_subs.c
xnu-2422.90.20.tar.gz
[apple/xnu.git] / bsd / nfs / nfs4_subs.c
index 6b1786cda4fe82cb0ec59385673d51c275ecddd7..50c657b94457a7db607cea89869567f3fdb317bd 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2006-2009 Apple Inc. All rights reserved.
+ * Copyright (c) 2006-2011 Apple Inc. All rights reserved.
  *
  * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
  * 
 #include <netinet/in.h>
 #include <net/kpi_interface.h>
 
+/*
+ * NFS_MAX_WHO is the maximum length of a string representation used
+ * in as an ace who, owner, or group. There is no explicit limit in the
+ * protocol, however the kauth routines have a limit of MAPATHLEN for
+ * strings including the trailing null character, so we impose that
+ * limit. This should be changed if kauth routines change.
+ *
+ * We also want some reasonable maximum, as 32 bits worth of string length
+ * is liable to cause problems. At the very least this limit must guarantee 
+ * that any express that contains the 32 bit length from off the wire used in
+ * allocations does not overflow.
+ */
+#define        NFS_MAX_WHO     MAXPATHLEN
 
 /*
  * Create the unique client ID to use for this mount.
  *
  * In an attempt to differentiate mounts we include the mntfromname and mntonname
  * strings to the client ID (as long as they fit).  We also make sure that the
- * value does not conflict with any existing values in use.
+ * value does not conflict with any existing values in use (changing the unique ID).
+ *
+ * Note that info such as the server's address may change over the lifetime of the
+ * mount.  But the client ID will not be updated because we don't want it changing
+ * simply because we switched to a different server address.
  */
 int
 nfs4_init_clientid(struct nfsmount *nmp)
@@ -120,7 +137,7 @@ nfs4_init_clientid(struct nfsmount *nmp)
                return (ENOMEM);
 
        vsfs = vfs_statfs(nmp->nm_mountp);
-       saddr = mbuf_data(nmp->nm_nam);
+       saddr = nmp->nm_saddr;
        ncip->nci_idlen = sizeof(uint32_t) + sizeof(en0addr) + saddr->sa_len +
                strlen(vsfs->f_mntfromname) + 1 + strlen(vsfs->f_mntonname) + 1;
        if (ncip->nci_idlen > NFS4_OPAQUE_LIMIT)
@@ -199,10 +216,12 @@ nfs4_setclientid(struct nfsmount *nmp)
        thread_t thd;
        kauth_cred_t cred;
        struct nfsm_chain nmreq, nmrep;
-       struct sockaddr_in sin;
-       uint8_t *addr;
-       char raddr[32];
-       int ralen = 0;
+       struct sockaddr_storage ss;
+       void *sinaddr = NULL;
+       char raddr[MAX_IPv6_STR_LEN];
+       char uaddr[MAX_IPv6_STR_LEN+16];
+       int ualen = 0;
+       in_port_t port;
 
        thd = current_thread();
        cred = IS_VALID_CRED(nmp->nm_mcred) ? nmp->nm_mcred : vfs_context_ucred(vfs_context_kernel());
@@ -224,26 +243,35 @@ nfs4_setclientid(struct nfsmount *nmp)
        nfsm_chain_add_64(error, &nmreq, nmp->nm_mounttime);
        nfsm_chain_add_32(error, &nmreq, nmp->nm_longid->nci_idlen);
        nfsm_chain_add_opaque(error, &nmreq, nmp->nm_longid->nci_id, nmp->nm_longid->nci_idlen);
+       nfsmout_if(error);
        /* cb_client4      callback; */
-       if (nmp->nm_cbid && nfs4_cb_port &&
-           !(error = sock_getsockname(nmp->nm_so, (struct sockaddr*)&sin, sizeof(sin)))) {
-               /* assemble r_addr = h1.h2.h3.h4.p1.p2 */
-               /* h = source address of nmp->nm_so */
-               /* p = nfs4_cb_port */
-               addr = (uint8_t*)&sin.sin_addr.s_addr;
-               ralen = snprintf(raddr, sizeof(raddr), "%d.%d.%d.%d.%d.%d", 
-                               addr[0], addr[1], addr[2], addr[3],
-                               ((nfs4_cb_port >> 8) & 0xff),
-                               (nfs4_cb_port & 0xff));
-               /* make sure it fit, give up if it didn't */
-               if (ralen >= (int)sizeof(raddr))
-                       ralen = 0;
-       }
-       if (ralen > 0) {
+       if (!NMFLAG(nmp, NOCALLBACK) && nmp->nm_cbid && nfs4_cb_port &&
+           !sock_getsockname(nmp->nm_nso->nso_so, (struct sockaddr*)&ss, sizeof(ss))) {
+               if (ss.ss_family == AF_INET) {
+                       sinaddr = &((struct sockaddr_in*)&ss)->sin_addr;
+                       port = nfs4_cb_port;
+               } else if (ss.ss_family == AF_INET6) {
+                       sinaddr = &((struct sockaddr_in6*)&ss)->sin6_addr;
+                       port = nfs4_cb_port6;
+               }
+               if (sinaddr && port && (inet_ntop(ss.ss_family, sinaddr, raddr, sizeof(raddr)) == raddr)) {
+                       /* assemble r_addr = universal address (nmp->nm_nso->nso_so source IP addr + port) */
+                       ualen = snprintf(uaddr, sizeof(uaddr), "%s.%d.%d", raddr,
+                                       ((port >> 8) & 0xff),
+                                       (port & 0xff));
+                       /* make sure it fit, give up if it didn't */
+                       if (ualen >= (int)sizeof(uaddr))
+                               ualen = 0;
+               }
+       }
+       if (ualen > 0) {
                /* add callback info */
                nfsm_chain_add_32(error, &nmreq, NFS4_CALLBACK_PROG); /* callback program */
-               nfsm_chain_add_string(error, &nmreq, "tcp", 3); /* callback r_netid */
-               nfsm_chain_add_string(error, &nmreq, raddr, ralen); /* callback r_addr */
+               if (ss.ss_family == AF_INET)
+                       nfsm_chain_add_string(error, &nmreq, "tcp", 3); /* callback r_netid */
+               else if (ss.ss_family == AF_INET6)
+                       nfsm_chain_add_string(error, &nmreq, "tcp6", 4); /* callback r_netid */
+               nfsm_chain_add_string(error, &nmreq, uaddr, ualen); /* callback r_addr */
                nfsm_chain_add_32(error, &nmreq, nmp->nm_cbid); /* callback_ident */
        } else {
                /* don't provide valid callback info */
@@ -255,9 +283,11 @@ nfs4_setclientid(struct nfsmount *nmp)
        nfsm_chain_build_done(error, &nmreq);
        nfsm_assert(error, (numops == 0), EPROTO);
        nfsmout_if(error);
-       error = nfs_request2(NULL, nmp->nm_mountp, &nmreq, NFSPROC4_COMPOUND, thd, cred, R_SETUP, &nmrep, &xid, &status);
+       error = nfs_request2(NULL, nmp->nm_mountp, &nmreq, NFSPROC4_COMPOUND, thd, cred, NULL, R_SETUP, &nmrep, &xid, &status);
        nfsm_chain_skip_tag(error, &nmrep);
        nfsm_chain_get_32(error, &nmrep, numops);
+       if (!error && (numops != 1) && status)
+               error = status;
        nfsm_chain_op_check(error, &nmrep, NFS_OP_SETCLIENTID);
        if (error == NFSERR_CLID_INUSE)
                printf("nfs4_setclientid: client ID in use?\n");
@@ -267,43 +297,57 @@ nfs4_setclientid(struct nfsmount *nmp)
        nfsm_chain_cleanup(&nmreq);
        nfsm_chain_cleanup(&nmrep);
 
-       // SETCLIENTID_CONFIRM, PUTFH, GETATTR(FS)
-       numops = nmp->nm_dnp ? 3 : 1;
-       nfsm_chain_build_alloc_init(error, &nmreq, 28 * NFSX_UNSIGNED);
+       // SETCLIENTID_CONFIRM
+       numops = 1;
+       nfsm_chain_build_alloc_init(error, &nmreq, 15 * NFSX_UNSIGNED);
        nfsm_chain_add_compound_header(error, &nmreq, "setclid_conf", numops);
        numops--;
        nfsm_chain_add_32(error, &nmreq, NFS_OP_SETCLIENTID_CONFIRM);
        nfsm_chain_add_64(error, &nmreq, nmp->nm_clientid);
        nfsm_chain_add_64(error, &nmreq, verifier);
-       if (nmp->nm_dnp) {
-               /* refresh fs attributes too */
-               numops--;
-               nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTFH);
-               nfsm_chain_add_fh(error, &nmreq, nmp->nm_vers, nmp->nm_dnp->n_fhp, nmp->nm_dnp->n_fhsize);
-               numops--;
-               nfsm_chain_add_32(error, &nmreq, NFS_OP_GETATTR);
-               NFS_CLEAR_ATTRIBUTES(bitmap);
-               NFS4_PER_FS_ATTRIBUTES(bitmap);
-               nfsm_chain_add_bitmap(error, &nmreq, bitmap, NFS_ATTR_BITMAP_LEN);
-       }
        nfsm_chain_build_done(error, &nmreq);
        nfsm_assert(error, (numops == 0), EPROTO);
        nfsmout_if(error);
-       error = nfs_request2(NULL, nmp->nm_mountp, &nmreq, NFSPROC4_COMPOUND, thd, cred, R_SETUP, &nmrep, &xid, &status);
+       error = nfs_request2(NULL, nmp->nm_mountp, &nmreq, NFSPROC4_COMPOUND, thd, cred, NULL, R_SETUP, &nmrep, &xid, &status);
        nfsm_chain_skip_tag(error, &nmrep);
        nfsm_chain_get_32(error, &nmrep, numops);
        nfsm_chain_op_check(error, &nmrep, NFS_OP_SETCLIENTID_CONFIRM);
        if (error)
                printf("nfs4_setclientid: confirm error %d\n", error);
-       if (nmp->nm_dnp) {
-               nfsm_chain_op_check(error, &nmrep, NFS_OP_PUTFH);
-               nfsm_chain_op_check(error, &nmrep, NFS_OP_GETATTR);
-               nfsmout_if(error);
-               lck_mtx_lock(&nmp->nm_lock);
-               error = nfs4_parsefattr(&nmrep, &nmp->nm_fsattr, NULL, NULL, NULL);
-               lck_mtx_unlock(&nmp->nm_lock);
-       }
+       lck_mtx_lock(&nmp->nm_lock);
+       if (!error)
+               nmp->nm_state |= NFSSTA_CLIENTID;
+       lck_mtx_unlock(&nmp->nm_lock);
+
+       nfsmout_if(error || !nmp->nm_dnp);
 
+       /* take the opportunity to refresh fs attributes too */
+       // PUTFH, GETATTR(FS)
+       numops = 2;
+       nfsm_chain_build_alloc_init(error, &nmreq, 23 * NFSX_UNSIGNED);
+       nfsm_chain_add_compound_header(error, &nmreq, "setclid_attr", numops);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTFH);
+       nfsm_chain_add_fh(error, &nmreq, nmp->nm_vers, nmp->nm_dnp->n_fhp, nmp->nm_dnp->n_fhsize);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_GETATTR);
+       NFS_CLEAR_ATTRIBUTES(bitmap);
+       NFS4_PER_FS_ATTRIBUTES(bitmap);
+       nfsm_chain_add_bitmap(error, &nmreq, bitmap, NFS_ATTR_BITMAP_LEN);
+       nfsm_chain_build_done(error, &nmreq);
+       nfsm_assert(error, (numops == 0), EPROTO);
+       nfsmout_if(error);
+       error = nfs_request2(NULL, nmp->nm_mountp, &nmreq, NFSPROC4_COMPOUND, thd, cred, NULL, R_SETUP, &nmrep, &xid, &status);
+       nfsm_chain_skip_tag(error, &nmrep);
+       nfsm_chain_get_32(error, &nmrep, numops);
+       lck_mtx_lock(&nmp->nm_lock);
+       nfsm_chain_op_check(error, &nmrep, NFS_OP_PUTFH);
+       nfsm_chain_op_check(error, &nmrep, NFS_OP_GETATTR);
+       if (!error)
+               error = nfs4_parsefattr(&nmrep, &nmp->nm_fsattr, NULL, NULL, NULL, NULL);
+       lck_mtx_unlock(&nmp->nm_lock);
+       if (error)  /* ignore any error from the getattr */
+               error = 0;
 nfsmout:
        nfsm_chain_cleanup(&nmreq);
        nfsm_chain_cleanup(&nmrep);
@@ -341,7 +385,7 @@ nfs4_renew(struct nfsmount *nmp, int rpcflag)
        nfsm_assert(error, (numops == 0), EPROTO);
        nfsmout_if(error);
        error = nfs_request2(NULL, nmp->nm_mountp, &nmreq, NFSPROC4_COMPOUND,
-                       current_thread(), cred, rpcflag, &nmrep, &xid, &status);
+                       current_thread(), cred, NULL, rpcflag, &nmrep, &xid, &status);
        nfsm_chain_skip_tag(error, &nmrep);
        nfsm_chain_get_32(error, &nmrep, numops);
        nfsm_chain_op_check(error, &nmrep, NFS_OP_RENEW);
@@ -381,8 +425,7 @@ out:
        if (error && (error != ETIMEDOUT) &&
            (nmp->nm_clientid == clientid) && !(nmp->nm_state & NFSSTA_RECOVER)) {
                printf("nfs4_renew_timer: error %d, initiating recovery\n", error);
-               nmp->nm_state |= NFSSTA_RECOVER;
-               nfs_mount_sock_thread_wake(nmp);
+               nfs_need_recover(nmp, error);
        }
 
        interval = nmp->nm_fsattr.nfsa_lease / (error ? 4 : 2);
@@ -392,6 +435,1034 @@ out:
        nfs_interval_timer_start(nmp->nm_renew_timer, interval * 1000);
 }
 
+/*
+ * get the list of supported security flavors
+ *
+ * How we get them depends on what args we are given:
+ *
+ * FH?   Name?  Action
+ * ----- -----  ------
+ * YES   YES    Use the fh and name provided
+ * YES   NO     4.1-only just use the fh provided
+ * NO    YES    Use the node's (or root) fh and the name provided
+ * NO    NO     Use the node's parent and the node's name (4.1 will just use node's fh)
+ */
+int
+nfs4_secinfo_rpc(struct nfsmount *nmp, struct nfsreq_secinfo_args *siap, kauth_cred_t cred, uint32_t *sec, int *seccountp)
+{
+       int error = 0, status, nfsvers, numops, namelen, fhsize;
+       vnode_t dvp = NULLVP;
+       nfsnode_t np, dnp;
+       u_char *fhp;
+       const char *vname = NULL, *name;
+       uint64_t xid;
+       struct nfsm_chain nmreq, nmrep;
+
+       *seccountp = 0;
+       if (!nmp)
+               return (ENXIO);
+       nfsvers = nmp->nm_vers;
+       np = siap->rsia_np;
+
+       nfsm_chain_null(&nmreq);
+       nfsm_chain_null(&nmrep);
+
+       fhp = siap->rsia_fh;
+       fhsize = fhp ? siap->rsia_fhsize : 0;
+       name = siap->rsia_name;
+       namelen = name ? siap->rsia_namelen : 0;
+       if (name && !namelen)
+               namelen = strlen(name);
+       if (!fhp && name) {
+               if (!np)  /* use PUTROOTFH */
+                       goto gotargs;
+               fhp = np->n_fhp;
+               fhsize = np->n_fhsize;
+       }
+       if (fhp && name)
+               goto gotargs;
+
+       if (!np)
+               return (EIO);
+       nfs_node_lock_force(np);
+       if ((vnode_vtype(NFSTOV(np)) != VDIR) && np->n_sillyrename) {
+               /*
+                * The node's been sillyrenamed, so we need to use
+                * the sillyrename directory/name to do the open.
+                */
+               struct nfs_sillyrename *nsp = np->n_sillyrename;
+               dnp = nsp->nsr_dnp;
+               dvp = NFSTOV(dnp);
+               if ((error = vnode_get(dvp))) {
+                       nfs_node_unlock(np);
+                       goto nfsmout;
+               }
+               fhp = dnp->n_fhp;
+               fhsize = dnp->n_fhsize;
+               name = nsp->nsr_name;
+               namelen = nsp->nsr_namlen;
+       } else {
+               /*
+                * [sigh] We can't trust VFS to get the parent right for named
+                * attribute nodes.  (It likes to reparent the nodes after we've
+                * created them.)  Luckily we can probably get the right parent
+                * from the n_parent we have stashed away.
+                */
+               if ((np->n_vattr.nva_flags & NFS_FFLAG_IS_ATTR) &&
+                   (((dvp = np->n_parent)) && (error = vnode_get(dvp))))
+                       dvp = NULL;
+               if (!dvp)
+                       dvp = vnode_getparent(NFSTOV(np));
+               vname = vnode_getname(NFSTOV(np));
+               if (!dvp || !vname) {
+                       if (!error)
+                               error = EIO;
+                       nfs_node_unlock(np);
+                       goto nfsmout;
+               }
+               dnp = VTONFS(dvp);
+               fhp = dnp->n_fhp;
+               fhsize = dnp->n_fhsize;
+               name = vname;
+               namelen = strnlen(vname, MAXPATHLEN);
+       }
+       nfs_node_unlock(np);
+
+gotargs:
+       // PUT(ROOT)FH + SECINFO
+       numops = 2;
+       nfsm_chain_build_alloc_init(error, &nmreq,
+               4 * NFSX_UNSIGNED + NFSX_FH(nfsvers) + nfsm_rndup(namelen));
+       nfsm_chain_add_compound_header(error, &nmreq, "secinfo", numops);
+       numops--;
+       if (fhp) {
+               nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTFH);
+               nfsm_chain_add_fh(error, &nmreq, nfsvers, fhp, fhsize);
+       } else {
+               nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTROOTFH);
+       }
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_SECINFO);
+       nfsm_chain_add_name(error, &nmreq, name, namelen, nmp);
+       nfsm_chain_build_done(error, &nmreq);
+       nfsm_assert(error, (numops == 0), EPROTO);
+       nfsmout_if(error);
+       error = nfs_request2(np, nmp->nm_mountp, &nmreq, NFSPROC4_COMPOUND,
+                       current_thread(), cred, NULL, 0, &nmrep, &xid, &status);
+       nfsm_chain_skip_tag(error, &nmrep);
+       nfsm_chain_get_32(error, &nmrep, numops);
+       nfsm_chain_op_check(error, &nmrep, fhp ? NFS_OP_PUTFH : NFS_OP_PUTROOTFH);
+       nfsm_chain_op_check(error, &nmrep, NFS_OP_SECINFO);
+       nfsmout_if(error);
+       error = nfsm_chain_get_secinfo(&nmrep, sec, seccountp);
+nfsmout:
+       nfsm_chain_cleanup(&nmreq);
+       nfsm_chain_cleanup(&nmrep);
+       if (vname)
+               vnode_putname(vname);
+       if (dvp != NULLVP)
+               vnode_put(dvp);
+       return (error);
+}
+
+/*
+ * Parse an NFSv4 SECINFO array to an array of pseudo flavors.
+ * (Note: also works for MOUNTv3 security arrays.)
+ */
+int
+nfsm_chain_get_secinfo(struct nfsm_chain *nmc, uint32_t *sec, int *seccountp)
+{
+       int error = 0, secmax, seccount, srvcount;
+       uint32_t flavor, val;
+       u_char oid[12];
+
+       seccount = srvcount = 0;
+       secmax = *seccountp;
+       *seccountp = 0;
+
+       nfsm_chain_get_32(error, nmc, srvcount);
+       while (!error && (srvcount > 0) && (seccount < secmax)) {
+               nfsm_chain_get_32(error, nmc, flavor);
+               nfsmout_if(error);
+               switch (flavor) {
+               case RPCAUTH_NONE:
+               case RPCAUTH_SYS:
+               case RPCAUTH_KRB5:
+               case RPCAUTH_KRB5I:
+               case RPCAUTH_KRB5P:
+                       sec[seccount++] = flavor;
+                       break;
+               case RPCSEC_GSS:
+                       /* we only recognize KRB5, KRB5I, KRB5P */
+                       nfsm_chain_get_32(error, nmc, val); /* OID length */
+                       nfsmout_if(error);
+                       if (val != sizeof(krb5_mech)) {
+                               nfsm_chain_adv(error, nmc, val);
+                               nfsm_chain_adv(error, nmc, 2*NFSX_UNSIGNED);
+                               break;
+                       }
+                       nfsm_chain_get_opaque(error, nmc, val, oid); /* OID bytes */
+                       nfsmout_if(error);
+                       if (bcmp(oid, krb5_mech, sizeof(krb5_mech))) {
+                               nfsm_chain_adv(error, nmc, 2*NFSX_UNSIGNED);
+                               break;
+                       }
+                       nfsm_chain_get_32(error, nmc, val); /* QOP */
+                       nfsm_chain_get_32(error, nmc, val); /* SERVICE */
+                       nfsmout_if(error);
+                       switch (val) {
+                       case RPCSEC_GSS_SVC_NONE:
+                               sec[seccount++] = RPCAUTH_KRB5;
+                               break;
+                       case RPCSEC_GSS_SVC_INTEGRITY:
+                               sec[seccount++] = RPCAUTH_KRB5I;
+                               break;
+                       case RPCSEC_GSS_SVC_PRIVACY:
+                               sec[seccount++] = RPCAUTH_KRB5P;
+                               break;
+                       }
+                       break;
+               }
+               srvcount--;
+       }
+nfsmout:
+       if (!error)
+               *seccountp = seccount;
+       return (error);
+}
+
+
+/*
+ * Fetch the FS_LOCATIONS attribute for the node found at directory/name.
+ */
+int
+nfs4_get_fs_locations(
+       struct nfsmount *nmp,
+       nfsnode_t dnp,
+       u_char *fhp,
+       int fhsize,
+       const char *name,
+       vfs_context_t ctx,
+       struct nfs_fs_locations *nfslsp)
+{
+       int error = 0, numops, status;
+       uint32_t bitmap[NFS_ATTR_BITMAP_LEN];
+       struct nfsreq rq, *req = &rq;
+       struct nfsreq_secinfo_args si;
+       struct nfsm_chain nmreq, nmrep;
+       uint64_t xid;
+
+       if (!fhp && dnp) {
+               fhp = dnp->n_fhp;
+               fhsize = dnp->n_fhsize;
+       }
+       if (!fhp)
+               return (EINVAL);
+
+       nfsm_chain_null(&nmreq);
+       nfsm_chain_null(&nmrep);
+
+       NFSREQ_SECINFO_SET(&si, NULL, fhp, fhsize, name, 0);
+       numops = 3;
+       nfsm_chain_build_alloc_init(error, &nmreq, 18 * NFSX_UNSIGNED);
+       nfsm_chain_add_compound_header(error, &nmreq, "fs_locations", numops);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTFH);
+       nfsm_chain_add_fh(error, &nmreq, NFS_VER4, fhp, fhsize);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_LOOKUP);
+       nfsm_chain_add_name(error, &nmreq, name, strlen(name), nmp);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_GETATTR);
+       NFS_CLEAR_ATTRIBUTES(bitmap);
+       NFS_BITMAP_SET(bitmap, NFS_FATTR_FS_LOCATIONS);
+       nfsm_chain_add_bitmap(error, &nmreq, bitmap, NFS_ATTR_BITMAP_LEN);
+       nfsm_chain_build_done(error, &nmreq);
+       nfsm_assert(error, (numops == 0), EPROTO);
+       nfsmout_if(error);
+       error = nfs_request_async(dnp, nmp->nm_mountp, &nmreq, NFSPROC4_COMPOUND,
+                       vfs_context_thread(ctx), vfs_context_ucred(ctx), &si, 0, NULL, &req);
+       if (!error)
+               error = nfs_request_async_finish(req, &nmrep, &xid, &status);
+       nfsm_chain_skip_tag(error, &nmrep);
+       nfsm_chain_get_32(error, &nmrep, numops);
+       nfsm_chain_op_check(error, &nmrep, NFS_OP_PUTFH);
+       nfsm_chain_op_check(error, &nmrep, NFS_OP_LOOKUP);
+       nfsm_chain_op_check(error, &nmrep, NFS_OP_GETATTR);
+       nfsmout_if(error);
+       error = nfs4_parsefattr(&nmrep, NULL, NULL, NULL, NULL, nfslsp);
+nfsmout:
+       nfsm_chain_cleanup(&nmrep);
+       nfsm_chain_cleanup(&nmreq);
+       return (error);
+}
+
+/*
+ * Referral trigger nodes may not have many attributes provided by the
+ * server, so put some default values in place.
+ */
+void
+nfs4_default_attrs_for_referral_trigger(
+       nfsnode_t dnp,
+       char *name,
+       int namelen,
+       struct nfs_vattr *nvap,
+       fhandle_t *fhp)
+{
+       struct timeval now;
+       microtime(&now);
+       int len;
+
+       nvap->nva_flags = NFS_FFLAG_TRIGGER | NFS_FFLAG_TRIGGER_REFERRAL;
+       if (!NFS_BITMAP_ISSET(nvap->nva_bitmap, NFS_FATTR_TYPE)) {
+               NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_TYPE);
+               nvap->nva_type = VDIR;
+       }
+       if (!NFS_BITMAP_ISSET(nvap->nva_bitmap, NFS_FATTR_FSID)) {
+               NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_FSID);
+               nvap->nva_fsid.major = 0;
+               nvap->nva_fsid.minor = 0;
+       }
+       if (!NFS_BITMAP_ISSET(nvap->nva_bitmap, NFS_FATTR_OWNER) && dnp) {
+               NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_OWNER);
+               nvap->nva_uid = dnp->n_vattr.nva_uid;
+               nvap->nva_uuuid = dnp->n_vattr.nva_uuuid;
+       }
+       if (!NFS_BITMAP_ISSET(nvap->nva_bitmap, NFS_FATTR_OWNER_GROUP) && dnp) {
+               NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_OWNER_GROUP);
+               nvap->nva_gid = dnp->n_vattr.nva_gid;
+               nvap->nva_guuid = dnp->n_vattr.nva_guuid;
+       }
+       if (!NFS_BITMAP_ISSET(nvap->nva_bitmap, NFS_FATTR_MODE)) {
+               NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_MODE);
+               nvap->nva_mode = 0777;
+       }
+       if (!NFS_BITMAP_ISSET(nvap->nva_bitmap, NFS_FATTR_SIZE)) {
+               NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_SIZE);
+               nvap->nva_size = 0;
+       }
+       if (!NFS_BITMAP_ISSET(nvap->nva_bitmap, NFS_FATTR_SPACE_USED)) {
+               NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_SPACE_USED);
+               nvap->nva_bytes = 0;
+       }
+       if (!NFS_BITMAP_ISSET(nvap->nva_bitmap, NFS_FATTR_NUMLINKS)) {
+               NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_NUMLINKS);
+               nvap->nva_nlink = 2;
+       }
+       if (!NFS_BITMAP_ISSET(nvap->nva_bitmap, NFS_FATTR_TIME_ACCESS)) {
+               NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_TIME_ACCESS);
+               nvap->nva_timesec[NFSTIME_ACCESS] = now.tv_sec;
+               nvap->nva_timensec[NFSTIME_ACCESS] = now.tv_usec * 1000;
+       }
+       if (!NFS_BITMAP_ISSET(nvap->nva_bitmap, NFS_FATTR_TIME_MODIFY)) {
+               NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_TIME_MODIFY);
+               nvap->nva_timesec[NFSTIME_MODIFY] = now.tv_sec;
+               nvap->nva_timensec[NFSTIME_MODIFY] = now.tv_usec * 1000;
+       }
+       if (!NFS_BITMAP_ISSET(nvap->nva_bitmap, NFS_FATTR_TIME_METADATA)) {
+               NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_TIME_METADATA);
+               nvap->nva_timesec[NFSTIME_CHANGE] = now.tv_sec;
+               nvap->nva_timensec[NFSTIME_CHANGE] = now.tv_usec * 1000;
+       }
+       if (!NFS_BITMAP_ISSET(nvap->nva_bitmap, NFS_FATTR_FILEID)) {
+               NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_FILEID);
+               nvap->nva_fileid = 42;
+       }
+       if (!NFS_BITMAP_ISSET(nvap->nva_bitmap, NFS_FATTR_FILEHANDLE) && dnp && name && fhp) {
+               /* Build a fake filehandle made up of parent node pointer and name */
+               NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_FILEHANDLE);
+               bcopy(&dnp, &fhp->fh_data[0], sizeof(dnp));
+               len = sizeof(fhp->fh_data) - sizeof(dnp);
+               bcopy(name, &fhp->fh_data[0] + sizeof(dnp), MIN(len, namelen));
+               fhp->fh_len = sizeof(dnp) + namelen;
+               if (fhp->fh_len > (int)sizeof(fhp->fh_data))
+                       fhp->fh_len = sizeof(fhp->fh_data);
+       }
+}
+
+/*
+ * Set NFS bitmap according to what's set in vnode_attr (and supported by the server).
+ */
+void
+nfs_vattr_set_bitmap(struct nfsmount *nmp, uint32_t *bitmap, struct vnode_attr *vap)
+{
+       int i;
+
+       NFS_CLEAR_ATTRIBUTES(bitmap);
+       if (VATTR_IS_ACTIVE(vap, va_data_size))
+               NFS_BITMAP_SET(bitmap, NFS_FATTR_SIZE);
+       if (VATTR_IS_ACTIVE(vap, va_acl) && (nmp->nm_fsattr.nfsa_flags & NFS_FSFLAG_ACL))
+               NFS_BITMAP_SET(bitmap, NFS_FATTR_ACL);
+       if (VATTR_IS_ACTIVE(vap, va_flags)) {
+               NFS_BITMAP_SET(bitmap, NFS_FATTR_ARCHIVE);
+               NFS_BITMAP_SET(bitmap, NFS_FATTR_HIDDEN);
+       }
+       // NFS_BITMAP_SET(bitmap, NFS_FATTR_MIMETYPE)
+       if (VATTR_IS_ACTIVE(vap, va_mode) && !NMFLAG(nmp, ACLONLY))
+               NFS_BITMAP_SET(bitmap, NFS_FATTR_MODE);
+       if (VATTR_IS_ACTIVE(vap, va_uid) || VATTR_IS_ACTIVE(vap, va_uuuid))
+               NFS_BITMAP_SET(bitmap, NFS_FATTR_OWNER);
+       if (VATTR_IS_ACTIVE(vap, va_gid) || VATTR_IS_ACTIVE(vap, va_guuid))
+               NFS_BITMAP_SET(bitmap, NFS_FATTR_OWNER_GROUP);
+       // NFS_BITMAP_SET(bitmap, NFS_FATTR_SYSTEM)
+       if (vap->va_vaflags & VA_UTIMES_NULL) {
+               NFS_BITMAP_SET(bitmap, NFS_FATTR_TIME_ACCESS_SET);
+               NFS_BITMAP_SET(bitmap, NFS_FATTR_TIME_MODIFY_SET);
+       } else {
+               if (VATTR_IS_ACTIVE(vap, va_access_time))
+                       NFS_BITMAP_SET(bitmap, NFS_FATTR_TIME_ACCESS_SET);
+               if (VATTR_IS_ACTIVE(vap, va_modify_time))
+                       NFS_BITMAP_SET(bitmap, NFS_FATTR_TIME_MODIFY_SET);
+       }
+       if (VATTR_IS_ACTIVE(vap, va_backup_time))
+               NFS_BITMAP_SET(bitmap, NFS_FATTR_TIME_BACKUP);
+       if (VATTR_IS_ACTIVE(vap, va_create_time))
+               NFS_BITMAP_SET(bitmap, NFS_FATTR_TIME_CREATE);
+       /* and limit to what is supported by server */
+       for (i=0; i < NFS_ATTR_BITMAP_LEN; i++)
+               bitmap[i] &= nmp->nm_fsattr.nfsa_supp_attr[i];
+}
+
+/*
+ * Convert between NFSv4 and VFS ACE types
+ */
+uint32_t
+nfs4_ace_nfstype_to_vfstype(uint32_t nfsacetype, int *errorp)
+{
+       switch (nfsacetype) {
+       case NFS_ACE_ACCESS_ALLOWED_ACE_TYPE:
+               return KAUTH_ACE_PERMIT;
+       case NFS_ACE_ACCESS_DENIED_ACE_TYPE:
+               return KAUTH_ACE_DENY;
+       case NFS_ACE_SYSTEM_AUDIT_ACE_TYPE:
+               return KAUTH_ACE_AUDIT;
+       case NFS_ACE_SYSTEM_ALARM_ACE_TYPE:
+               return KAUTH_ACE_ALARM;
+       }
+       *errorp = EBADRPC;
+       return 0;
+}
+
+uint32_t
+nfs4_ace_vfstype_to_nfstype(uint32_t vfstype, int *errorp)
+{
+       switch (vfstype) {
+       case KAUTH_ACE_PERMIT:
+               return NFS_ACE_ACCESS_ALLOWED_ACE_TYPE;
+       case KAUTH_ACE_DENY:
+               return NFS_ACE_ACCESS_DENIED_ACE_TYPE;
+       case KAUTH_ACE_AUDIT:
+               return NFS_ACE_SYSTEM_AUDIT_ACE_TYPE;
+       case KAUTH_ACE_ALARM:
+               return NFS_ACE_SYSTEM_ALARM_ACE_TYPE;
+       }
+       *errorp = EINVAL;
+       return 0;
+}
+
+/*
+ * Convert between NFSv4 and VFS ACE flags
+ */
+uint32_t
+nfs4_ace_nfsflags_to_vfsflags(uint32_t nfsflags)
+{
+       uint32_t vfsflags = 0;
+
+       if (nfsflags & NFS_ACE_FILE_INHERIT_ACE)
+               vfsflags |= KAUTH_ACE_FILE_INHERIT;
+       if (nfsflags & NFS_ACE_DIRECTORY_INHERIT_ACE)
+               vfsflags |= KAUTH_ACE_DIRECTORY_INHERIT;
+       if (nfsflags & NFS_ACE_NO_PROPAGATE_INHERIT_ACE)
+               vfsflags |= KAUTH_ACE_LIMIT_INHERIT;
+       if (nfsflags & NFS_ACE_INHERIT_ONLY_ACE)
+               vfsflags |= KAUTH_ACE_ONLY_INHERIT;
+       if (nfsflags & NFS_ACE_SUCCESSFUL_ACCESS_ACE_FLAG)
+               vfsflags |= KAUTH_ACE_SUCCESS;
+       if (nfsflags & NFS_ACE_FAILED_ACCESS_ACE_FLAG)
+               vfsflags |= KAUTH_ACE_FAILURE;
+       if (nfsflags & NFS_ACE_INHERITED_ACE)
+               vfsflags |= KAUTH_ACE_INHERITED;
+
+       return (vfsflags);
+}
+
+uint32_t
+nfs4_ace_vfsflags_to_nfsflags(uint32_t vfsflags)
+{
+       uint32_t nfsflags = 0;
+
+       if (vfsflags & KAUTH_ACE_FILE_INHERIT)
+               nfsflags |= NFS_ACE_FILE_INHERIT_ACE;
+       if (vfsflags & KAUTH_ACE_DIRECTORY_INHERIT)
+               nfsflags |= NFS_ACE_DIRECTORY_INHERIT_ACE;
+       if (vfsflags & KAUTH_ACE_LIMIT_INHERIT)
+               nfsflags |= NFS_ACE_NO_PROPAGATE_INHERIT_ACE;
+       if (vfsflags & KAUTH_ACE_ONLY_INHERIT)
+               nfsflags |= NFS_ACE_INHERIT_ONLY_ACE;
+       if (vfsflags & KAUTH_ACE_SUCCESS)
+               nfsflags |= NFS_ACE_SUCCESSFUL_ACCESS_ACE_FLAG;
+       if (vfsflags & KAUTH_ACE_FAILURE)
+               nfsflags |= NFS_ACE_FAILED_ACCESS_ACE_FLAG;
+       if (vfsflags & KAUTH_ACE_INHERITED)
+               nfsflags |= NFS_ACE_INHERITED_ACE;
+
+       return (nfsflags);
+}
+
+/*
+ * Convert between NFSv4 ACE access masks and VFS access rights
+ */
+uint32_t
+nfs4_ace_nfsmask_to_vfsrights(uint32_t nfsmask)
+{
+       uint32_t vfsrights = 0;
+
+       if (nfsmask & NFS_ACE_READ_DATA)
+               vfsrights |= KAUTH_VNODE_READ_DATA;
+       if (nfsmask & NFS_ACE_LIST_DIRECTORY)
+               vfsrights |= KAUTH_VNODE_LIST_DIRECTORY;
+       if (nfsmask & NFS_ACE_WRITE_DATA)
+               vfsrights |= KAUTH_VNODE_WRITE_DATA;
+       if (nfsmask & NFS_ACE_ADD_FILE)
+               vfsrights |= KAUTH_VNODE_ADD_FILE;
+       if (nfsmask & NFS_ACE_APPEND_DATA)
+               vfsrights |= KAUTH_VNODE_APPEND_DATA;
+       if (nfsmask & NFS_ACE_ADD_SUBDIRECTORY)
+               vfsrights |= KAUTH_VNODE_ADD_SUBDIRECTORY;
+       if (nfsmask & NFS_ACE_READ_NAMED_ATTRS)
+               vfsrights |= KAUTH_VNODE_READ_EXTATTRIBUTES;
+       if (nfsmask & NFS_ACE_WRITE_NAMED_ATTRS)
+               vfsrights |= KAUTH_VNODE_WRITE_EXTATTRIBUTES;
+       if (nfsmask & NFS_ACE_EXECUTE)
+               vfsrights |= KAUTH_VNODE_EXECUTE;
+       if (nfsmask & NFS_ACE_DELETE_CHILD)
+               vfsrights |= KAUTH_VNODE_DELETE_CHILD;
+       if (nfsmask & NFS_ACE_READ_ATTRIBUTES)
+               vfsrights |= KAUTH_VNODE_READ_ATTRIBUTES;
+       if (nfsmask & NFS_ACE_WRITE_ATTRIBUTES)
+               vfsrights |= KAUTH_VNODE_WRITE_ATTRIBUTES;
+       if (nfsmask & NFS_ACE_DELETE)
+               vfsrights |= KAUTH_VNODE_DELETE;
+       if (nfsmask & NFS_ACE_READ_ACL)
+               vfsrights |= KAUTH_VNODE_READ_SECURITY;
+       if (nfsmask & NFS_ACE_WRITE_ACL)
+               vfsrights |= KAUTH_VNODE_WRITE_SECURITY;
+       if (nfsmask & NFS_ACE_WRITE_OWNER)
+               vfsrights |= KAUTH_VNODE_CHANGE_OWNER;
+       if (nfsmask & NFS_ACE_SYNCHRONIZE)
+               vfsrights |= KAUTH_VNODE_SYNCHRONIZE;
+       if ((nfsmask & NFS_ACE_GENERIC_READ) == NFS_ACE_GENERIC_READ)
+               vfsrights |= KAUTH_ACE_GENERIC_READ;
+       if ((nfsmask & NFS_ACE_GENERIC_WRITE) == NFS_ACE_GENERIC_WRITE)
+               vfsrights |= KAUTH_ACE_GENERIC_WRITE;
+       if ((nfsmask & NFS_ACE_GENERIC_EXECUTE) == NFS_ACE_GENERIC_EXECUTE)
+               vfsrights |= KAUTH_ACE_GENERIC_EXECUTE;
+
+       return (vfsrights);
+}
+
+uint32_t
+nfs4_ace_vfsrights_to_nfsmask(uint32_t vfsrights)
+{
+       uint32_t nfsmask = 0;
+
+       if (vfsrights & KAUTH_VNODE_READ_DATA)
+               nfsmask |= NFS_ACE_READ_DATA;
+       if (vfsrights & KAUTH_VNODE_LIST_DIRECTORY)
+               nfsmask |= NFS_ACE_LIST_DIRECTORY;
+       if (vfsrights & KAUTH_VNODE_WRITE_DATA)
+               nfsmask |= NFS_ACE_WRITE_DATA;
+       if (vfsrights & KAUTH_VNODE_ADD_FILE)
+               nfsmask |= NFS_ACE_ADD_FILE;
+       if (vfsrights & KAUTH_VNODE_APPEND_DATA)
+               nfsmask |= NFS_ACE_APPEND_DATA;
+       if (vfsrights & KAUTH_VNODE_ADD_SUBDIRECTORY)
+               nfsmask |= NFS_ACE_ADD_SUBDIRECTORY;
+       if (vfsrights & KAUTH_VNODE_READ_EXTATTRIBUTES)
+               nfsmask |= NFS_ACE_READ_NAMED_ATTRS;
+       if (vfsrights & KAUTH_VNODE_WRITE_EXTATTRIBUTES)
+               nfsmask |= NFS_ACE_WRITE_NAMED_ATTRS;
+       if (vfsrights & KAUTH_VNODE_EXECUTE)
+               nfsmask |= NFS_ACE_EXECUTE;
+       if (vfsrights & KAUTH_VNODE_DELETE_CHILD)
+               nfsmask |= NFS_ACE_DELETE_CHILD;
+       if (vfsrights & KAUTH_VNODE_READ_ATTRIBUTES)
+               nfsmask |= NFS_ACE_READ_ATTRIBUTES;
+       if (vfsrights & KAUTH_VNODE_WRITE_ATTRIBUTES)
+               nfsmask |= NFS_ACE_WRITE_ATTRIBUTES;
+       if (vfsrights & KAUTH_VNODE_DELETE)
+               nfsmask |= NFS_ACE_DELETE;
+       if (vfsrights & KAUTH_VNODE_READ_SECURITY)
+               nfsmask |= NFS_ACE_READ_ACL;
+       if (vfsrights & KAUTH_VNODE_WRITE_SECURITY)
+               nfsmask |= NFS_ACE_WRITE_ACL;
+       if (vfsrights & KAUTH_VNODE_CHANGE_OWNER)
+               nfsmask |= NFS_ACE_WRITE_OWNER;
+       if (vfsrights & KAUTH_VNODE_SYNCHRONIZE)
+               nfsmask |= NFS_ACE_SYNCHRONIZE;
+       if (vfsrights & KAUTH_ACE_GENERIC_READ)
+               nfsmask |= NFS_ACE_GENERIC_READ;
+       if (vfsrights & KAUTH_ACE_GENERIC_WRITE)
+               nfsmask |= NFS_ACE_GENERIC_WRITE;
+       if (vfsrights & KAUTH_ACE_GENERIC_EXECUTE)
+               nfsmask |= NFS_ACE_GENERIC_EXECUTE;
+       if (vfsrights & KAUTH_ACE_GENERIC_ALL)
+               nfsmask |= (KAUTH_ACE_GENERIC_READ|KAUTH_ACE_GENERIC_WRITE|NFS_ACE_GENERIC_EXECUTE);
+
+       return (nfsmask);
+}
+
+/*
+ * Map an NFSv4 ID string to a VFS guid.
+ *
+ * Try to use the ID mapping service... but we may fallback to trying to do it ourselves.
+ */
+int
+nfs4_id2guid(/*const*/ char *id, guid_t *guidp, int isgroup)
+{
+       int error1 = 0, error = 0, compare;
+       guid_t guid1, guid2, *gp;
+       ntsid_t sid;
+       long num, unknown;
+       const char *p, *at;
+
+       *guidp = kauth_null_guid;
+       compare = ((nfs_idmap_ctrl & NFS_IDMAP_CTRL_USE_IDMAP_SERVICE) &&
+                  (nfs_idmap_ctrl & NFS_IDMAP_CTRL_COMPARE_RESULTS));
+       unknown = (nfs_idmap_ctrl & NFS_IDMAP_CTRL_UNKNOWN_IS_99) ? 99 : -2;
+
+       /*
+        * First check if it is just a simple numeric ID string or a special "XXX@" name.
+        * If it's a number, there's no need trying to ask the IDMAP service to map it.
+        * If it's a special "XXX@" name, we want to make sure to treat it as a group.
+        */
+       num = 1;
+       at = NULL;
+       p = id;
+       while (*p) {
+               if ((*p < '0') || (*p > '9'))
+                       num = 0;
+               if (*p == '@')
+                       at = p;
+               p++;
+       }
+       if (at && !at[1] && !isgroup)
+               isgroup = 1;  /* special "XXX@" names should always be treated as groups */
+       if (num) {
+               /* must be numeric ID (or empty) */
+               num = *id ? strtol(id, NULL, 10) : unknown;
+               gp = guidp;
+               goto gotnumid;
+       }
+
+       if (nfs_idmap_ctrl & NFS_IDMAP_CTRL_USE_IDMAP_SERVICE) {
+               /*
+                * Ask the ID mapping service to map the ID string to a GUID.
+                *
+                * [sigh] this isn't a "pwnam/grnam" it's an NFS ID string!
+                */
+               gp = compare ? &guid1 : guidp;
+               if (isgroup)
+                       error = kauth_cred_grnam2guid(id, gp);
+               else
+                       error = kauth_cred_pwnam2guid(id, gp);
+               if (error && (nfs_idmap_ctrl & NFS_IDMAP_CTRL_LOG_FAILED_MAPPINGS))
+                       printf("nfs4_id2guid: idmap failed for %s %s error %d\n", id, isgroup ? "G" : " ", error);
+               if (!error && (nfs_idmap_ctrl & NFS_IDMAP_CTRL_LOG_SUCCESSFUL_MAPPINGS))
+                       printf("nfs4_id2guid: idmap for %s %s got guid "
+                               "%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x\n",
+                               id, isgroup ? "G" : " ",
+                               gp->g_guid[0], gp->g_guid[1], gp->g_guid[2], gp->g_guid[3],
+                               gp->g_guid[4], gp->g_guid[5], gp->g_guid[6], gp->g_guid[7],
+                               gp->g_guid[8], gp->g_guid[9], gp->g_guid[10], gp->g_guid[11],
+                               gp->g_guid[12], gp->g_guid[13], gp->g_guid[14], gp->g_guid[15]);
+               error1 = error;
+       }
+       if (error || compare || !(nfs_idmap_ctrl & NFS_IDMAP_CTRL_USE_IDMAP_SERVICE)) {
+               /*
+                * fallback path... see if we can come up with an answer ourselves.
+                */
+               gp = compare ? &guid2 : guidp;
+
+               if (!(nfs_idmap_ctrl & NFS_IDMAP_CTRL_FALLBACK_NO_WELLKNOWN_IDS) && at && !at[1]) {
+                       /* must be a special ACE "who" ID */
+                       bzero(&sid, sizeof(sid));
+                       sid.sid_kind = 1;
+                       sid.sid_authcount = 1;
+                       if (!strcmp(id, "OWNER@")) {
+                               // S-1-3-0
+                               sid.sid_authority[5] = 3;
+                               sid.sid_authorities[0] = 0;
+                       } else if (!strcmp(id, "GROUP@")) {
+                               // S-1-3-1
+                               sid.sid_authority[5] = 3;
+                               sid.sid_authorities[0] = 1;
+                       } else if (!strcmp(id, "EVERYONE@")) {
+                               // S-1-1-0
+                               sid.sid_authority[5] = 1;
+                               sid.sid_authorities[0] = 0;
+                       } else if (!strcmp(id, "INTERACTIVE@")) {
+                               // S-1-5-4
+                               sid.sid_authority[5] = 5;
+                               sid.sid_authorities[0] = 4;
+                       } else if (!strcmp(id, "NETWORK@")) {
+                               // S-1-5-2
+                               sid.sid_authority[5] = 5;
+                               sid.sid_authorities[0] = 2;
+                       } else if (!strcmp(id, "DIALUP@")) {
+                               // S-1-5-1
+                               sid.sid_authority[5] = 5;
+                               sid.sid_authorities[0] = 1;
+                       } else if (!strcmp(id, "BATCH@")) {
+                               // S-1-5-3
+                               sid.sid_authority[5] = 5;
+                               sid.sid_authorities[0] = 3;
+                       } else if (!strcmp(id, "ANONYMOUS@")) {
+                               // S-1-5-7
+                               sid.sid_authority[5] = 5;
+                               sid.sid_authorities[0] = 7;
+                       } else if (!strcmp(id, "AUTHENTICATED@")) {
+                               // S-1-5-11
+                               sid.sid_authority[5] = 5;
+                               sid.sid_authorities[0] = 11;
+                       } else if (!strcmp(id, "SERVICE@")) {
+                               // S-1-5-6
+                               sid.sid_authority[5] = 5;
+                               sid.sid_authorities[0] = 6;
+                       } else {
+                               // S-1-0-0 "NOBODY"
+                               sid.sid_authority[5] = 0;
+                               sid.sid_authorities[0] = 0;
+                       }
+                       error = kauth_cred_ntsid2guid(&sid, gp);
+               } else {
+                       if (!(nfs_idmap_ctrl & NFS_IDMAP_CTRL_FALLBACK_NO_COMMON_IDS) && at) {
+                               /* must be user@domain */
+                               /* try to identify some well-known IDs */
+                               if (!strncmp(id, "root@", 5))
+                                       num = 0;
+                               else if (!strncmp(id, "wheel@", 6))
+                                       num = 0;
+                               else if (!strncmp(id, "nobody@", 7))
+                                       num = -2;
+                               else if (!strncmp(id, "nfsnobody@", 10))
+                                       num = -2;
+                               else
+                                       num = unknown;
+                       } else if (!(nfs_idmap_ctrl & NFS_IDMAP_CTRL_FALLBACK_NO_COMMON_IDS) && !strcmp(id, "nobody")) {
+                               num = -2;
+                       } else {
+                               num = unknown;
+                       }
+gotnumid:
+                       if (isgroup)
+                               error = kauth_cred_gid2guid((gid_t)num, gp);
+                       else
+                               error = kauth_cred_uid2guid((uid_t)num, gp);
+               }
+               if (error && (nfs_idmap_ctrl & NFS_IDMAP_CTRL_LOG_FAILED_MAPPINGS))
+                       printf("nfs4_id2guid: fallback map failed for %s %s error %d\n", id, isgroup ? "G" : " ", error);
+               if (!error && (nfs_idmap_ctrl & NFS_IDMAP_CTRL_LOG_SUCCESSFUL_MAPPINGS))
+                       printf("nfs4_id2guid: fallback map for %s %s got guid "
+                               "%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x\n",
+                               id, isgroup ? "G" : " ",
+                               gp->g_guid[0], gp->g_guid[1], gp->g_guid[2], gp->g_guid[3],
+                               gp->g_guid[4], gp->g_guid[5], gp->g_guid[6], gp->g_guid[7],
+                               gp->g_guid[8], gp->g_guid[9], gp->g_guid[10], gp->g_guid[11],
+                               gp->g_guid[12], gp->g_guid[13], gp->g_guid[14], gp->g_guid[15]);
+       }
+
+       if (compare) {
+               /* compare the results, log if different */
+               if (!error1 && !error) {
+                       if (!kauth_guid_equal(&guid1, &guid2))
+                               printf("nfs4_id2guid: idmap/fallback results differ for %s %s - "
+                                       "idmap %02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x "
+                                       "fallback %02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x\n",
+                                       id, isgroup ? "G" : " ",
+                                       guid1.g_guid[0], guid1.g_guid[1], guid1.g_guid[2], guid1.g_guid[3],
+                                       guid1.g_guid[4], guid1.g_guid[5], guid1.g_guid[6], guid1.g_guid[7],
+                                       guid1.g_guid[8], guid1.g_guid[9], guid1.g_guid[10], guid1.g_guid[11],
+                                       guid1.g_guid[12], guid1.g_guid[13], guid1.g_guid[14], guid1.g_guid[15],
+                                       guid2.g_guid[0], guid2.g_guid[1], guid2.g_guid[2], guid2.g_guid[3],
+                                       guid2.g_guid[4], guid2.g_guid[5], guid2.g_guid[6], guid2.g_guid[7],
+                                       guid2.g_guid[8], guid2.g_guid[9], guid2.g_guid[10], guid2.g_guid[11],
+                                       guid2.g_guid[12], guid2.g_guid[13], guid2.g_guid[14], guid2.g_guid[15]);
+                       /* copy idmap result to output guid */
+                       *guidp = guid1;
+               } else if (error1 && !error) {
+                       printf("nfs4_id2guid: idmap/fallback results differ for %s %s - "
+                               "idmap error %d "
+                               "fallback %02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x\n",
+                               id, isgroup ? "G" : " ",
+                               error1,
+                               guid2.g_guid[0], guid2.g_guid[1], guid2.g_guid[2], guid2.g_guid[3],
+                               guid2.g_guid[4], guid2.g_guid[5], guid2.g_guid[6], guid2.g_guid[7],
+                               guid2.g_guid[8], guid2.g_guid[9], guid2.g_guid[10], guid2.g_guid[11],
+                               guid2.g_guid[12], guid2.g_guid[13], guid2.g_guid[14], guid2.g_guid[15]);
+                       /* copy fallback result to output guid */
+                       *guidp = guid2;
+               } else if (!error1 && error) {
+                       printf("nfs4_id2guid: idmap/fallback results differ for %s %s - "
+                               "idmap %02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x "
+                               "fallback error %d\n",
+                               id, isgroup ? "G" : " ",
+                               guid1.g_guid[0], guid1.g_guid[1], guid1.g_guid[2], guid1.g_guid[3],
+                               guid1.g_guid[4], guid1.g_guid[5], guid1.g_guid[6], guid1.g_guid[7],
+                               guid1.g_guid[8], guid1.g_guid[9], guid1.g_guid[10], guid1.g_guid[11],
+                               guid1.g_guid[12], guid1.g_guid[13], guid1.g_guid[14], guid1.g_guid[15],
+                               error);
+                       /* copy idmap result to output guid */
+                       *guidp = guid1;
+                       error = 0;
+               } else {
+                       if (error1 != error)
+                               printf("nfs4_id2guid: idmap/fallback results differ for %s %s - "
+                                       "idmap error %d fallback error %d\n",
+                                       id, isgroup ? "G" : " ", error1, error);
+               }
+       }
+
+       return (error);
+}
+
+/*
+ * Map a VFS guid to an NFSv4 ID string.
+ *
+ * Try to use the ID mapping service... but we may fallback to trying to do it ourselves.
+ */
+int
+nfs4_guid2id(guid_t *guidp, char *id, int *idlen, int isgroup)
+{
+       int error1 = 0, error = 0, compare;
+       int id1len, id2len, len;
+       char *id1buf, *id1;
+       char numbuf[32];
+       const char *id2 = NULL;
+
+       id1buf = id1 = NULL;
+       id1len = id2len = 0;
+       compare = ((nfs_idmap_ctrl & NFS_IDMAP_CTRL_USE_IDMAP_SERVICE) &&
+                  (nfs_idmap_ctrl & NFS_IDMAP_CTRL_COMPARE_RESULTS));
+
+       if (nfs_idmap_ctrl & NFS_IDMAP_CTRL_USE_IDMAP_SERVICE) {
+               /*
+                * Ask the ID mapping service to map the GUID to an ID string.
+                *
+                * [sigh] this isn't a "pwnam" it's an NFS id string!
+                */
+
+               /*
+                * Stupid kauth_cred_guid2pwnam() function requires that the buffer
+                * be at least MAXPATHLEN bytes long even though most if not all ID
+                * strings will be much much shorter than that.
+                */
+               if (compare || (*idlen < MAXPATHLEN)) {
+                       MALLOC_ZONE(id1buf, char*, MAXPATHLEN, M_NAMEI, M_WAITOK);
+                       if (!id1buf)
+                               return (ENOMEM);
+                       id1 = id1buf;
+                       id1len = MAXPATHLEN;
+               } else {
+                       id1 = id;
+                       id1len = *idlen;
+               }
+
+               if (isgroup)
+                       error = kauth_cred_guid2grnam(guidp, id1);
+               else
+                       error = kauth_cred_guid2pwnam(guidp, id1);
+               if (error && (nfs_idmap_ctrl & NFS_IDMAP_CTRL_LOG_FAILED_MAPPINGS))
+                       printf("nfs4_guid2id: idmap failed for "
+                               "%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x %s "
+                               "error %d\n",
+                               guidp->g_guid[0], guidp->g_guid[1], guidp->g_guid[2], guidp->g_guid[3],
+                               guidp->g_guid[4], guidp->g_guid[5], guidp->g_guid[6], guidp->g_guid[7],
+                               guidp->g_guid[8], guidp->g_guid[9], guidp->g_guid[10], guidp->g_guid[11],
+                               guidp->g_guid[12], guidp->g_guid[13], guidp->g_guid[14], guidp->g_guid[15],
+                               isgroup ? "G" : " ", error);
+               if (!error && (nfs_idmap_ctrl & NFS_IDMAP_CTRL_LOG_SUCCESSFUL_MAPPINGS))
+                       printf("nfs4_guid2id: idmap for "
+                               "%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x %s "
+                               "got ID %s\n",
+                               guidp->g_guid[0], guidp->g_guid[1], guidp->g_guid[2], guidp->g_guid[3],
+                               guidp->g_guid[4], guidp->g_guid[5], guidp->g_guid[6], guidp->g_guid[7],
+                               guidp->g_guid[8], guidp->g_guid[9], guidp->g_guid[10], guidp->g_guid[11],
+                               guidp->g_guid[12], guidp->g_guid[13], guidp->g_guid[14], guidp->g_guid[15],
+                               isgroup ? "G" : " ", id1);
+               error1 = error;
+               if (!error) {
+                       if (compare) {
+                               id1len = strnlen(id1, id1len);
+                       } else if (id1 == id1buf) {
+                               /* copy idmap result to output buffer */
+                               len = strlcpy(id, id1, *idlen);
+                               if (len >= *idlen)
+                                       error = ENOSPC;
+                               else
+                                       *idlen = len;
+                       }
+               }
+       }
+       if (error || compare || !(nfs_idmap_ctrl & NFS_IDMAP_CTRL_USE_IDMAP_SERVICE)) {
+               /*
+                * fallback path... see if we can come up with an answer ourselves.
+                */
+               ntsid_t sid;
+               uid_t uid;
+
+               if (!(nfs_idmap_ctrl & NFS_IDMAP_CTRL_FALLBACK_NO_WELLKNOWN_IDS)) {
+                       error = kauth_cred_guid2ntsid(guidp, &sid);
+                       if (!error && (sid.sid_kind == 1) && (sid.sid_authcount == 1)) {
+                               /* check if it's one of our well-known ACE WHO names */
+                               if (sid.sid_authority[5] == 0) {
+                                       if (sid.sid_authorities[0] == 0) // S-1-0-0
+                                               id2 = "nobody@localdomain";
+                               } else if (sid.sid_authority[5] == 1) {
+                                       if (sid.sid_authorities[0] == 0) // S-1-1-0
+                                               id2 = "EVERYONE@";
+                               } else if (sid.sid_authority[5] == 3) {
+                                       if (sid.sid_authorities[0] == 0) // S-1-3-0
+                                               id2 = "OWNER@";
+                                       else if (sid.sid_authorities[0] == 1) // S-1-3-1
+                                               id2 = "GROUP@";
+                               } else if (sid.sid_authority[5] == 5) {
+                                       if (sid.sid_authorities[0] == ntohl(1)) // S-1-5-1
+                                               id2 = "DIALUP@";
+                                       else if (sid.sid_authorities[0] == ntohl(2)) // S-1-5-2
+                                               id2 = "NETWORK@";
+                                       else if (sid.sid_authorities[0] == ntohl(3)) // S-1-5-3
+                                               id2 = "BATCH@";
+                                       else if (sid.sid_authorities[0] == ntohl(4)) // S-1-5-4
+                                               id2 = "INTERACTIVE@";
+                                       else if (sid.sid_authorities[0] == ntohl(6)) // S-1-5-6
+                                               id2 = "SERVICE@";
+                                       else if (sid.sid_authorities[0] == ntohl(7)) // S-1-5-7
+                                               id2 = "ANONYMOUS@";
+                                       else if (sid.sid_authorities[0] == ntohl(11)) // S-1-5-11
+                                               id2 = "AUTHENTICATED@";
+                               }
+                       }
+               }
+               if (!id2) {
+                       /* OK, let's just try mapping it to a UID/GID */
+                       if (isgroup)
+                               error = kauth_cred_guid2gid(guidp, (gid_t*)&uid);
+                       else
+                               error = kauth_cred_guid2uid(guidp, &uid);
+                       if (!error) {
+                               if (!(nfs_idmap_ctrl & NFS_IDMAP_CTRL_FALLBACK_NO_COMMON_IDS)) {
+                                       /* map well known uid's to strings */
+                                       if (uid == 0)
+                                               id2 = isgroup ? "wheel@localdomain" : "root@localdomain";
+                                       else if (uid == (uid_t)-2)
+                                               id2 = "nobody@localdomain";
+                               }
+                               if (!id2) {
+                                       /* or just use a decimal number string. */
+                                       snprintf(numbuf, sizeof(numbuf), "%d", uid);
+                                       id2 = numbuf;
+                               }
+                       }
+               }
+               if (error && (nfs_idmap_ctrl & NFS_IDMAP_CTRL_LOG_FAILED_MAPPINGS))
+                       printf("nfs4_guid2id: fallback map failed for "
+                               "%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x %s "
+                               "error %d\n",
+                               guidp->g_guid[0], guidp->g_guid[1], guidp->g_guid[2], guidp->g_guid[3],
+                               guidp->g_guid[4], guidp->g_guid[5], guidp->g_guid[6], guidp->g_guid[7],
+                               guidp->g_guid[8], guidp->g_guid[9], guidp->g_guid[10], guidp->g_guid[11],
+                               guidp->g_guid[12], guidp->g_guid[13], guidp->g_guid[14], guidp->g_guid[15],
+                               isgroup ? "G" : " ", error);
+               if (!error && (nfs_idmap_ctrl & NFS_IDMAP_CTRL_LOG_SUCCESSFUL_MAPPINGS))
+                       printf("nfs4_guid2id: fallback map for "
+                               "%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x %s "
+                               "got ID %s\n",
+                               guidp->g_guid[0], guidp->g_guid[1], guidp->g_guid[2], guidp->g_guid[3],
+                               guidp->g_guid[4], guidp->g_guid[5], guidp->g_guid[6], guidp->g_guid[7],
+                               guidp->g_guid[8], guidp->g_guid[9], guidp->g_guid[10], guidp->g_guid[11],
+                               guidp->g_guid[12], guidp->g_guid[13], guidp->g_guid[14], guidp->g_guid[15],
+                               isgroup ? "G" : " ", id2);
+               if (!error && id2) {
+                       if (compare) {
+                               id2len = strnlen(id2, MAXPATHLEN);
+                       } else {
+                               /* copy fallback result to output buffer */
+                               len = strlcpy(id, id2, *idlen);
+                               if (len >= *idlen)
+                                       error = ENOSPC;
+                               else
+                                       *idlen = len;
+                       }
+               }
+       }
+
+       if (compare) {
+               /* compare the results, log if different */
+               if (!error1 && !error) {
+                       if ((id1len != id2len) || strncmp(id1, id2, id1len))
+                               printf("nfs4_guid2id: idmap/fallback results differ for "
+                                       "%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x %s "
+                                       "idmap %s fallback %s\n",
+                                       guidp->g_guid[0], guidp->g_guid[1], guidp->g_guid[2], guidp->g_guid[3],
+                                       guidp->g_guid[4], guidp->g_guid[5], guidp->g_guid[6], guidp->g_guid[7],
+                                       guidp->g_guid[8], guidp->g_guid[9], guidp->g_guid[10], guidp->g_guid[11],
+                                       guidp->g_guid[12], guidp->g_guid[13], guidp->g_guid[14], guidp->g_guid[15],
+                                       isgroup ? "G" : " ", id1, id2);
+                       if (id1 == id1buf) {
+                               /* copy idmap result to output buffer */
+                               len = strlcpy(id, id1, *idlen);
+                               if (len >= *idlen)
+                                       error = ENOSPC;
+                               else
+                                       *idlen = len;
+                       }
+               } else if (error1 && !error) {
+                       printf("nfs4_guid2id: idmap/fallback results differ for "
+                               "%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x %s "
+                               "idmap error %d fallback %s\n",
+                               guidp->g_guid[0], guidp->g_guid[1], guidp->g_guid[2], guidp->g_guid[3],
+                               guidp->g_guid[4], guidp->g_guid[5], guidp->g_guid[6], guidp->g_guid[7],
+                               guidp->g_guid[8], guidp->g_guid[9], guidp->g_guid[10], guidp->g_guid[11],
+                               guidp->g_guid[12], guidp->g_guid[13], guidp->g_guid[14], guidp->g_guid[15],
+                               isgroup ? "G" : " ", error1, id2);
+                       /* copy fallback result to output buffer */
+                       len = strlcpy(id, id2, *idlen);
+                       if (len >= *idlen)
+                               error = ENOSPC;
+                       else
+                               *idlen = len;
+               } else if (!error1 && error) {
+                       printf("nfs4_guid2id: idmap/fallback results differ for "
+                               "%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x_%02x%02x%02x%02x %s "
+                               "idmap %s fallback error %d\n",
+                               guidp->g_guid[0], guidp->g_guid[1], guidp->g_guid[2], guidp->g_guid[3],
+                               guidp->g_guid[4], guidp->g_guid[5], guidp->g_guid[6], guidp->g_guid[7],
+                               guidp->g_guid[8], guidp->g_guid[9], guidp->g_guid[10], guidp->g_guid[11],
+                               guidp->g_guid[12], guidp->g_guid[13], guidp->g_guid[14], guidp->g_guid[15],
+                               isgroup ? "G" : " ", id1, error);
+                       if (id1 == id1buf) {
+                               /* copy idmap result to output buffer */
+                               len = strlcpy(id, id1, *idlen);
+                               if (len >= *idlen)
+                                       error = ENOSPC;
+                               else
+                                       *idlen = len;
+                       }
+                       error = 0;
+               } else {
+                       if (error1 != error)
+                               printf("nfs4_guid2id: idmap/fallback results differ for %s %s - "
+                                       "idmap error %d fallback error %d\n",
+                                       id, isgroup ? "G" : " ", error1, error);
+               }
+       }
+       if (id1buf)
+               FREE_ZONE(id1buf, MAXPATHLEN, M_NAMEI);
+       return (error);
+}
+
+
 /*
  * Set a vnode attr's supported bits according to the given bitmap
  */
@@ -403,11 +1474,10 @@ nfs_vattr_set_supported(uint32_t *bitmap, struct vnode_attr *vap)
        // if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_CHANGE))
        if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_SIZE))
                VATTR_SET_SUPPORTED(vap, va_data_size);
-       // if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_NAMED_ATTR))
        if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_FSID))
                VATTR_SET_SUPPORTED(vap, va_fsid);
-//     if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_ACL))
-//             VATTR_SET_SUPPORTED(vap, va_acl);
+       if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_ACL))
+               VATTR_SET_SUPPORTED(vap, va_acl);
        if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_ARCHIVE))
                VATTR_SET_SUPPORTED(vap, va_flags);
        if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_FILEID))
@@ -419,10 +1489,14 @@ nfs_vattr_set_supported(uint32_t *bitmap, struct vnode_attr *vap)
                VATTR_SET_SUPPORTED(vap, va_mode);
        if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_NUMLINKS))
                VATTR_SET_SUPPORTED(vap, va_nlink);
-       if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_OWNER))
+       if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_OWNER)) {
                VATTR_SET_SUPPORTED(vap, va_uid);
-       if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_OWNER_GROUP))
+               VATTR_SET_SUPPORTED(vap, va_uuuid);
+       }
+       if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_OWNER_GROUP)) {
                VATTR_SET_SUPPORTED(vap, va_gid);
+               VATTR_SET_SUPPORTED(vap, va_guuid);
+       }
        if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_RAWDEV))
                VATTR_SET_SUPPORTED(vap, va_rdev);
        if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_SPACE_USED))
@@ -450,15 +1524,21 @@ nfs4_parsefattr(
        struct nfs_fsattr *nfsap,
        struct nfs_vattr *nvap,
        fhandle_t *fhp,
-       struct dqblk *dqbp)
+       struct dqblk *dqbp,
+       struct nfs_fs_locations *nfslsp)
 {
-       int error = 0, attrbytes;
-       uint32_t val, val2, val3, i, j;
+       int error = 0, error2, rderror = 0, attrbytes;
+       uint32_t val, val2, val3, i;
        uint32_t bitmap[NFS_ATTR_BITMAP_LEN], len;
-       char *s;
+       size_t slen;
+       char sbuf[64], *s;
        struct nfs_fsattr nfsa_dummy;
        struct nfs_vattr nva_dummy;
        struct dqblk dqb_dummy;
+       kauth_acl_t acl = NULL;
+       uint32_t ace_type, ace_flags, ace_mask;
+       struct nfs_fs_locations nfsls_dummy;
+       struct sockaddr_storage ss;
 
        /* if not interested in some values... throw 'em into a local dummy variable */
        if (!nfsap)
@@ -467,8 +1547,14 @@ nfs4_parsefattr(
                nvap = &nva_dummy;
        if (!dqbp)
                dqbp = &dqb_dummy;
+       if (!nfslsp)
+               nfslsp = &nfsls_dummy;
+       bzero(nfslsp, sizeof(*nfslsp));
 
        attrbytes = val = val2 = val3 = 0;
+       s = sbuf;
+       slen = sizeof(sbuf);
+       NVATTR_INIT(nvap);
 
        len = NFS_ATTR_BITMAP_LEN;
        nfsm_chain_get_bitmap(error, nmc, bitmap, len);
@@ -489,17 +1575,19 @@ nfs4_parsefattr(
        if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_TYPE)) {
                nfsm_chain_get_32(error, nmc, val);
                nvap->nva_type = nfstov_type(val, NFS_VER4);
+               if ((val == NFATTRDIR) || (val == NFNAMEDATTR))
+                       nvap->nva_flags |= NFS_FFLAG_IS_ATTR;
+               else
+                       nvap->nva_flags &= ~NFS_FFLAG_IS_ATTR;
                attrbytes -= NFSX_UNSIGNED;
        }
        if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_FH_EXPIRE_TYPE)) {
                nfsm_chain_get_32(error, nmc, val);
                nfsmout_if(error);
-               if (val != NFS_FH_PERSISTENT)
-                       printf("nfs: warning: non-persistent file handles!\n");
-               if (val & ~0xff)
-                       printf("nfs: warning unknown fh type: 0x%x\n", val);
                nfsap->nfsa_flags &= ~NFS_FSFLAG_FHTYPE_MASK;
                nfsap->nfsa_flags |= val << NFS_FSFLAG_FHTYPE_SHIFT;
+               if (val & ~0xff)
+                       printf("nfs: warning unknown fh type: 0x%x\n", val);
                attrbytes -= NFSX_UNSIGNED;
        }
        if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_CHANGE)) {
@@ -529,9 +1617,9 @@ nfs4_parsefattr(
        if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_NAMED_ATTR)) {
                nfsm_chain_get_32(error, nmc, val);
                if (val)
-                       nvap->nva_flags |= NFS_FFLAG_NAMED_ATTR;
+                       nvap->nva_flags |= NFS_FFLAG_HAS_NAMED_ATTRS;
                else
-                       nvap->nva_flags &= ~NFS_FFLAG_NAMED_ATTR;
+                       nvap->nva_flags &= ~NFS_FFLAG_HAS_NAMED_ATTRS;
                attrbytes -= NFSX_UNSIGNED;
        }
        if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_FSID)) {
@@ -552,26 +1640,82 @@ nfs4_parsefattr(
                attrbytes -= NFSX_UNSIGNED;
        }
        if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_RDATTR_ERROR)) {
-               nfsm_chain_get_32(error, nmc, error);
+               nfsm_chain_get_32(error, nmc, rderror);
                attrbytes -= NFSX_UNSIGNED;
-               nfsmout_if(error);
+               if (!rderror) { /* no error */
+                       NFS_BITMAP_CLR(bitmap, NFS_FATTR_RDATTR_ERROR);
+                       NFS_BITMAP_CLR(nvap->nva_bitmap, NFS_FATTR_RDATTR_ERROR);
+               }
        }
-       if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_ACL)) { /* skip for now */
+       if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_ACL)) {
+               error2 = 0;
+               ace_type = ace_flags = ace_mask = 0;
                nfsm_chain_get_32(error, nmc, val); /* ACE count */
+               if (!error && (val > KAUTH_ACL_MAX_ENTRIES))
+                       error = EOVERFLOW;
+               if (!error && !((acl = kauth_acl_alloc(val))))
+                       error = ENOMEM;
+               if (!error && acl) {
+                       acl->acl_entrycount = val;
+                       acl->acl_flags = 0;
+               }
+               attrbytes -= NFSX_UNSIGNED;
+               nfsm_assert(error, (attrbytes >= 0), EBADRPC);
                for (i=0; !error && (i < val); i++) {
-                       nfsm_chain_adv(error, nmc, 3 * NFSX_UNSIGNED);
-                       nfsm_chain_get_32(error, nmc, val2); /* string length */
-                       nfsm_chain_adv(error, nmc, nfsm_rndup(val2));
-                       attrbytes -= 4*NFSX_UNSIGNED + nfsm_rndup(val2);
+                       nfsm_chain_get_32(error, nmc, ace_type);
+                       nfsm_chain_get_32(error, nmc, ace_flags);
+                       nfsm_chain_get_32(error, nmc, ace_mask);
+                       nfsm_chain_get_32(error, nmc, len);
+                       if (!error && len >= NFS_MAX_WHO)
+                               error = EBADRPC;
+                       acl->acl_ace[i].ace_flags = nfs4_ace_nfstype_to_vfstype(ace_type, &error);
+                       acl->acl_ace[i].ace_flags |= nfs4_ace_nfsflags_to_vfsflags(ace_flags);
+                       acl->acl_ace[i].ace_rights = nfs4_ace_nfsmask_to_vfsrights(ace_mask);
+                       if (!error && !error2 && (len >= slen)) {
+                               if (s != sbuf) {
+                                       FREE(s, M_TEMP);
+                                       s = sbuf;
+                                       slen = sizeof(sbuf);
+                               }
+                               /* Let's add a bit more if we can to the allocation as to try and avoid future allocations */
+                               MALLOC(s, char*, (len + 16 < NFS_MAX_WHO) ? len+16 : NFS_MAX_WHO, M_TEMP, M_WAITOK);
+                               if (s)
+                                       slen = (len + 16 < NFS_MAX_WHO) ? len+16 : NFS_MAX_WHO;
+                               else
+                                       error = ENOMEM;
+                       }
+                       if (error2)
+                               nfsm_chain_adv(error, nmc, nfsm_rndup(len));
+                       else
+                               nfsm_chain_get_opaque(error, nmc, len, s);
+                       if (!error && !error2) {
+                               s[len] = '\0';
+                               error2 = nfs4_id2guid(s, &acl->acl_ace[i].ace_applicable,
+                                               (ace_flags & NFS_ACE_IDENTIFIER_GROUP));
+                               if (error2 && (nfs_idmap_ctrl & NFS_IDMAP_CTRL_LOG_FAILED_MAPPINGS))
+                                       printf("nfs4_parsefattr: ACE WHO %s is no one, no guid?, error %d\n", s, error2);
+                       }
+                       attrbytes -= 4*NFSX_UNSIGNED + nfsm_rndup(len);
                        nfsm_assert(error, (attrbytes >= 0), EBADRPC);
                }
+               nfsmout_if(error);
+               if ((nvap != &nva_dummy) && !error2) {
+                       nvap->nva_acl = acl;
+                       acl = NULL;
+               }
        }
        if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_ACLSUPPORT)) {
+               /*
+                * Support ACLs if: the server supports DENY/ALLOC ACEs and
+                * (just to be safe) FATTR_ACL is in the supported list too.
+                */
                nfsm_chain_get_32(error, nmc, val);
-               if (val)
+               if ((val & (NFS_ACL_SUPPORT_ALLOW_ACL|NFS_ACL_SUPPORT_DENY_ACL)) &&
+                   NFS_BITMAP_ISSET(nfsap->nfsa_supp_attr, NFS_FATTR_ACL)) {
                        nfsap->nfsa_flags |= NFS_FSFLAG_ACL;
-               else
+               } else {
                        nfsap->nfsa_flags &= ~NFS_FSFLAG_ACL;
+               }
                attrbytes -= NFSX_UNSIGNED;
        }
        if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_ARCHIVE)) { /* SF_ARCHIVED */
@@ -640,23 +1784,151 @@ nfs4_parsefattr(
                nfsm_chain_get_64(error, nmc, nfsap->nfsa_files_total);
                attrbytes -= 2 * NFSX_UNSIGNED;
        }
-       if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_FS_LOCATIONS)) { /* skip for now */
-               nfsm_chain_get_32(error, nmc, val); /* root path length */
-               nfsm_chain_adv(error, nmc, nfsm_rndup(val)); /* root path */
-               attrbytes -= (2 * NFSX_UNSIGNED) + nfsm_rndup(val);
-               nfsm_chain_get_32(error, nmc, val); /* location count */
-               for (i=0; !error && (i < val); i++) {
-                       nfsm_chain_get_32(error, nmc, val2); /* server string length */
-                       nfsm_chain_adv(error, nmc, nfsm_rndup(val2)); /* server string */
-                       attrbytes -= (2 * NFSX_UNSIGNED) + nfsm_rndup(val2);
-                       nfsm_chain_get_32(error, nmc, val2); /* pathname component count */
-                       for (j=0; !error && (j < val2); j++) {
-                               nfsm_chain_get_32(error, nmc, val3); /* component length */
-                               nfsm_chain_adv(error, nmc, nfsm_rndup(val3)); /* component */
-                               attrbytes -= NFSX_UNSIGNED + nfsm_rndup(val3);
-                               nfsm_assert(error, (attrbytes >= 0), EBADRPC);
+       if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_FS_LOCATIONS)) {
+               uint32_t loc, serv, comp;
+               struct nfs_fs_location *fsl;
+               struct nfs_fs_server *fss;
+               struct nfs_fs_path *fsp;
+
+               /* get root pathname */
+               fsp = &nfslsp->nl_root;
+               nfsm_chain_get_32(error, nmc, fsp->np_compcount); /* component count */
+               attrbytes -= NFSX_UNSIGNED;
+               /* sanity check component count */
+               if (!error && (fsp->np_compcount > MAXPATHLEN))
+                       error = EBADRPC;
+               nfsmout_if(error);
+               if (fsp->np_compcount) {
+                       MALLOC(fsp->np_components, char **, fsp->np_compcount * sizeof(char*), M_TEMP, M_WAITOK|M_ZERO);
+                       if (!fsp->np_components)
+                               error = ENOMEM;
+               }
+               for (comp = 0; comp < fsp->np_compcount; comp++) {
+                       nfsm_chain_get_32(error, nmc, val); /* component length */
+                       /* sanity check component length */
+                       if (!error && (val == 0)) {
+                               /*
+                                * Apparently some people think a path with zero components should
+                                * be encoded with one zero-length component.  So, just ignore any
+                                * zero length components.
+                                */
+                               comp--;
+                               fsp->np_compcount--;
+                               if (fsp->np_compcount == 0) {
+                                       FREE(fsp->np_components, M_TEMP);
+                                       fsp->np_components = NULL;
+                               }
+                               attrbytes -= NFSX_UNSIGNED;
+                               continue;
+                       }
+                       if (!error && ((val < 1) || (val > MAXPATHLEN)))
+                               error = EBADRPC;
+                       nfsmout_if(error);
+                       MALLOC(fsp->np_components[comp], char *, val+1, M_TEMP, M_WAITOK|M_ZERO);
+                       if (!fsp->np_components[comp])
+                               error = ENOMEM;
+                       nfsmout_if(error);
+                       nfsm_chain_get_opaque(error, nmc, val, fsp->np_components[comp]); /* component */
+                       attrbytes -= NFSX_UNSIGNED + nfsm_rndup(val);
+               }
+               nfsm_chain_get_32(error, nmc, nfslsp->nl_numlocs); /* fs location count */
+               attrbytes -= NFSX_UNSIGNED;
+               /* sanity check location count */
+               if (!error && (nfslsp->nl_numlocs > 256))
+                       error = EBADRPC;
+               nfsmout_if(error);
+               if (nfslsp->nl_numlocs > 0) {
+                       MALLOC(nfslsp->nl_locations, struct nfs_fs_location **, nfslsp->nl_numlocs * sizeof(struct nfs_fs_location*), M_TEMP, M_WAITOK|M_ZERO);
+                       if (!nfslsp->nl_locations)
+                               error = ENOMEM;
+               }
+               nfsmout_if(error);
+               for (loc = 0; loc < nfslsp->nl_numlocs; loc++) {
+                       nfsmout_if(error);
+                       MALLOC(fsl, struct nfs_fs_location *, sizeof(struct nfs_fs_location), M_TEMP, M_WAITOK|M_ZERO);
+                       if (!fsl)
+                               error = ENOMEM;
+                       nfslsp->nl_locations[loc] = fsl;
+                       nfsm_chain_get_32(error, nmc, fsl->nl_servcount); /* server count */
+                       attrbytes -= NFSX_UNSIGNED;
+                       /* sanity check server count */
+                       if (!error && ((fsl->nl_servcount < 1) || (fsl->nl_servcount > 256)))
+                               error = EBADRPC;
+                       nfsmout_if(error);
+                       MALLOC(fsl->nl_servers, struct nfs_fs_server **, fsl->nl_servcount * sizeof(struct nfs_fs_server*), M_TEMP, M_WAITOK|M_ZERO);
+                       if (!fsl->nl_servers)
+                               error = ENOMEM;
+                       for (serv = 0; serv < fsl->nl_servcount; serv++) {
+                               nfsmout_if(error);
+                               MALLOC(fss, struct nfs_fs_server *, sizeof(struct nfs_fs_server), M_TEMP, M_WAITOK|M_ZERO);
+                               if (!fss)
+                                       error = ENOMEM;
+                               fsl->nl_servers[serv] = fss;
+                               nfsm_chain_get_32(error, nmc, val); /* server name length */
+                               /* sanity check server name length */
+                               if (!error && ((val < 1) || (val > MAXPATHLEN)))
+                                       error = EINVAL;
+                               nfsmout_if(error);
+                               MALLOC(fss->ns_name, char *, val+1, M_TEMP, M_WAITOK|M_ZERO);
+                               if (!fss->ns_name)
+                                       error = ENOMEM;
+                               nfsm_chain_get_opaque(error, nmc, val, fss->ns_name); /* server name */
+                               attrbytes -= NFSX_UNSIGNED + nfsm_rndup(val);
+                               nfsmout_if(error);
+                               /* copy name to address if it converts to a sockaddr */
+                               if (nfs_uaddr2sockaddr(fss->ns_name, (struct sockaddr*)&ss)) {
+                                       fss->ns_addrcount = 1;
+                                       MALLOC(fss->ns_addresses, char **, sizeof(char *), M_TEMP, M_WAITOK|M_ZERO);
+                                       if (!fss->ns_addresses)
+                                               error = ENOMEM;
+                                       nfsmout_if(error);
+                                       MALLOC(fss->ns_addresses[0], char *, val+1, M_TEMP, M_WAITOK|M_ZERO);
+                                       if (!fss->ns_addresses[0])
+                                               error = ENOMEM;
+                                       nfsmout_if(error);
+                                       strlcpy(fss->ns_addresses[0], fss->ns_name, val+1);
+                               }
+                       }
+                       /* get pathname */
+                       fsp = &fsl->nl_path;
+                       nfsm_chain_get_32(error, nmc, fsp->np_compcount); /* component count */
+                       attrbytes -= NFSX_UNSIGNED;
+                       /* sanity check component count */
+                       if (!error && (fsp->np_compcount > MAXPATHLEN))
+                               error = EINVAL;
+                       nfsmout_if(error);
+                       if (fsp->np_compcount) {
+                               MALLOC(fsp->np_components, char **, fsp->np_compcount * sizeof(char*), M_TEMP, M_WAITOK|M_ZERO);
+                               if (!fsp->np_components)
+                                       error = ENOMEM;
+                       }
+                       for (comp = 0; comp < fsp->np_compcount; comp++) {
+                               nfsm_chain_get_32(error, nmc, val); /* component length */
+                               /* sanity check component length */
+                               if (!error && (val == 0)) {
+                                       /*
+                                        * Apparently some people think a path with zero components should
+                                        * be encoded with one zero-length component.  So, just ignore any
+                                        * zero length components.
+                                        */
+                                       comp--;
+                                       fsp->np_compcount--;
+                                       if (fsp->np_compcount == 0) {
+                                               FREE(fsp->np_components, M_TEMP);
+                                               fsp->np_components = NULL;
+                                       }
+                                       attrbytes -= NFSX_UNSIGNED;
+                                       continue;
+                               }
+                               if (!error && ((val < 1) || (val > MAXPATHLEN)))
+                                       error = EINVAL;
+                               nfsmout_if(error);
+                               MALLOC(fsp->np_components[comp], char *, val+1, M_TEMP, M_WAITOK|M_ZERO);
+                               if (!fsp->np_components[comp])
+                                       error = ENOMEM;
+                               nfsm_chain_get_opaque(error, nmc, val, fsp->np_components[comp]); /* component */
+                               attrbytes -= NFSX_UNSIGNED + nfsm_rndup(val);
                        }
-                       nfsm_assert(error, (attrbytes >= 0), EBADRPC);
                }
                nfsm_assert(error, (attrbytes >= 0), EBADRPC);
        }
@@ -724,34 +1996,74 @@ nfs4_parsefattr(
                attrbytes -= NFSX_UNSIGNED;
        }
        if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_OWNER)) {
-               /* XXX Need ID mapping infrastructure - use ugly hack for now */
                nfsm_chain_get_32(error, nmc, len);
-               nfsm_chain_get_opaque_pointer(error, nmc, len, s);
+               if (!error && len >= NFS_MAX_WHO)
+                       error = EBADRPC;
+               if (!error && (len >= slen)) {
+                       if (s != sbuf) {
+                               FREE(s, M_TEMP);
+                               s = sbuf;
+                               slen = sizeof(sbuf);
+                       }
+                       /* Let's add a bit more if we can to the allocation as to try and avoid future allocations */
+                       MALLOC(s, char*, (len + 16 < NFS_MAX_WHO) ? len+16 : NFS_MAX_WHO, M_TEMP, M_WAITOK);
+                       if (s)
+                               slen = (len + 16 < NFS_MAX_WHO) ? len+16 : NFS_MAX_WHO;
+                       else
+                               error = ENOMEM;
+               }
+               nfsm_chain_get_opaque(error, nmc, len, s);
+               if (!error) {
+                       s[len] = '\0';
+                       error = nfs4_id2guid(s, &nvap->nva_uuuid, 0);
+                       if (!error)
+                               error = kauth_cred_guid2uid(&nvap->nva_uuuid, &nvap->nva_uid);
+                       if (error) {
+                               /* unable to get either GUID or UID, set to default */
+                               nvap->nva_uid = (uid_t)((nfs_idmap_ctrl & NFS_IDMAP_CTRL_UNKNOWN_IS_99) ? 99 : -2);
+                               if (nfs_idmap_ctrl & NFS_IDMAP_CTRL_LOG_FAILED_MAPPINGS)
+                                       printf("nfs4_parsefattr: owner %s is no one, no %s?, error %d\n", s,
+                                               kauth_guid_equal(&nvap->nva_uuuid, &kauth_null_guid) ? "guid" : "uid",
+                                               error);
+                               error = 0;
+                       }
+               }
                attrbytes -= NFSX_UNSIGNED + nfsm_rndup(len);
-               nfsmout_if(error);
-               if ((*s >= '0') && (*s <= '9'))
-                       nvap->nva_uid = strtol(s, NULL, 10);
-               else if (!strncmp(s, "nobody@", 7))
-                       nvap->nva_uid = -2;
-               else if (!strncmp(s, "root@", 5))
-                       nvap->nva_uid = 0;
-               else
-                       nvap->nva_uid = 99; /* unknown */
        }
        if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_OWNER_GROUP)) {
-               /* XXX Need ID mapping infrastructure - use ugly hack for now */
                nfsm_chain_get_32(error, nmc, len);
-               nfsm_chain_get_opaque_pointer(error, nmc, len, s);
+               if (!error && len >= NFS_MAX_WHO)
+                       error = EBADRPC;
+               if (!error && (len >= slen)) {
+                       if (s != sbuf) {
+                               FREE(s, M_TEMP);
+                               s = sbuf;
+                               slen = sizeof(sbuf);
+                       }
+                       /* Let's add a bit more if we can to the allocation as to try and avoid future allocations */
+                       MALLOC(s, char*, (len + 16 < NFS_MAX_WHO) ? len+16 : NFS_MAX_WHO, M_TEMP, M_WAITOK);
+                       if (s)
+                               slen = (len + 16 < NFS_MAX_WHO) ? len+16 : NFS_MAX_WHO;
+                       else
+                               error = ENOMEM;
+               }
+               nfsm_chain_get_opaque(error, nmc, len, s);
+               if (!error) {
+                       s[len] = '\0';
+                       error = nfs4_id2guid(s, &nvap->nva_guuid, 1);
+                       if (!error)
+                               error = kauth_cred_guid2gid(&nvap->nva_guuid, &nvap->nva_gid);
+                       if (error) {
+                               /* unable to get either GUID or GID, set to default */
+                               nvap->nva_gid = (gid_t)((nfs_idmap_ctrl & NFS_IDMAP_CTRL_UNKNOWN_IS_99) ? 99 : -2);
+                               if (nfs_idmap_ctrl & NFS_IDMAP_CTRL_LOG_FAILED_MAPPINGS)
+                                       printf("nfs4_parsefattr: group %s is no one, no %s?, error %d\n", s,
+                                               kauth_guid_equal(&nvap->nva_guuid, &kauth_null_guid) ? "guid" : "gid",
+                                               error);
+                               error = 0;
+                       }
+               }
                attrbytes -= NFSX_UNSIGNED + nfsm_rndup(len);
-               nfsmout_if(error);
-               if ((*s >= '0') && (*s <= '9'))
-                       nvap->nva_gid = strtol(s, NULL, 10);
-               else if (!strncmp(s, "nobody@", 7))
-                       nvap->nva_gid = -2;
-               else if (!strncmp(s, "root@", 5))
-                       nvap->nva_uid = 0;
-               else
-                       nvap->nva_gid = 99; /* unknown */
        }
        if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_QUOTA_AVAIL_HARD)) {
                nfsm_chain_get_64(error, nmc, dqbp->dqb_bhardlimit);
@@ -828,14 +2140,32 @@ nfs4_parsefattr(
                nfsm_chain_adv(error, nmc, 4*NFSX_UNSIGNED); /* just skip it */
                attrbytes -= 4 * NFSX_UNSIGNED;
        }
-       if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_MOUNTED_ON_FILEID)) { /* skip for now */
+       if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_MOUNTED_ON_FILEID)) {
+#if CONFIG_TRIGGERS
+               /* we prefer the mounted on file ID, so just replace the fileid */
+               nfsm_chain_get_64(error, nmc, nvap->nva_fileid);
+#else
                nfsm_chain_adv(error, nmc, 2*NFSX_UNSIGNED);
+#endif
                attrbytes -= 2 * NFSX_UNSIGNED;
        }
        /* advance over any leftover attrbytes */
        nfsm_assert(error, (attrbytes >= 0), EBADRPC);
        nfsm_chain_adv(error, nmc, nfsm_rndup(attrbytes));
 nfsmout:
+       if (error)
+               nfs_fs_locations_cleanup(nfslsp);
+       if (!error && rderror)
+               error = rderror;
+       /* free up temporary resources */
+       if (s && (s != sbuf))
+               FREE(s, M_TEMP);
+       if (acl)
+               kauth_acl_free(acl);
+       if (error && nvap->nva_acl) {
+               kauth_acl_free(nvap->nva_acl);
+               nvap->nva_acl = NULL;
+       }
        return (error);
 }
 
@@ -845,51 +2175,18 @@ nfsmout:
 int
 nfsm_chain_add_fattr4_f(struct nfsm_chain *nmc, struct vnode_attr *vap, struct nfsmount *nmp)
 {
-       int error = 0, attrbytes, slen, i;
-       uint32_t *pattrbytes;
+       int error = 0, attrbytes, slen, len, i, isgroup;
+       uint32_t *pattrbytes, val, acecount;;
        uint32_t bitmap[NFS_ATTR_BITMAP_LEN];
-       char s[32];
+       char sbuf[64], *s;
+       kauth_acl_t acl;
+       gid_t gid;
 
-       /*
-        * Do this in two passes.
-        * First calculate the bitmap, then pack
-        * everything together and set the size.
-        */
+       s = sbuf;
+       slen = sizeof(sbuf);
 
-       NFS_CLEAR_ATTRIBUTES(bitmap);
-       if (VATTR_IS_ACTIVE(vap, va_data_size))
-               NFS_BITMAP_SET(bitmap, NFS_FATTR_SIZE);
-       if (VATTR_IS_ACTIVE(vap, va_acl)) {
-               // NFS_BITMAP_SET(bitmap, NFS_FATTR_ACL)
-       }
-       if (VATTR_IS_ACTIVE(vap, va_flags)) {
-               NFS_BITMAP_SET(bitmap, NFS_FATTR_ARCHIVE);
-               NFS_BITMAP_SET(bitmap, NFS_FATTR_HIDDEN);
-       }
-       // NFS_BITMAP_SET(bitmap, NFS_FATTR_MIMETYPE)
-       if (VATTR_IS_ACTIVE(vap, va_mode))
-               NFS_BITMAP_SET(bitmap, NFS_FATTR_MODE);
-       if (VATTR_IS_ACTIVE(vap, va_uid))
-               NFS_BITMAP_SET(bitmap, NFS_FATTR_OWNER);
-       if (VATTR_IS_ACTIVE(vap, va_gid))
-               NFS_BITMAP_SET(bitmap, NFS_FATTR_OWNER_GROUP);
-       // NFS_BITMAP_SET(bitmap, NFS_FATTR_SYSTEM)
-       if (vap->va_vaflags & VA_UTIMES_NULL) {
-               NFS_BITMAP_SET(bitmap, NFS_FATTR_TIME_ACCESS_SET);
-               NFS_BITMAP_SET(bitmap, NFS_FATTR_TIME_MODIFY_SET);
-       } else {
-               if (VATTR_IS_ACTIVE(vap, va_access_time))
-                       NFS_BITMAP_SET(bitmap, NFS_FATTR_TIME_ACCESS_SET);
-               if (VATTR_IS_ACTIVE(vap, va_modify_time))
-                       NFS_BITMAP_SET(bitmap, NFS_FATTR_TIME_MODIFY_SET);
-       }
-       if (VATTR_IS_ACTIVE(vap, va_backup_time))
-               NFS_BITMAP_SET(bitmap, NFS_FATTR_TIME_BACKUP);
-       if (VATTR_IS_ACTIVE(vap, va_create_time))
-               NFS_BITMAP_SET(bitmap, NFS_FATTR_TIME_CREATE);
-       /* and limit to what is supported by server */
-       for (i=0; i < NFS_ATTR_BITMAP_LEN; i++)
-               bitmap[i] &= nmp->nm_fsattr.nfsa_supp_attr[i];
+       /* First calculate the bitmap... */
+       nfs_vattr_set_bitmap(nmp, bitmap, vap);
 
        /*
         * Now pack it all together:
@@ -905,7 +2202,43 @@ nfsm_chain_add_fattr4_f(struct nfsm_chain *nmc, struct vnode_attr *vap, struct n
                nfsm_chain_add_64(error, nmc, vap->va_data_size);
                attrbytes += 2*NFSX_UNSIGNED;
        }
-       // NFS_BITMAP_ISSET(bitmap, NFS_FATTR_ACL)
+       if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_ACL)) {
+               acl = vap->va_acl;
+               if (!acl || (acl->acl_entrycount == KAUTH_FILESEC_NOACL))
+                       acecount = 0;
+               else
+                       acecount = acl->acl_entrycount;
+               nfsm_chain_add_32(error, nmc, acecount);
+               attrbytes += NFSX_UNSIGNED;
+               for (i=0; !error && (i < (int)acecount); i++) {
+                       val = (acl->acl_ace[i].ace_flags & KAUTH_ACE_KINDMASK);
+                       val = nfs4_ace_vfstype_to_nfstype(val, &error);
+                       nfsm_chain_add_32(error, nmc, val);
+                       val = nfs4_ace_vfsflags_to_nfsflags(acl->acl_ace[i].ace_flags);
+                       nfsm_chain_add_32(error, nmc, val);
+                       val = nfs4_ace_vfsrights_to_nfsmask(acl->acl_ace[i].ace_rights);
+                       nfsm_chain_add_32(error, nmc, val);
+                       len = slen;
+                       isgroup = (kauth_cred_guid2gid(&acl->acl_ace[i].ace_applicable, &gid) == 0);
+                       error = nfs4_guid2id(&acl->acl_ace[i].ace_applicable, s, &len, isgroup);
+                       if (error == ENOSPC) {
+                               if (s != sbuf) {
+                                       FREE(s, M_TEMP);
+                                       s = sbuf;
+                               }
+                               len += 8;
+                               MALLOC(s, char*, len, M_TEMP, M_WAITOK);
+                               if (s) {
+                                       slen = len;
+                                       error = nfs4_guid2id(&acl->acl_ace[i].ace_applicable, s, &len, isgroup);
+                               } else {
+                                       error = ENOMEM;
+                               }
+                       }
+                       nfsm_chain_add_name(error, nmc, s, len, nmp);
+                       attrbytes += 4*NFSX_UNSIGNED + nfsm_rndup(len);
+               }
+       }
        if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_ARCHIVE)) {
                nfsm_chain_add_32(error, nmc, (vap->va_flags & SF_ARCHIVED) ? 1 : 0);
                attrbytes += NFSX_UNSIGNED;
@@ -920,26 +2253,56 @@ nfsm_chain_add_fattr4_f(struct nfsm_chain *nmc, struct vnode_attr *vap, struct n
                attrbytes += NFSX_UNSIGNED;
        }
        if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_OWNER)) {
-               /* XXX Need ID mapping infrastructure - use ugly hack for now */
-               if (vap->va_uid == 0)
-                       slen = snprintf(s, sizeof(s), "root@localdomain");
-               else if (vap->va_uid == (uid_t)-2)
-                       slen = snprintf(s, sizeof(s), "nobody@localdomain");
-               else
-                       slen = snprintf(s, sizeof(s), "%d", vap->va_uid);
-               nfsm_chain_add_string(error, nmc, s, slen);
-               attrbytes += NFSX_UNSIGNED + nfsm_rndup(slen);
+               nfsmout_if(error);
+               /* if we have va_uuuid use it, otherwise use uid */
+               if (!VATTR_IS_ACTIVE(vap, va_uuuid)) {
+                       error = kauth_cred_uid2guid(vap->va_uid, &vap->va_uuuid);
+                       nfsmout_if(error);
+               }
+               len = slen;
+               error = nfs4_guid2id(&vap->va_uuuid, s, &len, 0);
+               if (error == ENOSPC) {
+                       if (s != sbuf) {
+                               FREE(s, M_TEMP);
+                               s = sbuf;
+                       }
+                       len += 8;
+                       MALLOC(s, char*, len, M_TEMP, M_WAITOK);
+                       if (s) {
+                               slen = len;
+                               error = nfs4_guid2id(&vap->va_uuuid, s, &len, 0);
+                       } else {
+                               error = ENOMEM;
+                       }
+               }
+               nfsm_chain_add_name(error, nmc, s, len, nmp);
+               attrbytes += NFSX_UNSIGNED + nfsm_rndup(len);
        }
        if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_OWNER_GROUP)) {
-               /* XXX Need ID mapping infrastructure - use ugly hack for now */
-               if (vap->va_gid == 0)
-                       slen = snprintf(s, sizeof(s), "root@localdomain");
-               else if (vap->va_gid == (gid_t)-2)
-                       slen = snprintf(s, sizeof(s), "nobody@localdomain");
-               else
-                       slen = snprintf(s, sizeof(s), "%d", vap->va_gid);
-               nfsm_chain_add_string(error, nmc, s, slen);
-               attrbytes += NFSX_UNSIGNED + nfsm_rndup(slen);
+               nfsmout_if(error);
+               /* if we have va_guuid use it, otherwise use gid */
+               if (!VATTR_IS_ACTIVE(vap, va_guuid)) {
+                       error = kauth_cred_gid2guid(vap->va_gid, &vap->va_guuid);
+                       nfsmout_if(error);
+               }
+               len = slen;
+               error = nfs4_guid2id(&vap->va_guuid, s, &len, 1);
+               if (error == ENOSPC) {
+                       if (s != sbuf) {
+                               FREE(s, M_TEMP);
+                               s = sbuf;
+                       }
+                       len += 8;
+                       MALLOC(s, char*, len, M_TEMP, M_WAITOK);
+                       if (s) {
+                               slen = len;
+                               error = nfs4_guid2id(&vap->va_guuid, s, &len, 1);
+                       } else {
+                               error = ENOMEM;
+                       }
+               }
+               nfsm_chain_add_name(error, nmc, s, len, nmp);
+               attrbytes += NFSX_UNSIGNED + nfsm_rndup(len);
        }
        // NFS_BITMAP_SET(bitmap, NFS_FATTR_SYSTEM)
        if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_TIME_ACCESS_SET)) {
@@ -978,16 +2341,100 @@ nfsm_chain_add_fattr4_f(struct nfsm_chain *nmc, struct vnode_attr *vap, struct n
        /* Now, set the attribute data length */
        *pattrbytes = txdr_unsigned(attrbytes);
 nfsmout:
+       if (s && (s != sbuf))
+               FREE(s, M_TEMP);
        return (error);
 }
 
+/*
+ * Got the given error and need to start recovery (if not already started).
+ * Note: nmp must be locked!
+ */
+void
+nfs_need_recover(struct nfsmount *nmp, int error)
+{
+       int wake = !(nmp->nm_state & NFSSTA_RECOVER);
+
+       nmp->nm_state |= NFSSTA_RECOVER;
+       if ((error == NFSERR_ADMIN_REVOKED) ||
+           (error == NFSERR_EXPIRED) ||
+           (error == NFSERR_STALE_CLIENTID))
+               nmp->nm_state |= NFSSTA_RECOVER_EXPIRED;
+       if (wake)
+               nfs_mount_sock_thread_wake(nmp);
+}
+
+/*
+ * After recovery due to state expiry, check each node and
+ * drop any lingering delegation we thought we had.
+ *
+ * If a node has an open that is not lost and is not marked
+ * for reopen, then we hold onto any delegation because it is
+ * likely newly-granted.
+ */
+static void
+nfs4_expired_check_delegation(nfsnode_t np, vfs_context_t ctx)
+{
+       struct nfsmount *nmp = NFSTONMP(np);
+       struct nfs_open_file *nofp;
+       int drop = 1;
+
+       if ((np->n_flag & NREVOKE) || !(np->n_openflags & N_DELEG_MASK))
+               return;
+
+       lck_mtx_lock(&np->n_openlock);
+
+       TAILQ_FOREACH(nofp, &np->n_opens, nof_link) {
+               if (!nofp->nof_opencnt)
+                       continue;
+               if (nofp->nof_flags & NFS_OPEN_FILE_LOST)
+                       continue;
+               if (nofp->nof_flags & NFS_OPEN_FILE_REOPEN)
+                       continue;
+               /* we have an open that is not lost and not marked for reopen */
+               // XXX print out what's keeping this node from dropping the delegation.
+               NP(nofp->nof_np, "nfs4_expired_check_delegation: !drop: opencnt %d flags 0x%x access %d %d mmap %d %d",
+                       nofp->nof_opencnt, nofp->nof_flags,
+                       nofp->nof_access, nofp->nof_deny,
+                       nofp->nof_mmap_access, nofp->nof_mmap_deny);
+               drop = 0;
+               break;
+       }
+
+       if (drop) {
+               /* need to drop a delegation */
+               if (np->n_dreturn.tqe_next != NFSNOLIST) {
+                       /* remove this node from the delegation return list */
+                       lck_mtx_lock(&nmp->nm_lock);
+                       if (np->n_dreturn.tqe_next != NFSNOLIST) {
+                               TAILQ_REMOVE(&nmp->nm_dreturnq, np, n_dreturn);
+                               np->n_dreturn.tqe_next = NFSNOLIST;
+                       }
+                       lck_mtx_unlock(&nmp->nm_lock);
+               }
+               if (np->n_openflags & N_DELEG_MASK) {
+                       np->n_openflags &= ~N_DELEG_MASK;
+                       lck_mtx_lock(&nmp->nm_lock);
+                       if (np->n_dlink.tqe_next != NFSNOLIST) {
+                               TAILQ_REMOVE(&nmp->nm_delegations, np, n_dlink);
+                               np->n_dlink.tqe_next = NFSNOLIST;
+                       }
+                       lck_mtx_unlock(&nmp->nm_lock);
+                       nfs4_delegreturn_rpc(nmp, np->n_fhp, np->n_fhsize, &np->n_dstateid,
+                               0, vfs_context_thread(ctx), vfs_context_ucred(ctx));
+               }
+       }
+
+       lck_mtx_unlock(&np->n_openlock);
+}
+
 /*
  * Recover state for an NFS mount.
  *
  * Iterates over all open files, reclaiming opens and lock state.
  */
 void
-nfs4_recover(struct nfsmount *nmp)
+nfs_recover(struct nfsmount *nmp)
 {
        struct timespec ts = { 1, 0 };
        int error, lost, reopen;
@@ -996,6 +2443,8 @@ nfs4_recover(struct nfsmount *nmp)
        struct nfs_file_lock *nflp, *nextnflp;
        struct nfs_lock_owner *nlop;
        thread_t thd = current_thread();
+       nfsnode_t np, nextnp;
+       struct timeval now;
 
 restart:
        error = 0;
@@ -1020,25 +2469,36 @@ restart:
        } while (nmp->nm_stateinuse);
        if (error) {
                if (error == EPIPE)
-                       printf("nfs recovery reconnecting\n");
+                       printf("nfs recovery reconnecting for %s, 0x%x\n", vfs_statfs(nmp->nm_mountp)->f_mntfromname, nmp->nm_stategenid);
                else
-                       printf("nfs recovery aborted\n");
+                       printf("nfs recovery aborted for %s, 0x%x\n", vfs_statfs(nmp->nm_mountp)->f_mntfromname, nmp->nm_stategenid);
                lck_mtx_unlock(&nmp->nm_lock);
                return;
        }
 
-       printf("nfs recovery started\n");
+       microuptime(&now);
+       if (now.tv_sec == nmp->nm_recover_start) {
+               printf("nfs recovery throttled for %s, 0x%x\n", vfs_statfs(nmp->nm_mountp)->f_mntfromname, nmp->nm_stategenid);
+               lck_mtx_unlock(&nmp->nm_lock);
+               tsleep(nfs_recover, (PZERO-1), "nfsrecoverrestart", hz);
+               goto restart;
+       }
+       nmp->nm_recover_start = now.tv_sec;
        if (++nmp->nm_stategenid == 0)
                ++nmp->nm_stategenid;
+       printf("nfs recovery started for %s, 0x%x\n", vfs_statfs(nmp->nm_mountp)->f_mntfromname, nmp->nm_stategenid);
        lck_mtx_unlock(&nmp->nm_lock);
 
        /* for each open owner... */
        TAILQ_FOREACH(noop, &nmp->nm_open_owners, noo_link) {
                /* for each of its opens... */
                TAILQ_FOREACH(nofp, &noop->noo_opens, nof_oolink) {
-                       if (!nofp->nof_access || (nofp->nof_flags & NFS_OPEN_FILE_LOST))
+                       if (!nofp->nof_access || (nofp->nof_flags & NFS_OPEN_FILE_LOST) || (nofp->nof_np->n_flag & NREVOKE))
                                continue;
                        lost = reopen = 0;
+                       /* for NFSv2/v3, just skip straight to lock reclaim */
+                       if (nmp->nm_vers < NFS_VER4)
+                               goto reclaim_locks;
                        if (nofp->nof_rw_drw)
                                error = nfs4_open_reclaim_rpc(nofp, NFS_OPEN_SHARE_ACCESS_BOTH, NFS_OPEN_SHARE_DENY_BOTH);
                        if (!error && nofp->nof_w_drw)
@@ -1056,45 +2516,80 @@ restart:
                         */
                        if (!error && nofp->nof_rw) {
                                error = nfs4_open_reclaim_rpc(nofp, NFS_OPEN_SHARE_ACCESS_BOTH, NFS_OPEN_SHARE_DENY_NONE);
-                               if ((error == NFSERR_ADMIN_REVOKED) || (error == NFSERR_EXPIRED) || (error == NFSERR_NO_GRACE))
-                                       reopen = 1;
+                               if ((error == NFSERR_ADMIN_REVOKED) || (error == NFSERR_EXPIRED) || (error == NFSERR_NO_GRACE)) {
+                                       reopen = error;
+                                       error = 0;
+                               }
                        }
-                       if (!error && nofp->nof_w) {
+                       if (!error && !reopen && nofp->nof_w) {
                                error = nfs4_open_reclaim_rpc(nofp, NFS_OPEN_SHARE_ACCESS_WRITE, NFS_OPEN_SHARE_DENY_NONE);
-                               if ((error == NFSERR_ADMIN_REVOKED) || (error == NFSERR_EXPIRED) || (error == NFSERR_NO_GRACE))
-                                       reopen = 1;
+                               if ((error == NFSERR_ADMIN_REVOKED) || (error == NFSERR_EXPIRED) || (error == NFSERR_NO_GRACE)) {
+                                       reopen = error;
+                                       error = 0;
+                               }
                        }
-                       if (!error && nofp->nof_r) {
+                       if (!error && !reopen && nofp->nof_r) {
                                error = nfs4_open_reclaim_rpc(nofp, NFS_OPEN_SHARE_ACCESS_READ, NFS_OPEN_SHARE_DENY_NONE);
-                               if ((error == NFSERR_ADMIN_REVOKED) || (error == NFSERR_EXPIRED) || (error == NFSERR_NO_GRACE))
-                                       reopen = 1;
+                               if ((error == NFSERR_ADMIN_REVOKED) || (error == NFSERR_EXPIRED) || (error == NFSERR_NO_GRACE)) {
+                                       reopen = error;
+                                       error = 0;
+                               }
                        }
 
-                       if (error) {
+                       /*
+                        * If we hold delegated state but we don't have any non-delegated opens,
+                        * then we should attempt to claim that state now (but don't return the
+                        * delegation unless asked to).
+                        */
+                       if ((nofp->nof_d_rw_drw || nofp->nof_d_w_drw || nofp->nof_d_r_drw ||
+                                   nofp->nof_d_rw_dw || nofp->nof_d_w_dw || nofp->nof_d_r_dw ||
+                                   nofp->nof_d_rw || nofp->nof_d_w || nofp->nof_d_r) &&
+                                   (!nofp->nof_rw_drw && !nofp->nof_w_drw && !nofp->nof_r_drw &&
+                                    !nofp->nof_rw_dw && !nofp->nof_w_dw && !nofp->nof_r_dw &&
+                                    !nofp->nof_rw && !nofp->nof_w && !nofp->nof_r)) {
+                               if (!error && !nfs_open_state_set_busy(nofp->nof_np, NULL)) {
+                                       error = nfs4_claim_delegated_state_for_node(nofp->nof_np, R_RECOVER);
+                                       if (!error && (nofp->nof_flags & NFS_OPEN_FILE_REOPEN))
+                                               reopen = EAGAIN;
+                                       nfs_open_state_clear_busy(nofp->nof_np);
+                                       /* if claim didn't go well, we may need to return delegation now */
+                                       if (nofp->nof_np->n_openflags & N_DELEG_RETURN) {
+                                               nfs4_delegation_return(nofp->nof_np, R_RECOVER, thd, noop->noo_cred);
+                                               if (!(nmp->nm_sockflags & NMSOCK_READY))
+                                                       error = ETIMEDOUT;  /* looks like we need a reconnect */
+                                       }
+                               }
+                       }
+
+                       /*
+                        * Handle any issue claiming open state.
+                        * Potential reopens need to first confirm that there are no locks.
+                        */
+                       if (error || reopen) {
                                /* restart recovery? */
                                if ((error == ETIMEDOUT) || nfs_mount_state_error_should_restart(error)) {
                                        if (error == ETIMEDOUT)
                                                nfs_need_reconnect(nmp);
-                                       tsleep(&lbolt, (PZERO-1), "nfsrecoverrestart", 0);
-                                       printf("nfs recovery restarting %d\n", error);
+                                       tsleep(nfs_recover, (PZERO-1), "nfsrecoverrestart", hz);
+                                       printf("nfs recovery restarting for %s, 0x%x, error %d\n",
+                                               vfs_statfs(nmp->nm_mountp)->f_mntfromname, nmp->nm_stategenid, error);
                                        goto restart;
                                }
-                               if (reopen && (nfs4_check_for_locks(noop, nofp) == 0)) {
+                               if (reopen && (nfs_check_for_locks(noop, nofp) == 0)) {
                                        /* just reopen the file on next access */
-                                       const char *vname = vnode_getname(NFSTOV(nofp->nof_np));
-                                       printf("nfs4_recover: %d, need reopen for %s\n", error, vname ? vname : "???");
-                                       vnode_putname(vname);
+                                       NP(nofp->nof_np, "nfs_recover: %d, need reopen for %d %p 0x%x", reopen,
+                                               kauth_cred_getuid(noop->noo_cred), nofp->nof_np, nofp->nof_np->n_flag);
                                        lck_mtx_lock(&nofp->nof_lock);
                                        nofp->nof_flags |= NFS_OPEN_FILE_REOPEN;
                                        lck_mtx_unlock(&nofp->nof_lock);
-                                       error = 0;
                                } else {
                                        /* open file state lost */
+                                       if (reopen)
+                                               NP(nofp->nof_np, "nfs_recover: %d, can't reopen because of locks %d %p", reopen,
+                                                       kauth_cred_getuid(noop->noo_cred), nofp->nof_np);
                                        lost = 1;
                                        error = 0;
-                                       lck_mtx_lock(&nofp->nof_lock);
-                                       nofp->nof_flags &= ~NFS_OPEN_FILE_REOPEN;
-                                       lck_mtx_unlock(&nofp->nof_lock);
+                                       reopen = 0;
                                }
                        } else {
                                /* no error, so make sure the reopen flag isn't set */
@@ -1102,83 +2597,96 @@ restart:
                                nofp->nof_flags &= ~NFS_OPEN_FILE_REOPEN;
                                lck_mtx_unlock(&nofp->nof_lock);
                        }
+
                        /*
                         * Scan this node's lock owner list for entries with this open owner,
                         * then walk the lock owner's held lock list recovering each lock.
                         */
-rescanlocks:
+reclaim_locks:
                        TAILQ_FOREACH(nlop, &nofp->nof_np->n_lock_owners, nlo_link) {
+                               if (lost || reopen)
+                                       break;
                                if (nlop->nlo_open_owner != noop)
                                        continue;
                                TAILQ_FOREACH_SAFE(nflp, &nlop->nlo_locks, nfl_lolink, nextnflp) {
+                                       /* skip dead & blocked lock requests (shouldn't be any in the held lock list) */
                                        if (nflp->nfl_flags & (NFS_FILE_LOCK_DEAD|NFS_FILE_LOCK_BLOCKED))
                                                continue;
-                                       if (!lost) {
-                                               error = nfs4_lock_rpc(nofp->nof_np, nofp, nflp, 1, thd, noop->noo_cred);
-                                               if (!error)
-                                                       continue;
-                                               /* restart recovery? */
-                                               if ((error == ETIMEDOUT) || nfs_mount_state_error_should_restart(error)) {
-                                                       if (error == ETIMEDOUT)
-                                                               nfs_need_reconnect(nmp);
-                                                       tsleep(&lbolt, (PZERO-1), "nfsrecoverrestart", 0);
-                                                       printf("nfs recovery restarting %d\n", error);
-                                                       goto restart;
-                                               }
-                                               /* lock state lost - attempt to close file */ 
-                                               lost = 1;
-                                               error = nfs4_close_rpc(nofp->nof_np, nofp, NULL, noop->noo_cred, R_RECOVER);
-                                               if ((error == ETIMEDOUT) || nfs_mount_state_error_should_restart(error)) {
-                                                       if (error == ETIMEDOUT)
-                                                               nfs_need_reconnect(nmp);
-                                                       tsleep(&lbolt, (PZERO-1), "nfsrecoverrestart", 0);
-                                                       printf("nfs recovery restarting %d\n", error);
-                                                       goto restart;
-                                               }
-                                               error = 0;
-                                               /* rescan locks so we can drop them all */
-                                               goto rescanlocks;
-                                       }
-                                       if (lost) {
-                                               /* kill/remove the lock */
-                                               lck_mtx_lock(&nofp->nof_np->n_openlock);
-                                               nflp->nfl_flags |= NFS_FILE_LOCK_DEAD;
-                                               lck_mtx_lock(&nlop->nlo_lock);
-                                               nextnflp = TAILQ_NEXT(nflp, nfl_lolink);
-                                               TAILQ_REMOVE(&nlop->nlo_locks, nflp, nfl_lolink);
-                                               lck_mtx_unlock(&nlop->nlo_lock);
-                                               if (nflp->nfl_blockcnt) {
-                                                       /* wake up anyone blocked on this lock */
-                                                       wakeup(nflp);
-                                               } else {
-                                                       /* remove nflp from lock list and destroy */
-                                                       TAILQ_REMOVE(&nofp->nof_np->n_locks, nflp, nfl_link);
-                                                       nfs_file_lock_destroy(nflp);
-                                               }
-                                               lck_mtx_unlock(&nofp->nof_np->n_openlock);
+                                       /* skip delegated locks */
+                                       if (nflp->nfl_flags & NFS_FILE_LOCK_DELEGATED)
+                                               continue;
+                                       error = nmp->nm_funcs->nf_setlock_rpc(nofp->nof_np, nofp, nflp, 1, R_RECOVER, thd, noop->noo_cred);
+                                       if (error)
+                                               NP(nofp->nof_np, "nfs: lock reclaim (0x%llx, 0x%llx) %s %d",
+                                                       nflp->nfl_start, nflp->nfl_end,
+                                                       error ? "failed" : "succeeded", error);
+                                       if (!error)
+                                               continue;
+                                       /* restart recovery? */
+                                       if ((error == ETIMEDOUT) || nfs_mount_state_error_should_restart(error)) {
+                                               if (error == ETIMEDOUT)
+                                                       nfs_need_reconnect(nmp);
+                                               tsleep(nfs_recover, (PZERO-1), "nfsrecoverrestart", hz);
+                                               printf("nfs recovery restarting for %s, 0x%x, error %d\n",
+                                                       vfs_statfs(nmp->nm_mountp)->f_mntfromname, nmp->nm_stategenid, error);
+                                               goto restart;
                                        }
+                                       /* lock state lost - attempt to close file */ 
+                                       lost = 1;
+                                       error = 0;
+                                       break;
+                               }
+                       }
+
+                       /*
+                        * If we've determined that we need to reopen the file then we probably
+                        * didn't receive any delegation we think we hold.  We should attempt to
+                        * return that delegation (and claim any delegated state).
+                        *
+                        * If we hold a delegation that is marked for return, then we should
+                        * return it now.
+                        */
+                       if ((nofp->nof_np->n_openflags & N_DELEG_RETURN) ||
+                           (reopen && (nofp->nof_np->n_openflags & N_DELEG_MASK))) {
+                               nfs4_delegation_return(nofp->nof_np, R_RECOVER, thd, noop->noo_cred);
+                               if (!(nmp->nm_sockflags & NMSOCK_READY)) {
+                                       /* looks like we need a reconnect */
+                                       tsleep(nfs_recover, (PZERO-1), "nfsrecoverrestart", hz);
+                                       printf("nfs recovery restarting for %s, 0x%x, error %d\n",
+                                               vfs_statfs(nmp->nm_mountp)->f_mntfromname, nmp->nm_stategenid, error);
+                                       goto restart;
                                }
                        }
+
                        if (lost) {
                                /* revoke open file state */
-                               lck_mtx_lock(&nofp->nof_lock);
-                               nofp->nof_flags |= NFS_OPEN_FILE_LOST;
-                               lck_mtx_unlock(&nofp->nof_lock);
-                               const char *vname = vnode_getname(NFSTOV(nofp->nof_np));
-                               printf("nfs4_recover: state lost for %s\n", vname ? vname : "???");
-                               vnode_putname(vname);
+                               NP(nofp->nof_np, "nfs_recover: state lost for %d %p 0x%x",
+                                       kauth_cred_getuid(noop->noo_cred), nofp->nof_np, nofp->nof_np->n_flag);
+                               nfs_revoke_open_state_for_node(nofp->nof_np);
                        }
                }
        }
 
        if (!error) {
+               /* If state expired, make sure we're not holding onto any stale delegations */
                lck_mtx_lock(&nmp->nm_lock);
-               nmp->nm_state &= ~NFSSTA_RECOVER;
+               if ((nmp->nm_vers >= NFS_VER4) && (nmp->nm_state & NFSSTA_RECOVER_EXPIRED)) {
+recheckdeleg:
+                       TAILQ_FOREACH_SAFE(np, &nmp->nm_delegations, n_dlink, nextnp) {
+                               lck_mtx_unlock(&nmp->nm_lock);
+                               nfs4_expired_check_delegation(np, vfs_context_kernel());
+                               lck_mtx_lock(&nmp->nm_lock);
+                               if (nextnp == NFSNOLIST)
+                                       goto recheckdeleg;
+                       }
+               }
+               nmp->nm_state &= ~(NFSSTA_RECOVER|NFSSTA_RECOVER_EXPIRED);
                wakeup(&nmp->nm_state);
-               printf("nfs recovery completed\n");
+               printf("nfs recovery completed for %s, 0x%x\n",
+                       vfs_statfs(nmp->nm_mountp)->f_mntfromname, nmp->nm_stategenid);
                lck_mtx_unlock(&nmp->nm_lock);
        } else {
-               printf("nfs recovery failed %d\n", error);
+               printf("nfs recovery failed for %s, 0x%x, error %d\n",
+                       vfs_statfs(nmp->nm_mountp)->f_mntfromname, nmp->nm_stategenid, error);
        }
 }
-