xref: /openbmc/linux/fs/nfs/delegation.c (revision 8c75593c)
1457c8996SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
21da177e4SLinus Torvalds /*
31da177e4SLinus Torvalds  * linux/fs/nfs/delegation.c
41da177e4SLinus Torvalds  *
51da177e4SLinus Torvalds  * Copyright (C) 2004 Trond Myklebust
61da177e4SLinus Torvalds  *
71da177e4SLinus Torvalds  * NFS file delegation management
81da177e4SLinus Torvalds  *
91da177e4SLinus Torvalds  */
101da177e4SLinus Torvalds #include <linux/completion.h>
1158d9714aSTrond Myklebust #include <linux/kthread.h>
121da177e4SLinus Torvalds #include <linux/module.h>
131da177e4SLinus Torvalds #include <linux/sched.h>
145a0e3ad6STejun Heo #include <linux/slab.h>
151da177e4SLinus Torvalds #include <linux/spinlock.h>
161eb5d98fSJeff Layton #include <linux/iversion.h>
171da177e4SLinus Torvalds 
181da177e4SLinus Torvalds #include <linux/nfs4.h>
191da177e4SLinus Torvalds #include <linux/nfs_fs.h>
201da177e4SLinus Torvalds #include <linux/nfs_xdr.h>
211da177e4SLinus Torvalds 
224ce79717STrond Myklebust #include "nfs4_fs.h"
23c01d3645STrond Myklebust #include "nfs4session.h"
241da177e4SLinus Torvalds #include "delegation.h"
2524c8dbbbSDavid Howells #include "internal.h"
26ca8acf8dSTrond Myklebust #include "nfs4trace.h"
271da177e4SLinus Torvalds 
2810717f45STrond Myklebust #define NFS_DEFAULT_DELEGATION_WATERMARK (5000U)
2910717f45STrond Myklebust 
30d2269ea1STrond Myklebust static atomic_long_t nfs_active_delegations;
3110717f45STrond Myklebust static unsigned nfs_delegation_watermark = NFS_DEFAULT_DELEGATION_WATERMARK;
32d2269ea1STrond Myklebust 
33d2269ea1STrond Myklebust static void __nfs_free_delegation(struct nfs_delegation *delegation)
34905f8d16STrond Myklebust {
35a52458b4SNeilBrown 	put_cred(delegation->cred);
36e00b8a24STrond Myklebust 	delegation->cred = NULL;
3726f04ddeSLai Jiangshan 	kfree_rcu(delegation, rcu);
388383e460STrond Myklebust }
398383e460STrond Myklebust 
40d2269ea1STrond Myklebust static void nfs_mark_delegation_revoked(struct nfs_delegation *delegation)
41d2269ea1STrond Myklebust {
42d2269ea1STrond Myklebust 	if (!test_and_set_bit(NFS_DELEGATION_REVOKED, &delegation->flags)) {
43d2269ea1STrond Myklebust 		delegation->stateid.type = NFS4_INVALID_STATEID_TYPE;
44d2269ea1STrond Myklebust 		atomic_long_dec(&nfs_active_delegations);
45efeda80dSTrond Myklebust 		if (!test_bit(NFS_DELEGATION_RETURNING, &delegation->flags))
46efeda80dSTrond Myklebust 			nfs_clear_verifier_delegated(delegation->inode);
47d2269ea1STrond Myklebust 	}
48d2269ea1STrond Myklebust }
49d2269ea1STrond Myklebust 
508c75593cSTrond Myklebust static struct nfs_delegation *nfs_get_delegation(struct nfs_delegation *delegation)
518c75593cSTrond Myklebust {
528c75593cSTrond Myklebust 	refcount_inc(&delegation->refcount);
538c75593cSTrond Myklebust 	return delegation;
548c75593cSTrond Myklebust }
558c75593cSTrond Myklebust 
568c75593cSTrond Myklebust static void nfs_put_delegation(struct nfs_delegation *delegation)
578c75593cSTrond Myklebust {
588c75593cSTrond Myklebust 	if (refcount_dec_and_test(&delegation->refcount))
598c75593cSTrond Myklebust 		__nfs_free_delegation(delegation);
608c75593cSTrond Myklebust }
618c75593cSTrond Myklebust 
62d2269ea1STrond Myklebust static void nfs_free_delegation(struct nfs_delegation *delegation)
63d2269ea1STrond Myklebust {
64d2269ea1STrond Myklebust 	nfs_mark_delegation_revoked(delegation);
658c75593cSTrond Myklebust 	nfs_put_delegation(delegation);
66d2269ea1STrond Myklebust }
67d2269ea1STrond Myklebust 
68d3978bb3SChuck Lever /**
69d3978bb3SChuck Lever  * nfs_mark_delegation_referenced - set delegation's REFERENCED flag
70d3978bb3SChuck Lever  * @delegation: delegation to process
71d3978bb3SChuck Lever  *
72d3978bb3SChuck Lever  */
73b7391f44STrond Myklebust void nfs_mark_delegation_referenced(struct nfs_delegation *delegation)
74b7391f44STrond Myklebust {
75b7391f44STrond Myklebust 	set_bit(NFS_DELEGATION_REFERENCED, &delegation->flags);
76b7391f44STrond Myklebust }
77b7391f44STrond Myklebust 
78aa05c87fSTrond Myklebust static bool
79aa05c87fSTrond Myklebust nfs4_is_valid_delegation(const struct nfs_delegation *delegation,
80aa05c87fSTrond Myklebust 		fmode_t flags)
81aa05c87fSTrond Myklebust {
82aa05c87fSTrond Myklebust 	if (delegation != NULL && (delegation->type & flags) == flags &&
83aa05c87fSTrond Myklebust 	    !test_bit(NFS_DELEGATION_REVOKED, &delegation->flags) &&
84aa05c87fSTrond Myklebust 	    !test_bit(NFS_DELEGATION_RETURNING, &delegation->flags))
85aa05c87fSTrond Myklebust 		return true;
86aa05c87fSTrond Myklebust 	return false;
87aa05c87fSTrond Myklebust }
88aa05c87fSTrond Myklebust 
89be3df3ddSTrond Myklebust struct nfs_delegation *nfs4_get_valid_delegation(const struct inode *inode)
90be3df3ddSTrond Myklebust {
91be3df3ddSTrond Myklebust 	struct nfs_delegation *delegation;
92be3df3ddSTrond Myklebust 
93be3df3ddSTrond Myklebust 	delegation = rcu_dereference(NFS_I(inode)->delegation);
94be3df3ddSTrond Myklebust 	if (nfs4_is_valid_delegation(delegation, 0))
95be3df3ddSTrond Myklebust 		return delegation;
96be3df3ddSTrond Myklebust 	return NULL;
97be3df3ddSTrond Myklebust }
98be3df3ddSTrond Myklebust 
9915bb3afeSPeng Tao static int
10015bb3afeSPeng Tao nfs4_do_check_delegation(struct inode *inode, fmode_t flags, bool mark)
101b7391f44STrond Myklebust {
102b7391f44STrond Myklebust 	struct nfs_delegation *delegation;
103b7391f44STrond Myklebust 	int ret = 0;
104b7391f44STrond Myklebust 
105b7391f44STrond Myklebust 	flags &= FMODE_READ|FMODE_WRITE;
106b7391f44STrond Myklebust 	rcu_read_lock();
107b7391f44STrond Myklebust 	delegation = rcu_dereference(NFS_I(inode)->delegation);
108aa05c87fSTrond Myklebust 	if (nfs4_is_valid_delegation(delegation, flags)) {
10915bb3afeSPeng Tao 		if (mark)
110b7391f44STrond Myklebust 			nfs_mark_delegation_referenced(delegation);
111b7391f44STrond Myklebust 		ret = 1;
112b7391f44STrond Myklebust 	}
113b7391f44STrond Myklebust 	rcu_read_unlock();
114b7391f44STrond Myklebust 	return ret;
115b7391f44STrond Myklebust }
11615bb3afeSPeng Tao /**
11715bb3afeSPeng Tao  * nfs_have_delegation - check if inode has a delegation, mark it
11815bb3afeSPeng Tao  * NFS_DELEGATION_REFERENCED if there is one.
11915bb3afeSPeng Tao  * @inode: inode to check
12015bb3afeSPeng Tao  * @flags: delegation types to check for
12115bb3afeSPeng Tao  *
12215bb3afeSPeng Tao  * Returns one if inode has the indicated delegation, otherwise zero.
12315bb3afeSPeng Tao  */
12415bb3afeSPeng Tao int nfs4_have_delegation(struct inode *inode, fmode_t flags)
12515bb3afeSPeng Tao {
12615bb3afeSPeng Tao 	return nfs4_do_check_delegation(inode, flags, true);
12715bb3afeSPeng Tao }
12815bb3afeSPeng Tao 
12915bb3afeSPeng Tao /*
13015bb3afeSPeng Tao  * nfs4_check_delegation - check if inode has a delegation, do not mark
13115bb3afeSPeng Tao  * NFS_DELEGATION_REFERENCED if it has one.
13215bb3afeSPeng Tao  */
13315bb3afeSPeng Tao int nfs4_check_delegation(struct inode *inode, fmode_t flags)
13415bb3afeSPeng Tao {
13515bb3afeSPeng Tao 	return nfs4_do_check_delegation(inode, flags, false);
13615bb3afeSPeng Tao }
137b7391f44STrond Myklebust 
13844f411c3SOlga Kornievskaia static int nfs_delegation_claim_locks(struct nfs4_state *state, const nfs4_stateid *stateid)
139888e694cSTrond Myklebust {
140888e694cSTrond Myklebust 	struct inode *inode = state->inode;
141888e694cSTrond Myklebust 	struct file_lock *fl;
142bd61e0a9SJeff Layton 	struct file_lock_context *flctx = inode->i_flctx;
143bd61e0a9SJeff Layton 	struct list_head *list;
144d5122201STrond Myklebust 	int status = 0;
145888e694cSTrond Myklebust 
146bd61e0a9SJeff Layton 	if (flctx == NULL)
14765b62a29STrond Myklebust 		goto out;
148314d7cc0SJeff Layton 
149bd61e0a9SJeff Layton 	list = &flctx->flc_posix;
1506109c850SJeff Layton 	spin_lock(&flctx->flc_lock);
151bd61e0a9SJeff Layton restart:
152bd61e0a9SJeff Layton 	list_for_each_entry(fl, list, fl_list) {
15344f411c3SOlga Kornievskaia 		if (nfs_file_open_context(fl->fl_file)->state != state)
154888e694cSTrond Myklebust 			continue;
1556109c850SJeff Layton 		spin_unlock(&flctx->flc_lock);
156db4f2e63STrond Myklebust 		status = nfs4_lock_delegation_recall(fl, state, stateid);
157d5122201STrond Myklebust 		if (status < 0)
1583f09df70STrond Myklebust 			goto out;
1596109c850SJeff Layton 		spin_lock(&flctx->flc_lock);
160888e694cSTrond Myklebust 	}
161bd61e0a9SJeff Layton 	if (list == &flctx->flc_posix) {
162bd61e0a9SJeff Layton 		list = &flctx->flc_flock;
163bd61e0a9SJeff Layton 		goto restart;
1645263e31eSJeff Layton 	}
1656109c850SJeff Layton 	spin_unlock(&flctx->flc_lock);
1663f09df70STrond Myklebust out:
167888e694cSTrond Myklebust 	return status;
168888e694cSTrond Myklebust }
169888e694cSTrond Myklebust 
17024311f88STrond Myklebust static int nfs_delegation_claim_opens(struct inode *inode,
17124311f88STrond Myklebust 		const nfs4_stateid *stateid, fmode_t type)
1721da177e4SLinus Torvalds {
1731da177e4SLinus Torvalds 	struct nfs_inode *nfsi = NFS_I(inode);
1741da177e4SLinus Torvalds 	struct nfs_open_context *ctx;
175d25be546STrond Myklebust 	struct nfs4_state_owner *sp;
1761da177e4SLinus Torvalds 	struct nfs4_state *state;
177d25be546STrond Myklebust 	unsigned int seq;
178888e694cSTrond Myklebust 	int err;
1791da177e4SLinus Torvalds 
1801da177e4SLinus Torvalds again:
1810de43976STrond Myklebust 	rcu_read_lock();
1820de43976STrond Myklebust 	list_for_each_entry_rcu(ctx, &nfsi->open_files, list) {
1831da177e4SLinus Torvalds 		state = ctx->state;
1841da177e4SLinus Torvalds 		if (state == NULL)
1851da177e4SLinus Torvalds 			continue;
1861da177e4SLinus Torvalds 		if (!test_bit(NFS_DELEGATED_STATE, &state->flags))
1871da177e4SLinus Torvalds 			continue;
188f8ebf7a8STrond Myklebust 		if (!nfs4_valid_open_stateid(state))
189f8ebf7a8STrond Myklebust 			continue;
190f597c537STrond Myklebust 		if (!nfs4_stateid_match(&state->stateid, stateid))
19190163027STrond Myklebust 			continue;
1920de43976STrond Myklebust 		if (!get_nfs_open_context(ctx))
1930de43976STrond Myklebust 			continue;
1940de43976STrond Myklebust 		rcu_read_unlock();
195d25be546STrond Myklebust 		sp = state->owner;
19665b62a29STrond Myklebust 		/* Block nfs4_proc_unlck */
19765b62a29STrond Myklebust 		mutex_lock(&sp->so_delegreturn_mutex);
198d25be546STrond Myklebust 		seq = raw_seqcount_begin(&sp->so_reclaim_seqcount);
1995eb8d18cSTrond Myklebust 		err = nfs4_open_delegation_recall(ctx, state, stateid);
200d25be546STrond Myklebust 		if (!err)
20144f411c3SOlga Kornievskaia 			err = nfs_delegation_claim_locks(state, stateid);
202d25be546STrond Myklebust 		if (!err && read_seqcount_retry(&sp->so_reclaim_seqcount, seq))
203d25be546STrond Myklebust 			err = -EAGAIN;
20465b62a29STrond Myklebust 		mutex_unlock(&sp->so_delegreturn_mutex);
2051da177e4SLinus Torvalds 		put_nfs_open_context(ctx);
206888e694cSTrond Myklebust 		if (err != 0)
207d18cc1fdSTrond Myklebust 			return err;
2081da177e4SLinus Torvalds 		goto again;
2091da177e4SLinus Torvalds 	}
2100de43976STrond Myklebust 	rcu_read_unlock();
211d18cc1fdSTrond Myklebust 	return 0;
2121da177e4SLinus Torvalds }
2131da177e4SLinus Torvalds 
214d3978bb3SChuck Lever /**
215d3978bb3SChuck Lever  * nfs_inode_reclaim_delegation - process a delegation reclaim request
216d3978bb3SChuck Lever  * @inode: inode to process
217d3978bb3SChuck Lever  * @cred: credential to use for request
21835156bffSTrond Myklebust  * @type: delegation type
21935156bffSTrond Myklebust  * @stateid: delegation stateid
22035156bffSTrond Myklebust  * @pagemod_limit: write delegation "space_limit"
221d3978bb3SChuck Lever  *
2221da177e4SLinus Torvalds  */
223a52458b4SNeilBrown void nfs_inode_reclaim_delegation(struct inode *inode, const struct cred *cred,
22435156bffSTrond Myklebust 				  fmode_t type,
22535156bffSTrond Myklebust 				  const nfs4_stateid *stateid,
22635156bffSTrond Myklebust 				  unsigned long pagemod_limit)
2271da177e4SLinus Torvalds {
2288f649c37STrond Myklebust 	struct nfs_delegation *delegation;
229a52458b4SNeilBrown 	const struct cred *oldcred = NULL;
2301da177e4SLinus Torvalds 
2318f649c37STrond Myklebust 	rcu_read_lock();
2328f649c37STrond Myklebust 	delegation = rcu_dereference(NFS_I(inode)->delegation);
2338f649c37STrond Myklebust 	if (delegation != NULL) {
2348f649c37STrond Myklebust 		spin_lock(&delegation->lock);
2351deed572STrond Myklebust 		if (nfs4_is_valid_delegation(delegation, 0)) {
23635156bffSTrond Myklebust 			nfs4_stateid_copy(&delegation->stateid, stateid);
23735156bffSTrond Myklebust 			delegation->type = type;
23835156bffSTrond Myklebust 			delegation->pagemod_limit = pagemod_limit;
23905c88babSTrond Myklebust 			oldcred = delegation->cred;
240a52458b4SNeilBrown 			delegation->cred = get_cred(cred);
2418f649c37STrond Myklebust 			clear_bit(NFS_DELEGATION_NEED_RECLAIM,
2428f649c37STrond Myklebust 				  &delegation->flags);
2438f649c37STrond Myklebust 			spin_unlock(&delegation->lock);
2448f649c37STrond Myklebust 			rcu_read_unlock();
245a52458b4SNeilBrown 			put_cred(oldcred);
24635156bffSTrond Myklebust 			trace_nfs4_reclaim_delegation(inode, type);
247b1a318deSTrond Myklebust 			return;
248b1a318deSTrond Myklebust 		}
2498f649c37STrond Myklebust 		/* We appear to have raced with a delegation return. */
2508f649c37STrond Myklebust 		spin_unlock(&delegation->lock);
251b1a318deSTrond Myklebust 	}
2528f649c37STrond Myklebust 	rcu_read_unlock();
25335156bffSTrond Myklebust 	nfs_inode_set_delegation(inode, cred, type, stateid, pagemod_limit);
2548f649c37STrond Myklebust }
2551da177e4SLinus Torvalds 
25657bfa891STrond Myklebust static int nfs_do_return_delegation(struct inode *inode, struct nfs_delegation *delegation, int issync)
25757bfa891STrond Myklebust {
25857bfa891STrond Myklebust 	int res = 0;
25957bfa891STrond Myklebust 
260869f9dfaSTrond Myklebust 	if (!test_bit(NFS_DELEGATION_REVOKED, &delegation->flags))
261869f9dfaSTrond Myklebust 		res = nfs4_proc_delegreturn(inode,
262869f9dfaSTrond Myklebust 				delegation->cred,
263869f9dfaSTrond Myklebust 				&delegation->stateid,
264869f9dfaSTrond Myklebust 				issync);
26557bfa891STrond Myklebust 	return res;
26657bfa891STrond Myklebust }
26757bfa891STrond Myklebust 
26886e89489STrond Myklebust static struct inode *nfs_delegation_grab_inode(struct nfs_delegation *delegation)
26986e89489STrond Myklebust {
27086e89489STrond Myklebust 	struct inode *inode = NULL;
27186e89489STrond Myklebust 
27286e89489STrond Myklebust 	spin_lock(&delegation->lock);
27386e89489STrond Myklebust 	if (delegation->inode != NULL)
27486e89489STrond Myklebust 		inode = igrab(delegation->inode);
2756f9449beSTrond Myklebust 	if (!inode)
2766f9449beSTrond Myklebust 		set_bit(NFS_DELEGATION_INODE_FREEING, &delegation->flags);
27786e89489STrond Myklebust 	spin_unlock(&delegation->lock);
27886e89489STrond Myklebust 	return inode;
27986e89489STrond Myklebust }
28086e89489STrond Myklebust 
281dda4b225SChuck Lever static struct nfs_delegation *
282d25be546STrond Myklebust nfs_start_delegation_return_locked(struct nfs_inode *nfsi)
28357bfa891STrond Myklebust {
284d25be546STrond Myklebust 	struct nfs_delegation *ret = NULL;
285d25be546STrond Myklebust 	struct nfs_delegation *delegation = rcu_dereference(nfsi->delegation);
28657bfa891STrond Myklebust 
28757bfa891STrond Myklebust 	if (delegation == NULL)
288d25be546STrond Myklebust 		goto out;
289d25be546STrond Myklebust 	spin_lock(&delegation->lock);
2908c75593cSTrond Myklebust 	if (!test_and_set_bit(NFS_DELEGATION_RETURNING, &delegation->flags)) {
2918c75593cSTrond Myklebust 		/* Refcount matched in nfs_end_delegation_return() */
2928c75593cSTrond Myklebust 		ret = nfs_get_delegation(delegation);
2938c75593cSTrond Myklebust 	}
294d25be546STrond Myklebust 	spin_unlock(&delegation->lock);
295efeda80dSTrond Myklebust 	if (ret)
296efeda80dSTrond Myklebust 		nfs_clear_verifier_delegated(&nfsi->vfs_inode);
297d25be546STrond Myklebust out:
298d25be546STrond Myklebust 	return ret;
299d25be546STrond Myklebust }
300d25be546STrond Myklebust 
301d25be546STrond Myklebust static struct nfs_delegation *
302d25be546STrond Myklebust nfs_start_delegation_return(struct nfs_inode *nfsi)
303d25be546STrond Myklebust {
304d25be546STrond Myklebust 	struct nfs_delegation *delegation;
305d25be546STrond Myklebust 
306d25be546STrond Myklebust 	rcu_read_lock();
307d25be546STrond Myklebust 	delegation = nfs_start_delegation_return_locked(nfsi);
308d25be546STrond Myklebust 	rcu_read_unlock();
309d25be546STrond Myklebust 	return delegation;
310d25be546STrond Myklebust }
311d25be546STrond Myklebust 
312d25be546STrond Myklebust static void
313d25be546STrond Myklebust nfs_abort_delegation_return(struct nfs_delegation *delegation,
314d25be546STrond Myklebust 		struct nfs_client *clp)
315d25be546STrond Myklebust {
316dda4b225SChuck Lever 
31734310430STrond Myklebust 	spin_lock(&delegation->lock);
318d25be546STrond Myklebust 	clear_bit(NFS_DELEGATION_RETURNING, &delegation->flags);
319d25be546STrond Myklebust 	set_bit(NFS_DELEGATION_RETURN, &delegation->flags);
320d25be546STrond Myklebust 	spin_unlock(&delegation->lock);
321d25be546STrond Myklebust 	set_bit(NFS4CLNT_DELEGRETURN, &clp->cl_state);
322d25be546STrond Myklebust }
323d25be546STrond Myklebust 
324d25be546STrond Myklebust static struct nfs_delegation *
325d25be546STrond Myklebust nfs_detach_delegation_locked(struct nfs_inode *nfsi,
326d25be546STrond Myklebust 		struct nfs_delegation *delegation,
327d25be546STrond Myklebust 		struct nfs_client *clp)
328d25be546STrond Myklebust {
329d25be546STrond Myklebust 	struct nfs_delegation *deleg_cur =
330d25be546STrond Myklebust 		rcu_dereference_protected(nfsi->delegation,
331d25be546STrond Myklebust 				lockdep_is_held(&clp->cl_lock));
332d25be546STrond Myklebust 
333d25be546STrond Myklebust 	if (deleg_cur == NULL || delegation != deleg_cur)
334d25be546STrond Myklebust 		return NULL;
335d25be546STrond Myklebust 
336d25be546STrond Myklebust 	spin_lock(&delegation->lock);
337f9e0cc9cSTrond Myklebust 	if (!delegation->inode) {
338f9e0cc9cSTrond Myklebust 		spin_unlock(&delegation->lock);
339f9e0cc9cSTrond Myklebust 		return NULL;
340f9e0cc9cSTrond Myklebust 	}
34157bfa891STrond Myklebust 	list_del_rcu(&delegation->super_list);
34286e89489STrond Myklebust 	delegation->inode = NULL;
34357bfa891STrond Myklebust 	rcu_assign_pointer(nfsi->delegation, NULL);
34434310430STrond Myklebust 	spin_unlock(&delegation->lock);
34557bfa891STrond Myklebust 	return delegation;
34657bfa891STrond Myklebust }
34757bfa891STrond Myklebust 
348dda4b225SChuck Lever static struct nfs_delegation *nfs_detach_delegation(struct nfs_inode *nfsi,
349d25be546STrond Myklebust 		struct nfs_delegation *delegation,
350d3978bb3SChuck Lever 		struct nfs_server *server)
351dda4b225SChuck Lever {
352d3978bb3SChuck Lever 	struct nfs_client *clp = server->nfs_client;
353dda4b225SChuck Lever 
354dda4b225SChuck Lever 	spin_lock(&clp->cl_lock);
355d25be546STrond Myklebust 	delegation = nfs_detach_delegation_locked(nfsi, delegation, clp);
356dda4b225SChuck Lever 	spin_unlock(&clp->cl_lock);
357dda4b225SChuck Lever 	return delegation;
358dda4b225SChuck Lever }
359dda4b225SChuck Lever 
360d25be546STrond Myklebust static struct nfs_delegation *
361d25be546STrond Myklebust nfs_inode_detach_delegation(struct inode *inode)
362d25be546STrond Myklebust {
363d25be546STrond Myklebust 	struct nfs_inode *nfsi = NFS_I(inode);
364d25be546STrond Myklebust 	struct nfs_server *server = NFS_SERVER(inode);
365d25be546STrond Myklebust 	struct nfs_delegation *delegation;
366d25be546STrond Myklebust 
367ee05f456STrond Myklebust 	rcu_read_lock();
368ee05f456STrond Myklebust 	delegation = rcu_dereference(nfsi->delegation);
369ee05f456STrond Myklebust 	if (delegation != NULL)
370ee05f456STrond Myklebust 		delegation = nfs_detach_delegation(nfsi, delegation, server);
371ee05f456STrond Myklebust 	rcu_read_unlock();
372ee05f456STrond Myklebust 	return delegation;
373d25be546STrond Myklebust }
374d25be546STrond Myklebust 
375cf6726e2STrond Myklebust static void
376cf6726e2STrond Myklebust nfs_update_inplace_delegation(struct nfs_delegation *delegation,
377cf6726e2STrond Myklebust 		const struct nfs_delegation *update)
378cf6726e2STrond Myklebust {
379cf6726e2STrond Myklebust 	if (nfs4_stateid_is_newer(&update->stateid, &delegation->stateid)) {
380cf6726e2STrond Myklebust 		delegation->stateid.seqid = update->stateid.seqid;
381cf6726e2STrond Myklebust 		smp_wmb();
382cf6726e2STrond Myklebust 		delegation->type = update->type;
383d2269ea1STrond Myklebust 		if (test_and_clear_bit(NFS_DELEGATION_REVOKED, &delegation->flags))
384d2269ea1STrond Myklebust 			atomic_long_inc(&nfs_active_delegations);
385cf6726e2STrond Myklebust 	}
386cf6726e2STrond Myklebust }
387cf6726e2STrond Myklebust 
388d3978bb3SChuck Lever /**
389d3978bb3SChuck Lever  * nfs_inode_set_delegation - set up a delegation on an inode
390d3978bb3SChuck Lever  * @inode: inode to which delegation applies
391d3978bb3SChuck Lever  * @cred: cred to use for subsequent delegation processing
39235156bffSTrond Myklebust  * @type: delegation type
39335156bffSTrond Myklebust  * @stateid: delegation stateid
39435156bffSTrond Myklebust  * @pagemod_limit: write delegation "space_limit"
395d3978bb3SChuck Lever  *
396d3978bb3SChuck Lever  * Returns zero on success, or a negative errno value.
3971da177e4SLinus Torvalds  */
398a52458b4SNeilBrown int nfs_inode_set_delegation(struct inode *inode, const struct cred *cred,
39935156bffSTrond Myklebust 				  fmode_t type,
40035156bffSTrond Myklebust 				  const nfs4_stateid *stateid,
40135156bffSTrond Myklebust 				  unsigned long pagemod_limit)
4021da177e4SLinus Torvalds {
403d3978bb3SChuck Lever 	struct nfs_server *server = NFS_SERVER(inode);
404d3978bb3SChuck Lever 	struct nfs_client *clp = server->nfs_client;
4051da177e4SLinus Torvalds 	struct nfs_inode *nfsi = NFS_I(inode);
40617d2c0a0SDavid Howells 	struct nfs_delegation *delegation, *old_delegation;
40757bfa891STrond Myklebust 	struct nfs_delegation *freeme = NULL;
4081da177e4SLinus Torvalds 	int status = 0;
4091da177e4SLinus Torvalds 
4108535b2beSTrond Myklebust 	delegation = kmalloc(sizeof(*delegation), GFP_NOFS);
4111da177e4SLinus Torvalds 	if (delegation == NULL)
4121da177e4SLinus Torvalds 		return -ENOMEM;
41335156bffSTrond Myklebust 	nfs4_stateid_copy(&delegation->stateid, stateid);
4148c75593cSTrond Myklebust 	refcount_set(&delegation->refcount, 1);
41535156bffSTrond Myklebust 	delegation->type = type;
41635156bffSTrond Myklebust 	delegation->pagemod_limit = pagemod_limit;
4171eb5d98fSJeff Layton 	delegation->change_attr = inode_peek_iversion_raw(inode);
418a52458b4SNeilBrown 	delegation->cred = get_cred(cred);
4191da177e4SLinus Torvalds 	delegation->inode = inode;
420b7391f44STrond Myklebust 	delegation->flags = 1<<NFS_DELEGATION_REFERENCED;
42134310430STrond Myklebust 	spin_lock_init(&delegation->lock);
4221da177e4SLinus Torvalds 
4231da177e4SLinus Torvalds 	spin_lock(&clp->cl_lock);
42417d2c0a0SDavid Howells 	old_delegation = rcu_dereference_protected(nfsi->delegation,
42517d2c0a0SDavid Howells 					lockdep_is_held(&clp->cl_lock));
426ee05f456STrond Myklebust 	if (old_delegation == NULL)
427ee05f456STrond Myklebust 		goto add_new;
428cf6726e2STrond Myklebust 	/* Is this an update of the existing delegation? */
429cf6726e2STrond Myklebust 	if (nfs4_stateid_match_other(&old_delegation->stateid,
430cf6726e2STrond Myklebust 				&delegation->stateid)) {
431e0f07896STrond Myklebust 		spin_lock(&old_delegation->lock);
432cf6726e2STrond Myklebust 		nfs_update_inplace_delegation(old_delegation,
433cf6726e2STrond Myklebust 				delegation);
434e0f07896STrond Myklebust 		spin_unlock(&old_delegation->lock);
43557bfa891STrond Myklebust 		goto out;
43657bfa891STrond Myklebust 	}
437ee05f456STrond Myklebust 	if (!test_bit(NFS_DELEGATION_REVOKED, &old_delegation->flags)) {
43857bfa891STrond Myklebust 		/*
43957bfa891STrond Myklebust 		 * Deal with broken servers that hand out two
44057bfa891STrond Myklebust 		 * delegations for the same file.
44117280175STrond Myklebust 		 * Allow for upgrades to a WRITE delegation, but
44217280175STrond Myklebust 		 * nothing else.
44357bfa891STrond Myklebust 		 */
44457bfa891STrond Myklebust 		dfprintk(FILE, "%s: server %s handed out "
44557bfa891STrond Myklebust 				"a duplicate delegation!\n",
4463110ff80SHarvey Harrison 				__func__, clp->cl_hostname);
44717280175STrond Myklebust 		if (delegation->type == old_delegation->type ||
44817280175STrond Myklebust 		    !(delegation->type & FMODE_WRITE)) {
44957bfa891STrond Myklebust 			freeme = delegation;
45057bfa891STrond Myklebust 			delegation = NULL;
45157bfa891STrond Myklebust 			goto out;
45257bfa891STrond Myklebust 		}
453ade04647STrond Myklebust 		if (test_and_set_bit(NFS_DELEGATION_RETURNING,
454ade04647STrond Myklebust 					&old_delegation->flags))
455ade04647STrond Myklebust 			goto out;
456ee05f456STrond Myklebust 	}
457ee05f456STrond Myklebust 	freeme = nfs_detach_delegation_locked(nfsi, old_delegation, clp);
458d25be546STrond Myklebust 	if (freeme == NULL)
459d25be546STrond Myklebust 		goto out;
460ee05f456STrond Myklebust add_new:
46138942ba2STrond Myklebust 	list_add_tail_rcu(&delegation->super_list, &server->delegations);
4628383e460STrond Myklebust 	rcu_assign_pointer(nfsi->delegation, delegation);
4631da177e4SLinus Torvalds 	delegation = NULL;
464412c77ceSTrond Myklebust 
465d2269ea1STrond Myklebust 	atomic_long_inc(&nfs_active_delegations);
466d2269ea1STrond Myklebust 
46735156bffSTrond Myklebust 	trace_nfs4_set_delegation(inode, type);
468412c77ceSTrond Myklebust 
46997c2c17aSTrond Myklebust 	spin_lock(&inode->i_lock);
47097c2c17aSTrond Myklebust 	if (NFS_I(inode)->cache_validity & (NFS_INO_INVALID_ATTR|NFS_INO_INVALID_ATIME))
47197c2c17aSTrond Myklebust 		NFS_I(inode)->cache_validity |= NFS_INO_REVAL_FORCED;
47297c2c17aSTrond Myklebust 	spin_unlock(&inode->i_lock);
47357bfa891STrond Myklebust out:
4741da177e4SLinus Torvalds 	spin_unlock(&clp->cl_lock);
475603c83daSTrond Myklebust 	if (delegation != NULL)
476d2269ea1STrond Myklebust 		__nfs_free_delegation(delegation);
477ee05f456STrond Myklebust 	if (freeme != NULL) {
47857bfa891STrond Myklebust 		nfs_do_return_delegation(inode, freeme, 0);
479ee05f456STrond Myklebust 		nfs_free_delegation(freeme);
480ee05f456STrond Myklebust 	}
4811da177e4SLinus Torvalds 	return status;
4821da177e4SLinus Torvalds }
4831da177e4SLinus Torvalds 
4841da177e4SLinus Torvalds /*
4851da177e4SLinus Torvalds  * Basic procedure for returning a delegation to the server
4861da177e4SLinus Torvalds  */
487d25be546STrond Myklebust static int nfs_end_delegation_return(struct inode *inode, struct nfs_delegation *delegation, int issync)
4881da177e4SLinus Torvalds {
489d25be546STrond Myklebust 	struct nfs_client *clp = NFS_SERVER(inode)->nfs_client;
490869f9dfaSTrond Myklebust 	int err = 0;
4911da177e4SLinus Torvalds 
492d25be546STrond Myklebust 	if (delegation == NULL)
493d25be546STrond Myklebust 		return 0;
494d25be546STrond Myklebust 	do {
495869f9dfaSTrond Myklebust 		if (test_bit(NFS_DELEGATION_REVOKED, &delegation->flags))
496869f9dfaSTrond Myklebust 			break;
49724311f88STrond Myklebust 		err = nfs_delegation_claim_opens(inode, &delegation->stateid,
49824311f88STrond Myklebust 				delegation->type);
499d25be546STrond Myklebust 		if (!issync || err != -EAGAIN)
500d25be546STrond Myklebust 			break;
501d25be546STrond Myklebust 		/*
502d25be546STrond Myklebust 		 * Guard against state recovery
503d25be546STrond Myklebust 		 */
504d25be546STrond Myklebust 		err = nfs4_wait_clnt_recover(clp);
505d25be546STrond Myklebust 	} while (err == 0);
506d25be546STrond Myklebust 
507d25be546STrond Myklebust 	if (err) {
508d25be546STrond Myklebust 		nfs_abort_delegation_return(delegation, clp);
509d25be546STrond Myklebust 		goto out;
510d25be546STrond Myklebust 	}
5111da177e4SLinus Torvalds 
512d18cc1fdSTrond Myklebust 	err = nfs_do_return_delegation(inode, delegation, issync);
513d18cc1fdSTrond Myklebust out:
5148c75593cSTrond Myklebust 	/* Refcount matched in nfs_start_delegation_return_locked() */
5158c75593cSTrond Myklebust 	nfs_put_delegation(delegation);
516d18cc1fdSTrond Myklebust 	return err;
51790163027STrond Myklebust }
51890163027STrond Myklebust 
519b757144fSTrond Myklebust static bool nfs_delegation_need_return(struct nfs_delegation *delegation)
520b757144fSTrond Myklebust {
521b757144fSTrond Myklebust 	bool ret = false;
522b757144fSTrond Myklebust 
523b757144fSTrond Myklebust 	if (test_and_clear_bit(NFS_DELEGATION_RETURN, &delegation->flags))
524b757144fSTrond Myklebust 		ret = true;
5250d104167STrond Myklebust 	else if (test_bit(NFS_DELEGATION_RETURN_IF_CLOSED, &delegation->flags)) {
526b757144fSTrond Myklebust 		struct inode *inode;
527b757144fSTrond Myklebust 
528b757144fSTrond Myklebust 		spin_lock(&delegation->lock);
529b757144fSTrond Myklebust 		inode = delegation->inode;
530b757144fSTrond Myklebust 		if (inode && list_empty(&NFS_I(inode)->open_files))
531b757144fSTrond Myklebust 			ret = true;
532b757144fSTrond Myklebust 		spin_unlock(&delegation->lock);
533b757144fSTrond Myklebust 	}
5340d104167STrond Myklebust 	if (ret)
5350d104167STrond Myklebust 		clear_bit(NFS_DELEGATION_RETURN_IF_CLOSED, &delegation->flags);
536af20b7b8STrond Myklebust 	if (test_bit(NFS_DELEGATION_RETURNING, &delegation->flags) ||
537af20b7b8STrond Myklebust 	    test_bit(NFS_DELEGATION_REVOKED, &delegation->flags))
538af20b7b8STrond Myklebust 		ret = false;
539af20b7b8STrond Myklebust 
540b757144fSTrond Myklebust 	return ret;
541b757144fSTrond Myklebust }
542b757144fSTrond Myklebust 
543d3978bb3SChuck Lever /**
544d3978bb3SChuck Lever  * nfs_client_return_marked_delegations - return previously marked delegations
545d3978bb3SChuck Lever  * @clp: nfs_client to process
546d3978bb3SChuck Lever  *
547dc327ed4STrond Myklebust  * Note that this function is designed to be called by the state
548dc327ed4STrond Myklebust  * manager thread. For this reason, it cannot flush the dirty data,
549dc327ed4STrond Myklebust  * since that could deadlock in case of a state recovery error.
550dc327ed4STrond Myklebust  *
551d3978bb3SChuck Lever  * Returns zero on success, or a negative errno value.
552515d8611STrond Myklebust  */
553d18cc1fdSTrond Myklebust int nfs_client_return_marked_delegations(struct nfs_client *clp)
554515d8611STrond Myklebust {
555515d8611STrond Myklebust 	struct nfs_delegation *delegation;
556e04bbf6bSNeilBrown 	struct nfs_delegation *prev;
557d3978bb3SChuck Lever 	struct nfs_server *server;
558515d8611STrond Myklebust 	struct inode *inode;
559e04bbf6bSNeilBrown 	struct inode *place_holder = NULL;
560e04bbf6bSNeilBrown 	struct nfs_delegation *place_holder_deleg = NULL;
561d18cc1fdSTrond Myklebust 	int err = 0;
562515d8611STrond Myklebust 
563515d8611STrond Myklebust restart:
564e04bbf6bSNeilBrown 	/*
565e04bbf6bSNeilBrown 	 * To avoid quadratic looping we hold a reference
566e04bbf6bSNeilBrown 	 * to an inode place_holder.  Each time we restart, we
567e04bbf6bSNeilBrown 	 * list nfs_servers from the server of that inode, and
568e04bbf6bSNeilBrown 	 * delegation in the server from the delegations of that
569e04bbf6bSNeilBrown 	 * inode.
570e04bbf6bSNeilBrown 	 * prev is an RCU-protected pointer to a delegation which
571e04bbf6bSNeilBrown 	 * wasn't marked for return and might be a good choice for
572e04bbf6bSNeilBrown 	 * the next place_holder.
573e04bbf6bSNeilBrown 	 */
574515d8611STrond Myklebust 	rcu_read_lock();
575e04bbf6bSNeilBrown 	prev = NULL;
576e04bbf6bSNeilBrown 	if (place_holder)
577e04bbf6bSNeilBrown 		server = NFS_SERVER(place_holder);
578e04bbf6bSNeilBrown 	else
579e04bbf6bSNeilBrown 		server = list_entry_rcu(clp->cl_superblocks.next,
580e04bbf6bSNeilBrown 					struct nfs_server, client_link);
581e04bbf6bSNeilBrown 	list_for_each_entry_from_rcu(server, &clp->cl_superblocks, client_link) {
582e04bbf6bSNeilBrown 		delegation = NULL;
583e04bbf6bSNeilBrown 		if (place_holder && server == NFS_SERVER(place_holder))
584e04bbf6bSNeilBrown 			delegation = rcu_dereference(NFS_I(place_holder)->delegation);
585e04bbf6bSNeilBrown 		if (!delegation || delegation != place_holder_deleg)
586e04bbf6bSNeilBrown 			delegation = list_entry_rcu(server->delegations.next,
587e04bbf6bSNeilBrown 						    struct nfs_delegation, super_list);
588e04bbf6bSNeilBrown 		list_for_each_entry_from_rcu(delegation, &server->delegations, super_list) {
589e04bbf6bSNeilBrown 			struct inode *to_put = NULL;
590e04bbf6bSNeilBrown 
591e04bbf6bSNeilBrown 			if (!nfs_delegation_need_return(delegation)) {
592e04bbf6bSNeilBrown 				prev = delegation;
593515d8611STrond Myklebust 				continue;
594e04bbf6bSNeilBrown 			}
5959f0f8e12STrond Myklebust 			if (!nfs_sb_active(server->super))
596f3893491SNeilBrown 				break; /* continue in outer loop */
597e04bbf6bSNeilBrown 
598e04bbf6bSNeilBrown 			if (prev) {
599e04bbf6bSNeilBrown 				struct inode *tmp;
600e04bbf6bSNeilBrown 
601e04bbf6bSNeilBrown 				tmp = nfs_delegation_grab_inode(prev);
602e04bbf6bSNeilBrown 				if (tmp) {
603e04bbf6bSNeilBrown 					to_put = place_holder;
604e04bbf6bSNeilBrown 					place_holder = tmp;
605e04bbf6bSNeilBrown 					place_holder_deleg = prev;
606e04bbf6bSNeilBrown 				}
607e04bbf6bSNeilBrown 			}
608e04bbf6bSNeilBrown 
6099f0f8e12STrond Myklebust 			inode = nfs_delegation_grab_inode(delegation);
6109f0f8e12STrond Myklebust 			if (inode == NULL) {
6119f0f8e12STrond Myklebust 				rcu_read_unlock();
612e04bbf6bSNeilBrown 				if (to_put)
613e04bbf6bSNeilBrown 					iput(to_put);
6149f0f8e12STrond Myklebust 				nfs_sb_deactive(server->super);
6159f0f8e12STrond Myklebust 				goto restart;
6169f0f8e12STrond Myklebust 			}
617d25be546STrond Myklebust 			delegation = nfs_start_delegation_return_locked(NFS_I(inode));
618515d8611STrond Myklebust 			rcu_read_unlock();
619d3978bb3SChuck Lever 
620e04bbf6bSNeilBrown 			if (to_put)
621e04bbf6bSNeilBrown 				iput(to_put);
622e04bbf6bSNeilBrown 
623d25be546STrond Myklebust 			err = nfs_end_delegation_return(inode, delegation, 0);
624515d8611STrond Myklebust 			iput(inode);
6259f0f8e12STrond Myklebust 			nfs_sb_deactive(server->super);
6263ca951b6SNeilBrown 			cond_resched();
627d18cc1fdSTrond Myklebust 			if (!err)
628515d8611STrond Myklebust 				goto restart;
629d18cc1fdSTrond Myklebust 			set_bit(NFS4CLNT_DELEGRETURN, &clp->cl_state);
630e04bbf6bSNeilBrown 			if (place_holder)
631e04bbf6bSNeilBrown 				iput(place_holder);
632d18cc1fdSTrond Myklebust 			return err;
633515d8611STrond Myklebust 		}
634d3978bb3SChuck Lever 	}
635515d8611STrond Myklebust 	rcu_read_unlock();
636e04bbf6bSNeilBrown 	if (place_holder)
637e04bbf6bSNeilBrown 		iput(place_holder);
638d18cc1fdSTrond Myklebust 	return 0;
639515d8611STrond Myklebust }
640515d8611STrond Myklebust 
641d3978bb3SChuck Lever /**
642b47e0e47STrond Myklebust  * nfs_inode_evict_delegation - return delegation, don't reclaim opens
643d3978bb3SChuck Lever  * @inode: inode to process
644d3978bb3SChuck Lever  *
645d3978bb3SChuck Lever  * Does not protect against delegation reclaims, therefore really only safe
646b47e0e47STrond Myklebust  * to be called from nfs4_clear_inode(). Guaranteed to always free
647b47e0e47STrond Myklebust  * the delegation structure.
648e6f81075STrond Myklebust  */
649b47e0e47STrond Myklebust void nfs_inode_evict_delegation(struct inode *inode)
650e6f81075STrond Myklebust {
651e6f81075STrond Myklebust 	struct nfs_delegation *delegation;
652e6f81075STrond Myklebust 
653d25be546STrond Myklebust 	delegation = nfs_inode_detach_delegation(inode);
654b47e0e47STrond Myklebust 	if (delegation != NULL) {
655f885ea64STrond Myklebust 		set_bit(NFS_DELEGATION_RETURNING, &delegation->flags);
656b47e0e47STrond Myklebust 		set_bit(NFS_DELEGATION_INODE_FREEING, &delegation->flags);
6575fcdfaccSTrond Myklebust 		nfs_do_return_delegation(inode, delegation, 1);
658ee05f456STrond Myklebust 		nfs_free_delegation(delegation);
659e6f81075STrond Myklebust 	}
660b47e0e47STrond Myklebust }
661e6f81075STrond Myklebust 
662d3978bb3SChuck Lever /**
663d3978bb3SChuck Lever  * nfs_inode_return_delegation - synchronously return a delegation
664d3978bb3SChuck Lever  * @inode: inode to process
665d3978bb3SChuck Lever  *
666c57d1bc5STrond Myklebust  * This routine will always flush any dirty data to disk on the
667c57d1bc5STrond Myklebust  * assumption that if we need to return the delegation, then
668c57d1bc5STrond Myklebust  * we should stop caching.
669c57d1bc5STrond Myklebust  *
670d3978bb3SChuck Lever  * Returns zero on success, or a negative errno value.
671d3978bb3SChuck Lever  */
67257ec14c5SBryan Schumaker int nfs4_inode_return_delegation(struct inode *inode)
67390163027STrond Myklebust {
67490163027STrond Myklebust 	struct nfs_inode *nfsi = NFS_I(inode);
67590163027STrond Myklebust 	struct nfs_delegation *delegation;
67690163027STrond Myklebust 	int err = 0;
67790163027STrond Myklebust 
678c57d1bc5STrond Myklebust 	nfs_wb_all(inode);
679d25be546STrond Myklebust 	delegation = nfs_start_delegation_return(nfsi);
680d25be546STrond Myklebust 	if (delegation != NULL)
681d25be546STrond Myklebust 		err = nfs_end_delegation_return(inode, delegation, 1);
68290163027STrond Myklebust 	return err;
6831da177e4SLinus Torvalds }
6841da177e4SLinus Torvalds 
685c01d3645STrond Myklebust /**
686b7b7dac6STrond Myklebust  * nfs_inode_return_delegation_on_close - asynchronously return a delegation
687b7b7dac6STrond Myklebust  * @inode: inode to process
688b7b7dac6STrond Myklebust  *
689b7b7dac6STrond Myklebust  * This routine is called on file close in order to determine if the
690b7b7dac6STrond Myklebust  * inode delegation needs to be returned immediately.
691b7b7dac6STrond Myklebust  */
692b7b7dac6STrond Myklebust void nfs4_inode_return_delegation_on_close(struct inode *inode)
693b7b7dac6STrond Myklebust {
694b7b7dac6STrond Myklebust 	struct nfs_delegation *delegation;
695b7b7dac6STrond Myklebust 	struct nfs_delegation *ret = NULL;
696b7b7dac6STrond Myklebust 
697b7b7dac6STrond Myklebust 	if (!inode)
698b7b7dac6STrond Myklebust 		return;
699b7b7dac6STrond Myklebust 	rcu_read_lock();
700b7b7dac6STrond Myklebust 	delegation = nfs4_get_valid_delegation(inode);
701b7b7dac6STrond Myklebust 	if (!delegation)
702b7b7dac6STrond Myklebust 		goto out;
70310717f45STrond Myklebust 	if (test_bit(NFS_DELEGATION_RETURN_IF_CLOSED, &delegation->flags) ||
70410717f45STrond Myklebust 	    atomic_long_read(&nfs_active_delegations) >= nfs_delegation_watermark) {
705b7b7dac6STrond Myklebust 		spin_lock(&delegation->lock);
706b7b7dac6STrond Myklebust 		if (delegation->inode &&
707b7b7dac6STrond Myklebust 		    list_empty(&NFS_I(inode)->open_files) &&
708b7b7dac6STrond Myklebust 		    !test_and_set_bit(NFS_DELEGATION_RETURNING, &delegation->flags)) {
709b7b7dac6STrond Myklebust 			clear_bit(NFS_DELEGATION_RETURN_IF_CLOSED, &delegation->flags);
7108c75593cSTrond Myklebust 			/* Refcount matched in nfs_end_delegation_return() */
7118c75593cSTrond Myklebust 			ret = nfs_get_delegation(delegation);
712b7b7dac6STrond Myklebust 		}
713b7b7dac6STrond Myklebust 		spin_unlock(&delegation->lock);
714efeda80dSTrond Myklebust 		if (ret)
715efeda80dSTrond Myklebust 			nfs_clear_verifier_delegated(inode);
716b7b7dac6STrond Myklebust 	}
717b7b7dac6STrond Myklebust out:
718b7b7dac6STrond Myklebust 	rcu_read_unlock();
719b7b7dac6STrond Myklebust 	nfs_end_delegation_return(inode, ret, 0);
720b7b7dac6STrond Myklebust }
721b7b7dac6STrond Myklebust 
722b7b7dac6STrond Myklebust /**
723c01d3645STrond Myklebust  * nfs4_inode_make_writeable
724c01d3645STrond Myklebust  * @inode: pointer to inode
725c01d3645STrond Myklebust  *
726c01d3645STrond Myklebust  * Make the inode writeable by returning the delegation if necessary
727c01d3645STrond Myklebust  *
728c01d3645STrond Myklebust  * Returns zero on success, or a negative errno value.
729c01d3645STrond Myklebust  */
730c01d3645STrond Myklebust int nfs4_inode_make_writeable(struct inode *inode)
731c01d3645STrond Myklebust {
7323887ce1aSTrond Myklebust 	struct nfs_delegation *delegation;
7333887ce1aSTrond Myklebust 
7343887ce1aSTrond Myklebust 	rcu_read_lock();
7353887ce1aSTrond Myklebust 	delegation = nfs4_get_valid_delegation(inode);
7363887ce1aSTrond Myklebust 	if (delegation == NULL ||
7373887ce1aSTrond Myklebust 	    (nfs4_has_session(NFS_SERVER(inode)->nfs_client) &&
7383887ce1aSTrond Myklebust 	     (delegation->type & FMODE_WRITE))) {
7393887ce1aSTrond Myklebust 		rcu_read_unlock();
740c01d3645STrond Myklebust 		return 0;
741c01d3645STrond Myklebust 	}
7423887ce1aSTrond Myklebust 	rcu_read_unlock();
7433887ce1aSTrond Myklebust 	return nfs4_inode_return_delegation(inode);
7443887ce1aSTrond Myklebust }
745c01d3645STrond Myklebust 
746b757144fSTrond Myklebust static void nfs_mark_return_if_closed_delegation(struct nfs_server *server,
747b757144fSTrond Myklebust 		struct nfs_delegation *delegation)
748b757144fSTrond Myklebust {
749b757144fSTrond Myklebust 	set_bit(NFS_DELEGATION_RETURN_IF_CLOSED, &delegation->flags);
750b757144fSTrond Myklebust 	set_bit(NFS4CLNT_DELEGRETURN, &server->nfs_client->cl_state);
751b757144fSTrond Myklebust }
752b757144fSTrond Myklebust 
753ed1e6211STrond Myklebust static void nfs_mark_return_delegation(struct nfs_server *server,
754ed1e6211STrond Myklebust 		struct nfs_delegation *delegation)
7556411bd4aSTrond Myklebust {
7566411bd4aSTrond Myklebust 	set_bit(NFS_DELEGATION_RETURN, &delegation->flags);
757ed1e6211STrond Myklebust 	set_bit(NFS4CLNT_DELEGRETURN, &server->nfs_client->cl_state);
7586411bd4aSTrond Myklebust }
7596411bd4aSTrond Myklebust 
7605c31e236STrond Myklebust static bool nfs_server_mark_return_all_delegations(struct nfs_server *server)
7615c31e236STrond Myklebust {
7625c31e236STrond Myklebust 	struct nfs_delegation *delegation;
7635c31e236STrond Myklebust 	bool ret = false;
7645c31e236STrond Myklebust 
7655c31e236STrond Myklebust 	list_for_each_entry_rcu(delegation, &server->delegations, super_list) {
7665c31e236STrond Myklebust 		nfs_mark_return_delegation(server, delegation);
7675c31e236STrond Myklebust 		ret = true;
7685c31e236STrond Myklebust 	}
7695c31e236STrond Myklebust 	return ret;
7705c31e236STrond Myklebust }
7715c31e236STrond Myklebust 
772b02ba0b6STrond Myklebust static void nfs_client_mark_return_all_delegations(struct nfs_client *clp)
773b02ba0b6STrond Myklebust {
774b02ba0b6STrond Myklebust 	struct nfs_server *server;
775b02ba0b6STrond Myklebust 
776b02ba0b6STrond Myklebust 	rcu_read_lock();
777b02ba0b6STrond Myklebust 	list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link)
778b02ba0b6STrond Myklebust 		nfs_server_mark_return_all_delegations(server);
779b02ba0b6STrond Myklebust 	rcu_read_unlock();
780b02ba0b6STrond Myklebust }
781b02ba0b6STrond Myklebust 
782b02ba0b6STrond Myklebust static void nfs_delegation_run_state_manager(struct nfs_client *clp)
783b02ba0b6STrond Myklebust {
784b02ba0b6STrond Myklebust 	if (test_bit(NFS4CLNT_DELEGRETURN, &clp->cl_state))
785b02ba0b6STrond Myklebust 		nfs4_schedule_state_manager(clp);
786b02ba0b6STrond Myklebust }
787b02ba0b6STrond Myklebust 
788b02ba0b6STrond Myklebust /**
789b02ba0b6STrond Myklebust  * nfs_expire_all_delegations
790b02ba0b6STrond Myklebust  * @clp: client to process
791b02ba0b6STrond Myklebust  *
792b02ba0b6STrond Myklebust  */
793b02ba0b6STrond Myklebust void nfs_expire_all_delegations(struct nfs_client *clp)
794b02ba0b6STrond Myklebust {
795b02ba0b6STrond Myklebust 	nfs_client_mark_return_all_delegations(clp);
796b02ba0b6STrond Myklebust 	nfs_delegation_run_state_manager(clp);
797b02ba0b6STrond Myklebust }
798b02ba0b6STrond Myklebust 
799d3978bb3SChuck Lever /**
800d3978bb3SChuck Lever  * nfs_super_return_all_delegations - return delegations for one superblock
801302fad7bSTrond Myklebust  * @server: pointer to nfs_server to process
802d3978bb3SChuck Lever  *
8031da177e4SLinus Torvalds  */
804eeebf916SBryan Schumaker void nfs_server_return_all_delegations(struct nfs_server *server)
8051da177e4SLinus Torvalds {
806d3978bb3SChuck Lever 	struct nfs_client *clp = server->nfs_client;
8075c31e236STrond Myklebust 	bool need_wait;
8081da177e4SLinus Torvalds 
8091da177e4SLinus Torvalds 	if (clp == NULL)
8101da177e4SLinus Torvalds 		return;
811d3978bb3SChuck Lever 
8128383e460STrond Myklebust 	rcu_read_lock();
8135c31e236STrond Myklebust 	need_wait = nfs_server_mark_return_all_delegations(server);
8148383e460STrond Myklebust 	rcu_read_unlock();
815d3978bb3SChuck Lever 
8165c31e236STrond Myklebust 	if (need_wait) {
817d18cc1fdSTrond Myklebust 		nfs4_schedule_state_manager(clp);
8185c31e236STrond Myklebust 		nfs4_wait_clnt_recover(clp);
8195c31e236STrond Myklebust 	}
820515d8611STrond Myklebust }
821515d8611STrond Myklebust 
822826e0013STrond Myklebust static void nfs_mark_return_unused_delegation_types(struct nfs_server *server,
823d3978bb3SChuck Lever 						 fmode_t flags)
824515d8611STrond Myklebust {
825515d8611STrond Myklebust 	struct nfs_delegation *delegation;
826515d8611STrond Myklebust 
827d3978bb3SChuck Lever 	list_for_each_entry_rcu(delegation, &server->delegations, super_list) {
828c79571a5SAlexandros Batsakis 		if ((delegation->type == (FMODE_READ|FMODE_WRITE)) && !(flags & FMODE_WRITE))
829c79571a5SAlexandros Batsakis 			continue;
830c79571a5SAlexandros Batsakis 		if (delegation->type & flags)
831826e0013STrond Myklebust 			nfs_mark_return_if_closed_delegation(server, delegation);
832707fb4b3STrond Myklebust 	}
833d3978bb3SChuck Lever }
834d3978bb3SChuck Lever 
835826e0013STrond Myklebust static void nfs_client_mark_return_unused_delegation_types(struct nfs_client *clp,
836d3978bb3SChuck Lever 							fmode_t flags)
837d3978bb3SChuck Lever {
838d3978bb3SChuck Lever 	struct nfs_server *server;
839d3978bb3SChuck Lever 
840d3978bb3SChuck Lever 	rcu_read_lock();
841d3978bb3SChuck Lever 	list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link)
842826e0013STrond Myklebust 		nfs_mark_return_unused_delegation_types(server, flags);
843515d8611STrond Myklebust 	rcu_read_unlock();
8441da177e4SLinus Torvalds }
8451da177e4SLinus Torvalds 
846ee05f456STrond Myklebust static void nfs_revoke_delegation(struct inode *inode,
84741020b67STrond Myklebust 		const nfs4_stateid *stateid)
848869f9dfaSTrond Myklebust {
849869f9dfaSTrond Myklebust 	struct nfs_delegation *delegation;
8507f048831STrond Myklebust 	nfs4_stateid tmp;
85141020b67STrond Myklebust 	bool ret = false;
85241020b67STrond Myklebust 
853869f9dfaSTrond Myklebust 	rcu_read_lock();
854869f9dfaSTrond Myklebust 	delegation = rcu_dereference(NFS_I(inode)->delegation);
85541020b67STrond Myklebust 	if (delegation == NULL)
85641020b67STrond Myklebust 		goto out;
8577f048831STrond Myklebust 	if (stateid == NULL) {
8587f048831STrond Myklebust 		nfs4_stateid_copy(&tmp, &delegation->stateid);
8597f048831STrond Myklebust 		stateid = &tmp;
860f2d47b55STrond Myklebust 	} else {
861f2d47b55STrond Myklebust 		if (!nfs4_stateid_match_other(stateid, &delegation->stateid))
86241020b67STrond Myklebust 			goto out;
863f2d47b55STrond Myklebust 		spin_lock(&delegation->lock);
864f2d47b55STrond Myklebust 		if (stateid->seqid) {
865f2d47b55STrond Myklebust 			if (nfs4_stateid_is_newer(&delegation->stateid, stateid)) {
866f2d47b55STrond Myklebust 				spin_unlock(&delegation->lock);
867f2d47b55STrond Myklebust 				goto out;
868f2d47b55STrond Myklebust 			}
869f2d47b55STrond Myklebust 			delegation->stateid.seqid = stateid->seqid;
870f2d47b55STrond Myklebust 		}
871f2d47b55STrond Myklebust 		spin_unlock(&delegation->lock);
872f2d47b55STrond Myklebust 	}
873d2269ea1STrond Myklebust 	nfs_mark_delegation_revoked(delegation);
87441020b67STrond Myklebust 	ret = true;
87541020b67STrond Myklebust out:
876869f9dfaSTrond Myklebust 	rcu_read_unlock();
8777f048831STrond Myklebust 	if (ret)
8787f048831STrond Myklebust 		nfs_inode_find_state_and_recover(inode, stateid);
879869f9dfaSTrond Myklebust }
880869f9dfaSTrond Myklebust 
88141020b67STrond Myklebust void nfs_remove_bad_delegation(struct inode *inode,
88241020b67STrond Myklebust 		const nfs4_stateid *stateid)
883a1d0b5eeSTrond Myklebust {
884ee05f456STrond Myklebust 	nfs_revoke_delegation(inode, stateid);
885a1d0b5eeSTrond Myklebust }
8869cb81968SAndy Adamson EXPORT_SYMBOL_GPL(nfs_remove_bad_delegation);
887a1d0b5eeSTrond Myklebust 
888d51f91d2STrond Myklebust void nfs_delegation_mark_returned(struct inode *inode,
889d51f91d2STrond Myklebust 		const nfs4_stateid *stateid)
890d51f91d2STrond Myklebust {
891d51f91d2STrond Myklebust 	struct nfs_delegation *delegation;
892d51f91d2STrond Myklebust 
893d51f91d2STrond Myklebust 	if (!inode)
894d51f91d2STrond Myklebust 		return;
895d51f91d2STrond Myklebust 
896d51f91d2STrond Myklebust 	rcu_read_lock();
897d51f91d2STrond Myklebust 	delegation = rcu_dereference(NFS_I(inode)->delegation);
898d51f91d2STrond Myklebust 	if (!delegation)
899d51f91d2STrond Myklebust 		goto out_rcu_unlock;
900d51f91d2STrond Myklebust 
901d51f91d2STrond Myklebust 	spin_lock(&delegation->lock);
902d51f91d2STrond Myklebust 	if (!nfs4_stateid_match_other(stateid, &delegation->stateid))
903d51f91d2STrond Myklebust 		goto out_spin_unlock;
904d51f91d2STrond Myklebust 	if (stateid->seqid) {
905d51f91d2STrond Myklebust 		/* If delegation->stateid is newer, dont mark as returned */
906d51f91d2STrond Myklebust 		if (nfs4_stateid_is_newer(&delegation->stateid, stateid))
907d51f91d2STrond Myklebust 			goto out_clear_returning;
908d51f91d2STrond Myklebust 		if (delegation->stateid.seqid != stateid->seqid)
909d51f91d2STrond Myklebust 			delegation->stateid.seqid = stateid->seqid;
910d51f91d2STrond Myklebust 	}
911d51f91d2STrond Myklebust 
912d2269ea1STrond Myklebust 	nfs_mark_delegation_revoked(delegation);
913d51f91d2STrond Myklebust 
914d51f91d2STrond Myklebust out_clear_returning:
915d51f91d2STrond Myklebust 	clear_bit(NFS_DELEGATION_RETURNING, &delegation->flags);
916d51f91d2STrond Myklebust out_spin_unlock:
917d51f91d2STrond Myklebust 	spin_unlock(&delegation->lock);
918d51f91d2STrond Myklebust out_rcu_unlock:
919d51f91d2STrond Myklebust 	rcu_read_unlock();
920d51f91d2STrond Myklebust 
921d51f91d2STrond Myklebust 	nfs_inode_find_state_and_recover(inode, stateid);
922d51f91d2STrond Myklebust }
923d51f91d2STrond Myklebust 
924d3978bb3SChuck Lever /**
925826e0013STrond Myklebust  * nfs_expire_unused_delegation_types
926d3978bb3SChuck Lever  * @clp: client to process
927d3978bb3SChuck Lever  * @flags: delegation types to expire
928d3978bb3SChuck Lever  *
929d3978bb3SChuck Lever  */
930826e0013STrond Myklebust void nfs_expire_unused_delegation_types(struct nfs_client *clp, fmode_t flags)
931c79571a5SAlexandros Batsakis {
932826e0013STrond Myklebust 	nfs_client_mark_return_unused_delegation_types(clp, flags);
933c79571a5SAlexandros Batsakis 	nfs_delegation_run_state_manager(clp);
934c79571a5SAlexandros Batsakis }
935c79571a5SAlexandros Batsakis 
936d3978bb3SChuck Lever static void nfs_mark_return_unreferenced_delegations(struct nfs_server *server)
937b7391f44STrond Myklebust {
938b7391f44STrond Myklebust 	struct nfs_delegation *delegation;
939b7391f44STrond Myklebust 
940d3978bb3SChuck Lever 	list_for_each_entry_rcu(delegation, &server->delegations, super_list) {
941b7391f44STrond Myklebust 		if (test_and_clear_bit(NFS_DELEGATION_REFERENCED, &delegation->flags))
942b7391f44STrond Myklebust 			continue;
943b757144fSTrond Myklebust 		nfs_mark_return_if_closed_delegation(server, delegation);
944b7391f44STrond Myklebust 	}
945b7391f44STrond Myklebust }
946b7391f44STrond Myklebust 
947d3978bb3SChuck Lever /**
948d3978bb3SChuck Lever  * nfs_expire_unreferenced_delegations - Eliminate unused delegations
949d3978bb3SChuck Lever  * @clp: nfs_client to process
950d3978bb3SChuck Lever  *
951d3978bb3SChuck Lever  */
952b7391f44STrond Myklebust void nfs_expire_unreferenced_delegations(struct nfs_client *clp)
953b7391f44STrond Myklebust {
954d3978bb3SChuck Lever 	struct nfs_server *server;
955d3978bb3SChuck Lever 
956d3978bb3SChuck Lever 	rcu_read_lock();
957d3978bb3SChuck Lever 	list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link)
958d3978bb3SChuck Lever 		nfs_mark_return_unreferenced_delegations(server);
959d3978bb3SChuck Lever 	rcu_read_unlock();
960d3978bb3SChuck Lever 
961b7391f44STrond Myklebust 	nfs_delegation_run_state_manager(clp);
962b7391f44STrond Myklebust }
963b7391f44STrond Myklebust 
964d3978bb3SChuck Lever /**
965d3978bb3SChuck Lever  * nfs_async_inode_return_delegation - asynchronously return a delegation
966d3978bb3SChuck Lever  * @inode: inode to process
9678e663f0eSTrond Myklebust  * @stateid: state ID information
968d3978bb3SChuck Lever  *
969d3978bb3SChuck Lever  * Returns zero on success, or a negative errno value.
9701da177e4SLinus Torvalds  */
971d3978bb3SChuck Lever int nfs_async_inode_return_delegation(struct inode *inode,
972d3978bb3SChuck Lever 				      const nfs4_stateid *stateid)
9731da177e4SLinus Torvalds {
974ed1e6211STrond Myklebust 	struct nfs_server *server = NFS_SERVER(inode);
975ed1e6211STrond Myklebust 	struct nfs_client *clp = server->nfs_client;
9766411bd4aSTrond Myklebust 	struct nfs_delegation *delegation;
9771da177e4SLinus Torvalds 
9786411bd4aSTrond Myklebust 	rcu_read_lock();
979457a5042STrond Myklebust 	delegation = nfs4_get_valid_delegation(inode);
980755a48a7STrond Myklebust 	if (delegation == NULL)
981755a48a7STrond Myklebust 		goto out_enoent;
9824816fdadSTrond Myklebust 	if (stateid != NULL &&
9834816fdadSTrond Myklebust 	    !clp->cl_mvops->match_stateid(&delegation->stateid, stateid))
984755a48a7STrond Myklebust 		goto out_enoent;
985ed1e6211STrond Myklebust 	nfs_mark_return_delegation(server, delegation);
9866411bd4aSTrond Myklebust 	rcu_read_unlock();
987d3978bb3SChuck Lever 
9886411bd4aSTrond Myklebust 	nfs_delegation_run_state_manager(clp);
9896411bd4aSTrond Myklebust 	return 0;
990755a48a7STrond Myklebust out_enoent:
991755a48a7STrond Myklebust 	rcu_read_unlock();
992755a48a7STrond Myklebust 	return -ENOENT;
9931da177e4SLinus Torvalds }
9941da177e4SLinus Torvalds 
995d3978bb3SChuck Lever static struct inode *
996d3978bb3SChuck Lever nfs_delegation_find_inode_server(struct nfs_server *server,
997d3978bb3SChuck Lever 				 const struct nfs_fh *fhandle)
9981da177e4SLinus Torvalds {
9991da177e4SLinus Torvalds 	struct nfs_delegation *delegation;
1000e39d8a18STrond Myklebust 	struct inode *freeme, *res = NULL;
1001d3978bb3SChuck Lever 
1002d3978bb3SChuck Lever 	list_for_each_entry_rcu(delegation, &server->delegations, super_list) {
100386e89489STrond Myklebust 		spin_lock(&delegation->lock);
100486e89489STrond Myklebust 		if (delegation->inode != NULL &&
1005457a5042STrond Myklebust 		    !test_bit(NFS_DELEGATION_REVOKED, &delegation->flags) &&
100686e89489STrond Myklebust 		    nfs_compare_fh(fhandle, &NFS_I(delegation->inode)->fh) == 0) {
1007e39d8a18STrond Myklebust 			freeme = igrab(delegation->inode);
1008e39d8a18STrond Myklebust 			if (freeme && nfs_sb_active(freeme->i_sb))
1009e39d8a18STrond Myklebust 				res = freeme;
101086e89489STrond Myklebust 			spin_unlock(&delegation->lock);
101186e89489STrond Myklebust 			if (res != NULL)
1012d3978bb3SChuck Lever 				return res;
1013e39d8a18STrond Myklebust 			if (freeme) {
1014e39d8a18STrond Myklebust 				rcu_read_unlock();
1015e39d8a18STrond Myklebust 				iput(freeme);
1016e39d8a18STrond Myklebust 				rcu_read_lock();
1017e39d8a18STrond Myklebust 			}
10186c342655STrond Myklebust 			return ERR_PTR(-EAGAIN);
10196c342655STrond Myklebust 		}
10206c342655STrond Myklebust 		spin_unlock(&delegation->lock);
10216c342655STrond Myklebust 	}
10226c342655STrond Myklebust 	return ERR_PTR(-ENOENT);
1023d3978bb3SChuck Lever }
1024d3978bb3SChuck Lever 
1025d3978bb3SChuck Lever /**
1026d3978bb3SChuck Lever  * nfs_delegation_find_inode - retrieve the inode associated with a delegation
1027d3978bb3SChuck Lever  * @clp: client state handle
1028d3978bb3SChuck Lever  * @fhandle: filehandle from a delegation recall
1029d3978bb3SChuck Lever  *
1030d3978bb3SChuck Lever  * Returns pointer to inode matching "fhandle," or NULL if a matching inode
1031d3978bb3SChuck Lever  * cannot be found.
1032d3978bb3SChuck Lever  */
1033d3978bb3SChuck Lever struct inode *nfs_delegation_find_inode(struct nfs_client *clp,
1034d3978bb3SChuck Lever 					const struct nfs_fh *fhandle)
1035d3978bb3SChuck Lever {
1036d3978bb3SChuck Lever 	struct nfs_server *server;
10376c342655STrond Myklebust 	struct inode *res;
1038d3978bb3SChuck Lever 
1039d3978bb3SChuck Lever 	rcu_read_lock();
1040d3978bb3SChuck Lever 	list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link) {
1041d3978bb3SChuck Lever 		res = nfs_delegation_find_inode_server(server, fhandle);
1042d5681f59SAnna Schumaker 		if (res != ERR_PTR(-ENOENT)) {
1043d5681f59SAnna Schumaker 			rcu_read_unlock();
10446c342655STrond Myklebust 			return res;
1045d3978bb3SChuck Lever 		}
1046d5681f59SAnna Schumaker 	}
10478383e460STrond Myklebust 	rcu_read_unlock();
10486c342655STrond Myklebust 	return ERR_PTR(-ENOENT);
10491da177e4SLinus Torvalds }
10501da177e4SLinus Torvalds 
1051d3978bb3SChuck Lever static void nfs_delegation_mark_reclaim_server(struct nfs_server *server)
1052d3978bb3SChuck Lever {
1053d3978bb3SChuck Lever 	struct nfs_delegation *delegation;
1054d3978bb3SChuck Lever 
105545870d69STrond Myklebust 	list_for_each_entry_rcu(delegation, &server->delegations, super_list) {
105645870d69STrond Myklebust 		/*
105745870d69STrond Myklebust 		 * If the delegation may have been admin revoked, then we
105845870d69STrond Myklebust 		 * cannot reclaim it.
105945870d69STrond Myklebust 		 */
106045870d69STrond Myklebust 		if (test_bit(NFS_DELEGATION_TEST_EXPIRED, &delegation->flags))
106145870d69STrond Myklebust 			continue;
1062d3978bb3SChuck Lever 		set_bit(NFS_DELEGATION_NEED_RECLAIM, &delegation->flags);
1063d3978bb3SChuck Lever 	}
106445870d69STrond Myklebust }
1065d3978bb3SChuck Lever 
1066d3978bb3SChuck Lever /**
1067d3978bb3SChuck Lever  * nfs_delegation_mark_reclaim - mark all delegations as needing to be reclaimed
1068d3978bb3SChuck Lever  * @clp: nfs_client to process
1069d3978bb3SChuck Lever  *
10701da177e4SLinus Torvalds  */
1071adfa6f98SDavid Howells void nfs_delegation_mark_reclaim(struct nfs_client *clp)
10721da177e4SLinus Torvalds {
1073d3978bb3SChuck Lever 	struct nfs_server *server;
1074d3978bb3SChuck Lever 
10758383e460STrond Myklebust 	rcu_read_lock();
1076d3978bb3SChuck Lever 	list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link)
1077d3978bb3SChuck Lever 		nfs_delegation_mark_reclaim_server(server);
10788383e460STrond Myklebust 	rcu_read_unlock();
10791da177e4SLinus Torvalds }
10801da177e4SLinus Torvalds 
1081d3978bb3SChuck Lever /**
1082d3978bb3SChuck Lever  * nfs_delegation_reap_unclaimed - reap unclaimed delegations after reboot recovery is done
1083d3978bb3SChuck Lever  * @clp: nfs_client to process
1084d3978bb3SChuck Lever  *
10851da177e4SLinus Torvalds  */
1086adfa6f98SDavid Howells void nfs_delegation_reap_unclaimed(struct nfs_client *clp)
10871da177e4SLinus Torvalds {
10888383e460STrond Myklebust 	struct nfs_delegation *delegation;
1089d3978bb3SChuck Lever 	struct nfs_server *server;
109086e89489STrond Myklebust 	struct inode *inode;
1091d3978bb3SChuck Lever 
10928383e460STrond Myklebust restart:
10938383e460STrond Myklebust 	rcu_read_lock();
1094d3978bb3SChuck Lever 	list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link) {
1095d3978bb3SChuck Lever 		list_for_each_entry_rcu(delegation, &server->delegations,
1096d3978bb3SChuck Lever 								super_list) {
10976f9449beSTrond Myklebust 			if (test_bit(NFS_DELEGATION_INODE_FREEING,
10986f9449beSTrond Myklebust 						&delegation->flags) ||
10996f9449beSTrond Myklebust 			    test_bit(NFS_DELEGATION_RETURNING,
11006f9449beSTrond Myklebust 						&delegation->flags) ||
11016f9449beSTrond Myklebust 			    test_bit(NFS_DELEGATION_NEED_RECLAIM,
1102d3978bb3SChuck Lever 						&delegation->flags) == 0)
11031da177e4SLinus Torvalds 				continue;
11049f0f8e12STrond Myklebust 			if (!nfs_sb_active(server->super))
1105f3893491SNeilBrown 				break; /* continue in outer loop */
11069f0f8e12STrond Myklebust 			inode = nfs_delegation_grab_inode(delegation);
11079f0f8e12STrond Myklebust 			if (inode == NULL) {
11089f0f8e12STrond Myklebust 				rcu_read_unlock();
11099f0f8e12STrond Myklebust 				nfs_sb_deactive(server->super);
11109f0f8e12STrond Myklebust 				goto restart;
11119f0f8e12STrond Myklebust 			}
1112b04b22f4STrond Myklebust 			delegation = nfs_start_delegation_return_locked(NFS_I(inode));
1113b04b22f4STrond Myklebust 			rcu_read_unlock();
1114b04b22f4STrond Myklebust 			if (delegation != NULL) {
11158c75593cSTrond Myklebust 				if (nfs_detach_delegation(NFS_I(inode), delegation,
11168c75593cSTrond Myklebust 							server) != NULL)
1117905f8d16STrond Myklebust 					nfs_free_delegation(delegation);
11188c75593cSTrond Myklebust 				/* Match nfs_start_delegation_return_locked */
11198c75593cSTrond Myklebust 				nfs_put_delegation(delegation);
1120b04b22f4STrond Myklebust 			}
112186e89489STrond Myklebust 			iput(inode);
11229f0f8e12STrond Myklebust 			nfs_sb_deactive(server->super);
11233ca951b6SNeilBrown 			cond_resched();
11248383e460STrond Myklebust 			goto restart;
11251da177e4SLinus Torvalds 		}
1126d3978bb3SChuck Lever 	}
11278383e460STrond Myklebust 	rcu_read_unlock();
11281da177e4SLinus Torvalds }
11293e4f6290STrond Myklebust 
1130bb3d1a3bSTrond Myklebust static inline bool nfs4_server_rebooted(const struct nfs_client *clp)
1131bb3d1a3bSTrond Myklebust {
1132bb3d1a3bSTrond Myklebust 	return (clp->cl_state & (BIT(NFS4CLNT_CHECK_LEASE) |
1133bb3d1a3bSTrond Myklebust 				BIT(NFS4CLNT_LEASE_EXPIRED) |
1134bb3d1a3bSTrond Myklebust 				BIT(NFS4CLNT_SESSION_RESET))) != 0;
1135bb3d1a3bSTrond Myklebust }
1136bb3d1a3bSTrond Myklebust 
113745870d69STrond Myklebust static void nfs_mark_test_expired_delegation(struct nfs_server *server,
113845870d69STrond Myklebust 	    struct nfs_delegation *delegation)
113945870d69STrond Myklebust {
1140059b43e9STrond Myklebust 	if (delegation->stateid.type == NFS4_INVALID_STATEID_TYPE)
1141059b43e9STrond Myklebust 		return;
114245870d69STrond Myklebust 	clear_bit(NFS_DELEGATION_NEED_RECLAIM, &delegation->flags);
114345870d69STrond Myklebust 	set_bit(NFS_DELEGATION_TEST_EXPIRED, &delegation->flags);
114445870d69STrond Myklebust 	set_bit(NFS4CLNT_DELEGATION_EXPIRED, &server->nfs_client->cl_state);
114545870d69STrond Myklebust }
114645870d69STrond Myklebust 
1147bb3d1a3bSTrond Myklebust static void nfs_inode_mark_test_expired_delegation(struct nfs_server *server,
1148bb3d1a3bSTrond Myklebust 		struct inode *inode)
1149bb3d1a3bSTrond Myklebust {
1150bb3d1a3bSTrond Myklebust 	struct nfs_delegation *delegation;
1151bb3d1a3bSTrond Myklebust 
1152bb3d1a3bSTrond Myklebust 	rcu_read_lock();
1153bb3d1a3bSTrond Myklebust 	delegation = rcu_dereference(NFS_I(inode)->delegation);
1154bb3d1a3bSTrond Myklebust 	if (delegation)
1155bb3d1a3bSTrond Myklebust 		nfs_mark_test_expired_delegation(server, delegation);
1156bb3d1a3bSTrond Myklebust 	rcu_read_unlock();
1157bb3d1a3bSTrond Myklebust 
1158bb3d1a3bSTrond Myklebust }
1159bb3d1a3bSTrond Myklebust 
116045870d69STrond Myklebust static void nfs_delegation_mark_test_expired_server(struct nfs_server *server)
116145870d69STrond Myklebust {
116245870d69STrond Myklebust 	struct nfs_delegation *delegation;
116345870d69STrond Myklebust 
116445870d69STrond Myklebust 	list_for_each_entry_rcu(delegation, &server->delegations, super_list)
116545870d69STrond Myklebust 		nfs_mark_test_expired_delegation(server, delegation);
116645870d69STrond Myklebust }
116745870d69STrond Myklebust 
116845870d69STrond Myklebust /**
116945870d69STrond Myklebust  * nfs_mark_test_expired_all_delegations - mark all delegations for testing
117045870d69STrond Myklebust  * @clp: nfs_client to process
117145870d69STrond Myklebust  *
117245870d69STrond Myklebust  * Iterates through all the delegations associated with this server and
117345870d69STrond Myklebust  * marks them as needing to be checked for validity.
117445870d69STrond Myklebust  */
117545870d69STrond Myklebust void nfs_mark_test_expired_all_delegations(struct nfs_client *clp)
117645870d69STrond Myklebust {
117745870d69STrond Myklebust 	struct nfs_server *server;
117845870d69STrond Myklebust 
117945870d69STrond Myklebust 	rcu_read_lock();
118045870d69STrond Myklebust 	list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link)
118145870d69STrond Myklebust 		nfs_delegation_mark_test_expired_server(server);
118245870d69STrond Myklebust 	rcu_read_unlock();
118345870d69STrond Myklebust }
118445870d69STrond Myklebust 
118545870d69STrond Myklebust /**
11868ca017c8SScott Mayhew  * nfs_test_expired_all_delegations - test all delegations for a client
11878ca017c8SScott Mayhew  * @clp: nfs_client to process
11888ca017c8SScott Mayhew  *
11898ca017c8SScott Mayhew  * Helper for handling "recallable state revoked" status from server.
11908ca017c8SScott Mayhew  */
11918ca017c8SScott Mayhew void nfs_test_expired_all_delegations(struct nfs_client *clp)
11928ca017c8SScott Mayhew {
11938ca017c8SScott Mayhew 	nfs_mark_test_expired_all_delegations(clp);
11948ca017c8SScott Mayhew 	nfs4_schedule_state_manager(clp);
11958ca017c8SScott Mayhew }
11968ca017c8SScott Mayhew 
1197ad114089STrond Myklebust static void
1198ad114089STrond Myklebust nfs_delegation_test_free_expired(struct inode *inode,
1199ad114089STrond Myklebust 		nfs4_stateid *stateid,
1200ad114089STrond Myklebust 		const struct cred *cred)
1201ad114089STrond Myklebust {
1202ad114089STrond Myklebust 	struct nfs_server *server = NFS_SERVER(inode);
1203ad114089STrond Myklebust 	const struct nfs4_minor_version_ops *ops = server->nfs_client->cl_mvops;
1204ad114089STrond Myklebust 	int status;
1205ad114089STrond Myklebust 
1206ad114089STrond Myklebust 	if (!cred)
1207ad114089STrond Myklebust 		return;
1208ad114089STrond Myklebust 	status = ops->test_and_free_expired(server, stateid, cred);
1209ad114089STrond Myklebust 	if (status == -NFS4ERR_EXPIRED || status == -NFS4ERR_BAD_STATEID)
1210ad114089STrond Myklebust 		nfs_remove_bad_delegation(inode, stateid);
1211ad114089STrond Myklebust }
1212ad114089STrond Myklebust 
12138ca017c8SScott Mayhew /**
121445870d69STrond Myklebust  * nfs_reap_expired_delegations - reap expired delegations
121545870d69STrond Myklebust  * @clp: nfs_client to process
121645870d69STrond Myklebust  *
121745870d69STrond Myklebust  * Iterates through all the delegations associated with this server and
121845870d69STrond Myklebust  * checks if they have may have been revoked. This function is usually
121945870d69STrond Myklebust  * expected to be called in cases where the server may have lost its
122045870d69STrond Myklebust  * lease.
122145870d69STrond Myklebust  */
122245870d69STrond Myklebust void nfs_reap_expired_delegations(struct nfs_client *clp)
122345870d69STrond Myklebust {
122445870d69STrond Myklebust 	struct nfs_delegation *delegation;
122545870d69STrond Myklebust 	struct nfs_server *server;
122645870d69STrond Myklebust 	struct inode *inode;
1227a52458b4SNeilBrown 	const struct cred *cred;
122845870d69STrond Myklebust 	nfs4_stateid stateid;
122945870d69STrond Myklebust 
123045870d69STrond Myklebust restart:
123145870d69STrond Myklebust 	rcu_read_lock();
123245870d69STrond Myklebust 	list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link) {
123345870d69STrond Myklebust 		list_for_each_entry_rcu(delegation, &server->delegations,
123445870d69STrond Myklebust 								super_list) {
12356f9449beSTrond Myklebust 			if (test_bit(NFS_DELEGATION_INODE_FREEING,
12366f9449beSTrond Myklebust 						&delegation->flags) ||
12376f9449beSTrond Myklebust 			    test_bit(NFS_DELEGATION_RETURNING,
12386f9449beSTrond Myklebust 						&delegation->flags) ||
12396f9449beSTrond Myklebust 			    test_bit(NFS_DELEGATION_TEST_EXPIRED,
124045870d69STrond Myklebust 						&delegation->flags) == 0)
124145870d69STrond Myklebust 				continue;
124245870d69STrond Myklebust 			if (!nfs_sb_active(server->super))
1243f3893491SNeilBrown 				break; /* continue in outer loop */
124445870d69STrond Myklebust 			inode = nfs_delegation_grab_inode(delegation);
124545870d69STrond Myklebust 			if (inode == NULL) {
124645870d69STrond Myklebust 				rcu_read_unlock();
124745870d69STrond Myklebust 				nfs_sb_deactive(server->super);
124845870d69STrond Myklebust 				goto restart;
124945870d69STrond Myklebust 			}
1250a52458b4SNeilBrown 			cred = get_cred_rcu(delegation->cred);
125145870d69STrond Myklebust 			nfs4_stateid_copy(&stateid, &delegation->stateid);
125245870d69STrond Myklebust 			clear_bit(NFS_DELEGATION_TEST_EXPIRED, &delegation->flags);
125345870d69STrond Myklebust 			rcu_read_unlock();
1254ad114089STrond Myklebust 			nfs_delegation_test_free_expired(inode, &stateid, cred);
1255a52458b4SNeilBrown 			put_cred(cred);
1256bb3d1a3bSTrond Myklebust 			if (nfs4_server_rebooted(clp)) {
1257bb3d1a3bSTrond Myklebust 				nfs_inode_mark_test_expired_delegation(server,inode);
1258bb3d1a3bSTrond Myklebust 				iput(inode);
1259bb3d1a3bSTrond Myklebust 				nfs_sb_deactive(server->super);
1260bb3d1a3bSTrond Myklebust 				return;
1261bb3d1a3bSTrond Myklebust 			}
126245870d69STrond Myklebust 			iput(inode);
126345870d69STrond Myklebust 			nfs_sb_deactive(server->super);
12643ca951b6SNeilBrown 			cond_resched();
126545870d69STrond Myklebust 			goto restart;
126645870d69STrond Myklebust 		}
126745870d69STrond Myklebust 	}
126845870d69STrond Myklebust 	rcu_read_unlock();
126945870d69STrond Myklebust }
127045870d69STrond Myklebust 
12716c2d8f8dSTrond Myklebust void nfs_inode_find_delegation_state_and_recover(struct inode *inode,
12726c2d8f8dSTrond Myklebust 		const nfs4_stateid *stateid)
12736c2d8f8dSTrond Myklebust {
12746c2d8f8dSTrond Myklebust 	struct nfs_client *clp = NFS_SERVER(inode)->nfs_client;
12756c2d8f8dSTrond Myklebust 	struct nfs_delegation *delegation;
12766c2d8f8dSTrond Myklebust 	bool found = false;
12776c2d8f8dSTrond Myklebust 
12786c2d8f8dSTrond Myklebust 	rcu_read_lock();
12796c2d8f8dSTrond Myklebust 	delegation = rcu_dereference(NFS_I(inode)->delegation);
12806c2d8f8dSTrond Myklebust 	if (delegation &&
128142c304c3STrond Myklebust 	    nfs4_stateid_match_or_older(&delegation->stateid, stateid) &&
128242c304c3STrond Myklebust 	    !test_bit(NFS_DELEGATION_REVOKED, &delegation->flags)) {
12836c2d8f8dSTrond Myklebust 		nfs_mark_test_expired_delegation(NFS_SERVER(inode), delegation);
12846c2d8f8dSTrond Myklebust 		found = true;
12856c2d8f8dSTrond Myklebust 	}
12866c2d8f8dSTrond Myklebust 	rcu_read_unlock();
12876c2d8f8dSTrond Myklebust 	if (found)
12886c2d8f8dSTrond Myklebust 		nfs4_schedule_state_manager(clp);
12896c2d8f8dSTrond Myklebust }
12906c2d8f8dSTrond Myklebust 
1291d3978bb3SChuck Lever /**
1292d3978bb3SChuck Lever  * nfs_delegations_present - check for existence of delegations
1293d3978bb3SChuck Lever  * @clp: client state handle
1294d3978bb3SChuck Lever  *
1295d3978bb3SChuck Lever  * Returns one if there are any nfs_delegation structures attached
1296d3978bb3SChuck Lever  * to this nfs_client.
1297d3978bb3SChuck Lever  */
1298d3978bb3SChuck Lever int nfs_delegations_present(struct nfs_client *clp)
1299d3978bb3SChuck Lever {
1300d3978bb3SChuck Lever 	struct nfs_server *server;
1301d3978bb3SChuck Lever 	int ret = 0;
1302d3978bb3SChuck Lever 
1303d3978bb3SChuck Lever 	rcu_read_lock();
1304d3978bb3SChuck Lever 	list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link)
1305d3978bb3SChuck Lever 		if (!list_empty(&server->delegations)) {
1306d3978bb3SChuck Lever 			ret = 1;
1307d3978bb3SChuck Lever 			break;
1308d3978bb3SChuck Lever 		}
1309d3978bb3SChuck Lever 	rcu_read_unlock();
1310d3978bb3SChuck Lever 	return ret;
1311d3978bb3SChuck Lever }
1312d3978bb3SChuck Lever 
1313d3978bb3SChuck Lever /**
131412f275cdSTrond Myklebust  * nfs4_refresh_delegation_stateid - Update delegation stateid seqid
131512f275cdSTrond Myklebust  * @dst: stateid to refresh
131612f275cdSTrond Myklebust  * @inode: inode to check
131712f275cdSTrond Myklebust  *
131812f275cdSTrond Myklebust  * Returns "true" and updates "dst->seqid" * if inode had a delegation
131912f275cdSTrond Myklebust  * that matches our delegation stateid. Otherwise "false" is returned.
132012f275cdSTrond Myklebust  */
132112f275cdSTrond Myklebust bool nfs4_refresh_delegation_stateid(nfs4_stateid *dst, struct inode *inode)
132212f275cdSTrond Myklebust {
132312f275cdSTrond Myklebust 	struct nfs_delegation *delegation;
132412f275cdSTrond Myklebust 	bool ret = false;
132512f275cdSTrond Myklebust 	if (!inode)
132612f275cdSTrond Myklebust 		goto out;
132712f275cdSTrond Myklebust 
132812f275cdSTrond Myklebust 	rcu_read_lock();
132912f275cdSTrond Myklebust 	delegation = rcu_dereference(NFS_I(inode)->delegation);
133012f275cdSTrond Myklebust 	if (delegation != NULL &&
1331b5756208STrond Myklebust 	    nfs4_stateid_match_other(dst, &delegation->stateid) &&
1332246afc0aSTrond Myklebust 	    nfs4_stateid_is_newer(&delegation->stateid, dst) &&
1333b5756208STrond Myklebust 	    !test_bit(NFS_DELEGATION_REVOKED, &delegation->flags)) {
133412f275cdSTrond Myklebust 		dst->seqid = delegation->stateid.seqid;
133579cc5542STrond Myklebust 		ret = true;
133612f275cdSTrond Myklebust 	}
133712f275cdSTrond Myklebust 	rcu_read_unlock();
133812f275cdSTrond Myklebust out:
133912f275cdSTrond Myklebust 	return ret;
134012f275cdSTrond Myklebust }
134112f275cdSTrond Myklebust 
134212f275cdSTrond Myklebust /**
1343d3978bb3SChuck Lever  * nfs4_copy_delegation_stateid - Copy inode's state ID information
1344d3978bb3SChuck Lever  * @inode: inode to check
13450032a7a7STrond Myklebust  * @flags: delegation type requirement
1346abf4e13cSTrond Myklebust  * @dst: stateid data structure to fill in
1347abf4e13cSTrond Myklebust  * @cred: optional argument to retrieve credential
1348d3978bb3SChuck Lever  *
13490032a7a7STrond Myklebust  * Returns "true" and fills in "dst->data" * if inode had a delegation,
13500032a7a7STrond Myklebust  * otherwise "false" is returned.
1351d3978bb3SChuck Lever  */
1352abf4e13cSTrond Myklebust bool nfs4_copy_delegation_stateid(struct inode *inode, fmode_t flags,
1353a52458b4SNeilBrown 		nfs4_stateid *dst, const struct cred **cred)
13543e4f6290STrond Myklebust {
13553e4f6290STrond Myklebust 	struct nfs_inode *nfsi = NFS_I(inode);
13563e4f6290STrond Myklebust 	struct nfs_delegation *delegation;
13570032a7a7STrond Myklebust 	bool ret;
13583e4f6290STrond Myklebust 
13590032a7a7STrond Myklebust 	flags &= FMODE_READ|FMODE_WRITE;
13608383e460STrond Myklebust 	rcu_read_lock();
13618383e460STrond Myklebust 	delegation = rcu_dereference(nfsi->delegation);
1362aa05c87fSTrond Myklebust 	ret = nfs4_is_valid_delegation(delegation, flags);
13630032a7a7STrond Myklebust 	if (ret) {
1364f597c537STrond Myklebust 		nfs4_stateid_copy(dst, &delegation->stateid);
13650032a7a7STrond Myklebust 		nfs_mark_delegation_referenced(delegation);
1366abf4e13cSTrond Myklebust 		if (cred)
1367a52458b4SNeilBrown 			*cred = get_cred(delegation->cred);
13683e4f6290STrond Myklebust 	}
13698383e460STrond Myklebust 	rcu_read_unlock();
13708383e460STrond Myklebust 	return ret;
13713e4f6290STrond Myklebust }
13725445b1fbSTrond Myklebust 
13735445b1fbSTrond Myklebust /**
13745445b1fbSTrond Myklebust  * nfs4_delegation_flush_on_close - Check if we must flush file on close
13755445b1fbSTrond Myklebust  * @inode: inode to check
13765445b1fbSTrond Myklebust  *
13775445b1fbSTrond Myklebust  * This function checks the number of outstanding writes to the file
13785445b1fbSTrond Myklebust  * against the delegation 'space_limit' field to see if
13795445b1fbSTrond Myklebust  * the spec requires us to flush the file on close.
13805445b1fbSTrond Myklebust  */
13815445b1fbSTrond Myklebust bool nfs4_delegation_flush_on_close(const struct inode *inode)
13825445b1fbSTrond Myklebust {
13835445b1fbSTrond Myklebust 	struct nfs_inode *nfsi = NFS_I(inode);
13845445b1fbSTrond Myklebust 	struct nfs_delegation *delegation;
13855445b1fbSTrond Myklebust 	bool ret = true;
13865445b1fbSTrond Myklebust 
13875445b1fbSTrond Myklebust 	rcu_read_lock();
13885445b1fbSTrond Myklebust 	delegation = rcu_dereference(nfsi->delegation);
13895445b1fbSTrond Myklebust 	if (delegation == NULL || !(delegation->type & FMODE_WRITE))
13905445b1fbSTrond Myklebust 		goto out;
1391a6b6d5b8STrond Myklebust 	if (atomic_long_read(&nfsi->nrequests) < delegation->pagemod_limit)
13925445b1fbSTrond Myklebust 		ret = false;
13935445b1fbSTrond Myklebust out:
13945445b1fbSTrond Myklebust 	rcu_read_unlock();
13955445b1fbSTrond Myklebust 	return ret;
13965445b1fbSTrond Myklebust }
139710717f45STrond Myklebust 
139810717f45STrond Myklebust module_param_named(delegation_watermark, nfs_delegation_watermark, uint, 0644);
1399