block: restore multiple bd_link_disk_holder() support
authorTejun Heo <tj@kernel.org>
Fri, 14 Jan 2011 17:43:57 +0000 (18:43 +0100)
committerJens Axboe <jaxboe@fusionio.com>
Fri, 14 Jan 2011 17:44:22 +0000 (18:44 +0100)
Commit e09b457b (block: simplify holder symlink handling) incorrectly
assumed that there is only one link at maximum.  dm may use multiple
links and expects block layer to track reference count for each link,
which is different from and unrelated to the exclusive device holder
identified by @holder when the device is opened.

Remove the single holder assumption and automatic removal of the link
and revive the per-link reference count tracking.  The code
essentially behaves the same as before commit e09b457b sans the
unnecessary kobject reference count dancing.

While at it, note that this facility should not be used by anyone else
than the current ones.  Sysfs symlinks shouldn't be abused like this
and the whole thing doesn't belong in the block layer at all.

Signed-off-by: Tejun Heo <tj@kernel.org>
Reported-by: Milan Broz <mbroz@redhat.com>
Cc: Jun'ichi Nomura <j-nomura@ce.jp.nec.com>
Cc: Neil Brown <neilb@suse.de>
Cc: linux-raid@vger.kernel.org
Cc: Kay Sievers <kay.sievers@vrfy.org>
Signed-off-by: Jens Axboe <jaxboe@fusionio.com>
drivers/md/dm-table.c
drivers/md/md.c
fs/block_dev.c
include/linux/fs.h

index dffa0ac7c4f0e135ec410bcc0e30dff9a443a78d..38e4eb1bb9656ba565150a48d68594836f51c4f9 100644 (file)
@@ -350,6 +350,7 @@ static void close_dev(struct dm_dev_internal *d, struct mapped_device *md)
        if (!d->dm_dev.bdev)
                return;
 
+       bd_unlink_disk_holder(d->dm_dev.bdev, dm_disk(md));
        blkdev_put(d->dm_dev.bdev, d->dm_dev.mode | FMODE_EXCL);
        d->dm_dev.bdev = NULL;
 }
index cf8594c5ea21a481577705b17b95da1186bfb044..b76cfc89e1b57ce5557d7d4d931132c6f4efcc82 100644 (file)
@@ -1912,6 +1912,7 @@ static void unbind_rdev_from_array(mdk_rdev_t * rdev)
                MD_BUG();
                return;
        }
+       bd_unlink_disk_holder(rdev->bdev, rdev->mddev->gendisk);
        list_del_rcu(&rdev->same_set);
        printk(KERN_INFO "md: unbind<%s>\n", bdevname(rdev->bdev,b));
        rdev->mddev = NULL;
index fe3f59c14a02bb523aaaffc4f668b785fed06f01..333a7bb4cb9c0865543bc210c6df2613662950c9 100644 (file)
@@ -432,6 +432,9 @@ static void init_once(void *foo)
        mutex_init(&bdev->bd_mutex);
        INIT_LIST_HEAD(&bdev->bd_inodes);
        INIT_LIST_HEAD(&bdev->bd_list);
+#ifdef CONFIG_SYSFS
+       INIT_LIST_HEAD(&bdev->bd_holder_disks);
+#endif
        inode_init_once(&ei->vfs_inode);
        /* Initialize mutex for freeze. */
        mutex_init(&bdev->bd_fsfreeze_mutex);
@@ -779,6 +782,23 @@ static struct block_device *bd_start_claiming(struct block_device *bdev,
 }
 
 #ifdef CONFIG_SYSFS
+struct bd_holder_disk {
+       struct list_head        list;
+       struct gendisk          *disk;
+       int                     refcnt;
+};
+
+static struct bd_holder_disk *bd_find_holder_disk(struct block_device *bdev,
+                                                 struct gendisk *disk)
+{
+       struct bd_holder_disk *holder;
+
+       list_for_each_entry(holder, &bdev->bd_holder_disks, list)
+               if (holder->disk == disk)
+                       return holder;
+       return NULL;
+}
+
 static int add_symlink(struct kobject *from, struct kobject *to)
 {
        return sysfs_create_link(from, to, kobject_name(to));
@@ -794,6 +814,8 @@ static void del_symlink(struct kobject *from, struct kobject *to)
  * @bdev: the claimed slave bdev
  * @disk: the holding disk
  *
+ * DON'T USE THIS UNLESS YOU'RE ALREADY USING IT.
+ *
  * This functions creates the following sysfs symlinks.
  *
  * - from "slaves" directory of the holder @disk to the claimed @bdev
@@ -817,47 +839,83 @@ static void del_symlink(struct kobject *from, struct kobject *to)
  */
 int bd_link_disk_holder(struct block_device *bdev, struct gendisk *disk)
 {
+       struct bd_holder_disk *holder;
        int ret = 0;
 
        mutex_lock(&bdev->bd_mutex);
 
-       WARN_ON_ONCE(!bdev->bd_holder || bdev->bd_holder_disk);
+       WARN_ON_ONCE(!bdev->bd_holder);
 
        /* FIXME: remove the following once add_disk() handles errors */
        if (WARN_ON(!disk->slave_dir || !bdev->bd_part->holder_dir))
                goto out_unlock;
 
-       ret = add_symlink(disk->slave_dir, &part_to_dev(bdev->bd_part)->kobj);
-       if (ret)
+       holder = bd_find_holder_disk(bdev, disk);
+       if (holder) {
+               holder->refcnt++;
                goto out_unlock;
+       }
 
-       ret = add_symlink(bdev->bd_part->holder_dir, &disk_to_dev(disk)->kobj);
-       if (ret) {
-               del_symlink(disk->slave_dir, &part_to_dev(bdev->bd_part)->kobj);
+       holder = kzalloc(sizeof(*holder), GFP_KERNEL);
+       if (!holder) {
+               ret = -ENOMEM;
                goto out_unlock;
        }
 
-       bdev->bd_holder_disk = disk;
+       INIT_LIST_HEAD(&holder->list);
+       holder->disk = disk;
+       holder->refcnt = 1;
+
+       ret = add_symlink(disk->slave_dir, &part_to_dev(bdev->bd_part)->kobj);
+       if (ret)
+               goto out_free;
+
+       ret = add_symlink(bdev->bd_part->holder_dir, &disk_to_dev(disk)->kobj);
+       if (ret)
+               goto out_del;
+
+       list_add(&holder->list, &bdev->bd_holder_disks);
+       goto out_unlock;
+
+out_del:
+       del_symlink(disk->slave_dir, &part_to_dev(bdev->bd_part)->kobj);
+out_free:
+       kfree(holder);
 out_unlock:
        mutex_unlock(&bdev->bd_mutex);
        return ret;
 }
 EXPORT_SYMBOL_GPL(bd_link_disk_holder);
 
-static void bd_unlink_disk_holder(struct block_device *bdev)
+/**
+ * bd_unlink_disk_holder - destroy symlinks created by bd_link_disk_holder()
+ * @bdev: the calimed slave bdev
+ * @disk: the holding disk
+ *
+ * DON'T USE THIS UNLESS YOU'RE ALREADY USING IT.
+ *
+ * CONTEXT:
+ * Might sleep.
+ */
+void bd_unlink_disk_holder(struct block_device *bdev, struct gendisk *disk)
 {
-       struct gendisk *disk = bdev->bd_holder_disk;
+       struct bd_holder_disk *holder;
 
-       bdev->bd_holder_disk = NULL;
-       if (!disk)
-               return;
+       mutex_lock(&bdev->bd_mutex);
 
-       del_symlink(disk->slave_dir, &part_to_dev(bdev->bd_part)->kobj);
-       del_symlink(bdev->bd_part->holder_dir, &disk_to_dev(disk)->kobj);
+       holder = bd_find_holder_disk(bdev, disk);
+
+       if (!WARN_ON_ONCE(holder == NULL) && !--holder->refcnt) {
+               del_symlink(disk->slave_dir, &part_to_dev(bdev->bd_part)->kobj);
+               del_symlink(bdev->bd_part->holder_dir,
+                           &disk_to_dev(disk)->kobj);
+               list_del_init(&holder->list);
+               kfree(holder);
+       }
+
+       mutex_unlock(&bdev->bd_mutex);
 }
-#else
-static inline void bd_unlink_disk_holder(struct block_device *bdev)
-{ }
+EXPORT_SYMBOL_GPL(bd_unlink_disk_holder);
 #endif
 
 /**
@@ -1380,7 +1438,6 @@ int blkdev_put(struct block_device *bdev, fmode_t mode)
                 * unblock evpoll if it was a write holder.
                 */
                if (bdev_free) {
-                       bd_unlink_disk_holder(bdev);
                        if (bdev->bd_write_holder) {
                                disk_unblock_events(bdev->bd_disk);
                                bdev->bd_write_holder = false;
index 3984f2358d1f56d98de234203ddec0cc9f20b538..fb2190349cdfb412b932a66c0dd4ffb490062dea 100644 (file)
@@ -666,7 +666,7 @@ struct block_device {
        int                     bd_holders;
        bool                    bd_write_holder;
 #ifdef CONFIG_SYSFS
-       struct gendisk *        bd_holder_disk; /* for sysfs slave linkng */
+       struct list_head        bd_holder_disks;
 #endif
        struct block_device *   bd_contains;
        unsigned                bd_block_size;
@@ -2058,12 +2058,18 @@ extern struct block_device *blkdev_get_by_dev(dev_t dev, fmode_t mode,
 extern int blkdev_put(struct block_device *bdev, fmode_t mode);
 #ifdef CONFIG_SYSFS
 extern int bd_link_disk_holder(struct block_device *bdev, struct gendisk *disk);
+extern void bd_unlink_disk_holder(struct block_device *bdev,
+                                 struct gendisk *disk);
 #else
 static inline int bd_link_disk_holder(struct block_device *bdev,
                                      struct gendisk *disk)
 {
        return 0;
 }
+static inline void bd_unlink_disk_holder(struct block_device *bdev,
+                                        struct gendisk *disk)
+{
+}
 #endif
 #endif