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;
|
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 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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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.
|
||||||
|
|
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,
|
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,
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue