dm ioctl: release _hash_lock between devices in remove_all
authorKiyoshi Ueda <k-ueda@ct.jp.nec.com>
Thu, 12 Aug 2010 03:13:55 +0000 (04:13 +0100)
committerAlasdair G Kergon <agk@redhat.com>
Thu, 12 Aug 2010 03:13:55 +0000 (04:13 +0100)
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>
drivers/md/dm-ioctl.c

index 79ee5ba..6a6d475 100644 (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;
-               }
-       }
 
-       /*
-        * 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;
+                       up_write(&_hash_lock);
 
-               DMWARN("remove_all left %d open device(s)", dev_skipped);
+                       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;
+               }
        }
 
        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,