mount/fuse: unlink the inode on revalidate if entry not found

If an inode/dentry is linked via a client and removed via a
separate client, the inode/dentry mapping in the initial client
remains. A lookup of the removed name on the initial client
typically returns ENOENT once the associated caches expire. If the
initial client has multiple dentries linked to the same inode,
however, lookups on the non-removed dentry create windows of time
where lookups on the stale/removed name return successfully. This
occurs because the stale mapping resolves to the still valid inode
and tricks md-cache into returning valid lookup data.

To correct this situation, unlink the stale inode mapping on a
failed (ENOENT) revalidation lookup (i.e., when fuse has resolved
the inode but a lookup returns ENOENT). Note that with this change,
the state still occurs until an md-cache window has expired,
allowed a lookup to pass through to the server and given the fuse
translator an opportunity to clean up.

Change-Id: I47dde2f11e2ef5b8dd51e9ac8be0f36cdb5081a3
BUG: 985074
Signed-off-by: Brian Foster <bfoster@redhat.com>
Reviewed-on: http://review.gluster.org/5337
Tested-by: Gluster Build System <jenkins@build.gluster.com>
Reviewed-by: Anand Avati <avati@redhat.com>
This commit is contained in:
Brian Foster 2013-07-17 11:17:04 -04:00 committed by Anand Avati
parent deb19de9f6
commit 70e5090326
2 changed files with 62 additions and 0 deletions

55
tests/bugs/bug-985074.t Normal file
View File

@ -0,0 +1,55 @@
#!/bin/bash
#
# Bug 985074 - Verify stale inode/dentry mappings are cleaned out.
#
# This test verifies that an inode/dentry mapping for a file removed via a
# separate mount point is cleaned up appropriately. We create a file and hard
# link from client 1. Next we remove the link via client 2. Finally, from client
# 1 we attempt to rename the original filename to the name of the just removed
# hard link.
#
# If the inode is not unlinked properly, the removed directory entry can resolve
# to an inode (on the client that never saw the rm) that ends up passed down
# through the lookup call. If md-cache holds valid metadata on the inode (due to
# a large timeout value or recent lookup on the valid name), it is tricked into
# returning a successful lookup that should have returned ENOENT. This manifests
# as an error from the mv command in the following test sequence because file
# and file.link resolve to the same file:
#
# # mv /mnt/glusterfs/0/file /mnt/glusterfs/0/file.link
# mv: `/mnt/glusterfs/0/file' and `/mnt/glusterfs/0/file.link' are the same file
#
###
. $(dirname $0)/../include.rc
. $(dirname $0)/../volume.rc
cleanup;
TEST glusterd
TEST $CLI volume create $V0 $H0:$B0/$V0
TEST $CLI volume start $V0
TEST $CLI volume set $V0 md-cache-timeout 3
TEST glusterfs --volfile-id=$V0 --volfile-server=$H0 $M0 --entry-timeout=0 --attribute-timeout=0
TEST glusterfs --volfile-id=$V0 --volfile-server=$H0 $M1 --entry-timeout=0 --attribute-timeout=0
TEST touch $M0/file
TEST ln $M0/file $M0/file.link
TEST ls -ali $M0 $M1
TEST rm -f $M1/file.link
TEST ls -ali $M0 $M1
# expire the md-cache timeout
sleep 3
TEST mv $M0/file $M0/file.link
TEST stat $M0/file.link
TEST ! stat $M0/file
TEST umount $M1
TEST umount $M0
TEST $CLI volume stop $V0
TEST $CLI volume delete $V0
cleanup;

View File

@ -464,6 +464,13 @@ fuse_lookup_cbk (call_frame_t *frame, void *cookie, xlator_t *this,
if (op_ret == -1 && state->is_revalidate == 1) {
itable = state->itable;
/*
* A stale mapping might exist for a dentry/inode that has been
* removed from another client.
*/
if (op_errno == ENOENT)
inode_unlink(state->loc.inode, state->loc.parent,
state->loc.name);
inode_unref (state->loc.inode);
state->loc.inode = inode_new (itable);
state->is_revalidate = 2;