]> git.saurik.com Git - apple/xnu.git/blobdiff - bsd/nfs/nfs4_vnops.c
xnu-1228.tar.gz
[apple/xnu.git] / bsd / nfs / nfs4_vnops.c
diff --git a/bsd/nfs/nfs4_vnops.c b/bsd/nfs/nfs4_vnops.c
new file mode 100644 (file)
index 0000000..d8247ea
--- /dev/null
@@ -0,0 +1,2151 @@
+/*
+ * Copyright (c) 2006-2007 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@
+ */
+
+/*
+ * vnode op calls for NFS version 4
+ */
+#include <sys/param.h>
+#include <sys/kernel.h>
+#include <sys/systm.h>
+#include <sys/resourcevar.h>
+#include <sys/proc_internal.h>
+#include <sys/kauth.h>
+#include <sys/mount_internal.h>
+#include <sys/malloc.h>
+#include <sys/kpi_mbuf.h>
+#include <sys/conf.h>
+#include <sys/vnode_internal.h>
+#include <sys/dirent.h>
+#include <sys/fcntl.h>
+#include <sys/lockf.h>
+#include <sys/ubc_internal.h>
+#include <sys/attr.h>
+#include <sys/signalvar.h>
+#include <sys/uio_internal.h>
+
+#include <vfs/vfs_support.h>
+
+#include <sys/vm.h>
+
+#include <sys/time.h>
+#include <kern/clock.h>
+#include <libkern/OSAtomic.h>
+
+#include <miscfs/fifofs/fifo.h>
+#include <miscfs/specfs/specdev.h>
+
+#include <nfs/rpcv2.h>
+#include <nfs/nfsproto.h>
+#include <nfs/nfs.h>
+#include <nfs/nfsnode.h>
+#include <nfs/nfs_gss.h>
+#include <nfs/nfsmount.h>
+#include <nfs/nfs_lock.h>
+#include <nfs/xdr_subs.h>
+#include <nfs/nfsm_subs.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/in_var.h>
+#include <vm/vm_kern.h>
+
+#include <kern/task.h>
+#include <kern/sched_prim.h>
+
+
+int
+nfs4_access_rpc(nfsnode_t np, u_long *mode, vfs_context_t ctx)
+{
+       int error = 0, status, numops, slot;
+       u_int64_t xid;
+       struct nfsm_chain nmreq, nmrep;
+       struct timeval now;
+       uint32_t access, supported = 0, missing;
+       struct nfsmount *nmp = NFSTONMP(np);
+       int nfsvers = nmp->nm_vers;
+       uid_t uid;
+
+       nfsm_chain_null(&nmreq);
+       nfsm_chain_null(&nmrep);
+
+       numops = 3; // PUTFH + ACCESS + GETATTR
+       nfsm_chain_build_alloc_init(error, &nmreq, 17 * NFSX_UNSIGNED);
+       nfsm_chain_add_compound_header(error, &nmreq, "access", numops);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTFH);
+       nfsm_chain_add_fh(error, &nmreq, nfsvers, np->n_fhp, np->n_fhsize);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_ACCESS);
+       nfsm_chain_add_32(error, &nmreq, *mode);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_GETATTR);
+       nfsm_chain_add_bitmap_masked(error, &nmreq, nfs_getattr_bitmap,
+               NFS_ATTR_BITMAP_LEN, nmp->nm_fsattr.nfsa_supp_attr);
+       nfsm_chain_build_done(error, &nmreq);
+       nfsm_assert(error, (numops == 0), EPROTO);
+       nfsmout_if(error);
+       error = nfs_request(np, NULL, &nmreq, NFSPROC4_COMPOUND, ctx, &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_ACCESS);
+       nfsm_chain_get_32(error, &nmrep, supported);
+       nfsm_chain_get_32(error, &nmrep, access);
+       nfsmout_if(error);
+       if ((missing = (*mode & ~supported))) {
+               /* missing support for something(s) we wanted */
+               if (missing & NFS_ACCESS_DELETE) {
+                       /*
+                        * If the server doesn't report DELETE (possible
+                        * on UNIX systems), we'll assume that it is OK
+                        * and just let any subsequent delete action fail
+                        * if it really isn't deletable.
+                        */
+                       access |= NFS_ACCESS_DELETE;
+               }
+       }
+       nfsm_chain_op_check(error, &nmrep, NFS_OP_GETATTR);
+       nfsm_chain_loadattr(error, &nmrep, np, nfsvers, NULL, &xid);
+       nfsmout_if(error);
+
+       uid = kauth_cred_getuid(vfs_context_ucred(ctx));
+       slot = nfs_node_mode_slot(np, uid, 1);
+       np->n_modeuid[slot] = uid;
+       microuptime(&now);
+       np->n_modestamp[slot] = now.tv_sec;
+       np->n_mode[slot] = access;
+
+       /* pass back the mode returned with this request */
+       *mode = np->n_mode[slot];
+nfsmout:
+       nfsm_chain_cleanup(&nmreq);
+       nfsm_chain_cleanup(&nmrep);
+       return (error);
+}
+
+int
+nfs4_getattr_rpc(
+       nfsnode_t np,
+       mount_t mp,
+       u_char *fhp,
+       size_t fhsize,
+       vfs_context_t ctx,
+       struct nfs_vattr *nvap,
+       u_int64_t *xidp)
+{
+       struct nfsmount *nmp = mp ? VFSTONFS(mp) : NFSTONMP(np);
+       int error = 0, status, nfsvers, numops;
+       struct nfsm_chain nmreq, nmrep;
+
+       if (!nmp)
+               return (ENXIO);
+       nfsvers = nmp->nm_vers;
+
+       nfsm_chain_null(&nmreq);
+       nfsm_chain_null(&nmrep);
+
+       numops = 2; // PUTFH + GETATTR
+       nfsm_chain_build_alloc_init(error, &nmreq, 15 * NFSX_UNSIGNED);
+       nfsm_chain_add_compound_header(error, &nmreq, "getattr", numops);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTFH);
+       nfsm_chain_add_fh(error, &nmreq, nfsvers, fhp, fhsize);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_GETATTR);
+       nfsm_chain_add_bitmap_masked(error, &nmreq, nfs_getattr_bitmap,
+               NFS_ATTR_BITMAP_LEN, nmp->nm_fsattr.nfsa_supp_attr);
+       nfsm_chain_build_done(error, &nmreq);
+       nfsm_assert(error, (numops == 0), EPROTO);
+       nfsmout_if(error);
+       error = nfs_request(np, mp, &nmreq, NFSPROC4_COMPOUND, ctx, &nmrep, xidp, &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_GETATTR);
+       nfsmout_if(error);
+       NFS_CLEAR_ATTRIBUTES(nvap->nva_bitmap);
+       error = nfs4_parsefattr(&nmrep, NULL, nvap, NULL, NULL);
+nfsmout:
+       nfsm_chain_cleanup(&nmreq);
+       nfsm_chain_cleanup(&nmrep);
+       return (error);
+}
+
+int
+nfs4_readlink_rpc(nfsnode_t np, char *buf, uint32_t *buflenp, vfs_context_t ctx)
+{
+       struct nfsmount *nmp;
+       int error = 0, lockerror = ENOENT, status, numops;
+       uint32_t len = 0;
+       u_int64_t xid;
+       struct nfsm_chain nmreq, nmrep;
+
+       nmp = NFSTONMP(np);
+       if (!nmp)
+               return (ENXIO);
+       nfsm_chain_null(&nmreq);
+       nfsm_chain_null(&nmrep);
+
+       numops = 3; // PUTFH + GETATTR + READLINK
+       nfsm_chain_build_alloc_init(error, &nmreq, 16 * NFSX_UNSIGNED);
+       nfsm_chain_add_compound_header(error, &nmreq, "readlink", numops);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTFH);
+       nfsm_chain_add_fh(error, &nmreq, NFS_VER4, np->n_fhp, np->n_fhsize);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_GETATTR);
+       nfsm_chain_add_bitmap_masked(error, &nmreq, nfs_getattr_bitmap,
+               NFS_ATTR_BITMAP_LEN, nmp->nm_fsattr.nfsa_supp_attr);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_READLINK);
+       nfsm_chain_build_done(error, &nmreq);
+       nfsm_assert(error, (numops == 0), EPROTO);
+       nfsmout_if(error);
+       error = nfs_request(np, NULL, &nmreq, NFSPROC4_COMPOUND, ctx, &nmrep, &xid, &status);
+
+       if ((lockerror = nfs_lock(np, NFS_NODE_LOCK_EXCLUSIVE)))
+               error = lockerror;
+       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_GETATTR);
+       nfsm_chain_loadattr(error, &nmrep, np, NFS_VER4, NULL, &xid);
+       nfsm_chain_op_check(error, &nmrep, NFS_OP_READLINK);
+       nfsm_chain_get_32(error, &nmrep, len);
+       nfsmout_if(error);
+       if (len >= *buflenp) {
+               if (np->n_size && (np->n_size < *buflenp))
+                       len = np->n_size;
+               else
+                       len = *buflenp - 1;
+       }
+       nfsm_chain_get_opaque(error, &nmrep, len, buf);
+       if (!error)
+               *buflenp = len;
+nfsmout:
+       if (!lockerror)
+               nfs_unlock(np);
+       nfsm_chain_cleanup(&nmreq);
+       nfsm_chain_cleanup(&nmrep);
+       return (error);
+}
+
+int
+nfs4_read_rpc_async(
+       nfsnode_t np,
+       off_t offset,
+       size_t len,
+       thread_t thd,
+       kauth_cred_t cred,
+       struct nfsreq_cbinfo *cb,
+       struct nfsreq **reqp)
+{
+       struct nfsmount *nmp;
+       int error = 0, nfsvers, numops;
+       struct nfsm_chain nmreq;
+
+       nmp = NFSTONMP(np);
+       if (!nmp)
+               return (ENXIO);
+       nfsvers = nmp->nm_vers;
+
+       nfsm_chain_null(&nmreq);
+
+       // PUTFH + READ + GETATTR
+       numops = 3;
+       nfsm_chain_build_alloc_init(error, &nmreq, 22 * NFSX_UNSIGNED);
+       nfsm_chain_add_compound_header(error, &nmreq, "read", numops);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTFH);
+       nfsm_chain_add_fh(error, &nmreq, nfsvers, np->n_fhp, np->n_fhsize);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_READ);
+
+       /* XXX use special stateid for now */
+       nfsm_chain_add_32(error, &nmreq, 0xffffffff);
+       nfsm_chain_add_32(error, &nmreq, 0xffffffff);
+       nfsm_chain_add_32(error, &nmreq, 0xffffffff);
+       nfsm_chain_add_32(error, &nmreq, 0xffffffff);
+
+       nfsm_chain_add_64(error, &nmreq, offset);
+       nfsm_chain_add_32(error, &nmreq, len);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_GETATTR);
+       nfsm_chain_add_bitmap_masked(error, &nmreq, nfs_getattr_bitmap,
+               NFS_ATTR_BITMAP_LEN, nmp->nm_fsattr.nfsa_supp_attr);
+       nfsm_chain_build_done(error, &nmreq);
+       nfsm_assert(error, (numops == 0), EPROTO);
+       nfsmout_if(error);
+       error = nfs_request_async(np, NULL, &nmreq, NFSPROC4_COMPOUND, thd, cred, cb, reqp);
+nfsmout:
+       nfsm_chain_cleanup(&nmreq);
+       return (error);
+}
+
+int
+nfs4_read_rpc_async_finish(
+       nfsnode_t np,
+       struct nfsreq *req,
+       struct uio *uiop,
+       size_t *lenp,
+       int *eofp)
+{
+       struct nfsmount *nmp;
+       int error = 0, lockerror, nfsvers, numops, status, eof = 0;
+       size_t retlen = 0;
+       u_int64_t xid;
+       struct nfsm_chain nmrep;
+
+       nmp = NFSTONMP(np);
+       if (!nmp) {
+               nfs_request_async_cancel(req);
+               return (ENXIO);
+       }
+       nfsvers = nmp->nm_vers;
+
+       nfsm_chain_null(&nmrep);
+
+       error = nfs_request_async_finish(req, &nmrep, &xid, &status);
+       if (error == EINPROGRESS) /* async request restarted */
+               return (error);
+
+       if ((lockerror = nfs_lock(np, NFS_NODE_LOCK_EXCLUSIVE)))
+               error = lockerror;
+       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_READ);
+       nfsm_chain_get_32(error, &nmrep, eof);
+       nfsm_chain_get_32(error, &nmrep, retlen);
+       if (!error) {
+               *lenp = MIN(retlen, *lenp);
+               error = nfsm_chain_get_uio(&nmrep, *lenp, uiop);
+       }
+       nfsm_chain_op_check(error, &nmrep, NFS_OP_GETATTR);
+       nfsm_chain_loadattr(error, &nmrep, np, nfsvers, NULL, &xid);
+       if (!lockerror)
+               nfs_unlock(np);
+       if (eofp) {
+               if (!eof && !retlen)
+                       eof = 1;
+               *eofp = eof;
+       }
+       nfsm_chain_cleanup(&nmrep);
+       return (error);
+}
+
+int
+nfs4_write_rpc_async(
+       nfsnode_t np,
+       struct uio *uiop,
+       size_t len,
+       thread_t thd,
+       kauth_cred_t cred,
+       int iomode,
+       struct nfsreq_cbinfo *cb,
+       struct nfsreq **reqp)
+{
+       struct nfsmount *nmp;
+       int error = 0, nfsvers, numops;
+       off_t offset;
+       struct nfsm_chain nmreq;
+
+       nmp = NFSTONMP(np);
+       if (!nmp)
+               return (ENXIO);
+       nfsvers = nmp->nm_vers;
+
+       offset = uiop->uio_offset;
+
+       nfsm_chain_null(&nmreq);
+
+       // PUTFH + WRITE + GETATTR
+       numops = 3;
+       nfsm_chain_build_alloc_init(error, &nmreq, 25 * NFSX_UNSIGNED + len);
+       nfsm_chain_add_compound_header(error, &nmreq, "write", numops);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTFH);
+       nfsm_chain_add_fh(error, &nmreq, nfsvers, np->n_fhp, np->n_fhsize);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_WRITE);
+
+       /* XXX use special stateid for now */
+       nfsm_chain_add_32(error, &nmreq, 0xffffffff);
+       nfsm_chain_add_32(error, &nmreq, 0xffffffff);
+       nfsm_chain_add_32(error, &nmreq, 0xffffffff);
+       nfsm_chain_add_32(error, &nmreq, 0xffffffff);
+
+       nfsm_chain_add_64(error, &nmreq, uiop->uio_offset);
+       nfsm_chain_add_32(error, &nmreq, iomode);
+       nfsm_chain_add_32(error, &nmreq, len);
+       if (!error)
+               error = nfsm_chain_add_uio(&nmreq, uiop, len);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_GETATTR);
+       nfsm_chain_add_bitmap_masked(error, &nmreq, nfs_getattr_bitmap,
+               NFS_ATTR_BITMAP_LEN, nmp->nm_fsattr.nfsa_supp_attr);
+       nfsm_chain_build_done(error, &nmreq);
+       nfsm_assert(error, (numops == 0), EPROTO);
+       nfsmout_if(error);
+
+       error = nfs_request_async(np, NULL, &nmreq, NFSPROC4_COMPOUND, thd, cred, cb, reqp);
+nfsmout:
+       nfsm_chain_cleanup(&nmreq);
+       return (error);
+}
+
+int
+nfs4_write_rpc_async_finish(
+       nfsnode_t np,
+       struct nfsreq *req,
+       int *iomodep,
+       size_t *rlenp,
+       uint64_t *wverfp)
+{
+       struct nfsmount *nmp;
+       int error = 0, lockerror = ENOENT, nfsvers, numops, status;
+       int committed = NFS_WRITE_FILESYNC;
+       size_t rlen = 0;
+       u_int64_t xid, wverf;
+       mount_t mp;
+       struct nfsm_chain nmrep;
+
+       nmp = NFSTONMP(np);
+       if (!nmp) {
+               nfs_request_async_cancel(req);
+               return (ENXIO);
+       }
+       nfsvers = nmp->nm_vers;
+
+       nfsm_chain_null(&nmrep);
+
+       error = nfs_request_async_finish(req, &nmrep, &xid, &status);
+       if (error == EINPROGRESS) /* async request restarted */
+               return (error);
+       nmp = NFSTONMP(np);
+       if (!nmp)
+               error = ENXIO;
+       if (!error && (lockerror = nfs_lock(np, NFS_NODE_LOCK_EXCLUSIVE)))
+               error = lockerror;
+       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_WRITE);
+       nfsm_chain_get_32(error, &nmrep, rlen);
+       nfsmout_if(error);
+       *rlenp = rlen;
+       if (rlen <= 0)
+               error = NFSERR_IO;
+       nfsm_chain_get_32(error, &nmrep, committed);
+       nfsm_chain_get_64(error, &nmrep, wverf);
+       nfsmout_if(error);
+       if (wverfp)
+               *wverfp = wverf;
+       lck_mtx_lock(&nmp->nm_lock);
+       if (!(nmp->nm_state & NFSSTA_HASWRITEVERF)) {
+               nmp->nm_verf = wverf;
+               nmp->nm_state |= NFSSTA_HASWRITEVERF;
+       } else if (nmp->nm_verf != wverf) {
+               nmp->nm_verf = wverf;
+       }
+       lck_mtx_unlock(&nmp->nm_lock);
+       nfsm_chain_op_check(error, &nmrep, NFS_OP_GETATTR);
+       nfsm_chain_loadattr(error, &nmrep, np, nfsvers, NULL, &xid);
+nfsmout:
+       if (!lockerror)
+               nfs_unlock(np);
+       nfsm_chain_cleanup(&nmrep);
+       if ((committed != NFS_WRITE_FILESYNC) && nfs_allow_async &&
+           ((mp = NFSTOMP(np))) && (vfs_flags(mp) & MNT_ASYNC))
+               committed = NFS_WRITE_FILESYNC;
+       *iomodep = committed;
+       return (error);
+}
+
+int
+nfs4_remove_rpc(
+       nfsnode_t dnp,
+       char *name,
+       int namelen,
+       thread_t thd,
+       kauth_cred_t cred)
+{
+       int error = 0, remove_error = 0, status;
+       struct nfsmount *nmp;
+       int nfsvers, numops;
+       u_int64_t xid;
+       struct nfsm_chain nmreq, nmrep;
+
+       nmp = NFSTONMP(dnp);
+       if (!nmp)
+               return (ENXIO);
+       nfsvers = nmp->nm_vers;
+
+       nfsm_chain_null(&nmreq);
+       nfsm_chain_null(&nmrep);
+
+       // PUTFH, REMOVE, GETATTR
+       numops = 3;
+       nfsm_chain_build_alloc_init(error, &nmreq, 17 * NFSX_UNSIGNED + namelen);
+       nfsm_chain_add_compound_header(error, &nmreq, "remove", numops);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTFH);
+       nfsm_chain_add_fh(error, &nmreq, nfsvers, dnp->n_fhp, dnp->n_fhsize);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_REMOVE);
+       nfsm_chain_add_string(error, &nmreq, name, namelen);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_GETATTR);
+       nfsm_chain_add_bitmap_masked(error, &nmreq, nfs_getattr_bitmap,
+               NFS_ATTR_BITMAP_LEN, nmp->nm_fsattr.nfsa_supp_attr);
+       nfsm_chain_build_done(error, &nmreq);
+       nfsm_assert(error, (numops == 0), EPROTO);
+       nfsmout_if(error);
+
+       error = nfs_request2(dnp, NULL, &nmreq, NFSPROC4_COMPOUND, thd, cred, 0, &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_REMOVE);
+       remove_error = error;
+       nfsm_chain_check_change_info(error, &nmrep, dnp);
+       nfsm_chain_op_check(error, &nmrep, NFS_OP_GETATTR);
+       nfsm_chain_loadattr(error, &nmrep, dnp, nfsvers, NULL, &xid);
+       if (error)
+               NATTRINVALIDATE(dnp);
+nfsmout:
+       nfsm_chain_cleanup(&nmreq);
+       nfsm_chain_cleanup(&nmrep);
+
+       dnp->n_flag |= NMODIFIED;
+
+       return (remove_error);
+}
+
+int
+nfs4_rename_rpc(
+       nfsnode_t fdnp,
+       char *fnameptr,
+       int fnamelen,
+       nfsnode_t tdnp,
+       char *tnameptr,
+       int tnamelen,
+       vfs_context_t ctx)
+{
+       int error = 0, status, nfsvers, numops;
+       struct nfsmount *nmp;
+       u_int64_t xid, savedxid;
+       struct nfsm_chain nmreq, nmrep;
+
+       nmp = NFSTONMP(fdnp);
+       if (!nmp)
+               return (ENXIO);
+       nfsvers = nmp->nm_vers;
+
+       nfsm_chain_null(&nmreq);
+       nfsm_chain_null(&nmrep);
+
+       // PUTFH(FROM), SAVEFH, PUTFH(TO), RENAME, GETATTR(TO), RESTOREFH, GETATTR(FROM)
+       numops = 7;
+       nfsm_chain_build_alloc_init(error, &nmreq, 30 * NFSX_UNSIGNED + fnamelen + tnamelen);
+       nfsm_chain_add_compound_header(error, &nmreq, "rename", numops);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTFH);
+       nfsm_chain_add_fh(error, &nmreq, nfsvers, fdnp->n_fhp, fdnp->n_fhsize);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_SAVEFH);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTFH);
+       nfsm_chain_add_fh(error, &nmreq, nfsvers, tdnp->n_fhp, tdnp->n_fhsize);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_RENAME);
+       nfsm_chain_add_string(error, &nmreq, fnameptr, fnamelen);
+       nfsm_chain_add_string(error, &nmreq, tnameptr, tnamelen);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_GETATTR);
+       nfsm_chain_add_bitmap_masked(error, &nmreq, nfs_getattr_bitmap,
+               NFS_ATTR_BITMAP_LEN, nmp->nm_fsattr.nfsa_supp_attr);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_RESTOREFH);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_GETATTR);
+       nfsm_chain_add_bitmap_masked(error, &nmreq, nfs_getattr_bitmap,
+               NFS_ATTR_BITMAP_LEN, nmp->nm_fsattr.nfsa_supp_attr);
+       nfsm_chain_build_done(error, &nmreq);
+       nfsm_assert(error, (numops == 0), EPROTO);
+       nfsmout_if(error);
+
+       error = nfs_request(fdnp, NULL, &nmreq, NFSPROC4_COMPOUND, ctx, &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_SAVEFH);
+       nfsm_chain_op_check(error, &nmrep, NFS_OP_PUTFH);
+       nfsm_chain_op_check(error, &nmrep, NFS_OP_RENAME);
+       nfsm_chain_check_change_info(error, &nmrep, fdnp);
+       nfsm_chain_check_change_info(error, &nmrep, tdnp);
+       /* directory attributes: if we don't get them, make sure to invalidate */
+       nfsm_chain_op_check(error, &nmrep, NFS_OP_GETATTR);
+       savedxid = xid;
+       nfsm_chain_loadattr(error, &nmrep, tdnp, nfsvers, NULL, &xid);
+       if (error)
+               NATTRINVALIDATE(tdnp);
+       nfsm_chain_op_check(error, &nmrep, NFS_OP_RESTOREFH);
+       nfsm_chain_op_check(error, &nmrep, NFS_OP_GETATTR);
+       xid = savedxid;
+       nfsm_chain_loadattr(error, &nmrep, fdnp, nfsvers, NULL, &xid);
+       if (error)
+               NATTRINVALIDATE(fdnp);
+nfsmout:
+       nfsm_chain_cleanup(&nmreq);
+       nfsm_chain_cleanup(&nmrep);
+       fdnp->n_flag |= NMODIFIED;
+       tdnp->n_flag |= NMODIFIED;
+       /* Kludge: Map EEXIST => 0 assuming that it is a reply to a retry. */
+       if (error == EEXIST)
+               error = 0;
+       return (error);
+}
+
+/*
+ * NFS V4 readdir RPC.
+ */
+#define        DIRHDSIZ        ((int)(sizeof(struct dirent) - (MAXNAMLEN + 1)))
+int
+nfs4_readdir_rpc(nfsnode_t dnp, struct uio *uiop, vfs_context_t ctx)
+{
+       size_t len, tlen, skiplen, left;
+       struct dirent *dp = NULL;
+       vnode_t newvp;
+       nfsuint64 *cookiep;
+       struct componentname cn, *cnp = &cn;
+       nfsuint64 cookie;
+       struct nfsmount *nmp;
+       nfsnode_t np;
+       int error = 0, lockerror, status, more_entries = 1, blksiz = 0, bigenough = 1;
+       int nfsvers, rdirplus, nmreaddirsize, nmrsize, eof, i, numops;
+       u_int64_t xid, savexid;
+       struct nfs_vattr nvattr;
+       struct nfsm_chain nmreq, nmrep;
+       char *cp;
+       const char *tag;
+       uint32_t entry_attrs[NFS_ATTR_BITMAP_LEN];
+       fhandle_t fh;
+
+#if DIAGNOSTIC
+       /* XXX limitation based on need to adjust uio */
+       if (uiop->uio_iovcnt != 1 || (uiop->uio_offset & (DIRBLKSIZ - 1)) ||
+               (uio_uio_resid(uiop) & (DIRBLKSIZ - 1)))
+               panic("nfs4_readdir_rpc: bad uio");
+#endif
+       nmp = NFSTONMP(dnp);
+       if (!nmp)
+               return (ENXIO);
+       nfsvers = nmp->nm_vers;
+       nmreaddirsize = nmp->nm_readdirsize;
+       nmrsize = nmp->nm_rsize;
+       rdirplus = (nmp->nm_flag & NFSMNT_RDIRPLUS) ? 1 : 0;
+
+       bzero(cnp, sizeof(*cnp));
+       newvp = NULLVP;
+
+       /*
+        * Set up attribute request for entries.
+        * For READDIRPLUS functionality, get everything.
+        * Otherwise, just get what we need for struct dirent.
+        */
+       if (rdirplus) {
+               tag = "READDIRPLUS";
+               for (i=0; i < NFS_ATTR_BITMAP_LEN; i++)
+                       entry_attrs[i] =
+                               nfs_getattr_bitmap[i] &
+                               nmp->nm_fsattr.nfsa_supp_attr[i];
+               NFS_BITMAP_SET(entry_attrs, NFS_FATTR_FILEHANDLE);
+       } else {
+               tag = "READDIR";
+               NFS_CLEAR_ATTRIBUTES(entry_attrs);
+               NFS_BITMAP_SET(entry_attrs, NFS_FATTR_TYPE);
+               NFS_BITMAP_SET(entry_attrs, NFS_FATTR_FILEID);
+       }
+       /* XXX NFS_BITMAP_SET(entry_attrs, NFS_FATTR_MOUNTED_ON_FILEID); */
+       NFS_BITMAP_SET(entry_attrs, NFS_FATTR_RDATTR_ERROR);
+
+       if ((lockerror = nfs_lock(dnp, NFS_NODE_LOCK_SHARED)))
+               return (lockerror);
+
+       /*
+        * If there is no cookie, assume directory was stale.
+        */
+       cookiep = nfs_getcookie(dnp, uiop->uio_offset, 0);
+       if (cookiep)
+               cookie = *cookiep;
+       else {
+               nfs_unlock(dnp);
+               return (NFSERR_BAD_COOKIE);
+       }
+
+       /*
+        * The NFS client is responsible for the "." and ".."
+        * entries in the directory.  So, we put them at the top.
+        */
+       if ((uiop->uio_offset == 0) &&
+           ((2*(4 + DIRHDSIZ)) <= uio_uio_resid(uiop))) {
+               /* add "." entry */
+               len = 2;
+               tlen = nfsm_rndup(len);
+               // LP64todo - fix this!
+               dp = (struct dirent *) CAST_DOWN(caddr_t, uio_iov_base(uiop));
+               dp->d_fileno = dnp->n_vattr.nva_fileid;
+               dp->d_namlen = len;
+               dp->d_reclen = tlen + DIRHDSIZ;
+               dp->d_type = DT_DIR;
+               strlcpy(dp->d_name, ".", len);
+               blksiz += dp->d_reclen;
+               if (blksiz == DIRBLKSIZ)
+                       blksiz = 0;
+               uiop->uio_offset += DIRHDSIZ + tlen;
+               uio_iov_base_add(uiop, DIRHDSIZ + tlen);
+               uio_uio_resid_add(uiop, -(DIRHDSIZ + tlen));
+               uio_iov_len_add(uiop, -(DIRHDSIZ + tlen));
+               /* add ".." entry */
+               len = 3;
+               tlen = nfsm_rndup(len);
+               // LP64todo - fix this!
+               dp = (struct dirent *) CAST_DOWN(caddr_t, uio_iov_base(uiop));
+               if (dnp->n_parent)
+                       dp->d_fileno = VTONFS(dnp->n_parent)->n_vattr.nva_fileid;
+               else
+                       dp->d_fileno = dnp->n_vattr.nva_fileid;
+               dp->d_namlen = len;
+               dp->d_reclen = tlen + DIRHDSIZ;
+               dp->d_type = DT_DIR;
+               strlcpy(dp->d_name, "..", len);
+               blksiz += dp->d_reclen;
+               if (blksiz == DIRBLKSIZ)
+                       blksiz = 0;
+               uiop->uio_offset += DIRHDSIZ + tlen;
+               uio_iov_base_add(uiop, DIRHDSIZ + tlen);
+               uio_uio_resid_add(uiop, -(DIRHDSIZ + tlen));
+               uio_iov_len_add(uiop, -(DIRHDSIZ + tlen));
+               cookie.nfsuquad[0] = 0;
+               cookie.nfsuquad[1] = 2;
+       }
+
+       /*
+        * Loop around doing readdir rpc's of size nm_readdirsize
+        * truncated to a multiple of DIRBLKSIZ.
+        * The stopping criteria is EOF or buffer full.
+        */
+       while (more_entries && bigenough) {
+               nfsm_chain_null(&nmreq);
+               nfsm_chain_null(&nmrep);
+               nfsm_assert(error, NFSTONMP(dnp), ENXIO);
+
+               numops = 3; // PUTFH + GETATTR + READDIR
+               nfsm_chain_build_alloc_init(error, &nmreq, 26 * NFSX_UNSIGNED);
+               nfsm_chain_add_compound_header(error, &nmreq, tag, numops);
+               numops--;
+               nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTFH);
+               nfsm_chain_add_fh(error, &nmreq, nfsvers, dnp->n_fhp, dnp->n_fhsize);
+               numops--;
+               nfsm_chain_add_32(error, &nmreq, NFS_OP_GETATTR);
+               nfsm_chain_add_bitmap_masked(error, &nmreq, nfs_getattr_bitmap,
+                       NFS_ATTR_BITMAP_LEN, nmp->nm_fsattr.nfsa_supp_attr);
+               numops--;
+               nfsm_chain_add_32(error, &nmreq, NFS_OP_READDIR);
+               /* opaque values don't need swapping, but as long */
+               /* as we are consistent about it, it should be ok */
+               nfsm_chain_add_32(error, &nmreq, cookie.nfsuquad[0]);
+               if ((cookie.nfsuquad[0] == 0) && (cookie.nfsuquad[1] <= 2))
+                       nfsm_chain_add_32(error, &nmreq, 0);
+               else
+                       nfsm_chain_add_32(error, &nmreq, cookie.nfsuquad[1]);
+               nfsm_chain_add_32(error, &nmreq, dnp->n_cookieverf.nfsuquad[0]);
+               nfsm_chain_add_32(error, &nmreq, dnp->n_cookieverf.nfsuquad[1]);
+               nfsm_chain_add_32(error, &nmreq, nmreaddirsize);
+               nfsm_chain_add_32(error, &nmreq, nmrsize);
+               nfsm_chain_add_bitmap(error, &nmreq, entry_attrs, NFS_ATTR_BITMAP_LEN);
+               nfsm_chain_build_done(error, &nmreq);
+               nfsm_assert(error, (numops == 0), EPROTO);
+               nfs_unlock(dnp);
+               nfsmout_if(error);
+               error = nfs_request(dnp, NULL, &nmreq, NFSPROC4_COMPOUND, ctx, &nmrep, &xid, &status);
+
+               if ((lockerror = nfs_lock(dnp, NFS_NODE_LOCK_EXCLUSIVE)))
+                       error = lockerror;
+               savexid = xid;
+               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_GETATTR);
+               nfsm_chain_loadattr(error, &nmrep, dnp, nfsvers, NULL, &xid);
+               nfsm_chain_op_check(error, &nmrep, NFS_OP_READDIR);
+               nfsm_chain_get_32(error, &nmrep, dnp->n_cookieverf.nfsuquad[0]);
+               nfsm_chain_get_32(error, &nmrep, dnp->n_cookieverf.nfsuquad[1]);
+               nfsm_chain_get_32(error, &nmrep, more_entries);
+               nfs_unlock(dnp);
+               nfsmout_if(error);
+
+               /* Loop through the entries, massaging them into "dirent" form. */
+               /* If READDIRPLUS, also create the vnodes. */
+               while (more_entries && bigenough) {
+                       /* Entry: COOKIE, NAME, FATTR */
+                       nfsm_chain_get_32(error, &nmrep, cookie.nfsuquad[0]);
+                       nfsm_chain_get_32(error, &nmrep, cookie.nfsuquad[1]);
+                       nfsm_chain_get_32(error, &nmrep, len);
+                       nfsmout_if(error);
+                       /* Note: NFS supports longer names, but struct dirent doesn't */
+                       /* so we just truncate the names to fit */
+                       if (len <= 0) {
+                               error = EBADRPC;
+                               goto nfsmout;
+                       }
+                       if (len > MAXNAMLEN) {
+                               skiplen = len - MAXNAMLEN;
+                               len = MAXNAMLEN;
+                       } else {
+                               skiplen = 0;
+                       }
+                       tlen = nfsm_rndup(len);
+                       if (tlen == len)
+                               tlen += 4;      /* To ensure null termination */
+                       left = DIRBLKSIZ - blksiz;
+                       if ((tlen + DIRHDSIZ) > left) {
+                               dp->d_reclen += left;
+                               uio_iov_base_add(uiop, left);
+                               uio_iov_len_add(uiop, -left);
+                               uiop->uio_offset += left;
+                               uio_uio_resid_add(uiop, -left);
+                               blksiz = 0;
+                       }
+                       if ((tlen + DIRHDSIZ) > uio_uio_resid(uiop)) {
+                               bigenough = 0;
+                               break;
+                       }
+                       // LP64todo - fix this!
+                       dp = (struct dirent *) CAST_DOWN(caddr_t, uio_iov_base(uiop));
+                       dp->d_fileno = 0;
+                       dp->d_namlen = len;
+                       dp->d_reclen = tlen + DIRHDSIZ;
+                       dp->d_type = DT_UNKNOWN;
+                       blksiz += dp->d_reclen;
+                       if (blksiz == DIRBLKSIZ)
+                               blksiz = 0;
+                       uiop->uio_offset += DIRHDSIZ;
+#if LP64KERN
+                       uio_uio_resid_add(uiop, -((int64_t)DIRHDSIZ));
+                       uio_iov_len_add(uiop, -((int64_t)DIRHDSIZ));
+#else
+                       uio_uio_resid_add(uiop, -((int)DIRHDSIZ));
+                       uio_iov_len_add(uiop, -((int)DIRHDSIZ));
+#endif
+                       uio_iov_base_add(uiop, DIRHDSIZ);
+                       // LP64todo - fix this!
+                       cnp->cn_nameptr = CAST_DOWN(caddr_t, uio_iov_base(uiop));
+                       cnp->cn_namelen = len;
+                       error = nfsm_chain_get_uio(&nmrep, len, uiop);
+                       if (skiplen)
+                               nfsm_chain_adv(error, &nmrep,
+                                       nfsm_rndup(len + skiplen) - nfsm_rndup(len));
+                       nfsmout_if(error);
+                       NFS_CLEAR_ATTRIBUTES(nvattr.nva_bitmap);
+                       error = nfs4_parsefattr(&nmrep, NULL, &nvattr, &fh, NULL);
+                       if (error && NFS_BITMAP_ISSET(nvattr.nva_bitmap, NFS_FATTR_RDATTR_ERROR)) {
+                               /* OK, we didn't get attributes, whatever... */
+                               NFS_CLEAR_ATTRIBUTES(nvattr.nva_bitmap);
+                               error = 0;
+                       }
+                       nfsm_chain_get_32(error, &nmrep, more_entries);
+                       nfsmout_if(error);
+
+                       cp = CAST_DOWN(caddr_t, uio_iov_base(uiop));
+                       tlen -= len;
+                       *cp = '\0';
+                       uio_iov_base_add(uiop, tlen);
+                       uio_iov_len_add(uiop, -tlen);
+                       uiop->uio_offset += tlen;
+                       uio_uio_resid_add(uiop, -tlen);
+
+                       /*
+                        * Skip any "." and ".." entries returned from server.
+                        * (Actually, just leave it in place with d_fileno == 0.)
+                        */
+                       if ((cnp->cn_nameptr[0] == '.') &&
+                           ((len == 1) || ((len == 2) && (cnp->cn_nameptr[1] == '.')))) {
+                               /* clear the name too */
+                               dp->d_namlen = 0;
+                               dp->d_name[0] = '\0';
+                               continue;
+                       }
+
+                       if (NFS_BITMAP_ISSET(nvattr.nva_bitmap, NFS_FATTR_TYPE))
+                               dp->d_type = IFTODT(VTTOIF(nvattr.nva_type));
+                       if (NFS_BITMAP_ISSET(nvattr.nva_bitmap, NFS_FATTR_FILEID))
+                               dp->d_fileno = (int)nvattr.nva_fileid;
+                       if (rdirplus && NFS_BITMAP_ISSET(nvattr.nva_bitmap, NFS_FATTR_FILEHANDLE) &&
+                           !NFS_CMPFH(dnp, fh.fh_data, fh.fh_len)) {
+                               cnp->cn_hash = 0;
+                               error = nfs_nget(NFSTOMP(dnp), dnp, cnp,
+                                               fh.fh_data, fh.fh_len, &nvattr, &xid, NG_MAKEENTRY, &np);
+                               if (!error) {
+                                       nfs_unlock(np);
+                                       vnode_put(NFSTOV(np));
+                               }
+                       }
+                       nfsmout_if(error);
+               }
+               /* If at end of rpc data, get the eof boolean */
+               if (!more_entries) {
+                       nfsm_chain_get_32(error, &nmrep, eof);
+                       if (!error)
+                               more_entries = (eof == 0);
+               }
+               if ((lockerror = nfs_lock(dnp, NFS_NODE_LOCK_SHARED)))
+                       error = lockerror;
+               nfsmout_if(error);
+               nfsm_chain_cleanup(&nmrep);
+       }
+       nfs_unlock(dnp);
+       /*
+        * Fill last record, iff any, out to a multiple of DIRBLKSIZ
+        * by increasing d_reclen for the last record.
+        */
+       if (blksiz > 0) {
+               left = DIRBLKSIZ - blksiz;
+               dp->d_reclen += left;
+               uio_iov_base_add(uiop, left);
+               uio_iov_len_add(uiop, -left);
+               uiop->uio_offset += left;
+               uio_uio_resid_add(uiop, -left);
+       }
+
+       if ((lockerror = nfs_lock(dnp, NFS_NODE_LOCK_EXCLUSIVE)))
+               error = lockerror;
+       nfsmout_if(error);
+
+       /*
+        * We are now either at the end of the directory or have filled the
+        * block.
+        */
+       if (bigenough)
+               dnp->n_direofoffset = uiop->uio_offset;
+       else {
+               if (uio_uio_resid(uiop) > 0)
+                       printf("EEK! nfs4_readdir_rpc resid > 0\n");
+               cookiep = nfs_getcookie(dnp, uiop->uio_offset, 1);
+               if (cookiep)
+                       *cookiep = cookie;
+       }
+
+       nfs_unlock(dnp);
+nfsmout:
+       nfsm_chain_cleanup(&nmreq);
+       nfsm_chain_cleanup(&nmrep);
+       return (error);
+}
+
+int
+nfs4_lookup_rpc_async(
+       nfsnode_t dnp,
+       char *name,
+       int namelen,
+       vfs_context_t ctx,
+       struct nfsreq **reqp)
+{
+       int error = 0, isdotdot = 0, getattrs = 1, nfsvers, numops;
+       struct nfsm_chain nmreq;
+       uint32_t bitmap[NFS_ATTR_BITMAP_LEN];
+       struct nfsmount *nmp;
+
+       nmp = NFSTONMP(dnp);
+       if (!nmp)
+               return (ENXIO);
+       nfsvers = nmp->nm_vers;
+
+       if ((name[0] == '.') && (name[1] == '.') && (namelen == 2))
+               isdotdot = 1;
+
+       nfsm_chain_null(&nmreq);
+
+       // PUTFH, GETATTR, LOOKUP(P), GETATTR (FH)
+       numops = getattrs ? 4 : 3;
+       nfsm_chain_build_alloc_init(error, &nmreq, 20 * NFSX_UNSIGNED + namelen);
+       nfsm_chain_add_compound_header(error, &nmreq, "lookup", numops);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTFH);
+       nfsm_chain_add_fh(error, &nmreq, nfsvers, dnp->n_fhp, dnp->n_fhsize);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_GETATTR);
+       nfsm_chain_add_bitmap_masked(error, &nmreq, nfs_getattr_bitmap,
+               NFS_ATTR_BITMAP_LEN, nmp->nm_fsattr.nfsa_supp_attr);
+       numops--;
+       if (isdotdot) {
+               nfsm_chain_add_32(error, &nmreq, NFS_OP_LOOKUPP);
+       } else {
+               nfsm_chain_add_32(error, &nmreq, NFS_OP_LOOKUP);
+               nfsm_chain_add_string(error, &nmreq, name, namelen);
+       }
+       if (getattrs) {
+               numops--;
+               nfsm_chain_add_32(error, &nmreq, NFS_OP_GETATTR);
+               NFS_COPY_ATTRIBUTES(nfs_getattr_bitmap, bitmap);
+               NFS_BITMAP_SET(bitmap, NFS_FATTR_FILEHANDLE);
+               nfsm_chain_add_bitmap_masked(error, &nmreq, bitmap,
+                       NFS_ATTR_BITMAP_LEN, nmp->nm_fsattr.nfsa_supp_attr);
+       }
+       nfsm_chain_build_done(error, &nmreq);
+       nfsm_assert(error, (numops == 0), EPROTO);
+       nfsmout_if(error);
+       error = nfs_request_async(dnp, NULL, &nmreq, NFSPROC4_COMPOUND,
+                       vfs_context_thread(ctx), vfs_context_ucred(ctx), NULL, reqp);
+nfsmout:
+       nfsm_chain_cleanup(&nmreq);
+       return (error);
+}
+
+int
+nfs4_lookup_rpc_async_finish(
+       nfsnode_t dnp,
+       __unused vfs_context_t ctx,
+       struct nfsreq *req,
+       u_int64_t *xidp,
+       fhandle_t *fhp,
+       struct nfs_vattr *nvap)
+{
+       int error = 0, status, nfsvers, numops;
+       uint32_t val = 0;
+       u_int64_t xid;
+       struct nfsmount *nmp;
+       struct nfsm_chain nmrep;
+
+       nmp = NFSTONMP(dnp);
+       nfsvers = nmp->nm_vers;
+
+       nfsm_chain_null(&nmrep);
+
+       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_GETATTR);
+       if (xidp)
+               *xidp = xid;
+       nfsm_chain_loadattr(error, &nmrep, dnp, nfsvers, NULL, &xid);
+
+       // nfsm_chain_op_check(error, &nmrep, (isdotdot ? NFS_OP_LOOKUPP : NFS_OP_LOOKUP));
+       nfsm_chain_get_32(error, &nmrep, val);
+       nfsm_assert(error, (val == NFS_OP_LOOKUPP) || (val == NFS_OP_LOOKUP), EBADRPC);
+       nfsm_chain_get_32(error, &nmrep, val);
+       nfsm_assert(error, (val == NFS_OK), val);
+
+       nfsmout_if(error || !fhp || !nvap);
+       nfsm_chain_op_check(error, &nmrep, NFS_OP_GETATTR);
+       nfsmout_if(error);
+       NFS_CLEAR_ATTRIBUTES(nvap->nva_bitmap);
+       error = nfs4_parsefattr(&nmrep, NULL, nvap, fhp, NULL);
+       if (!NFS_BITMAP_ISSET(nvap->nva_bitmap, NFS_FATTR_FILEHANDLE)) {
+               error = EBADRPC;
+               goto nfsmout;
+       }
+nfsmout:
+       nfsm_chain_cleanup(&nmrep);
+       return (error);
+}
+
+int
+nfs4_commit_rpc(
+       nfsnode_t np,
+       u_int64_t offset,
+       u_int64_t count,
+       kauth_cred_t cred)
+{
+       struct nfsmount *nmp;
+       int error = 0, lockerror, status, nfsvers, numops;
+       u_int64_t xid, wverf;
+       uint32_t count32;
+       struct nfsm_chain nmreq, nmrep;
+
+       nmp = NFSTONMP(np);
+       FSDBG(521, np, offset, count, nmp ? nmp->nm_state : 0);
+       if (!nmp)
+               return (ENXIO);
+       if (!(nmp->nm_state & NFSSTA_HASWRITEVERF))
+               return (0);
+       nfsvers = nmp->nm_vers;
+
+       if (count > UINT32_MAX)
+               count32 = 0;
+       else
+               count32 = count;
+
+       nfsm_chain_null(&nmreq);
+       nfsm_chain_null(&nmrep);
+
+       // PUTFH, COMMIT, GETATTR
+       numops = 3;
+       nfsm_chain_build_alloc_init(error, &nmreq, 19 * NFSX_UNSIGNED);
+       nfsm_chain_add_compound_header(error, &nmreq, "commit", numops);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTFH);
+       nfsm_chain_add_fh(error, &nmreq, nfsvers, np->n_fhp, np->n_fhsize);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_COMMIT);
+       nfsm_chain_add_64(error, &nmreq, offset);
+       nfsm_chain_add_32(error, &nmreq, count32);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_GETATTR);
+       nfsm_chain_add_bitmap_masked(error, &nmreq, nfs_getattr_bitmap,
+               NFS_ATTR_BITMAP_LEN, nmp->nm_fsattr.nfsa_supp_attr);
+       nfsm_chain_build_done(error, &nmreq);
+       nfsm_assert(error, (numops == 0), EPROTO);
+       nfsmout_if(error);
+       error = nfs_request2(np, NULL, &nmreq, NFSPROC4_COMPOUND,
+                       current_thread(), cred, 0, &nmrep, &xid, &status);
+
+       if ((lockerror = nfs_lock(np, NFS_NODE_LOCK_EXCLUSIVE)))
+               error = lockerror;
+       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_COMMIT);
+       nfsm_chain_get_64(error, &nmrep, wverf);
+       nfsm_chain_op_check(error, &nmrep, NFS_OP_GETATTR);
+       nfsm_chain_loadattr(error, &nmrep, np, nfsvers, NULL, &xid);
+       if (!lockerror)
+               nfs_unlock(np);
+       nfsmout_if(error);
+       lck_mtx_lock(&nmp->nm_lock);
+       if (nmp->nm_verf != wverf) {
+               nmp->nm_verf = wverf;
+               error = NFSERR_STALEWRITEVERF;
+       }
+       lck_mtx_unlock(&nmp->nm_lock);
+nfsmout:
+       nfsm_chain_cleanup(&nmreq);
+       nfsm_chain_cleanup(&nmrep);
+       return (error);
+}
+
+int
+nfs4_pathconf_rpc(
+       nfsnode_t np,
+       struct nfs_fsattr *nfsap,
+       vfs_context_t ctx)
+{
+       u_int64_t xid;
+       int error = 0, lockerror, status, nfsvers, numops;
+       struct nfsm_chain nmreq, nmrep;
+       struct nfsmount *nmp = NFSTONMP(np);
+       uint32_t bitmap[NFS_ATTR_BITMAP_LEN];
+       struct nfs_vattr nvattr;
+
+       if (!nmp)
+               return (ENXIO);
+       nfsvers = nmp->nm_vers;
+
+       nfsm_chain_null(&nmreq);
+       nfsm_chain_null(&nmrep);
+
+       /* NFSv4: fetch "pathconf" info for this node */
+       numops = 2; // PUTFH + GETATTR
+       nfsm_chain_build_alloc_init(error, &nmreq, 16 * NFSX_UNSIGNED);
+       nfsm_chain_add_compound_header(error, &nmreq, "pathconf", numops);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTFH);
+       nfsm_chain_add_fh(error, &nmreq, nfsvers, np->n_fhp, np->n_fhsize);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_GETATTR);
+       NFS_COPY_ATTRIBUTES(nfs_getattr_bitmap, bitmap);
+       NFS_BITMAP_SET(bitmap, NFS_FATTR_MAXLINK);
+       NFS_BITMAP_SET(bitmap, NFS_FATTR_MAXNAME);
+       NFS_BITMAP_SET(bitmap, NFS_FATTR_NO_TRUNC);
+       NFS_BITMAP_SET(bitmap, NFS_FATTR_CHOWN_RESTRICTED);
+       NFS_BITMAP_SET(bitmap, NFS_FATTR_CASE_INSENSITIVE);
+       NFS_BITMAP_SET(bitmap, NFS_FATTR_CASE_PRESERVING);
+       nfsm_chain_add_bitmap_masked(error, &nmreq, bitmap,
+               NFS_ATTR_BITMAP_LEN, nmp->nm_fsattr.nfsa_supp_attr);
+       nfsm_chain_build_done(error, &nmreq);
+       nfsm_assert(error, (numops == 0), EPROTO);
+       nfsmout_if(error);
+       error = nfs_request(np, NULL, &nmreq, NFSPROC4_COMPOUND, ctx, &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_GETATTR);
+       nfsmout_if(error);
+       NFS_CLEAR_ATTRIBUTES(nvattr.nva_bitmap);
+       error = nfs4_parsefattr(&nmrep, nfsap, &nvattr, NULL, NULL);
+       nfsmout_if(error);
+       if ((lockerror = nfs_lock(np, NFS_NODE_LOCK_EXCLUSIVE)))
+               error = lockerror;
+       nfs_loadattrcache(np, &nvattr, &xid, 0);
+       if (!lockerror)
+               nfs_unlock(np);
+nfsmout:
+       nfsm_chain_cleanup(&nmreq);
+       nfsm_chain_cleanup(&nmrep);
+       return (error);
+}
+
+int
+nfs4_vnop_getattr(
+       struct vnop_getattr_args /* {
+               struct vnodeop_desc *a_desc;
+               vnode_t a_vp;
+               struct vnode_attr *a_vap;
+               vfs_context_t a_context;
+       } */ *ap)
+{
+       struct vnode_attr *vap = ap->a_vap;
+       struct nfs_vattr nva;
+       int error;
+
+       error = nfs_getattr(VTONFS(ap->a_vp), &nva, ap->a_context, 0);
+       if (error)
+               return (error);
+
+       /* copy what we have in nva to *a_vap */
+       if (NFS_BITMAP_ISSET(nva.nva_bitmap, NFS_FATTR_RAWDEV)) {
+               dev_t rdev = makedev(nva.nva_rawdev.specdata1, nva.nva_rawdev.specdata2);
+               VATTR_RETURN(vap, va_rdev, rdev);
+       }
+       if (NFS_BITMAP_ISSET(nva.nva_bitmap, NFS_FATTR_NUMLINKS))
+               VATTR_RETURN(vap, va_nlink, nva.nva_nlink);
+       if (NFS_BITMAP_ISSET(nva.nva_bitmap, NFS_FATTR_SIZE))
+               VATTR_RETURN(vap, va_data_size, nva.nva_size);
+       // VATTR_RETURN(vap, va_data_alloc, ???);
+       // VATTR_RETURN(vap, va_total_size, ???);
+       if (NFS_BITMAP_ISSET(nva.nva_bitmap, NFS_FATTR_SPACE_USED))
+               VATTR_RETURN(vap, va_total_alloc, nva.nva_bytes);
+       if (NFS_BITMAP_ISSET(nva.nva_bitmap, NFS_FATTR_OWNER))
+               VATTR_RETURN(vap, va_uid, nva.nva_uid);
+       if (NFS_BITMAP_ISSET(nva.nva_bitmap, NFS_FATTR_OWNER_GROUP))
+               VATTR_RETURN(vap, va_gid, nva.nva_gid);
+       if (NFS_BITMAP_ISSET(nva.nva_bitmap, NFS_FATTR_MODE))
+               VATTR_RETURN(vap, va_mode, nva.nva_mode);
+       if (NFS_BITMAP_ISSET(nva.nva_bitmap, NFS_FATTR_ARCHIVE) ||
+           NFS_BITMAP_ISSET(nva.nva_bitmap, NFS_FATTR_HIDDEN)) {
+               uint32_t flags = 0;
+               if (NFS_BITMAP_ISSET(nva.nva_bitmap, NFS_FATTR_ARCHIVE))
+                       flags |= SF_ARCHIVED;
+               if (NFS_BITMAP_ISSET(nva.nva_bitmap, NFS_FATTR_HIDDEN))
+                       flags |= UF_HIDDEN;
+               VATTR_RETURN(vap, va_flags, flags);
+       }
+       if (NFS_BITMAP_ISSET(nva.nva_bitmap, NFS_FATTR_TIME_CREATE)) {
+               vap->va_create_time.tv_sec = nva.nva_timesec[NFSTIME_CREATE];
+               vap->va_create_time.tv_nsec = nva.nva_timensec[NFSTIME_CREATE];
+               VATTR_SET_SUPPORTED(vap, va_create_time);
+       }
+       if (NFS_BITMAP_ISSET(nva.nva_bitmap, NFS_FATTR_TIME_ACCESS)) {
+               vap->va_access_time.tv_sec = nva.nva_timesec[NFSTIME_ACCESS];
+               vap->va_access_time.tv_nsec = nva.nva_timensec[NFSTIME_ACCESS];
+               VATTR_SET_SUPPORTED(vap, va_access_time);
+       }
+       if (NFS_BITMAP_ISSET(nva.nva_bitmap, NFS_FATTR_TIME_MODIFY)) {
+               vap->va_modify_time.tv_sec = nva.nva_timesec[NFSTIME_MODIFY];
+               vap->va_modify_time.tv_nsec = nva.nva_timensec[NFSTIME_MODIFY];
+               VATTR_SET_SUPPORTED(vap, va_modify_time);
+       }
+       if (NFS_BITMAP_ISSET(nva.nva_bitmap, NFS_FATTR_TIME_METADATA)) {
+               vap->va_change_time.tv_sec = nva.nva_timesec[NFSTIME_CHANGE];
+               vap->va_change_time.tv_nsec = nva.nva_timensec[NFSTIME_CHANGE];
+               VATTR_SET_SUPPORTED(vap, va_change_time);
+       }
+       if (NFS_BITMAP_ISSET(nva.nva_bitmap, NFS_FATTR_TIME_BACKUP)) {
+               vap->va_backup_time.tv_sec = nva.nva_timesec[NFSTIME_BACKUP];
+               vap->va_backup_time.tv_nsec = nva.nva_timensec[NFSTIME_BACKUP];
+               VATTR_SET_SUPPORTED(vap, va_backup_time);
+       }
+       if (NFS_BITMAP_ISSET(nva.nva_bitmap, NFS_FATTR_FILEID))
+               VATTR_RETURN(vap, va_fileid, nva.nva_fileid);
+       if (NFS_BITMAP_ISSET(nva.nva_bitmap, NFS_FATTR_TYPE))
+               VATTR_RETURN(vap, va_type, nva.nva_type);
+       if (NFS_BITMAP_ISSET(nva.nva_bitmap, NFS_FATTR_CHANGE))
+               VATTR_RETURN(vap, va_filerev, nva.nva_change);
+
+       // other attrs we might support someday:
+       // VATTR_RETURN(vap, va_encoding, ??? /* potentially unnormalized UTF-8? */);
+       // struct kauth_acl *va_acl;    /* access control list */
+       // guid_t       va_uuuid;       /* file owner UUID */
+       // guid_t       va_guuid;       /* file group UUID */
+
+       return (error);
+}
+
+int
+nfs4_setattr_rpc(
+       nfsnode_t np,
+       struct vnode_attr *vap,
+       vfs_context_t ctx,
+       int alreadylocked)
+{
+       struct nfsmount *nmp = NFSTONMP(np);
+       int error = 0, lockerror = ENOENT, status, nfsvers, numops;
+       u_int64_t xid;
+       struct nfsm_chain nmreq, nmrep;
+       uint32_t bitmap[NFS_ATTR_BITMAP_LEN], bmlen, stateid;
+
+       if (!nmp)
+               return (ENXIO);
+       nfsvers = nmp->nm_vers;
+
+       if (VATTR_IS_ACTIVE(vap, va_flags) && (vap->va_flags & ~(SF_ARCHIVED|UF_HIDDEN))) {
+               /* we don't support setting unsupported flags (duh!) */
+               if (vap->va_active & ~VNODE_ATTR_va_flags)
+                       return (EINVAL);        /* return EINVAL if other attributes also set */
+               else
+                       return (ENOTSUP);       /* return ENOTSUP for chflags(2) */
+       }
+
+       nfsm_chain_null(&nmreq);
+       nfsm_chain_null(&nmrep);
+
+       // PUTFH, SETATTR, GETATTR
+       numops = 3;
+       nfsm_chain_build_alloc_init(error, &nmreq, 40 * NFSX_UNSIGNED);
+       nfsm_chain_add_compound_header(error, &nmreq, "setattr", numops);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTFH);
+       nfsm_chain_add_fh(error, &nmreq, nfsvers, np->n_fhp, np->n_fhsize);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_SETATTR);
+       if (VATTR_IS_ACTIVE(vap, va_data_size))
+               stateid = 0xffffffff; /* XXX use the special stateid for now */
+       else
+               stateid = 0;
+       nfsm_chain_add_32(error, &nmreq, stateid);
+       nfsm_chain_add_32(error, &nmreq, stateid);
+       nfsm_chain_add_32(error, &nmreq, stateid);
+       nfsm_chain_add_32(error, &nmreq, stateid);
+       nfsm_chain_add_fattr4(error, &nmreq, vap, nmp);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_GETATTR);
+       nfsm_chain_add_bitmap_masked(error, &nmreq, nfs_getattr_bitmap,
+               NFS_ATTR_BITMAP_LEN, nmp->nm_fsattr.nfsa_supp_attr);
+       nfsm_chain_build_done(error, &nmreq);
+       nfsm_assert(error, (numops == 0), EPROTO);
+       nfsmout_if(error);
+       error = nfs_request(np, NULL, &nmreq, NFSPROC4_COMPOUND, ctx, &nmrep, &xid, &status);
+
+       if (!alreadylocked && ((lockerror = nfs_lock(np, NFS_NODE_LOCK_EXCLUSIVE))))
+               error = lockerror;
+       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_SETATTR);
+       bmlen = NFS_ATTR_BITMAP_LEN;
+       nfsm_chain_get_bitmap(error, &nmrep, bitmap, bmlen);
+       nfsmout_if(error);
+       nfs_vattr_set_supported(bitmap, vap);
+       nfsm_chain_op_check(error, &nmrep, NFS_OP_GETATTR);
+       nfsm_chain_loadattr(error, &nmrep, np, nfsvers, NULL, &xid);
+       if (error)
+               NATTRINVALIDATE(np);
+nfsmout:
+       if (!alreadylocked && !lockerror)
+               nfs_unlock(np);
+       nfsm_chain_cleanup(&nmreq);
+       nfsm_chain_cleanup(&nmrep);
+       return (error);
+}
+
+int
+nfs4_vnop_open(struct vnop_open_args *ap)
+{
+       return nfs3_vnop_open(ap);
+}
+
+int
+nfs4_vnop_close(struct vnop_close_args *ap)
+{
+       return nfs3_vnop_close(ap);
+}
+
+int
+nfs4_vnop_advlock(__unused struct vnop_advlock_args *ap)
+{
+       return (ENOSYS);
+}
+
+/*
+ * Note: the NFSv4 CREATE RPC is for everything EXCEPT regular files.
+ * Files are created using the NFSv4 OPEN RPC.  So we must open the
+ * file to create it and then close it immediately.
+ */
+int
+nfs4_vnop_create(
+       struct vnop_create_args /* {
+               struct vnodeop_desc *a_desc;
+               vnode_t a_dvp;
+               vnode_t *a_vpp;
+               struct componentname *a_cnp;
+               struct vnode_attr *a_vap;
+               vfs_context_t a_context;
+       } */ *ap)
+{
+       vfs_context_t ctx = ap->a_context;
+       struct componentname *cnp = ap->a_cnp;
+       struct vnode_attr *vap = ap->a_vap;
+       vnode_t dvp = ap->a_dvp;
+       vnode_t *vpp = ap->a_vpp;
+       struct nfsmount *nmp;
+       struct nfs_vattr nvattr, dnvattr;
+       int error = 0, create_error = EIO, lockerror = ENOENT, status;
+       int nfsvers, numops;
+       u_int64_t xid, savedxid = 0;
+       nfsnode_t dnp = VTONFS(dvp);
+       nfsnode_t np = NULL;
+       vnode_t newvp = NULL;
+       struct nfsm_chain nmreq, nmrep;
+       uint32_t bitmap[NFS_ATTR_BITMAP_LEN], bmlen;
+       uint32_t seqid, stateid[4], rflags, delegation, val;
+       fhandle_t fh;
+       struct nfsreq *req = NULL;
+       struct nfs_dulookup dul;
+
+       static uint32_t nfs4_open_owner_hack = 0;
+
+       nmp = VTONMP(dvp);
+       if (!nmp)
+               return (ENXIO);
+       nfsvers = nmp->nm_vers;
+
+       seqid = stateid[0] = stateid[1] = stateid[2] = stateid[3] = 0;
+       rflags = 0;
+
+       nfs_dulookup_init(&dul, dnp, cnp->cn_nameptr, cnp->cn_namelen);
+
+       nfsm_chain_null(&nmreq);
+       nfsm_chain_null(&nmrep);
+
+       // PUTFH, SAVEFH, OPEN(CREATE), GETATTR(FH), RESTOREFH, GETATTR
+       numops = 6;
+       nfsm_chain_build_alloc_init(error, &nmreq, 53 * NFSX_UNSIGNED + cnp->cn_namelen);
+       nfsm_chain_add_compound_header(error, &nmreq, "create", numops);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTFH);
+       nfsm_chain_add_fh(error, &nmreq, nfsvers, dnp->n_fhp, dnp->n_fhsize);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_SAVEFH);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_OPEN);
+       nfsm_chain_add_32(error, &nmreq, seqid);
+       seqid++;
+       nfsm_chain_add_32(error, &nmreq, NFS_OPEN_SHARE_ACCESS_BOTH);
+       nfsm_chain_add_32(error, &nmreq, NFS_OPEN_SHARE_DENY_NONE);
+       nfsm_chain_add_64(error, &nmreq, nmp->nm_clientid); // open_owner4.clientid
+       OSAddAtomic(1, (SInt32*)&nfs4_open_owner_hack);
+       nfsm_chain_add_32(error, &nmreq, sizeof(nfs4_open_owner_hack));
+       nfsm_chain_add_opaque(error, &nmreq, &nfs4_open_owner_hack, sizeof(nfs4_open_owner_hack)); // open_owner4.owner
+       // openflag4
+       nfsm_chain_add_32(error, &nmreq, NFS_OPEN_CREATE);
+       nfsm_chain_add_32(error, &nmreq, NFS_CREATE_UNCHECKED); // XXX exclusive/guarded
+       nfsm_chain_add_fattr4(error, &nmreq, vap, nmp);
+       // open_claim4
+       nfsm_chain_add_32(error, &nmreq, NFS_CLAIM_NULL);
+       nfsm_chain_add_string(error, &nmreq, cnp->cn_nameptr, cnp->cn_namelen);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_GETATTR);
+       NFS_COPY_ATTRIBUTES(nfs_getattr_bitmap, bitmap);
+       NFS_BITMAP_SET(bitmap, NFS_FATTR_FILEHANDLE);
+       nfsm_chain_add_bitmap_masked(error, &nmreq, bitmap,
+               NFS_ATTR_BITMAP_LEN, nmp->nm_fsattr.nfsa_supp_attr);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_RESTOREFH);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_GETATTR);
+       nfsm_chain_add_bitmap_masked(error, &nmreq, nfs_getattr_bitmap,
+               NFS_ATTR_BITMAP_LEN, nmp->nm_fsattr.nfsa_supp_attr);
+       nfsm_chain_build_done(error, &nmreq);
+       nfsm_assert(error, (numops == 0), EPROTO);
+       nfsmout_if(error);
+       if ((lockerror = nfs_lock(dnp, NFS_NODE_LOCK_EXCLUSIVE)))
+               error = lockerror;
+       nfsmout_if(error);
+
+       error = nfs_request_async(dnp, NULL, &nmreq, NFSPROC4_COMPOUND,
+                       vfs_context_thread(ctx), vfs_context_ucred(ctx), NULL, &req);
+       if (!error) {
+               nfs_dulookup_start(&dul, dnp, ctx);
+               error = nfs_request_async_finish(req, &nmrep, &xid, &status);
+       }
+       savedxid = xid;
+
+       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_SAVEFH);
+       nfsm_chain_op_check(error, &nmrep, NFS_OP_OPEN);
+       nfsm_chain_get_32(error, &nmrep, stateid[0]);
+       nfsm_chain_get_32(error, &nmrep, stateid[1]);
+       nfsm_chain_get_32(error, &nmrep, stateid[2]);
+       nfsm_chain_get_32(error, &nmrep, stateid[3]);
+       nfsm_chain_check_change_info(error, &nmrep, dnp);
+       nfsm_chain_get_32(error, &nmrep, rflags);
+       bmlen = NFS_ATTR_BITMAP_LEN;
+       nfsm_chain_get_bitmap(error, &nmrep, bitmap, bmlen);
+       nfsm_chain_get_32(error, &nmrep, delegation);
+       if (!error)
+               switch (delegation) {
+               case NFS_OPEN_DELEGATE_NONE:
+                       break;
+               case NFS_OPEN_DELEGATE_READ:
+                       printf("nfs4_vnop_create: read delegation?\n");
+                       nfsm_chain_adv(error, &nmrep, 5*NFSX_UNSIGNED);
+                       // ACE:
+                       nfsm_chain_adv(error, &nmrep, 3 * NFSX_UNSIGNED);
+                       nfsm_chain_get_32(error, &nmrep, val); /* string length */
+                       nfsm_chain_adv(error, &nmrep, nfsm_rndup(val));
+                       break;
+               case NFS_OPEN_DELEGATE_WRITE:
+                       printf("nfs4_vnop_create: write delegation?\n");
+                       nfsm_chain_adv(error, &nmrep, 5*NFSX_UNSIGNED);
+                       nfsm_chain_adv(error, &nmrep, 3*NFSX_UNSIGNED);
+                       // ACE:
+                       nfsm_chain_adv(error, &nmrep, 3 * NFSX_UNSIGNED);
+                       nfsm_chain_get_32(error, &nmrep, val); /* string length */
+                       nfsm_chain_adv(error, &nmrep, nfsm_rndup(val));
+                       break;
+               default:
+                       error = EBADRPC;
+                       break;
+               }
+       /* At this point if we have no error, the object was created. */
+       /* if we don't get attributes, then we should lookitup. */
+       create_error = error;
+       nfsmout_if(error);
+       nfs_vattr_set_supported(bitmap, vap);
+       nfsm_chain_op_check(error, &nmrep, NFS_OP_GETATTR);
+       nfsmout_if(error);
+       NFS_CLEAR_ATTRIBUTES(nvattr.nva_bitmap);
+       error = nfs4_parsefattr(&nmrep, NULL, &nvattr, &fh, NULL);
+       nfsmout_if(error);
+       if (!NFS_BITMAP_ISSET(nvattr.nva_bitmap, NFS_FATTR_FILEHANDLE)) {
+               printf("nfs: open/create didn't return filehandle?\n");
+               error = EBADRPC;
+               goto nfsmout;
+       }
+       /* directory attributes: if we don't get them, make sure to invalidate */
+       nfsm_chain_op_check(error, &nmrep, NFS_OP_RESTOREFH);
+       nfsm_chain_op_check(error, &nmrep, NFS_OP_GETATTR);
+       nfsm_chain_loadattr(error, &nmrep, dnp, nfsvers, NULL, &xid);
+       if (error)
+               NATTRINVALIDATE(dnp);
+
+       if (rflags & NFS_OPEN_RESULT_CONFIRM) {
+               nfsm_chain_cleanup(&nmreq);
+               nfsm_chain_cleanup(&nmrep);
+               // PUTFH, OPEN_CONFIRM, GETATTR
+               numops = 3;
+               nfsm_chain_build_alloc_init(error, &nmreq, 23 * NFSX_UNSIGNED);
+               nfsm_chain_add_compound_header(error, &nmreq, "create_confirm", numops);
+               numops--;
+               nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTFH);
+               nfsm_chain_add_fh(error, &nmreq, nfsvers, fh.fh_data, fh.fh_len);
+               numops--;
+               nfsm_chain_add_32(error, &nmreq, NFS_OP_OPEN_CONFIRM);
+               nfsm_chain_add_32(error, &nmreq, stateid[0]);
+               nfsm_chain_add_32(error, &nmreq, stateid[1]);
+               nfsm_chain_add_32(error, &nmreq, stateid[2]);
+               nfsm_chain_add_32(error, &nmreq, stateid[3]);
+               nfsm_chain_add_32(error, &nmreq, seqid);
+               seqid++;
+               numops--;
+               nfsm_chain_add_32(error, &nmreq, NFS_OP_GETATTR);
+               nfsm_chain_add_bitmap_masked(error, &nmreq, nfs_getattr_bitmap,
+                       NFS_ATTR_BITMAP_LEN, nmp->nm_fsattr.nfsa_supp_attr);
+               nfsm_chain_build_done(error, &nmreq);
+               nfsm_assert(error, (numops == 0), EPROTO);
+               nfsmout_if(error);
+               error = nfs_request(dnp, NULL, &nmreq, NFSPROC4_COMPOUND, ctx, &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_OPEN_CONFIRM);
+               nfsm_chain_get_32(error, &nmrep, stateid[0]);
+               nfsm_chain_get_32(error, &nmrep, stateid[1]);
+               nfsm_chain_get_32(error, &nmrep, stateid[2]);
+               nfsm_chain_get_32(error, &nmrep, stateid[3]);
+               nfsm_chain_op_check(error, &nmrep, NFS_OP_GETATTR);
+               nfsmout_if(error);
+               NFS_CLEAR_ATTRIBUTES(nvattr.nva_bitmap);
+               error = nfs4_parsefattr(&nmrep, NULL, &nvattr, NULL, NULL);
+               nfsmout_if(error);
+               savedxid = xid;
+       }
+       nfsmout_if(error);
+       nfsm_chain_cleanup(&nmreq);
+       nfsm_chain_cleanup(&nmrep);
+
+       // PUTFH, CLOSE
+       numops = 2;
+       nfsm_chain_build_alloc_init(error, &nmreq, 19 * NFSX_UNSIGNED);
+       nfsm_chain_add_compound_header(error, &nmreq, "create_close", numops);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTFH);
+       nfsm_chain_add_fh(error, &nmreq, nfsvers, fh.fh_data, fh.fh_len);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_CLOSE);
+       nfsm_chain_add_32(error, &nmreq, seqid);
+       seqid++;
+       nfsm_chain_add_32(error, &nmreq, stateid[0]);
+       nfsm_chain_add_32(error, &nmreq, stateid[1]);
+       nfsm_chain_add_32(error, &nmreq, stateid[2]);
+       nfsm_chain_add_32(error, &nmreq, stateid[3]);
+       nfsm_chain_build_done(error, &nmreq);
+       nfsm_assert(error, (numops == 0), EPROTO);
+       nfsmout_if(error);
+       error = nfs_request(dnp, NULL, &nmreq, NFSPROC4_COMPOUND, ctx, &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_CLOSE);
+       nfsm_chain_get_32(error, &nmrep, stateid[0]);
+       nfsm_chain_get_32(error, &nmrep, stateid[1]);
+       nfsm_chain_get_32(error, &nmrep, stateid[2]);
+       nfsm_chain_get_32(error, &nmrep, stateid[3]);
+       if (error)
+               printf("nfs4_vnop_create: close error %d\n", error);
+
+nfsmout:
+       nfsm_chain_cleanup(&nmreq);
+       nfsm_chain_cleanup(&nmrep);
+
+       if (!lockerror) {
+               if (!create_error && (dnp->n_flag & NNEGNCENTRIES)) {
+                       dnp->n_flag &= ~NNEGNCENTRIES;
+                       cache_purge_negatives(dvp);
+               }
+               dnp->n_flag |= NMODIFIED;
+               if (!nfs_getattr(dnp, &dnvattr, ctx, 1)) {
+                       if (NFS_CHANGED_NC(nfsvers, dnp, &dnvattr)) {
+                               dnp->n_flag &= ~NNEGNCENTRIES;
+                               cache_purge(dvp);
+                               NFS_CHANGED_UPDATE_NC(nfsvers, dnp, &dnvattr);
+                       }
+               }
+       }
+
+       if (!error && fh.fh_len) {
+               /* create the vnode with the filehandle and attributes */
+               xid = savedxid;
+               error = nfs_nget(NFSTOMP(dnp), dnp, cnp, fh.fh_data, fh.fh_len, &nvattr, &xid, NG_MAKEENTRY, &np);
+               if (!error)
+                       newvp = NFSTOV(np);
+       }
+
+       nfs_dulookup_finish(&dul, dnp, ctx);
+
+       /*
+        * Kludge: Map EEXIST => 0 assuming that you have a reply to a retry
+        * if we can succeed in looking up the object.
+        */
+       if ((create_error == EEXIST) || (!create_error && !newvp)) {
+               error = nfs_lookitup(dnp, cnp->cn_nameptr, cnp->cn_namelen, ctx, &np);
+               if (!error) {
+                       newvp = NFSTOV(np);
+                       if (vnode_vtype(newvp) != VLNK)
+                               error = EEXIST;
+               }
+       }
+       if (!lockerror)
+               nfs_unlock(dnp);
+       if (error) {
+               if (newvp) {
+                       nfs_unlock(np);
+                       vnode_put(newvp);
+               }
+       } else {
+               nfs_unlock(np);
+               *vpp = newvp;
+       }
+       return (error);
+}
+
+/*
+ * Note: the NFSv4 CREATE RPC is for everything EXCEPT regular files.
+ */
+static int
+nfs4_create_rpc(
+       vfs_context_t ctx,
+       nfsnode_t dnp,
+       struct componentname *cnp,
+       struct vnode_attr *vap,
+       int type,
+       char *link,
+       nfsnode_t *npp)
+{
+       struct nfsmount *nmp;
+       struct nfs_vattr nvattr, dnvattr;
+       int error = 0, create_error = EIO, lockerror = ENOENT, status;
+       int nfsvers, numops;
+       u_int64_t xid, savedxid = 0;
+       nfsnode_t np = NULL;
+       vnode_t newvp = NULL;
+       struct nfsm_chain nmreq, nmrep;
+       uint32_t bitmap[NFS_ATTR_BITMAP_LEN], bmlen;
+       const char *tag;
+       nfs_specdata sd;
+       fhandle_t fh;
+       struct nfsreq *req = NULL;
+       struct nfs_dulookup dul;
+
+       nmp = NFSTONMP(dnp);
+       if (!nmp)
+               return (ENXIO);
+       nfsvers = nmp->nm_vers;
+
+       sd.specdata1 = sd.specdata2 = 0;
+
+       switch (type) {
+       case NFLNK:
+               tag = "symlink";
+               break;
+       case NFBLK:
+       case NFCHR:
+               tag = "mknod";
+               if (!VATTR_IS_ACTIVE(vap, va_rdev))
+                       return (EINVAL);
+               sd.specdata1 = major(vap->va_rdev);
+               sd.specdata2 = minor(vap->va_rdev);
+               break;
+       case NFSOCK:
+       case NFFIFO:
+               tag = "mknod";
+               break;
+       case NFDIR:
+               tag = "mkdir";
+               break;
+       default:
+               return (EINVAL);
+       }
+
+       nfs_dulookup_init(&dul, dnp, cnp->cn_nameptr, cnp->cn_namelen);
+
+       nfsm_chain_null(&nmreq);
+       nfsm_chain_null(&nmrep);
+
+       // PUTFH, SAVEFH, CREATE, GETATTR(FH), RESTOREFH, GETATTR
+       numops = 6;
+       nfsm_chain_build_alloc_init(error, &nmreq, 66 * NFSX_UNSIGNED);
+       nfsm_chain_add_compound_header(error, &nmreq, tag, numops);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTFH);
+       nfsm_chain_add_fh(error, &nmreq, nfsvers, dnp->n_fhp, dnp->n_fhsize);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_SAVEFH);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_CREATE);
+       nfsm_chain_add_32(error, &nmreq, type);
+       if (type == NFLNK) {
+               nfsm_chain_add_string(error, &nmreq, link, strlen(link));
+       } else if ((type == NFBLK) || (type == NFCHR)) {
+               nfsm_chain_add_32(error, &nmreq, sd.specdata1);
+               nfsm_chain_add_32(error, &nmreq, sd.specdata2);
+       }
+       nfsm_chain_add_string(error, &nmreq, cnp->cn_nameptr, cnp->cn_namelen);
+       nfsm_chain_add_fattr4(error, &nmreq, vap, nmp);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_GETATTR);
+       NFS_COPY_ATTRIBUTES(nfs_getattr_bitmap, bitmap);
+       NFS_BITMAP_SET(bitmap, NFS_FATTR_FILEHANDLE);
+       nfsm_chain_add_bitmap_masked(error, &nmreq, bitmap,
+               NFS_ATTR_BITMAP_LEN, nmp->nm_fsattr.nfsa_supp_attr);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_RESTOREFH);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_GETATTR);
+       nfsm_chain_add_bitmap_masked(error, &nmreq, nfs_getattr_bitmap,
+               NFS_ATTR_BITMAP_LEN, nmp->nm_fsattr.nfsa_supp_attr);
+       nfsm_chain_build_done(error, &nmreq);
+       nfsm_assert(error, (numops == 0), EPROTO);
+       nfsmout_if(error);
+       if ((lockerror = nfs_lock(dnp, NFS_NODE_LOCK_EXCLUSIVE)))
+               error = lockerror;
+       nfsmout_if(error);
+
+       error = nfs_request_async(dnp, NULL, &nmreq, NFSPROC4_COMPOUND,
+                       vfs_context_thread(ctx), vfs_context_ucred(ctx), NULL, &req);
+       if (!error) {
+               nfs_dulookup_start(&dul, dnp, ctx);
+               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_SAVEFH);
+       nfsmout_if(error);
+       nfsm_chain_op_check(error, &nmrep, NFS_OP_CREATE);
+       nfsm_chain_check_change_info(error, &nmrep, dnp);
+       bmlen = NFS_ATTR_BITMAP_LEN;
+       nfsm_chain_get_bitmap(error, &nmrep, bitmap, bmlen);
+       /* At this point if we have no error, the object was created. */
+       /* if we don't get attributes, then we should lookitup. */
+       create_error = error;
+       nfsmout_if(error);
+       nfs_vattr_set_supported(bitmap, vap);
+       nfsm_chain_op_check(error, &nmrep, NFS_OP_GETATTR);
+       nfsmout_if(error);
+       NFS_CLEAR_ATTRIBUTES(nvattr.nva_bitmap);
+       error = nfs4_parsefattr(&nmrep, NULL, &nvattr, &fh, NULL);
+       nfsmout_if(error);
+       if (!NFS_BITMAP_ISSET(nvattr.nva_bitmap, NFS_FATTR_FILEHANDLE)) {
+               printf("nfs: create/%s didn't return filehandle?\n", tag);
+               error = EBADRPC;
+               goto nfsmout;
+       }
+       /* directory attributes: if we don't get them, make sure to invalidate */
+       nfsm_chain_op_check(error, &nmrep, NFS_OP_RESTOREFH);
+       nfsm_chain_op_check(error, &nmrep, NFS_OP_GETATTR);
+       savedxid = xid;
+       nfsm_chain_loadattr(error, &nmrep, dnp, nfsvers, NULL, &xid);
+       if (error)
+               NATTRINVALIDATE(dnp);
+
+nfsmout:
+       nfsm_chain_cleanup(&nmreq);
+       nfsm_chain_cleanup(&nmrep);
+
+       if (!lockerror) {
+               if (!create_error && (dnp->n_flag & NNEGNCENTRIES)) {
+                       dnp->n_flag &= ~NNEGNCENTRIES;
+                       cache_purge_negatives(NFSTOV(dnp));
+               }
+               dnp->n_flag |= NMODIFIED;
+               if (!nfs_getattr(dnp, &dnvattr, ctx, 1)) {
+                       if (NFS_CHANGED_NC(nfsvers, dnp, &dnvattr)) {
+                               dnp->n_flag &= ~NNEGNCENTRIES;
+                               cache_purge(NFSTOV(dnp));
+                               NFS_CHANGED_UPDATE_NC(nfsvers, dnp, &dnvattr);
+                       }
+               }
+       }
+
+       if (!error && fh.fh_len) {
+               /* create the vnode with the filehandle and attributes */
+               xid = savedxid;
+               error = nfs_nget(NFSTOMP(dnp), dnp, cnp, fh.fh_data, fh.fh_len, &nvattr, &xid, NG_MAKEENTRY, &np);
+               if (!error)
+                       newvp = NFSTOV(np);
+       }
+
+       nfs_dulookup_finish(&dul, dnp, ctx);
+
+       /*
+        * Kludge: Map EEXIST => 0 assuming that you have a reply to a retry
+        * if we can succeed in looking up the object.
+        */
+       if ((create_error == EEXIST) || (!create_error && !newvp)) {
+               error = nfs_lookitup(dnp, cnp->cn_nameptr, cnp->cn_namelen, ctx, &np);
+               if (!error) {
+                       newvp = NFSTOV(np);
+                       if (vnode_vtype(newvp) != VLNK)
+                               error = EEXIST;
+               }
+       }
+       if (!lockerror)
+               nfs_unlock(dnp);
+       if (error) {
+               if (newvp) {
+                       nfs_unlock(np);
+                       vnode_put(newvp);
+               }
+       } else {
+               nfs_unlock(np);
+               *npp = np;
+       }
+       return (error);
+}
+
+int
+nfs4_vnop_mknod(
+       struct vnop_mknod_args /* {
+               struct vnodeop_desc *a_desc;
+               vnode_t a_dvp;
+               vnode_t *a_vpp;
+               struct componentname *a_cnp;
+               struct vnode_attr *a_vap;
+               vfs_context_t a_context;
+       } */ *ap)
+{
+       nfsnode_t np = NULL;
+       struct nfsmount *nmp;
+       int error;
+
+       nmp = VTONMP(ap->a_dvp);
+       if (!nmp)
+               return (ENXIO);
+
+       if (!VATTR_IS_ACTIVE(ap->a_vap, va_type))
+               return (EINVAL);
+       switch (ap->a_vap->va_type) {
+       case VBLK:
+       case VCHR:
+       case VFIFO:
+       case VSOCK:
+               break;
+       default:
+               return (ENOTSUP);
+       }
+
+       error = nfs4_create_rpc(ap->a_context, VTONFS(ap->a_dvp), ap->a_cnp, ap->a_vap,
+                       vtonfs_type(ap->a_vap->va_type, nmp->nm_vers), NULL, &np);
+       if (!error)
+               *ap->a_vpp = NFSTOV(np);
+       return (error);
+}
+
+int
+nfs4_vnop_mkdir(
+       struct vnop_mkdir_args /* {
+               struct vnodeop_desc *a_desc;
+               vnode_t a_dvp;
+               vnode_t *a_vpp;
+               struct componentname *a_cnp;
+               struct vnode_attr *a_vap;
+               vfs_context_t a_context;
+       } */ *ap)
+{
+       nfsnode_t np = NULL;
+       int error;
+
+       error = nfs4_create_rpc(ap->a_context, VTONFS(ap->a_dvp), ap->a_cnp, ap->a_vap,
+                       NFDIR, NULL, &np);
+       if (!error)
+               *ap->a_vpp = NFSTOV(np);
+       return (error);
+}
+
+int
+nfs4_vnop_symlink(
+       struct vnop_symlink_args /* {
+               struct vnodeop_desc *a_desc;
+               vnode_t a_dvp;
+               vnode_t *a_vpp;
+               struct componentname *a_cnp;
+               struct vnode_attr *a_vap;
+               char *a_target;
+               vfs_context_t a_context;
+       } */ *ap)
+{
+       nfsnode_t np = NULL;
+       int error;
+
+       error = nfs4_create_rpc(ap->a_context, VTONFS(ap->a_dvp), ap->a_cnp, ap->a_vap,
+                       NFLNK, ap->a_target, &np);
+       if (!error)
+               *ap->a_vpp = NFSTOV(np);
+       return (error);
+}
+
+int
+nfs4_vnop_link(
+       struct vnop_link_args /* {
+               struct vnodeop_desc *a_desc;
+               vnode_t a_vp;
+               vnode_t a_tdvp;
+               struct componentname *a_cnp;
+               vfs_context_t a_context;
+       } */ *ap)
+{
+       vfs_context_t ctx = ap->a_context;
+       vnode_t vp = ap->a_vp;
+       vnode_t tdvp = ap->a_tdvp;
+       struct componentname *cnp = ap->a_cnp;
+       int error = 0, status;
+       struct nfsmount *nmp;
+       nfsnode_t np = VTONFS(vp);
+       nfsnode_t tdnp = VTONFS(tdvp);
+       int nfsvers, numops;
+       u_int64_t xid, savedxid;
+       struct nfsm_chain nmreq, nmrep;
+
+       if (vnode_mount(vp) != vnode_mount(tdvp))
+               return (EXDEV);
+
+       nmp = VTONMP(vp);
+       if (!nmp)
+               return (ENXIO);
+       nfsvers = nmp->nm_vers;
+
+       /*
+        * Push all writes to the server, so that the attribute cache
+        * doesn't get "out of sync" with the server.
+        * XXX There should be a better way!
+        */
+       nfs_flush(np, MNT_WAIT, vfs_context_thread(ctx), V_IGNORE_WRITEERR);
+
+       error = nfs_lock2(tdnp, np, NFS_NODE_LOCK_EXCLUSIVE);
+       if (error)
+               return (error);
+
+       nfsm_chain_null(&nmreq);
+       nfsm_chain_null(&nmrep);
+
+       // PUTFH(SOURCE), SAVEFH, PUTFH(DIR), LINK, GETATTR(DIR), RESTOREFH, GETATTR
+       numops = 7;
+       nfsm_chain_build_alloc_init(error, &nmreq, 29 * NFSX_UNSIGNED + cnp->cn_namelen);
+       nfsm_chain_add_compound_header(error, &nmreq, "link", numops);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTFH);
+       nfsm_chain_add_fh(error, &nmreq, nfsvers, np->n_fhp, np->n_fhsize);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_SAVEFH);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTFH);
+       nfsm_chain_add_fh(error, &nmreq, nfsvers, tdnp->n_fhp, tdnp->n_fhsize);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_LINK);
+       nfsm_chain_add_string(error, &nmreq, cnp->cn_nameptr, cnp->cn_namelen);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_GETATTR);
+       nfsm_chain_add_bitmap_masked(error, &nmreq, nfs_getattr_bitmap,
+               NFS_ATTR_BITMAP_LEN, nmp->nm_fsattr.nfsa_supp_attr);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_RESTOREFH);
+       numops--;
+       nfsm_chain_add_32(error, &nmreq, NFS_OP_GETATTR);
+       nfsm_chain_add_bitmap_masked(error, &nmreq, nfs_getattr_bitmap,
+               NFS_ATTR_BITMAP_LEN, nmp->nm_fsattr.nfsa_supp_attr);
+       nfsm_chain_build_done(error, &nmreq);
+       nfsm_assert(error, (numops == 0), EPROTO);
+       nfsmout_if(error);
+       error = nfs_request(tdnp, NULL, &nmreq, NFSPROC4_COMPOUND, ctx, &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_SAVEFH);
+       nfsm_chain_op_check(error, &nmrep, NFS_OP_PUTFH);
+       nfsm_chain_op_check(error, &nmrep, NFS_OP_LINK);
+       nfsm_chain_check_change_info(error, &nmrep, tdnp);
+       /* directory attributes: if we don't get them, make sure to invalidate */
+       nfsm_chain_op_check(error, &nmrep, NFS_OP_GETATTR);
+       savedxid = xid;
+       nfsm_chain_loadattr(error, &nmrep, tdnp, nfsvers, NULL, &xid);
+       if (error)
+               NATTRINVALIDATE(tdnp);
+       /* link attributes: if we don't get them, make sure to invalidate */
+       nfsm_chain_op_check(error, &nmrep, NFS_OP_RESTOREFH);
+       nfsm_chain_op_check(error, &nmrep, NFS_OP_GETATTR);
+       xid = savedxid;
+       nfsm_chain_loadattr(error, &nmrep, np, nfsvers, NULL, &xid);
+       if (error)
+               NATTRINVALIDATE(np);
+nfsmout:
+       nfsm_chain_cleanup(&nmreq);
+       nfsm_chain_cleanup(&nmrep);
+       tdnp->n_flag |= NMODIFIED;
+       /* Kludge: Map EEXIST => 0 assuming that it is a reply to a retry. */
+       if (error == EEXIST)
+               error = 0;
+       if (!error && (tdnp->n_flag & NNEGNCENTRIES)) {
+               tdnp->n_flag &= ~NNEGNCENTRIES;
+               cache_purge_negatives(tdvp);
+       }
+       nfs_unlock2(tdnp, np);
+       return (error);
+}
+
+int
+nfs4_vnop_rmdir(
+       struct vnop_rmdir_args /* {
+               struct vnodeop_desc *a_desc;
+               vnode_t a_dvp;
+               vnode_t a_vp;
+               struct componentname *a_cnp;
+               vfs_context_t a_context;
+       } */ *ap)
+{
+       vfs_context_t ctx = ap->a_context;
+       vnode_t vp = ap->a_vp;
+       vnode_t dvp = ap->a_dvp;
+       struct componentname *cnp = ap->a_cnp;
+       int error = 0;
+       nfsnode_t np = VTONFS(vp);
+       nfsnode_t dnp = VTONFS(dvp);
+       struct nfs_vattr dnvattr;
+       struct nfs_dulookup dul;
+
+       if (vnode_vtype(vp) != VDIR)
+               return (EINVAL);
+
+       nfs_dulookup_init(&dul, dnp, cnp->cn_nameptr, cnp->cn_namelen);
+
+       if ((error = nfs_lock2(dnp, np, NFS_NODE_LOCK_EXCLUSIVE)))
+               return (error);
+
+       nfs_dulookup_start(&dul, dnp, ctx);
+
+       error = nfs4_remove_rpc(dnp, cnp->cn_nameptr, cnp->cn_namelen,
+                       vfs_context_thread(ctx), vfs_context_ucred(ctx));
+
+       cache_purge(vp);
+       if (!nfs_getattr(dnp, &dnvattr, ctx, 1)) {
+               if (NFS_CHANGED_NC(NFS_VER4, dnp, &dnvattr)) {
+                       dnp->n_flag &= ~NNEGNCENTRIES;
+                       cache_purge(dvp);
+                       NFS_CHANGED_UPDATE_NC(NFS_VER4, dnp, &dnvattr);
+               }
+       }
+
+       nfs_dulookup_finish(&dul, dnp, ctx);
+       nfs_unlock2(dnp, np);
+
+       /*
+        * Kludge: Map ENOENT => 0 assuming that you have a reply to a retry.
+        */
+       if (error == ENOENT)
+               error = 0;
+       if (!error) {
+               /*
+                * remove nfsnode from hash now so we can't accidentally find it
+                * again if another object gets created with the same filehandle
+                * before this vnode gets reclaimed
+                */
+               lck_mtx_lock(nfs_node_hash_mutex);
+               if (np->n_hflag & NHHASHED) {
+                       LIST_REMOVE(np, n_hash);
+                       np->n_hflag &= ~NHHASHED;
+                       FSDBG(266, 0, np, np->n_flag, 0xb1eb1e);
+               }
+               lck_mtx_unlock(nfs_node_hash_mutex);
+       }
+       return (error);
+}
+