NFSv4: Deal more correctly with duplicate delegations
authorTrond Myklebust <Trond.Myklebust@netapp.com>
Fri, 25 Jan 2008 21:38:18 +0000 (16:38 -0500)
committerTrond Myklebust <Trond.Myklebust@netapp.com>
Wed, 30 Jan 2008 07:06:12 +0000 (02:06 -0500)
If a (broken?) server hands out two different delegations for the same
file, then we should return one of them.

Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
fs/nfs/delegation.c

index 2dead8d1dd554c4e37022ac702bbc4c3164e365c..b9eadd18ba7070a796497a7601fed53f3700ddb4 100644 (file)
@@ -125,6 +125,32 @@ void nfs_inode_reclaim_delegation(struct inode *inode, struct rpc_cred *cred, st
        put_rpccred(oldcred);
 }
 
+static int nfs_do_return_delegation(struct inode *inode, struct nfs_delegation *delegation, int issync)
+{
+       int res = 0;
+
+       res = nfs4_proc_delegreturn(inode, delegation->cred, &delegation->stateid, issync);
+       nfs_free_delegation(delegation);
+       return res;
+}
+
+static struct nfs_delegation *nfs_detach_delegation_locked(struct nfs_inode *nfsi, const nfs4_stateid *stateid)
+{
+       struct nfs_delegation *delegation = rcu_dereference(nfsi->delegation);
+
+       if (delegation == NULL)
+               goto nomatch;
+       if (stateid != NULL && memcmp(delegation->stateid.data, stateid->data,
+                               sizeof(delegation->stateid.data)) != 0)
+               goto nomatch;
+       list_del_rcu(&delegation->super_list);
+       nfsi->delegation_state = 0;
+       rcu_assign_pointer(nfsi->delegation, NULL);
+       return delegation;
+nomatch:
+       return NULL;
+}
+
 /*
  * Set up a delegation on an inode
  */
@@ -133,6 +159,7 @@ int nfs_inode_set_delegation(struct inode *inode, struct rpc_cred *cred, struct
        struct nfs_client *clp = NFS_SERVER(inode)->nfs_client;
        struct nfs_inode *nfsi = NFS_I(inode);
        struct nfs_delegation *delegation;
+       struct nfs_delegation *freeme = NULL;
        int status = 0;
 
        delegation = kmalloc(sizeof(*delegation), GFP_KERNEL);
@@ -147,42 +174,45 @@ int nfs_inode_set_delegation(struct inode *inode, struct rpc_cred *cred, struct
        delegation->inode = inode;
 
        spin_lock(&clp->cl_lock);
-       if (rcu_dereference(nfsi->delegation) == NULL) {
-               list_add_rcu(&delegation->super_list, &clp->cl_delegations);
-               nfsi->delegation_state = delegation->type;
-               rcu_assign_pointer(nfsi->delegation, delegation);
-               delegation = NULL;
-       } else {
+       if (rcu_dereference(nfsi->delegation) != NULL) {
                if (memcmp(&delegation->stateid, &nfsi->delegation->stateid,
-                                       sizeof(delegation->stateid)) != 0 ||
-                               delegation->type != nfsi->delegation->type) {
-                       printk(KERN_WARNING "%s: server %s handed out "
-                                       "a duplicate delegation!\n",
-                                       __FUNCTION__, clp->cl_hostname);
-                       status = -EIO;
+                                       sizeof(delegation->stateid)) == 0 &&
+                               delegation->type == nfsi->delegation->type) {
+                       goto out;
+               }
+               /*
+                * Deal with broken servers that hand out two
+                * delegations for the same file.
+                */
+               dfprintk(FILE, "%s: server %s handed out "
+                               "a duplicate delegation!\n",
+                               __FUNCTION__, clp->cl_hostname);
+               if (delegation->type <= nfsi->delegation->type) {
+                       freeme = delegation;
+                       delegation = NULL;
+                       goto out;
                }
+               freeme = nfs_detach_delegation_locked(nfsi, NULL);
        }
+       list_add_rcu(&delegation->super_list, &clp->cl_delegations);
+       nfsi->delegation_state = delegation->type;
+       rcu_assign_pointer(nfsi->delegation, delegation);
+       delegation = NULL;
 
        /* Ensure we revalidate the attributes and page cache! */
        spin_lock(&inode->i_lock);
        nfsi->cache_validity |= NFS_INO_REVAL_FORCED;
        spin_unlock(&inode->i_lock);
 
+out:
        spin_unlock(&clp->cl_lock);
        if (delegation != NULL)
                nfs_free_delegation(delegation);
+       if (freeme != NULL)
+               nfs_do_return_delegation(inode, freeme, 0);
        return status;
 }
 
-static int nfs_do_return_delegation(struct inode *inode, struct nfs_delegation *delegation, int issync)
-{
-       int res = 0;
-
-       res = nfs4_proc_delegreturn(inode, delegation->cred, &delegation->stateid, issync);
-       nfs_free_delegation(delegation);
-       return res;
-}
-
 /* Sync all data to disk upon delegation return */
 static void nfs_msync_inode(struct inode *inode)
 {
@@ -211,23 +241,6 @@ static int __nfs_inode_return_delegation(struct inode *inode, struct nfs_delegat
        return nfs_do_return_delegation(inode, delegation, 1);
 }
 
-static struct nfs_delegation *nfs_detach_delegation_locked(struct nfs_inode *nfsi, const nfs4_stateid *stateid)
-{
-       struct nfs_delegation *delegation = rcu_dereference(nfsi->delegation);
-
-       if (delegation == NULL)
-               goto nomatch;
-       if (stateid != NULL && memcmp(delegation->stateid.data, stateid->data,
-                               sizeof(delegation->stateid.data)) != 0)
-               goto nomatch;
-       list_del_rcu(&delegation->super_list);
-       nfsi->delegation_state = 0;
-       rcu_assign_pointer(nfsi->delegation, NULL);
-       return delegation;
-nomatch:
-       return NULL;
-}
-
 /*
  * This function returns the delegation without reclaiming opens
  * or protecting against delegation reclaims.