From 0ebd1f47c50ee089524e8bd4f9b6dfb9c05764b7 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Wed, 9 Jun 2021 14:27:42 +0800 Subject: [PATCH] btrfs-progs: check: add the ability to reset btrfs_dev_item::bytes_used There is a report from the mailing list that one user got its filesystem with device item bytes_used mismatch. This problem leaves the device item with some ghost bytes_used, meaning even if we delete all device extents of that device, the bytes_used still won't be 0. This itself is not a big deal, but when the user used up all its unallocated space, write time tree-checker can be triggered and make the fs RO, as the new device::bytes_used can be larger than device::total_bytes. Thus we need to fix the problem in btrfs-check to avoid above write-time tree check warning. This patch will add the ability to reset a device's bytes_used to both original mode and lowmem mode. Signed-off-by: Qu Wenruo Signed-off-by: David Sterba --- check/main.c | 8 ++++++- check/mode-common.c | 58 +++++++++++++++++++++++++++++++++++++++++++++ check/mode-common.h | 3 +++ check/mode-lowmem.c | 14 +++++++++-- 4 files changed, 80 insertions(+), 3 deletions(-) diff --git a/check/main.c b/check/main.c index ee6cf793..6cf96b1a 100644 --- a/check/main.c +++ b/check/main.c @@ -8397,11 +8397,17 @@ static int check_device_used(struct device_record *dev_rec, } if (total_byte != dev_rec->byte_used) { + int ret = -1; + fprintf(stderr, "Dev extent's total-byte(%llu) is not equal to byte-used(%llu) in dev[%llu, %u, %llu]\n", total_byte, dev_rec->byte_used, dev_rec->objectid, dev_rec->type, dev_rec->offset); - return -1; + if (repair) { + ret = repair_dev_item_bytes_used(gfs_info, + dev_rec->devid, total_byte); + } + return ret; } else { return 0; } diff --git a/check/mode-common.c b/check/mode-common.c index cb22f323..0059672c 100644 --- a/check/mode-common.c +++ b/check/mode-common.c @@ -21,6 +21,7 @@ #include "kernel-shared/transaction.h" #include "common/utils.h" #include "kernel-shared/disk-io.h" +#include "kernel-shared/volumes.h" #include "common/repair.h" #include "check/mode-common.h" @@ -1243,3 +1244,60 @@ out: btrfs_release_path(&path); return ret; } + +int repair_dev_item_bytes_used(struct btrfs_fs_info *fs_info, + u64 devid, u64 bytes_used_expected) +{ + struct btrfs_trans_handle *trans; + struct btrfs_device *device; + int ret; + + device = btrfs_find_device_by_devid(fs_info->fs_devices, devid, 0); + if (!device) { + error("failed to find device with devid %llu", devid); + return -ENOENT; + } + + /* Bytes_used matches, not what we can repair */ + if (device->bytes_used == bytes_used_expected) + return -ENOTSUP; + + /* + * We have to set the device bytes_used right now, before starting a + * new transaction, as it may allocate a new chunk and modify + * device->bytes_used. + */ + device->bytes_used = bytes_used_expected; + trans = btrfs_start_transaction(fs_info->chunk_root, 1); + if (IS_ERR(trans)) { + ret = PTR_ERR(trans); + errno = -ret; + error("failed to start transaction: %m"); + return ret; + } + + /* Manually update the device item in chunk tree */ + ret = btrfs_update_device(trans, device); + if (ret < 0) { + errno = -ret; + error("failed to update device item for devid %llu: %m", devid); + goto error; + } + + /* + * Commit transaction not only to save the above change but also update + * the device item in super block. + */ + ret = btrfs_commit_transaction(trans, fs_info->chunk_root); + if (ret < 0) { + errno = -ret; + error("failed to commit transaction: %m"); + } else { + printf("reset devid %llu bytes_used to %llu\n", devid, + device->bytes_used); + } + return ret; +error: + btrfs_abort_transaction(trans, ret); + return ret; +} diff --git a/check/mode-common.h b/check/mode-common.h index 3ba29eca..cdfb10d5 100644 --- a/check/mode-common.h +++ b/check/mode-common.h @@ -192,4 +192,7 @@ static inline void btrfs_check_subpage_eb_alignment(u64 start, u32 len) start, start + len); } +int repair_dev_item_bytes_used(struct btrfs_fs_info *fs_info, + u64 devid, u64 bytes_used_expected); + #endif diff --git a/check/mode-lowmem.c b/check/mode-lowmem.c index 2f736712..323e66bc 100644 --- a/check/mode-lowmem.c +++ b/check/mode-lowmem.c @@ -4454,7 +4454,8 @@ out: /* * Check if the used space is correct with the dev item */ -static int check_dev_item(struct extent_buffer *eb, int slot) +static int check_dev_item(struct extent_buffer *eb, int slot, + u64 *bytes_used_expected) { struct btrfs_root *dev_root = gfs_info->dev_root; struct btrfs_dev_item *dev_item; @@ -4543,6 +4544,7 @@ next: } btrfs_release_path(&path); + *bytes_used_expected = total; if (used != total) { btrfs_item_key_to_cpu(eb, &key, slot); error( @@ -4744,6 +4746,7 @@ static int repair_chunk_item(struct btrfs_root *chunk_root, static int check_leaf_items(struct btrfs_root *root, struct btrfs_path *path, struct node_refs *nrefs, int account_bytes) { + u64 bytes_used_expected = (u64)-1; struct btrfs_key key; struct extent_buffer *eb; int slot; @@ -4782,7 +4785,14 @@ again: err |= ret; break; case BTRFS_DEV_ITEM_KEY: - ret = check_dev_item(eb, slot); + ret = check_dev_item(eb, slot, &bytes_used_expected); + if (repair && (ret & ACCOUNTING_MISMATCH) && + bytes_used_expected != (u64)-1) { + ret = repair_dev_item_bytes_used(root->fs_info, + key.offset, bytes_used_expected); + if (ret < 0) + ret = ACCOUNTING_MISMATCH; + } err |= ret; break; case BTRFS_CHUNK_ITEM_KEY: