diff options
author | John Muir <john@jmuir.com> | 2011-12-06 15:50:06 -0500 |
---|---|---|
committer | Miklos Szeredi <mszeredi@suse.cz> | 2011-12-13 05:58:49 -0500 |
commit | 451d0f599934fd97faf54a5d7954b518e66192cb (patch) | |
tree | 6dd7a6fcdc8ff3bc50dec37b114c447b3b1d7ba1 /fs/fuse | |
parent | b18da0c56e9ff43a007b6c8e302c62e720964151 (diff) |
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>
Diffstat (limited to 'fs/fuse')
-rw-r--r-- | fs/fuse/dev.c | 57 | ||||
-rw-r--r-- | fs/fuse/dir.c | 32 | ||||
-rw-r--r-- | fs/fuse/fuse_i.h | 8 |
3 files changed, 93 insertions, 4 deletions
diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 2aaf3eaaf13d..5f3368ab0fa9 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c | |||
@@ -1378,7 +1378,59 @@ static int fuse_notify_inval_entry(struct fuse_conn *fc, unsigned int size, | |||
1378 | down_read(&fc->killsb); | 1378 | down_read(&fc->killsb); |
1379 | err = -ENOENT; | 1379 | err = -ENOENT; |
1380 | if (fc->sb) | 1380 | if (fc->sb) |
1381 | err = fuse_reverse_inval_entry(fc->sb, outarg.parent, &name); | 1381 | err = fuse_reverse_inval_entry(fc->sb, outarg.parent, 0, &name); |
1382 | up_read(&fc->killsb); | ||
1383 | kfree(buf); | ||
1384 | return err; | ||
1385 | |||
1386 | err: | ||
1387 | kfree(buf); | ||
1388 | fuse_copy_finish(cs); | ||
1389 | return err; | ||
1390 | } | ||
1391 | |||
1392 | static int fuse_notify_delete(struct fuse_conn *fc, unsigned int size, | ||
1393 | struct fuse_copy_state *cs) | ||
1394 | { | ||
1395 | struct fuse_notify_delete_out outarg; | ||
1396 | int err = -ENOMEM; | ||
1397 | char *buf; | ||
1398 | struct qstr name; | ||
1399 | |||
1400 | buf = kzalloc(FUSE_NAME_MAX + 1, GFP_KERNEL); | ||
1401 | if (!buf) | ||
1402 | goto err; | ||
1403 | |||
1404 | err = -EINVAL; | ||
1405 | if (size < sizeof(outarg)) | ||
1406 | goto err; | ||
1407 | |||
1408 | err = fuse_copy_one(cs, &outarg, sizeof(outarg)); | ||
1409 | if (err) | ||
1410 | goto err; | ||
1411 | |||
1412 | err = -ENAMETOOLONG; | ||
1413 | if (outarg.namelen > FUSE_NAME_MAX) | ||
1414 | goto err; | ||
1415 | |||
1416 | err = -EINVAL; | ||
1417 | if (size != sizeof(outarg) + outarg.namelen + 1) | ||
1418 | goto err; | ||
1419 | |||
1420 | name.name = buf; | ||
1421 | name.len = outarg.namelen; | ||
1422 | err = fuse_copy_one(cs, buf, outarg.namelen + 1); | ||
1423 | if (err) | ||
1424 | goto err; | ||
1425 | fuse_copy_finish(cs); | ||
1426 | buf[outarg.namelen] = 0; | ||
1427 | name.hash = full_name_hash(name.name, name.len); | ||
1428 | |||
1429 | down_read(&fc->killsb); | ||
1430 | err = -ENOENT; | ||
1431 | if (fc->sb) | ||
1432 | err = fuse_reverse_inval_entry(fc->sb, outarg.parent, | ||
1433 | outarg.child, &name); | ||
1382 | up_read(&fc->killsb); | 1434 | up_read(&fc->killsb); |
1383 | kfree(buf); | 1435 | kfree(buf); |
1384 | return err; | 1436 | return err; |
@@ -1597,6 +1649,9 @@ static int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code, | |||
1597 | case FUSE_NOTIFY_RETRIEVE: | 1649 | case FUSE_NOTIFY_RETRIEVE: |
1598 | return fuse_notify_retrieve(fc, size, cs); | 1650 | return fuse_notify_retrieve(fc, size, cs); |
1599 | 1651 | ||
1652 | case FUSE_NOTIFY_DELETE: | ||
1653 | return fuse_notify_delete(fc, size, cs); | ||
1654 | |||
1600 | default: | 1655 | default: |
1601 | fuse_copy_finish(cs); | 1656 | fuse_copy_finish(cs); |
1602 | return -EINVAL; | 1657 | return -EINVAL; |
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 344577933f62..bef8c3011d31 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c | |||
@@ -868,7 +868,7 @@ int fuse_update_attributes(struct inode *inode, struct kstat *stat, | |||
868 | } | 868 | } |
869 | 869 | ||
870 | int fuse_reverse_inval_entry(struct super_block *sb, u64 parent_nodeid, | 870 | int fuse_reverse_inval_entry(struct super_block *sb, u64 parent_nodeid, |
871 | struct qstr *name) | 871 | u64 child_nodeid, struct qstr *name) |
872 | { | 872 | { |
873 | int err = -ENOTDIR; | 873 | int err = -ENOTDIR; |
874 | struct inode *parent; | 874 | struct inode *parent; |
@@ -895,8 +895,36 @@ int fuse_reverse_inval_entry(struct super_block *sb, u64 parent_nodeid, | |||
895 | 895 | ||
896 | fuse_invalidate_attr(parent); | 896 | fuse_invalidate_attr(parent); |
897 | fuse_invalidate_entry(entry); | 897 | fuse_invalidate_entry(entry); |
898 | |||
899 | if (child_nodeid != 0 && entry->d_inode) { | ||
900 | mutex_lock(&entry->d_inode->i_mutex); | ||
901 | if (get_node_id(entry->d_inode) != child_nodeid) { | ||
902 | err = -ENOENT; | ||
903 | goto badentry; | ||
904 | } | ||
905 | if (d_mountpoint(entry)) { | ||
906 | err = -EBUSY; | ||
907 | goto badentry; | ||
908 | } | ||
909 | if (S_ISDIR(entry->d_inode->i_mode)) { | ||
910 | shrink_dcache_parent(entry); | ||
911 | if (!simple_empty(entry)) { | ||
912 | err = -ENOTEMPTY; | ||
913 | goto badentry; | ||
914 | } | ||
915 | entry->d_inode->i_flags |= S_DEAD; | ||
916 | } | ||
917 | dont_mount(entry); | ||
918 | clear_nlink(entry->d_inode); | ||
919 | err = 0; | ||
920 | badentry: | ||
921 | mutex_unlock(&entry->d_inode->i_mutex); | ||
922 | if (!err) | ||
923 | d_delete(entry); | ||
924 | } else { | ||
925 | err = 0; | ||
926 | } | ||
898 | dput(entry); | 927 | dput(entry); |
899 | err = 0; | ||
900 | 928 | ||
901 | unlock: | 929 | unlock: |
902 | mutex_unlock(&parent->i_mutex); | 930 | mutex_unlock(&parent->i_mutex); |
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 09337bcc2554..a571584a091a 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h | |||
@@ -755,9 +755,15 @@ int fuse_reverse_inval_inode(struct super_block *sb, u64 nodeid, | |||
755 | /** | 755 | /** |
756 | * File-system tells the kernel to invalidate parent attributes and | 756 | * File-system tells the kernel to invalidate parent attributes and |
757 | * the dentry matching parent/name. | 757 | * the dentry matching parent/name. |
758 | * | ||
759 | * If the child_nodeid is non-zero and: | ||
760 | * - matches the inode number for the dentry matching parent/name, | ||
761 | * - is not a mount point | ||
762 | * - is a file or oan empty directory | ||
763 | * then the dentry is unhashed (d_delete()). | ||
758 | */ | 764 | */ |
759 | int fuse_reverse_inval_entry(struct super_block *sb, u64 parent_nodeid, | 765 | int fuse_reverse_inval_entry(struct super_block *sb, u64 parent_nodeid, |
760 | struct qstr *name); | 766 | u64 child_nodeid, struct qstr *name); |
761 | 767 | ||
762 | int fuse_do_open(struct fuse_conn *fc, u64 nodeid, struct file *file, | 768 | int fuse_do_open(struct fuse_conn *fc, u64 nodeid, struct file *file, |
763 | bool isdir); | 769 | bool isdir); |