dm ioctl: release _hash_lock between devices in remove_all

This patch changes dm_hash_remove_all() to release _hash_lock when
removing a device.  After removing the device, dm_hash_remove_all()
takes _hash_lock and searches the hash from scratch again.

This patch is a preparation for the next patch, which changes device
deletion code to wait for md reference to be 0.  Without this patch,
the wait in the next patch may cause AB-BA deadlock:
  CPU0                                CPU1
  -----------------------------------------------------------------------
  dm_hash_remove_all()
    down_write(_hash_lock)
                                      table_status()
                                        md = find_device()
                                               dm_get(md)
                                                 <increment md->holders>
                                        dm_get_live_or_inactive_table()
                                          dm_get_inactive_table()
                                            down_write(_hash_lock)
    <in the md deletion code>
      <wait for md->holders to be 0>

Signed-off-by: Kiyoshi Ueda <k-ueda@ct.jp.nec.com>
Signed-off-by: Jun'ichi Nomura <j-nomura@ce.jp.nec.com>
Cc: stable@kernel.org
Signed-off-by: Alasdair G Kergon <agk@redhat.com>
This commit is contained in:
Kiyoshi Ueda 2010-08-12 04:13:55 +01:00 committed by Alasdair G Kergon
parent abdc568b05
commit 98f332855e

View File

@ -249,40 +249,46 @@ static void __hash_remove(struct hash_cell *hc)
static void dm_hash_remove_all(int keep_open_devices)
{
int i, dev_skipped, dev_removed;
int i, dev_skipped;
struct hash_cell *hc;
struct list_head *tmp, *n;
struct mapped_device *md;
retry:
dev_skipped = 0;
down_write(&_hash_lock);
retry:
dev_skipped = dev_removed = 0;
for (i = 0; i < NUM_BUCKETS; i++) {
list_for_each_safe (tmp, n, _name_buckets + i) {
hc = list_entry(tmp, struct hash_cell, name_list);
list_for_each_entry(hc, _name_buckets + i, name_list) {
md = hc->md;
dm_get(md);
if (keep_open_devices &&
dm_lock_for_deletion(hc->md)) {
if (keep_open_devices && dm_lock_for_deletion(md)) {
dm_put(md);
dev_skipped++;
continue;
}
__hash_remove(hc);
dev_removed = 1;
up_write(&_hash_lock);
dm_put(md);
/*
* Some mapped devices may be using other mapped
* devices, so repeat until we make no further
* progress. If a new mapped device is created
* here it will also get removed.
*/
goto retry;
}
}
/*
* Some mapped devices may be using other mapped devices, so if any
* still exist, repeat until we make no further progress.
*/
if (dev_skipped) {
if (dev_removed)
goto retry;
DMWARN("remove_all left %d open device(s)", dev_skipped);
}
up_write(&_hash_lock);
if (dev_skipped)
DMWARN("remove_all left %d open device(s)", dev_skipped);
}
static struct mapped_device *dm_hash_rename(struct dm_ioctl *param,