c6ba06d86c
Make the nfsclient always call vnode_pager_setsize() with the vnode exclusively locked. This ensures that page fault always can find the backing page if the object size check succeeded. Set VV_VMSIZEVNLOCK flag on NFS nodes. The main offender breaking the interface in nfsclient is nfs_loadattrcache(), which is used whenever server responded with updated attributes, which can happen on non-changing operations as well. Also, iod threads only have buffers locked (and even that is LK_KERNPROC), but they still may call nfs_loadattrcache() on RPC response. Instead of immediately calling vnode_pager_setsize() if server response indicated changed file size, but the vnode is not exclusively locked, set a new node flag NVNSETSZSKIP. When the vnode exclusively locked, or when we can temporary upgrade the lock to exclusive, call vnode_pager_setsize(), by providing the nfsclient VOP_LOCK() implementation. Tested by: pho Discussed with: rmacklem Sponsored by: The FreeBSD Foundation MFC after: 2 weeks Differential revision: https://reviews.freebsd.org/D21883
361 lines
9.3 KiB
C
361 lines
9.3 KiB
C
/*-
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*
|
|
* Copyright (c) 1989, 1993
|
|
* The Regents of the University of California. All rights reserved.
|
|
*
|
|
* This code is derived from software contributed to Berkeley by
|
|
* Rick Macklem at The University of Guelph.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. Neither the name of the University nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*
|
|
* from nfs_node.c 8.6 (Berkeley) 5/22/95
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/fcntl.h>
|
|
#include <sys/lock.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/namei.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/taskqueue.h>
|
|
#include <sys/vnode.h>
|
|
|
|
#include <vm/uma.h>
|
|
|
|
#include <fs/nfs/nfsport.h>
|
|
#include <fs/nfsclient/nfsnode.h>
|
|
#include <fs/nfsclient/nfsmount.h>
|
|
#include <fs/nfsclient/nfs.h>
|
|
#include <fs/nfsclient/nfs_kdtrace.h>
|
|
|
|
#include <nfs/nfs_lock.h>
|
|
|
|
extern struct vop_vector newnfs_vnodeops;
|
|
extern struct buf_ops buf_ops_newnfs;
|
|
MALLOC_DECLARE(M_NEWNFSREQ);
|
|
|
|
uma_zone_t newnfsnode_zone;
|
|
|
|
const char nfs_vnode_tag[] = "nfs";
|
|
|
|
static void nfs_freesillyrename(void *arg, __unused int pending);
|
|
|
|
void
|
|
ncl_nhinit(void)
|
|
{
|
|
|
|
newnfsnode_zone = uma_zcreate("NCLNODE", sizeof(struct nfsnode), NULL,
|
|
NULL, NULL, NULL, UMA_ALIGN_PTR, 0);
|
|
}
|
|
|
|
void
|
|
ncl_nhuninit(void)
|
|
{
|
|
uma_zdestroy(newnfsnode_zone);
|
|
}
|
|
|
|
/*
|
|
* ONLY USED FOR THE ROOT DIRECTORY. nfscl_nget() does the rest. If this
|
|
* function is going to be used to get Regular Files, code must be added
|
|
* to fill in the "struct nfsv4node".
|
|
* Look up a vnode/nfsnode by file handle.
|
|
* Callers must check for mount points!!
|
|
* In all cases, a pointer to a
|
|
* nfsnode structure is returned.
|
|
*/
|
|
int
|
|
ncl_nget(struct mount *mntp, u_int8_t *fhp, int fhsize, struct nfsnode **npp,
|
|
int lkflags)
|
|
{
|
|
struct thread *td = curthread; /* XXX */
|
|
struct nfsnode *np;
|
|
struct vnode *vp;
|
|
struct vnode *nvp;
|
|
int error;
|
|
u_int hash;
|
|
struct nfsmount *nmp;
|
|
struct nfsfh *nfhp;
|
|
|
|
nmp = VFSTONFS(mntp);
|
|
*npp = NULL;
|
|
|
|
hash = fnv_32_buf(fhp, fhsize, FNV1_32_INIT);
|
|
|
|
nfhp = malloc(sizeof (struct nfsfh) + fhsize,
|
|
M_NFSFH, M_WAITOK);
|
|
bcopy(fhp, &nfhp->nfh_fh[0], fhsize);
|
|
nfhp->nfh_len = fhsize;
|
|
error = vfs_hash_get(mntp, hash, lkflags,
|
|
td, &nvp, newnfs_vncmpf, nfhp);
|
|
free(nfhp, M_NFSFH);
|
|
if (error)
|
|
return (error);
|
|
if (nvp != NULL) {
|
|
*npp = VTONFS(nvp);
|
|
return (0);
|
|
}
|
|
np = uma_zalloc(newnfsnode_zone, M_WAITOK | M_ZERO);
|
|
|
|
error = getnewvnode(nfs_vnode_tag, mntp, &newnfs_vnodeops, &nvp);
|
|
if (error) {
|
|
uma_zfree(newnfsnode_zone, np);
|
|
return (error);
|
|
}
|
|
vp = nvp;
|
|
KASSERT(vp->v_bufobj.bo_bsize != 0, ("ncl_nget: bo_bsize == 0"));
|
|
vp->v_bufobj.bo_ops = &buf_ops_newnfs;
|
|
vp->v_data = np;
|
|
np->n_vnode = vp;
|
|
/*
|
|
* Initialize the mutex even if the vnode is going to be a loser.
|
|
* This simplifies the logic in reclaim, which can then unconditionally
|
|
* destroy the mutex (in the case of the loser, or if hash_insert
|
|
* happened to return an error no special casing is needed).
|
|
*/
|
|
mtx_init(&np->n_mtx, "NEWNFSnode lock", NULL, MTX_DEF | MTX_DUPOK);
|
|
lockinit(&np->n_excl, PVFS, "nfsupg", VLKTIMEOUT, LK_NOSHARE |
|
|
LK_CANRECURSE);
|
|
|
|
/*
|
|
* NFS supports recursive and shared locking.
|
|
*/
|
|
lockmgr(vp->v_vnlock, LK_EXCLUSIVE | LK_NOWITNESS, NULL);
|
|
VN_LOCK_AREC(vp);
|
|
VN_LOCK_ASHARE(vp);
|
|
/*
|
|
* Are we getting the root? If so, make sure the vnode flags
|
|
* are correct
|
|
*/
|
|
if ((fhsize == nmp->nm_fhsize) &&
|
|
!bcmp(fhp, nmp->nm_fh, fhsize)) {
|
|
if (vp->v_type == VNON)
|
|
vp->v_type = VDIR;
|
|
vp->v_vflag |= VV_ROOT;
|
|
}
|
|
|
|
vp->v_vflag |= VV_VMSIZEVNLOCK;
|
|
|
|
np->n_fhp = malloc(sizeof (struct nfsfh) + fhsize,
|
|
M_NFSFH, M_WAITOK);
|
|
bcopy(fhp, np->n_fhp->nfh_fh, fhsize);
|
|
np->n_fhp->nfh_len = fhsize;
|
|
error = insmntque(vp, mntp);
|
|
if (error != 0) {
|
|
*npp = NULL;
|
|
free(np->n_fhp, M_NFSFH);
|
|
mtx_destroy(&np->n_mtx);
|
|
lockdestroy(&np->n_excl);
|
|
uma_zfree(newnfsnode_zone, np);
|
|
return (error);
|
|
}
|
|
error = vfs_hash_insert(vp, hash, lkflags,
|
|
td, &nvp, newnfs_vncmpf, np->n_fhp);
|
|
if (error)
|
|
return (error);
|
|
if (nvp != NULL) {
|
|
*npp = VTONFS(nvp);
|
|
/* vfs_hash_insert() vput()'s the losing vnode */
|
|
return (0);
|
|
}
|
|
*npp = np;
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Do the vrele(sp->s_dvp) as a separate task in order to avoid a
|
|
* deadlock because of a LOR when vrele() locks the directory vnode.
|
|
*/
|
|
static void
|
|
nfs_freesillyrename(void *arg, __unused int pending)
|
|
{
|
|
struct sillyrename *sp;
|
|
|
|
sp = arg;
|
|
vrele(sp->s_dvp);
|
|
free(sp, M_NEWNFSREQ);
|
|
}
|
|
|
|
static void
|
|
ncl_releasesillyrename(struct vnode *vp, struct thread *td)
|
|
{
|
|
struct nfsnode *np;
|
|
struct sillyrename *sp;
|
|
|
|
ASSERT_VOP_ELOCKED(vp, "releasesillyrename");
|
|
np = VTONFS(vp);
|
|
NFSASSERTNODE(np);
|
|
if (vp->v_type != VDIR) {
|
|
sp = np->n_sillyrename;
|
|
np->n_sillyrename = NULL;
|
|
} else
|
|
sp = NULL;
|
|
if (sp != NULL) {
|
|
NFSUNLOCKNODE(np);
|
|
(void) ncl_vinvalbuf(vp, 0, td, 1);
|
|
/*
|
|
* Remove the silly file that was rename'd earlier
|
|
*/
|
|
ncl_removeit(sp, vp);
|
|
crfree(sp->s_cred);
|
|
TASK_INIT(&sp->s_task, 0, nfs_freesillyrename, sp);
|
|
taskqueue_enqueue(taskqueue_thread, &sp->s_task);
|
|
NFSLOCKNODE(np);
|
|
}
|
|
}
|
|
|
|
int
|
|
ncl_inactive(struct vop_inactive_args *ap)
|
|
{
|
|
struct vnode *vp = ap->a_vp;
|
|
struct nfsnode *np;
|
|
boolean_t retv;
|
|
|
|
if (NFS_ISV4(vp) && vp->v_type == VREG) {
|
|
/*
|
|
* Since mmap()'d files do I/O after VOP_CLOSE(), the NFSv4
|
|
* Close operations are delayed until now. Any dirty
|
|
* buffers/pages must be flushed before the close, so that the
|
|
* stateid is available for the writes.
|
|
*/
|
|
if (vp->v_object != NULL) {
|
|
VM_OBJECT_WLOCK(vp->v_object);
|
|
retv = vm_object_page_clean(vp->v_object, 0, 0,
|
|
OBJPC_SYNC);
|
|
VM_OBJECT_WUNLOCK(vp->v_object);
|
|
} else
|
|
retv = TRUE;
|
|
if (retv == TRUE) {
|
|
(void)ncl_flush(vp, MNT_WAIT, ap->a_td, 1, 0);
|
|
(void)nfsrpc_close(vp, 1, ap->a_td);
|
|
}
|
|
}
|
|
|
|
np = VTONFS(vp);
|
|
NFSLOCKNODE(np);
|
|
ncl_releasesillyrename(vp, ap->a_td);
|
|
|
|
/*
|
|
* NMODIFIED means that there might be dirty/stale buffers
|
|
* associated with the NFS vnode.
|
|
* NDSCOMMIT means that the file is on a pNFS server and commits
|
|
* should be done to the DS.
|
|
* None of the other flags are meaningful after the vnode is unused.
|
|
*/
|
|
np->n_flag &= (NMODIFIED | NDSCOMMIT);
|
|
NFSUNLOCKNODE(np);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Reclaim an nfsnode so that it can be used for other purposes.
|
|
*/
|
|
int
|
|
ncl_reclaim(struct vop_reclaim_args *ap)
|
|
{
|
|
struct vnode *vp = ap->a_vp;
|
|
struct nfsnode *np = VTONFS(vp);
|
|
struct nfsdmap *dp, *dp2;
|
|
|
|
/*
|
|
* If the NLM is running, give it a chance to abort pending
|
|
* locks.
|
|
*/
|
|
if (nfs_reclaim_p != NULL)
|
|
nfs_reclaim_p(ap);
|
|
|
|
NFSLOCKNODE(np);
|
|
ncl_releasesillyrename(vp, ap->a_td);
|
|
NFSUNLOCKNODE(np);
|
|
|
|
if (NFS_ISV4(vp) && vp->v_type == VREG)
|
|
/*
|
|
* We can now safely close any remaining NFSv4 Opens for
|
|
* this file. Most opens will have already been closed by
|
|
* ncl_inactive(), but there are cases where it is not
|
|
* called, so we need to do it again here.
|
|
*/
|
|
(void) nfsrpc_close(vp, 1, ap->a_td);
|
|
|
|
vfs_hash_remove(vp);
|
|
|
|
/*
|
|
* Call nfscl_reclaimnode() to save attributes in the delegation,
|
|
* as required.
|
|
*/
|
|
if (vp->v_type == VREG)
|
|
nfscl_reclaimnode(vp);
|
|
|
|
/*
|
|
* Free up any directory cookie structures and
|
|
* large file handle structures that might be associated with
|
|
* this nfs node.
|
|
*/
|
|
if (vp->v_type == VDIR) {
|
|
dp = LIST_FIRST(&np->n_cookies);
|
|
while (dp) {
|
|
dp2 = dp;
|
|
dp = LIST_NEXT(dp, ndm_list);
|
|
free(dp2, M_NFSDIROFF);
|
|
}
|
|
}
|
|
if (np->n_writecred != NULL)
|
|
crfree(np->n_writecred);
|
|
free(np->n_fhp, M_NFSFH);
|
|
if (np->n_v4 != NULL)
|
|
free(np->n_v4, M_NFSV4NODE);
|
|
mtx_destroy(&np->n_mtx);
|
|
lockdestroy(&np->n_excl);
|
|
uma_zfree(newnfsnode_zone, vp->v_data);
|
|
vp->v_data = NULL;
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Invalidate both the access and attribute caches for this vnode.
|
|
*/
|
|
void
|
|
ncl_invalcaches(struct vnode *vp)
|
|
{
|
|
struct nfsnode *np = VTONFS(vp);
|
|
int i;
|
|
|
|
NFSLOCKNODE(np);
|
|
for (i = 0; i < NFS_ACCESSCACHESIZE; i++)
|
|
np->n_accesscache[i].stamp = 0;
|
|
KDTRACE_NFS_ACCESSCACHE_FLUSH_DONE(vp);
|
|
np->n_attrstamp = 0;
|
|
KDTRACE_NFS_ATTRCACHE_FLUSH_DONE(vp);
|
|
NFSUNLOCKNODE(np);
|
|
}
|