]> git.saurik.com Git - apple/xnu.git/blobdiff - bsd/nfs/nfs_node.c
xnu-3789.1.32.tar.gz
[apple/xnu.git] / bsd / nfs / nfs_node.c
index 7d19267871a32128c8c080f4b2e142a2ccf10170..4372429196c416e0cbc2e83f3307d0a9ef04ab3b 100644 (file)
@@ -1,8 +1,8 @@
 /*
- * Copyright (c) 2000-2009 Apple Inc. All rights reserved.
+ * Copyright (c) 2000-2016 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
  * 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,
@@ -22,7 +22,7 @@
  * 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@
  */
 /* Copyright (c) 1995 NeXT Computer, Inc. All Rights Reserved */
 #include <sys/proc.h>
 #include <sys/kauth.h>
 #include <sys/mount_internal.h>
+#include <sys/vnode_internal.h>
 #include <sys/vnode.h>
 #include <sys/ubc.h>
 #include <sys/malloc.h>
+#include <sys/fcntl.h>
+#include <sys/time.h>
 
 #include <nfs/rpcv2.h>
 #include <nfs/nfsproto.h>
@@ -93,6 +96,8 @@ static lck_grp_t *nfs_node_lck_grp;
 static lck_grp_t *nfs_data_lck_grp;
 lck_mtx_t *nfs_node_hash_mutex;
 
+#define NFS_NODE_DBG(...) NFS_DBG(NFS_FAC_NODE, 7, ## __VA_ARGS__)
+
 /*
  * Initialize hash links for nfsnodes
  * and build nfsnode free list.
@@ -130,6 +135,48 @@ nfs_hash(u_char *fhp, int fhsize)
        return (fhsum);
 }
 
+       
+int nfs_case_insensitive(mount_t);
+
+int
+nfs_case_insensitive(mount_t mp)
+{
+       struct nfsmount *nmp = VFSTONFS(mp);
+       int answer = 0;
+       int skip = 0;
+       
+       if (nfs_mount_gone(nmp)) {
+               return (0);
+       }
+       
+       if (nmp->nm_vers == NFS_VER2) {
+               /* V2 has no way to know */
+               return (0);
+       }
+
+       lck_mtx_lock(&nmp->nm_lock);
+       if (nmp->nm_vers == NFS_VER3) {
+               if (!(nmp->nm_state & NFSSTA_GOTPATHCONF)) {
+                       /* We're holding the node lock so we just return 
+                        * with answer as case sensitive. Is very rare
+                        * for file systems not to be homogenous w.r.t. pathconf
+                        */
+                       skip = 1;
+               } 
+       } else if (!(nmp->nm_fsattr.nfsa_flags & NFS_FSFLAG_HOMOGENEOUS)) {
+               /* no pathconf info cached */
+               skip = 1;
+       }
+
+       if (!skip && (nmp->nm_fsattr.nfsa_flags & NFS_FSFLAG_CASE_INSENSITIVE))
+               answer = 1;
+
+       lck_mtx_unlock(&nmp->nm_lock);
+
+       return (answer);
+}
+
+       
 /*
  * Look up a vnode/nfsnode by file handle.
  * Callers must check for mount points!!
@@ -145,6 +192,7 @@ nfs_nget(
        int fhsize,
        struct nfs_vattr *nvap,
        u_int64_t *xidp,
+       uint32_t auth,
        int flags,
        nfsnode_t *npp)
 {
@@ -159,7 +207,7 @@ nfs_nget(
        FSDBG_TOP(263, mp, dnp, flags, npp);
 
        /* Check for unmount in progress */
-       if (!mp || (mp->mnt_kern_flag & MNTK_FRCUNMOUNT)) {
+       if (!mp || vfs_isforce(mp)) {
                *npp = NULL;
                error = ENXIO;
                FSDBG_BOT(263, mp, dnp, 0xd1e, error);
@@ -175,6 +223,21 @@ loop:
                if (mp != mp2 || np->n_fhsize != fhsize ||
                    bcmp(fhp, np->n_fhp, fhsize))
                        continue;
+               if (nvap && (nvap->nva_flags & NFS_FFLAG_TRIGGER_REFERRAL) &&
+                   cnp && (cnp->cn_namelen > (fhsize - (int)sizeof(dnp)))) {
+                       /* The name was too long to fit in the file handle.  Check it against the node's name. */
+                       int namecmp = 0;
+                       const char *vname = vnode_getname(NFSTOV(np));
+                       if (vname) {
+                               if (cnp->cn_namelen != (int)strlen(vname))
+                                       namecmp = 1;
+                               else
+                                       namecmp = strncmp(vname, cnp->cn_nameptr, cnp->cn_namelen);
+                               vnode_putname(vname);
+                       }
+                       if (namecmp)  /* full name didn't match */
+                               continue;
+               }
                FSDBG(263, dnp, np, np->n_flag, 0xcace0000);
                /* if the node is locked, sleep on it */
                if ((np->n_hflag & NHLOCKED) && !(flags & NG_NOCREATE)) {
@@ -216,6 +279,86 @@ loop:
                } else {
                        if (dnp && cnp && (flags & NG_MAKEENTRY))
                                cache_enter(NFSTOV(dnp), vp, cnp);
+                       /*
+                        * Update the vnode if the name/and or the parent has
+                        * changed. We need to do this so that if getattrlist is
+                        * called asking for ATTR_CMN_NAME, that the "most"
+                        * correct name is being returned. In addition for
+                        * monitored vnodes we need to kick the vnode out of the
+                        * name cache. We do this so that if there are hard
+                        * links in the same directory the link will not be
+                        * found and a lookup will get us here to return the
+                        * name of the current link. In addition by removing the
+                        * name from the name cache the old name will not be
+                        * found after a rename done on another client or the
+                        * server.  The principle reason to do this is because
+                        * Finder is asking for notifications on a directory.
+                        * The directory changes, Finder gets notified, reads
+                        * the directory (which we have purged) and for each
+                        * entry returned calls getattrlist with the name
+                        * returned from readdir. gettattrlist has to call
+                        * namei/lookup to resolve the name, because its not in
+                        * the cache we end up here. We need to update the name
+                        * so Finder will get the name it called us with.
+                        *
+                        * We had an imperfect solution with respect to case
+                        * sensitivity.  There is a test that is run in
+                        * FileBuster that does renames from some name to
+                        * another name differing only in case. It then reads
+                        * the directory looking for the new name, after it
+                        * finds that new name, it ask gettattrlist to verify
+                        * that the name is the new name.  Usually that works,
+                        * but renames generate fsevents and fseventsd will do a
+                        * lookup on the name via lstat. Since that test renames
+                        * old name to new name back and forth there is a race
+                        * that an fsevent will be behind and will access the
+                        * file by the old name, on a case insensitive file
+                        * system that will work. Problem is if we do a case
+                        * sensitive compare, we're going to change the name,
+                        * which the test's getattrlist verification step is
+                        * going to fail. So we will check the case sensitivity
+                        * of the file system and do the appropriate compare. In
+                        * a rare instance for non homogeneous file systems
+                        * w.r.t. pathconf we will use case sensitive compares.
+                        * That could break if the file system is actually case
+                        * insensitive.
+                        *
+                        * Note that V2 does not know the case, so we just
+                        * assume case sensitivity. 
+                        *
+                        * This is clearly not perfect due to races, but this is
+                        * as good as its going to get. You can defeat the
+                        * handling of hard links simply by doing:
+                        *
+                        *      while :; do ls -l > /dev/null; done
+                        *
+                        * in a terminal window. Even a single ls -l can cause a
+                        * race.
+                        *
+                        * <rant>What we really need is for the caller, that
+                        * knows the name being used is valid since it got it
+                        * from a readdir to use that name and not ask for the
+                        * ATTR_CMN_NAME</rant>
+                        */
+                       if (dnp && cnp && (vp != NFSTOV(dnp))) {
+                               int update_flags = (vnode_ismonitored((NFSTOV(dnp)))) ? VNODE_UPDATE_CACHE : 0;
+                               int (*cmp)(const char *s1, const char *s2, size_t n);
+
+                               cmp = nfs_case_insensitive(mp) ? strncasecmp : strncmp;
+
+                               if (vp->v_name && cnp->cn_namelen && (*cmp)(cnp->cn_nameptr, vp->v_name, cnp->cn_namelen))
+                                       update_flags |= VNODE_UPDATE_NAME;
+                               if ((vp->v_name == NULL && cnp->cn_namelen != 0) || (vp->v_name != NULL && cnp->cn_namelen == 0))
+                                       update_flags |= VNODE_UPDATE_NAME;
+                               if (vnode_parent(vp) != NFSTOV(dnp))
+                                       update_flags |= VNODE_UPDATE_PARENT;
+                               if (update_flags) {
+                                       NFS_NODE_DBG("vnode_update_identity old name %s new name %.*s update flags = %x\n",
+                                                    vp->v_name, cnp->cn_namelen, cnp->cn_nameptr ? cnp->cn_nameptr : "", update_flags);
+                                       vnode_update_identity(vp, NFSTOV(dnp), cnp->cn_nameptr, cnp->cn_namelen, 0, update_flags);
+                               }
+                       }
+
                        *npp = np;
                }
                FSDBG_BOT(263, dnp, *npp, 0xcace0000, error);
@@ -246,10 +389,21 @@ loop:
        bzero(np, sizeof *np);
        np->n_hflag |= (NHINIT | NHLOCKED);
        np->n_mount = mp;
+       np->n_auth = auth;
        TAILQ_INIT(&np->n_opens);
        TAILQ_INIT(&np->n_lock_owners);
        TAILQ_INIT(&np->n_locks);
        np->n_dlink.tqe_next = NFSNOLIST;
+       np->n_dreturn.tqe_next = NFSNOLIST;
+       np->n_monlink.le_next = NFSNOLIST;
+
+       /* ugh... need to keep track of ".zfs" directories to workaround server bugs */
+       if ((nvap->nva_type == VDIR) && cnp && (cnp->cn_namelen == 4) &&
+           (cnp->cn_nameptr[0] == '.') && (cnp->cn_nameptr[1] == 'z') &&
+           (cnp->cn_nameptr[2] == 'f') && (cnp->cn_nameptr[3] == 's'))
+               np->n_flag |= NISDOTZFS;
+       if (dnp && (dnp->n_flag & NISDOTZFS))
+               np->n_flag |= NISDOTZFSCHILD;
 
        if (dnp && cnp && ((cnp->cn_namelen != 2) ||
            (cnp->cn_nameptr[0] != '.') || (cnp->cn_nameptr[1] != '.'))) {
@@ -293,6 +447,8 @@ loop:
        lck_mtx_unlock(nfs_node_hash_mutex);
 
        /* do initial loading of attributes */
+       NACLINVALIDATE(np);
+       NACCESSINVALIDATE(np);
        error = nfs_loadattrcache(np, nvap, xidp, 1);
        if (error) {
                FSDBG(266, 0, np, np->n_flag, 0xb1eb1e);
@@ -325,7 +481,6 @@ loop:
        NFS_CHANGED_UPDATE(nfsvers, np, nvap);
        if (nvap->nva_type == VDIR)
                NFS_CHANGED_UPDATE_NC(nfsvers, np, nvap);
-       NMODEINVALIDATE(np);
 
        /* now, attempt to get a new vnode */
        vfsp.vnfs_mp = mp;
@@ -363,7 +518,21 @@ loop:
        if (!dnp || !cnp || !(flags & NG_MAKEENTRY))
                vfsp.vnfs_flags |= VNFS_NOCACHE;
 
-       error = vnode_create(VNCREATE_FLAVOR, VCREATESIZE, &vfsp, &np->n_vnode);
+#if CONFIG_TRIGGERS
+       if ((nfsvers >= NFS_VER4) && (nvap->nva_type == VDIR) && (np->n_vattr.nva_flags & NFS_FFLAG_TRIGGER)) {
+               struct vnode_trigger_param vtp;
+               bzero(&vtp, sizeof(vtp));
+               bcopy(&vfsp, &vtp.vnt_params, sizeof(vfsp));
+               vtp.vnt_resolve_func = nfs_mirror_mount_trigger_resolve;
+               vtp.vnt_unresolve_func = nfs_mirror_mount_trigger_unresolve;
+               vtp.vnt_rearm_func = nfs_mirror_mount_trigger_rearm;
+               vtp.vnt_flags = VNT_AUTO_REARM;
+               error = vnode_create(VNCREATE_TRIGGER, VNCREATE_TRIGGER_SIZE, &vtp, &np->n_vnode);
+       } else
+#endif
+       {
+               error = vnode_create(VNCREATE_FLAVOR, VCREATESIZE, &vfsp, &np->n_vnode);
+       }
        if (error) {
                FSDBG(266, 0, np, np->n_flag, 0xb1eb1e);
                nfs_node_unlock(np);
@@ -413,69 +582,92 @@ loop:
 
 
 int
-nfs_vnop_inactive(ap)
+nfs_vnop_inactive(
        struct vnop_inactive_args /* {
                struct vnodeop_desc *a_desc;
                vnode_t a_vp;
                vfs_context_t a_context;
-       } */ *ap;
+       } */ *ap)
 {
        vnode_t vp = ap->a_vp;
        vfs_context_t ctx = ap->a_context;
-       nfsnode_t np = VTONFS(ap->a_vp);
+       nfsnode_t np;
        struct nfs_sillyrename *nsp;
        struct nfs_vattr nvattr;
-       int unhash, attrerr, busyerror, error, inuse, busied;
+       int unhash, attrerr, busyerror, error, inuse, busied, force;
        struct nfs_open_file *nofp;
-       const char *vname = NULL;
        struct componentname cn;
-       struct nfsmount *nmp = NFSTONMP(np);
+       struct nfsmount *nmp;
+       mount_t mp;
+
+       if (vp == NULL)
+               panic("nfs_vnop_inactive: vp == NULL");
+       np = VTONFS(vp);
+       if (np == NULL)
+               panic("nfs_vnop_inactive: np == NULL");
+                       
+       nmp = NFSTONMP(np);
+       mp = vnode_mount(vp);
 
 restart:
+       force = (!mp || vfs_isforce(mp));
        error = 0;
-       inuse = ((nmp->nm_vers >= NFS_VER4) && (nfs_mount_state_in_use_start(nmp) == 0));
+       inuse = (nfs_mount_state_in_use_start(nmp, NULL) == 0);
 
        /* There shouldn't be any open or lock state at this point */
        lck_mtx_lock(&np->n_openlock);
-       if (np->n_openrefcnt) {
-               vname = vnode_getname(vp);
-               printf("nfs_vnop_inactive: still open: %d %s\n", np->n_openrefcnt, vname ? vname : "//");
+       if (np->n_openrefcnt && !force) {
+               /*
+                * vnode_rele and vnode_put drop the vnode lock before
+                * calling VNOP_INACTIVE, so there is a race were the
+                * vnode could become active again. Perhaps there are
+                * other places where this can happen, so if we've got
+                * here we need to get out.
+                */
+#ifdef NFS_NODE_DEBUG
+               NP(np, "nfs_vnop_inactive: still open: %d", np->n_openrefcnt);
+#endif         
+               lck_mtx_unlock(&np->n_openlock);
+               return 0;
        }
+
        TAILQ_FOREACH(nofp, &np->n_opens, nof_link) {
                lck_mtx_lock(&nofp->nof_lock);
                if (nofp->nof_flags & NFS_OPEN_FILE_BUSY) {
-                       if (!vname)
-                               vname = vnode_getname(vp);
-                       printf("nfs_vnop_inactive: open file busy: %s\n", vname ? vname : "//");
+                       if (!force)
+                               NP(np, "nfs_vnop_inactive: open file busy");
                        busied = 0;
                } else {
                        nofp->nof_flags |= NFS_OPEN_FILE_BUSY;
                        busied = 1;
                }
                lck_mtx_unlock(&nofp->nof_lock);
+               if ((np->n_flag & NREVOKE) || (nofp->nof_flags & NFS_OPEN_FILE_LOST)) {
+                       if (busied)
+                               nfs_open_file_clear_busy(nofp);
+                       continue;
+               }
                /*
                 * If we just created the file, we already had it open in
                 * anticipation of getting a subsequent open call.  If the
                 * node has gone inactive without being open, we need to
                 * clean up (close) the open done in the create.
                 */
-               if ((nofp->nof_flags & NFS_OPEN_FILE_CREATE) && nofp->nof_creator) {
+               if ((nofp->nof_flags & NFS_OPEN_FILE_CREATE) && nofp->nof_creator && !force) {
                        if (nofp->nof_flags & NFS_OPEN_FILE_REOPEN) {
                                lck_mtx_unlock(&np->n_openlock);
                                if (busied)
                                        nfs_open_file_clear_busy(nofp);
                                if (inuse)
                                        nfs_mount_state_in_use_end(nmp, 0);
-                               nfs4_reopen(nofp, vfs_context_thread(ctx));
-                               goto restart;
+                               if (!nfs4_reopen(nofp, NULL))
+                                       goto restart;
                        }
                        nofp->nof_flags &= ~NFS_OPEN_FILE_CREATE;
                        lck_mtx_unlock(&np->n_openlock);
-                       error = nfs4_close(np, nofp, NFS_OPEN_SHARE_ACCESS_BOTH, NFS_OPEN_SHARE_DENY_NONE, ctx);
+                       error = nfs_close(np, nofp, NFS_OPEN_SHARE_ACCESS_BOTH, NFS_OPEN_SHARE_DENY_NONE, ctx);
                        if (error) {
-                               if (!vname)
-                                       vname = vnode_getname(vp);
-                               printf("nfs_vnop_inactive: create close error: %d, %s\n", error, vname);
+                               NP(np, "nfs_vnop_inactive: create close error: %d", error);
                                nofp->nof_flags |= NFS_OPEN_FILE_CREATE;
                        }
                        if (busied)
@@ -495,21 +687,19 @@ restart:
                                nofp->nof_r--;
                                nofp->nof_opencnt--;
                                nofp->nof_access = 0;
-                       } else {
+                       } else if (!force) {
                                lck_mtx_unlock(&np->n_openlock);
                                if (nofp->nof_flags & NFS_OPEN_FILE_REOPEN) {
                                        if (busied)
                                                nfs_open_file_clear_busy(nofp);
                                        if (inuse)
                                                nfs_mount_state_in_use_end(nmp, 0);
-                                       nfs4_reopen(nofp, vfs_context_thread(ctx));
-                                       goto restart;
+                                       if (!nfs4_reopen(nofp, NULL))
+                                               goto restart;
                                }
-                               error = nfs4_close(np, nofp, NFS_OPEN_SHARE_ACCESS_READ, NFS_OPEN_SHARE_DENY_NONE, ctx);
+                               error = nfs_close(np, nofp, NFS_OPEN_SHARE_ACCESS_READ, NFS_OPEN_SHARE_DENY_NONE, ctx);
                                if (error) {
-                                       if (!vname)
-                                               vname = vnode_getname(vp);
-                                       printf("nfs_vnop_inactive: need close error: %d, %s\n", error, vname);
+                                       NP(np, "nfs_vnop_inactive: need close error: %d", error);
                                        nofp->nof_flags |= NFS_OPEN_FILE_NEEDCLOSE;
                                }
                                if (busied)
@@ -519,32 +709,33 @@ restart:
                                goto restart;
                        }
                }
-               if (nofp->nof_opencnt) {
-                       if (!vname)
-                               vname = vnode_getname(vp);
-                       printf("nfs_vnop_inactive: file still open: %d %s\n", nofp->nof_opencnt, vname ? vname : "//");
-               }
-               if (nofp->nof_access || nofp->nof_deny ||
+               if (nofp->nof_opencnt && !force)
+                       NP(np, "nfs_vnop_inactive: file still open: %d", nofp->nof_opencnt);
+               if (!force && (nofp->nof_access || nofp->nof_deny ||
                    nofp->nof_mmap_access || nofp->nof_mmap_deny ||
                    nofp->nof_r || nofp->nof_w || nofp->nof_rw ||
                    nofp->nof_r_dw || nofp->nof_w_dw || nofp->nof_rw_dw ||
-                   nofp->nof_r_drw || nofp->nof_w_drw || nofp->nof_rw_drw) {
-                       if (!vname)
-                               vname = vnode_getname(vp);
-                       printf("nfs_vnop_inactive: non-zero access: %d %d %d %d # %u %u %u dw %u %u %u drw %u %u %u %s\n",
+                   nofp->nof_r_drw || nofp->nof_w_drw || nofp->nof_rw_drw ||
+                   nofp->nof_d_r || nofp->nof_d_w || nofp->nof_d_rw ||
+                   nofp->nof_d_r_dw || nofp->nof_d_w_dw || nofp->nof_d_rw_dw ||
+                   nofp->nof_d_r_drw || nofp->nof_d_w_drw || nofp->nof_d_rw_drw)) {
+                       NP(np, "nfs_vnop_inactive: non-zero access: %d %d %d %d # %u.%u %u.%u %u.%u dw %u.%u %u.%u %u.%u drw %u.%u %u.%u %u.%u",
                                nofp->nof_access, nofp->nof_deny,
                                nofp->nof_mmap_access, nofp->nof_mmap_deny,
-                               nofp->nof_r, nofp->nof_w, nofp->nof_rw,
-                               nofp->nof_r_dw, nofp->nof_w_dw, nofp->nof_rw_dw,
-                               nofp->nof_r_drw, nofp->nof_w_drw, nofp->nof_rw_drw,
-                               vname ? vname : "//");
+                               nofp->nof_r, nofp->nof_d_r,
+                               nofp->nof_w, nofp->nof_d_w,
+                               nofp->nof_rw, nofp->nof_d_rw,
+                               nofp->nof_r_dw, nofp->nof_d_r_dw,
+                               nofp->nof_w_dw, nofp->nof_d_w_dw,
+                               nofp->nof_rw_dw, nofp->nof_d_rw_dw,
+                               nofp->nof_r_drw, nofp->nof_d_r_drw,
+                               nofp->nof_w_drw, nofp->nof_d_w_drw,
+                               nofp->nof_rw_drw, nofp->nof_d_rw_drw);
                }
                if (busied)
                        nfs_open_file_clear_busy(nofp);
        }
        lck_mtx_unlock(&np->n_openlock);
-       if (vname)
-               vnode_putname(vname);
 
        if (inuse && nfs_mount_state_in_use_end(nmp, error))
                goto restart;
@@ -660,12 +851,12 @@ restart:
  * Reclaim an nfsnode so that it can be used for other purposes.
  */
 int
-nfs_vnop_reclaim(ap)
+nfs_vnop_reclaim(
        struct vnop_reclaim_args /* {
                struct vnodeop_desc *a_desc;
                vnode_t a_vp;
                vfs_context_t a_context;
-       } */ *ap;
+       } */ *ap)
 {
        vnode_t vp = ap->a_vp;
        nfsnode_t np = VTONFS(vp);
@@ -673,42 +864,59 @@ nfs_vnop_reclaim(ap)
        struct nfs_open_file *nofp, *nextnofp;
        struct nfs_file_lock *nflp, *nextnflp;
        struct nfs_lock_owner *nlop, *nextnlop;
-       const char *vname = NULL;
        struct nfsmount *nmp = np->n_mount ? VFSTONFS(np->n_mount) : NFSTONMP(np);
+       mount_t mp = vnode_mount(vp);
+       int force;
 
        FSDBG_TOP(265, vp, np, np->n_flag, 0);
+       force = (!mp || vfs_isforce(mp) || nfs_mount_gone(nmp));
 
        /* There shouldn't be any open or lock state at this point */
        lck_mtx_lock(&np->n_openlock);
 
        if (nmp && (nmp->nm_vers >= NFS_VER4)) {
                /* 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_dlink.tqe_next != NFSNOLIST) {
-                       /* remove this node from the recall list */
+                       /* remove this node from the delegation list */
                        lck_mtx_lock(&nmp->nm_lock);
                        if (np->n_dlink.tqe_next != NFSNOLIST) {
-                               TAILQ_REMOVE(&nmp->nm_recallq, np, n_dlink);
+                               TAILQ_REMOVE(&nmp->nm_delegations, np, n_dlink);
                                np->n_dlink.tqe_next = NFSNOLIST;
                        }
                        lck_mtx_unlock(&nmp->nm_lock);
                }
-               if (np->n_openflags & N_DELEG_MASK) {
+               if ((np->n_openflags & N_DELEG_MASK) && !force) {
+                       /* try to return the delegation */
                        np->n_openflags &= ~N_DELEG_MASK;
                        nfs4_delegreturn_rpc(nmp, np->n_fhp, np->n_fhsize, &np->n_dstateid,
-                               vfs_context_thread(ctx), vfs_context_ucred(ctx));
+                               R_RECOVER, vfs_context_thread(ctx), vfs_context_ucred(ctx));
+               }
+               if (np->n_attrdirfh) {
+                       FREE(np->n_attrdirfh, M_TEMP);
+                       np->n_attrdirfh = NULL;
                }
        }
 
        /* clean up file locks */
        TAILQ_FOREACH_SAFE(nflp, &np->n_locks, nfl_link, nextnflp) {
-               if (!(nflp->nfl_flags & NFS_FILE_LOCK_DEAD)) {
-                       if (!vname)
-                               vname = vnode_getname(vp);
-                       printf("nfs_vnop_reclaim: lock 0x%llx 0x%llx 0x%x (bc %d) %s\n",
-                               nflp->nfl_start, nflp->nfl_end, nflp->nfl_flags,
-                               nflp->nfl_blockcnt, vname ? vname : "//");
+               if (!(nflp->nfl_flags & NFS_FILE_LOCK_DEAD) && !force) {
+                       NP(np, "nfs_vnop_reclaim: lock 0x%llx 0x%llx 0x%x (bc %d)",
+                               nflp->nfl_start, nflp->nfl_end, nflp->nfl_flags, nflp->nfl_blockcnt);
                }
-               if (!(nflp->nfl_flags & NFS_FILE_LOCK_BLOCKED)) {
+               if (!(nflp->nfl_flags & (NFS_FILE_LOCK_BLOCKED|NFS_FILE_LOCK_DEAD))) {
+                       /* try sending an unlock RPC if it wasn't delegated */
+                       if (!(nflp->nfl_flags & NFS_FILE_LOCK_DELEGATED) && !force)
+                               nmp->nm_funcs->nf_unlock_rpc(np, nflp->nfl_owner, F_WRLCK, nflp->nfl_start, nflp->nfl_end, R_RECOVER,
+                                       NULL, nflp->nfl_owner->nlo_open_owner->noo_cred);
                        lck_mtx_lock(&nflp->nfl_owner->nlo_lock);
                        TAILQ_REMOVE(&nflp->nfl_owner->nlo_locks, nflp, nfl_lolink);
                        lck_mtx_unlock(&nflp->nfl_owner->nlo_lock);
@@ -718,72 +926,79 @@ nfs_vnop_reclaim(ap)
        }
        /* clean up lock owners */
        TAILQ_FOREACH_SAFE(nlop, &np->n_lock_owners, nlo_link, nextnlop) {
-               if (!TAILQ_EMPTY(&nlop->nlo_locks)) {
-                       if (!vname)
-                               vname = vnode_getname(vp);
-                       printf("nfs_vnop_reclaim: lock owner with locks %s\n",
-                               vname ? vname : "//");
-               }
+               if (!TAILQ_EMPTY(&nlop->nlo_locks) && !force)
+                       NP(np, "nfs_vnop_reclaim: lock owner with locks");
                TAILQ_REMOVE(&np->n_lock_owners, nlop, nlo_link);
                nfs_lock_owner_destroy(nlop);
        }
        /* clean up open state */
-       if (np->n_openrefcnt) {
-               if (!vname)
-                       vname = vnode_getname(vp);
-               printf("nfs_vnop_reclaim: still open: %d %s\n",
-                       np->n_openrefcnt, vname ? vname : "//");
-       }
+       if (np->n_openrefcnt && !force)
+               NP(np, "nfs_vnop_reclaim: still open: %d", np->n_openrefcnt);
        TAILQ_FOREACH_SAFE(nofp, &np->n_opens, nof_link, nextnofp) {
-               if (nofp->nof_flags & NFS_OPEN_FILE_BUSY) {
-                       if (!vname)
-                               vname = vnode_getname(vp);
-                       printf("nfs_vnop_reclaim: open file busy: %s\n",
-                               vname ? vname : "//");
-               }
-               if (nofp->nof_opencnt) {
-                       if (!vname)
-                               vname = vnode_getname(vp);
-                       printf("nfs_vnop_reclaim: file still open: %d %s\n",
-                               nofp->nof_opencnt, vname ? vname : "//");
-               }
-               if (nofp->nof_access || nofp->nof_deny ||
-                   nofp->nof_mmap_access || nofp->nof_mmap_deny ||
-                   nofp->nof_r || nofp->nof_w || nofp->nof_rw ||
-                   nofp->nof_r_dw || nofp->nof_w_dw || nofp->nof_rw_dw ||
-                   nofp->nof_r_drw || nofp->nof_w_drw || nofp->nof_rw_drw) {
-                       if (!vname)
-                               vname = vnode_getname(vp);
-                       printf("nfs_vnop_reclaim: non-zero access: %d %d %d %d # %u %u %u dw %u %u %u drw %u %u %u %s\n",
-                               nofp->nof_access, nofp->nof_deny,
-                               nofp->nof_mmap_access, nofp->nof_mmap_deny,
-                               nofp->nof_r, nofp->nof_w, nofp->nof_rw,
-                               nofp->nof_r_dw, nofp->nof_w_dw, nofp->nof_rw_dw,
-                               nofp->nof_r_drw, nofp->nof_w_drw, nofp->nof_rw_drw,
-                               vname ? vname : "//");
+               if (nofp->nof_flags & NFS_OPEN_FILE_BUSY)
+                       NP(np, "nfs_vnop_reclaim: open file busy");
+               if (!(np->n_flag & NREVOKE) && !(nofp->nof_flags & NFS_OPEN_FILE_LOST)) {
+                       if (nofp->nof_opencnt && !force)
+                               NP(np, "nfs_vnop_reclaim: file still open: %d", nofp->nof_opencnt);
+                       if (!force && (nofp->nof_access || nofp->nof_deny ||
+                           nofp->nof_mmap_access || nofp->nof_mmap_deny ||
+                           nofp->nof_r || nofp->nof_w || nofp->nof_rw ||
+                           nofp->nof_r_dw || nofp->nof_w_dw || nofp->nof_rw_dw ||
+                           nofp->nof_r_drw || nofp->nof_w_drw || nofp->nof_rw_drw ||
+                           nofp->nof_d_r || nofp->nof_d_w || nofp->nof_d_rw ||
+                           nofp->nof_d_r_dw || nofp->nof_d_w_dw || nofp->nof_d_rw_dw ||
+                           nofp->nof_d_r_drw || nofp->nof_d_w_drw || nofp->nof_d_rw_drw)) {
+                               NP(np, "nfs_vnop_reclaim: non-zero access: %d %d %d %d # %u.%u %u.%u %u.%u dw %u.%u %u.%u %u.%u drw %u.%u %u.%u %u.%u",
+                                       nofp->nof_access, nofp->nof_deny,
+                                       nofp->nof_mmap_access, nofp->nof_mmap_deny,
+                                       nofp->nof_r, nofp->nof_d_r,
+                                       nofp->nof_w, nofp->nof_d_w,
+                                       nofp->nof_rw, nofp->nof_d_rw,
+                                       nofp->nof_r_dw, nofp->nof_d_r_dw,
+                                       nofp->nof_w_dw, nofp->nof_d_w_dw,
+                                       nofp->nof_rw_dw, nofp->nof_d_rw_dw,
+                                       nofp->nof_r_drw, nofp->nof_d_r_drw,
+                                       nofp->nof_w_drw, nofp->nof_d_w_drw,
+                                       nofp->nof_rw_drw, nofp->nof_d_rw_drw);
+                               /* try sending a close RPC if it wasn't delegated */
+                               if (nofp->nof_r || nofp->nof_w || nofp->nof_rw ||
+                                   nofp->nof_r_dw || nofp->nof_w_dw || nofp->nof_rw_dw ||
+                                   nofp->nof_r_drw || nofp->nof_w_drw || nofp->nof_rw_drw)
+                                       nfs4_close_rpc(np, nofp, NULL, nofp->nof_owner->noo_cred, R_RECOVER);
+                       }
                }
                TAILQ_REMOVE(&np->n_opens, nofp, nof_link);
                nfs_open_file_destroy(nofp);
        }
        lck_mtx_unlock(&np->n_openlock);
 
-       lck_mtx_lock(nfs_buf_mutex);
-       if (!LIST_EMPTY(&np->n_dirtyblkhd) || !LIST_EMPTY(&np->n_cleanblkhd)) {
-               if (!vname)
-                       vname = vnode_getname(vp);
-               printf("nfs_reclaim: dropping %s buffers for file %s\n",
-                       (!LIST_EMPTY(&np->n_dirtyblkhd) ? "dirty" : "clean"),
-                       (vname ? vname : "//"));
+       if (np->n_monlink.le_next != NFSNOLIST) {
+               /* Wait for any in-progress getattr to complete, */
+               /* then remove this node from the monitored node list. */
+               lck_mtx_lock(&nmp->nm_lock);
+               while (np->n_mflag & NMMONSCANINPROG) {
+                       struct timespec ts = { 1, 0 };
+                       np->n_mflag |= NMMONSCANWANT;
+                       msleep(&np->n_mflag, &nmp->nm_lock, PZERO-1, "nfswaitmonscan", &ts);
+               }
+               if (np->n_monlink.le_next != NFSNOLIST) {
+                       LIST_REMOVE(np, n_monlink);
+                       np->n_monlink.le_next = NFSNOLIST;
+               }
+               lck_mtx_unlock(&nmp->nm_lock);
        }
+
+       lck_mtx_lock(nfs_buf_mutex);
+       if (!force && (!LIST_EMPTY(&np->n_dirtyblkhd) || !LIST_EMPTY(&np->n_cleanblkhd)))
+               NP(np, "nfs_reclaim: dropping %s buffers", (!LIST_EMPTY(&np->n_dirtyblkhd) ? "dirty" : "clean"));
        lck_mtx_unlock(nfs_buf_mutex);
-       if (vname)
-               vnode_putname(vname);
        nfs_vinvalbuf(vp, V_IGNORE_WRITEERR, ap->a_context, 0);
 
        lck_mtx_lock(nfs_node_hash_mutex);
 
        if ((vnode_vtype(vp) != VDIR) && np->n_sillyrename) {
-               printf("nfs_reclaim: leaving unlinked file %s\n", np->n_sillyrename->nsr_name);
+               if (!force)
+                       NP(np, "nfs_reclaim: leaving unlinked file %s", np->n_sillyrename->nsr_name);
                if (np->n_sillyrename->nsr_cred != NOCRED)
                        kauth_cred_unref(&np->n_sillyrename->nsr_cred);
                vnode_rele(NFSTOV(np->n_sillyrename->nsr_dnp));
@@ -808,6 +1023,8 @@ nfs_vnop_reclaim(ap)
                FREE_ZONE(np->n_cookiecache, sizeof(struct nfsdmap), M_NFSDIROFF);
        if (np->n_fhsize > NFS_SMALLFH)
                FREE_ZONE(np->n_fhp, np->n_fhsize, M_NFSBIGFH);
+       if (np->n_vattr.nva_acl)
+               kauth_acl_free(np->n_vattr.nva_acl);
        nfs_node_unlock(np);
        vnode_clearfsnode(vp);
 
@@ -1108,3 +1325,37 @@ nfs_data_update_size(nfsnode_t np, int datalocked)
        FSDBG_BOT(272, np, np->n_flag, np->n_size, np->n_newsize);
 }
 
+#define DODEBUG 1
+
+int
+nfs_mount_is_dirty(mount_t mp)
+{
+       u_long i;
+       nfsnode_t np;
+#ifdef DODEBUG 
+       struct timeval now, then, diff;
+       u_long ncnt = 0;
+       microuptime(&now);
+#endif
+       lck_mtx_lock(nfs_node_hash_mutex);
+       for (i = 0; i <= nfsnodehash; i++) {
+               LIST_FOREACH(np, &nfsnodehashtbl[i], n_hash) {
+#ifdef DODEBUG
+                       ncnt++;
+#endif                 
+                       if (np->n_mount == mp && !LIST_EMPTY(&np->n_dirtyblkhd))
+                               goto out;
+               }
+       }
+out:
+       lck_mtx_unlock(nfs_node_hash_mutex);
+#ifdef DODEBUG
+       microuptime(&then);
+       timersub(&then, &now, &diff);
+       
+       NFS_DBG(NFS_FAC_SOCK, 7, "mount_is_dirty for %s took %lld mics for %ld slots and %ld nodes return %d\n",
+               vfs_statfs(mp)->f_mntfromname, (uint64_t)diff.tv_sec * 1000000LL + diff.tv_usec, i, ncnt, (i <= nfsnodehash));
+#endif
+
+       return (i <=  nfsnodehash);
+}