block: WARN_ON_ONCE() when we remove active partitions
authorChristian Brauner <brauner@kernel.org>
Tue, 17 Oct 2023 18:48:20 +0000 (20:48 +0200)
committerChristian Brauner <brauner@kernel.org>
Sat, 28 Oct 2023 11:29:22 +0000 (13:29 +0200)
The logic for disk->open_partitions is:

blkdev_get_by_*()
-> bdev_is_partition()
   -> blkdev_get_part()
      -> blkdev_get_whole() // bdev_whole->bd_openers++
      -> if (part->bd_openers == 0)
                 disk->open_partitions++
         part->bd_openers

In other words, when we first claim/open a partition we increment
disk->open_partitions and only when all part->bd_openers are closed will
disk->open_partitions be zero. That should mean that
disk->open_partitions is always > 0 as long as there's anyone that
has an open partition.

So the check for disk->open_partitions should mean that we can never
remove an active partition that has a holder and holder ops set. Assert
that in the code. The main disk isn't removed so that check doesn't work
for disk->part0 which is what we want. After all we only care about
partition not about the main disk.

Link: https://lore.kernel.org/r/20231017184823.1383356-3-hch@lst.de
Reviewed-by: Ming Lei <ming.lei@redhat.com>
Reviewed-by: Jan Kara <jack@suse.cz>
Reviewed-by: Jens Axboe <axboe@kernel.dk>
Signed-off-by: Christian Brauner <brauner@kernel.org>
block/partitions/core.c

index b0585536b407a576d70bfe4ec4fcff72515a9ef9..f47ffcfdfcec225c14afa5b838d4fb08478228ea 100644 (file)
@@ -274,17 +274,6 @@ void drop_partition(struct block_device *part)
        put_device(&part->bd_device);
 }
 
-static void delete_partition(struct block_device *part)
-{
-       /*
-        * Remove the block device from the inode hash, so that it cannot be
-        * looked up any more even when openers still hold references.
-        */
-       remove_inode_hash(part->bd_inode);
-       bdev_mark_dead(part, false);
-       drop_partition(part);
-}
-
 static ssize_t whole_disk_show(struct device *dev,
                               struct device_attribute *attr, char *buf)
 {
@@ -674,8 +663,23 @@ rescan:
        sync_blockdev(disk->part0);
        invalidate_bdev(disk->part0);
 
-       xa_for_each_start(&disk->part_tbl, idx, part, 1)
-               delete_partition(part);
+       xa_for_each_start(&disk->part_tbl, idx, part, 1) {
+               /*
+                * Remove the block device from the inode hash, so that
+                * it cannot be looked up any more even when openers
+                * still hold references.
+                */
+               remove_inode_hash(part->bd_inode);
+
+               /*
+                * If @disk->open_partitions isn't elevated but there's
+                * still an active holder of that block device things
+                * are broken.
+                */
+               WARN_ON_ONCE(atomic_read(&part->bd_openers));
+               invalidate_bdev(part);
+               drop_partition(part);
+       }
        clear_bit(GD_NEED_PART_SCAN, &disk->state);
 
        /*