btrfs-progs: check: lowmem: Add ability to repair dir item with mismatched hash

For DIR_ITEM with mismatch hash, we could just remove the offending dir
item from the tree.

Lowmem mode will handle the rest, either re-create the correct dir_item
or move the orphan inode to lost+found.

This is especially important for old filesystems, since later kernel
introduces stricter tree-checker, which could detect such hash mismatch
and refuse to read the corrupted leaf.

With this repair ability, user could repair with 'btrfs check
--mode=lowmem --repair'.

Link: https://bugzilla.opensuse.org/show_bug.cgi?id=1111991
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
This commit is contained in:
Qu Wenruo 2018-10-25 13:20:46 +08:00 committed by David Sterba
parent 1bff5b8261
commit 38aff3d1ed
6 changed files with 103 additions and 10 deletions

View File

@ -744,3 +744,54 @@ void cleanup_excluded_extents(struct btrfs_fs_info *fs_info)
} }
fs_info->excluded_extents = NULL; fs_info->excluded_extents = NULL;
} }
/*
* Delete one corrupted dir item whose hash doesn't match its name.
*
* Since its hash is incorrect, we can't use btrfs_name_hash() to calculate
* the search key, but rely on @di_key parameter to do the search.
*/
int delete_corrupted_dir_item(struct btrfs_trans_handle *trans,
struct btrfs_root *root,
struct btrfs_key *di_key, char *namebuf,
u32 namelen)
{
struct btrfs_dir_item *di_item;
struct btrfs_path path;
int ret;
btrfs_init_path(&path);
ret = btrfs_search_slot(trans, root, di_key, &path, 0, 1);
if (ret > 0) {
error("key (%llu %u %llu) doesn't exist in root %llu",
di_key->objectid, di_key->type, di_key->offset,
root->root_key.objectid);
ret = -ENOENT;
goto out;
}
if (ret < 0) {
error("failed to search root %llu: %d",
root->root_key.objectid, ret);
goto out;
}
di_item = btrfs_match_dir_item_name(root, &path, namebuf, namelen);
if (!di_item) {
/*
* This is possible if the dir_item has incorrect namelen.
* But in that case, we shouldn't reach repair path here.
*/
error("no dir item named '%s' found with key (%llu %u %llu)",
namebuf, di_key->objectid, di_key->type,
di_key->offset);
ret = -ENOENT;
goto out;
}
ret = btrfs_delete_one_dir_name(trans, root, &path, di_item);
if (ret < 0)
error("failed to delete one dir name: %d", ret);
out:
btrfs_release_path(&path);
return ret;
}

View File

@ -121,5 +121,9 @@ void reset_cached_block_groups(struct btrfs_fs_info *fs_info);
int pin_metadata_blocks(struct btrfs_fs_info *fs_info); int pin_metadata_blocks(struct btrfs_fs_info *fs_info);
int exclude_metadata_blocks(struct btrfs_fs_info *fs_info); int exclude_metadata_blocks(struct btrfs_fs_info *fs_info);
void cleanup_excluded_extents(struct btrfs_fs_info *fs_info); void cleanup_excluded_extents(struct btrfs_fs_info *fs_info);
int delete_corrupted_dir_item(struct btrfs_trans_handle *trans,
struct btrfs_root *root,
struct btrfs_key *di_key, char *namebuf,
u32 namelen);
#endif #endif

View File

@ -1473,17 +1473,53 @@ out:
return ret; return ret;
} }
/*
* A wrapper for delete_corrupted_dir_item(), with support part like
* start/commit transaction.
*/
static int lowmem_delete_corrupted_dir_item(struct btrfs_root *root,
struct btrfs_key *di_key,
char *namebuf, u32 name_len)
{
struct btrfs_trans_handle *trans;
int ret;
trans = btrfs_start_transaction(root, 1);
if (IS_ERR(trans)) {
ret = PTR_ERR(trans);
error("failed to start transaction: %d", ret);
return ret;
}
ret = delete_corrupted_dir_item(trans, root, di_key, namebuf, name_len);
if (ret < 0) {
btrfs_abort_transaction(trans, ret);
} else {
ret = btrfs_commit_transaction(trans, root);
if (ret < 0)
error("failed to commit transaction: %d", ret);
}
return ret;
}
/* /*
* Call repair_inode_item_missing and repair_ternary_lowmem to repair * Call repair_inode_item_missing and repair_ternary_lowmem to repair
* *
* Returns error after repair * Returns error after repair
*/ */
static int repair_dir_item(struct btrfs_root *root, u64 dirid, u64 ino, static int repair_dir_item(struct btrfs_root *root, struct btrfs_key *di_key,
u64 index, u8 filetype, char *namebuf, u32 name_len, u64 ino, u64 index, u8 filetype, char *namebuf,
int err) u32 name_len, int err)
{ {
u64 dirid = di_key->objectid;
int ret; int ret;
if (err & (DIR_ITEM_HASH_MISMATCH)) {
ret = lowmem_delete_corrupted_dir_item(root, di_key, namebuf,
name_len);
if (!ret)
err &= ~(DIR_ITEM_HASH_MISMATCH);
}
if (err & INODE_ITEM_MISSING) { if (err & INODE_ITEM_MISSING) {
ret = repair_inode_item_missing(root, ino, filetype); ret = repair_inode_item_missing(root, ino, filetype);
if (!ret) if (!ret)
@ -1631,11 +1667,12 @@ begin:
if (di_key->type == BTRFS_DIR_ITEM_KEY && if (di_key->type == BTRFS_DIR_ITEM_KEY &&
di_key->offset != btrfs_name_hash(namebuf, len)) { di_key->offset != btrfs_name_hash(namebuf, len)) {
err |= -EIO;
error("root %llu DIR_ITEM[%llu %llu] name %s namelen %u filetype %u mismatch with its hash, wanted %llu have %llu", error("root %llu DIR_ITEM[%llu %llu] name %s namelen %u filetype %u mismatch with its hash, wanted %llu have %llu",
root->objectid, di_key->objectid, di_key->offset, root->objectid, di_key->objectid, di_key->offset,
namebuf, len, filetype, di_key->offset, namebuf, len, filetype, di_key->offset,
btrfs_name_hash(namebuf, len)); btrfs_name_hash(namebuf, len));
tmp_err |= DIR_ITEM_HASH_MISMATCH;
goto next;
} }
btrfs_dir_item_key_to_cpu(node, di, &location); btrfs_dir_item_key_to_cpu(node, di, &location);
@ -1683,7 +1720,7 @@ begin:
next: next:
if (tmp_err && repair) { if (tmp_err && repair) {
ret = repair_dir_item(root, di_key->objectid, ret = repair_dir_item(root, di_key,
location.objectid, index, location.objectid, index,
imode_to_type(mode), namebuf, imode_to_type(mode), namebuf,
name_len, tmp_err); name_len, tmp_err);

View File

@ -45,6 +45,7 @@
#define BG_ACCOUNTING_ERROR (1<<21) /* Block group accounting error */ #define BG_ACCOUNTING_ERROR (1<<21) /* Block group accounting error */
#define FATAL_ERROR (1<<22) /* Fatal bit for errno */ #define FATAL_ERROR (1<<22) /* Fatal bit for errno */
#define INODE_FLAGS_ERROR (1<<23) /* Invalid inode flags */ #define INODE_FLAGS_ERROR (1<<23) /* Invalid inode flags */
#define DIR_ITEM_HASH_MISMATCH (1<<24) /* Dir item hash mismatch */
/* /*
* Error bit for low memory mode check. * Error bit for low memory mode check.

View File

@ -2722,6 +2722,10 @@ int btrfs_insert_xattr_item(struct btrfs_trans_handle *trans,
struct btrfs_root *root, const char *name, struct btrfs_root *root, const char *name,
u16 name_len, const void *data, u16 data_len, u16 name_len, const void *data, u16 data_len,
u64 dir); u64 dir);
struct btrfs_dir_item *btrfs_match_dir_item_name(struct btrfs_root *root,
struct btrfs_path *path,
const char *name, int name_len);
/* inode-map.c */ /* inode-map.c */
int btrfs_find_free_objectid(struct btrfs_trans_handle *trans, int btrfs_find_free_objectid(struct btrfs_trans_handle *trans,
struct btrfs_root *fs_root, struct btrfs_root *fs_root,

View File

@ -22,10 +22,6 @@
#include "hash.h" #include "hash.h"
#include "transaction.h" #include "transaction.h"
static struct btrfs_dir_item *btrfs_match_dir_item_name(struct btrfs_root *root,
struct btrfs_path *path,
const char *name, int name_len);
static struct btrfs_dir_item *insert_with_overflow(struct btrfs_trans_handle static struct btrfs_dir_item *insert_with_overflow(struct btrfs_trans_handle
*trans, *trans,
struct btrfs_root *root, struct btrfs_root *root,
@ -323,7 +319,7 @@ static int verify_dir_item(struct btrfs_root *root,
return 0; return 0;
} }
static struct btrfs_dir_item *btrfs_match_dir_item_name(struct btrfs_root *root, struct btrfs_dir_item *btrfs_match_dir_item_name(struct btrfs_root *root,
struct btrfs_path *path, struct btrfs_path *path,
const char *name, int name_len) const char *name, int name_len)
{ {