root/fs/nfs/delegation.c
// 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);

bool directory_delegations = true;
module_param(directory_delegations, bool, 0644);
MODULE_PARM_DESC(directory_delegations,
                 "Enable the use of directory delegations, defaults to on.");

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)
{
        bool put_ref = false;

        if (test_and_set_bit(NFS_DELEGATION_REVOKED, &delegation->flags))
                return;

        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);

        spin_lock(&server->delegations_lock);
        if (!list_empty(&delegation->entry)) {
                list_del_init(&delegation->entry);
                put_ref = true;
        }
        spin_unlock(&server->delegations_lock);

        if (put_ref)
                nfs_put_delegation(delegation);
}

void nfs_put_delegation(struct nfs_delegation *delegation)
{
        if (refcount_dec_and_test(&delegation->refcount))
                __nfs_free_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)
{
        spin_lock(&server->delegations_lock);
        if (list_empty(&delegation->entry))
                refcount_inc(&delegation->refcount);
        list_move_tail(&delegation->entry, &server->delegations_return);
        spin_unlock(&server->delegations_lock);

        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;

        rcu_read_lock();
        delegation = rcu_dereference(NFS_I(inode)->delegation);
        if (!nfs4_is_valid_delegation(delegation, 0) ||
            !refcount_inc_not_zero(&delegation->refcount))
                delegation = NULL;
        rcu_read_unlock();

        return delegation;
}

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)
{
        if (S_ISDIR(inode->i_mode) && !directory_delegations)
                nfs4_inode_set_return_delegation_on_close(inode);
        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;
}

static int nfs_delegation_claim_opens(struct inode *inode,
                const nfs4_stateid *stateid, fmode_t type)
{
        struct nfs_inode *nfsi = NFS_I(inode);
        struct nfs_open_context *ctx;
        struct nfs4_state_owner *sp;
        struct nfs4_state *state;
        int err;

again:
        rcu_read_lock();
        list_for_each_entry_rcu(ctx, &nfsi->open_files, list) {
                state = ctx->state;
                if (state == NULL)
                        continue;
                if (!test_bit(NFS_DELEGATED_STATE, &state->flags))
                        continue;
                if (!nfs4_valid_open_stateid(state))
                        continue;
                if (!nfs4_stateid_match(&state->stateid, stateid))
                        continue;
                if (!get_nfs_open_context(ctx))
                        continue;
                rcu_read_unlock();
                sp = state->owner;
                /* Block nfs4_proc_unlck */
                mutex_lock(&sp->so_delegreturn_mutex);
                err = nfs4_open_delegation_recall(ctx, state, stateid);
                if (!err)
                        err = nfs_delegation_claim_locks(state, stateid);
                mutex_unlock(&sp->so_delegreturn_mutex);
                put_nfs_open_context(ctx);
                if (err != 0)
                        return err;
                goto again;
        }
        rcu_read_unlock();
        return 0;
}

/**
 * nfs_inode_reclaim_delegation - process a delegation reclaim request
 * @inode: inode to process
 * @cred: credential to use for request
 * @type: delegation type
 * @stateid: delegation stateid
 * @pagemod_limit: write delegation "space_limit"
 * @deleg_type: raw delegation type
 *
 */
void nfs_inode_reclaim_delegation(struct inode *inode, const struct cred *cred,
                                  fmode_t type, const nfs4_stateid *stateid,
                                  unsigned long pagemod_limit, u32 deleg_type)
{
        struct nfs_delegation *delegation;
        const struct cred *oldcred = NULL;

        rcu_read_lock();
        delegation = rcu_dereference(NFS_I(inode)->delegation);
        if (!delegation) {
                rcu_read_unlock();
                nfs_inode_set_delegation(inode, cred, type, stateid,
                                         pagemod_limit, deleg_type);
                return;
        }

        spin_lock(&delegation->lock);
        nfs4_stateid_copy(&delegation->stateid, stateid);
        delegation->type = type;
        delegation->pagemod_limit = pagemod_limit;
        oldcred = delegation->cred;
        delegation->cred = get_cred(cred);
        switch (deleg_type) {
        case NFS4_OPEN_DELEGATE_READ_ATTRS_DELEG:
        case NFS4_OPEN_DELEGATE_WRITE_ATTRS_DELEG:
                set_bit(NFS_DELEGATION_DELEGTIME, &delegation->flags);
                break;
        default:
                clear_bit(NFS_DELEGATION_DELEGTIME, &delegation->flags);
        }
        clear_bit(NFS_DELEGATION_NEED_RECLAIM, &delegation->flags);
        if (test_and_clear_bit(NFS_DELEGATION_REVOKED, &delegation->flags))
                atomic_long_inc(&NFS_SERVER(inode)->nr_active_delegations);
        spin_unlock(&delegation->lock);
        rcu_read_unlock();
        put_cred(oldcred);
        trace_nfs4_reclaim_delegation(inode, type);
}

static int nfs_do_return_delegation(struct inode *inode,
                                    struct nfs_delegation *delegation,
                                    int issync)
{
        const struct cred *cred;
        int res = 0;

        if (!test_bit(NFS_DELEGATION_REVOKED, &delegation->flags)) {
                spin_lock(&delegation->lock);
                cred = get_cred(delegation->cred);
                spin_unlock(&delegation->lock);
                res = nfs4_proc_delegreturn(inode, cred, &delegation->stateid,
                                            delegation, issync);
                put_cred(cred);
        }
        return res;
}

static struct inode *nfs_delegation_grab_inode(struct nfs_delegation *delegation)
{
        struct inode *inode = NULL;

        spin_lock(&delegation->lock);
        if (delegation->inode != NULL)
                inode = igrab(delegation->inode);
        spin_unlock(&delegation->lock);
        return inode;
}

static struct nfs_delegation *
nfs_start_delegation_return(struct nfs_inode *nfsi)
{
        struct nfs_delegation *delegation;
        bool return_now = false;

        rcu_read_lock();
        delegation = rcu_dereference(nfsi->delegation);
        if (!delegation || !refcount_inc_not_zero(&delegation->refcount)) {
                rcu_read_unlock();
                return NULL;
        }
        rcu_read_unlock();

        spin_lock(&delegation->lock);
        if (delegation->inode &&
            !test_and_set_bit(NFS_DELEGATION_RETURNING, &delegation->flags))
                return_now = true;
        spin_unlock(&delegation->lock);

        if (!return_now) {
                nfs_put_delegation(delegation);
                return NULL;
        }
        nfs_clear_verifier_delegated(&nfsi->vfs_inode);
        return delegation;
}

static bool
nfs_detach_delegations_locked(struct nfs_inode *nfsi,
                struct nfs_delegation *delegation,
                struct nfs_client *clp)
{
        lockdep_assert_held(&clp->cl_lock);

        trace_nfs4_detach_delegation(&nfsi->vfs_inode, delegation->type);

        spin_lock(&delegation->lock);
        if (!delegation->inode) {
                spin_unlock(&delegation->lock);
                return false;
        }
        hlist_del_init_rcu(&delegation->hash);
        list_del_rcu(&delegation->super_list);
        delegation->inode = NULL;
        rcu_assign_pointer(nfsi->delegation, NULL);
        spin_unlock(&delegation->lock);
        clear_bit(NFS_INO_REQ_DIR_DELEG, &nfsi->flags);
        return true;
}

static bool nfs_detach_delegation(struct nfs_inode *nfsi,
                struct nfs_delegation *delegation,
                struct nfs_server *server)
{
        struct nfs_client *clp = server->nfs_client;
        struct nfs_delegation *deleg_cur;
        bool ret = false;

        spin_lock(&clp->cl_lock);
        deleg_cur = rcu_dereference_protected(nfsi->delegation,
                                lockdep_is_held(&clp->cl_lock));
        if (delegation == deleg_cur)
                ret = nfs_detach_delegations_locked(nfsi, delegation, clp);
        spin_unlock(&clp->cl_lock);
        return ret;
}

static void
nfs_update_delegation_cred(struct nfs_delegation *delegation,
                const struct cred *cred)
{
        const struct cred *old;

        if (cred_fscmp(delegation->cred, cred) != 0) {
                old = xchg(&delegation->cred, get_cred(cred));
                put_cred(old);
        }
}

static void
nfs_update_inplace_delegation(struct nfs_server *server,
                struct nfs_delegation *delegation,
                const struct nfs_delegation *update)
{
        if (nfs4_stateid_is_newer(&update->stateid, &delegation->stateid)) {
                delegation->stateid.seqid = update->stateid.seqid;
                smp_wmb();
                delegation->type = update->type;
                delegation->pagemod_limit = update->pagemod_limit;
                if (test_bit(NFS_DELEGATION_REVOKED, &delegation->flags)) {
                        delegation->change_attr = update->change_attr;
                        nfs_update_delegation_cred(delegation, update->cred);
                        /* smp_mb__before_atomic() is implicit due to xchg() */
                        clear_bit(NFS_DELEGATION_REVOKED, &delegation->flags);
                        atomic_long_inc(&server->nr_active_delegations);
                }
        }
}

/**
 * nfs_inode_set_delegation - set up a delegation on an inode
 * @inode: inode to which delegation applies
 * @cred: cred to use for subsequent delegation processing
 * @type: delegation type
 * @stateid: delegation stateid
 * @pagemod_limit: write delegation "space_limit"
 * @deleg_type: raw delegation type
 *
 * Returns zero on success, or a negative errno value.
 */
int nfs_inode_set_delegation(struct inode *inode, const struct cred *cred,
                             fmode_t type, const nfs4_stateid *stateid,
                             unsigned long pagemod_limit, u32 deleg_type)
{
        struct nfs_server *server = NFS_SERVER(inode);
        struct nfs_client *clp = server->nfs_client;
        struct nfs_inode *nfsi = NFS_I(inode);
        struct nfs_delegation *delegation, *old_delegation;
        struct nfs_delegation *freeme = NULL;
        int status = 0;

        delegation = kmalloc_obj(*delegation, GFP_KERNEL_ACCOUNT);
        if (delegation == NULL)
                return -ENOMEM;
        nfs4_stateid_copy(&delegation->stateid, stateid);
        refcount_set(&delegation->refcount, 1);
        delegation->type = type;
        delegation->pagemod_limit = pagemod_limit;
        delegation->change_attr = inode_peek_iversion_raw(inode);
        delegation->cred = get_cred(cred);
        delegation->inode = inode;
        delegation->flags = 1<<NFS_DELEGATION_REFERENCED;
        INIT_LIST_HEAD(&delegation->entry);
        switch (deleg_type) {
        case NFS4_OPEN_DELEGATE_READ_ATTRS_DELEG:
        case NFS4_OPEN_DELEGATE_WRITE_ATTRS_DELEG:
                delegation->flags |= BIT(NFS_DELEGATION_DELEGTIME);
        }
        delegation->test_gen = 0;
        spin_lock_init(&delegation->lock);

        spin_lock(&clp->cl_lock);
        old_delegation = rcu_dereference_protected(nfsi->delegation,
                                        lockdep_is_held(&clp->cl_lock));
        if (old_delegation == NULL)
                goto add_new;
        /* Is this an update of the existing delegation? */
        if (nfs4_stateid_match_other(&old_delegation->stateid,
                                &delegation->stateid)) {
                spin_lock(&old_delegation->lock);
                nfs_update_inplace_delegation(server, old_delegation,
                                delegation);
                spin_unlock(&old_delegation->lock);
                goto out;
        }
        if (!test_bit(NFS_DELEGATION_REVOKED, &old_delegation->flags)) {
                /*
                 * Deal with broken servers that hand out two
                 * delegations for the same file.
                 * Allow for upgrades to a WRITE delegation, but
                 * nothing else.
                 */
                dfprintk(FILE, "%s: server %s handed out "
                                "a duplicate delegation!\n",
                                __func__, clp->cl_hostname);
                if (delegation->type == old_delegation->type ||
                    !(delegation->type & FMODE_WRITE)) {
                        freeme = delegation;
                        delegation = NULL;
                        goto out;
                }
                if (test_and_set_bit(NFS_DELEGATION_RETURNING,
                                        &old_delegation->flags))
                        goto out;
        }
        if (!nfs_detach_delegations_locked(nfsi, old_delegation, clp))
                goto out;
        freeme = old_delegation;
add_new:
        /*
         * If we didn't revalidate the change attribute before setting
         * the delegation, then pre-emptively ask for a full attribute
         * cache revalidation.
         */
        spin_lock(&inode->i_lock);
        if (NFS_I(inode)->cache_validity & NFS_INO_INVALID_CHANGE)
                nfs_set_cache_invalid(inode,
                        NFS_INO_INVALID_ATIME | NFS_INO_INVALID_CTIME |
                        NFS_INO_INVALID_MTIME | NFS_INO_INVALID_SIZE |
                        NFS_INO_INVALID_BLOCKS | NFS_INO_INVALID_NLINK |
                        NFS_INO_INVALID_OTHER | NFS_INO_INVALID_DATA |
                        NFS_INO_INVALID_ACCESS | NFS_INO_INVALID_ACL |
                        NFS_INO_INVALID_XATTR);
        spin_unlock(&inode->i_lock);

        list_add_tail_rcu(&delegation->super_list, &server->delegations);
        hlist_add_head_rcu(&delegation->hash,
                        nfs_delegation_hash(server, &NFS_I(inode)->fh));
        rcu_assign_pointer(nfsi->delegation, delegation);
        delegation = NULL;

        atomic_long_inc(&server->nr_active_delegations);

        trace_nfs4_set_delegation(inode, type);

        /* If we hold writebacks and have delegated mtime then update */
        if (deleg_type == NFS4_OPEN_DELEGATE_WRITE_ATTRS_DELEG &&
            nfs_have_writebacks(inode))
                nfs_update_delegated_mtime(inode);
out:
        spin_unlock(&clp->cl_lock);
        if (delegation != NULL)
                __nfs_free_delegation(delegation);
        if (freeme != NULL) {
                nfs_do_return_delegation(inode, freeme, 0);
                nfs_mark_delegation_revoked(server, freeme);
                nfs_put_delegation(freeme);
        }
        return status;
}

/*
 * Basic procedure for returning a delegation to the server.
 * If @issync is set, wait until state recovery has finished.  Otherwise
 * return -EAGAIN to the caller if we need more time.
 */
static int nfs_end_delegation_return(struct inode *inode,
                struct nfs_delegation *delegation, bool issync)
{
        struct nfs_server *server = NFS_SERVER(inode);
        unsigned int mode = O_WRONLY | O_RDWR;
        int err = 0;

        /* Directory delegations don't require any state recovery */
        if (!S_ISREG(inode->i_mode))
                goto out_return;

        if (!issync)
                mode |= O_NONBLOCK;
        /* Recall of any remaining application leases */
        err = break_lease(inode, mode);

        while (err == 0) {
                if (test_bit(NFS_DELEGATION_REVOKED, &delegation->flags))
                        break;
                err = nfs_delegation_claim_opens(inode, &delegation->stateid,
                                delegation->type);
                if (!err)
                        break;
                if (err != -EAGAIN)
                        goto abort;
                if (!issync)
                        goto delay;

                /*
                 * Guard against state recovery
                 */
                err = nfs4_wait_clnt_recover(server->nfs_client);
        }

out_return:
        return nfs_do_return_delegation(inode, delegation, issync);
delay:
        spin_lock(&server->delegations_lock);
        if (list_empty(&delegation->entry))
                refcount_inc(&delegation->refcount);
        list_move_tail(&delegation->entry, &server->delegations_return);
        spin_unlock(&server->delegations_lock);
        set_bit(NFS4CLNT_DELEGRETURN_DELAYED, &server->nfs_client->cl_state);
abort:
        clear_bit(NFS_DELEGATION_RETURNING, &delegation->flags);
        return err;
}

static int nfs_return_one_delegation(struct nfs_server *server)
{
        struct nfs_delegation *delegation;
        struct inode *inode;
        int err = 0;

        spin_lock(&server->delegations_lock);
        delegation = list_first_entry_or_null(&server->delegations_return,
                        struct nfs_delegation, entry);
        if (!delegation) {
                spin_unlock(&server->delegations_lock);
                return 0; /* no more delegations */
        }
        list_del_init(&delegation->entry);
        spin_unlock(&server->delegations_lock);

        spin_lock(&delegation->lock);
        inode = delegation->inode;
        if (!inode || !igrab(inode)) {
                spin_unlock(&delegation->lock);
                goto out_put_delegation;
        }
        if (test_bit(NFS_DELEGATION_REVOKED, &delegation->flags) ||
            test_and_set_bit(NFS_DELEGATION_RETURNING, &delegation->flags)) {
                spin_unlock(&delegation->lock);
                goto out_put_inode;
        }
        spin_unlock(&delegation->lock);

        nfs_clear_verifier_delegated(inode);

        err = nfs_end_delegation_return(inode, delegation, false);

out_put_inode:
        iput(inode);
out_put_delegation:
        nfs_put_delegation(delegation);
        if (err)
                return err;
        return 1; /* keep going */
}

static int nfs_server_return_marked_delegations(struct nfs_server *server,
                void __always_unused *data)
{
        int err;

        while ((err = nfs_return_one_delegation(server)) > 0)
                cond_resched();
        return err;
}

static inline bool nfs_delegations_over_limit(struct nfs_server *server)
{
        return !list_empty_careful(&server->delegations_lru) &&
                atomic_long_read(&server->nr_active_delegations) >
                nfs_delegation_watermark;
}

static void nfs_delegations_return_from_lru(struct nfs_server *server)
{
        struct nfs_delegation *d, *n;
        unsigned int pass = 0;
        bool moved = false;

retry:
        spin_lock(&server->delegations_lock);
        list_for_each_entry_safe(d, n, &server->delegations_lru, entry) {
                if (!nfs_delegations_over_limit(server))
                        break;
                if (pass == 0 && test_bit(NFS_DELEGATION_REFERENCED, &d->flags))
                        continue;
                list_move_tail(&d->entry, &server->delegations_return);
                moved = true;
        }
        spin_unlock(&server->delegations_lock);

        /*
         * If we are still over the limit, try to reclaim referenced delegations
         * as well.
         */
        if (pass == 0 && nfs_delegations_over_limit(server)) {
                pass++;
                goto retry;
        }

        if (moved) {
                set_bit(NFS4CLNT_DELEGRETURN, &server->nfs_client->cl_state);
                nfs4_schedule_state_manager(server->nfs_client);
        }
}

static void nfs_delegation_add_lru(struct nfs_server *server,
                struct nfs_delegation *delegation)
{
        spin_lock(&server->delegations_lock);
        if (list_empty(&delegation->entry)) {
                list_add_tail(&delegation->entry, &server->delegations_lru);
                refcount_inc(&delegation->refcount);
        }
        spin_unlock(&server->delegations_lock);

        if (nfs_delegations_over_limit(server))
                nfs_delegations_return_from_lru(server);
}

static bool nfs_server_clear_delayed_delegations(struct nfs_server *server)
{
        bool ret = false;

        if (list_empty_careful(&server->delegations_delayed))
                return false;

        spin_lock(&server->delegations_lock);
        if (!list_empty(&server->delegations_delayed)) {
                list_splice_tail_init(&server->delegations_delayed,
                                      &server->delegations_return);
                ret = true;
        }
        spin_unlock(&server->delegations_lock);

        return ret;
}

static bool nfs_client_clear_delayed_delegations(struct nfs_client *clp)
{
        struct nfs_server *server;
        bool ret = false;

        if (!test_and_clear_bit(NFS4CLNT_DELEGRETURN_DELAYED, &clp->cl_state))
                return false;

        rcu_read_lock();
        list_for_each_entry_rcu (server, &clp->cl_superblocks, client_link) {
                if (nfs_server_clear_delayed_delegations(server))
                        ret = true;
        }
        rcu_read_unlock();

        if (ret)
                set_bit(NFS4CLNT_DELEGRETURN, &clp->cl_state);
        return ret;
}

/**
 * nfs_client_return_marked_delegations - return previously marked delegations
 * @clp: nfs_client to process
 *
 * Note that this function is designed to be called by the state
 * manager thread. For this reason, it cannot flush the dirty data,
 * since that could deadlock in case of a state recovery error.
 *
 * Returns zero on success, or a negative errno value.
 */
int nfs_client_return_marked_delegations(struct nfs_client *clp)
{
        int err = nfs_client_for_each_server(
                clp, nfs_server_return_marked_delegations, NULL);
        if (err)
                return err;
        /* If a return was delayed, sleep to prevent hard looping */
        if (nfs_client_clear_delayed_delegations(clp))
                ssleep(1);
        return 0;
}

/**
 * nfs_inode_evict_delegation - return delegation, don't reclaim opens
 * @inode: inode to process
 *
 * Does not protect against delegation reclaims, therefore really only safe
 * to be called from nfs4_clear_inode(). Guaranteed to always free
 * the delegation structure.
 */
void nfs_inode_evict_delegation(struct inode *inode)
{
        struct nfs_inode *nfsi = NFS_I(inode);
        struct nfs_server *server = NFS_SERVER(inode);
        struct nfs_delegation *delegation;

        rcu_read_lock();
        delegation = rcu_dereference(nfsi->delegation);
        if (delegation && !nfs_detach_delegation(nfsi, delegation, server))
                delegation = NULL;
        rcu_read_unlock();

        if (!delegation)
                return;

        set_bit(NFS_DELEGATION_RETURNING, &delegation->flags);
        nfs_do_return_delegation(inode, delegation, 1);
        nfs_mark_delegation_revoked(server, delegation);
        nfs_put_delegation(delegation);
}

/**
 * nfs4_inode_return_delegation - synchronously return a delegation
 * @inode: inode to process
 *
 * This routine will always flush any dirty data to disk on the
 * assumption that if we need to return the delegation, then
 * we should stop caching.
 *
 * Returns zero on success, or a negative errno value.
 */
void nfs4_inode_return_delegation(struct inode *inode)
{
        struct nfs_inode *nfsi = NFS_I(inode);
        struct nfs_delegation *delegation;

        delegation = nfs_start_delegation_return(nfsi);
        if (!delegation)
                return;

        /* Synchronous recall of any application leases */
        break_lease(inode, O_WRONLY | O_RDWR);
        if (S_ISREG(inode->i_mode))
                nfs_wb_all(inode);
        nfs_end_delegation_return(inode, delegation, true);
        nfs_put_delegation(delegation);
}

/**
 * nfs4_inode_set_return_delegation_on_close - asynchronously return a delegation
 * @inode: inode to process
 *
 * This routine is called to request that the delegation be returned as soon
 * as the file is closed. If the file is already closed, the delegation is
 * immediately returned.
 */
void nfs4_inode_set_return_delegation_on_close(struct inode *inode)
{
        struct nfs_delegation *delegation;
        bool return_now = false;

        if (!inode)
                return;

        delegation = nfs4_get_valid_delegation(inode);
        if (!delegation)
                return;

        spin_lock(&delegation->lock);
        if (!delegation->inode)
                goto out_unlock;
        if (list_empty(&NFS_I(inode)->open_files) &&
            !test_and_set_bit(NFS_DELEGATION_RETURNING, &delegation->flags))
                return_now = true;
        else
                set_bit(NFS_DELEGATION_RETURN_IF_CLOSED, &delegation->flags);
out_unlock:
        spin_unlock(&delegation->lock);
        if (return_now) {
                nfs_clear_verifier_delegated(inode);
                nfs_end_delegation_return(inode, delegation, false);
        }
        nfs_put_delegation(delegation);
}

/**
 * nfs4_inode_return_delegation_on_close - asynchronously return a delegation
 * @inode: inode to process
 *
 * This routine is called on file close in order to determine if the
 * inode delegation needs to be returned immediately.
 */
void nfs4_inode_return_delegation_on_close(struct inode *inode)
{
        struct nfs_server *server = NFS_SERVER(inode);
        struct nfs_delegation *delegation;
        bool return_now = false;

        delegation = nfs4_get_valid_delegation(inode);
        if (!delegation)
                return;

        if (test_bit(NFS_DELEGATION_RETURN_IF_CLOSED, &delegation->flags)) {
                spin_lock(&delegation->lock);
                if (delegation->inode &&
                    list_empty(&NFS_I(inode)->open_files) &&
                    !test_and_set_bit(NFS_DELEGATION_RETURNING, &delegation->flags)) {
                        clear_bit(NFS_DELEGATION_RETURN_IF_CLOSED, &delegation->flags);
                        return_now = true;
                }
                spin_unlock(&delegation->lock);
        }

        if (return_now) {
                nfs_clear_verifier_delegated(inode);
                nfs_end_delegation_return(inode, delegation, false);
        } else {
                nfs_delegation_add_lru(server, delegation);
        }
        nfs_put_delegation(delegation);
}

/**
 * nfs4_inode_make_writeable
 * @inode: pointer to inode
 *
 * Make the inode writeable by returning the delegation if necessary
 */
void nfs4_inode_make_writeable(struct inode *inode)
{
        struct nfs_delegation *delegation;

        delegation = nfs4_get_valid_delegation(inode);
        if (!delegation)
                return;

        if (!nfs4_has_session(NFS_SERVER(inode)->nfs_client) ||
            !(delegation->type & FMODE_WRITE))
                nfs4_inode_return_delegation(inode);
        nfs_put_delegation(delegation);
}

static void
nfs_mark_return_if_closed_delegation(struct nfs_server *server,
                                     struct nfs_delegation *delegation)
{
        struct inode *inode;

        if (!list_empty_careful(&server->delegations_return) ||
            test_bit(NFS_DELEGATION_RETURN_IF_CLOSED, &delegation->flags))
                return;
        spin_lock(&delegation->lock);
        inode = delegation->inode;
        if (!inode)
                goto out;
        if (list_empty(&NFS_I(inode)->open_files))
                nfs_mark_return_delegation(server, delegation);
        else
                set_bit(NFS_DELEGATION_RETURN_IF_CLOSED, &delegation->flags);
out:
        spin_unlock(&delegation->lock);
}

static bool nfs_server_mark_return_all_delegations(struct nfs_server *server)
{
        struct nfs_delegation *delegation;
        bool ret = false;

        list_for_each_entry_rcu(delegation, &server->delegations, super_list) {
                nfs_mark_return_delegation(server, delegation);
                ret = true;
        }
        return ret;
}

static void nfs_delegation_run_state_manager(struct nfs_client *clp)
{
        if (test_bit(NFS4CLNT_DELEGRETURN, &clp->cl_state))
                nfs4_schedule_state_manager(clp);
}

/**
 * nfs_expire_all_delegations
 * @clp: client to process
 *
 */
void nfs_expire_all_delegations(struct nfs_client *clp)
{
        struct nfs_server *server;

        rcu_read_lock();
        list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link)
                nfs_server_mark_return_all_delegations(server);
        rcu_read_unlock();

        nfs_delegation_run_state_manager(clp);
}

/**
 * nfs_server_return_all_delegations - return delegations for one superblock
 * @server: pointer to nfs_server to process
 *
 */
void nfs_server_return_all_delegations(struct nfs_server *server)
{
        struct nfs_client *clp = server->nfs_client;
        bool need_wait;

        if (clp == NULL)
                return;

        rcu_read_lock();
        need_wait = nfs_server_mark_return_all_delegations(server);
        rcu_read_unlock();

        if (need_wait) {
                nfs4_schedule_state_manager(clp);
                nfs4_wait_clnt_recover(clp);
        }
}

static void nfs_mark_return_unused_delegation_types(struct nfs_server *server,
                                                 fmode_t flags)
{
        struct nfs_delegation *delegation;

        list_for_each_entry_rcu(delegation, &server->delegations, super_list) {
                if ((delegation->type == (FMODE_READ|FMODE_WRITE)) && !(flags & FMODE_WRITE))
                        continue;
                if (delegation->type & flags)
                        nfs_mark_return_if_closed_delegation(server, delegation);
        }
}

void nfs_expire_unused_delegation_types(struct nfs_client *clp, fmode_t flags)
{
        struct nfs_server *server;

        rcu_read_lock();
        list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link)
                nfs_mark_return_unused_delegation_types(server, flags);
        rcu_read_unlock();

        nfs_delegation_run_state_manager(clp);
}

static void nfs_revoke_delegation(struct inode *inode,
                const nfs4_stateid *stateid)
{
        struct nfs_delegation *delegation;
        nfs4_stateid tmp;
        bool ret = false;

        rcu_read_lock();
        delegation = rcu_dereference(NFS_I(inode)->delegation);
        if (delegation == NULL)
                goto out;
        if (stateid == NULL) {
                nfs4_stateid_copy(&tmp, &delegation->stateid);
                stateid = &tmp;
        } else {
                if (!nfs4_stateid_match_other(stateid, &delegation->stateid))
                        goto out;
                spin_lock(&delegation->lock);
                if (stateid->seqid) {
                        if (nfs4_stateid_is_newer(&delegation->stateid, stateid)) {
                                spin_unlock(&delegation->lock);
                                goto out;
                        }
                        delegation->stateid.seqid = stateid->seqid;
                }
                spin_unlock(&delegation->lock);
        }
        nfs_mark_delegation_revoked(NFS_SERVER(inode), delegation);
        ret = true;
out:
        rcu_read_unlock();
        if (ret)
                nfs_inode_find_state_and_recover(inode, stateid);
}

void nfs_delegation_mark_returned(struct inode *inode,
                const nfs4_stateid *stateid)
{
        struct nfs_delegation *delegation;

        if (!inode)
                return;

        rcu_read_lock();
        delegation = rcu_dereference(NFS_I(inode)->delegation);
        if (!delegation)
                goto out_rcu_unlock;

        spin_lock(&delegation->lock);
        if (!nfs4_stateid_match_other(stateid, &delegation->stateid))
                goto out_spin_unlock;
        if (stateid->seqid) {
                /* If delegation->stateid is newer, dont mark as returned */
                if (nfs4_stateid_is_newer(&delegation->stateid, stateid))
                        goto out_clear_returning;
                if (delegation->stateid.seqid != stateid->seqid)
                        delegation->stateid.seqid = stateid->seqid;
        }

        nfs_mark_delegation_revoked(NFS_SERVER(inode), delegation);
        clear_bit(NFS_DELEGATION_RETURNING, &delegation->flags);
        spin_unlock(&delegation->lock);
        if (nfs_detach_delegation(NFS_I(inode), delegation, NFS_SERVER(inode)))
                nfs_put_delegation(delegation);
        goto out_rcu_unlock;

out_clear_returning:
        clear_bit(NFS_DELEGATION_RETURNING, &delegation->flags);
out_spin_unlock:
        spin_unlock(&delegation->lock);
out_rcu_unlock:
        rcu_read_unlock();

        nfs_inode_find_state_and_recover(inode, stateid);
}

/**
 * nfs_remove_bad_delegation - handle delegations that are unusable
 * @inode: inode to process
 * @stateid: the delegation's stateid
 *
 * If the server ACK-ed our FREE_STATEID then clean
 * up the delegation, else mark and keep the revoked state.
 */
void nfs_remove_bad_delegation(struct inode *inode,
                const nfs4_stateid *stateid)
{
        if (stateid && stateid->type == NFS4_FREED_STATEID_TYPE)
                nfs_delegation_mark_returned(inode, stateid);
        else
                nfs_revoke_delegation(inode, stateid);
}
EXPORT_SYMBOL_GPL(nfs_remove_bad_delegation);

static bool nfs_mark_return_unreferenced_delegations(struct nfs_server *server)
{
        struct nfs_delegation *d, *n;
        bool marked = false;

        spin_lock(&server->delegations_lock);
        list_for_each_entry_safe(d, n, &server->delegations_lru, entry) {
                if (test_and_clear_bit(NFS_DELEGATION_REFERENCED, &d->flags))
                        continue;
                list_move_tail(&d->entry, &server->delegations_return);
                marked = true;
        }
        spin_unlock(&server->delegations_lock);

        return marked;
}

/**
 * nfs_expire_unreferenced_delegations - Eliminate unused delegations
 * @clp: nfs_client to process
 *
 */
void nfs_expire_unreferenced_delegations(struct nfs_client *clp)
{
        struct nfs_server *server;
        bool marked = false;

        rcu_read_lock();
        list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link)
                marked |= nfs_mark_return_unreferenced_delegations(server);
        rcu_read_unlock();

        if (marked) {
                set_bit(NFS4CLNT_DELEGRETURN, &clp->cl_state);
                nfs4_schedule_state_manager(clp);
        }
}

/**
 * nfs_async_inode_return_delegation - asynchronously return a delegation
 * @inode: inode to process
 * @stateid: state ID information
 *
 * Returns zero on success, or a negative errno value.
 */
int nfs_async_inode_return_delegation(struct inode *inode,
                                      const nfs4_stateid *stateid)
{
        struct nfs_server *server = NFS_SERVER(inode);
        struct nfs_client *clp = server->nfs_client;
        struct nfs_delegation *delegation;

        delegation = nfs4_get_valid_delegation(inode);
        if (!delegation)
                return -ENOENT;

        if (stateid != NULL &&
            !clp->cl_mvops->match_stateid(&delegation->stateid, stateid)) {
                nfs_put_delegation(delegation);
                return -ENOENT;
        }

        nfs_mark_return_delegation(server, delegation);
        nfs_put_delegation(delegation);

        /* If there are any application leases or delegations, recall them */
        break_lease(inode, O_WRONLY | O_RDWR | O_NONBLOCK);

        nfs_delegation_run_state_manager(clp);
        return 0;
}

static struct inode *
nfs_delegation_find_inode_server(struct nfs_server *server,
                                 const struct nfs_fh *fhandle)
{
        struct hlist_head *head = nfs_delegation_hash(server, fhandle);
        struct nfs_delegation *delegation;
        struct super_block *freeme = NULL;
        struct inode *res = NULL;

        hlist_for_each_entry_rcu(delegation, head, hash) {
                spin_lock(&delegation->lock);
                if (delegation->inode != NULL &&
                    !test_bit(NFS_DELEGATION_REVOKED, &delegation->flags) &&
                    nfs_compare_fh(fhandle, &NFS_I(delegation->inode)->fh) == 0) {
                        if (nfs_sb_active(server->super)) {
                                freeme = server->super;
                                res = igrab(delegation->inode);
                        }
                        spin_unlock(&delegation->lock);
                        if (res != NULL)
                                return res;
                        if (freeme) {
                                rcu_read_unlock();
                                nfs_sb_deactive(freeme);
                                rcu_read_lock();
                        }
                        return ERR_PTR(-EAGAIN);
                }
                spin_unlock(&delegation->lock);
        }
        return ERR_PTR(-ENOENT);
}

/**
 * nfs_delegation_find_inode - retrieve the inode associated with a delegation
 * @clp: client state handle
 * @fhandle: filehandle from a delegation recall
 *
 * Returns pointer to inode matching "fhandle," or NULL if a matching inode
 * cannot be found.
 */
struct inode *nfs_delegation_find_inode(struct nfs_client *clp,
                                        const struct nfs_fh *fhandle)
{
        struct nfs_server *server;
        struct inode *res;

        rcu_read_lock();
        list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link) {
                res = nfs_delegation_find_inode_server(server, fhandle);
                if (res != ERR_PTR(-ENOENT)) {
                        rcu_read_unlock();
                        return res;
                }
        }
        rcu_read_unlock();
        return ERR_PTR(-ENOENT);
}

static void nfs_delegation_mark_reclaim_server(struct nfs_server *server)
{
        struct nfs_delegation *delegation;

        list_for_each_entry_rcu(delegation, &server->delegations, super_list) {
                /*
                 * If the delegation may have been admin revoked, then we
                 * cannot reclaim it.
                 */
                if (test_bit(NFS_DELEGATION_TEST_EXPIRED, &delegation->flags))
                        continue;
                set_bit(NFS_DELEGATION_NEED_RECLAIM, &delegation->flags);
        }
}

/**
 * nfs_delegation_mark_reclaim - mark all delegations as needing to be reclaimed
 * @clp: nfs_client to process
 *
 */
void nfs_delegation_mark_reclaim(struct nfs_client *clp)
{
        struct nfs_server *server;

        rcu_read_lock();
        list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link)
                nfs_delegation_mark_reclaim_server(server);
        rcu_read_unlock();
}

static int nfs_server_reap_unclaimed_delegations(struct nfs_server *server,
                void __always_unused *data)
{
        struct nfs_delegation *delegation;
        struct inode *inode;
restart:
        rcu_read_lock();
        list_for_each_entry_rcu(delegation, &server->delegations, super_list) {
                if (test_bit(NFS_DELEGATION_RETURNING,
                                        &delegation->flags) ||
                    test_bit(NFS_DELEGATION_NEED_RECLAIM,
                                        &delegation->flags) == 0)
                        continue;
                inode = nfs_delegation_grab_inode(delegation);
                if (inode == NULL)
                        continue;
                delegation = nfs_start_delegation_return(NFS_I(inode));
                rcu_read_unlock();
                if (delegation != NULL) {
                        if (nfs_detach_delegation(NFS_I(inode), delegation,
                                        server)) {
                                nfs_mark_delegation_revoked(server, delegation);
                                nfs_put_delegation(delegation);
                        }
                        /* Match nfs_start_delegation_return */
                        nfs_put_delegation(delegation);
                }
                iput(inode);
                cond_resched();
                goto restart;
        }
        rcu_read_unlock();
        return 0;
}

/**
 * nfs_delegation_reap_unclaimed - reap unclaimed delegations after reboot recovery is done
 * @clp: nfs_client to process
 *
 */
void nfs_delegation_reap_unclaimed(struct nfs_client *clp)
{
        nfs_client_for_each_server(clp, nfs_server_reap_unclaimed_delegations,
                        NULL);
}

static inline bool nfs4_server_rebooted(const struct nfs_client *clp)
{
        return (clp->cl_state & (BIT(NFS4CLNT_CHECK_LEASE) |
                                BIT(NFS4CLNT_LEASE_EXPIRED) |
                                BIT(NFS4CLNT_SESSION_RESET))) != 0;
}

static void nfs_mark_test_expired_delegation(struct nfs_server *server,
            struct nfs_delegation *delegation)
{
        if (delegation->stateid.type == NFS4_INVALID_STATEID_TYPE)
                return;
        clear_bit(NFS_DELEGATION_NEED_RECLAIM, &delegation->flags);
        set_bit(NFS_DELEGATION_TEST_EXPIRED, &delegation->flags);
        set_bit(NFS4SERV_DELEGATION_EXPIRED, &server->delegation_flags);
        set_bit(NFS4CLNT_DELEGATION_EXPIRED, &server->nfs_client->cl_state);
}

static void nfs_inode_mark_test_expired_delegation(struct nfs_server *server,
                struct inode *inode)
{
        struct nfs_delegation *delegation;

        rcu_read_lock();
        delegation = rcu_dereference(NFS_I(inode)->delegation);
        if (delegation)
                nfs_mark_test_expired_delegation(server, delegation);
        rcu_read_unlock();

}

static void nfs_delegation_mark_test_expired_server(struct nfs_server *server)
{
        struct nfs_delegation *delegation;

        list_for_each_entry_rcu(delegation, &server->delegations, super_list)
                nfs_mark_test_expired_delegation(server, delegation);
}

/**
 * nfs_mark_test_expired_all_delegations - mark all delegations for testing
 * @clp: nfs_client to process
 *
 * Iterates through all the delegations associated with this server and
 * marks them as needing to be checked for validity.
 */
void nfs_mark_test_expired_all_delegations(struct nfs_client *clp)
{
        struct nfs_server *server;

        rcu_read_lock();
        list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link)
                nfs_delegation_mark_test_expired_server(server);
        rcu_read_unlock();
}

/**
 * nfs_test_expired_all_delegations - test all delegations for a client
 * @clp: nfs_client to process
 *
 * Helper for handling "recallable state revoked" status from server.
 */
void nfs_test_expired_all_delegations(struct nfs_client *clp)
{
        nfs_mark_test_expired_all_delegations(clp);
        nfs4_schedule_state_manager(clp);
}

static void
nfs_delegation_test_free_expired(struct inode *inode,
                nfs4_stateid *stateid,
                const struct cred *cred)
{
        struct nfs_server *server = NFS_SERVER(inode);
        const struct nfs4_minor_version_ops *ops = server->nfs_client->cl_mvops;
        int status;

        if (!cred)
                return;
        status = ops->test_and_free_expired(server, stateid, cred);
        if (status == -NFS4ERR_EXPIRED || status == -NFS4ERR_BAD_STATEID)
                nfs_remove_bad_delegation(inode, stateid);
}

static int nfs_server_reap_expired_delegations(struct nfs_server *server,
                void __always_unused *data)
{
        struct nfs_delegation *delegation;
        struct inode *inode;
        const struct cred *cred;
        nfs4_stateid stateid;
        unsigned long gen = ++server->delegation_gen;

        if (!test_and_clear_bit(NFS4SERV_DELEGATION_EXPIRED,
                                &server->delegation_flags))
                return 0;
restart:
        rcu_read_lock();
        list_for_each_entry_rcu(delegation, &server->delegations, super_list) {
                if (test_bit(NFS_DELEGATION_RETURNING,
                                        &delegation->flags) ||
                    test_bit(NFS_DELEGATION_TEST_EXPIRED,
                                        &delegation->flags) == 0 ||
                        delegation->test_gen == gen)
                        continue;
                inode = nfs_delegation_grab_inode(delegation);
                if (inode == NULL)
                        continue;
                spin_lock(&delegation->lock);
                cred = get_cred_rcu(delegation->cred);
                nfs4_stateid_copy(&stateid, &delegation->stateid);
                spin_unlock(&delegation->lock);
                delegation->test_gen = gen;
                clear_bit(NFS_DELEGATION_TEST_EXPIRED, &delegation->flags);
                rcu_read_unlock();
                nfs_delegation_test_free_expired(inode, &stateid, cred);
                put_cred(cred);
                if (!nfs4_server_rebooted(server->nfs_client)) {
                        iput(inode);
                        cond_resched();
                        goto restart;
                }
                nfs_inode_mark_test_expired_delegation(server,inode);
                set_bit(NFS4SERV_DELEGATION_EXPIRED, &server->delegation_flags);
                set_bit(NFS4CLNT_DELEGATION_EXPIRED,
                        &server->nfs_client->cl_state);
                iput(inode);
                return -EAGAIN;
        }
        rcu_read_unlock();
        return 0;
}

/**
 * nfs_reap_expired_delegations - reap expired delegations
 * @clp: nfs_client to process
 *
 * Iterates through all the delegations associated with this server and
 * checks if they have may have been revoked. This function is usually
 * expected to be called in cases where the server may have lost its
 * lease.
 */
void nfs_reap_expired_delegations(struct nfs_client *clp)
{
        nfs_client_for_each_server(clp, nfs_server_reap_expired_delegations,
                        NULL);
}

void nfs_inode_find_delegation_state_and_recover(struct inode *inode,
                const nfs4_stateid *stateid)
{
        struct nfs_client *clp = NFS_SERVER(inode)->nfs_client;
        struct nfs_delegation *delegation;
        bool found = false;

        rcu_read_lock();
        delegation = rcu_dereference(NFS_I(inode)->delegation);
        if (delegation &&
            nfs4_stateid_match_or_older(&delegation->stateid, stateid) &&
            !test_bit(NFS_DELEGATION_REVOKED, &delegation->flags)) {
                nfs_mark_test_expired_delegation(NFS_SERVER(inode), delegation);
                found = true;
        }
        rcu_read_unlock();
        if (found)
                nfs4_schedule_state_manager(clp);
}

/**
 * nfs_delegations_present - check for existence of delegations
 * @clp: client state handle
 *
 * Returns one if there are any nfs_delegation structures attached
 * to this nfs_client.
 */
int nfs_delegations_present(struct nfs_client *clp)
{
        struct nfs_server *server;
        int ret = 0;

        rcu_read_lock();
        list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link)
                if (atomic_long_read(&server->nr_active_delegations) > 0) {
                        ret = 1;
                        break;
                }
        rcu_read_unlock();
        return ret;
}

/**
 * nfs4_refresh_delegation_stateid - Update delegation stateid seqid
 * @dst: stateid to refresh
 * @inode: inode to check
 *
 * Returns "true" and updates "dst->seqid" * if inode had a delegation
 * that matches our delegation stateid. Otherwise "false" is returned.
 */
bool nfs4_refresh_delegation_stateid(nfs4_stateid *dst, struct inode *inode)
{
        struct nfs_delegation *delegation;
        bool ret = false;
        if (!inode)
                goto out;

        rcu_read_lock();
        delegation = rcu_dereference(NFS_I(inode)->delegation);
        if (delegation != NULL &&
            nfs4_stateid_match_other(dst, &delegation->stateid) &&
            nfs4_stateid_is_newer(&delegation->stateid, dst) &&
            !test_bit(NFS_DELEGATION_REVOKED, &delegation->flags)) {
                dst->seqid = delegation->stateid.seqid;
                ret = true;
        }
        rcu_read_unlock();
out:
        return ret;
}

/**
 * nfs4_copy_delegation_stateid - Copy inode's state ID information
 * @inode: inode to check
 * @flags: delegation type requirement
 * @dst: stateid data structure to fill in
 * @cred: optional argument to retrieve credential
 *
 * Returns "true" and fills in "dst->data" * if inode had a delegation,
 * otherwise "false" is returned.
 */
bool nfs4_copy_delegation_stateid(struct inode *inode, fmode_t flags,
                nfs4_stateid *dst, const struct cred **cred)
{
        struct nfs_inode *nfsi = NFS_I(inode);
        struct nfs_delegation *delegation;
        bool ret = false;

        flags &= FMODE_READ|FMODE_WRITE;
        rcu_read_lock();
        delegation = rcu_dereference(nfsi->delegation);
        if (!delegation)
                goto out;
        spin_lock(&delegation->lock);
        ret = nfs4_is_valid_delegation(delegation, flags);
        if (ret) {
                nfs4_stateid_copy(dst, &delegation->stateid);
                nfs_mark_delegation_referenced(delegation);
                if (cred)
                        *cred = get_cred(delegation->cred);
        }
        spin_unlock(&delegation->lock);
out:
        rcu_read_unlock();
        return ret;
}

/**
 * nfs4_delegation_flush_on_close - Check if we must flush file on close
 * @inode: inode to check
 *
 * This function checks the number of outstanding writes to the file
 * against the delegation 'space_limit' field to see if
 * the spec requires us to flush the file on close.
 */
bool nfs4_delegation_flush_on_close(const struct inode *inode)
{
        struct nfs_inode *nfsi = NFS_I(inode);
        struct nfs_delegation *delegation;
        bool ret = true;

        rcu_read_lock();
        delegation = rcu_dereference(nfsi->delegation);
        if (delegation == NULL || !(delegation->type & FMODE_WRITE))
                goto out;
        if (atomic_long_read(&nfsi->nrequests) < delegation->pagemod_limit)
                ret = false;
out:
        rcu_read_unlock();
        return ret;
}

int nfs4_delegation_hash_alloc(struct nfs_server *server)
{
        int delegation_buckets, i;

        delegation_buckets = roundup_pow_of_two(nfs_delegation_watermark / 16);
        server->delegation_hash_mask = delegation_buckets - 1;
        server->delegation_hash_table = kmalloc_objs(*server->delegation_hash_table,
                                                     delegation_buckets);
        if (!server->delegation_hash_table)
                return -ENOMEM;
        for (i = 0; i < delegation_buckets; i++)
                INIT_HLIST_HEAD(&server->delegation_hash_table[i]);
        return 0;
}