FUSE: Notifying the kernel of deletion.
authorJohn Muir <john@jmuir.com>
Tue, 6 Dec 2011 20:50:06 +0000 (21:50 +0100)
committerMiklos Szeredi <mszeredi@suse.cz>
Tue, 13 Dec 2011 10:58:49 +0000 (11:58 +0100)
Allows a FUSE file-system to tell the kernel when a file or directory is
deleted. If the specified dentry has the specified inode number, the kernel will
unhash it.

The current 'fuse_notify_inval_entry' does not cause the kernel to clean up
directories that are in use properly, and as a result the users of those
directories see incorrect semantics from the file-system. The error condition
seen when 'fuse_notify_inval_entry' is used to notify of a deleted directory is
avoided when 'fuse_notify_delete' is used instead.

The following scenario demonstrates the difference:
1. User A chdirs into 'testdir' and starts reading 'testfile'.
2. User B rm -rf 'testdir'.
3. User B creates 'testdir'.
4. User C chdirs into 'testdir'.

If you run the above within the same machine on any file-system (including fuse
file-systems), there is no problem: user C is able to chdir into the new
testdir. The old testdir is removed from the dentry tree, but still open by user
A.

If operations 2 and 3 are performed via the network such that the fuse
file-system uses one of the notify functions to tell the kernel that the nodes
are gone, then the following error occurs for user C while user A holds the
original directory open:

muirj@empacher:~> ls /test/testdir
ls: cannot access /test/testdir: No such file or directory

The issue here is that the kernel still has a dentry for testdir, and so it is
requesting the attributes for the old directory, while the file-system is
responding that the directory no longer exists.

If on the other hand, if the file-system can notify the kernel that the
directory is deleted using the new 'fuse_notify_delete' function, then the above
ls will find the new directory as expected.

Signed-off-by: John Muir <john@jmuir.com>
Signed-off-by: Miklos Szeredi <mszeredi@suse.cz>
fs/fuse/dev.c
fs/fuse/dir.c
fs/fuse/fuse_i.h
include/linux/fuse.h

index 2aaf3eaaf13da03e328ac3523fe16ca09a60c690..5f3368ab0fa9d44ff8b5121db33d158ef5989da9 100644 (file)
@@ -1378,7 +1378,59 @@ static int fuse_notify_inval_entry(struct fuse_conn *fc, unsigned int size,
        down_read(&fc->killsb);
        err = -ENOENT;
        if (fc->sb)
-               err = fuse_reverse_inval_entry(fc->sb, outarg.parent, &name);
+               err = fuse_reverse_inval_entry(fc->sb, outarg.parent, 0, &name);
+       up_read(&fc->killsb);
+       kfree(buf);
+       return err;
+
+err:
+       kfree(buf);
+       fuse_copy_finish(cs);
+       return err;
+}
+
+static int fuse_notify_delete(struct fuse_conn *fc, unsigned int size,
+                             struct fuse_copy_state *cs)
+{
+       struct fuse_notify_delete_out outarg;
+       int err = -ENOMEM;
+       char *buf;
+       struct qstr name;
+
+       buf = kzalloc(FUSE_NAME_MAX + 1, GFP_KERNEL);
+       if (!buf)
+               goto err;
+
+       err = -EINVAL;
+       if (size < sizeof(outarg))
+               goto err;
+
+       err = fuse_copy_one(cs, &outarg, sizeof(outarg));
+       if (err)
+               goto err;
+
+       err = -ENAMETOOLONG;
+       if (outarg.namelen > FUSE_NAME_MAX)
+               goto err;
+
+       err = -EINVAL;
+       if (size != sizeof(outarg) + outarg.namelen + 1)
+               goto err;
+
+       name.name = buf;
+       name.len = outarg.namelen;
+       err = fuse_copy_one(cs, buf, outarg.namelen + 1);
+       if (err)
+               goto err;
+       fuse_copy_finish(cs);
+       buf[outarg.namelen] = 0;
+       name.hash = full_name_hash(name.name, name.len);
+
+       down_read(&fc->killsb);
+       err = -ENOENT;
+       if (fc->sb)
+               err = fuse_reverse_inval_entry(fc->sb, outarg.parent,
+                                              outarg.child, &name);
        up_read(&fc->killsb);
        kfree(buf);
        return err;
@@ -1597,6 +1649,9 @@ static int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code,
        case FUSE_NOTIFY_RETRIEVE:
                return fuse_notify_retrieve(fc, size, cs);
 
+       case FUSE_NOTIFY_DELETE:
+               return fuse_notify_delete(fc, size, cs);
+
        default:
                fuse_copy_finish(cs);
                return -EINVAL;
index 344577933f620cc1df0e580a762fbea58659b918..bef8c3011d312a4652bf0ff8189494181b03fcc6 100644 (file)
@@ -868,7 +868,7 @@ int fuse_update_attributes(struct inode *inode, struct kstat *stat,
 }
 
 int fuse_reverse_inval_entry(struct super_block *sb, u64 parent_nodeid,
-                            struct qstr *name)
+                            u64 child_nodeid, struct qstr *name)
 {
        int err = -ENOTDIR;
        struct inode *parent;
@@ -895,8 +895,36 @@ int fuse_reverse_inval_entry(struct super_block *sb, u64 parent_nodeid,
 
        fuse_invalidate_attr(parent);
        fuse_invalidate_entry(entry);
+
+       if (child_nodeid != 0 && entry->d_inode) {
+               mutex_lock(&entry->d_inode->i_mutex);
+               if (get_node_id(entry->d_inode) != child_nodeid) {
+                       err = -ENOENT;
+                       goto badentry;
+               }
+               if (d_mountpoint(entry)) {
+                       err = -EBUSY;
+                       goto badentry;
+               }
+               if (S_ISDIR(entry->d_inode->i_mode)) {
+                       shrink_dcache_parent(entry);
+                       if (!simple_empty(entry)) {
+                               err = -ENOTEMPTY;
+                               goto badentry;
+                       }
+                       entry->d_inode->i_flags |= S_DEAD;
+               }
+               dont_mount(entry);
+               clear_nlink(entry->d_inode);
+               err = 0;
+ badentry:
+               mutex_unlock(&entry->d_inode->i_mutex);
+               if (!err)
+                       d_delete(entry);
+       } else {
+               err = 0;
+       }
        dput(entry);
-       err = 0;
 
  unlock:
        mutex_unlock(&parent->i_mutex);
index 09337bcc255427fe16dbcb1f6c4605831c3cae92..a571584a091ad0539cc9d393d5ada4a8adc92565 100644 (file)
@@ -755,9 +755,15 @@ int fuse_reverse_inval_inode(struct super_block *sb, u64 nodeid,
 /**
  * File-system tells the kernel to invalidate parent attributes and
  * the dentry matching parent/name.
+ *
+ * If the child_nodeid is non-zero and:
+ *    - matches the inode number for the dentry matching parent/name,
+ *    - is not a mount point
+ *    - is a file or oan empty directory
+ * then the dentry is unhashed (d_delete()).
  */
 int fuse_reverse_inval_entry(struct super_block *sb, u64 parent_nodeid,
-                            struct qstr *name);
+                            u64 child_nodeid, struct qstr *name);
 
 int fuse_do_open(struct fuse_conn *fc, u64 nodeid, struct file *file,
                 bool isdir);
index 446c89718b9cdb986f80feaba08ab4593d865607..8ba2c9460b28fb90bfee024aa8f73601f6c4cf68 100644 (file)
@@ -53,6 +53,7 @@
  *
  * 7.18
  *  - add FUSE_IOCTL_DIR flag
+ *  - add FUSE_NOTIFY_DELETE
  */
 
 #ifndef _LINUX_FUSE_H
@@ -288,6 +289,7 @@ enum fuse_notify_code {
        FUSE_NOTIFY_INVAL_ENTRY = 3,
        FUSE_NOTIFY_STORE = 4,
        FUSE_NOTIFY_RETRIEVE = 5,
+       FUSE_NOTIFY_DELETE = 6,
        FUSE_NOTIFY_CODE_MAX,
 };
 
@@ -611,6 +613,13 @@ struct fuse_notify_inval_entry_out {
        __u32   padding;
 };
 
+struct fuse_notify_delete_out {
+       __u64   parent;
+       __u64   child;
+       __u32   namelen;
+       __u32   padding;
+};
+
 struct fuse_notify_store_out {
        __u64   nodeid;
        __u64   offset;