btrfs: fix ->free_chunk_space math in btrfs_shrink_device
authorJosef Bacik <josef@toxicpanda.com>
Wed, 27 Sep 2023 17:46:59 +0000 (13:46 -0400)
committerDavid Sterba <dsterba@suse.com>
Thu, 12 Oct 2023 14:44:15 +0000 (16:44 +0200)
There are two bugs in how we adjust ->free_chunk_space in
btrfs_shrink_device.  First we're removing the entire diff between
new_size and old_size from ->free_chunk_space.  This only works if we're
reducing the free area, which we could potentially not be.  So adjust
the math to only subtract the diff in the free space from
->free_chunk_space.

Additionally in the error case we're unconditionally adding the diff
back into ->free_chunk_space, which we need to only do if this device is
writeable.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/volumes.c

index 298e5885ed06893935ada146f1767de9c70a593d..8355533fd2876c6f7e4ca2c36349510ac18d852f 100644 (file)
@@ -4722,6 +4722,7 @@ int btrfs_shrink_device(struct btrfs_device *device, u64 new_size)
        u64 old_size = btrfs_device_get_total_bytes(device);
        u64 diff;
        u64 start;
+       u64 free_diff = 0;
 
        new_size = round_down(new_size, fs_info->sectorsize);
        start = new_size;
@@ -4747,7 +4748,19 @@ int btrfs_shrink_device(struct btrfs_device *device, u64 new_size)
        btrfs_device_set_total_bytes(device, new_size);
        if (test_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state)) {
                device->fs_devices->total_rw_bytes -= diff;
-               atomic64_sub(diff, &fs_info->free_chunk_space);
+
+               /*
+                * The new free_chunk_space is new_size - used, so we have to
+                * subtract the delta of the old free_chunk_space which included
+                * old_size - used.  If used > new_size then just subtract this
+                * entire device's free space.
+                */
+               if (device->bytes_used < new_size)
+                       free_diff = (old_size - device->bytes_used) -
+                                   (new_size - device->bytes_used);
+               else
+                       free_diff = old_size - device->bytes_used;
+               atomic64_sub(free_diff, &fs_info->free_chunk_space);
        }
 
        /*
@@ -4882,9 +4895,10 @@ done:
        if (ret) {
                mutex_lock(&fs_info->chunk_mutex);
                btrfs_device_set_total_bytes(device, old_size);
-               if (test_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state))
+               if (test_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state)) {
                        device->fs_devices->total_rw_bytes += diff;
-               atomic64_add(diff, &fs_info->free_chunk_space);
+                       atomic64_add(free_diff, &fs_info->free_chunk_space);
+               }
                mutex_unlock(&fs_info->chunk_mutex);
        }
        return ret;