xref: /openbmc/linux/fs/nfs/delegation.c (revision 17280175)
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;
108f597c537STrond Myklebust 		if (!nfs4_stateid_match(&state->stateid, stateid))
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) {
142f597c537STrond Myklebust 			nfs4_stateid_copy(&delegation->stateid, &res->delegation);
1431da177e4SLinus Torvalds 			delegation->type = res->delegation_type;
1441da177e4SLinus Torvalds 			delegation->maxsize = res->maxsize;
14505c88babSTrond Myklebust 			oldcred = delegation->cred;
1461da177e4SLinus Torvalds 			delegation->cred = get_rpccred(cred);
1478f649c37STrond Myklebust 			clear_bit(NFS_DELEGATION_NEED_RECLAIM,
1488f649c37STrond Myklebust 				  &delegation->flags);
1491da177e4SLinus Torvalds 			NFS_I(inode)->delegation_state = delegation->type;
1508f649c37STrond Myklebust 			spin_unlock(&delegation->lock);
15105c88babSTrond Myklebust 			put_rpccred(oldcred);
1528f649c37STrond Myklebust 			rcu_read_unlock();
1538f649c37STrond Myklebust 		} else {
1548f649c37STrond Myklebust 			/* We appear to have raced with a delegation return. */
1558f649c37STrond Myklebust 			spin_unlock(&delegation->lock);
1568f649c37STrond Myklebust 			rcu_read_unlock();
1578f649c37STrond Myklebust 			nfs_inode_set_delegation(inode, cred, res);
1588f649c37STrond Myklebust 		}
1598f649c37STrond Myklebust 	} else {
1608f649c37STrond Myklebust 		rcu_read_unlock();
1618f649c37STrond Myklebust 	}
1621da177e4SLinus Torvalds }
1631da177e4SLinus Torvalds 
16457bfa891STrond Myklebust static int nfs_do_return_delegation(struct inode *inode, struct nfs_delegation *delegation, int issync)
16557bfa891STrond Myklebust {
16657bfa891STrond Myklebust 	int res = 0;
16757bfa891STrond Myklebust 
16857bfa891STrond Myklebust 	res = nfs4_proc_delegreturn(inode, delegation->cred, &delegation->stateid, issync);
16957bfa891STrond Myklebust 	nfs_free_delegation(delegation);
17057bfa891STrond Myklebust 	return res;
17157bfa891STrond Myklebust }
17257bfa891STrond Myklebust 
17386e89489STrond Myklebust static struct inode *nfs_delegation_grab_inode(struct nfs_delegation *delegation)
17486e89489STrond Myklebust {
17586e89489STrond Myklebust 	struct inode *inode = NULL;
17686e89489STrond Myklebust 
17786e89489STrond Myklebust 	spin_lock(&delegation->lock);
17886e89489STrond Myklebust 	if (delegation->inode != NULL)
17986e89489STrond Myklebust 		inode = igrab(delegation->inode);
18086e89489STrond Myklebust 	spin_unlock(&delegation->lock);
18186e89489STrond Myklebust 	return inode;
18286e89489STrond Myklebust }
18386e89489STrond Myklebust 
184dda4b225SChuck Lever static struct nfs_delegation *
185dda4b225SChuck Lever nfs_detach_delegation_locked(struct nfs_inode *nfsi,
186d3978bb3SChuck Lever 			     struct nfs_server *server)
18757bfa891STrond Myklebust {
18817d2c0a0SDavid Howells 	struct nfs_delegation *delegation =
18917d2c0a0SDavid Howells 		rcu_dereference_protected(nfsi->delegation,
190d3978bb3SChuck Lever 				lockdep_is_held(&server->nfs_client->cl_lock));
19157bfa891STrond Myklebust 
19257bfa891STrond Myklebust 	if (delegation == NULL)
19357bfa891STrond Myklebust 		goto nomatch;
194dda4b225SChuck Lever 
19534310430STrond Myklebust 	spin_lock(&delegation->lock);
19657bfa891STrond Myklebust 	list_del_rcu(&delegation->super_list);
19786e89489STrond Myklebust 	delegation->inode = NULL;
19857bfa891STrond Myklebust 	nfsi->delegation_state = 0;
19957bfa891STrond Myklebust 	rcu_assign_pointer(nfsi->delegation, NULL);
20034310430STrond Myklebust 	spin_unlock(&delegation->lock);
20157bfa891STrond Myklebust 	return delegation;
20257bfa891STrond Myklebust nomatch:
20357bfa891STrond Myklebust 	return NULL;
20457bfa891STrond Myklebust }
20557bfa891STrond Myklebust 
206dda4b225SChuck Lever static struct nfs_delegation *nfs_detach_delegation(struct nfs_inode *nfsi,
207d3978bb3SChuck Lever 						    struct nfs_server *server)
208dda4b225SChuck Lever {
209d3978bb3SChuck Lever 	struct nfs_client *clp = server->nfs_client;
210dda4b225SChuck Lever 	struct nfs_delegation *delegation;
211dda4b225SChuck Lever 
212dda4b225SChuck Lever 	spin_lock(&clp->cl_lock);
213d3978bb3SChuck Lever 	delegation = nfs_detach_delegation_locked(nfsi, server);
214dda4b225SChuck Lever 	spin_unlock(&clp->cl_lock);
215dda4b225SChuck Lever 	return delegation;
216dda4b225SChuck Lever }
217dda4b225SChuck Lever 
218d3978bb3SChuck Lever /**
219d3978bb3SChuck Lever  * nfs_inode_set_delegation - set up a delegation on an inode
220d3978bb3SChuck Lever  * @inode: inode to which delegation applies
221d3978bb3SChuck Lever  * @cred: cred to use for subsequent delegation processing
222d3978bb3SChuck Lever  * @res: new delegation state from server
223d3978bb3SChuck Lever  *
224d3978bb3SChuck Lever  * Returns zero on success, or a negative errno value.
2251da177e4SLinus Torvalds  */
2261da177e4SLinus Torvalds int nfs_inode_set_delegation(struct inode *inode, struct rpc_cred *cred, struct nfs_openres *res)
2271da177e4SLinus Torvalds {
228d3978bb3SChuck Lever 	struct nfs_server *server = NFS_SERVER(inode);
229d3978bb3SChuck Lever 	struct nfs_client *clp = server->nfs_client;
2301da177e4SLinus Torvalds 	struct nfs_inode *nfsi = NFS_I(inode);
23117d2c0a0SDavid Howells 	struct nfs_delegation *delegation, *old_delegation;
23257bfa891STrond Myklebust 	struct nfs_delegation *freeme = NULL;
2331da177e4SLinus Torvalds 	int status = 0;
2341da177e4SLinus Torvalds 
2358535b2beSTrond Myklebust 	delegation = kmalloc(sizeof(*delegation), GFP_NOFS);
2361da177e4SLinus Torvalds 	if (delegation == NULL)
2371da177e4SLinus Torvalds 		return -ENOMEM;
238f597c537STrond Myklebust 	nfs4_stateid_copy(&delegation->stateid, &res->delegation);
2391da177e4SLinus Torvalds 	delegation->type = res->delegation_type;
2401da177e4SLinus Torvalds 	delegation->maxsize = res->maxsize;
241a9a4a87aSTrond Myklebust 	delegation->change_attr = inode->i_version;
2421da177e4SLinus Torvalds 	delegation->cred = get_rpccred(cred);
2431da177e4SLinus Torvalds 	delegation->inode = inode;
244b7391f44STrond Myklebust 	delegation->flags = 1<<NFS_DELEGATION_REFERENCED;
24534310430STrond Myklebust 	spin_lock_init(&delegation->lock);
2461da177e4SLinus Torvalds 
2471da177e4SLinus Torvalds 	spin_lock(&clp->cl_lock);
24817d2c0a0SDavid Howells 	old_delegation = rcu_dereference_protected(nfsi->delegation,
24917d2c0a0SDavid Howells 					lockdep_is_held(&clp->cl_lock));
25017d2c0a0SDavid Howells 	if (old_delegation != NULL) {
251f597c537STrond Myklebust 		if (nfs4_stateid_match(&delegation->stateid,
252f597c537STrond Myklebust 					&old_delegation->stateid) &&
25317d2c0a0SDavid Howells 				delegation->type == old_delegation->type) {
25457bfa891STrond Myklebust 			goto out;
25557bfa891STrond Myklebust 		}
25657bfa891STrond Myklebust 		/*
25757bfa891STrond Myklebust 		 * Deal with broken servers that hand out two
25857bfa891STrond Myklebust 		 * delegations for the same file.
25917280175STrond Myklebust 		 * Allow for upgrades to a WRITE delegation, but
26017280175STrond Myklebust 		 * nothing else.
26157bfa891STrond Myklebust 		 */
26257bfa891STrond Myklebust 		dfprintk(FILE, "%s: server %s handed out "
26357bfa891STrond Myklebust 				"a duplicate delegation!\n",
2643110ff80SHarvey Harrison 				__func__, clp->cl_hostname);
26517280175STrond Myklebust 		if (delegation->type == old_delegation->type ||
26617280175STrond Myklebust 		    !(delegation->type & FMODE_WRITE)) {
26757bfa891STrond Myklebust 			freeme = delegation;
26857bfa891STrond Myklebust 			delegation = NULL;
26957bfa891STrond Myklebust 			goto out;
27057bfa891STrond Myklebust 		}
271d3978bb3SChuck Lever 		freeme = nfs_detach_delegation_locked(nfsi, server);
27257bfa891STrond Myklebust 	}
273d3978bb3SChuck Lever 	list_add_rcu(&delegation->super_list, &server->delegations);
2741da177e4SLinus Torvalds 	nfsi->delegation_state = delegation->type;
2758383e460STrond Myklebust 	rcu_assign_pointer(nfsi->delegation, delegation);
2761da177e4SLinus Torvalds 	delegation = NULL;
277412c77ceSTrond Myklebust 
278412c77ceSTrond Myklebust 	/* Ensure we revalidate the attributes and page cache! */
279412c77ceSTrond Myklebust 	spin_lock(&inode->i_lock);
280412c77ceSTrond Myklebust 	nfsi->cache_validity |= NFS_INO_REVAL_FORCED;
281412c77ceSTrond Myklebust 	spin_unlock(&inode->i_lock);
282412c77ceSTrond Myklebust 
28357bfa891STrond Myklebust out:
2841da177e4SLinus Torvalds 	spin_unlock(&clp->cl_lock);
285603c83daSTrond Myklebust 	if (delegation != NULL)
286603c83daSTrond Myklebust 		nfs_free_delegation(delegation);
28757bfa891STrond Myklebust 	if (freeme != NULL)
28857bfa891STrond Myklebust 		nfs_do_return_delegation(inode, freeme, 0);
2891da177e4SLinus Torvalds 	return status;
2901da177e4SLinus Torvalds }
2911da177e4SLinus Torvalds 
2921da177e4SLinus Torvalds /*
2931da177e4SLinus Torvalds  * Basic procedure for returning a delegation to the server
2941da177e4SLinus Torvalds  */
295d18cc1fdSTrond Myklebust static int __nfs_inode_return_delegation(struct inode *inode, struct nfs_delegation *delegation, int issync)
2961da177e4SLinus Torvalds {
2971da177e4SLinus Torvalds 	struct nfs_inode *nfsi = NFS_I(inode);
298d18cc1fdSTrond Myklebust 	int err;
2991da177e4SLinus Torvalds 
3003f09df70STrond Myklebust 	/*
3013f09df70STrond Myklebust 	 * Guard against new delegated open/lock/unlock calls and against
3023f09df70STrond Myklebust 	 * state recovery
3033f09df70STrond Myklebust 	 */
3041da177e4SLinus Torvalds 	down_write(&nfsi->rwsem);
305d18cc1fdSTrond Myklebust 	err = nfs_delegation_claim_opens(inode, &delegation->stateid);
3061da177e4SLinus Torvalds 	up_write(&nfsi->rwsem);
307d18cc1fdSTrond Myklebust 	if (err)
308d18cc1fdSTrond Myklebust 		goto out;
3091da177e4SLinus Torvalds 
310d18cc1fdSTrond Myklebust 	err = nfs_do_return_delegation(inode, delegation, issync);
311d18cc1fdSTrond Myklebust out:
312d18cc1fdSTrond Myklebust 	return err;
31390163027STrond Myklebust }
31490163027STrond Myklebust 
315d3978bb3SChuck Lever /**
316d3978bb3SChuck Lever  * nfs_client_return_marked_delegations - return previously marked delegations
317d3978bb3SChuck Lever  * @clp: nfs_client to process
318d3978bb3SChuck Lever  *
319d3978bb3SChuck Lever  * Returns zero on success, or a negative errno value.
320515d8611STrond Myklebust  */
321d18cc1fdSTrond Myklebust int nfs_client_return_marked_delegations(struct nfs_client *clp)
322515d8611STrond Myklebust {
323515d8611STrond Myklebust 	struct nfs_delegation *delegation;
324d3978bb3SChuck Lever 	struct nfs_server *server;
325515d8611STrond Myklebust 	struct inode *inode;
326d18cc1fdSTrond Myklebust 	int err = 0;
327515d8611STrond Myklebust 
328515d8611STrond Myklebust restart:
329515d8611STrond Myklebust 	rcu_read_lock();
330d3978bb3SChuck Lever 	list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link) {
331d3978bb3SChuck Lever 		list_for_each_entry_rcu(delegation, &server->delegations,
332d3978bb3SChuck Lever 								super_list) {
333d3978bb3SChuck Lever 			if (!test_and_clear_bit(NFS_DELEGATION_RETURN,
334d3978bb3SChuck Lever 							&delegation->flags))
335515d8611STrond Myklebust 				continue;
336515d8611STrond Myklebust 			inode = nfs_delegation_grab_inode(delegation);
337515d8611STrond Myklebust 			if (inode == NULL)
338515d8611STrond Myklebust 				continue;
339d3978bb3SChuck Lever 			delegation = nfs_detach_delegation(NFS_I(inode),
340d3978bb3SChuck Lever 								server);
341515d8611STrond Myklebust 			rcu_read_unlock();
342d3978bb3SChuck Lever 
343d18cc1fdSTrond Myklebust 			if (delegation != NULL) {
344d18cc1fdSTrond Myklebust 				filemap_flush(inode->i_mapping);
345d3978bb3SChuck Lever 				err = __nfs_inode_return_delegation(inode,
346d3978bb3SChuck Lever 								delegation, 0);
347d18cc1fdSTrond Myklebust 			}
348515d8611STrond Myklebust 			iput(inode);
349d18cc1fdSTrond Myklebust 			if (!err)
350515d8611STrond Myklebust 				goto restart;
351d18cc1fdSTrond Myklebust 			set_bit(NFS4CLNT_DELEGRETURN, &clp->cl_state);
352d18cc1fdSTrond Myklebust 			return err;
353515d8611STrond Myklebust 		}
354d3978bb3SChuck Lever 	}
355515d8611STrond Myklebust 	rcu_read_unlock();
356d18cc1fdSTrond Myklebust 	return 0;
357515d8611STrond Myklebust }
358515d8611STrond Myklebust 
359d3978bb3SChuck Lever /**
360d3978bb3SChuck Lever  * nfs_inode_return_delegation_noreclaim - return delegation, don't reclaim opens
361d3978bb3SChuck Lever  * @inode: inode to process
362d3978bb3SChuck Lever  *
363d3978bb3SChuck Lever  * Does not protect against delegation reclaims, therefore really only safe
364d3978bb3SChuck Lever  * to be called from nfs4_clear_inode().
365e6f81075STrond Myklebust  */
366e6f81075STrond Myklebust void nfs_inode_return_delegation_noreclaim(struct inode *inode)
367e6f81075STrond Myklebust {
368d3978bb3SChuck Lever 	struct nfs_server *server = NFS_SERVER(inode);
369e6f81075STrond Myklebust 	struct nfs_inode *nfsi = NFS_I(inode);
370e6f81075STrond Myklebust 	struct nfs_delegation *delegation;
371e6f81075STrond Myklebust 
37217d2c0a0SDavid Howells 	if (rcu_access_pointer(nfsi->delegation) != NULL) {
373d3978bb3SChuck Lever 		delegation = nfs_detach_delegation(nfsi, server);
374e6f81075STrond Myklebust 		if (delegation != NULL)
375e6f81075STrond Myklebust 			nfs_do_return_delegation(inode, delegation, 0);
376e6f81075STrond Myklebust 	}
377e6f81075STrond Myklebust }
378e6f81075STrond Myklebust 
379d3978bb3SChuck Lever /**
380d3978bb3SChuck Lever  * nfs_inode_return_delegation - synchronously return a delegation
381d3978bb3SChuck Lever  * @inode: inode to process
382d3978bb3SChuck Lever  *
383d3978bb3SChuck Lever  * Returns zero on success, or a negative errno value.
384d3978bb3SChuck Lever  */
38590163027STrond Myklebust int nfs_inode_return_delegation(struct inode *inode)
38690163027STrond Myklebust {
387d3978bb3SChuck Lever 	struct nfs_server *server = NFS_SERVER(inode);
38890163027STrond Myklebust 	struct nfs_inode *nfsi = NFS_I(inode);
38990163027STrond Myklebust 	struct nfs_delegation *delegation;
39090163027STrond Myklebust 	int err = 0;
39190163027STrond Myklebust 
39217d2c0a0SDavid Howells 	if (rcu_access_pointer(nfsi->delegation) != NULL) {
393d3978bb3SChuck Lever 		delegation = nfs_detach_delegation(nfsi, server);
394d18cc1fdSTrond Myklebust 		if (delegation != NULL) {
3951b924e5fSTrond Myklebust 			nfs_wb_all(inode);
396d18cc1fdSTrond Myklebust 			err = __nfs_inode_return_delegation(inode, delegation, 1);
397d18cc1fdSTrond Myklebust 		}
39890163027STrond Myklebust 	}
39990163027STrond Myklebust 	return err;
4001da177e4SLinus Torvalds }
4011da177e4SLinus Torvalds 
402ed1e6211STrond Myklebust static void nfs_mark_return_delegation(struct nfs_server *server,
403ed1e6211STrond Myklebust 		struct nfs_delegation *delegation)
4046411bd4aSTrond Myklebust {
4056411bd4aSTrond Myklebust 	set_bit(NFS_DELEGATION_RETURN, &delegation->flags);
406ed1e6211STrond Myklebust 	set_bit(NFS4CLNT_DELEGRETURN, &server->nfs_client->cl_state);
4076411bd4aSTrond Myklebust }
4086411bd4aSTrond Myklebust 
409d3978bb3SChuck Lever /**
410d3978bb3SChuck Lever  * nfs_super_return_all_delegations - return delegations for one superblock
411d3978bb3SChuck Lever  * @sb: sb to process
412d3978bb3SChuck Lever  *
4131da177e4SLinus Torvalds  */
414515d8611STrond Myklebust void nfs_super_return_all_delegations(struct super_block *sb)
4151da177e4SLinus Torvalds {
416d3978bb3SChuck Lever 	struct nfs_server *server = NFS_SB(sb);
417d3978bb3SChuck Lever 	struct nfs_client *clp = server->nfs_client;
4181da177e4SLinus Torvalds 	struct nfs_delegation *delegation;
4191da177e4SLinus Torvalds 
4201da177e4SLinus Torvalds 	if (clp == NULL)
4211da177e4SLinus Torvalds 		return;
422d3978bb3SChuck Lever 
4238383e460STrond Myklebust 	rcu_read_lock();
424d3978bb3SChuck Lever 	list_for_each_entry_rcu(delegation, &server->delegations, super_list) {
42586e89489STrond Myklebust 		spin_lock(&delegation->lock);
426515d8611STrond Myklebust 		set_bit(NFS_DELEGATION_RETURN, &delegation->flags);
42786e89489STrond Myklebust 		spin_unlock(&delegation->lock);
4281da177e4SLinus Torvalds 	}
4298383e460STrond Myklebust 	rcu_read_unlock();
430d3978bb3SChuck Lever 
431d18cc1fdSTrond Myklebust 	if (nfs_client_return_marked_delegations(clp) != 0)
432d18cc1fdSTrond Myklebust 		nfs4_schedule_state_manager(clp);
433515d8611STrond Myklebust }
434515d8611STrond Myklebust 
435d3978bb3SChuck Lever static void nfs_mark_return_all_delegation_types(struct nfs_server *server,
436d3978bb3SChuck Lever 						 fmode_t flags)
437515d8611STrond Myklebust {
438515d8611STrond Myklebust 	struct nfs_delegation *delegation;
439515d8611STrond Myklebust 
440d3978bb3SChuck Lever 	list_for_each_entry_rcu(delegation, &server->delegations, super_list) {
441c79571a5SAlexandros Batsakis 		if ((delegation->type == (FMODE_READ|FMODE_WRITE)) && !(flags & FMODE_WRITE))
442c79571a5SAlexandros Batsakis 			continue;
443c79571a5SAlexandros Batsakis 		if (delegation->type & flags)
444ed1e6211STrond Myklebust 			nfs_mark_return_delegation(server, delegation);
445707fb4b3STrond Myklebust 	}
446d3978bb3SChuck Lever }
447d3978bb3SChuck Lever 
448d3978bb3SChuck Lever static void nfs_client_mark_return_all_delegation_types(struct nfs_client *clp,
449d3978bb3SChuck Lever 							fmode_t flags)
450d3978bb3SChuck Lever {
451d3978bb3SChuck Lever 	struct nfs_server *server;
452d3978bb3SChuck Lever 
453d3978bb3SChuck Lever 	rcu_read_lock();
454d3978bb3SChuck Lever 	list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link)
455d3978bb3SChuck Lever 		nfs_mark_return_all_delegation_types(server, flags);
456515d8611STrond Myklebust 	rcu_read_unlock();
4571da177e4SLinus Torvalds }
4581da177e4SLinus Torvalds 
459b0d3ded1STrond Myklebust static void nfs_delegation_run_state_manager(struct nfs_client *clp)
46058d9714aSTrond Myklebust {
461b0d3ded1STrond Myklebust 	if (test_bit(NFS4CLNT_DELEGRETURN, &clp->cl_state))
462b0d3ded1STrond Myklebust 		nfs4_schedule_state_manager(clp);
46358d9714aSTrond Myklebust }
46458d9714aSTrond Myklebust 
465a1d0b5eeSTrond Myklebust void nfs_remove_bad_delegation(struct inode *inode)
466a1d0b5eeSTrond Myklebust {
467a1d0b5eeSTrond Myklebust 	struct nfs_delegation *delegation;
468a1d0b5eeSTrond Myklebust 
469a1d0b5eeSTrond Myklebust 	delegation = nfs_detach_delegation(NFS_I(inode), NFS_SERVER(inode));
470a1d0b5eeSTrond Myklebust 	if (delegation) {
471a1d0b5eeSTrond Myklebust 		nfs_inode_find_state_and_recover(inode, &delegation->stateid);
472a1d0b5eeSTrond Myklebust 		nfs_free_delegation(delegation);
473a1d0b5eeSTrond Myklebust 	}
474a1d0b5eeSTrond Myklebust }
4759cb81968SAndy Adamson EXPORT_SYMBOL_GPL(nfs_remove_bad_delegation);
476a1d0b5eeSTrond Myklebust 
477d3978bb3SChuck Lever /**
478d3978bb3SChuck Lever  * nfs_expire_all_delegation_types
479d3978bb3SChuck Lever  * @clp: client to process
480d3978bb3SChuck Lever  * @flags: delegation types to expire
481d3978bb3SChuck Lever  *
482d3978bb3SChuck Lever  */
48331f09607SAlexandros Batsakis void nfs_expire_all_delegation_types(struct nfs_client *clp, fmode_t flags)
484c79571a5SAlexandros Batsakis {
485c79571a5SAlexandros Batsakis 	nfs_client_mark_return_all_delegation_types(clp, flags);
486c79571a5SAlexandros Batsakis 	nfs_delegation_run_state_manager(clp);
487c79571a5SAlexandros Batsakis }
488c79571a5SAlexandros Batsakis 
489d3978bb3SChuck Lever /**
490d3978bb3SChuck Lever  * nfs_expire_all_delegations
491d3978bb3SChuck Lever  * @clp: client to process
492d3978bb3SChuck Lever  *
493d3978bb3SChuck Lever  */
494adfa6f98SDavid Howells void nfs_expire_all_delegations(struct nfs_client *clp)
49558d9714aSTrond Myklebust {
496c79571a5SAlexandros Batsakis 	nfs_expire_all_delegation_types(clp, FMODE_READ|FMODE_WRITE);
49758d9714aSTrond Myklebust }
49858d9714aSTrond Myklebust 
499d3978bb3SChuck Lever static void nfs_mark_return_unreferenced_delegations(struct nfs_server *server)
500b7391f44STrond Myklebust {
501b7391f44STrond Myklebust 	struct nfs_delegation *delegation;
502b7391f44STrond Myklebust 
503d3978bb3SChuck Lever 	list_for_each_entry_rcu(delegation, &server->delegations, super_list) {
504b7391f44STrond Myklebust 		if (test_and_clear_bit(NFS_DELEGATION_REFERENCED, &delegation->flags))
505b7391f44STrond Myklebust 			continue;
506ed1e6211STrond Myklebust 		nfs_mark_return_delegation(server, delegation);
507b7391f44STrond Myklebust 	}
508b7391f44STrond Myklebust }
509b7391f44STrond Myklebust 
510d3978bb3SChuck Lever /**
511d3978bb3SChuck Lever  * nfs_expire_unreferenced_delegations - Eliminate unused delegations
512d3978bb3SChuck Lever  * @clp: nfs_client to process
513d3978bb3SChuck Lever  *
514d3978bb3SChuck Lever  */
515b7391f44STrond Myklebust void nfs_expire_unreferenced_delegations(struct nfs_client *clp)
516b7391f44STrond Myklebust {
517d3978bb3SChuck Lever 	struct nfs_server *server;
518d3978bb3SChuck Lever 
519d3978bb3SChuck Lever 	rcu_read_lock();
520d3978bb3SChuck Lever 	list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link)
521d3978bb3SChuck Lever 		nfs_mark_return_unreferenced_delegations(server);
522d3978bb3SChuck Lever 	rcu_read_unlock();
523d3978bb3SChuck Lever 
524b7391f44STrond Myklebust 	nfs_delegation_run_state_manager(clp);
525b7391f44STrond Myklebust }
526b7391f44STrond Myklebust 
527d3978bb3SChuck Lever /**
528d3978bb3SChuck Lever  * nfs_async_inode_return_delegation - asynchronously return a delegation
529d3978bb3SChuck Lever  * @inode: inode to process
5308e663f0eSTrond Myklebust  * @stateid: state ID information
531d3978bb3SChuck Lever  *
532d3978bb3SChuck Lever  * Returns zero on success, or a negative errno value.
5331da177e4SLinus Torvalds  */
534d3978bb3SChuck Lever int nfs_async_inode_return_delegation(struct inode *inode,
535d3978bb3SChuck Lever 				      const nfs4_stateid *stateid)
5361da177e4SLinus Torvalds {
537ed1e6211STrond Myklebust 	struct nfs_server *server = NFS_SERVER(inode);
538ed1e6211STrond Myklebust 	struct nfs_client *clp = server->nfs_client;
5396411bd4aSTrond Myklebust 	struct nfs_delegation *delegation;
5401da177e4SLinus Torvalds 
5416411bd4aSTrond Myklebust 	rcu_read_lock();
5426411bd4aSTrond Myklebust 	delegation = rcu_dereference(NFS_I(inode)->delegation);
5432597641dSAlexandros Batsakis 
54436281caaSTrond Myklebust 	if (!clp->cl_mvops->match_stateid(&delegation->stateid, stateid)) {
5456411bd4aSTrond Myklebust 		rcu_read_unlock();
5466411bd4aSTrond Myklebust 		return -ENOENT;
5476411bd4aSTrond Myklebust 	}
548ed1e6211STrond Myklebust 	nfs_mark_return_delegation(server, delegation);
5496411bd4aSTrond Myklebust 	rcu_read_unlock();
550d3978bb3SChuck Lever 
5516411bd4aSTrond Myklebust 	nfs_delegation_run_state_manager(clp);
5526411bd4aSTrond Myklebust 	return 0;
5531da177e4SLinus Torvalds }
5541da177e4SLinus Torvalds 
555d3978bb3SChuck Lever static struct inode *
556d3978bb3SChuck Lever nfs_delegation_find_inode_server(struct nfs_server *server,
557d3978bb3SChuck Lever 				 const struct nfs_fh *fhandle)
5581da177e4SLinus Torvalds {
5591da177e4SLinus Torvalds 	struct nfs_delegation *delegation;
5601da177e4SLinus Torvalds 	struct inode *res = NULL;
561d3978bb3SChuck Lever 
562d3978bb3SChuck Lever 	list_for_each_entry_rcu(delegation, &server->delegations, super_list) {
56386e89489STrond Myklebust 		spin_lock(&delegation->lock);
56486e89489STrond Myklebust 		if (delegation->inode != NULL &&
56586e89489STrond Myklebust 		    nfs_compare_fh(fhandle, &NFS_I(delegation->inode)->fh) == 0) {
5661da177e4SLinus Torvalds 			res = igrab(delegation->inode);
5671da177e4SLinus Torvalds 		}
56886e89489STrond Myklebust 		spin_unlock(&delegation->lock);
56986e89489STrond Myklebust 		if (res != NULL)
57086e89489STrond Myklebust 			break;
5711da177e4SLinus Torvalds 	}
572d3978bb3SChuck Lever 	return res;
573d3978bb3SChuck Lever }
574d3978bb3SChuck Lever 
575d3978bb3SChuck Lever /**
576d3978bb3SChuck Lever  * nfs_delegation_find_inode - retrieve the inode associated with a delegation
577d3978bb3SChuck Lever  * @clp: client state handle
578d3978bb3SChuck Lever  * @fhandle: filehandle from a delegation recall
579d3978bb3SChuck Lever  *
580d3978bb3SChuck Lever  * Returns pointer to inode matching "fhandle," or NULL if a matching inode
581d3978bb3SChuck Lever  * cannot be found.
582d3978bb3SChuck Lever  */
583d3978bb3SChuck Lever struct inode *nfs_delegation_find_inode(struct nfs_client *clp,
584d3978bb3SChuck Lever 					const struct nfs_fh *fhandle)
585d3978bb3SChuck Lever {
586d3978bb3SChuck Lever 	struct nfs_server *server;
587d3978bb3SChuck Lever 	struct inode *res = NULL;
588d3978bb3SChuck Lever 
589d3978bb3SChuck Lever 	rcu_read_lock();
590d3978bb3SChuck Lever 	list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link) {
591d3978bb3SChuck Lever 		res = nfs_delegation_find_inode_server(server, fhandle);
592d3978bb3SChuck Lever 		if (res != NULL)
593d3978bb3SChuck Lever 			break;
594d3978bb3SChuck Lever 	}
5958383e460STrond Myklebust 	rcu_read_unlock();
5961da177e4SLinus Torvalds 	return res;
5971da177e4SLinus Torvalds }
5981da177e4SLinus Torvalds 
599d3978bb3SChuck Lever static void nfs_delegation_mark_reclaim_server(struct nfs_server *server)
600d3978bb3SChuck Lever {
601d3978bb3SChuck Lever 	struct nfs_delegation *delegation;
602d3978bb3SChuck Lever 
603d3978bb3SChuck Lever 	list_for_each_entry_rcu(delegation, &server->delegations, super_list)
604d3978bb3SChuck Lever 		set_bit(NFS_DELEGATION_NEED_RECLAIM, &delegation->flags);
605d3978bb3SChuck Lever }
606d3978bb3SChuck Lever 
607d3978bb3SChuck Lever /**
608d3978bb3SChuck Lever  * nfs_delegation_mark_reclaim - mark all delegations as needing to be reclaimed
609d3978bb3SChuck Lever  * @clp: nfs_client to process
610d3978bb3SChuck Lever  *
6111da177e4SLinus Torvalds  */
612adfa6f98SDavid Howells void nfs_delegation_mark_reclaim(struct nfs_client *clp)
6131da177e4SLinus Torvalds {
614d3978bb3SChuck Lever 	struct nfs_server *server;
615d3978bb3SChuck Lever 
6168383e460STrond Myklebust 	rcu_read_lock();
617d3978bb3SChuck Lever 	list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link)
618d3978bb3SChuck Lever 		nfs_delegation_mark_reclaim_server(server);
6198383e460STrond Myklebust 	rcu_read_unlock();
6201da177e4SLinus Torvalds }
6211da177e4SLinus Torvalds 
622d3978bb3SChuck Lever /**
623d3978bb3SChuck Lever  * nfs_delegation_reap_unclaimed - reap unclaimed delegations after reboot recovery is done
624d3978bb3SChuck Lever  * @clp: nfs_client to process
625d3978bb3SChuck Lever  *
6261da177e4SLinus Torvalds  */
627adfa6f98SDavid Howells void nfs_delegation_reap_unclaimed(struct nfs_client *clp)
6281da177e4SLinus Torvalds {
6298383e460STrond Myklebust 	struct nfs_delegation *delegation;
630d3978bb3SChuck Lever 	struct nfs_server *server;
63186e89489STrond Myklebust 	struct inode *inode;
632d3978bb3SChuck Lever 
6338383e460STrond Myklebust restart:
6348383e460STrond Myklebust 	rcu_read_lock();
635d3978bb3SChuck Lever 	list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link) {
636d3978bb3SChuck Lever 		list_for_each_entry_rcu(delegation, &server->delegations,
637d3978bb3SChuck Lever 								super_list) {
638d3978bb3SChuck Lever 			if (test_bit(NFS_DELEGATION_NEED_RECLAIM,
639d3978bb3SChuck Lever 						&delegation->flags) == 0)
6401da177e4SLinus Torvalds 				continue;
64186e89489STrond Myklebust 			inode = nfs_delegation_grab_inode(delegation);
64286e89489STrond Myklebust 			if (inode == NULL)
64386e89489STrond Myklebust 				continue;
644d3978bb3SChuck Lever 			delegation = nfs_detach_delegation(NFS_I(inode),
645d3978bb3SChuck Lever 								server);
6468383e460STrond Myklebust 			rcu_read_unlock();
647d3978bb3SChuck Lever 
6488383e460STrond Myklebust 			if (delegation != NULL)
649905f8d16STrond Myklebust 				nfs_free_delegation(delegation);
65086e89489STrond Myklebust 			iput(inode);
6518383e460STrond Myklebust 			goto restart;
6521da177e4SLinus Torvalds 		}
653d3978bb3SChuck Lever 	}
6548383e460STrond Myklebust 	rcu_read_unlock();
6551da177e4SLinus Torvalds }
6563e4f6290STrond Myklebust 
657d3978bb3SChuck Lever /**
658d3978bb3SChuck Lever  * nfs_delegations_present - check for existence of delegations
659d3978bb3SChuck Lever  * @clp: client state handle
660d3978bb3SChuck Lever  *
661d3978bb3SChuck Lever  * Returns one if there are any nfs_delegation structures attached
662d3978bb3SChuck Lever  * to this nfs_client.
663d3978bb3SChuck Lever  */
664d3978bb3SChuck Lever int nfs_delegations_present(struct nfs_client *clp)
665d3978bb3SChuck Lever {
666d3978bb3SChuck Lever 	struct nfs_server *server;
667d3978bb3SChuck Lever 	int ret = 0;
668d3978bb3SChuck Lever 
669d3978bb3SChuck Lever 	rcu_read_lock();
670d3978bb3SChuck Lever 	list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link)
671d3978bb3SChuck Lever 		if (!list_empty(&server->delegations)) {
672d3978bb3SChuck Lever 			ret = 1;
673d3978bb3SChuck Lever 			break;
674d3978bb3SChuck Lever 		}
675d3978bb3SChuck Lever 	rcu_read_unlock();
676d3978bb3SChuck Lever 	return ret;
677d3978bb3SChuck Lever }
678d3978bb3SChuck Lever 
679d3978bb3SChuck Lever /**
680d3978bb3SChuck Lever  * nfs4_copy_delegation_stateid - Copy inode's state ID information
681d3978bb3SChuck Lever  * @dst: stateid data structure to fill in
682d3978bb3SChuck Lever  * @inode: inode to check
6830032a7a7STrond Myklebust  * @flags: delegation type requirement
684d3978bb3SChuck Lever  *
6850032a7a7STrond Myklebust  * Returns "true" and fills in "dst->data" * if inode had a delegation,
6860032a7a7STrond Myklebust  * otherwise "false" is returned.
687d3978bb3SChuck Lever  */
6880032a7a7STrond Myklebust bool nfs4_copy_delegation_stateid(nfs4_stateid *dst, struct inode *inode,
6890032a7a7STrond Myklebust 		fmode_t flags)
6903e4f6290STrond Myklebust {
6913e4f6290STrond Myklebust 	struct nfs_inode *nfsi = NFS_I(inode);
6923e4f6290STrond Myklebust 	struct nfs_delegation *delegation;
6930032a7a7STrond Myklebust 	bool ret;
6943e4f6290STrond Myklebust 
6950032a7a7STrond Myklebust 	flags &= FMODE_READ|FMODE_WRITE;
6968383e460STrond Myklebust 	rcu_read_lock();
6978383e460STrond Myklebust 	delegation = rcu_dereference(nfsi->delegation);
6980032a7a7STrond Myklebust 	ret = (delegation != NULL && (delegation->type & flags) == flags);
6990032a7a7STrond Myklebust 	if (ret) {
700f597c537STrond Myklebust 		nfs4_stateid_copy(dst, &delegation->stateid);
7010032a7a7STrond Myklebust 		nfs_mark_delegation_referenced(delegation);
7023e4f6290STrond Myklebust 	}
7038383e460STrond Myklebust 	rcu_read_unlock();
7048383e460STrond Myklebust 	return ret;
7053e4f6290STrond Myklebust }
706