NFSv4: Fix revalidation of dentries with delegations

If a dentry was not initially looked up while we were holding a
delegation, then we do still need to revalidate that it still holds
the same name. If there are multiple hard links to the same file,
then all the hard links need validation.

Reported-by: Benjamin Coddington <bcodding@redhat.com>
Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
Reviewed-by: Benjamin Coddington <bcodding@redhat.com>
Tested-by: Benjamin Coddington <bcodding@redhat.com>
[Anna: Put nfs_unset_verifier_delegated() under CONFIG_NFS_V4]
Signed-off-by: Anna Schumaker <Anna.Schumaker@Netapp.com>
This commit is contained in:
Trond Myklebust 2020-02-05 09:01:54 -05:00 committed by Anna Schumaker
parent cf5b4059ba
commit efeda80da3
4 changed files with 115 additions and 23 deletions

View File

@ -42,6 +42,8 @@ static void nfs_mark_delegation_revoked(struct nfs_delegation *delegation)
if (!test_and_set_bit(NFS_DELEGATION_REVOKED, &delegation->flags)) { if (!test_and_set_bit(NFS_DELEGATION_REVOKED, &delegation->flags)) {
delegation->stateid.type = NFS4_INVALID_STATEID_TYPE; delegation->stateid.type = NFS4_INVALID_STATEID_TYPE;
atomic_long_dec(&nfs_active_delegations); atomic_long_dec(&nfs_active_delegations);
if (!test_bit(NFS_DELEGATION_RETURNING, &delegation->flags))
nfs_clear_verifier_delegated(delegation->inode);
} }
} }
@ -276,6 +278,8 @@ nfs_start_delegation_return_locked(struct nfs_inode *nfsi)
if (!test_and_set_bit(NFS_DELEGATION_RETURNING, &delegation->flags)) if (!test_and_set_bit(NFS_DELEGATION_RETURNING, &delegation->flags))
ret = delegation; ret = delegation;
spin_unlock(&delegation->lock); spin_unlock(&delegation->lock);
if (ret)
nfs_clear_verifier_delegated(&nfsi->vfs_inode);
out: out:
return ret; return ret;
} }
@ -689,6 +693,8 @@ void nfs4_inode_return_delegation_on_close(struct inode *inode)
ret = delegation; ret = delegation;
} }
spin_unlock(&delegation->lock); spin_unlock(&delegation->lock);
if (ret)
nfs_clear_verifier_delegated(inode);
} }
out: out:
rcu_read_unlock(); rcu_read_unlock();

View File

@ -986,14 +986,113 @@ static int nfs_fsync_dir(struct file *filp, loff_t start, loff_t end,
* full lookup on all child dentries of 'dir' whenever a change occurs * full lookup on all child dentries of 'dir' whenever a change occurs
* on the server that might have invalidated our dcache. * on the server that might have invalidated our dcache.
* *
* Note that we reserve bit '0' as a tag to let us know when a dentry
* was revalidated while holding a delegation on its inode.
*
* The caller should be holding dir->i_lock * The caller should be holding dir->i_lock
*/ */
void nfs_force_lookup_revalidate(struct inode *dir) void nfs_force_lookup_revalidate(struct inode *dir)
{ {
NFS_I(dir)->cache_change_attribute++; NFS_I(dir)->cache_change_attribute += 2;
} }
EXPORT_SYMBOL_GPL(nfs_force_lookup_revalidate); EXPORT_SYMBOL_GPL(nfs_force_lookup_revalidate);
/**
* nfs_verify_change_attribute - Detects NFS remote directory changes
* @dir: pointer to parent directory inode
* @verf: previously saved change attribute
*
* Return "false" if the verifiers doesn't match the change attribute.
* This would usually indicate that the directory contents have changed on
* the server, and that any dentries need revalidating.
*/
static bool nfs_verify_change_attribute(struct inode *dir, unsigned long verf)
{
return (verf & ~1UL) == nfs_save_change_attribute(dir);
}
static void nfs_set_verifier_delegated(unsigned long *verf)
{
*verf |= 1UL;
}
#if IS_ENABLED(CONFIG_NFS_V4)
static void nfs_unset_verifier_delegated(unsigned long *verf)
{
*verf &= ~1UL;
}
#endif /* IS_ENABLED(CONFIG_NFS_V4) */
static bool nfs_test_verifier_delegated(unsigned long verf)
{
return verf & 1;
}
static bool nfs_verifier_is_delegated(struct dentry *dentry)
{
return nfs_test_verifier_delegated(dentry->d_time);
}
static void nfs_set_verifier_locked(struct dentry *dentry, unsigned long verf)
{
struct inode *inode = d_inode(dentry);
if (!nfs_verifier_is_delegated(dentry) &&
!nfs_verify_change_attribute(d_inode(dentry->d_parent), verf))
goto out;
if (inode && NFS_PROTO(inode)->have_delegation(inode, FMODE_READ))
nfs_set_verifier_delegated(&verf);
out:
dentry->d_time = verf;
}
/**
* nfs_set_verifier - save a parent directory verifier in the dentry
* @dentry: pointer to dentry
* @verf: verifier to save
*
* Saves the parent directory verifier in @dentry. If the inode has
* a delegation, we also tag the dentry as having been revalidated
* while holding a delegation so that we know we don't have to
* look it up again after a directory change.
*/
void nfs_set_verifier(struct dentry *dentry, unsigned long verf)
{
spin_lock(&dentry->d_lock);
nfs_set_verifier_locked(dentry, verf);
spin_unlock(&dentry->d_lock);
}
EXPORT_SYMBOL_GPL(nfs_set_verifier);
#if IS_ENABLED(CONFIG_NFS_V4)
/**
* nfs_clear_verifier_delegated - clear the dir verifier delegation tag
* @inode: pointer to inode
*
* Iterates through the dentries in the inode alias list and clears
* the tag used to indicate that the dentry has been revalidated
* while holding a delegation.
* This function is intended for use when the delegation is being
* returned or revoked.
*/
void nfs_clear_verifier_delegated(struct inode *inode)
{
struct dentry *alias;
if (!inode)
return;
spin_lock(&inode->i_lock);
hlist_for_each_entry(alias, &inode->i_dentry, d_u.d_alias) {
spin_lock(&alias->d_lock);
nfs_unset_verifier_delegated(&alias->d_time);
spin_unlock(&alias->d_lock);
}
spin_unlock(&inode->i_lock);
}
EXPORT_SYMBOL_GPL(nfs_clear_verifier_delegated);
#endif /* IS_ENABLED(CONFIG_NFS_V4) */
/* /*
* A check for whether or not the parent directory has changed. * A check for whether or not the parent directory has changed.
* In the case it has, we assume that the dentries are untrustworthy * In the case it has, we assume that the dentries are untrustworthy
@ -1235,7 +1334,7 @@ nfs_do_lookup_revalidate(struct inode *dir, struct dentry *dentry,
goto out_bad; goto out_bad;
} }
if (NFS_PROTO(dir)->have_delegation(inode, FMODE_READ)) if (nfs_verifier_is_delegated(dentry))
return nfs_lookup_revalidate_delegated(dir, dentry, inode); return nfs_lookup_revalidate_delegated(dir, dentry, inode);
/* Force a full look up iff the parent directory has changed */ /* Force a full look up iff the parent directory has changed */
@ -1675,7 +1774,7 @@ nfs4_do_lookup_revalidate(struct inode *dir, struct dentry *dentry,
if (inode == NULL) if (inode == NULL)
goto full_reval; goto full_reval;
if (NFS_PROTO(dir)->have_delegation(inode, FMODE_READ)) if (nfs_verifier_is_delegated(dentry))
return nfs_lookup_revalidate_delegated(dir, dentry, inode); return nfs_lookup_revalidate_delegated(dir, dentry, inode);
/* NFS only supports OPEN on regular files */ /* NFS only supports OPEN on regular files */

View File

@ -2114,6 +2114,7 @@ static void init_once(void *foo)
init_rwsem(&nfsi->rmdir_sem); init_rwsem(&nfsi->rmdir_sem);
mutex_init(&nfsi->commit_mutex); mutex_init(&nfsi->commit_mutex);
nfs4_init_once(nfsi); nfs4_init_once(nfsi);
nfsi->cache_change_attribute = 0;
} }
static int __init nfs_init_inodecache(void) static int __init nfs_init_inodecache(void)

View File

@ -337,35 +337,17 @@ static inline int nfs_server_capable(struct inode *inode, int cap)
return NFS_SERVER(inode)->caps & cap; return NFS_SERVER(inode)->caps & cap;
} }
static inline void nfs_set_verifier(struct dentry * dentry, unsigned long verf)
{
dentry->d_time = verf;
}
/** /**
* nfs_save_change_attribute - Returns the inode attribute change cookie * nfs_save_change_attribute - Returns the inode attribute change cookie
* @dir - pointer to parent directory inode * @dir - pointer to parent directory inode
* The "change attribute" is updated every time we finish an operation * The "cache change attribute" is updated when we need to revalidate
* that will result in a metadata change on the server. * our dentry cache after a directory was seen to change on the server.
*/ */
static inline unsigned long nfs_save_change_attribute(struct inode *dir) static inline unsigned long nfs_save_change_attribute(struct inode *dir)
{ {
return NFS_I(dir)->cache_change_attribute; return NFS_I(dir)->cache_change_attribute;
} }
/**
* nfs_verify_change_attribute - Detects NFS remote directory changes
* @dir - pointer to parent directory inode
* @chattr - previously saved change attribute
* Return "false" if the verifiers doesn't match the change attribute.
* This would usually indicate that the directory contents have changed on
* the server, and that any dentries need revalidating.
*/
static inline int nfs_verify_change_attribute(struct inode *dir, unsigned long chattr)
{
return chattr == NFS_I(dir)->cache_change_attribute;
}
/* /*
* linux/fs/nfs/inode.c * linux/fs/nfs/inode.c
*/ */
@ -495,6 +477,10 @@ extern const struct file_operations nfs_dir_operations;
extern const struct dentry_operations nfs_dentry_operations; extern const struct dentry_operations nfs_dentry_operations;
extern void nfs_force_lookup_revalidate(struct inode *dir); extern void nfs_force_lookup_revalidate(struct inode *dir);
extern void nfs_set_verifier(struct dentry * dentry, unsigned long verf);
#if IS_ENABLED(CONFIG_NFS_V4)
extern void nfs_clear_verifier_delegated(struct inode *inode);
#endif /* IS_ENABLED(CONFIG_NFS_V4) */
extern struct dentry *nfs_add_or_obtain(struct dentry *dentry, extern struct dentry *nfs_add_or_obtain(struct dentry *dentry,
struct nfs_fh *fh, struct nfs_fattr *fattr, struct nfs_fh *fh, struct nfs_fattr *fattr,
struct nfs4_label *label); struct nfs4_label *label);