xref: /openbmc/linux/fs/nfs/delegation.c (revision 36281caa)
11da177e4SLinus Torvalds /*
21da177e4SLinus Torvalds  * linux/fs/nfs/delegation.c
31da177e4SLinus Torvalds  *
41da177e4SLinus Torvalds  * Copyright (C) 2004 Trond Myklebust
51da177e4SLinus Torvalds  *
61da177e4SLinus Torvalds  * NFS file delegation management
71da177e4SLinus Torvalds  *
81da177e4SLinus Torvalds  */
91da177e4SLinus Torvalds #include <linux/completion.h>
1058d9714aSTrond Myklebust #include <linux/kthread.h>
111da177e4SLinus Torvalds #include <linux/module.h>
121da177e4SLinus Torvalds #include <linux/sched.h>
135a0e3ad6STejun Heo #include <linux/slab.h>
141da177e4SLinus Torvalds #include <linux/spinlock.h>
151da177e4SLinus Torvalds 
161da177e4SLinus Torvalds #include <linux/nfs4.h>
171da177e4SLinus Torvalds #include <linux/nfs_fs.h>
181da177e4SLinus Torvalds #include <linux/nfs_xdr.h>
191da177e4SLinus Torvalds 
204ce79717STrond Myklebust #include "nfs4_fs.h"
211da177e4SLinus Torvalds #include "delegation.h"
2224c8dbbbSDavid Howells #include "internal.h"
231da177e4SLinus Torvalds 
24905f8d16STrond Myklebust static void nfs_free_delegation(struct nfs_delegation *delegation)
25905f8d16STrond Myklebust {
26e00b8a24STrond Myklebust 	if (delegation->cred) {
27e00b8a24STrond Myklebust 		put_rpccred(delegation->cred);
28e00b8a24STrond Myklebust 		delegation->cred = NULL;
29e00b8a24STrond Myklebust 	}
3026f04ddeSLai Jiangshan 	kfree_rcu(delegation, rcu);
318383e460STrond Myklebust }
328383e460STrond Myklebust 
33d3978bb3SChuck Lever /**
34d3978bb3SChuck Lever  * nfs_mark_delegation_referenced - set delegation's REFERENCED flag
35d3978bb3SChuck Lever  * @delegation: delegation to process
36d3978bb3SChuck Lever  *
37d3978bb3SChuck Lever  */
38b7391f44STrond Myklebust void nfs_mark_delegation_referenced(struct nfs_delegation *delegation)
39b7391f44STrond Myklebust {
40b7391f44STrond Myklebust 	set_bit(NFS_DELEGATION_REFERENCED, &delegation->flags);
41b7391f44STrond Myklebust }
42b7391f44STrond Myklebust 
43d3978bb3SChuck Lever /**
44d3978bb3SChuck Lever  * nfs_have_delegation - check if inode has a delegation
45d3978bb3SChuck Lever  * @inode: inode to check
46d3978bb3SChuck Lever  * @flags: delegation types to check for
47d3978bb3SChuck Lever  *
48d3978bb3SChuck Lever  * Returns one if inode has the indicated delegation, otherwise zero.
49d3978bb3SChuck Lever  */
50bd7bf9d5STrond Myklebust int nfs_have_delegation(struct inode *inode, fmode_t flags)
51b7391f44STrond Myklebust {
52b7391f44STrond Myklebust 	struct nfs_delegation *delegation;
53b7391f44STrond Myklebust 	int ret = 0;
54b7391f44STrond Myklebust 
55b7391f44STrond Myklebust 	flags &= FMODE_READ|FMODE_WRITE;
56b7391f44STrond Myklebust 	rcu_read_lock();
57b7391f44STrond Myklebust 	delegation = rcu_dereference(NFS_I(inode)->delegation);
58b7391f44STrond Myklebust 	if (delegation != NULL && (delegation->type & flags) == flags) {
59b7391f44STrond Myklebust 		nfs_mark_delegation_referenced(delegation);
60b7391f44STrond Myklebust 		ret = 1;
61b7391f44STrond Myklebust 	}
62b7391f44STrond Myklebust 	rcu_read_unlock();
63b7391f44STrond Myklebust 	return ret;
64b7391f44STrond Myklebust }
65b7391f44STrond Myklebust 
66888e694cSTrond Myklebust static int nfs_delegation_claim_locks(struct nfs_open_context *ctx, struct nfs4_state *state)
67888e694cSTrond Myklebust {
68888e694cSTrond Myklebust 	struct inode *inode = state->inode;
69888e694cSTrond Myklebust 	struct file_lock *fl;
70d5122201STrond Myklebust 	int status = 0;
71888e694cSTrond Myklebust 
723f09df70STrond Myklebust 	if (inode->i_flock == NULL)
733f09df70STrond Myklebust 		goto out;
743f09df70STrond Myklebust 
75b89f4321SArnd Bergmann 	/* Protect inode->i_flock using the file locks lock */
76b89f4321SArnd Bergmann 	lock_flocks();
7790dc7d27SHarvey Harrison 	for (fl = inode->i_flock; fl != NULL; fl = fl->fl_next) {
78888e694cSTrond Myklebust 		if (!(fl->fl_flags & (FL_POSIX|FL_FLOCK)))
79888e694cSTrond Myklebust 			continue;
80cd3758e3STrond Myklebust 		if (nfs_file_open_context(fl->fl_file) != ctx)
81888e694cSTrond Myklebust 			continue;
82b89f4321SArnd Bergmann 		unlock_flocks();
83888e694cSTrond Myklebust 		status = nfs4_lock_delegation_recall(state, fl);
84d5122201STrond Myklebust 		if (status < 0)
853f09df70STrond Myklebust 			goto out;
86b89f4321SArnd Bergmann 		lock_flocks();
87888e694cSTrond Myklebust 	}
88b89f4321SArnd Bergmann 	unlock_flocks();
893f09df70STrond Myklebust out:
90888e694cSTrond Myklebust 	return status;
91888e694cSTrond Myklebust }
92888e694cSTrond Myklebust 
93d18cc1fdSTrond Myklebust static int nfs_delegation_claim_opens(struct inode *inode, const nfs4_stateid *stateid)
941da177e4SLinus Torvalds {
951da177e4SLinus Torvalds 	struct nfs_inode *nfsi = NFS_I(inode);
961da177e4SLinus Torvalds 	struct nfs_open_context *ctx;
971da177e4SLinus Torvalds 	struct nfs4_state *state;
98888e694cSTrond Myklebust 	int err;
991da177e4SLinus Torvalds 
1001da177e4SLinus Torvalds again:
1011da177e4SLinus Torvalds 	spin_lock(&inode->i_lock);
1021da177e4SLinus Torvalds 	list_for_each_entry(ctx, &nfsi->open_files, list) {
1031da177e4SLinus Torvalds 		state = ctx->state;
1041da177e4SLinus Torvalds 		if (state == NULL)
1051da177e4SLinus Torvalds 			continue;
1061da177e4SLinus Torvalds 		if (!test_bit(NFS_DELEGATED_STATE, &state->flags))
1071da177e4SLinus Torvalds 			continue;
10890163027STrond Myklebust 		if (memcmp(state->stateid.data, stateid->data, sizeof(state->stateid.data)) != 0)
10990163027STrond Myklebust 			continue;
1101da177e4SLinus Torvalds 		get_nfs_open_context(ctx);
1111da177e4SLinus Torvalds 		spin_unlock(&inode->i_lock);
11213437e12STrond Myklebust 		err = nfs4_open_delegation_recall(ctx, state, stateid);
113888e694cSTrond Myklebust 		if (err >= 0)
114888e694cSTrond Myklebust 			err = nfs_delegation_claim_locks(ctx, state);
1151da177e4SLinus Torvalds 		put_nfs_open_context(ctx);
116888e694cSTrond Myklebust 		if (err != 0)
117d18cc1fdSTrond Myklebust 			return err;
1181da177e4SLinus Torvalds 		goto again;
1191da177e4SLinus Torvalds 	}
1201da177e4SLinus Torvalds 	spin_unlock(&inode->i_lock);
121d18cc1fdSTrond Myklebust 	return 0;
1221da177e4SLinus Torvalds }
1231da177e4SLinus Torvalds 
124d3978bb3SChuck Lever /**
125d3978bb3SChuck Lever  * nfs_inode_reclaim_delegation - process a delegation reclaim request
126d3978bb3SChuck Lever  * @inode: inode to process
127d3978bb3SChuck Lever  * @cred: credential to use for request
128d3978bb3SChuck Lever  * @res: new delegation state from server
129d3978bb3SChuck Lever  *
1301da177e4SLinus Torvalds  */
131d3978bb3SChuck Lever void nfs_inode_reclaim_delegation(struct inode *inode, struct rpc_cred *cred,
132d3978bb3SChuck Lever 				  struct nfs_openres *res)
1331da177e4SLinus Torvalds {
1348f649c37STrond Myklebust 	struct nfs_delegation *delegation;
1358f649c37STrond Myklebust 	struct rpc_cred *oldcred = NULL;
1361da177e4SLinus Torvalds 
1378f649c37STrond Myklebust 	rcu_read_lock();
1388f649c37STrond Myklebust 	delegation = rcu_dereference(NFS_I(inode)->delegation);
1398f649c37STrond Myklebust 	if (delegation != NULL) {
1408f649c37STrond Myklebust 		spin_lock(&delegation->lock);
1418f649c37STrond Myklebust 		if (delegation->inode != NULL) {
1421da177e4SLinus Torvalds 			memcpy(delegation->stateid.data, res->delegation.data,
1431da177e4SLinus Torvalds 			       sizeof(delegation->stateid.data));
1441da177e4SLinus Torvalds 			delegation->type = res->delegation_type;
1451da177e4SLinus Torvalds 			delegation->maxsize = res->maxsize;
14605c88babSTrond Myklebust 			oldcred = delegation->cred;
1471da177e4SLinus Torvalds 			delegation->cred = get_rpccred(cred);
1488f649c37STrond Myklebust 			clear_bit(NFS_DELEGATION_NEED_RECLAIM,
1498f649c37STrond Myklebust 				  &delegation->flags);
1501da177e4SLinus Torvalds 			NFS_I(inode)->delegation_state = delegation->type;
1518f649c37STrond Myklebust 			spin_unlock(&delegation->lock);
15205c88babSTrond Myklebust 			put_rpccred(oldcred);
1538f649c37STrond Myklebust 			rcu_read_unlock();
1548f649c37STrond Myklebust 		} else {
1558f649c37STrond Myklebust 			/* We appear to have raced with a delegation return. */
1568f649c37STrond Myklebust 			spin_unlock(&delegation->lock);
1578f649c37STrond Myklebust 			rcu_read_unlock();
1588f649c37STrond Myklebust 			nfs_inode_set_delegation(inode, cred, res);
1598f649c37STrond Myklebust 		}
1608f649c37STrond Myklebust 	} else {
1618f649c37STrond Myklebust 		rcu_read_unlock();
1628f649c37STrond Myklebust 	}
1631da177e4SLinus Torvalds }
1641da177e4SLinus Torvalds 
16557bfa891STrond Myklebust static int nfs_do_return_delegation(struct inode *inode, struct nfs_delegation *delegation, int issync)
16657bfa891STrond Myklebust {
16757bfa891STrond Myklebust 	int res = 0;
16857bfa891STrond Myklebust 
16957bfa891STrond Myklebust 	res = nfs4_proc_delegreturn(inode, delegation->cred, &delegation->stateid, issync);
17057bfa891STrond Myklebust 	nfs_free_delegation(delegation);
17157bfa891STrond Myklebust 	return res;
17257bfa891STrond Myklebust }
17357bfa891STrond Myklebust 
17486e89489STrond Myklebust static struct inode *nfs_delegation_grab_inode(struct nfs_delegation *delegation)
17586e89489STrond Myklebust {
17686e89489STrond Myklebust 	struct inode *inode = NULL;
17786e89489STrond Myklebust 
17886e89489STrond Myklebust 	spin_lock(&delegation->lock);
17986e89489STrond Myklebust 	if (delegation->inode != NULL)
18086e89489STrond Myklebust 		inode = igrab(delegation->inode);
18186e89489STrond Myklebust 	spin_unlock(&delegation->lock);
18286e89489STrond Myklebust 	return inode;
18386e89489STrond Myklebust }
18486e89489STrond Myklebust 
185dda4b225SChuck Lever static struct nfs_delegation *
186dda4b225SChuck Lever nfs_detach_delegation_locked(struct nfs_inode *nfsi,
187d3978bb3SChuck Lever 			     struct nfs_server *server)
18857bfa891STrond Myklebust {
18917d2c0a0SDavid Howells 	struct nfs_delegation *delegation =
19017d2c0a0SDavid Howells 		rcu_dereference_protected(nfsi->delegation,
191d3978bb3SChuck Lever 				lockdep_is_held(&server->nfs_client->cl_lock));
19257bfa891STrond Myklebust 
19357bfa891STrond Myklebust 	if (delegation == NULL)
19457bfa891STrond Myklebust 		goto nomatch;
195dda4b225SChuck Lever 
19634310430STrond Myklebust 	spin_lock(&delegation->lock);
19757bfa891STrond Myklebust 	list_del_rcu(&delegation->super_list);
19886e89489STrond Myklebust 	delegation->inode = NULL;
19957bfa891STrond Myklebust 	nfsi->delegation_state = 0;
20057bfa891STrond Myklebust 	rcu_assign_pointer(nfsi->delegation, NULL);
20134310430STrond Myklebust 	spin_unlock(&delegation->lock);
20257bfa891STrond Myklebust 	return delegation;
20357bfa891STrond Myklebust nomatch:
20457bfa891STrond Myklebust 	return NULL;
20557bfa891STrond Myklebust }
20657bfa891STrond Myklebust 
207dda4b225SChuck Lever static struct nfs_delegation *nfs_detach_delegation(struct nfs_inode *nfsi,
208d3978bb3SChuck Lever 						    struct nfs_server *server)
209dda4b225SChuck Lever {
210d3978bb3SChuck Lever 	struct nfs_client *clp = server->nfs_client;
211dda4b225SChuck Lever 	struct nfs_delegation *delegation;
212dda4b225SChuck Lever 
213dda4b225SChuck Lever 	spin_lock(&clp->cl_lock);
214d3978bb3SChuck Lever 	delegation = nfs_detach_delegation_locked(nfsi, server);
215dda4b225SChuck Lever 	spin_unlock(&clp->cl_lock);
216dda4b225SChuck Lever 	return delegation;
217dda4b225SChuck Lever }
218dda4b225SChuck Lever 
219d3978bb3SChuck Lever /**
220d3978bb3SChuck Lever  * nfs_inode_set_delegation - set up a delegation on an inode
221d3978bb3SChuck Lever  * @inode: inode to which delegation applies
222d3978bb3SChuck Lever  * @cred: cred to use for subsequent delegation processing
223d3978bb3SChuck Lever  * @res: new delegation state from server
224d3978bb3SChuck Lever  *
225d3978bb3SChuck Lever  * Returns zero on success, or a negative errno value.
2261da177e4SLinus Torvalds  */
2271da177e4SLinus Torvalds int nfs_inode_set_delegation(struct inode *inode, struct rpc_cred *cred, struct nfs_openres *res)
2281da177e4SLinus Torvalds {
229d3978bb3SChuck Lever 	struct nfs_server *server = NFS_SERVER(inode);
230d3978bb3SChuck Lever 	struct nfs_client *clp = server->nfs_client;
2311da177e4SLinus Torvalds 	struct nfs_inode *nfsi = NFS_I(inode);
23217d2c0a0SDavid Howells 	struct nfs_delegation *delegation, *old_delegation;
23357bfa891STrond Myklebust 	struct nfs_delegation *freeme = NULL;
2341da177e4SLinus Torvalds 	int status = 0;
2351da177e4SLinus Torvalds 
2368535b2beSTrond Myklebust 	delegation = kmalloc(sizeof(*delegation), GFP_NOFS);
2371da177e4SLinus Torvalds 	if (delegation == NULL)
2381da177e4SLinus Torvalds 		return -ENOMEM;
2391da177e4SLinus Torvalds 	memcpy(delegation->stateid.data, res->delegation.data,
2401da177e4SLinus Torvalds 			sizeof(delegation->stateid.data));
2411da177e4SLinus Torvalds 	delegation->type = res->delegation_type;
2421da177e4SLinus Torvalds 	delegation->maxsize = res->maxsize;
243a9a4a87aSTrond Myklebust 	delegation->change_attr = inode->i_version;
2441da177e4SLinus Torvalds 	delegation->cred = get_rpccred(cred);
2451da177e4SLinus Torvalds 	delegation->inode = inode;
246b7391f44STrond Myklebust 	delegation->flags = 1<<NFS_DELEGATION_REFERENCED;
24734310430STrond Myklebust 	spin_lock_init(&delegation->lock);
2481da177e4SLinus Torvalds 
2491da177e4SLinus Torvalds 	spin_lock(&clp->cl_lock);
25017d2c0a0SDavid Howells 	old_delegation = rcu_dereference_protected(nfsi->delegation,
25117d2c0a0SDavid Howells 					lockdep_is_held(&clp->cl_lock));
25217d2c0a0SDavid Howells 	if (old_delegation != NULL) {
25317d2c0a0SDavid Howells 		if (memcmp(&delegation->stateid, &old_delegation->stateid,
25417d2c0a0SDavid Howells 					sizeof(old_delegation->stateid)) == 0 &&
25517d2c0a0SDavid Howells 				delegation->type == old_delegation->type) {
25657bfa891STrond Myklebust 			goto out;
25757bfa891STrond Myklebust 		}
25857bfa891STrond Myklebust 		/*
25957bfa891STrond Myklebust 		 * Deal with broken servers that hand out two
26057bfa891STrond Myklebust 		 * delegations for the same file.
26157bfa891STrond Myklebust 		 */
26257bfa891STrond Myklebust 		dfprintk(FILE, "%s: server %s handed out "
26357bfa891STrond Myklebust 				"a duplicate delegation!\n",
2643110ff80SHarvey Harrison 				__func__, clp->cl_hostname);
26517d2c0a0SDavid Howells 		if (delegation->type <= old_delegation->type) {
26657bfa891STrond Myklebust 			freeme = delegation;
26757bfa891STrond Myklebust 			delegation = NULL;
26857bfa891STrond Myklebust 			goto out;
26957bfa891STrond Myklebust 		}
270d3978bb3SChuck Lever 		freeme = nfs_detach_delegation_locked(nfsi, server);
27157bfa891STrond Myklebust 	}
272d3978bb3SChuck Lever 	list_add_rcu(&delegation->super_list, &server->delegations);
2731da177e4SLinus Torvalds 	nfsi->delegation_state = delegation->type;
2748383e460STrond Myklebust 	rcu_assign_pointer(nfsi->delegation, delegation);
2751da177e4SLinus Torvalds 	delegation = NULL;
276412c77ceSTrond Myklebust 
277412c77ceSTrond Myklebust 	/* Ensure we revalidate the attributes and page cache! */
278412c77ceSTrond Myklebust 	spin_lock(&inode->i_lock);
279412c77ceSTrond Myklebust 	nfsi->cache_validity |= NFS_INO_REVAL_FORCED;
280412c77ceSTrond Myklebust 	spin_unlock(&inode->i_lock);
281412c77ceSTrond Myklebust 
28257bfa891STrond Myklebust out:
2831da177e4SLinus Torvalds 	spin_unlock(&clp->cl_lock);
284603c83daSTrond Myklebust 	if (delegation != NULL)
285603c83daSTrond Myklebust 		nfs_free_delegation(delegation);
28657bfa891STrond Myklebust 	if (freeme != NULL)
28757bfa891STrond Myklebust 		nfs_do_return_delegation(inode, freeme, 0);
2881da177e4SLinus Torvalds 	return status;
2891da177e4SLinus Torvalds }
2901da177e4SLinus Torvalds 
2911da177e4SLinus Torvalds /*
2921da177e4SLinus Torvalds  * Basic procedure for returning a delegation to the server
2931da177e4SLinus Torvalds  */
294d18cc1fdSTrond Myklebust static int __nfs_inode_return_delegation(struct inode *inode, struct nfs_delegation *delegation, int issync)
2951da177e4SLinus Torvalds {
2961da177e4SLinus Torvalds 	struct nfs_inode *nfsi = NFS_I(inode);
297d18cc1fdSTrond Myklebust 	int err;
2981da177e4SLinus Torvalds 
2993f09df70STrond Myklebust 	/*
3003f09df70STrond Myklebust 	 * Guard against new delegated open/lock/unlock calls and against
3013f09df70STrond Myklebust 	 * state recovery
3023f09df70STrond Myklebust 	 */
3031da177e4SLinus Torvalds 	down_write(&nfsi->rwsem);
304d18cc1fdSTrond Myklebust 	err = nfs_delegation_claim_opens(inode, &delegation->stateid);
3051da177e4SLinus Torvalds 	up_write(&nfsi->rwsem);
306d18cc1fdSTrond Myklebust 	if (err)
307d18cc1fdSTrond Myklebust 		goto out;
3081da177e4SLinus Torvalds 
309d18cc1fdSTrond Myklebust 	err = nfs_do_return_delegation(inode, delegation, issync);
310d18cc1fdSTrond Myklebust out:
311d18cc1fdSTrond Myklebust 	return err;
31290163027STrond Myklebust }
31390163027STrond Myklebust 
314d3978bb3SChuck Lever /**
315d3978bb3SChuck Lever  * nfs_client_return_marked_delegations - return previously marked delegations
316d3978bb3SChuck Lever  * @clp: nfs_client to process
317d3978bb3SChuck Lever  *
318d3978bb3SChuck Lever  * Returns zero on success, or a negative errno value.
319515d8611STrond Myklebust  */
320d18cc1fdSTrond Myklebust int nfs_client_return_marked_delegations(struct nfs_client *clp)
321515d8611STrond Myklebust {
322515d8611STrond Myklebust 	struct nfs_delegation *delegation;
323d3978bb3SChuck Lever 	struct nfs_server *server;
324515d8611STrond Myklebust 	struct inode *inode;
325d18cc1fdSTrond Myklebust 	int err = 0;
326515d8611STrond Myklebust 
327515d8611STrond Myklebust restart:
328515d8611STrond Myklebust 	rcu_read_lock();
329d3978bb3SChuck Lever 	list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link) {
330d3978bb3SChuck Lever 		list_for_each_entry_rcu(delegation, &server->delegations,
331d3978bb3SChuck Lever 								super_list) {
332d3978bb3SChuck Lever 			if (!test_and_clear_bit(NFS_DELEGATION_RETURN,
333d3978bb3SChuck Lever 							&delegation->flags))
334515d8611STrond Myklebust 				continue;
335515d8611STrond Myklebust 			inode = nfs_delegation_grab_inode(delegation);
336515d8611STrond Myklebust 			if (inode == NULL)
337515d8611STrond Myklebust 				continue;
338d3978bb3SChuck Lever 			delegation = nfs_detach_delegation(NFS_I(inode),
339d3978bb3SChuck Lever 								server);
340515d8611STrond Myklebust 			rcu_read_unlock();
341d3978bb3SChuck Lever 
342d18cc1fdSTrond Myklebust 			if (delegation != NULL) {
343d18cc1fdSTrond Myklebust 				filemap_flush(inode->i_mapping);
344d3978bb3SChuck Lever 				err = __nfs_inode_return_delegation(inode,
345d3978bb3SChuck Lever 								delegation, 0);
346d18cc1fdSTrond Myklebust 			}
347515d8611STrond Myklebust 			iput(inode);
348d18cc1fdSTrond Myklebust 			if (!err)
349515d8611STrond Myklebust 				goto restart;
350d18cc1fdSTrond Myklebust 			set_bit(NFS4CLNT_DELEGRETURN, &clp->cl_state);
351d18cc1fdSTrond Myklebust 			return err;
352515d8611STrond Myklebust 		}
353d3978bb3SChuck Lever 	}
354515d8611STrond Myklebust 	rcu_read_unlock();
355d18cc1fdSTrond Myklebust 	return 0;
356515d8611STrond Myklebust }
357515d8611STrond Myklebust 
358d3978bb3SChuck Lever /**
359d3978bb3SChuck Lever  * nfs_inode_return_delegation_noreclaim - return delegation, don't reclaim opens
360d3978bb3SChuck Lever  * @inode: inode to process
361d3978bb3SChuck Lever  *
362d3978bb3SChuck Lever  * Does not protect against delegation reclaims, therefore really only safe
363d3978bb3SChuck Lever  * to be called from nfs4_clear_inode().
364e6f81075STrond Myklebust  */
365e6f81075STrond Myklebust void nfs_inode_return_delegation_noreclaim(struct inode *inode)
366e6f81075STrond Myklebust {
367d3978bb3SChuck Lever 	struct nfs_server *server = NFS_SERVER(inode);
368e6f81075STrond Myklebust 	struct nfs_inode *nfsi = NFS_I(inode);
369e6f81075STrond Myklebust 	struct nfs_delegation *delegation;
370e6f81075STrond Myklebust 
37117d2c0a0SDavid Howells 	if (rcu_access_pointer(nfsi->delegation) != NULL) {
372d3978bb3SChuck Lever 		delegation = nfs_detach_delegation(nfsi, server);
373e6f81075STrond Myklebust 		if (delegation != NULL)
374e6f81075STrond Myklebust 			nfs_do_return_delegation(inode, delegation, 0);
375e6f81075STrond Myklebust 	}
376e6f81075STrond Myklebust }
377e6f81075STrond Myklebust 
378d3978bb3SChuck Lever /**
379d3978bb3SChuck Lever  * nfs_inode_return_delegation - synchronously return a delegation
380d3978bb3SChuck Lever  * @inode: inode to process
381d3978bb3SChuck Lever  *
382d3978bb3SChuck Lever  * Returns zero on success, or a negative errno value.
383d3978bb3SChuck Lever  */
38490163027STrond Myklebust int nfs_inode_return_delegation(struct inode *inode)
38590163027STrond Myklebust {
386d3978bb3SChuck Lever 	struct nfs_server *server = NFS_SERVER(inode);
38790163027STrond Myklebust 	struct nfs_inode *nfsi = NFS_I(inode);
38890163027STrond Myklebust 	struct nfs_delegation *delegation;
38990163027STrond Myklebust 	int err = 0;
39090163027STrond Myklebust 
39117d2c0a0SDavid Howells 	if (rcu_access_pointer(nfsi->delegation) != NULL) {
392d3978bb3SChuck Lever 		delegation = nfs_detach_delegation(nfsi, server);
393d18cc1fdSTrond Myklebust 		if (delegation != NULL) {
3941b924e5fSTrond Myklebust 			nfs_wb_all(inode);
395d18cc1fdSTrond Myklebust 			err = __nfs_inode_return_delegation(inode, delegation, 1);
396d18cc1fdSTrond Myklebust 		}
39790163027STrond Myklebust 	}
39890163027STrond Myklebust 	return err;
3991da177e4SLinus Torvalds }
4001da177e4SLinus Torvalds 
401ed1e6211STrond Myklebust static void nfs_mark_return_delegation(struct nfs_server *server,
402ed1e6211STrond Myklebust 		struct nfs_delegation *delegation)
4036411bd4aSTrond Myklebust {
4046411bd4aSTrond Myklebust 	set_bit(NFS_DELEGATION_RETURN, &delegation->flags);
405ed1e6211STrond Myklebust 	set_bit(NFS4CLNT_DELEGRETURN, &server->nfs_client->cl_state);
4066411bd4aSTrond Myklebust }
4076411bd4aSTrond Myklebust 
408d3978bb3SChuck Lever /**
409d3978bb3SChuck Lever  * nfs_super_return_all_delegations - return delegations for one superblock
410d3978bb3SChuck Lever  * @sb: sb to process
411d3978bb3SChuck Lever  *
4121da177e4SLinus Torvalds  */
413515d8611STrond Myklebust void nfs_super_return_all_delegations(struct super_block *sb)
4141da177e4SLinus Torvalds {
415d3978bb3SChuck Lever 	struct nfs_server *server = NFS_SB(sb);
416d3978bb3SChuck Lever 	struct nfs_client *clp = server->nfs_client;
4171da177e4SLinus Torvalds 	struct nfs_delegation *delegation;
4181da177e4SLinus Torvalds 
4191da177e4SLinus Torvalds 	if (clp == NULL)
4201da177e4SLinus Torvalds 		return;
421d3978bb3SChuck Lever 
4228383e460STrond Myklebust 	rcu_read_lock();
423d3978bb3SChuck Lever 	list_for_each_entry_rcu(delegation, &server->delegations, super_list) {
42486e89489STrond Myklebust 		spin_lock(&delegation->lock);
425515d8611STrond Myklebust 		set_bit(NFS_DELEGATION_RETURN, &delegation->flags);
42686e89489STrond Myklebust 		spin_unlock(&delegation->lock);
4271da177e4SLinus Torvalds 	}
4288383e460STrond Myklebust 	rcu_read_unlock();
429d3978bb3SChuck Lever 
430d18cc1fdSTrond Myklebust 	if (nfs_client_return_marked_delegations(clp) != 0)
431d18cc1fdSTrond Myklebust 		nfs4_schedule_state_manager(clp);
432515d8611STrond Myklebust }
433515d8611STrond Myklebust 
434d3978bb3SChuck Lever static void nfs_mark_return_all_delegation_types(struct nfs_server *server,
435d3978bb3SChuck Lever 						 fmode_t flags)
436515d8611STrond Myklebust {
437515d8611STrond Myklebust 	struct nfs_delegation *delegation;
438515d8611STrond Myklebust 
439d3978bb3SChuck Lever 	list_for_each_entry_rcu(delegation, &server->delegations, super_list) {
440c79571a5SAlexandros Batsakis 		if ((delegation->type == (FMODE_READ|FMODE_WRITE)) && !(flags & FMODE_WRITE))
441c79571a5SAlexandros Batsakis 			continue;
442c79571a5SAlexandros Batsakis 		if (delegation->type & flags)
443ed1e6211STrond Myklebust 			nfs_mark_return_delegation(server, delegation);
444707fb4b3STrond Myklebust 	}
445d3978bb3SChuck Lever }
446d3978bb3SChuck Lever 
447d3978bb3SChuck Lever static void nfs_client_mark_return_all_delegation_types(struct nfs_client *clp,
448d3978bb3SChuck Lever 							fmode_t flags)
449d3978bb3SChuck Lever {
450d3978bb3SChuck Lever 	struct nfs_server *server;
451d3978bb3SChuck Lever 
452d3978bb3SChuck Lever 	rcu_read_lock();
453d3978bb3SChuck Lever 	list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link)
454d3978bb3SChuck Lever 		nfs_mark_return_all_delegation_types(server, flags);
455515d8611STrond Myklebust 	rcu_read_unlock();
4561da177e4SLinus Torvalds }
4571da177e4SLinus Torvalds 
458c79571a5SAlexandros Batsakis static void nfs_client_mark_return_all_delegations(struct nfs_client *clp)
459c79571a5SAlexandros Batsakis {
460c79571a5SAlexandros Batsakis 	nfs_client_mark_return_all_delegation_types(clp, FMODE_READ|FMODE_WRITE);
461c79571a5SAlexandros Batsakis }
462c79571a5SAlexandros Batsakis 
463b0d3ded1STrond Myklebust static void nfs_delegation_run_state_manager(struct nfs_client *clp)
46458d9714aSTrond Myklebust {
465b0d3ded1STrond Myklebust 	if (test_bit(NFS4CLNT_DELEGRETURN, &clp->cl_state))
466b0d3ded1STrond Myklebust 		nfs4_schedule_state_manager(clp);
46758d9714aSTrond Myklebust }
46858d9714aSTrond Myklebust 
469a1d0b5eeSTrond Myklebust void nfs_remove_bad_delegation(struct inode *inode)
470a1d0b5eeSTrond Myklebust {
471a1d0b5eeSTrond Myklebust 	struct nfs_delegation *delegation;
472a1d0b5eeSTrond Myklebust 
473a1d0b5eeSTrond Myklebust 	delegation = nfs_detach_delegation(NFS_I(inode), NFS_SERVER(inode));
474a1d0b5eeSTrond Myklebust 	if (delegation) {
475a1d0b5eeSTrond Myklebust 		nfs_inode_find_state_and_recover(inode, &delegation->stateid);
476a1d0b5eeSTrond Myklebust 		nfs_free_delegation(delegation);
477a1d0b5eeSTrond Myklebust 	}
478a1d0b5eeSTrond Myklebust }
479a1d0b5eeSTrond Myklebust 
480d3978bb3SChuck Lever /**
481d3978bb3SChuck Lever  * nfs_expire_all_delegation_types
482d3978bb3SChuck Lever  * @clp: client to process
483d3978bb3SChuck Lever  * @flags: delegation types to expire
484d3978bb3SChuck Lever  *
485d3978bb3SChuck Lever  */
48631f09607SAlexandros Batsakis void nfs_expire_all_delegation_types(struct nfs_client *clp, fmode_t flags)
487c79571a5SAlexandros Batsakis {
488c79571a5SAlexandros Batsakis 	nfs_client_mark_return_all_delegation_types(clp, flags);
489c79571a5SAlexandros Batsakis 	nfs_delegation_run_state_manager(clp);
490c79571a5SAlexandros Batsakis }
491c79571a5SAlexandros Batsakis 
492d3978bb3SChuck Lever /**
493d3978bb3SChuck Lever  * nfs_expire_all_delegations
494d3978bb3SChuck Lever  * @clp: client to process
495d3978bb3SChuck Lever  *
496d3978bb3SChuck Lever  */
497adfa6f98SDavid Howells void nfs_expire_all_delegations(struct nfs_client *clp)
49858d9714aSTrond Myklebust {
499c79571a5SAlexandros Batsakis 	nfs_expire_all_delegation_types(clp, FMODE_READ|FMODE_WRITE);
50058d9714aSTrond Myklebust }
50158d9714aSTrond Myklebust 
502d3978bb3SChuck Lever /**
503d3978bb3SChuck Lever  * nfs_handle_cb_pathdown - return all delegations after NFS4ERR_CB_PATH_DOWN
504d3978bb3SChuck Lever  * @clp: client to process
505d3978bb3SChuck Lever  *
5061da177e4SLinus Torvalds  */
507adfa6f98SDavid Howells void nfs_handle_cb_pathdown(struct nfs_client *clp)
5081da177e4SLinus Torvalds {
5091da177e4SLinus Torvalds 	if (clp == NULL)
5101da177e4SLinus Torvalds 		return;
511707fb4b3STrond Myklebust 	nfs_client_mark_return_all_delegations(clp);
5121da177e4SLinus Torvalds }
5131da177e4SLinus Torvalds 
514d3978bb3SChuck Lever static void nfs_mark_return_unreferenced_delegations(struct nfs_server *server)
515b7391f44STrond Myklebust {
516b7391f44STrond Myklebust 	struct nfs_delegation *delegation;
517b7391f44STrond Myklebust 
518d3978bb3SChuck Lever 	list_for_each_entry_rcu(delegation, &server->delegations, super_list) {
519b7391f44STrond Myklebust 		if (test_and_clear_bit(NFS_DELEGATION_REFERENCED, &delegation->flags))
520b7391f44STrond Myklebust 			continue;
521ed1e6211STrond Myklebust 		nfs_mark_return_delegation(server, delegation);
522b7391f44STrond Myklebust 	}
523b7391f44STrond Myklebust }
524b7391f44STrond Myklebust 
525d3978bb3SChuck Lever /**
526d3978bb3SChuck Lever  * nfs_expire_unreferenced_delegations - Eliminate unused delegations
527d3978bb3SChuck Lever  * @clp: nfs_client to process
528d3978bb3SChuck Lever  *
529d3978bb3SChuck Lever  */
530b7391f44STrond Myklebust void nfs_expire_unreferenced_delegations(struct nfs_client *clp)
531b7391f44STrond Myklebust {
532d3978bb3SChuck Lever 	struct nfs_server *server;
533d3978bb3SChuck Lever 
534d3978bb3SChuck Lever 	rcu_read_lock();
535d3978bb3SChuck Lever 	list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link)
536d3978bb3SChuck Lever 		nfs_mark_return_unreferenced_delegations(server);
537d3978bb3SChuck Lever 	rcu_read_unlock();
538d3978bb3SChuck Lever 
539b7391f44STrond Myklebust 	nfs_delegation_run_state_manager(clp);
540b7391f44STrond Myklebust }
541b7391f44STrond Myklebust 
542d3978bb3SChuck Lever /**
543d3978bb3SChuck Lever  * nfs_async_inode_return_delegation - asynchronously return a delegation
544d3978bb3SChuck Lever  * @inode: inode to process
5458e663f0eSTrond Myklebust  * @stateid: state ID information
546d3978bb3SChuck Lever  *
547d3978bb3SChuck Lever  * Returns zero on success, or a negative errno value.
5481da177e4SLinus Torvalds  */
549d3978bb3SChuck Lever int nfs_async_inode_return_delegation(struct inode *inode,
550d3978bb3SChuck Lever 				      const nfs4_stateid *stateid)
5511da177e4SLinus Torvalds {
552ed1e6211STrond Myklebust 	struct nfs_server *server = NFS_SERVER(inode);
553ed1e6211STrond Myklebust 	struct nfs_client *clp = server->nfs_client;
5546411bd4aSTrond Myklebust 	struct nfs_delegation *delegation;
5551da177e4SLinus Torvalds 
5566411bd4aSTrond Myklebust 	rcu_read_lock();
5576411bd4aSTrond Myklebust 	delegation = rcu_dereference(NFS_I(inode)->delegation);
5582597641dSAlexandros Batsakis 
55936281caaSTrond Myklebust 	if (!clp->cl_mvops->match_stateid(&delegation->stateid, stateid)) {
5606411bd4aSTrond Myklebust 		rcu_read_unlock();
5616411bd4aSTrond Myklebust 		return -ENOENT;
5626411bd4aSTrond Myklebust 	}
563ed1e6211STrond Myklebust 	nfs_mark_return_delegation(server, delegation);
5646411bd4aSTrond Myklebust 	rcu_read_unlock();
565d3978bb3SChuck Lever 
5666411bd4aSTrond Myklebust 	nfs_delegation_run_state_manager(clp);
5676411bd4aSTrond Myklebust 	return 0;
5681da177e4SLinus Torvalds }
5691da177e4SLinus Torvalds 
570d3978bb3SChuck Lever static struct inode *
571d3978bb3SChuck Lever nfs_delegation_find_inode_server(struct nfs_server *server,
572d3978bb3SChuck Lever 				 const struct nfs_fh *fhandle)
5731da177e4SLinus Torvalds {
5741da177e4SLinus Torvalds 	struct nfs_delegation *delegation;
5751da177e4SLinus Torvalds 	struct inode *res = NULL;
576d3978bb3SChuck Lever 
577d3978bb3SChuck Lever 	list_for_each_entry_rcu(delegation, &server->delegations, super_list) {
57886e89489STrond Myklebust 		spin_lock(&delegation->lock);
57986e89489STrond Myklebust 		if (delegation->inode != NULL &&
58086e89489STrond Myklebust 		    nfs_compare_fh(fhandle, &NFS_I(delegation->inode)->fh) == 0) {
5811da177e4SLinus Torvalds 			res = igrab(delegation->inode);
5821da177e4SLinus Torvalds 		}
58386e89489STrond Myklebust 		spin_unlock(&delegation->lock);
58486e89489STrond Myklebust 		if (res != NULL)
58586e89489STrond Myklebust 			break;
5861da177e4SLinus Torvalds 	}
587d3978bb3SChuck Lever 	return res;
588d3978bb3SChuck Lever }
589d3978bb3SChuck Lever 
590d3978bb3SChuck Lever /**
591d3978bb3SChuck Lever  * nfs_delegation_find_inode - retrieve the inode associated with a delegation
592d3978bb3SChuck Lever  * @clp: client state handle
593d3978bb3SChuck Lever  * @fhandle: filehandle from a delegation recall
594d3978bb3SChuck Lever  *
595d3978bb3SChuck Lever  * Returns pointer to inode matching "fhandle," or NULL if a matching inode
596d3978bb3SChuck Lever  * cannot be found.
597d3978bb3SChuck Lever  */
598d3978bb3SChuck Lever struct inode *nfs_delegation_find_inode(struct nfs_client *clp,
599d3978bb3SChuck Lever 					const struct nfs_fh *fhandle)
600d3978bb3SChuck Lever {
601d3978bb3SChuck Lever 	struct nfs_server *server;
602d3978bb3SChuck Lever 	struct inode *res = NULL;
603d3978bb3SChuck Lever 
604d3978bb3SChuck Lever 	rcu_read_lock();
605d3978bb3SChuck Lever 	list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link) {
606d3978bb3SChuck Lever 		res = nfs_delegation_find_inode_server(server, fhandle);
607d3978bb3SChuck Lever 		if (res != NULL)
608d3978bb3SChuck Lever 			break;
609d3978bb3SChuck Lever 	}
6108383e460STrond Myklebust 	rcu_read_unlock();
6111da177e4SLinus Torvalds 	return res;
6121da177e4SLinus Torvalds }
6131da177e4SLinus Torvalds 
614d3978bb3SChuck Lever static void nfs_delegation_mark_reclaim_server(struct nfs_server *server)
615d3978bb3SChuck Lever {
616d3978bb3SChuck Lever 	struct nfs_delegation *delegation;
617d3978bb3SChuck Lever 
618d3978bb3SChuck Lever 	list_for_each_entry_rcu(delegation, &server->delegations, super_list)
619d3978bb3SChuck Lever 		set_bit(NFS_DELEGATION_NEED_RECLAIM, &delegation->flags);
620d3978bb3SChuck Lever }
621d3978bb3SChuck Lever 
622d3978bb3SChuck Lever /**
623d3978bb3SChuck Lever  * nfs_delegation_mark_reclaim - mark all delegations as needing to be reclaimed
624d3978bb3SChuck Lever  * @clp: nfs_client to process
625d3978bb3SChuck Lever  *
6261da177e4SLinus Torvalds  */
627adfa6f98SDavid Howells void nfs_delegation_mark_reclaim(struct nfs_client *clp)
6281da177e4SLinus Torvalds {
629d3978bb3SChuck Lever 	struct nfs_server *server;
630d3978bb3SChuck Lever 
6318383e460STrond Myklebust 	rcu_read_lock();
632d3978bb3SChuck Lever 	list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link)
633d3978bb3SChuck Lever 		nfs_delegation_mark_reclaim_server(server);
6348383e460STrond Myklebust 	rcu_read_unlock();
6351da177e4SLinus Torvalds }
6361da177e4SLinus Torvalds 
637d3978bb3SChuck Lever /**
638d3978bb3SChuck Lever  * nfs_delegation_reap_unclaimed - reap unclaimed delegations after reboot recovery is done
639d3978bb3SChuck Lever  * @clp: nfs_client to process
640d3978bb3SChuck Lever  *
6411da177e4SLinus Torvalds  */
642adfa6f98SDavid Howells void nfs_delegation_reap_unclaimed(struct nfs_client *clp)
6431da177e4SLinus Torvalds {
6448383e460STrond Myklebust 	struct nfs_delegation *delegation;
645d3978bb3SChuck Lever 	struct nfs_server *server;
64686e89489STrond Myklebust 	struct inode *inode;
647d3978bb3SChuck Lever 
6488383e460STrond Myklebust restart:
6498383e460STrond Myklebust 	rcu_read_lock();
650d3978bb3SChuck Lever 	list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link) {
651d3978bb3SChuck Lever 		list_for_each_entry_rcu(delegation, &server->delegations,
652d3978bb3SChuck Lever 								super_list) {
653d3978bb3SChuck Lever 			if (test_bit(NFS_DELEGATION_NEED_RECLAIM,
654d3978bb3SChuck Lever 						&delegation->flags) == 0)
6551da177e4SLinus Torvalds 				continue;
65686e89489STrond Myklebust 			inode = nfs_delegation_grab_inode(delegation);
65786e89489STrond Myklebust 			if (inode == NULL)
65886e89489STrond Myklebust 				continue;
659d3978bb3SChuck Lever 			delegation = nfs_detach_delegation(NFS_I(inode),
660d3978bb3SChuck Lever 								server);
6618383e460STrond Myklebust 			rcu_read_unlock();
662d3978bb3SChuck Lever 
6638383e460STrond Myklebust 			if (delegation != NULL)
664905f8d16STrond Myklebust 				nfs_free_delegation(delegation);
66586e89489STrond Myklebust 			iput(inode);
6668383e460STrond Myklebust 			goto restart;
6671da177e4SLinus Torvalds 		}
668d3978bb3SChuck Lever 	}
6698383e460STrond Myklebust 	rcu_read_unlock();
6701da177e4SLinus Torvalds }
6713e4f6290STrond Myklebust 
672d3978bb3SChuck Lever /**
673d3978bb3SChuck Lever  * nfs_delegations_present - check for existence of delegations
674d3978bb3SChuck Lever  * @clp: client state handle
675d3978bb3SChuck Lever  *
676d3978bb3SChuck Lever  * Returns one if there are any nfs_delegation structures attached
677d3978bb3SChuck Lever  * to this nfs_client.
678d3978bb3SChuck Lever  */
679d3978bb3SChuck Lever int nfs_delegations_present(struct nfs_client *clp)
680d3978bb3SChuck Lever {
681d3978bb3SChuck Lever 	struct nfs_server *server;
682d3978bb3SChuck Lever 	int ret = 0;
683d3978bb3SChuck Lever 
684d3978bb3SChuck Lever 	rcu_read_lock();
685d3978bb3SChuck Lever 	list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link)
686d3978bb3SChuck Lever 		if (!list_empty(&server->delegations)) {
687d3978bb3SChuck Lever 			ret = 1;
688d3978bb3SChuck Lever 			break;
689d3978bb3SChuck Lever 		}
690d3978bb3SChuck Lever 	rcu_read_unlock();
691d3978bb3SChuck Lever 	return ret;
692d3978bb3SChuck Lever }
693d3978bb3SChuck Lever 
694d3978bb3SChuck Lever /**
695d3978bb3SChuck Lever  * nfs4_copy_delegation_stateid - Copy inode's state ID information
696d3978bb3SChuck Lever  * @dst: stateid data structure to fill in
697d3978bb3SChuck Lever  * @inode: inode to check
698d3978bb3SChuck Lever  *
699d3978bb3SChuck Lever  * Returns one and fills in "dst->data" * if inode had a delegation,
700d3978bb3SChuck Lever  * otherwise zero is returned.
701d3978bb3SChuck Lever  */
7023e4f6290STrond Myklebust int nfs4_copy_delegation_stateid(nfs4_stateid *dst, struct inode *inode)
7033e4f6290STrond Myklebust {
7043e4f6290STrond Myklebust 	struct nfs_inode *nfsi = NFS_I(inode);
7053e4f6290STrond Myklebust 	struct nfs_delegation *delegation;
7068383e460STrond Myklebust 	int ret = 0;
7073e4f6290STrond Myklebust 
7088383e460STrond Myklebust 	rcu_read_lock();
7098383e460STrond Myklebust 	delegation = rcu_dereference(nfsi->delegation);
7103e4f6290STrond Myklebust 	if (delegation != NULL) {
7113e4f6290STrond Myklebust 		memcpy(dst->data, delegation->stateid.data, sizeof(dst->data));
7128383e460STrond Myklebust 		ret = 1;
7133e4f6290STrond Myklebust 	}
7148383e460STrond Myklebust 	rcu_read_unlock();
7158383e460STrond Myklebust 	return ret;
7163e4f6290STrond Myklebust }
717