Skip to content

Commit

Permalink
more graceful recovery in umount_collect()
Browse files Browse the repository at this point in the history
commit 9c8c10e 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: Al Viro <viro@zeniv.linux.org.uk>
Cc: "Nicholas A. Bellinger" <nab@linux-iscsi.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
  • Loading branch information
Al Viro authored and Greg Kroah-Hartman committed Aug 17, 2015
1 parent c214cb8 commit 85903ac
Showing 1 changed file with 25 additions and 76 deletions.
101 changes: 25 additions & 76 deletions fs/dcache.c
Original file line number Diff line number Diff line change
Expand Up @@ -393,22 +393,6 @@ static void dentry_lru_add(struct dentry *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
* @dentry: dentry to drop
Expand Down Expand Up @@ -1277,45 +1261,35 @@ void shrink_dcache_parent(struct dentry *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;
enum d_walk_ret ret = D_WALK_CONTINUE;
/* it has busy descendents; complain about those instead */
if (!list_empty(&dentry->d_subdirs))
return D_WALK_CONTINUE;

if (dentry->d_lockref.count) {
dentry_lru_del(dentry);
if (likely(!list_empty(&dentry->d_subdirs)))
goto out;
if (dentry == data->start && dentry->d_lockref.count == 1)
goto out;
printk(KERN_ERR
"BUG: Dentry %p{i=%lx,n=%s}"
" still in use (%d)"
" [unmount of %s %s]\n",
/* root with refcount 1 is fine */
if (dentry == _data && dentry->d_lockref.count == 1)
return D_WALK_CONTINUE;

printk(KERN_ERR "BUG: Dentry %p{i=%lx,n=%pd} "
" still in use (%d) [unmount of %s %s]\n",
dentry,
dentry->d_inode ?
dentry->d_inode->i_ino : 0UL,
dentry->d_name.name,
dentry,
dentry->d_lockref.count,
dentry->d_sb->s_type->name,
dentry->d_sb->s_id);
BUG();
} else if (!(dentry->d_flags & DCACHE_SHRINK_LIST)) {
/*
* We can't use d_lru_shrink_move() because we
* need to get the global LRU lock and do the
* LRU accounting.
*/
if (dentry->d_flags & DCACHE_LRU_LIST)
d_lru_del(dentry);
d_shrink_add(dentry, &data->dispose);
data->found++;
ret = D_WALK_NORETRY;
}
out:
if (data->found && need_resched())
ret = D_WALK_QUIT;
return ret;
WARN_ON(1);
return D_WALK_CONTINUE;
}

static void do_one_tree(struct dentry *dentry)
{
shrink_dcache_parent(dentry);
d_walk(dentry, dentry, umount_check, NULL);
d_drop(dentry);
dput(dentry);
}

/*
Expand All @@ -1325,40 +1299,15 @@ void shrink_dcache_for_umount(struct super_block *sb)
{
struct dentry *dentry;

if (down_read_trylock(&sb->s_umount))
BUG();
WARN(down_read_trylock(&sb->s_umount), "s_umount should've been locked");

dentry = sb->s_root;
sb->s_root = NULL;
for (;;) {
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);
do_one_tree(dentry);

while (!hlist_bl_empty(&sb->s_anon)) {
struct select_data data;
dentry = hlist_bl_entry(hlist_bl_first(&sb->s_anon), struct dentry, d_hash);

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();
dentry = dget(hlist_bl_entry(hlist_bl_first(&sb->s_anon), struct dentry, d_hash));
do_one_tree(dentry);
}
}

Expand Down

0 comments on commit 85903ac

Please sign in to comment.