afs: Don't invalidate callback if AFS_VNODE_DIR_VALID not set
[sfrench/cifs-2.6.git] / fs / afs / dir.c
index 8a2562e3a3163378deb576daeb6129ffa03cfafe..c15550310f625d93dda0c7107a67d78739ab258f 100644 (file)
@@ -26,6 +26,7 @@ static int afs_dir_open(struct inode *inode, struct file *file);
 static int afs_readdir(struct file *file, struct dir_context *ctx);
 static int afs_d_revalidate(struct dentry *dentry, unsigned int flags);
 static int afs_d_delete(const struct dentry *dentry);
+static void afs_d_iput(struct dentry *dentry, struct inode *inode);
 static int afs_lookup_one_filldir(struct dir_context *ctx, const char *name, int nlen,
                                  loff_t fpos, u64 ino, unsigned dtype);
 static int afs_lookup_filldir(struct dir_context *ctx, const char *name, int nlen,
@@ -85,6 +86,7 @@ const struct dentry_operations afs_fs_dentry_operations = {
        .d_delete       = afs_d_delete,
        .d_release      = afs_d_release,
        .d_automount    = afs_d_automount,
+       .d_iput         = afs_d_iput,
 };
 
 struct afs_lookup_one_cookie {
@@ -159,6 +161,38 @@ error:
        return false;
 }
 
+/*
+ * Check the contents of a directory that we've just read.
+ */
+static bool afs_dir_check_pages(struct afs_vnode *dvnode, struct afs_read *req)
+{
+       struct afs_xdr_dir_page *dbuf;
+       unsigned int i, j, qty = PAGE_SIZE / sizeof(union afs_xdr_dir_block);
+
+       for (i = 0; i < req->nr_pages; i++)
+               if (!afs_dir_check_page(dvnode, req->pages[i], req->actual_len))
+                       goto bad;
+       return true;
+
+bad:
+       pr_warn("DIR %llx:%llx f=%llx l=%llx al=%llx r=%llx\n",
+               dvnode->fid.vid, dvnode->fid.vnode,
+               req->file_size, req->len, req->actual_len, req->remain);
+       pr_warn("DIR %llx %x %x %x\n",
+               req->pos, req->index, req->nr_pages, req->offset);
+
+       for (i = 0; i < req->nr_pages; i++) {
+               dbuf = kmap(req->pages[i]);
+               for (j = 0; j < qty; j++) {
+                       union afs_xdr_dir_block *block = &dbuf->blocks[j];
+
+                       pr_warn("[%02x] %32phN\n", i * qty + j, block);
+               }
+               kunmap(req->pages[i]);
+       }
+       return false;
+}
+
 /*
  * open an AFS directory file
  */
@@ -277,6 +311,7 @@ retry:
                goto error;
 
        if (!test_bit(AFS_VNODE_DIR_VALID, &dvnode->flags)) {
+               trace_afs_reload_dir(dvnode);
                ret = afs_fetch_data(dvnode, key, req);
                if (ret < 0)
                        goto error_unlock;
@@ -288,10 +323,8 @@ retry:
 
                /* Validate the data we just read. */
                ret = -EIO;
-               for (i = 0; i < req->nr_pages; i++)
-                       if (!afs_dir_check_page(dvnode, req->pages[i],
-                                               req->actual_len))
-                               goto error_unlock;
+               if (!afs_dir_check_pages(dvnode, req))
+                       goto error_unlock;
 
                // TODO: Trim excess pages
 
@@ -671,7 +704,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)) {
@@ -706,7 +739,7 @@ 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)) {
                        afs_fs_fetch_status(&fc,
                                            afs_v2net(dvnode),
@@ -743,7 +776,7 @@ success:
                ti = afs_iget(dir->i_sb, key, &cookie->fids[i],
                              &cookie->statuses[i],
                              &cookie->callbacks[i],
-                             cbi);
+                             cbi, dvnode);
                if (i == 0) {
                        inode = ti;
                } else {
@@ -875,8 +908,14 @@ static struct dentry *afs_lookup(struct inode *dir, struct dentry *dentry,
                        (void *)(unsigned long)dvnode->status.data_version;
        }
        d = d_splice_alias(inode, dentry);
-       if (!IS_ERR_OR_NULL(d))
+       if (!IS_ERR_OR_NULL(d)) {
                d->d_fsdata = dentry->d_fsdata;
+               trace_afs_lookup(dvnode, &d->d_name,
+                                inode ? AFS_FS_I(inode) : NULL);
+       } else {
+               trace_afs_lookup(dvnode, &dentry->d_name,
+                                inode ? AFS_FS_I(inode) : NULL);
+       }
        return d;
 }
 
@@ -1052,6 +1091,16 @@ zap:
        return 1;
 }
 
+/*
+ * Clean up sillyrename files on dentry removal.
+ */
+static void afs_d_iput(struct dentry *dentry, struct inode *inode)
+{
+       if (dentry->d_flags & DCACHE_NFSFS_RENAMED)
+               afs_silly_iput(dentry, inode);
+       iput(inode);
+}
+
 /*
  * handle dentry release
  */
@@ -1076,7 +1125,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);
+                        newfid, newstatus, newcb, fc->cbi, fc->vnode);
        if (IS_ERR(inode)) {
                /* ENOMEM or EINTR at a really inconvenient time - just abandon
                 * the new directory on the server.
@@ -1117,7 +1166,7 @@ static int afs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
        }
 
        ret = -ERESTARTSYS;
-       if (afs_begin_vnode_operation(&fc, dvnode, key)) {
+       if (afs_begin_vnode_operation(&fc, dvnode, key, true)) {
                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,
@@ -1194,8 +1243,14 @@ static int afs_rmdir(struct inode *dir, struct dentry *dentry)
                        goto error_key;
        }
 
+       if (vnode) {
+               ret = down_write_killable(&vnode->rmdir_lock);
+               if (ret < 0)
+                       goto error_key;
+       }
+
        ret = -ERESTARTSYS;
-       if (afs_begin_vnode_operation(&fc, dvnode, key)) {
+       if (afs_begin_vnode_operation(&fc, dvnode, key, true)) {
                while (afs_select_fileserver(&fc)) {
                        fc.cb_break = afs_calc_vnode_cb_break(dvnode);
                        afs_fs_remove(&fc, vnode, dentry->d_name.name, true,
@@ -1212,6 +1267,8 @@ static int afs_rmdir(struct inode *dir, struct dentry *dentry)
                }
        }
 
+       if (vnode)
+               up_write(&vnode->rmdir_lock);
 error_key:
        key_put(key);
 error:
@@ -1228,9 +1285,9 @@ error:
  * However, if we didn't have a callback promise outstanding, or it was
  * outstanding on a different server, then it won't break it either...
  */
-static int afs_dir_remove_link(struct dentry *dentry, struct key *key,
-                              unsigned long d_version_before,
-                              unsigned long d_version_after)
+int afs_dir_remove_link(struct dentry *dentry, struct key *key,
+                       unsigned long d_version_before,
+                       unsigned long d_version_after)
 {
        bool dir_valid;
        int ret = 0;
@@ -1277,6 +1334,7 @@ static int afs_unlink(struct inode *dir, struct dentry *dentry)
        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;
 
@@ -1300,8 +1358,23 @@ static int afs_unlink(struct inode *dir, struct dentry *dentry)
                        goto error_key;
        }
 
+       spin_lock(&dentry->d_lock);
+       if (vnode && d_count(dentry) > 1) {
+               spin_unlock(&dentry->d_lock);
+               /* Start asynchronous writeout of the inode */
+               write_inode_now(d_inode(dentry), 0);
+               ret = afs_sillyrename(dvnode, vnode, dentry, key);
+               goto error_key;
+       }
+       if (!d_unhashed(dentry)) {
+               /* Prevent a race with RCU lookup. */
+               __d_drop(dentry);
+               need_rehash = true;
+       }
+       spin_unlock(&dentry->d_lock);
+
        ret = -ERESTARTSYS;
-       if (afs_begin_vnode_operation(&fc, dvnode, key)) {
+       if (afs_begin_vnode_operation(&fc, dvnode, key, true)) {
                while (afs_select_fileserver(&fc)) {
                        fc.cb_break = afs_calc_vnode_cb_break(dvnode);
 
@@ -1331,6 +1404,9 @@ static int afs_unlink(struct inode *dir, struct dentry *dentry)
                                            afs_edit_dir_for_unlink);
        }
 
+       if (need_rehash && ret < 0 && ret != -ENOENT)
+               d_rehash(dentry);
+
 error_key:
        key_put(key);
 error:
@@ -1369,7 +1445,7 @@ static int afs_create(struct inode *dir, struct dentry *dentry, umode_t mode,
        }
 
        ret = -ERESTARTSYS;
-       if (afs_begin_vnode_operation(&fc, dvnode, key)) {
+       if (afs_begin_vnode_operation(&fc, dvnode, key, true)) {
                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,
@@ -1434,7 +1510,7 @@ static int afs_link(struct dentry *from, struct inode *dir,
        }
 
        ret = -ERESTARTSYS;
-       if (afs_begin_vnode_operation(&fc, dvnode, key)) {
+       if (afs_begin_vnode_operation(&fc, dvnode, key, true)) {
                if (mutex_lock_interruptible_nested(&vnode->io_lock, 1) < 0) {
                        afs_end_vnode_operation(&fc);
                        goto error_key;
@@ -1508,7 +1584,7 @@ static int afs_symlink(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)) {
                while (afs_select_fileserver(&fc)) {
                        fc.cb_break = afs_calc_vnode_cb_break(dvnode);
                        afs_fs_symlink(&fc, dentry->d_name.name,
@@ -1551,6 +1627,8 @@ static int afs_rename(struct inode *old_dir, struct dentry *old_dentry,
 {
        struct afs_fs_cursor fc;
        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);
@@ -1559,6 +1637,10 @@ static int afs_rename(struct inode *old_dir, struct dentry *old_dentry,
        if (flags)
                return -EINVAL;
 
+       /* Don't allow silly-rename files be moved around. */
+       if (old_dentry->d_flags & DCACHE_NFSFS_RENAMED)
+               return -EINVAL;
+
        vnode = AFS_FS_I(d_inode(old_dentry));
        orig_dvnode = AFS_FS_I(old_dir);
        new_dvnode = AFS_FS_I(new_dir);
@@ -1577,12 +1659,48 @@ static int afs_rename(struct inode *old_dir, struct dentry *old_dentry,
                goto error;
        }
 
+       /* For non-directories, check whether the target is busy and if so,
+        * make a copy of the dentry and then do a silly-rename.  If the
+        * silly-rename succeeds, the copied dentry is hashed and becomes the
+        * new target.
+        */
+       if (d_is_positive(new_dentry) && !d_is_dir(new_dentry)) {
+               /* To prevent any new references to the target during the
+                * rename, we unhash the dentry in advance.
+                */
+               if (!d_unhashed(new_dentry)) {
+                       d_drop(new_dentry);
+                       rehash = new_dentry;
+               }
+
+               if (d_count(new_dentry) > 2) {
+                       /* copy the target dentry's name */
+                       ret = -ENOMEM;
+                       tmp = d_alloc(new_dentry->d_parent,
+                                     &new_dentry->d_name);
+                       if (!tmp)
+                               goto error_rehash;
+
+                       ret = afs_sillyrename(new_dvnode,
+                                             AFS_FS_I(d_inode(new_dentry)),
+                                             new_dentry, key);
+                       if (ret)
+                               goto error_rehash;
+
+                       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)) {
                if (orig_dvnode != new_dvnode) {
                        if (mutex_lock_interruptible_nested(&new_dvnode->io_lock, 1) < 0) {
                                afs_end_vnode_operation(&fc);
-                               goto error_key;
+                               goto error_rehash;
                        }
                }
                while (afs_select_fileserver(&fc)) {
@@ -1599,25 +1717,42 @@ static int afs_rename(struct inode *old_dir, struct dentry *old_dentry,
                        mutex_unlock(&new_dvnode->io_lock);
                ret = afs_end_vnode_operation(&fc);
                if (ret < 0)
-                       goto error_key;
+                       goto error_rehash;
        }
 
        if (ret == 0) {
+               if (rehash)
+                       d_rehash(rehash);
                if (test_bit(AFS_VNODE_DIR_VALID, &orig_dvnode->flags))
                    afs_edit_dir_remove(orig_dvnode, &old_dentry->d_name,
-                                       afs_edit_dir_for_rename);
+                                       afs_edit_dir_for_rename_0);
 
                if (!new_negative &&
                    test_bit(AFS_VNODE_DIR_VALID, &new_dvnode->flags))
                        afs_edit_dir_remove(new_dvnode, &new_dentry->d_name,
-                                           afs_edit_dir_for_rename);
+                                           afs_edit_dir_for_rename_1);
 
                if (test_bit(AFS_VNODE_DIR_VALID, &new_dvnode->flags))
                        afs_edit_dir_add(new_dvnode, &new_dentry->d_name,
-                                        &vnode->fid,  afs_edit_dir_for_rename);
+                                        &vnode->fid, afs_edit_dir_for_rename_2);
+
+               new_inode = d_inode(new_dentry);
+               if (new_inode) {
+                       spin_lock(&new_inode->i_lock);
+                       if (new_inode->i_nlink > 0)
+                               drop_nlink(new_inode);
+                       spin_unlock(&new_inode->i_lock);
+               }
+               d_move(old_dentry, new_dentry);
+               goto error_tmp;
        }
 
-error_key:
+error_rehash:
+       if (rehash)
+               d_rehash(rehash);
+error_tmp:
+       if (tmp)
+               dput(tmp);
        key_put(key);
 error:
        _leave(" = %d", ret);