FUSE: Notifying the kernel of deletion.
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>
This commit is contained in:
committed by
Miklos Szeredi
parent
b18da0c56e
commit
451d0f5999
@@ -1378,7 +1378,59 @@ static int fuse_notify_inval_entry(struct fuse_conn *fc, unsigned int size,
|
|||||||
down_read(&fc->killsb);
|
down_read(&fc->killsb);
|
||||||
err = -ENOENT;
|
err = -ENOENT;
|
||||||
if (fc->sb)
|
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);
|
up_read(&fc->killsb);
|
||||||
kfree(buf);
|
kfree(buf);
|
||||||
return err;
|
return err;
|
||||||
@@ -1597,6 +1649,9 @@ static int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code,
|
|||||||
case FUSE_NOTIFY_RETRIEVE:
|
case FUSE_NOTIFY_RETRIEVE:
|
||||||
return fuse_notify_retrieve(fc, size, cs);
|
return fuse_notify_retrieve(fc, size, cs);
|
||||||
|
|
||||||
|
case FUSE_NOTIFY_DELETE:
|
||||||
|
return fuse_notify_delete(fc, size, cs);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
fuse_copy_finish(cs);
|
fuse_copy_finish(cs);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
@@ -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,
|
int fuse_reverse_inval_entry(struct super_block *sb, u64 parent_nodeid,
|
||||||
struct qstr *name)
|
u64 child_nodeid, struct qstr *name)
|
||||||
{
|
{
|
||||||
int err = -ENOTDIR;
|
int err = -ENOTDIR;
|
||||||
struct inode *parent;
|
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_attr(parent);
|
||||||
fuse_invalidate_entry(entry);
|
fuse_invalidate_entry(entry);
|
||||||
dput(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;
|
err = 0;
|
||||||
|
badentry:
|
||||||
|
mutex_unlock(&entry->d_inode->i_mutex);
|
||||||
|
if (!err)
|
||||||
|
d_delete(entry);
|
||||||
|
} else {
|
||||||
|
err = 0;
|
||||||
|
}
|
||||||
|
dput(entry);
|
||||||
|
|
||||||
unlock:
|
unlock:
|
||||||
mutex_unlock(&parent->i_mutex);
|
mutex_unlock(&parent->i_mutex);
|
||||||
|
@@ -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
|
* File-system tells the kernel to invalidate parent attributes and
|
||||||
* the dentry matching parent/name.
|
* 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,
|
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,
|
int fuse_do_open(struct fuse_conn *fc, u64 nodeid, struct file *file,
|
||||||
bool isdir);
|
bool isdir);
|
||||||
|
@@ -53,6 +53,7 @@
|
|||||||
*
|
*
|
||||||
* 7.18
|
* 7.18
|
||||||
* - add FUSE_IOCTL_DIR flag
|
* - add FUSE_IOCTL_DIR flag
|
||||||
|
* - add FUSE_NOTIFY_DELETE
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef _LINUX_FUSE_H
|
#ifndef _LINUX_FUSE_H
|
||||||
@@ -288,6 +289,7 @@ enum fuse_notify_code {
|
|||||||
FUSE_NOTIFY_INVAL_ENTRY = 3,
|
FUSE_NOTIFY_INVAL_ENTRY = 3,
|
||||||
FUSE_NOTIFY_STORE = 4,
|
FUSE_NOTIFY_STORE = 4,
|
||||||
FUSE_NOTIFY_RETRIEVE = 5,
|
FUSE_NOTIFY_RETRIEVE = 5,
|
||||||
|
FUSE_NOTIFY_DELETE = 6,
|
||||||
FUSE_NOTIFY_CODE_MAX,
|
FUSE_NOTIFY_CODE_MAX,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -611,6 +613,13 @@ struct fuse_notify_inval_entry_out {
|
|||||||
__u32 padding;
|
__u32 padding;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct fuse_notify_delete_out {
|
||||||
|
__u64 parent;
|
||||||
|
__u64 child;
|
||||||
|
__u32 namelen;
|
||||||
|
__u32 padding;
|
||||||
|
};
|
||||||
|
|
||||||
struct fuse_notify_store_out {
|
struct fuse_notify_store_out {
|
||||||
__u64 nodeid;
|
__u64 nodeid;
|
||||||
__u64 offset;
|
__u64 offset;
|
||||||
|
Reference in New Issue
Block a user