afs: Fix application of status and callback to be under same lock
[linux-2.6-microblaze.git] / fs / afs / dir.c
index 9a466be..f7344b0 100644 (file)
@@ -18,6 +18,7 @@
 #include <linux/sched.h>
 #include <linux/task_io_accounting_ops.h>
 #include "internal.h"
+#include "afs_fs.h"
 #include "xdr_fs.h"
 
 static struct dentry *afs_lookup(struct inode *dir, struct dentry *dentry,
@@ -102,8 +103,7 @@ struct afs_lookup_cookie {
        bool                    found;
        bool                    one_only;
        unsigned short          nr_fids;
-       struct afs_file_status  *statuses;
-       struct afs_callback     *callbacks;
+       struct afs_status_cb    *statuses;
        struct afs_fid          fids[50];
 };
 
@@ -640,6 +640,7 @@ static struct inode *afs_do_lookup(struct inode *dir, struct dentry *dentry,
        struct afs_lookup_cookie *cookie;
        struct afs_cb_interest *cbi = NULL;
        struct afs_super_info *as = dir->i_sb->s_fs_info;
+       struct afs_status_cb *scb;
        struct afs_iget_data data;
        struct afs_fs_cursor fc;
        struct afs_vnode *dvnode = AFS_FS_I(dir);
@@ -686,16 +687,11 @@ static struct inode *afs_do_lookup(struct inode *dir, struct dentry *dentry,
 
        /* Need space for examining all the selected files */
        inode = ERR_PTR(-ENOMEM);
-       cookie->statuses = kcalloc(cookie->nr_fids, sizeof(struct afs_file_status),
-                                  GFP_KERNEL);
+       cookie->statuses = kvcalloc(cookie->nr_fids, sizeof(struct afs_status_cb),
+                                   GFP_KERNEL);
        if (!cookie->statuses)
                goto out;
 
-       cookie->callbacks = kcalloc(cookie->nr_fids, sizeof(struct afs_callback),
-                                   GFP_KERNEL);
-       if (!cookie->callbacks)
-               goto out_s;
-
        /* Try FS.InlineBulkStatus first.  Abort codes for the individual
         * lookups contained therein are stored in the reply without aborting
         * the whole operation.
@@ -704,7 +700,7 @@ static struct inode *afs_do_lookup(struct inode *dir, struct dentry *dentry,
                goto no_inline_bulk_status;
 
        inode = ERR_PTR(-ERESTARTSYS);
-       if (afs_begin_vnode_operation(&fc, dvnode, key)) {
+       if (afs_begin_vnode_operation(&fc, dvnode, key, true)) {
                while (afs_select_fileserver(&fc)) {
                        if (test_bit(AFS_SERVER_FL_NO_IBULK,
                                      &fc.cbi->server->flags)) {
@@ -716,7 +712,6 @@ static struct inode *afs_do_lookup(struct inode *dir, struct dentry *dentry,
                                                  afs_v2net(dvnode),
                                                  cookie->fids,
                                                  cookie->statuses,
-                                                 cookie->callbacks,
                                                  cookie->nr_fids, NULL);
                }
 
@@ -739,13 +734,13 @@ no_inline_bulk_status:
         */
        cookie->nr_fids = 1;
        inode = ERR_PTR(-ERESTARTSYS);
-       if (afs_begin_vnode_operation(&fc, dvnode, key)) {
+       if (afs_begin_vnode_operation(&fc, dvnode, key, true)) {
                while (afs_select_fileserver(&fc)) {
+                       scb = &cookie->statuses[0];
                        afs_fs_fetch_status(&fc,
                                            afs_v2net(dvnode),
                                            cookie->fids,
-                                           cookie->statuses,
-                                           cookie->callbacks,
+                                           scb,
                                            NULL);
                }
 
@@ -758,25 +753,25 @@ no_inline_bulk_status:
                goto out_c;
 
        for (i = 0; i < cookie->nr_fids; i++)
-               cookie->statuses[i].abort_code = 0;
+               cookie->statuses[i].status.abort_code = 0;
 
 success:
        /* Turn all the files into inodes and save the first one - which is the
         * one we actually want.
         */
-       if (cookie->statuses[0].abort_code != 0)
-               inode = ERR_PTR(afs_abort_to_error(cookie->statuses[0].abort_code));
+       scb = &cookie->statuses[0];
+       if (scb->status.abort_code != 0)
+               inode = ERR_PTR(afs_abort_to_error(scb->status.abort_code));
 
        for (i = 0; i < cookie->nr_fids; i++) {
+               struct afs_status_cb *scb = &cookie->statuses[i];
                struct inode *ti;
 
-               if (cookie->statuses[i].abort_code != 0)
+               if (scb->status.abort_code != 0)
                        continue;
 
                ti = afs_iget(dir->i_sb, key, &cookie->fids[i],
-                             &cookie->statuses[i],
-                             &cookie->callbacks[i],
-                             cbi, dvnode);
+                             scb, cbi, dvnode);
                if (i == 0) {
                        inode = ti;
                } else {
@@ -787,9 +782,7 @@ success:
 
 out_c:
        afs_put_cb_interest(afs_v2net(dvnode), cbi);
-       kfree(cookie->callbacks);
-out_s:
-       kfree(cookie->statuses);
+       kvfree(cookie->statuses);
 out:
        kfree(cookie);
        return inode;
@@ -1115,8 +1108,7 @@ void afs_d_release(struct dentry *dentry)
 static void afs_vnode_new_inode(struct afs_fs_cursor *fc,
                                struct dentry *new_dentry,
                                struct afs_fid *newfid,
-                               struct afs_file_status *newstatus,
-                               struct afs_callback *newcb)
+                               struct afs_status_cb *new_scb)
 {
        struct afs_vnode *vnode;
        struct inode *inode;
@@ -1125,7 +1117,7 @@ static void afs_vnode_new_inode(struct afs_fs_cursor *fc,
                return;
 
        inode = afs_iget(fc->vnode->vfs_inode.i_sb, fc->key,
-                        newfid, newstatus, newcb, fc->cbi, fc->vnode);
+                        newfid, new_scb, fc->cbi, fc->vnode);
        if (IS_ERR(inode)) {
                /* ENOMEM or EINTR at a really inconvenient time - just abandon
                 * the new directory on the server.
@@ -1136,7 +1128,8 @@ static void afs_vnode_new_inode(struct afs_fs_cursor *fc,
 
        vnode = AFS_FS_I(inode);
        set_bit(AFS_VNODE_NEW_CONTENT, &vnode->flags);
-       afs_vnode_commit_status(fc, vnode, 0);
+       if (fc->ac.error == 0)
+               afs_cache_permit(vnode, fc->key, vnode->cb_break, new_scb);
        d_instantiate(new_dentry, inode);
 }
 
@@ -1145,13 +1138,11 @@ static void afs_vnode_new_inode(struct afs_fs_cursor *fc,
  */
 static int afs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
 {
-       struct afs_file_status newstatus;
+       struct afs_status_cb *scb;
        struct afs_fs_cursor fc;
-       struct afs_callback newcb;
        struct afs_vnode *dvnode = AFS_FS_I(dir);
        struct afs_fid newfid;
        struct key *key;
-       u64 data_version = dvnode->status.data_version;
        int ret;
 
        mode |= S_IFDIR;
@@ -1159,23 +1150,31 @@ static int afs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
        _enter("{%llx:%llu},{%pd},%ho",
               dvnode->fid.vid, dvnode->fid.vnode, dentry, mode);
 
+       ret = -ENOMEM;
+       scb = kcalloc(2, sizeof(struct afs_status_cb), GFP_KERNEL);
+       if (!scb)
+               goto error;
+
        key = afs_request_key(dvnode->volume->cell);
        if (IS_ERR(key)) {
                ret = PTR_ERR(key);
-               goto error;
+               goto error_scb;
        }
 
        ret = -ERESTARTSYS;
-       if (afs_begin_vnode_operation(&fc, dvnode, key)) {
+       if (afs_begin_vnode_operation(&fc, dvnode, key, true)) {
+               afs_dataversion_t data_version = dvnode->status.data_version + 1;
+
                while (afs_select_fileserver(&fc)) {
                        fc.cb_break = afs_calc_vnode_cb_break(dvnode);
-                       afs_fs_create(&fc, dentry->d_name.name, mode, data_version,
-                                     &newfid, &newstatus, &newcb);
+                       afs_fs_create(&fc, dentry->d_name.name, mode,
+                                     &scb[0], &newfid, &scb[1]);
                }
 
-               afs_check_for_remote_deletion(&fc, fc.vnode);
-               afs_vnode_commit_status(&fc, dvnode, fc.cb_break);
-               afs_vnode_new_inode(&fc, dentry, &newfid, &newstatus, &newcb);
+               afs_check_for_remote_deletion(&fc, dvnode);
+               afs_vnode_commit_status(&fc, dvnode, fc.cb_break,
+                                       &data_version, &scb[0]);
+               afs_vnode_new_inode(&fc, dentry, &newfid, &scb[1]);
                ret = afs_end_vnode_operation(&fc);
                if (ret < 0)
                        goto error_key;
@@ -1189,11 +1188,14 @@ static int afs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
                                 afs_edit_dir_for_create);
 
        key_put(key);
+       kfree(scb);
        _leave(" = 0");
        return 0;
 
 error_key:
        key_put(key);
+error_scb:
+       kfree(scb);
 error:
        d_drop(dentry);
        _leave(" = %d", ret);
@@ -1220,15 +1222,19 @@ static void afs_dir_remove_subdir(struct dentry *dentry)
  */
 static int afs_rmdir(struct inode *dir, struct dentry *dentry)
 {
+       struct afs_status_cb *scb;
        struct afs_fs_cursor fc;
        struct afs_vnode *dvnode = AFS_FS_I(dir), *vnode = NULL;
        struct key *key;
-       u64 data_version = dvnode->status.data_version;
        int ret;
 
        _enter("{%llx:%llu},{%pd}",
               dvnode->fid.vid, dvnode->fid.vnode, dentry);
 
+       scb = kzalloc(sizeof(struct afs_status_cb), GFP_KERNEL);
+       if (!scb)
+               return -ENOMEM;
+
        key = afs_request_key(dvnode->volume->cell);
        if (IS_ERR(key)) {
                ret = PTR_ERR(key);
@@ -1250,14 +1256,16 @@ static int afs_rmdir(struct inode *dir, struct dentry *dentry)
        }
 
        ret = -ERESTARTSYS;
-       if (afs_begin_vnode_operation(&fc, dvnode, key)) {
+       if (afs_begin_vnode_operation(&fc, dvnode, key, true)) {
+               afs_dataversion_t data_version = dvnode->status.data_version + 1;
+
                while (afs_select_fileserver(&fc)) {
                        fc.cb_break = afs_calc_vnode_cb_break(dvnode);
-                       afs_fs_remove(&fc, vnode, dentry->d_name.name, true,
-                                     data_version);
+                       afs_fs_remove(&fc, vnode, dentry->d_name.name, true, scb);
                }
 
-               afs_vnode_commit_status(&fc, dvnode, fc.cb_break);
+               afs_vnode_commit_status(&fc, dvnode, fc.cb_break,
+                                       &data_version, scb);
                ret = afs_end_vnode_operation(&fc);
                if (ret == 0) {
                        afs_dir_remove_subdir(dentry);
@@ -1272,6 +1280,7 @@ static int afs_rmdir(struct inode *dir, struct dentry *dentry)
 error_key:
        key_put(key);
 error:
+       kfree(scb);
        return ret;
 }
 
@@ -1331,11 +1340,11 @@ int afs_dir_remove_link(struct dentry *dentry, struct key *key,
 static int afs_unlink(struct inode *dir, struct dentry *dentry)
 {
        struct afs_fs_cursor fc;
+       struct afs_status_cb *scb;
        struct afs_vnode *dvnode = AFS_FS_I(dir), *vnode = NULL;
        struct key *key;
        unsigned long d_version = (unsigned long)dentry->d_fsdata;
        bool need_rehash = false;
-       u64 data_version = dvnode->status.data_version;
        int ret;
 
        _enter("{%llx:%llu},{%pd}",
@@ -1344,10 +1353,15 @@ static int afs_unlink(struct inode *dir, struct dentry *dentry)
        if (dentry->d_name.len >= AFSNAMEMAX)
                return -ENAMETOOLONG;
 
+       ret = -ENOMEM;
+       scb = kcalloc(2, sizeof(struct afs_status_cb), GFP_KERNEL);
+       if (!scb)
+               goto error;
+
        key = afs_request_key(dvnode->volume->cell);
        if (IS_ERR(key)) {
                ret = PTR_ERR(key);
-               goto error;
+               goto error_scb;
        }
 
        /* Try to make sure we have a callback promise on the victim. */
@@ -1374,25 +1388,33 @@ static int afs_unlink(struct inode *dir, struct dentry *dentry)
        spin_unlock(&dentry->d_lock);
 
        ret = -ERESTARTSYS;
-       if (afs_begin_vnode_operation(&fc, dvnode, key)) {
+       if (afs_begin_vnode_operation(&fc, dvnode, key, true)) {
+               afs_dataversion_t data_version = dvnode->status.data_version + 1;
+
                while (afs_select_fileserver(&fc)) {
                        fc.cb_break = afs_calc_vnode_cb_break(dvnode);
 
                        if (test_bit(AFS_SERVER_FL_IS_YFS, &fc.cbi->server->flags) &&
                            !test_bit(AFS_SERVER_FL_NO_RM2, &fc.cbi->server->flags)) {
                                yfs_fs_remove_file2(&fc, vnode, dentry->d_name.name,
-                                                   data_version);
+                                                   &scb[0], &scb[1]);
+                               if (fc.ac.error == 0 &&
+                                   scb[1].status.abort_code == VNOVNODE) {
+                                       set_bit(AFS_VNODE_DELETED, &vnode->flags);
+                                       afs_break_callback(vnode);
+                               }
+
                                if (fc.ac.error != -ECONNABORTED ||
                                    fc.ac.abort_code != RXGEN_OPCODE)
                                        continue;
                                set_bit(AFS_SERVER_FL_NO_RM2, &fc.cbi->server->flags);
                        }
 
-                       afs_fs_remove(&fc, vnode, dentry->d_name.name, false,
-                                     data_version);
+                       afs_fs_remove(&fc, vnode, dentry->d_name.name, false, &scb[0]);
                }
 
-               afs_vnode_commit_status(&fc, dvnode, fc.cb_break);
+               afs_vnode_commit_status(&fc, dvnode, fc.cb_break,
+                                       &data_version, &scb[0]);
                ret = afs_end_vnode_operation(&fc);
                if (ret == 0)
                        ret = afs_dir_remove_link(
@@ -1409,6 +1431,8 @@ static int afs_unlink(struct inode *dir, struct dentry *dentry)
 
 error_key:
        key_put(key);
+error_scb:
+       kfree(scb);
 error:
        _leave(" = %d", ret);
        return ret;
@@ -1421,12 +1445,10 @@ static int afs_create(struct inode *dir, struct dentry *dentry, umode_t mode,
                      bool excl)
 {
        struct afs_fs_cursor fc;
-       struct afs_file_status newstatus;
-       struct afs_callback newcb;
+       struct afs_status_cb *scb;
        struct afs_vnode *dvnode = AFS_FS_I(dir);
        struct afs_fid newfid;
        struct key *key;
-       u64 data_version = dvnode->status.data_version;
        int ret;
 
        mode |= S_IFREG;
@@ -1444,17 +1466,25 @@ static int afs_create(struct inode *dir, struct dentry *dentry, umode_t mode,
                goto error;
        }
 
+       ret = -ENOMEM;
+       scb = kcalloc(2, sizeof(struct afs_status_cb), GFP_KERNEL);
+       if (!scb)
+               goto error_scb;
+
        ret = -ERESTARTSYS;
-       if (afs_begin_vnode_operation(&fc, dvnode, key)) {
+       if (afs_begin_vnode_operation(&fc, dvnode, key, true)) {
+               afs_dataversion_t data_version = dvnode->status.data_version + 1;
+
                while (afs_select_fileserver(&fc)) {
                        fc.cb_break = afs_calc_vnode_cb_break(dvnode);
-                       afs_fs_create(&fc, dentry->d_name.name, mode, data_version,
-                                     &newfid, &newstatus, &newcb);
+                       afs_fs_create(&fc, dentry->d_name.name, mode,
+                                     &scb[0], &newfid, &scb[1]);
                }
 
-               afs_check_for_remote_deletion(&fc, fc.vnode);
-               afs_vnode_commit_status(&fc, dvnode, fc.cb_break);
-               afs_vnode_new_inode(&fc, dentry, &newfid, &newstatus, &newcb);
+               afs_check_for_remote_deletion(&fc, dvnode);
+               afs_vnode_commit_status(&fc, dvnode, fc.cb_break,
+                                       &data_version, &scb[0]);
+               afs_vnode_new_inode(&fc, dentry, &newfid, &scb[1]);
                ret = afs_end_vnode_operation(&fc);
                if (ret < 0)
                        goto error_key;
@@ -1466,10 +1496,13 @@ static int afs_create(struct inode *dir, struct dentry *dentry, umode_t mode,
                afs_edit_dir_add(dvnode, &dentry->d_name, &newfid,
                                 afs_edit_dir_for_create);
 
+       kfree(scb);
        key_put(key);
        _leave(" = 0");
        return 0;
 
+error_scb:
+       kfree(scb);
 error_key:
        key_put(key);
 error:
@@ -1485,15 +1518,12 @@ static int afs_link(struct dentry *from, struct inode *dir,
                    struct dentry *dentry)
 {
        struct afs_fs_cursor fc;
-       struct afs_vnode *dvnode, *vnode;
+       struct afs_status_cb *scb;
+       struct afs_vnode *dvnode = AFS_FS_I(dir);
+       struct afs_vnode *vnode = AFS_FS_I(d_inode(from));
        struct key *key;
-       u64 data_version;
        int ret;
 
-       vnode = AFS_FS_I(d_inode(from));
-       dvnode = AFS_FS_I(dir);
-       data_version = dvnode->status.data_version;
-
        _enter("{%llx:%llu},{%llx:%llu},{%pd}",
               vnode->fid.vid, vnode->fid.vnode,
               dvnode->fid.vid, dvnode->fid.vnode,
@@ -1503,14 +1533,21 @@ static int afs_link(struct dentry *from, struct inode *dir,
        if (dentry->d_name.len >= AFSNAMEMAX)
                goto error;
 
+       ret = -ENOMEM;
+       scb = kcalloc(2, sizeof(struct afs_status_cb), GFP_KERNEL);
+       if (!scb)
+               goto error;
+
        key = afs_request_key(dvnode->volume->cell);
        if (IS_ERR(key)) {
                ret = PTR_ERR(key);
-               goto error;
+               goto error_scb;
        }
 
        ret = -ERESTARTSYS;
-       if (afs_begin_vnode_operation(&fc, dvnode, key)) {
+       if (afs_begin_vnode_operation(&fc, dvnode, key, true)) {
+               afs_dataversion_t data_version = dvnode->status.data_version + 1;
+
                if (mutex_lock_interruptible_nested(&vnode->io_lock, 1) < 0) {
                        afs_end_vnode_operation(&fc);
                        goto error_key;
@@ -1519,11 +1556,14 @@ static int afs_link(struct dentry *from, struct inode *dir,
                while (afs_select_fileserver(&fc)) {
                        fc.cb_break = afs_calc_vnode_cb_break(dvnode);
                        fc.cb_break_2 = afs_calc_vnode_cb_break(vnode);
-                       afs_fs_link(&fc, vnode, dentry->d_name.name, data_version);
+                       afs_fs_link(&fc, vnode, dentry->d_name.name,
+                                   &scb[0], &scb[1]);
                }
 
-               afs_vnode_commit_status(&fc, dvnode, fc.cb_break);
-               afs_vnode_commit_status(&fc, vnode, fc.cb_break_2);
+               afs_vnode_commit_status(&fc, dvnode, fc.cb_break,
+                                       &data_version, &scb[0]);
+               afs_vnode_commit_status(&fc, vnode, fc.cb_break_2,
+                                       NULL, &scb[1]);
                ihold(&vnode->vfs_inode);
                d_instantiate(dentry, &vnode->vfs_inode);
 
@@ -1540,11 +1580,14 @@ static int afs_link(struct dentry *from, struct inode *dir,
                                 afs_edit_dir_for_link);
 
        key_put(key);
+       kfree(scb);
        _leave(" = 0");
        return 0;
 
 error_key:
        key_put(key);
+error_scb:
+       kfree(scb);
 error:
        d_drop(dentry);
        _leave(" = %d", ret);
@@ -1558,11 +1601,10 @@ static int afs_symlink(struct inode *dir, struct dentry *dentry,
                       const char *content)
 {
        struct afs_fs_cursor fc;
-       struct afs_file_status newstatus;
+       struct afs_status_cb *scb;
        struct afs_vnode *dvnode = AFS_FS_I(dir);
        struct afs_fid newfid;
        struct key *key;
-       u64 data_version = dvnode->status.data_version;
        int ret;
 
        _enter("{%llx:%llu},{%pd},%s",
@@ -1577,24 +1619,31 @@ static int afs_symlink(struct inode *dir, struct dentry *dentry,
        if (strlen(content) >= AFSPATHMAX)
                goto error;
 
+       ret = -ENOMEM;
+       scb = kcalloc(2, sizeof(struct afs_status_cb), GFP_KERNEL);
+       if (!scb)
+               goto error;
+
        key = afs_request_key(dvnode->volume->cell);
        if (IS_ERR(key)) {
                ret = PTR_ERR(key);
-               goto error;
+               goto error_scb;
        }
 
        ret = -ERESTARTSYS;
-       if (afs_begin_vnode_operation(&fc, dvnode, key)) {
+       if (afs_begin_vnode_operation(&fc, dvnode, key, true)) {
+               afs_dataversion_t data_version = dvnode->status.data_version + 1;
+
                while (afs_select_fileserver(&fc)) {
                        fc.cb_break = afs_calc_vnode_cb_break(dvnode);
-                       afs_fs_symlink(&fc, dentry->d_name.name,
-                                      content, data_version,
-                                      &newfid, &newstatus);
+                       afs_fs_symlink(&fc, dentry->d_name.name, content,
+                                      &scb[0], &newfid, &scb[1]);
                }
 
-               afs_check_for_remote_deletion(&fc, fc.vnode);
-               afs_vnode_commit_status(&fc, dvnode, fc.cb_break);
-               afs_vnode_new_inode(&fc, dentry, &newfid, &newstatus, NULL);
+               afs_check_for_remote_deletion(&fc, dvnode);
+               afs_vnode_commit_status(&fc, dvnode, fc.cb_break,
+                                       &data_version, &scb[0]);
+               afs_vnode_new_inode(&fc, dentry, &newfid, &scb[1]);
                ret = afs_end_vnode_operation(&fc);
                if (ret < 0)
                        goto error_key;
@@ -1607,11 +1656,14 @@ static int afs_symlink(struct inode *dir, struct dentry *dentry,
                                 afs_edit_dir_for_symlink);
 
        key_put(key);
+       kfree(scb);
        _leave(" = 0");
        return 0;
 
 error_key:
        key_put(key);
+error_scb:
+       kfree(scb);
 error:
        d_drop(dentry);
        _leave(" = %d", ret);
@@ -1626,11 +1678,11 @@ static int afs_rename(struct inode *old_dir, struct dentry *old_dentry,
                      unsigned int flags)
 {
        struct afs_fs_cursor fc;
+       struct afs_status_cb *scb;
        struct afs_vnode *orig_dvnode, *new_dvnode, *vnode;
        struct dentry *tmp = NULL, *rehash = NULL;
        struct inode *new_inode;
        struct key *key;
-       u64 orig_data_version, new_data_version;
        bool new_negative = d_is_negative(new_dentry);
        int ret;
 
@@ -1644,8 +1696,6 @@ static int afs_rename(struct inode *old_dir, struct dentry *old_dentry,
        vnode = AFS_FS_I(d_inode(old_dentry));
        orig_dvnode = AFS_FS_I(old_dir);
        new_dvnode = AFS_FS_I(new_dir);
-       orig_data_version = orig_dvnode->status.data_version;
-       new_data_version = new_dvnode->status.data_version;
 
        _enter("{%llx:%llu},{%llx:%llu},{%llx:%llu},{%pd}",
               orig_dvnode->fid.vid, orig_dvnode->fid.vnode,
@@ -1653,10 +1703,15 @@ static int afs_rename(struct inode *old_dir, struct dentry *old_dentry,
               new_dvnode->fid.vid, new_dvnode->fid.vnode,
               new_dentry);
 
+       ret = -ENOMEM;
+       scb = kcalloc(2, sizeof(struct afs_status_cb), GFP_KERNEL);
+       if (!scb)
+               goto error;
+
        key = afs_request_key(orig_dvnode->volume->cell);
        if (IS_ERR(key)) {
                ret = PTR_ERR(key);
-               goto error;
+               goto error_scb;
        }
 
        /* For non-directories, check whether the target is busy and if so,
@@ -1690,31 +1745,43 @@ static int afs_rename(struct inode *old_dir, struct dentry *old_dentry,
                        new_dentry = tmp;
                        rehash = NULL;
                        new_negative = true;
-                       orig_data_version = orig_dvnode->status.data_version;
-                       new_data_version = new_dvnode->status.data_version;
                }
        }
 
        ret = -ERESTARTSYS;
-       if (afs_begin_vnode_operation(&fc, orig_dvnode, key)) {
+       if (afs_begin_vnode_operation(&fc, orig_dvnode, key, true)) {
+               afs_dataversion_t orig_data_version;
+               afs_dataversion_t new_data_version;
+               struct afs_status_cb *new_scb = &scb[1];
+
+               orig_data_version = orig_dvnode->status.data_version + 1;
+
                if (orig_dvnode != new_dvnode) {
                        if (mutex_lock_interruptible_nested(&new_dvnode->io_lock, 1) < 0) {
                                afs_end_vnode_operation(&fc);
                                goto error_rehash;
                        }
+                       new_data_version = new_dvnode->status.data_version;
+               } else {
+                       new_data_version = orig_data_version;
+                       new_scb = &scb[0];
                }
+
                while (afs_select_fileserver(&fc)) {
                        fc.cb_break = afs_calc_vnode_cb_break(orig_dvnode);
                        fc.cb_break_2 = afs_calc_vnode_cb_break(new_dvnode);
                        afs_fs_rename(&fc, old_dentry->d_name.name,
                                      new_dvnode, new_dentry->d_name.name,
-                                     orig_data_version, new_data_version);
+                                     &scb[0], new_scb);
                }
 
-               afs_vnode_commit_status(&fc, orig_dvnode, fc.cb_break);
-               afs_vnode_commit_status(&fc, new_dvnode, fc.cb_break_2);
-               if (orig_dvnode != new_dvnode)
+               afs_vnode_commit_status(&fc, orig_dvnode, fc.cb_break,
+                                       &orig_data_version, &scb[0]);
+               if (new_dvnode != orig_dvnode) {
+                       afs_vnode_commit_status(&fc, new_dvnode, fc.cb_break_2,
+                                               &new_data_version, &scb[1]);
                        mutex_unlock(&new_dvnode->io_lock);
+               }
                ret = afs_end_vnode_operation(&fc);
                if (ret < 0)
                        goto error_rehash;
@@ -1754,6 +1821,8 @@ error_tmp:
        if (tmp)
                dput(tmp);
        key_put(key);
+error_scb:
+       kfree(scb);
 error:
        _leave(" = %d", ret);
        return ret;