nfs4_subs.c   [plain text]


/*
 * Copyright (c) 2006-2011 Apple Inc. All rights reserved.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. The rights granted to you under the License
 * may not be used to create, or enable the creation or redistribution of,
 * unlawful or unlicensed copies of an Apple operating system, or to
 * circumvent, violate, or enable the circumvention or violation of, any
 * terms of an Apple operating system software license agreement.
 * 
 * Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
 */

/*
 * miscellaneous support functions for NFSv4
 */
#include <sys/param.h>
#include <sys/proc.h>
#include <sys/kauth.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/mount_internal.h>
#include <sys/vnode_internal.h>
#include <sys/kpi_mbuf.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/malloc.h>
#include <sys/syscall.h>
#include <sys/ubc_internal.h>
#include <sys/fcntl.h>
#include <sys/quota.h>
#include <sys/domain.h>
#include <libkern/OSAtomic.h>
#include <kern/thread_call.h>

#include <sys/vm.h>
#include <sys/vmparam.h>

#include <sys/time.h>
#include <kern/clock.h>

#include <nfs/rpcv2.h>
#include <nfs/nfsproto.h>
#include <nfs/nfs.h>
#include <nfs/nfsnode.h>
#include <nfs/xdr_subs.h>
#include <nfs/nfsm_subs.h>
#include <nfs/nfs_gss.h>
#include <nfs/nfsmount.h>
#include <nfs/nfs_lock.h>

#include <miscfs/specfs/specdev.h>

#include <netinet/in.h>
#include <net/kpi_interface.h>


/*
 * Create the unique client ID to use for this mount.
 *
 * Format: unique ID + en0_address + server_address + mntfromname + mntonname
 *
 * We could possibly use one client ID for all mounts of the same server;
 * however, that would complicate some aspects of state management.
 *
 * Each mount socket connection sends a SETCLIENTID.  If the ID is the same but
 * the verifier (mounttime) changes, then all previous (mounts') state gets dropped.
 *
 * State is typically managed per-mount and in order to keep it that way
 * each mount needs to use a separate client ID.  However, we also need to
 * make sure that each mount uses the same client ID each time.
 *
 * 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 (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)
{
	struct nfs_client_id *ncip, *ncip2;
	struct sockaddr *saddr;
	int error, len, len2, cmp;
	struct vfsstatfs *vsfs;

	static uint8_t en0addr[6];
	static uint8_t en0addr_set = 0;

	lck_mtx_lock(nfs_global_mutex);
	if (!en0addr_set) {
		ifnet_t interface = NULL;
		error = ifnet_find_by_name("en0", &interface);
		if (!error)
			error = ifnet_lladdr_copy_bytes(interface, en0addr, sizeof(en0addr));
		if (error)
			printf("nfs4_init_clientid: error getting en0 address, %d\n", error);
		if (!error)
			en0addr_set = 1;
		if (interface)
			ifnet_release(interface);
	}
	lck_mtx_unlock(nfs_global_mutex);

	MALLOC(ncip, struct nfs_client_id *, sizeof(struct nfs_client_id), M_TEMP, M_WAITOK);
	if (!ncip)
		return (ENOMEM);

	vsfs = vfs_statfs(nmp->nm_mountp);
	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)
		ncip->nci_idlen = NFS4_OPAQUE_LIMIT;
	MALLOC(ncip->nci_id, char *, ncip->nci_idlen, M_TEMP, M_WAITOK);
	if (!ncip->nci_id) {
		FREE(ncip, M_TEMP);
		return (ENOMEM);
	}

	*(uint32_t*)ncip->nci_id = 0;
	len = sizeof(uint32_t);
	len2 = min(sizeof(en0addr), ncip->nci_idlen-len);
	bcopy(en0addr, &ncip->nci_id[len], len2);
	len += sizeof(en0addr);
	len2 = min(saddr->sa_len, ncip->nci_idlen-len);
	bcopy(saddr, &ncip->nci_id[len], len2);
	len += len2;
	if (len < ncip->nci_idlen) {
		len2 = strlcpy(&ncip->nci_id[len], vsfs->f_mntfromname, ncip->nci_idlen-len);
		if (len2 < (ncip->nci_idlen - len))
			len += len2 + 1;
		else
			len = ncip->nci_idlen;
	}
	if (len < ncip->nci_idlen) {
		len2 = strlcpy(&ncip->nci_id[len], vsfs->f_mntonname, ncip->nci_idlen-len);
		if (len2 < (ncip->nci_idlen - len))
			len += len2 + 1;
		else
			len = ncip->nci_idlen;
	}

	/* make sure the ID is unique, and add it to the sorted list */
	lck_mtx_lock(nfs_global_mutex);
	TAILQ_FOREACH(ncip2, &nfsclientids, nci_link) {
		if (ncip->nci_idlen > ncip2->nci_idlen)
			continue;
		if (ncip->nci_idlen < ncip2->nci_idlen)
			break;
		cmp = bcmp(ncip->nci_id + sizeof(uint32_t),
			ncip2->nci_id + sizeof(uint32_t),
			ncip->nci_idlen - sizeof(uint32_t));
		if (cmp > 0)
			continue;
		if (cmp < 0)
			break;
		if (*(uint32_t*)ncip->nci_id > *(uint32_t*)ncip2->nci_id)
			continue;
		if (*(uint32_t*)ncip->nci_id < *(uint32_t*)ncip2->nci_id)
			break;
		*(uint32_t*)ncip->nci_id += 1;
	}
	if (*(uint32_t*)ncip->nci_id)
		printf("nfs client ID collision (%d) for %s on %s\n", *(uint32_t*)ncip->nci_id,
			vsfs->f_mntfromname, vsfs->f_mntonname);
	if (ncip2)
		TAILQ_INSERT_BEFORE(ncip2, ncip, nci_link);
	else
		TAILQ_INSERT_TAIL(&nfsclientids, ncip, nci_link);
	nmp->nm_longid = ncip;
	lck_mtx_unlock(nfs_global_mutex);

	return (0);
}

/*
 * NFSv4 SETCLIENTID
 */
int
nfs4_setclientid(struct nfsmount *nmp)
{
	uint64_t verifier, xid;
	int error = 0, status, numops;
	uint32_t bitmap[NFS_ATTR_BITMAP_LEN];
	thread_t thd;
	kauth_cred_t cred;
	struct nfsm_chain nmreq, nmrep;
	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());
	kauth_cred_ref(cred);

	nfsm_chain_null(&nmreq);
	nfsm_chain_null(&nmrep);

	if (!nmp->nm_longid)
		error = nfs4_init_clientid(nmp);

	// SETCLIENTID
	numops = 1;
	nfsm_chain_build_alloc_init(error, &nmreq, 14 * NFSX_UNSIGNED + nmp->nm_longid->nci_idlen);
	nfsm_chain_add_compound_header(error, &nmreq, "setclid", numops);
	numops--;
	nfsm_chain_add_32(error, &nmreq, NFS_OP_SETCLIENTID);
	/* nfs_client_id4  client; */
	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 (!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 */
		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 */
		nfsm_chain_add_32(error, &nmreq, 0); /* callback program */
		nfsm_chain_add_string(error, &nmreq, "", 0); /* callback r_netid */
		nfsm_chain_add_string(error, &nmreq, "", 0); /* callback r_addr */
		nfsm_chain_add_32(error, &nmreq, 0); /* callback_ident */
	}
	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);
	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");
	nfsmout_if(error);
	nfsm_chain_get_64(error, &nmrep, nmp->nm_clientid);
	nfsm_chain_get_64(error, &nmrep, verifier);
	nfsm_chain_cleanup(&nmreq);
	nfsm_chain_cleanup(&nmrep);

	// 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);
	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);
	nfsm_chain_op_check(error, &nmrep, NFS_OP_SETCLIENTID_CONFIRM);
	if (error)
		printf("nfs4_setclientid: confirm error %d\n", error);
	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);
	kauth_cred_unref(&cred);
	if (error)
		printf("nfs4_setclientid failed, %d\n", error);
	return (error);
}

/*
 * renew/check lease state on server
 */
int
nfs4_renew(struct nfsmount *nmp, int rpcflag)
{
	int error = 0, status, numops;
	u_int64_t xid;
	struct nfsm_chain nmreq, nmrep;
	kauth_cred_t cred;

	cred = IS_VALID_CRED(nmp->nm_mcred) ? nmp->nm_mcred : vfs_context_ucred(vfs_context_kernel());
	kauth_cred_ref(cred);

	nfsm_chain_null(&nmreq);
	nfsm_chain_null(&nmrep);

	// RENEW
	numops = 1;
	nfsm_chain_build_alloc_init(error, &nmreq, 8 * NFSX_UNSIGNED);
	nfsm_chain_add_compound_header(error, &nmreq, "renew", numops);
	numops--;
	nfsm_chain_add_32(error, &nmreq, NFS_OP_RENEW);
	nfsm_chain_add_64(error, &nmreq, nmp->nm_clientid);
	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,
			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);
nfsmout:
	nfsm_chain_cleanup(&nmreq);
	nfsm_chain_cleanup(&nmrep);
	kauth_cred_unref(&cred);
	return (error);
}


/*
 * periodic timer to renew lease state on server
 */
void
nfs4_renew_timer(void *param0, __unused void *param1)
{
	struct nfsmount *nmp = param0;
	u_int64_t clientid;
	int error = 0, interval;

	lck_mtx_lock(&nmp->nm_lock);
	clientid = nmp->nm_clientid;
	if ((nmp->nm_state & NFSSTA_RECOVER) || !(nmp->nm_sockflags & NMSOCK_READY)) {
		lck_mtx_unlock(&nmp->nm_lock);
		goto out;
	}
	lck_mtx_unlock(&nmp->nm_lock);

	error = nfs4_renew(nmp, R_RECOVER);
out:
	if (error == ETIMEDOUT)
		nfs_need_reconnect(nmp);
	else if (error)
		printf("nfs4_renew_timer: error %d\n", error);
	lck_mtx_lock(&nmp->nm_lock);
	if (error && (error != ETIMEDOUT) &&
	    (nmp->nm_clientid == clientid) && !(nmp->nm_state & NFSSTA_RECOVER)) {
		printf("nfs4_renew_timer: error %d, initiating recovery\n", error);
		nfs_need_recover(nmp, error);
	}

	interval = nmp->nm_fsattr.nfsa_lease / (error ? 4 : 2);
	if ((interval < 1) || (nmp->nm_state & NFSSTA_RECOVER))
		interval = 1;
	lck_mtx_unlock(&nmp->nm_lock);
	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
 */
void
nfs_vattr_set_supported(uint32_t *bitmap, struct vnode_attr *vap)
{
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_TYPE))
		VATTR_SET_SUPPORTED(vap, va_type);
	// 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_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_ARCHIVE))
		VATTR_SET_SUPPORTED(vap, va_flags);
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_FILEID))
		VATTR_SET_SUPPORTED(vap, va_fileid);
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_HIDDEN))
		VATTR_SET_SUPPORTED(vap, va_flags);
	// if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_MIMETYPE))
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_MODE))
		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)) {
		VATTR_SET_SUPPORTED(vap, va_uid);
		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))
		VATTR_SET_SUPPORTED(vap, va_total_alloc);
	// if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_SYSTEM))
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_TIME_ACCESS))
		VATTR_SET_SUPPORTED(vap, va_access_time);
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_TIME_BACKUP))
		VATTR_SET_SUPPORTED(vap, va_backup_time);
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_TIME_CREATE))
		VATTR_SET_SUPPORTED(vap, va_create_time);
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_TIME_METADATA))
		VATTR_SET_SUPPORTED(vap, va_change_time);
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_TIME_MODIFY))
		VATTR_SET_SUPPORTED(vap, va_modify_time);
}

/*
 * Parse the attributes that are in the mbuf list and store them in
 * the given structures.
 */
int
nfs4_parsefattr(
	struct nfsm_chain *nmc,
	struct nfs_fsattr *nfsap,
	struct nfs_vattr *nvap,
	fhandle_t *fhp,
	struct dqblk *dqbp,
	struct nfs_fs_locations *nfslsp)
{
	int error = 0, error2, rderror = 0, attrbytes;
	uint32_t val, val2, val3, i;
	uint32_t bitmap[NFS_ATTR_BITMAP_LEN], len, 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)
		nfsap = &nfsa_dummy;
	if (!nvap)
		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);
	/* add bits to object/fs attr bitmaps */
	for (i=0; i < NFS_ATTR_BITMAP_LEN; i++) {
		nvap->nva_bitmap[i] |= bitmap[i] & nfs_object_attr_bitmap[i];
		nfsap->nfsa_bitmap[i] |= bitmap[i] & nfs_fs_attr_bitmap[i];
	}

	nfsm_chain_get_32(error, nmc, attrbytes);
	nfsmout_if(error);

	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_SUPPORTED_ATTRS)) {
		len = NFS_ATTR_BITMAP_LEN;
		nfsm_chain_get_bitmap(error, nmc, nfsap->nfsa_supp_attr, len);
		attrbytes -= (len + 1) * NFSX_UNSIGNED;
	}
	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);
		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)) {
		nfsm_chain_get_64(error, nmc, nvap->nva_change);
		attrbytes -= 2 * NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_SIZE)) {
		nfsm_chain_get_64(error, nmc, nvap->nva_size);
		attrbytes -= 2 * NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_LINK_SUPPORT)) {
		nfsm_chain_get_32(error, nmc, val);
		if (val)
			nfsap->nfsa_flags |= NFS_FSFLAG_LINK;
		else
			nfsap->nfsa_flags &= ~NFS_FSFLAG_LINK;
		attrbytes -= NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_SYMLINK_SUPPORT)) {
		nfsm_chain_get_32(error, nmc, val);
		if (val)
			nfsap->nfsa_flags |= NFS_FSFLAG_SYMLINK;
		else
			nfsap->nfsa_flags &= ~NFS_FSFLAG_SYMLINK;
		attrbytes -= NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_NAMED_ATTR)) {
		nfsm_chain_get_32(error, nmc, val);
		if (val)
			nvap->nva_flags |= NFS_FFLAG_HAS_NAMED_ATTRS;
		else
			nvap->nva_flags &= ~NFS_FFLAG_HAS_NAMED_ATTRS;
		attrbytes -= NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_FSID)) {
		nfsm_chain_get_64(error, nmc, nvap->nva_fsid.major);
		nfsm_chain_get_64(error, nmc, nvap->nva_fsid.minor);
		attrbytes -= 4 * NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_UNIQUE_HANDLES)) {
		nfsm_chain_get_32(error, nmc, val);
		if (val)
			nfsap->nfsa_flags |= NFS_FSFLAG_UNIQUE_FH;
		else
			nfsap->nfsa_flags &= ~NFS_FSFLAG_UNIQUE_FH;
		attrbytes -= NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_LEASE_TIME)) {
		nfsm_chain_get_32(error, nmc, nfsap->nfsa_lease);
		attrbytes -= NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_RDATTR_ERROR)) {
		nfsm_chain_get_32(error, nmc, rderror);
		attrbytes -= NFSX_UNSIGNED;
		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)) {
		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_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);
			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);
				}
				MALLOC(s, char*, len+16, M_TEMP, M_WAITOK);
				if (s)
					slen = len+16;
				else
					error2 = 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 & (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 {
			nfsap->nfsa_flags &= ~NFS_FSFLAG_ACL;
		}
		attrbytes -= NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_ARCHIVE)) { /* SF_ARCHIVED */
		nfsm_chain_get_32(error, nmc, val);
		if (val)
			nvap->nva_flags |= NFS_FFLAG_ARCHIVED;
		else
			nvap->nva_flags &= ~NFS_FFLAG_ARCHIVED;
		attrbytes -= NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_CANSETTIME)) {
		nfsm_chain_get_32(error, nmc, val);
		if (val)
			nfsap->nfsa_flags |= NFS_FSFLAG_SET_TIME;
		else
			nfsap->nfsa_flags &= ~NFS_FSFLAG_SET_TIME;
		attrbytes -= NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_CASE_INSENSITIVE)) {
		nfsm_chain_get_32(error, nmc, val);
		if (val)
			nfsap->nfsa_flags |= NFS_FSFLAG_CASE_INSENSITIVE;
		else
			nfsap->nfsa_flags &= ~NFS_FSFLAG_CASE_INSENSITIVE;
		attrbytes -= NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_CASE_PRESERVING)) {
		nfsm_chain_get_32(error, nmc, val);
		if (val)
			nfsap->nfsa_flags |= NFS_FSFLAG_CASE_PRESERVING;
		else
			nfsap->nfsa_flags &= ~NFS_FSFLAG_CASE_PRESERVING;
		attrbytes -= NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_CHOWN_RESTRICTED)) {
		nfsm_chain_get_32(error, nmc, val);
		if (val)
			nfsap->nfsa_flags |= NFS_FSFLAG_CHOWN_RESTRICTED;
		else
			nfsap->nfsa_flags &= ~NFS_FSFLAG_CHOWN_RESTRICTED;
		attrbytes -= NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_FILEHANDLE)) {
		nfsm_chain_get_32(error, nmc, val);
		if (fhp) {
			fhp->fh_len = val;
			nfsm_chain_get_opaque(error, nmc, nfsm_rndup(val), fhp->fh_data);
		} else {
			nfsm_chain_adv(error, nmc, nfsm_rndup(val));
		}
		attrbytes -= NFSX_UNSIGNED + nfsm_rndup(val);
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_FILEID)) {
		nfsm_chain_get_64(error, nmc, nvap->nva_fileid);
		attrbytes -= 2 * NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_FILES_AVAIL)) {
		nfsm_chain_get_64(error, nmc, nfsap->nfsa_files_avail);
		attrbytes -= 2 * NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_FILES_FREE)) {
		nfsm_chain_get_64(error, nmc, nfsap->nfsa_files_free);
		attrbytes -= 2 * NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_FILES_TOTAL)) {
		nfsm_chain_get_64(error, nmc, nfsap->nfsa_files_total);
		attrbytes -= 2 * NFSX_UNSIGNED;
	}
	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);
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_HIDDEN)) { /* UF_HIDDEN */
		nfsm_chain_get_32(error, nmc, val);
		if (val)
			nvap->nva_flags |= NFS_FFLAG_HIDDEN;
		else
			nvap->nva_flags &= ~NFS_FFLAG_HIDDEN;
		attrbytes -= NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_HOMOGENEOUS)) {
		/* XXX If NOT homogeneous, we may need to clear flags on the mount */
		nfsm_chain_get_32(error, nmc, val);
		if (val)
			nfsap->nfsa_flags |= NFS_FSFLAG_HOMOGENEOUS;
		else
			nfsap->nfsa_flags &= ~NFS_FSFLAG_HOMOGENEOUS;
		attrbytes -= NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_MAXFILESIZE)) {
		nfsm_chain_get_64(error, nmc, nfsap->nfsa_maxfilesize);
		attrbytes -= 2 * NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_MAXLINK)) {
		nfsm_chain_get_32(error, nmc, nvap->nva_maxlink);
		if (!error && (nfsap->nfsa_maxlink > INT32_MAX))
			nfsap->nfsa_maxlink = INT32_MAX;
		attrbytes -= NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_MAXNAME)) {
		nfsm_chain_get_32(error, nmc, nfsap->nfsa_maxname);
		if (!error && (nfsap->nfsa_maxname > INT32_MAX))
			nfsap->nfsa_maxname = INT32_MAX;
		attrbytes -= NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_MAXREAD)) {
		nfsm_chain_get_64(error, nmc, nfsap->nfsa_maxread);
		attrbytes -= 2 * NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_MAXWRITE)) {
		nfsm_chain_get_64(error, nmc, nfsap->nfsa_maxwrite);
		attrbytes -= 2 * NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_MIMETYPE)) {
		nfsm_chain_get_32(error, nmc, val);
		nfsm_chain_adv(error, nmc, nfsm_rndup(val));
		attrbytes -= NFSX_UNSIGNED + nfsm_rndup(val);
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_MODE)) {
		nfsm_chain_get_32(error, nmc, nvap->nva_mode);
		attrbytes -= NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_NO_TRUNC)) {
		nfsm_chain_get_32(error, nmc, val);
		if (val)
			nfsap->nfsa_flags |= NFS_FSFLAG_NO_TRUNC;
		else
			nfsap->nfsa_flags &= ~NFS_FSFLAG_NO_TRUNC;
		attrbytes -= NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_NUMLINKS)) {
		nfsm_chain_get_32(error, nmc, val);
		nvap->nva_nlink = val;
		attrbytes -= NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_OWNER)) {
		nfsm_chain_get_32(error, nmc, len);
		if (!error && (len >= slen)) {
			if (s != sbuf) {
				FREE(s, M_TEMP);
				s = sbuf;
				slen = sizeof(sbuf);
			}
			MALLOC(s, char*, len+16, M_TEMP, M_WAITOK);
			if (s)
				slen = len+16;
			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);
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_OWNER_GROUP)) {
		nfsm_chain_get_32(error, nmc, len);
		if (!error && (len >= slen)) {
			if (s != sbuf) {
				FREE(s, M_TEMP);
				s = sbuf;
				slen = sizeof(sbuf);
			}
			MALLOC(s, char*, len+16, M_TEMP, M_WAITOK);
			if (s)
				slen = len+16;
			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);
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_QUOTA_AVAIL_HARD)) {
		nfsm_chain_get_64(error, nmc, dqbp->dqb_bhardlimit);
		attrbytes -= 2 * NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_QUOTA_AVAIL_SOFT)) {
		nfsm_chain_get_64(error, nmc, dqbp->dqb_bsoftlimit);
		attrbytes -= 2 * NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_QUOTA_USED)) {
		nfsm_chain_get_64(error, nmc, dqbp->dqb_curbytes);
		attrbytes -= 2 * NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_RAWDEV)) {
		nfsm_chain_get_32(error, nmc, nvap->nva_rawdev.specdata1);
		nfsm_chain_get_32(error, nmc, nvap->nva_rawdev.specdata2);
		attrbytes -= 2 * NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_SPACE_AVAIL)) {
		nfsm_chain_get_64(error, nmc, nfsap->nfsa_space_avail);
		attrbytes -= 2 * NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_SPACE_FREE)) {
		nfsm_chain_get_64(error, nmc, nfsap->nfsa_space_free);
		attrbytes -= 2 * NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_SPACE_TOTAL)) {
		nfsm_chain_get_64(error, nmc, nfsap->nfsa_space_total);
		attrbytes -= 2 * NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_SPACE_USED)) {
		nfsm_chain_get_64(error, nmc, nvap->nva_bytes);
		attrbytes -= 2 * NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_SYSTEM)) {
		/* we'd support this if we had a flag to map it to... */
		nfsm_chain_adv(error, nmc, NFSX_UNSIGNED);
		attrbytes -= NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_TIME_ACCESS)) {
		nfsm_chain_get_64(error, nmc, nvap->nva_timesec[NFSTIME_ACCESS]);
		nfsm_chain_get_32(error, nmc, nvap->nva_timensec[NFSTIME_ACCESS]);
		attrbytes -= 3 * NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_TIME_ACCESS_SET)) {
		nfsm_chain_adv(error, nmc, 4*NFSX_UNSIGNED); /* just skip it */
		attrbytes -= 4 * NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_TIME_BACKUP)) {
		nfsm_chain_get_64(error, nmc, nvap->nva_timesec[NFSTIME_BACKUP]);
		nfsm_chain_get_32(error, nmc, nvap->nva_timensec[NFSTIME_BACKUP]);
		attrbytes -= 3 * NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_TIME_CREATE)) {
		nfsm_chain_get_64(error, nmc, nvap->nva_timesec[NFSTIME_CREATE]);
		nfsm_chain_get_32(error, nmc, nvap->nva_timensec[NFSTIME_CREATE]);
		attrbytes -= 3 * NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_TIME_DELTA)) { /* skip for now */
		nfsm_chain_adv(error, nmc, 3*NFSX_UNSIGNED);
		attrbytes -= 3 * NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_TIME_METADATA)) {
		nfsm_chain_get_64(error, nmc, nvap->nva_timesec[NFSTIME_CHANGE]);
		nfsm_chain_get_32(error, nmc, nvap->nva_timensec[NFSTIME_CHANGE]);
		attrbytes -= 3 * NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_TIME_MODIFY)) {
		nfsm_chain_get_64(error, nmc, nvap->nva_timesec[NFSTIME_MODIFY]);
		nfsm_chain_get_32(error, nmc, nvap->nva_timensec[NFSTIME_MODIFY]);
		attrbytes -= 3 * NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_TIME_MODIFY_SET)) {
		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)) {
#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);
}

/*
 * Add an NFSv4 "sattr" structure to an mbuf chain
 */
int
nfsm_chain_add_fattr4_f(struct nfsm_chain *nmc, struct vnode_attr *vap, struct nfsmount *nmp)
{
	int error = 0, attrbytes, slen, len, i, isgroup;
	uint32_t *pattrbytes, val, acecount;;
	uint32_t bitmap[NFS_ATTR_BITMAP_LEN];
	char sbuf[64], *s;
	kauth_acl_t acl;
	gid_t gid;

	s = sbuf;
	slen = sizeof(sbuf);

	/* First calculate the bitmap... */
	nfs_vattr_set_bitmap(nmp, bitmap, vap);

	/*
	 * Now pack it all together:
	 *     BITMAP, #BYTES, ATTRS
	 * Keep a pointer to the length so we can set it later.
	 */
	nfsm_chain_add_bitmap(error, nmc, bitmap, NFS_ATTR_BITMAP_LEN);
	attrbytes = 0;
	nfsm_chain_add_32(error, nmc, attrbytes);
	pattrbytes = (uint32_t*)(nmc->nmc_ptr - NFSX_UNSIGNED);

	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_SIZE)) {
		nfsm_chain_add_64(error, nmc, vap->va_data_size);
		attrbytes += 2*NFSX_UNSIGNED;
	}
	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;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_HIDDEN)) {
		nfsm_chain_add_32(error, nmc, (vap->va_flags & UF_HIDDEN) ? 1 : 0);
		attrbytes += NFSX_UNSIGNED;
	}
	// NFS_BITMAP_ISSET(bitmap, NFS_FATTR_MIMETYPE)
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_MODE)) {
		nfsm_chain_add_32(error, nmc, vap->va_mode);
		attrbytes += NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_OWNER)) {
		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)) {
		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)) {
		if (vap->va_vaflags & VA_UTIMES_NULL) {
			nfsm_chain_add_32(error, nmc, NFS4_TIME_SET_TO_SERVER);
			attrbytes += NFSX_UNSIGNED;
		} else {
			nfsm_chain_add_32(error, nmc, NFS4_TIME_SET_TO_CLIENT);
			nfsm_chain_add_64(error, nmc, vap->va_access_time.tv_sec);
			nfsm_chain_add_32(error, nmc, vap->va_access_time.tv_nsec);
			attrbytes += 4*NFSX_UNSIGNED;
		}
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_TIME_BACKUP)) {
		nfsm_chain_add_64(error, nmc, vap->va_backup_time.tv_sec);
		nfsm_chain_add_32(error, nmc, vap->va_backup_time.tv_nsec);
		attrbytes += 3*NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_TIME_CREATE)) {
		nfsm_chain_add_64(error, nmc, vap->va_create_time.tv_sec);
		nfsm_chain_add_32(error, nmc, vap->va_create_time.tv_nsec);
		attrbytes += 3*NFSX_UNSIGNED;
	}
	if (NFS_BITMAP_ISSET(bitmap, NFS_FATTR_TIME_MODIFY_SET)) {
		if (vap->va_vaflags & VA_UTIMES_NULL) {
			nfsm_chain_add_32(error, nmc, NFS4_TIME_SET_TO_SERVER);
			attrbytes += NFSX_UNSIGNED;
		} else {
			nfsm_chain_add_32(error, nmc, NFS4_TIME_SET_TO_CLIENT);
			nfsm_chain_add_64(error, nmc, vap->va_modify_time.tv_sec);
			nfsm_chain_add_32(error, nmc, vap->va_modify_time.tv_nsec);
			attrbytes += 4*NFSX_UNSIGNED;
		}
	}
	nfsmout_if(error);
	/* 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
nfs_recover(struct nfsmount *nmp)
{
	struct timespec ts = { 1, 0 };
	int error, lost, reopen;
	struct nfs_open_owner *noop;
	struct nfs_open_file *nofp;
	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;
	lck_mtx_lock(&nmp->nm_lock);
	/*
	 * First, wait for the state inuse count to go to zero so
	 * we know there are no state operations in progress.
	 */
	do {
		if ((error = nfs_sigintr(nmp, NULL, NULL, 1)))
			break;
		if (!(nmp->nm_sockflags & NMSOCK_READY))
			error = EPIPE;
		if (nmp->nm_state & NFSSTA_FORCE)
			error = ENXIO;
		if (nmp->nm_sockflags & NMSOCK_UNMOUNT)
			error = ENXIO;
		if (error)
			break;
		if (nmp->nm_stateinuse)
			msleep(&nmp->nm_stateinuse, &nmp->nm_lock, (PZERO-1), "nfsrecoverstartwait", &ts);
	} while (nmp->nm_stateinuse);
	if (error) {
		if (error == EPIPE)
			printf("nfs recovery reconnecting for %s, 0x%x\n", vfs_statfs(nmp->nm_mountp)->f_mntfromname, nmp->nm_stategenid);
		else
			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;
	}

	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(&lbolt, (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) || (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)
				error = nfs4_open_reclaim_rpc(nofp, NFS_OPEN_SHARE_ACCESS_WRITE, NFS_OPEN_SHARE_DENY_BOTH);
			if (!error && nofp->nof_r_drw)
				error = nfs4_open_reclaim_rpc(nofp, NFS_OPEN_SHARE_ACCESS_READ, NFS_OPEN_SHARE_DENY_BOTH);
			if (!error && nofp->nof_rw_dw)
				error = nfs4_open_reclaim_rpc(nofp, NFS_OPEN_SHARE_ACCESS_BOTH, NFS_OPEN_SHARE_DENY_WRITE);
			if (!error && nofp->nof_w_dw)
				error = nfs4_open_reclaim_rpc(nofp, NFS_OPEN_SHARE_ACCESS_WRITE, NFS_OPEN_SHARE_DENY_WRITE);
			if (!error && nofp->nof_r_dw)
				error = nfs4_open_reclaim_rpc(nofp, NFS_OPEN_SHARE_ACCESS_READ, NFS_OPEN_SHARE_DENY_WRITE);
			/*
			 * deny-none opens with no locks can just be reopened (later) if reclaim fails.
			 */
			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 = error;
					error = 0;
				}
			}
			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 = error;
					error = 0;
				}
			}
			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 = error;
					error = 0;
				}
			}

			/*
			 * 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 for %s, 0x%x, error %d\n",
						vfs_statfs(nmp->nm_mountp)->f_mntfromname, nmp->nm_stategenid, error);
					goto restart;
				}
				if (reopen && (nfs_check_for_locks(noop, nofp) == 0)) {
					/* just reopen the file on next access */
					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);
				} 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;
					reopen = 0;
				}
			} else {
				/* no error, so make sure the reopen flag isn't set */
				lck_mtx_lock(&nofp->nof_lock);
				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.
			 */
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;
					/* 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(&lbolt, (PZERO-1), "nfsrecoverrestart", 0);
						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(&lbolt, (PZERO-1), "nfsrecoverrestart", 0);
					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 */
				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);
		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 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 for %s, 0x%x, error %d\n",
			vfs_statfs(nmp->nm_mountp)->f_mntfromname, nmp->nm_stategenid, error);
	}
}