fs/dcache: allow d_obtain_alias() to return unhashed dentries

Without this patch, inodes are not promptly freed on last close of an
unlinked file by an nfs client:

	client$ mount -tnfs4 server:/export/ /mnt/
	client$ tail -f /mnt/FOO
	...
	server$ df -i /export
	server$ rm /export/FOO
	(^C the tail -f)
	server$ df -i /export
	server$ echo 2 >/proc/sys/vm/drop_caches
	server$ df -i /export

the df's will show that the inode is not freed on the filesystem until
the last step, when it could have been freed after killing the client's
tail -f. On-disk data won't be deallocated either, leading to possible
spurious ENOSPC.

This occurs because when the client does the close, it arrives in a
compound with a putfh and a close, processed like:

	- putfh: look up the filehandle.  The only alias found for the
	  inode will be DCACHE_UNHASHED alias referenced by the filp
	  this, so it creates a new DCACHE_DISCONECTED dentry and
	  returns that instead.
	- close: closes the existing filp, which is destroyed
	  immediately by dput() since it's DCACHE_UNHASHED.
	- end of the compound: release the reference
	  to the current filehandle, and dput() the new
	  DCACHE_DISCONECTED dentry, which gets put on the
	  unused list instead of being destroyed immediately.

Nick Piggin suggested fixing this by allowing d_obtain_alias to return
the unhashed dentry that is referenced by the filp, instead of making it
create a new dentry.

Leave __d_find_alias() alone to avoid changing behavior of other
callers.

Also nfsd doesn't need all the checks of __d_find_alias(); any dentry,
hashed or unhashed, disconnected or not, should work.

Signed-off-by: J. Bruce Fields <bfields@redhat.com>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
This commit is contained in:
J. Bruce Fields 2011-01-18 15:45:09 -05:00 committed by Al Viro
parent 1ca551c6ca
commit d891eedbc3

View File

@ -1523,6 +1523,28 @@ struct dentry * d_alloc_root(struct inode * root_inode)
} }
EXPORT_SYMBOL(d_alloc_root); EXPORT_SYMBOL(d_alloc_root);
static struct dentry * __d_find_any_alias(struct inode *inode)
{
struct dentry *alias;
if (list_empty(&inode->i_dentry))
return NULL;
alias = list_first_entry(&inode->i_dentry, struct dentry, d_alias);
__dget(alias);
return alias;
}
static struct dentry * d_find_any_alias(struct inode *inode)
{
struct dentry *de;
spin_lock(&inode->i_lock);
de = __d_find_any_alias(inode);
spin_unlock(&inode->i_lock);
return de;
}
/** /**
* d_obtain_alias - find or allocate a dentry for a given inode * d_obtain_alias - find or allocate a dentry for a given inode
* @inode: inode to allocate the dentry for * @inode: inode to allocate the dentry for
@ -1552,7 +1574,7 @@ struct dentry *d_obtain_alias(struct inode *inode)
if (IS_ERR(inode)) if (IS_ERR(inode))
return ERR_CAST(inode); return ERR_CAST(inode);
res = d_find_alias(inode); res = d_find_any_alias(inode);
if (res) if (res)
goto out_iput; goto out_iput;
@ -1565,7 +1587,7 @@ struct dentry *d_obtain_alias(struct inode *inode)
spin_lock(&inode->i_lock); spin_lock(&inode->i_lock);
res = __d_find_alias(inode, 0); res = __d_find_any_alias(inode);
if (res) { if (res) {
spin_unlock(&inode->i_lock); spin_unlock(&inode->i_lock);
dput(tmp); dput(tmp);