Commit 85903ac9 authored by Al Viro's avatar Al Viro Committed by Greg Kroah-Hartman

more graceful recovery in umount_collect()

commit 9c8c10e2 upstream.

Start with shrink_dcache_parent(), then scan what remains.

First of all, BUG() is very much an overkill here; we are holding
->s_umount, and hitting BUG() means that a lot of interesting stuff
will be hanging after that point (sync(2), for example).  Moreover,
in cases when there had been more than one leak, we'll be better
off reporting all of them.  And more than just the last component
of pathname - %pd is there for just such uses...

That was the last user of dentry_lru_del(), so kill it off...
Signed-off-by: default avatarAl Viro <viro@zeniv.linux.org.uk>
Cc: "Nicholas A. Bellinger" <nab@linux-iscsi.org>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent c214cb82
...@@ -393,22 +393,6 @@ static void dentry_lru_add(struct dentry *dentry) ...@@ -393,22 +393,6 @@ static void dentry_lru_add(struct dentry *dentry)
d_lru_add(dentry); d_lru_add(dentry);
} }
/*
* Remove a dentry with references from the LRU.
*
* If we are on the shrink list, then we can get to try_prune_one_dentry() and
* lose our last reference through the parent walk. In this case, we need to
* remove ourselves from the shrink list, not the LRU.
*/
static void dentry_lru_del(struct dentry *dentry)
{
if (dentry->d_flags & DCACHE_LRU_LIST) {
if (dentry->d_flags & DCACHE_SHRINK_LIST)
return d_shrink_del(dentry);
d_lru_del(dentry);
}
}
/** /**
* d_drop - drop a dentry * d_drop - drop a dentry
* @dentry: dentry to drop * @dentry: dentry to drop
...@@ -1277,45 +1261,35 @@ void shrink_dcache_parent(struct dentry *parent) ...@@ -1277,45 +1261,35 @@ void shrink_dcache_parent(struct dentry *parent)
} }
EXPORT_SYMBOL(shrink_dcache_parent); EXPORT_SYMBOL(shrink_dcache_parent);
static enum d_walk_ret umount_collect(void *_data, struct dentry *dentry) static enum d_walk_ret umount_check(void *_data, struct dentry *dentry)
{ {
struct select_data *data = _data; /* it has busy descendents; complain about those instead */
enum d_walk_ret ret = D_WALK_CONTINUE; if (!list_empty(&dentry->d_subdirs))
return D_WALK_CONTINUE;
if (dentry->d_lockref.count) { /* root with refcount 1 is fine */
dentry_lru_del(dentry); if (dentry == _data && dentry->d_lockref.count == 1)
if (likely(!list_empty(&dentry->d_subdirs))) return D_WALK_CONTINUE;
goto out;
if (dentry == data->start && dentry->d_lockref.count == 1) printk(KERN_ERR "BUG: Dentry %p{i=%lx,n=%pd} "
goto out; " still in use (%d) [unmount of %s %s]\n",
printk(KERN_ERR
"BUG: Dentry %p{i=%lx,n=%s}"
" still in use (%d)"
" [unmount of %s %s]\n",
dentry, dentry,
dentry->d_inode ? dentry->d_inode ?
dentry->d_inode->i_ino : 0UL, dentry->d_inode->i_ino : 0UL,
dentry->d_name.name, dentry,
dentry->d_lockref.count, dentry->d_lockref.count,
dentry->d_sb->s_type->name, dentry->d_sb->s_type->name,
dentry->d_sb->s_id); dentry->d_sb->s_id);
BUG(); WARN_ON(1);
} else if (!(dentry->d_flags & DCACHE_SHRINK_LIST)) { return D_WALK_CONTINUE;
/* }
* We can't use d_lru_shrink_move() because we
* need to get the global LRU lock and do the static void do_one_tree(struct dentry *dentry)
* LRU accounting. {
*/ shrink_dcache_parent(dentry);
if (dentry->d_flags & DCACHE_LRU_LIST) d_walk(dentry, dentry, umount_check, NULL);
d_lru_del(dentry); d_drop(dentry);
d_shrink_add(dentry, &data->dispose); dput(dentry);
data->found++;
ret = D_WALK_NORETRY;
}
out:
if (data->found && need_resched())
ret = D_WALK_QUIT;
return ret;
} }
/* /*
...@@ -1325,40 +1299,15 @@ void shrink_dcache_for_umount(struct super_block *sb) ...@@ -1325,40 +1299,15 @@ void shrink_dcache_for_umount(struct super_block *sb)
{ {
struct dentry *dentry; struct dentry *dentry;
if (down_read_trylock(&sb->s_umount)) WARN(down_read_trylock(&sb->s_umount), "s_umount should've been locked");
BUG();
dentry = sb->s_root; dentry = sb->s_root;
sb->s_root = NULL; sb->s_root = NULL;
for (;;) { do_one_tree(dentry);
struct select_data data;
INIT_LIST_HEAD(&data.dispose);
data.start = dentry;
data.found = 0;
d_walk(dentry, &data, umount_collect, NULL);
if (!data.found)
break;
shrink_dentry_list(&data.dispose);
cond_resched();
}
d_drop(dentry);
dput(dentry);
while (!hlist_bl_empty(&sb->s_anon)) { while (!hlist_bl_empty(&sb->s_anon)) {
struct select_data data; dentry = dget(hlist_bl_entry(hlist_bl_first(&sb->s_anon), struct dentry, d_hash));
dentry = hlist_bl_entry(hlist_bl_first(&sb->s_anon), struct dentry, d_hash); do_one_tree(dentry);
INIT_LIST_HEAD(&data.dispose);
data.start = NULL;
data.found = 0;
d_walk(dentry, &data, umount_collect, NULL);
if (data.found)
shrink_dentry_list(&data.dispose);
cond_resched();
} }
} }
......
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