summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorQu Wenruo <quwenruo@cn.fujitsu.com>2016-10-13 17:22:26 +0800
committerDavid Sterba <dsterba@suse.com>2016-11-09 13:12:47 +0100
commiteb8a61b235ad035cc474bef78e10453ec3d11d72 (patch)
treeec41a685063f7a315e728abeeef31afd4031d110
parent08d31c1c7ca5a6bb4e6485fa118c552c938e9efb (diff)
btrfs-progs: check: add support to clear v1 free space cache
Kernel clear_cache mount option will only rebuild free space cache if the used space of that chunk has changed. So it won't ensure any corrupted free space cache get cleared. So add a new option "--clear-space-cache v1|v2" to btrfsck, to completely wipe out free space cache. So kernel won't complain again. Reported-by: Ivan P <chrnosphered@gmail.com> Signed-off-by: Qu Wenruo <quwenruo@cn.fujitsu.com> [ adjusted error messages, doc wording changes ] Signed-off-by: David Sterba <dsterba@suse.com>
-rw-r--r--Documentation/btrfs-check.asciidoc10
-rw-r--r--cmds-check.c65
-rw-r--r--free-space-cache.c127
-rw-r--r--free-space-cache.h2
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