Merge tag 'nfs-for-4.9-1' of git://git.linux-nfs.org/projects/anna/linux-nfs
[cascardo/linux.git] / fs / nfs / delegation.c
index 322c258..dff600a 100644 (file)
@@ -41,6 +41,17 @@ void nfs_mark_delegation_referenced(struct nfs_delegation *delegation)
        set_bit(NFS_DELEGATION_REFERENCED, &delegation->flags);
 }
 
+static bool
+nfs4_is_valid_delegation(const struct nfs_delegation *delegation,
+               fmode_t flags)
+{
+       if (delegation != NULL && (delegation->type & flags) == flags &&
+           !test_bit(NFS_DELEGATION_REVOKED, &delegation->flags) &&
+           !test_bit(NFS_DELEGATION_RETURNING, &delegation->flags))
+               return true;
+       return false;
+}
+
 static int
 nfs4_do_check_delegation(struct inode *inode, fmode_t flags, bool mark)
 {
@@ -50,8 +61,7 @@ nfs4_do_check_delegation(struct inode *inode, fmode_t flags, bool mark)
        flags &= FMODE_READ|FMODE_WRITE;
        rcu_read_lock();
        delegation = rcu_dereference(NFS_I(inode)->delegation);
-       if (delegation != NULL && (delegation->type & flags) == flags &&
-           !test_bit(NFS_DELEGATION_RETURNING, &delegation->flags)) {
+       if (nfs4_is_valid_delegation(delegation, flags)) {
                if (mark)
                        nfs_mark_delegation_referenced(delegation);
                ret = 1;
@@ -185,15 +195,13 @@ void nfs_inode_reclaim_delegation(struct inode *inode, struct rpc_cred *cred,
                        rcu_read_unlock();
                        put_rpccred(oldcred);
                        trace_nfs4_reclaim_delegation(inode, res->delegation_type);
-               } else {
-                       /* We appear to have raced with a delegation return. */
-                       spin_unlock(&delegation->lock);
-                       rcu_read_unlock();
-                       nfs_inode_set_delegation(inode, cred, res);
+                       return;
                }
-       } else {
-               rcu_read_unlock();
+               /* We appear to have raced with a delegation return. */
+               spin_unlock(&delegation->lock);
        }
+       rcu_read_unlock();
+       nfs_inode_set_delegation(inode, cred, res);
 }
 
 static int nfs_do_return_delegation(struct inode *inode, struct nfs_delegation *delegation, int issync)
@@ -642,28 +650,49 @@ static void nfs_client_mark_return_unused_delegation_types(struct nfs_client *cl
        rcu_read_unlock();
 }
 
-static void nfs_revoke_delegation(struct inode *inode)
+static void nfs_mark_delegation_revoked(struct nfs_server *server,
+               struct nfs_delegation *delegation)
+{
+       set_bit(NFS_DELEGATION_REVOKED, &delegation->flags);
+       delegation->stateid.type = NFS4_INVALID_STATEID_TYPE;
+       nfs_mark_return_delegation(server, delegation);
+}
+
+static bool nfs_revoke_delegation(struct inode *inode,
+               const nfs4_stateid *stateid)
 {
        struct nfs_delegation *delegation;
+       nfs4_stateid tmp;
+       bool ret = false;
+
        rcu_read_lock();
        delegation = rcu_dereference(NFS_I(inode)->delegation);
-       if (delegation != NULL) {
-               set_bit(NFS_DELEGATION_REVOKED, &delegation->flags);
-               nfs_mark_return_delegation(NFS_SERVER(inode), delegation);
-       }
+       if (delegation == NULL)
+               goto out;
+       if (stateid == NULL) {
+               nfs4_stateid_copy(&tmp, &delegation->stateid);
+               stateid = &tmp;
+       } else if (!nfs4_stateid_match(stateid, &delegation->stateid))
+               goto out;
+       nfs_mark_delegation_revoked(NFS_SERVER(inode), delegation);
+       ret = true;
+out:
        rcu_read_unlock();
+       if (ret)
+               nfs_inode_find_state_and_recover(inode, stateid);
+       return ret;
 }
 
-void nfs_remove_bad_delegation(struct inode *inode)
+void nfs_remove_bad_delegation(struct inode *inode,
+               const nfs4_stateid *stateid)
 {
        struct nfs_delegation *delegation;
 
-       nfs_revoke_delegation(inode);
+       if (!nfs_revoke_delegation(inode, stateid))
+               return;
        delegation = nfs_inode_detach_delegation(inode);
-       if (delegation) {
-               nfs_inode_find_state_and_recover(inode, &delegation->stateid);
+       if (delegation)
                nfs_free_delegation(delegation);
-       }
 }
 EXPORT_SYMBOL_GPL(nfs_remove_bad_delegation);
 
@@ -786,8 +815,15 @@ static void nfs_delegation_mark_reclaim_server(struct nfs_server *server)
 {
        struct nfs_delegation *delegation;
 
-       list_for_each_entry_rcu(delegation, &server->delegations, super_list)
+       list_for_each_entry_rcu(delegation, &server->delegations, super_list) {
+               /*
+                * If the delegation may have been admin revoked, then we
+                * cannot reclaim it.
+                */
+               if (test_bit(NFS_DELEGATION_TEST_EXPIRED, &delegation->flags))
+                       continue;
                set_bit(NFS_DELEGATION_NEED_RECLAIM, &delegation->flags);
+       }
 }
 
 /**
@@ -851,6 +887,141 @@ restart:
        rcu_read_unlock();
 }
 
+static inline bool nfs4_server_rebooted(const struct nfs_client *clp)
+{
+       return (clp->cl_state & (BIT(NFS4CLNT_CHECK_LEASE) |
+                               BIT(NFS4CLNT_LEASE_EXPIRED) |
+                               BIT(NFS4CLNT_SESSION_RESET))) != 0;
+}
+
+static void nfs_mark_test_expired_delegation(struct nfs_server *server,
+           struct nfs_delegation *delegation)
+{
+       if (delegation->stateid.type == NFS4_INVALID_STATEID_TYPE)
+               return;
+       clear_bit(NFS_DELEGATION_NEED_RECLAIM, &delegation->flags);
+       set_bit(NFS_DELEGATION_TEST_EXPIRED, &delegation->flags);
+       set_bit(NFS4CLNT_DELEGATION_EXPIRED, &server->nfs_client->cl_state);
+}
+
+static void nfs_inode_mark_test_expired_delegation(struct nfs_server *server,
+               struct inode *inode)
+{
+       struct nfs_delegation *delegation;
+
+       rcu_read_lock();
+       delegation = rcu_dereference(NFS_I(inode)->delegation);
+       if (delegation)
+               nfs_mark_test_expired_delegation(server, delegation);
+       rcu_read_unlock();
+
+}
+
+static void nfs_delegation_mark_test_expired_server(struct nfs_server *server)
+{
+       struct nfs_delegation *delegation;
+
+       list_for_each_entry_rcu(delegation, &server->delegations, super_list)
+               nfs_mark_test_expired_delegation(server, delegation);
+}
+
+/**
+ * nfs_mark_test_expired_all_delegations - mark all delegations for testing
+ * @clp: nfs_client to process
+ *
+ * Iterates through all the delegations associated with this server and
+ * marks them as needing to be checked for validity.
+ */
+void nfs_mark_test_expired_all_delegations(struct nfs_client *clp)
+{
+       struct nfs_server *server;
+
+       rcu_read_lock();
+       list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link)
+               nfs_delegation_mark_test_expired_server(server);
+       rcu_read_unlock();
+}
+
+/**
+ * nfs_reap_expired_delegations - reap expired delegations
+ * @clp: nfs_client to process
+ *
+ * Iterates through all the delegations associated with this server and
+ * checks if they have may have been revoked. This function is usually
+ * expected to be called in cases where the server may have lost its
+ * lease.
+ */
+void nfs_reap_expired_delegations(struct nfs_client *clp)
+{
+       const struct nfs4_minor_version_ops *ops = clp->cl_mvops;
+       struct nfs_delegation *delegation;
+       struct nfs_server *server;
+       struct inode *inode;
+       struct rpc_cred *cred;
+       nfs4_stateid stateid;
+
+restart:
+       rcu_read_lock();
+       list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link) {
+               list_for_each_entry_rcu(delegation, &server->delegations,
+                                                               super_list) {
+                       if (test_bit(NFS_DELEGATION_RETURNING,
+                                               &delegation->flags))
+                               continue;
+                       if (test_bit(NFS_DELEGATION_TEST_EXPIRED,
+                                               &delegation->flags) == 0)
+                               continue;
+                       if (!nfs_sb_active(server->super))
+                               continue;
+                       inode = nfs_delegation_grab_inode(delegation);
+                       if (inode == NULL) {
+                               rcu_read_unlock();
+                               nfs_sb_deactive(server->super);
+                               goto restart;
+                       }
+                       cred = get_rpccred_rcu(delegation->cred);
+                       nfs4_stateid_copy(&stateid, &delegation->stateid);
+                       clear_bit(NFS_DELEGATION_TEST_EXPIRED, &delegation->flags);
+                       rcu_read_unlock();
+                       if (cred != NULL &&
+                           ops->test_and_free_expired(server, &stateid, cred) < 0) {
+                               nfs_revoke_delegation(inode, &stateid);
+                               nfs_inode_find_state_and_recover(inode, &stateid);
+                       }
+                       put_rpccred(cred);
+                       if (nfs4_server_rebooted(clp)) {
+                               nfs_inode_mark_test_expired_delegation(server,inode);
+                               iput(inode);
+                               nfs_sb_deactive(server->super);
+                               return;
+                       }
+                       iput(inode);
+                       nfs_sb_deactive(server->super);
+                       goto restart;
+               }
+       }
+       rcu_read_unlock();
+}
+
+void nfs_inode_find_delegation_state_and_recover(struct inode *inode,
+               const nfs4_stateid *stateid)
+{
+       struct nfs_client *clp = NFS_SERVER(inode)->nfs_client;
+       struct nfs_delegation *delegation;
+       bool found = false;
+
+       rcu_read_lock();
+       delegation = rcu_dereference(NFS_I(inode)->delegation);
+       if (delegation &&
+           nfs4_stateid_match_other(&delegation->stateid, stateid)) {
+               nfs_mark_test_expired_delegation(NFS_SERVER(inode), delegation);
+               found = true;
+       }
+       rcu_read_unlock();
+       if (found)
+               nfs4_schedule_state_manager(clp);
+}
+
 /**
  * nfs_delegations_present - check for existence of delegations
  * @clp: client state handle
@@ -893,7 +1064,7 @@ bool nfs4_copy_delegation_stateid(struct inode *inode, fmode_t flags,
        flags &= FMODE_READ|FMODE_WRITE;
        rcu_read_lock();
        delegation = rcu_dereference(nfsi->delegation);
-       ret = (delegation != NULL && (delegation->type & flags) == flags);
+       ret = nfs4_is_valid_delegation(delegation, flags);
        if (ret) {
                nfs4_stateid_copy(dst, &delegation->stateid);
                nfs_mark_delegation_referenced(delegation);