static void nfs_mark_delegation_revoked(struct nfs_server *server,
struct nfs_delegation *delegation)
{
+ bool put_ref = false;
+
if (test_and_set_bit(NFS_DELEGATION_REVOKED, &delegation->flags))
return;
atomic_long_dec(&server->nr_active_delegations);
if (!test_bit(NFS_DELEGATION_RETURNING, &delegation->flags))
nfs_clear_verifier_delegated(delegation->inode);
+
+ spin_lock(&server->delegations_lock);
+ if (!list_empty(&delegation->entry)) {
+ list_del_init(&delegation->entry);
+ put_ref = true;
+ }
+ spin_unlock(&server->delegations_lock);
+
+ if (put_ref)
+ nfs_put_delegation(delegation);
}
void nfs_put_delegation(struct nfs_delegation *delegation)
static void nfs_mark_return_delegation(struct nfs_server *server,
struct nfs_delegation *delegation)
{
- set_bit(NFS_DELEGATION_RETURN, &delegation->flags);
- set_bit(NFS4SERV_DELEGRETURN, &server->delegation_flags);
+ spin_lock(&server->delegations_lock);
+ if (list_empty(&delegation->entry))
+ refcount_inc(&delegation->refcount);
+ list_move_tail(&delegation->entry, &server->delegations_return);
+ spin_unlock(&server->delegations_lock);
+
set_bit(NFS4CLNT_DELEGRETURN, &server->nfs_client->cl_state);
}
}
static bool
-nfs_detach_delegation_locked(struct nfs_inode *nfsi,
+nfs_detach_delegations_locked(struct nfs_inode *nfsi,
struct nfs_delegation *delegation,
struct nfs_client *clp)
{
deleg_cur = rcu_dereference_protected(nfsi->delegation,
lockdep_is_held(&clp->cl_lock));
if (delegation == deleg_cur)
- ret = nfs_detach_delegation_locked(nfsi, delegation, clp);
+ ret = nfs_detach_delegations_locked(nfsi, delegation, clp);
spin_unlock(&clp->cl_lock);
return ret;
}
delegation->cred = get_cred(cred);
delegation->inode = inode;
delegation->flags = 1<<NFS_DELEGATION_REFERENCED;
+ INIT_LIST_HEAD(&delegation->entry);
switch (deleg_type) {
case NFS4_OPEN_DELEGATE_READ_ATTRS_DELEG:
case NFS4_OPEN_DELEGATE_WRITE_ATTRS_DELEG:
&old_delegation->flags))
goto out;
}
- if (!nfs_detach_delegation_locked(nfsi, old_delegation, clp))
+ if (!nfs_detach_delegations_locked(nfsi, old_delegation, clp))
goto out;
freeme = old_delegation;
add_new:
return nfs_do_return_delegation(inode, delegation, issync);
}
-static int nfs_server_return_marked_delegations(struct nfs_server *server,
- void __always_unused *data)
+static int nfs_return_one_delegation(struct nfs_server *server)
{
struct nfs_delegation *delegation;
- struct nfs_delegation *prev;
struct inode *inode;
- struct inode *place_holder = NULL;
- struct nfs_delegation *place_holder_deleg = NULL;
int err = 0;
- if (!test_and_clear_bit(NFS4SERV_DELEGRETURN,
- &server->delegation_flags))
- return 0;
-restart:
- /*
- * To avoid quadratic looping we hold a reference
- * to an inode place_holder. Each time we restart, we
- * list delegation in the server from the delegations
- * of that inode.
- * prev is an RCU-protected pointer to a delegation which
- * wasn't marked for return and might be a good choice for
- * the next place_holder.
- */
- prev = NULL;
- delegation = NULL;
- rcu_read_lock();
- if (place_holder)
- delegation = rcu_dereference(NFS_I(place_holder)->delegation);
- if (!delegation || delegation != place_holder_deleg)
- delegation = list_entry_rcu(server->delegations.next,
- struct nfs_delegation, super_list);
- list_for_each_entry_from_rcu(delegation, &server->delegations, super_list) {
- struct inode *to_put = NULL;
-
- trace_nfs_delegation_need_return(delegation);
-
- if (!test_and_clear_bit(NFS_DELEGATION_RETURN, &delegation->flags) ||
- test_bit(NFS_DELEGATION_RETURNING, &delegation->flags) ||
- test_bit(NFS_DELEGATION_RETURN_DELAYED, &delegation->flags) ||
- test_bit(NFS_DELEGATION_REVOKED, &delegation->flags)) {
- if (nfs4_is_valid_delegation(delegation, 0))
- prev = delegation;
- continue;
- }
+ spin_lock(&server->delegations_lock);
+ delegation = list_first_entry_or_null(&server->delegations_return,
+ struct nfs_delegation, entry);
+ if (!delegation) {
+ spin_unlock(&server->delegations_lock);
+ return 0; /* no more delegations */
+ }
+ list_del_init(&delegation->entry);
+ spin_unlock(&server->delegations_lock);
- inode = nfs_delegation_grab_inode(delegation);
- if (inode == NULL)
- continue;
+ spin_lock(&delegation->lock);
+ inode = delegation->inode;
+ if (!inode || !igrab(inode)) {
+ spin_unlock(&delegation->lock);
+ goto out_put_delegation;
+ }
+ if (test_bit(NFS_DELEGATION_RETURN_DELAYED, &delegation->flags) ||
+ test_bit(NFS_DELEGATION_REVOKED, &delegation->flags) ||
+ test_and_set_bit(NFS_DELEGATION_RETURNING, &delegation->flags)) {
+ spin_unlock(&delegation->lock);
+ goto out_put_inode;
+ }
+ clear_bit(NFS_DELEGATION_RETURN_DELAYED, &delegation->flags);
+ spin_unlock(&delegation->lock);
- if (prev) {
- struct inode *tmp = nfs_delegation_grab_inode(prev);
- if (tmp) {
- to_put = place_holder;
- place_holder = tmp;
- place_holder_deleg = prev;
- }
- }
+ nfs_clear_verifier_delegated(inode);
- delegation = nfs_start_delegation_return(NFS_I(inode));
- rcu_read_unlock();
+ err = nfs_end_delegation_return(inode, delegation, 0);
+ if (err) {
+ nfs_mark_return_delegation(server, delegation);
+ goto out_put_inode;
+ }
- iput(to_put);
+out_put_inode:
+ iput(inode);
+out_put_delegation:
+ nfs_put_delegation(delegation);
+ if (err)
+ return err;
+ return 1; /* keep going */
+}
- if (delegation) {
- err = nfs_end_delegation_return(inode, delegation, 0);
- nfs_put_delegation(delegation);
- }
+static int nfs_server_return_marked_delegations(struct nfs_server *server,
+ void __always_unused *data)
+{
+ int err;
- iput(inode);
+ while ((err = nfs_return_one_delegation(server)) > 0)
cond_resched();
- if (!err)
- goto restart;
- set_bit(NFS4SERV_DELEGRETURN, &server->delegation_flags);
- set_bit(NFS4CLNT_DELEGRETURN, &server->nfs_client->cl_state);
- goto out;
- }
- rcu_read_unlock();
-out:
- iput(place_holder);
return err;
}
if (!test_and_clear_bit(NFS4SERV_DELEGRETURN_DELAYED,
&server->delegation_flags))
- goto out;
- list_for_each_entry_rcu (d, &server->delegations, super_list) {
- if (!test_bit(NFS_DELEGATION_RETURN_DELAYED, &d->flags))
- continue;
- nfs_mark_return_delegation(server, d);
- clear_bit(NFS_DELEGATION_RETURN_DELAYED, &d->flags);
+ return false;
+
+ spin_lock(&server->delegations_lock);
+ list_for_each_entry_rcu(d, &server->delegations_return, entry) {
+ if (test_bit(NFS_DELEGATION_RETURN_DELAYED, &d->flags))
+ clear_bit(NFS_DELEGATION_RETURN_DELAYED, &d->flags);
ret = true;
}
-out:
+
return ret;
}
bool ret = false;
if (!test_and_clear_bit(NFS4CLNT_DELEGRETURN_DELAYED, &clp->cl_state))
- goto out;
+ return false;
+
rcu_read_lock();
list_for_each_entry_rcu (server, &clp->cl_superblocks, client_link) {
if (nfs_server_clear_delayed_delegations(server))
ret = true;
}
rcu_read_unlock();
-out:
+
+ if (ret)
+ set_bit(NFS4CLNT_DELEGRETURN, &clp->cl_state);
return ret;
}
{
struct inode *inode;
- if (test_bit(NFS_DELEGATION_RETURN, &delegation->flags) ||
+ if (!list_empty_careful(&server->delegations_return) ||
test_bit(NFS_DELEGATION_RETURN_IF_CLOSED, &delegation->flags))
return;
spin_lock(&delegation->lock);