Commit 772a49b7 authored by Neil Brown's avatar Neil Brown Committed by Linus Torvalds

[PATCH] Fix disconnected dentries on NFS exports

A disconnected dentry can hold an inode active after the last link has
been removed.  The file will not then be truncated and removed until
memory pressure flushes the disconnected dentry from the dcache.

This problem can be resolved by making sure that an inode never has
both a connected and a disconnected dentry.

This is already the case for directories (as they must only have one
dentry), but it is not the case for non-directories.

This addresses it by:
 - changes d_alloc_anon to make sure that a new disconnected dentry is
   only allocated if there is currently no (hashed) dentry for the
   inode. (Previously this would noramlly be true, but a race was
   possible).
 - changes d_splice_alias to re-use a disconnected dentry on
   non-directories as well as directories.
 - splits most of d_find_alias out into a separate function to make
   the above easier.

Problem originally reported by Nikita Danilov.

Acked by Al Viro ("It looks sane"), and Jakob Oestergaard reports this
fixes his problems. Further pushed by Christoph Hellwig.
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent 02e55c81
...@@ -288,12 +288,11 @@ struct dentry * dget_locked(struct dentry *dentry) ...@@ -288,12 +288,11 @@ struct dentry * dget_locked(struct dentry *dentry)
* any other hashed alias over that one. * any other hashed alias over that one.
*/ */
struct dentry * d_find_alias(struct inode *inode) static struct dentry * __d_find_alias(struct inode *inode, int want_discon)
{ {
struct list_head *head, *next, *tmp; struct list_head *head, *next, *tmp;
struct dentry *alias, *discon_alias=NULL; struct dentry *alias, *discon_alias=NULL;
spin_lock(&dcache_lock);
head = &inode->i_dentry; head = &inode->i_dentry;
next = inode->i_dentry.next; next = inode->i_dentry.next;
while (next != head) { while (next != head) {
...@@ -304,19 +303,26 @@ struct dentry * d_find_alias(struct inode *inode) ...@@ -304,19 +303,26 @@ struct dentry * d_find_alias(struct inode *inode)
if (!d_unhashed(alias)) { if (!d_unhashed(alias)) {
if (alias->d_flags & DCACHE_DISCONNECTED) if (alias->d_flags & DCACHE_DISCONNECTED)
discon_alias = alias; discon_alias = alias;
else { else if (!want_discon) {
__dget_locked(alias); __dget_locked(alias);
spin_unlock(&dcache_lock);
return alias; return alias;
} }
} }
} }
if (discon_alias) if (discon_alias)
__dget_locked(discon_alias); __dget_locked(discon_alias);
spin_unlock(&dcache_lock);
return discon_alias; return discon_alias;
} }
struct dentry * d_find_alias(struct inode *inode)
{
struct dentry *de;
spin_lock(&dcache_lock);
de = __d_find_alias(inode, 0);
spin_unlock(&dcache_lock);
return de;
}
/* /*
* Try to kill dentries associated with this inode. * Try to kill dentries associated with this inode.
* WARNING: you must own a reference to inode. * WARNING: you must own a reference to inode.
...@@ -835,33 +841,27 @@ struct dentry * d_alloc_anon(struct inode *inode) ...@@ -835,33 +841,27 @@ struct dentry * d_alloc_anon(struct inode *inode)
tmp->d_parent = tmp; /* make sure dput doesn't croak */ tmp->d_parent = tmp; /* make sure dput doesn't croak */
spin_lock(&dcache_lock); spin_lock(&dcache_lock);
if (S_ISDIR(inode->i_mode) && !list_empty(&inode->i_dentry)) { res = __d_find_alias(inode, 0);
/* A directory can only have one dentry. if (!res) {
* This (now) has one, so use it.
*/
res = list_entry(inode->i_dentry.next, struct dentry, d_alias);
__dget_locked(res);
} else {
/* attach a disconnected dentry */ /* attach a disconnected dentry */
res = tmp; res = tmp;
tmp = NULL; tmp = NULL;
if (res) { spin_lock(&res->d_lock);
spin_lock(&res->d_lock); res->d_sb = inode->i_sb;
res->d_sb = inode->i_sb; res->d_parent = res;
res->d_parent = res; res->d_inode = inode;
res->d_inode = inode;
/*
* Set d_bucket to an "impossible" bucket address so
* that d_move() doesn't get a false positive
*/
res->d_bucket = NULL;
res->d_flags |= DCACHE_DISCONNECTED;
res->d_flags &= ~DCACHE_UNHASHED;
list_add(&res->d_alias, &inode->i_dentry);
hlist_add_head(&res->d_hash, &inode->i_sb->s_anon);
spin_unlock(&res->d_lock);
/*
* Set d_bucket to an "impossible" bucket address so
* that d_move() doesn't get a false positive
*/
res->d_bucket = NULL;
res->d_flags |= DCACHE_DISCONNECTED;
res->d_flags &= ~DCACHE_UNHASHED;
list_add(&res->d_alias, &inode->i_dentry);
hlist_add_head(&res->d_hash, &inode->i_sb->s_anon);
spin_unlock(&res->d_lock);
}
inode = NULL; /* don't drop reference */ inode = NULL; /* don't drop reference */
} }
spin_unlock(&dcache_lock); spin_unlock(&dcache_lock);
...@@ -883,7 +883,7 @@ struct dentry * d_alloc_anon(struct inode *inode) ...@@ -883,7 +883,7 @@ struct dentry * d_alloc_anon(struct inode *inode)
* DCACHE_DISCONNECTED), then d_move that in place of the given dentry * DCACHE_DISCONNECTED), then d_move that in place of the given dentry
* and return it, else simply d_add the inode to the dentry and return NULL. * and return it, else simply d_add the inode to the dentry and return NULL.
* *
* This is (will be) needed in the lookup routine of any filesystem that is exportable * This is needed in the lookup routine of any filesystem that is exportable
* (via knfsd) so that we can build dcache paths to directories effectively. * (via knfsd) so that we can build dcache paths to directories effectively.
* *
* If a dentry was found and moved, then it is returned. Otherwise NULL * If a dentry was found and moved, then it is returned. Otherwise NULL
...@@ -894,11 +894,11 @@ struct dentry *d_splice_alias(struct inode *inode, struct dentry *dentry) ...@@ -894,11 +894,11 @@ struct dentry *d_splice_alias(struct inode *inode, struct dentry *dentry)
{ {
struct dentry *new = NULL; struct dentry *new = NULL;
if (inode && S_ISDIR(inode->i_mode)) { if (inode) {
spin_lock(&dcache_lock); spin_lock(&dcache_lock);
if (!list_empty(&inode->i_dentry)) { new = __d_find_alias(inode, 1);
new = list_entry(inode->i_dentry.next, struct dentry, d_alias); if (new) {
__dget_locked(new); BUG_ON(!(new->d_flags & DCACHE_DISCONNECTED));
spin_unlock(&dcache_lock); spin_unlock(&dcache_lock);
security_d_instantiate(new, inode); security_d_instantiate(new, inode);
d_rehash(dentry); d_rehash(dentry);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment