diff --git a/kernel-shared/volumes.c b/kernel-shared/volumes.c index 40032a4b..1aa4345a 100644 --- a/kernel-shared/volumes.c +++ b/kernel-shared/volumes.c @@ -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