btrfs-progs: rescue: allow fix-device-size to shrink device item

If we found that the underlying block device size is smaller than
total_bytes in dev item, kernel will reject the mount, and there is no
progs tool to fix it.

Under most case it's just a small mismatch, and there is no dev extent
in the shrunk range.

In that case, we can let "btrfs rescue fix-device-size" to reset the
total_bytes in dev items to fix.

We add some extra checks, like to make sure there is no dev extent in
the shrunk device range, to make sure we won't lose data during the
device item shrink.

And also update the test case to verify the repaired fs can pass the
check.

Issue: #504
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
This commit is contained in:
Qu Wenruo 2022-08-30 14:49:44 +08:00 committed by David Sterba
parent 192d436503
commit e47c34821f
1 changed files with 125 additions and 9 deletions

View File

@ -2786,12 +2786,62 @@ u64 btrfs_stripe_length(struct btrfs_fs_info *fs_info,
}
/*
* Return 0 if size of @device is already good
* Return >0 if size of @device is not aligned but fixed without problems
* Return <0 if something wrong happened when aligning the size of @device
* Return <0 for error.
* Return >0 if we can not find any dev extent beyond @physical
* REturn 0 if we can find any dev extent beyond @physical or covers @physical.
*/
int btrfs_fix_device_size(struct btrfs_fs_info *fs_info,
struct btrfs_device *device)
static int check_dev_extent_beyond_bytenr(struct btrfs_fs_info *fs_info,
struct btrfs_device *device,
u64 physical)
{
struct btrfs_root *root = fs_info->dev_root;
struct btrfs_path path;
struct btrfs_dev_extent *dext;
struct btrfs_key key;
u64 dext_len;
u64 last_dev_extent_end = 0;
int ret;
key.objectid = device->devid;
key.type = BTRFS_DEV_EXTENT_KEY;
key.offset = (u64)-1;
btrfs_init_path(&path);
ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0);
if (ret < 0)
return ret;
if (ret == 0) {
ret = -EUCLEAN;
error("invalid dev extent found for devid %llu", device->devid);
goto out;
}
ret = btrfs_previous_item(root, &path, device->devid, BTRFS_DEV_EXTENT_KEY);
/*
* Either <0 we error out, or ret > 0 we can not find any dev extent
* for this device, then last_dev_extent_end will be 0 and we will
* return 1.
*/
if (ret)
goto out;
btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]);
dext = btrfs_item_ptr(path.nodes[0], path.slots[0], struct btrfs_dev_extent);
dext_len = btrfs_dev_extent_length(path.nodes[0], dext);
last_dev_extent_end = dext_len + key.offset;
out:
btrfs_release_path(&path);
if (ret < 0)
return ret;
if (last_dev_extent_end <= physical)
return 1;
return 0;
}
static int reset_device_item_total_bytes(struct btrfs_fs_info *fs_info,
struct btrfs_device *device,
u64 new_size)
{
struct btrfs_trans_handle *trans;
struct btrfs_key key;
@ -2801,12 +2851,10 @@ int btrfs_fix_device_size(struct btrfs_fs_info *fs_info,
u64 old_bytes = device->total_bytes;
int ret;
if (IS_ALIGNED(old_bytes, fs_info->sectorsize))
return 0;
ASSERT(IS_ALIGNED(new_size, fs_info->sectorsize));
/* Align the in-memory total_bytes first, and use it as correct size */
device->total_bytes = round_down(device->total_bytes,
fs_info->sectorsize);
device->total_bytes = new_size;
key.objectid = BTRFS_DEV_ITEMS_OBJECTID;
key.type = BTRFS_DEV_ITEM_KEY;
@ -2854,6 +2902,74 @@ err:
return ret;
}
static int btrfs_fix_block_device_size(struct btrfs_fs_info *fs_info,
struct btrfs_device *device)
{
struct stat st;
u64 block_dev_size;
int ret;
if (device->fd < 0 || !device->writeable) {
error("devid %llu is missing or not writable", device->devid);
return -EINVAL;
}
ret = fstat(device->fd, &st);
if (ret < 0) {
error("failed to get block device size for devid %llu: %m",
device->devid);
return -errno;
}
block_dev_size = round_down(btrfs_device_size(device->fd, &st),
fs_info->sectorsize);
/*
* Total_bytes in device item is no larger than the device block size,
* already the correct case.
*/
if (device->total_bytes <= block_dev_size)
return 0;
/*
* Now we need to check if there is any device extent beyond
* @block_dev_size.
*/
ret = check_dev_extent_beyond_bytenr(fs_info, device, block_dev_size);
if (ret < 0)
return ret;
if (ret == 0) {
error(
"found dev extents covering or beyond bytenr %llu, can not shrink the device without losing data",
device->devid);
return -EINVAL;
}
/* Now we can shrink the device item total_bytes to @block_dev_size. */
return reset_device_item_total_bytes(fs_info, device, block_dev_size);
}
/*
* Return 0 if size of @device is already good
* Return >0 if size of @device is not aligned but fixed without problems
* Return <0 if something wrong happened when aligning the size of @device
*/
int btrfs_fix_device_size(struct btrfs_fs_info *fs_info, struct btrfs_device *device)
{
u64 old_bytes = device->total_bytes;
/*
* Our value is already good, then check if it's device item mismatch against
* block device size.
*/
if (IS_ALIGNED(old_bytes, fs_info->sectorsize))
return btrfs_fix_block_device_size(fs_info, device);
return reset_device_item_total_bytes(fs_info, device,
round_down(old_bytes, fs_info->sectorsize));
}
/*
* Return 0 if super block total_bytes matches all devices' total_bytes
* Return >0 if super block total_bytes mismatch but fixed without problem