block: change the refcounting for partitions
authorChristoph Hellwig <hch@lst.de>
Thu, 22 Jul 2021 07:53:58 +0000 (09:53 +0200)
committerJens Axboe <axboe@kernel.dk>
Mon, 2 Aug 2021 19:37:28 +0000 (13:37 -0600)
Instead of acquiring an inode reference on open make sure partitions
always hold device model references to the disk while alive, and switch
open to grab only a device model reference to the opened block device.
If that is a partition the disk reference is transitively held by the
partition already.

Link: https://lore.kernel.org/r/20210722075402.983367-6-hch@lst.de
Signed-off-by: Jens Axboe <axboe@kernel.dk>
block/partitions/core.c
fs/block_dev.c

index 09c58a1..4f7a1a9 100644 (file)
@@ -261,6 +261,7 @@ static void part_release(struct device *dev)
 {
        if (MAJOR(dev->devt) == BLOCK_EXT_MAJOR)
                blk_free_ext_minor(MINOR(dev->devt));
+       put_disk(dev_to_bdev(dev)->bd_disk);
        bdput(dev_to_bdev(dev));
 }
 
@@ -349,9 +350,13 @@ static struct block_device *add_partition(struct gendisk *disk, int partno,
        if (xa_load(&disk->part_tbl, partno))
                return ERR_PTR(-EBUSY);
 
+       /* ensure we always have a reference to the whole disk */
+       get_device(disk_to_dev(disk));
+
+       err = -ENOMEM;
        bdev = bdev_alloc(disk, partno);
        if (!bdev)
-               return ERR_PTR(-ENOMEM);
+               goto out_put_disk;
 
        bdev->bd_start_sect = start;
        bdev_set_nr_sectors(bdev, len);
@@ -420,6 +425,8 @@ out_del:
        device_del(pdev);
 out_put:
        put_device(pdev);
+out_put_disk:
+       put_disk(disk);
        return ERR_PTR(err);
 }
 
index 932f403..4a6c8c0 100644 (file)
@@ -921,16 +921,6 @@ void bdev_add(struct block_device *bdev, dev_t dev)
        insert_inode_hash(bdev->bd_inode);
 }
 
-static struct block_device *bdget(dev_t dev)
-{
-       struct inode *inode;
-
-       inode = ilookup(blockdev_superblock, dev);
-       if (!inode)
-               return NULL;
-       return &BDEV_I(inode)->bdev;
-}
-
 /**
  * bdgrab -- Grab a reference to an already referenced block device
  * @bdev:      Block device to grab a reference to.
@@ -1282,16 +1272,14 @@ static void blkdev_put_whole(struct block_device *bdev, fmode_t mode)
 static int blkdev_get_part(struct block_device *part, fmode_t mode)
 {
        struct gendisk *disk = part->bd_disk;
-       struct block_device *whole;
        int ret;
 
        if (part->bd_openers)
                goto done;
 
-       whole = bdgrab(disk->part0);
-       ret = blkdev_get_whole(whole, mode);
+       ret = blkdev_get_whole(bdev_whole(part), mode);
        if (ret)
-               goto out_put_whole;
+               return ret;
 
        ret = -ENXIO;
        if (!bdev_nr_sectors(part))
@@ -1306,9 +1294,7 @@ done:
        return 0;
 
 out_blkdev_put:
-       blkdev_put_whole(whole, mode);
-out_put_whole:
-       bdput(whole);
+       blkdev_put_whole(bdev_whole(part), mode);
        return ret;
 }
 
@@ -1321,42 +1307,42 @@ static void blkdev_put_part(struct block_device *part, fmode_t mode)
        blkdev_flush_mapping(part);
        whole->bd_disk->open_partitions--;
        blkdev_put_whole(whole, mode);
-       bdput(whole);
 }
 
 struct block_device *blkdev_get_no_open(dev_t dev)
 {
        struct block_device *bdev;
-       struct gendisk *disk;
+       struct inode *inode;
 
-       bdev = bdget(dev);
-       if (!bdev) {
+       inode = ilookup(blockdev_superblock, dev);
+       if (!inode) {
                blk_request_module(dev);
-               bdev = bdget(dev);
-               if (!bdev)
+               inode = ilookup(blockdev_superblock, dev);
+               if (!inode)
                        return NULL;
        }
 
-       disk = bdev->bd_disk;
-       if (!kobject_get_unless_zero(&disk_to_dev(disk)->kobj))
-               goto bdput;
-       if (disk->flags & GENHD_FL_HIDDEN)
-               goto put_disk;
-       if (!try_module_get(bdev->bd_disk->fops->owner))
-               goto put_disk;
+       /* switch from the inode reference to a device mode one: */
+       bdev = &BDEV_I(inode)->bdev;
+       if (!kobject_get_unless_zero(&bdev->bd_device.kobj))
+               bdev = NULL;
+       iput(inode);
+
+       if (!bdev)
+               return NULL;
+       if ((bdev->bd_disk->flags & GENHD_FL_HIDDEN) ||
+           !try_module_get(bdev->bd_disk->fops->owner)) {
+               put_device(&bdev->bd_device);
+               return NULL;
+       }
+
        return bdev;
-put_disk:
-       put_disk(disk);
-bdput:
-       bdput(bdev);
-       return NULL;
 }
 
 void blkdev_put_no_open(struct block_device *bdev)
 {
        module_put(bdev->bd_disk->fops->owner);
-       put_disk(bdev->bd_disk);
-       bdput(bdev);
+       put_device(&bdev->bd_device);
 }
 
 /**