diff options
-rw-r--r-- | Documentation/btrfs-check.asciidoc | 10 | ||||
-rw-r--r-- | cmds-check.c | 65 | ||||
-rw-r--r-- | free-space-cache.c | 127 | ||||
-rw-r--r-- | free-space-cache.h | 2 |
4 files changed, 203 insertions, 1 deletions
diff --git a/Documentation/btrfs-check.asciidoc b/Documentation/btrfs-check.asciidoc index a32e1c7e..5ef414eb 100644 --- a/Documentation/btrfs-check.asciidoc +++ b/Documentation/btrfs-check.asciidoc @@ -78,6 +78,16 @@ respective superblock offset is within the device size This can be used to use a different starting point if some of the primary superblock is damaged. +--clear-space-cache v1|v2:: +completely wipe all free space cache of given type + +NOTE: Only v1 free space cache supported is implemented. ++ +Kernel mount option 'clear_cache' is only designed to rebuild free space cache +which is modified during the lifetime of that mount option. It doesn't rebuild +all free space cache, nor clear them out. + + DANGEROUS OPTIONS ----------------- diff --git a/cmds-check.c b/cmds-check.c index 7718c7be..dadc143f 100644 --- a/cmds-check.c +++ b/cmds-check.c @@ -11209,6 +11209,36 @@ out: return bad_roots; } +static int clear_free_space_cache(struct btrfs_fs_info *fs_info) +{ + struct btrfs_trans_handle *trans; + struct btrfs_block_group_cache *bg_cache; + u64 current = 0; + int ret = 0; + + /* Clear all free space cache inodes and its extent data */ + while (1) { + bg_cache = btrfs_lookup_first_block_group(fs_info, current); + if (!bg_cache) + break; + ret = btrfs_clear_free_space_cache(fs_info, bg_cache); + if (ret < 0) + return ret; + current = bg_cache->key.objectid + bg_cache->key.offset; + } + + /* Don't forget to set cache_generation to -1 */ + trans = btrfs_start_transaction(fs_info->tree_root, 0); + if (IS_ERR(trans)) { + error("failed to update super block cache generation"); + return PTR_ERR(trans); + } + btrfs_set_super_cache_generation(fs_info->super_copy, (u64)-1); + btrfs_commit_transaction(trans, fs_info->tree_root); + + return ret; +} + const char * const cmd_check_usage[] = { "btrfs check [options] <device>", "Check structural integrity of a filesystem (unmounted).", @@ -11236,6 +11266,8 @@ const char * const cmd_check_usage[] = { "-r|--tree-root <bytenr> use the given bytenr for the tree root", "--chunk-root <bytenr> use the given bytenr for the chunk tree root", "-p|--progress indicate progress", + "--clear-space-cache v1|v2 clear space cache for v1 or v2", + " NOTE: v1 support implemented", NULL }; @@ -11253,6 +11285,7 @@ int cmd_check(int argc, char **argv) u64 num; int init_csum_tree = 0; int readonly = 0; + int clear_space_cache = 0; int qgroup_report = 0; int qgroups_repaired = 0; unsigned ctree_flags = OPEN_CTREE_EXCLUSIVE; @@ -11262,7 +11295,7 @@ int cmd_check(int argc, char **argv) enum { GETOPT_VAL_REPAIR = 257, GETOPT_VAL_INIT_CSUM, GETOPT_VAL_INIT_EXTENT, GETOPT_VAL_CHECK_CSUM, GETOPT_VAL_READONLY, GETOPT_VAL_CHUNK_TREE, - GETOPT_VAL_MODE }; + GETOPT_VAL_MODE, GETOPT_VAL_CLEAR_SPACE_CACHE }; static const struct option long_options[] = { { "super", required_argument, NULL, 's' }, { "repair", no_argument, NULL, GETOPT_VAL_REPAIR }, @@ -11282,6 +11315,8 @@ int cmd_check(int argc, char **argv) { "progress", no_argument, NULL, 'p' }, { "mode", required_argument, NULL, GETOPT_VAL_MODE }, + { "clear-space-cache", required_argument, NULL, + GETOPT_VAL_CLEAR_SPACE_CACHE}, { NULL, 0, NULL, 0} }; @@ -11353,6 +11388,16 @@ int cmd_check(int argc, char **argv) exit(1); } break; + case GETOPT_VAL_CLEAR_SPACE_CACHE: + if (strcmp(optarg, "v1") != 0) { + error( + "only v1 support implmented, unrecognized value %s", + optarg); + exit(1); + } + clear_space_cache = 1; + ctree_flags |= OPEN_CTREE_WRITES; + break; } } @@ -11404,6 +11449,24 @@ int cmd_check(int argc, char **argv) global_info = info; root = info->fs_root; + if (clear_space_cache) { + if (btrfs_fs_compat_ro(info, + BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE)) { + error( + "free space cache v2 detected, clearing not implemented"); + ret = 1; + goto close_out; + } + printf("Clearing free space cache\n"); + ret = clear_free_space_cache(info); + if (ret) { + error("failed to clear free space cache"); + ret = 1; + } else { + printf("Free space cache cleared\n"); + } + goto close_out; + } /* * repair mode will force us to commit transaction which diff --git a/free-space-cache.c b/free-space-cache.c index 1919d90e..286b185e 100644 --- a/free-space-cache.c +++ b/free-space-cache.c @@ -25,6 +25,7 @@ #include "crc32c.h" #include "bitops.h" #include "internal.h" +#include "utils.h" /* * Kernel always uses PAGE_CACHE_SIZE for sectorsize, but we don't have @@ -877,3 +878,129 @@ next: prev = e; } } + +int btrfs_clear_free_space_cache(struct btrfs_fs_info *fs_info, + struct btrfs_block_group_cache *bg) +{ + struct btrfs_trans_handle *trans; + struct btrfs_root *tree_root = fs_info->tree_root; + struct btrfs_path path; + struct btrfs_key key; + struct btrfs_disk_key location; + struct btrfs_free_space_header *sc_header; + struct extent_buffer *node; + u64 ino; + int slot; + int ret; + + trans = btrfs_start_transaction(tree_root, 1); + if (IS_ERR(trans)) + return PTR_ERR(trans); + + btrfs_init_path(&path); + + key.objectid = BTRFS_FREE_SPACE_OBJECTID; + key.type = 0; + key.offset = bg->key.objectid; + + ret = btrfs_search_slot(trans, tree_root, &key, &path, -1, 1); + if (ret > 0) { + ret = 0; + goto out; + } + if (ret < 0) + goto out; + + node = path.nodes[0]; + slot = path.slots[0]; + sc_header = btrfs_item_ptr(node, slot, struct btrfs_free_space_header); + btrfs_free_space_key(node, sc_header, &location); + ino = location.objectid; + + /* Delete the free space header, as we have the ino to continue */ + ret = btrfs_del_item(trans, tree_root, &path); + if (ret < 0) { + error("failed to remove free space header for block group %llu: %d", + bg->key.objectid, ret); + goto out; + } + btrfs_release_path(&path); + + /* Iterate from the end of the free space cache inode */ + key.objectid = ino; + key.type = BTRFS_EXTENT_DATA_KEY; + key.offset = (u64)-1; + ret = btrfs_search_slot(trans, tree_root, &key, &path, -1, 1); + if (ret < 0) { + error("failed to locate free space cache extent for block group %llu: %d", + bg->key.objectid, ret); + goto out; + } + while (1) { + struct btrfs_file_extent_item *fi; + u64 disk_bytenr; + u64 disk_num_bytes; + + ret = btrfs_previous_item(tree_root, &path, ino, + BTRFS_EXTENT_DATA_KEY); + if (ret > 0) { + ret = 0; + break; + } + if (ret < 0) { + error( + "failed to locate free space cache extent for block group %llu: %d", + bg->key.objectid, ret); + goto out; + } + node = path.nodes[0]; + slot = path.slots[0]; + btrfs_item_key_to_cpu(node, &key, slot); + fi = btrfs_item_ptr(node, slot, struct btrfs_file_extent_item); + disk_bytenr = btrfs_file_extent_disk_bytenr(node, fi); + disk_num_bytes = btrfs_file_extent_disk_num_bytes(node, fi); + + ret = btrfs_free_extent(trans, tree_root, disk_bytenr, + disk_num_bytes, 0, tree_root->objectid, + ino, key.offset); + if (ret < 0) { + error("failed to remove backref for disk bytenr %llu: %d", + disk_bytenr, ret); + goto out; + } + ret = btrfs_del_item(trans, tree_root, &path); + if (ret < 0) { + error( + "failed to remove free space extent data for ino %llu offset %llu: %d", + ino, key.offset, ret); + goto out; + } + } + btrfs_release_path(&path); + + /* Now delete free space cache inode item */ + key.objectid = ino; + key.type = BTRFS_INODE_ITEM_KEY; + key.offset = 0; + + ret = btrfs_search_slot(trans, tree_root, &key, &path, -1, 1); + if (ret > 0) + warning("free space inode %llu not found, ignore", ino); + if (ret < 0) { + error( + "failed to locate free space cache inode %llu for block group %llu: %d", + ino, bg->key.objectid, ret); + goto out; + } + ret = btrfs_del_item(trans, tree_root, &path); + if (ret < 0) { + error( + "failed to delete free space cache inode %llu for block group %llu: %d", + ino, bg->key.objectid, ret); + } +out: + btrfs_release_path(&path); + if (!ret) + btrfs_commit_transaction(trans, tree_root); + return ret; +} diff --git a/free-space-cache.h b/free-space-cache.h index 9214077a..707fb6d3 100644 --- a/free-space-cache.h +++ b/free-space-cache.h @@ -59,4 +59,6 @@ void unlink_free_space(struct btrfs_free_space_ctl *ctl, struct btrfs_free_space *info); int btrfs_add_free_space(struct btrfs_free_space_ctl *ctl, u64 offset, u64 bytes); +int btrfs_clear_free_space_cache(struct btrfs_fs_info *fs_info, + struct btrfs_block_group_cache *bg); #endif |