// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/fs/nfs/delegation.c
*
* Copyright (C) 2004 Trond Myklebust
*
* NFS file delegation management
*
*/
#include <linux/completion.h>
#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/iversion.h>
#include <linux/nfs4.h>
#include <linux/nfs_fs.h>
#include <linux/nfs_xdr.h>
#include "nfs4_fs.h"
#include "nfs4session.h"
#include "delegation.h"
#include "internal.h"
#include "nfs4trace.h"
#define NFS_DEFAULT_DELEGATION_WATERMARK (5000U)
static unsigned nfs_delegation_watermark = NFS_DEFAULT_DELEGATION_WATERMARK;
module_param_named(delegation_watermark, nfs_delegation_watermark, uint, 0644);
static struct hlist_head *nfs_delegation_hash(struct nfs_server *server,
const struct nfs_fh *fhandle)
{
return server->delegation_hash_table +
(nfs_fhandle_hash(fhandle) & server->delegation_hash_mask);
}
static void __nfs_free_delegation(struct nfs_delegation *delegation)
{
put_cred(delegation->cred);
delegation->cred = NULL;
kfree_rcu(delegation, rcu);
}
static void nfs_mark_delegation_revoked(struct nfs_server *server,
struct nfs_delegation *delegation)
{
if (!test_and_set_bit(NFS_DELEGATION_REVOKED, &delegation->flags)) {
delegation->stateid.type = NFS4_INVALID_STATEID_TYPE;
atomic_long_dec(&server->nr_active_delegations);
if (!test_bit(NFS_DELEGATION_RETURNING, &delegation->flags))
nfs_clear_verifier_delegated(delegation->inode);
}
}
static struct nfs_delegation *nfs_get_delegation(struct nfs_delegation *delegation)
{
refcount_inc(&delegation->refcount);
return delegation;
}
static void nfs_put_delegation(struct nfs_delegation *delegation)
{
if (refcount_dec_and_test(&delegation->refcount))
__nfs_free_delegation(delegation);
}
static void nfs_free_delegation(struct nfs_server *server,
struct nfs_delegation *delegation)
{
nfs_mark_delegation_revoked(server, delegation);
nfs_put_delegation(delegation);
}
/**
* nfs_mark_delegation_referenced - set delegation's REFERENCED flag
* @delegation: delegation to process
*
*/
void nfs_mark_delegation_referenced(struct nfs_delegation *delegation)
{
set_bit(NFS_DELEGATION_REFERENCED, &delegation->flags);
}
static void nfs_mark_return_delegation(struct nfs_server *server,
struct nfs_delegation *delegation)
{
set_bit(NFS_DELEGATION_RETURN, &delegation->flags);
set_bit(NFS4SERV_DELEGRETURN, &server->delegation_flags);
set_bit(NFS4CLNT_DELEGRETURN, &server->nfs_client->cl_state);
}
static bool nfs4_is_valid_delegation(const struct nfs_delegation *delegation,
fmode_t type)
{
if (delegation != NULL && (delegation->type & type) == type &&
!test_bit(NFS_DELEGATION_REVOKED, &delegation->flags) &&
!test_bit(NFS_DELEGATION_RETURNING, &delegation->flags))
return true;
return false;
}
struct nfs_delegation *nfs4_get_valid_delegation(const struct inode *inode)
{
struct nfs_delegation *delegation;
delegation = rcu_dereference(NFS_I(inode)->delegation);
if (nfs4_is_valid_delegation(delegation, 0))
return delegation;
return NULL;
}
static int nfs4_do_check_delegation(struct inode *inode, fmode_t type,
int flags, bool mark)
{
struct nfs_delegation *delegation;
int ret = 0;
type &= FMODE_READ|FMODE_WRITE;
rcu_read_lock();
delegation = rcu_dereference(NFS_I(inode)->delegation);
if (nfs4_is_valid_delegation(delegation, type)) {
if (mark)
nfs_mark_delegation_referenced(delegation);
ret = 1;
if ((flags & NFS_DELEGATION_FLAG_TIME) &&
!test_bit(NFS_DELEGATION_DELEGTIME, &delegation->flags))
ret = 0;
}
rcu_read_unlock();
return ret;
}
/**
* nfs4_have_delegation - check if inode has a delegation, mark it
* NFS_DELEGATION_REFERENCED if there is one.
* @inode: inode to check
* @type: delegation types to check for
* @flags: various modifiers
*
* Returns one if inode has the indicated delegation, otherwise zero.
*/
int nfs4_have_delegation(struct inode *inode, fmode_t type, int flags)
{
return nfs4_do_check_delegation(inode, type, flags, true);
}
/*
* nfs4_check_delegation - check if inode has a delegation, do not mark
* NFS_DELEGATION_REFERENCED if it has one.
*/
int nfs4_check_delegation(struct inode *inode, fmode_t type)
{
return nfs4_do_check_delegation(inode, type, 0, false);
}
static int nfs_delegation_claim_locks(struct nfs4_state *state, const nfs4_stateid *stateid)
{
struct inode *inode = state->inode;
struct file_lock *fl;
struct file_lock_context *flctx = locks_inode_context(inode);
struct list_head *list;
int status = 0;
if (flctx == NULL)
goto out;
list = &flctx->flc_posix;
spin_lock(&flctx->flc_lock);
restart:
for_each_file_lock(fl, list) {
if (nfs_file_open_context(fl->c.flc_file)->state != state)
continue;
spin_unlock(&flctx->flc_lock);
status = nfs4_lock_delegation_recall(fl, state, stateid);
if (status < 0)
goto out;
spin_lock(&flctx->flc_lock);
}
if (list == &flctx->flc_posix) {
list = &flctx->flc_flock;
goto restart;
}
spin_unlock(&flctx->flc_lock);
out:
return status;
}
|