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:
parent
1bff5b8261
commit
38aff3d1ed
|
@ -744,3 +744,54 @@ void cleanup_excluded_extents(struct btrfs_fs_info *fs_info)
|
|||
}
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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 exclude_metadata_blocks(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
|
||||
|
|
|
@ -1473,17 +1473,53 @@ out:
|
|||
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
|
||||
*
|
||||
* Returns error after repair
|
||||
*/
|
||||
static int repair_dir_item(struct btrfs_root *root, u64 dirid, u64 ino,
|
||||
u64 index, u8 filetype, char *namebuf, u32 name_len,
|
||||
int err)
|
||||
static int repair_dir_item(struct btrfs_root *root, struct btrfs_key *di_key,
|
||||
u64 ino, u64 index, u8 filetype, char *namebuf,
|
||||
u32 name_len, int err)
|
||||
{
|
||||
u64 dirid = di_key->objectid;
|
||||
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) {
|
||||
ret = repair_inode_item_missing(root, ino, filetype);
|
||||
if (!ret)
|
||||
|
@ -1631,11 +1667,12 @@ begin:
|
|||
|
||||
if (di_key->type == BTRFS_DIR_ITEM_KEY &&
|
||||
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",
|
||||
root->objectid, di_key->objectid, di_key->offset,
|
||||
namebuf, len, filetype, di_key->offset,
|
||||
btrfs_name_hash(namebuf, len));
|
||||
tmp_err |= DIR_ITEM_HASH_MISMATCH;
|
||||
goto next;
|
||||
}
|
||||
|
||||
btrfs_dir_item_key_to_cpu(node, di, &location);
|
||||
|
@ -1683,7 +1720,7 @@ begin:
|
|||
next:
|
||||
|
||||
if (tmp_err && repair) {
|
||||
ret = repair_dir_item(root, di_key->objectid,
|
||||
ret = repair_dir_item(root, di_key,
|
||||
location.objectid, index,
|
||||
imode_to_type(mode), namebuf,
|
||||
name_len, tmp_err);
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
#define BG_ACCOUNTING_ERROR (1<<21) /* Block group accounting error */
|
||||
#define FATAL_ERROR (1<<22) /* Fatal bit for errno */
|
||||
#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.
|
||||
|
|
4
ctree.h
4
ctree.h
|
@ -2722,6 +2722,10 @@ int btrfs_insert_xattr_item(struct btrfs_trans_handle *trans,
|
|||
struct btrfs_root *root, const char *name,
|
||||
u16 name_len, const void *data, u16 data_len,
|
||||
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 */
|
||||
int btrfs_find_free_objectid(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *fs_root,
|
||||
|
|
|
@ -22,10 +22,6 @@
|
|||
#include "hash.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
|
||||
*trans,
|
||||
struct btrfs_root *root,
|
||||
|
@ -323,7 +319,7 @@ static int verify_dir_item(struct btrfs_root *root,
|
|||
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,
|
||||
const char *name, int name_len)
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue