#include "repair.h"
#include "disk-io.h"
#include "print-tree.h"
+#include "task-utils.h"
#include "transaction.h"
#include "utils.h"
#include "commands.h"
#include "free-space-cache.h"
+#include "free-space-tree.h"
#include "btrfsck.h"
#include "qgroup-verify.h"
#include "rbtree-utils.h"
#include "backref.h"
#include "ulist.h"
+enum task_position {
+ TASK_EXTENTS,
+ TASK_FREE_SPACE,
+ TASK_FS_ROOTS,
+ TASK_NOTHING, /* have to be the last element */
+};
+
+struct task_ctx {
+ int progress_enabled;
+ enum task_position tp;
+
+ struct task_info *info;
+};
+
static u64 bytes_used = 0;
static u64 total_csum_bytes = 0;
static u64 total_btree_bytes = 0;
static int no_holes = 0;
static int init_extent_tree = 0;
static int check_data_csum = 0;
+static struct btrfs_fs_info *global_info;
+static struct task_ctx ctx = { 0 };
+
+static void *print_status_check(void *p)
+{
+ struct task_ctx *priv = p;
+ const char work_indicator[] = { '.', 'o', 'O', 'o' };
+ uint32_t count = 0;
+ static char *task_position_string[] = {
+ "checking extents",
+ "checking free space cache",
+ "checking fs roots",
+ };
+
+ task_period_start(priv->info, 1000 /* 1s */);
+
+ if (priv->tp == TASK_NOTHING)
+ return NULL;
+
+ while (1) {
+ printf("%s [%c]\r", task_position_string[priv->tp],
+ work_indicator[count % 4]);
+ count++;
+ fflush(stdout);
+ task_period_wait(priv->info);
+ }
+ return NULL;
+}
+
+static int print_status_return(void *p)
+{
+ printf("\n");
+ fflush(stdout);
+
+ return 0;
+}
struct extent_backref {
struct list_head list;
unsigned int is_root:1;
unsigned int metadata:1;
unsigned int bad_full_backref:1;
+ unsigned int crossing_stripes:1;
+ unsigned int wrong_chunk_type:1;
};
struct inode_backref {
return hole->start;
}
-int compare_hole(struct rb_node *node1, struct rb_node *node2)
+static int compare_hole(struct rb_node *node1, struct rb_node *node2)
{
struct file_extent_hole *hole1;
struct file_extent_hole *hole2;
{
struct file_extent_hole *hole;
struct file_extent_hole tmp;
- struct file_extent_hole prev;
- struct file_extent_hole next;
+ u64 prev_start = 0;
+ u64 prev_len = 0;
+ u64 next_start = 0;
+ u64 next_len = 0;
struct rb_node *node;
int have_prev = 0;
int have_next = 0;
* split(s) if they exists.
*/
if (start > hole->start) {
- prev.start = hole->start;
- prev.len = start - hole->start;
+ prev_start = hole->start;
+ prev_len = start - hole->start;
have_prev = 1;
}
if (hole->start + hole->len > start + len) {
- next.start = start + len;
- next.len = hole->start + hole->len - start - len;
+ next_start = start + len;
+ next_len = hole->start + hole->len - start - len;
have_next = 1;
}
rb_erase(node, holes);
free(hole);
if (have_prev) {
- ret = add_file_extent_hole(holes, prev.start, prev.len);
+ ret = add_file_extent_hole(holes, prev_start, prev_len);
if (ret < 0)
return ret;
}
if (have_next) {
- ret = add_file_extent_hole(holes, next.start, next.len);
+ ret = add_file_extent_hole(holes, next_start, next_len);
if (ret < 0)
return ret;
}
struct inode_record *rec;
struct inode_backref *backref;
struct inode_backref *orig;
+ struct inode_backref *tmp;
struct orphan_data_extent *src_orphan;
struct orphan_data_extent *dst_orphan;
size_t size;
+ int ret;
rec = malloc(sizeof(*rec));
+ if (!rec)
+ return ERR_PTR(-ENOMEM);
memcpy(rec, orig_rec, sizeof(*rec));
rec->refs = 1;
INIT_LIST_HEAD(&rec->backrefs);
INIT_LIST_HEAD(&rec->orphan_extents);
+ rec->holes = RB_ROOT;
list_for_each_entry(orig, &orig_rec->backrefs, list) {
size = sizeof(*orig) + orig->namelen + 1;
backref = malloc(size);
+ if (!backref) {
+ ret = -ENOMEM;
+ goto cleanup;
+ }
memcpy(backref, orig, size);
list_add_tail(&backref->list, &rec->backrefs);
}
list_for_each_entry(src_orphan, &orig_rec->orphan_extents, list) {
dst_orphan = malloc(sizeof(*dst_orphan));
- /* TODO: Fix all the HELL of un-catched -ENOMEM case */
- BUG_ON(!dst_orphan);
+ if (!dst_orphan) {
+ ret = -ENOMEM;
+ goto cleanup;
+ }
memcpy(dst_orphan, src_orphan, sizeof(*src_orphan));
list_add_tail(&dst_orphan->list, &rec->orphan_extents);
}
+ ret = copy_file_extent_holes(&rec->holes, &orig_rec->holes);
+ BUG_ON(ret < 0);
+
return rec;
+
+cleanup:
+ if (!list_empty(&rec->backrefs))
+ list_for_each_entry_safe(orig, tmp, &rec->backrefs, list) {
+ list_del(&orig->list);
+ free(orig);
+ }
+
+ if (!list_empty(&rec->orphan_extents))
+ list_for_each_entry_safe(orig, tmp, &rec->orphan_extents, list) {
+ list_del(&orig->list);
+ free(orig);
+ }
+
+ free(rec);
+
+ return ERR_PTR(ret);
}
static void print_orphan_data_extents(struct list_head *orphan_extents,
if (errors & I_ERR_FILE_EXTENT_DISCOUNT) {
struct file_extent_hole *hole;
struct rb_node *node;
+ int found = 0;
node = rb_first(&rec->holes);
fprintf(stderr, "Found file extent holes:\n");
while (node) {
+ found = 1;
hole = rb_entry(node, struct file_extent_hole, node);
- fprintf(stderr, "\tstart: %llu, len:%llu\n",
+ fprintf(stderr, "\tstart: %llu, len: %llu\n",
hole->start, hole->len);
node = rb_next(node);
}
+ if (!found)
+ fprintf(stderr, "\tstart: 0, len: %llu\n",
+ round_up(rec->isize, root->sectorsize));
}
}
rec = node->data;
if (mod && rec->refs > 1) {
node->data = clone_inode_rec(rec);
+ if (IS_ERR(node->data))
+ return node->data;
rec->refs--;
rec = node->data;
}
} else if (mod) {
rec = calloc(1, sizeof(*rec));
+ if (!rec)
+ return ERR_PTR(-ENOMEM);
rec->ino = ino;
rec->extent_start = (u64)-1;
rec->refs = 1;
rec->holes = RB_ROOT;
node = malloc(sizeof(*node));
+ if (!node) {
+ free(rec);
+ return ERR_PTR(-ENOMEM);
+ }
node->cache.start = ino;
node->cache.size = 1;
node->data = rec;
rec->found_link = 1;
ret = insert_cache_extent(inode_cache, &node->cache);
- BUG_ON(ret);
+ if (ret)
+ return ERR_PTR(-EEXIST);
}
return rec;
}
if (backref->found_dir_item && backref->found_dir_index) {
if (backref->filetype != filetype)
backref->errors |= REF_ERR_FILETYPE_UNMATCH;
- if (!backref->errors && backref->found_inode_ref) {
+ if (!backref->errors && backref->found_inode_ref &&
+ rec->nlink == rec->found_link) {
list_del(&backref->list);
free(backref);
}
}
backref = malloc(sizeof(*backref) + namelen + 1);
+ if (!backref)
+ return NULL;
memset(backref, 0, sizeof(*backref));
backref->dir = dir;
backref->namelen = namelen;
struct inode_backref *backref;
rec = get_inode_rec(inode_cache, ino, 1);
+ BUG_ON(IS_ERR(rec));
backref = get_inode_backref(rec, name, namelen, dir);
+ BUG_ON(!backref);
if (errors)
backref->errors |= errors;
if (itemtype == BTRFS_DIR_INDEX_KEY) {
ins = node;
} else {
ins = malloc(sizeof(*ins));
+ BUG_ON(!ins);
ins->cache.start = node->cache.start;
ins->cache.size = node->cache.size;
ins->data = rec;
ret = insert_cache_extent(dst, &ins->cache);
if (ret == -EEXIST) {
conflict = get_inode_rec(dst, rec->ino, 1);
+ BUG_ON(IS_ERR(conflict));
merge_inode_recs(rec, conflict, dst);
if (rec->checked) {
conflict->checked = 1;
maybe_free_inode_rec(dst, dst_node->current);
}
dst_node->current = get_inode_rec(dst, current_ino, 1);
+ BUG_ON(IS_ERR(dst_node->current));
}
return 0;
}
struct shared_node *node;
node = calloc(1, sizeof(*node));
+ if (!node)
+ return -ENOMEM;
node->cache.start = bytenr;
node->cache.size = 1;
cache_tree_init(&node->root_cache);
node->refs = refs;
ret = insert_cache_extent(shared, &node->cache);
- BUG_ON(ret);
- return 0;
+
+ return ret;
}
static int enter_shared_node(struct btrfs_root *root, u64 bytenr, u32 refs,
{
struct shared_node *node;
struct shared_node *dest;
+ int ret;
if (level == wc->active_node)
return 0;
BUG_ON(wc->active_node <= level);
node = find_shared_node(&wc->shared, bytenr);
if (!node) {
- add_shared_node(&wc->shared, bytenr, refs);
+ ret = add_shared_node(&wc->shared, bytenr, refs);
+ BUG_ON(ret);
node = find_shared_node(&wc->shared, bytenr);
wc->nodes[level] = node;
wc->active_node = level;
}
active_node->current = get_inode_rec(inode_cache,
key.objectid, 1);
+ BUG_ON(IS_ERR(active_node->current));
}
switch (key.type) {
case BTRFS_DIR_ITEM_KEY:
return ret;
}
+static int repair_inode_nbytes(struct btrfs_trans_handle *trans,
+ struct btrfs_root *root,
+ struct btrfs_path *path,
+ struct inode_record *rec)
+{
+ struct btrfs_inode_item *ei;
+ struct btrfs_key key;
+ int ret = 0;
+
+ key.objectid = rec->ino;
+ key.type = BTRFS_INODE_ITEM_KEY;
+ key.offset = 0;
+
+ ret = btrfs_search_slot(trans, root, &key, path, 0, 1);
+ if (ret) {
+ if (ret > 0)
+ ret = -ENOENT;
+ goto out;
+ }
+
+ /* Since ret == 0, no need to check anything */
+ ei = btrfs_item_ptr(path->nodes[0], path->slots[0],
+ struct btrfs_inode_item);
+ btrfs_set_inode_nbytes(path->nodes[0], ei, rec->found_size);
+ btrfs_mark_buffer_dirty(path->nodes[0]);
+ rec->errors &= ~I_ERR_FILE_NBYTES_WRONG;
+ printf("reset nbytes for ino %llu root %llu\n",
+ rec->ino, root->root_key.objectid);
+out:
+ btrfs_release_path(path);
+ return ret;
+}
+
static int add_missing_dir_index(struct btrfs_root *root,
struct cache_tree *inode_cache,
struct inode_record *rec,
backref->found_dir_index = 1;
dir_rec = get_inode_rec(inode_cache, backref->dir, 0);
+ BUG_ON(IS_ERR(dir_rec));
if (!dir_rec)
return 0;
dir_rec->found_size += backref->namelen;
list_for_each_entry(backref, &rec->backrefs, list) {
ret = btrfs_add_link(trans, root, rec->ino, backref->dir,
backref->name, backref->namelen,
- backref->ref_type, &backref->index, 1);
+ backref->filetype, &backref->index, 1);
if (ret < 0)
goto out;
}
BTRFS_FIRST_FREE_OBJECTID, &lost_found_ino,
mode);
if (ret < 0) {
- fprintf(stderr, "Failed to create '%s' dir: %s",
+ fprintf(stderr, "Failed to create '%s' dir: %s\n",
dir_name, strerror(-ret));
goto out;
}
ret = btrfs_add_link(trans, root, rec->ino, lost_found_ino,
namebuf, namelen, type, NULL, 1);
- if (ret == -EEXIST) {
+ /*
+ * Add ".INO" suffix several times to handle case where
+ * "FILENAME.INO" is already taken by another file.
+ */
+ while (ret == -EEXIST) {
/*
* Conflicting file name, add ".INO" as suffix * +1 for '.'
*/
}
if (ret < 0) {
fprintf(stderr,
- "Failed to link the inode %llu to %s dir: %s",
+ "Failed to link the inode %llu to %s dir: %s\n",
rec->ino, dir_name, strerror(-ret));
goto out;
}
printf("Moving file '%.*s' to '%s' dir since it has no valid backref\n",
namelen, namebuf, dir_name);
}
- rec->errors &= ~I_ERR_LINK_COUNT_WRONG;
printf("Fixed the nlink of inode %llu\n", rec->ino);
out:
+ /*
+ * Clear the flag anyway, or we will loop forever for the same inode
+ * as it will not be removed from the bad inode list and the dead loop
+ * happens.
+ */
+ rec->errors &= ~I_ERR_LINK_COUNT_WRONG;
btrfs_release_path(path);
return ret;
}
{
struct rb_node *node;
struct file_extent_hole *hole;
+ int found = 0;
int ret = 0;
node = rb_first(&rec->holes);
while (node) {
+ found = 1;
hole = rb_entry(node, struct file_extent_hole, node);
ret = btrfs_punch_hole(trans, root, rec->ino,
hole->start, hole->len);
rec->errors &= ~I_ERR_FILE_EXTENT_DISCOUNT;
node = rb_first(&rec->holes);
}
+ /* special case for a file losing all its file extent */
+ if (!found) {
+ ret = btrfs_punch_hole(trans, root, rec->ino, 0,
+ round_up(rec->isize, root->sectorsize));
+ if (ret < 0)
+ goto out;
+ }
printf("Fixed discount file extents for inode: %llu in root: %llu\n",
rec->ino, root->objectid);
out:
I_ERR_LINK_COUNT_WRONG |
I_ERR_NO_INODE_ITEM |
I_ERR_FILE_EXTENT_ORPHAN |
- I_ERR_FILE_EXTENT_DISCOUNT)))
+ I_ERR_FILE_EXTENT_DISCOUNT|
+ I_ERR_FILE_NBYTES_WRONG)))
return rec->errors;
path = btrfs_alloc_path();
ret = repair_inode_orphan_item(trans, root, path, rec);
if (!ret && rec->errors & I_ERR_LINK_COUNT_WRONG)
ret = repair_inode_nlinks(trans, root, path, rec);
+ if (!ret && rec->errors & I_ERR_FILE_NBYTES_WRONG)
+ ret = repair_inode_nbytes(trans, root, path, rec);
btrfs_commit_transaction(trans, root);
btrfs_free_path(path);
return ret;
return err;
rec = get_inode_rec(inode_cache, root_dirid, 0);
+ BUG_ON(IS_ERR(rec));
if (rec) {
ret = check_root_dir(rec);
if (ret) {
rec = container_of(cache, struct root_record, cache);
} else {
rec = calloc(1, sizeof(*rec));
+ if (!rec)
+ return ERR_PTR(-ENOMEM);
rec->objectid = objectid;
INIT_LIST_HEAD(&rec->backrefs);
rec->cache.start = objectid;
rec->cache.size = 1;
ret = insert_cache_extent(root_cache, &rec->cache);
- BUG_ON(ret);
+ if (ret)
+ return ERR_PTR(-EEXIST);
}
return rec;
}
return backref;
}
- backref = malloc(sizeof(*backref) + namelen + 1);
- memset(backref, 0, sizeof(*backref));
+ backref = calloc(1, sizeof(*backref) + namelen + 1);
+ if (!backref)
+ return NULL;
backref->ref_root = ref_root;
backref->dir = dir;
backref->index = index;
struct root_backref *backref;
rec = get_root_rec(root_cache, root_id);
+ BUG_ON(IS_ERR(rec));
backref = get_root_backref(rec, ref_root, dir, index, name, namelen);
+ BUG_ON(!backref);
backref->errors |= errors;
int errors = 0;
rec = get_root_rec(root_cache, BTRFS_FS_TREE_OBJECTID);
+ BUG_ON(IS_ERR(rec));
rec->found_ref = 1;
/* fixme: this can not detect circular references */
ref_root = get_root_rec(root_cache,
backref->ref_root);
+ BUG_ON(IS_ERR(ref_root));
if (ref_root->found_ref > 0)
continue;
if (root->root_key.objectid != BTRFS_TREE_RELOC_OBJECTID) {
rec = get_root_rec(root_cache, root->root_key.objectid);
+ BUG_ON(IS_ERR(rec));
if (btrfs_root_refs(root_item) > 0)
rec->found_root_item = 1;
}
inode = get_inode_rec(&root_node.inode_cache, orphan->objectid,
1);
+ BUG_ON(IS_ERR(inode));
inode->errors |= I_ERR_FILE_EXTENT_ORPHAN;
list_move(&orphan->list, &inode->orphan_extents);
}
int ret;
int err = 0;
+ if (ctx.progress_enabled) {
+ ctx.tp = TASK_FS_ROOTS;
+ task_start(ctx.info);
+ }
+
/*
* Just in case we made any changes to the extent tree that weren't
* reflected into the free space cache yet.
if (!cache_tree_empty(&wc.shared))
fprintf(stderr, "warning line %d\n", __LINE__);
+ task_stop(ctx.info);
+
return err;
}
if (rec->content_checked && rec->owner_ref_checked &&
rec->extent_item_refs == rec->refs && rec->refs > 0 &&
rec->num_duplicates == 0 && !all_backpointers_checked(rec, 0) &&
- !rec->bad_full_backref) {
+ !rec->bad_full_backref && !rec->crossing_stripes &&
+ !rec->wrong_chunk_type) {
remove_cache_extent(extent_cache, &rec->cache);
free_all_extent_backrefs(rec);
list_del_init(&rec->list);
u64 parent, u64 root)
{
struct tree_backref *ref = malloc(sizeof(*ref));
+
+ if (!ref)
+ return NULL;
memset(&ref->node, 0, sizeof(ref->node));
if (parent > 0) {
ref->parent = parent;
u64 max_size)
{
struct data_backref *ref = malloc(sizeof(*ref));
+
+ if (!ref)
+ return NULL;
memset(&ref->node, 0, sizeof(ref->node));
ref->node.is_data = 1;
return ref;
}
+/* Check if the type of extent matches with its chunk */
+static void check_extent_type(struct extent_record *rec)
+{
+ struct btrfs_block_group_cache *bg_cache;
+
+ bg_cache = btrfs_lookup_first_block_group(global_info, rec->start);
+ if (!bg_cache)
+ return;
+
+ /* data extent, check chunk directly*/
+ if (!rec->metadata) {
+ if (!(bg_cache->flags & BTRFS_BLOCK_GROUP_DATA))
+ rec->wrong_chunk_type = 1;
+ return;
+ }
+
+ /* metadata extent, check the obvious case first */
+ if (!(bg_cache->flags & (BTRFS_BLOCK_GROUP_SYSTEM |
+ BTRFS_BLOCK_GROUP_METADATA))) {
+ rec->wrong_chunk_type = 1;
+ return;
+ }
+
+ /*
+ * Check SYSTEM extent, as it's also marked as metadata, we can only
+ * make sure it's a SYSTEM extent by its backref
+ */
+ if (!list_empty(&rec->backrefs)) {
+ struct extent_backref *node;
+ struct tree_backref *tback;
+ u64 bg_type;
+
+ node = list_entry(rec->backrefs.next, struct extent_backref,
+ list);
+ if (node->is_data) {
+ /* tree block shouldn't have data backref */
+ rec->wrong_chunk_type = 1;
+ return;
+ }
+ tback = container_of(node, struct tree_backref, node);
+
+ if (tback->root == BTRFS_CHUNK_TREE_OBJECTID)
+ bg_type = BTRFS_BLOCK_GROUP_SYSTEM;
+ else
+ bg_type = BTRFS_BLOCK_GROUP_METADATA;
+ if (!(bg_cache->flags & bg_type))
+ rec->wrong_chunk_type = 1;
+ }
+}
+
static int add_extent_rec(struct cache_tree *extent_cache,
struct btrfs_key *parent_key, u64 parent_gen,
u64 start, u64 nr, u64 extent_item_refs,
if (rec->max_size < max_size)
rec->max_size = max_size;
+ /*
+ * A metadata extent can't cross stripe_len boundary, otherwise
+ * kernel scrub won't be able to handle it.
+ * As now stripe_len is fixed to BTRFS_STRIPE_LEN, just check
+ * it.
+ */
+ if (metadata && check_crossing_stripes(rec->start,
+ rec->max_size))
+ rec->crossing_stripes = 1;
+ check_extent_type(rec);
maybe_free_extent_rec(extent_cache, rec);
return ret;
}
rec = malloc(sizeof(*rec));
+ if (!rec)
+ return -ENOMEM;
rec->start = start;
rec->max_size = max_size;
rec->nr = max(nr, max_size);
rec->metadata = metadata;
rec->flag_block_full_backref = -1;
rec->bad_full_backref = 0;
+ rec->crossing_stripes = 0;
+ rec->wrong_chunk_type = 0;
INIT_LIST_HEAD(&rec->backrefs);
INIT_LIST_HEAD(&rec->dups);
INIT_LIST_HEAD(&rec->list);
rec->content_checked = 1;
rec->owner_ref_checked = 1;
}
+
+ if (metadata)
+ if (check_crossing_stripes(rec->start, rec->max_size))
+ rec->crossing_stripes = 1;
+ check_extent_type(rec);
return ret;
}
}
back = find_tree_backref(rec, parent, root);
- if (!back)
+ if (!back) {
back = alloc_tree_backref(rec, parent, root);
+ BUG_ON(!back);
+ }
if (found_ref) {
if (back->node.found_ref) {
}
back->node.found_extent_tree = 1;
}
+ check_extent_type(rec);
maybe_free_extent_rec(extent_cache, rec);
return 0;
}
*/
back = find_data_backref(rec, parent, root, owner, offset, found_ref,
bytenr, max_size);
- if (!back)
+ if (!back) {
back = alloc_data_backref(rec, parent, root, owner, offset,
max_size);
+ BUG_ON(!back);
+ }
if (found_ref) {
BUG_ON(num_refs != 1);
ptr = btrfs_item_ptr(leaf, slot, struct btrfs_chunk);
num_stripes = btrfs_chunk_num_stripes(leaf, ptr);
- rec = malloc(btrfs_chunk_record_size(num_stripes));
+ rec = calloc(1, btrfs_chunk_record_size(num_stripes));
if (!rec) {
fprintf(stderr, "memory allocation failed\n");
exit(-1);
}
- memset(rec, 0, btrfs_chunk_record_size(num_stripes));
-
INIT_LIST_HEAD(&rec->list);
INIT_LIST_HEAD(&rec->dextents);
rec->bg_rec = NULL;
struct btrfs_block_group_item *ptr;
struct block_group_record *rec;
- rec = malloc(sizeof(*rec));
+ rec = calloc(1, sizeof(*rec));
if (!rec) {
fprintf(stderr, "memory allocation failed\n");
exit(-1);
}
- memset(rec, 0, sizeof(*rec));
rec->cache.start = key->objectid;
rec->cache.size = key->offset;
struct device_extent_record *rec;
struct btrfs_dev_extent *ptr;
- rec = malloc(sizeof(*rec));
+ rec = calloc(1, sizeof(*rec));
if (!rec) {
fprintf(stderr, "memory allocation failed\n");
exit(-1);
}
- memset(rec, 0, sizeof(*rec));
rec->cache.objectid = key->objectid;
rec->cache.start = key->offset;
ei = btrfs_item_ptr(eb, slot, struct btrfs_extent_item);
refs = btrfs_extent_refs(eb, ei);
+ if (btrfs_extent_flags(eb, ei) & BTRFS_EXTENT_FLAG_TREE_BLOCK)
+ metadata = 1;
+ else
+ metadata = 0;
add_extent_rec(extent_cache, NULL, 0, key.objectid, num_bytes,
refs, 0, 0, 0, metadata, 1, num_bytes);
return 0;
}
+ if (ctx.progress_enabled) {
+ ctx.tp = TASK_FREE_SPACE;
+ task_start(ctx.info);
+ }
+
while (1) {
cache = btrfs_lookup_first_block_group(root->fs_info, start);
if (!cache)
btrfs_remove_free_space_cache(cache);
}
- ret = load_free_space_cache(root->fs_info, cache);
- if (!ret)
- continue;
+ if (btrfs_fs_compat_ro(root->fs_info,
+ BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE)) {
+ ret = exclude_super_stripes(root, cache);
+ if (ret) {
+ fprintf(stderr, "could not exclude super stripes: %s\n",
+ strerror(-ret));
+ error++;
+ continue;
+ }
+ ret = load_free_space_tree(root->fs_info, cache);
+ free_excluded_extents(root, cache);
+ if (ret < 0) {
+ fprintf(stderr, "could not load free space tree: %s\n",
+ strerror(-ret));
+ error++;
+ continue;
+ }
+ error += ret;
+ } else {
+ ret = load_free_space_cache(root->fs_info, cache);
+ if (!ret)
+ continue;
+ }
ret = verify_space_cache(root, cache);
if (ret) {
}
}
- return error ? -EINVAL : 0;
-}
-
-static int read_extent_data(struct btrfs_root *root, char *data,
- u64 logical, u64 *len, int mirror)
-{
- u64 offset = 0;
- struct btrfs_multi_bio *multi = NULL;
- struct btrfs_fs_info *info = root->fs_info;
- struct btrfs_device *device;
- int ret = 0;
- u64 max_len = *len;
-
- ret = btrfs_map_block(&info->mapping_tree, READ, logical, len,
- &multi, mirror, NULL);
- if (ret) {
- fprintf(stderr, "Couldn't map the block %llu\n",
- logical + offset);
- goto err;
- }
- device = multi->stripes[0].dev;
-
- if (device->fd == 0)
- goto err;
- if (*len > max_len)
- *len = max_len;
+ task_stop(ctx.info);
- ret = pread64(device->fd, data, *len, multi->stripes[0].physical);
- if (ret != *len)
- ret = -EIO;
- else
- ret = 0;
-err:
- kfree(multi);
- return ret;
+ return error ? -EINVAL : 0;
}
static int check_extent_csums(struct btrfs_root *root, u64 bytenr,
"start %llu len %llu parent %llu root %llu\n",
rec->start, rec->max_size, parent, tback->root);
}
- if (ret)
- goto fail;
fail:
btrfs_release_path(path);
return ret;
err = 1;
cur_err = 1;
}
+ /*
+ * Although it's not a extent ref's problem, we reuse this
+ * routine for error reporting.
+ * No repair function yet.
+ */
+ if (rec->crossing_stripes) {
+ fprintf(stderr,
+ "bad metadata [%llu, %llu) crossing stripe boundary\n",
+ rec->start, rec->start + rec->max_size);
+ err = 1;
+ cur_err = 1;
+ }
+
+ if (rec->wrong_chunk_type) {
+ fprintf(stderr,
+ "bad extent [%llu, %llu), type mismatch with chunk\n",
+ rec->start, rec->start + rec->max_size);
+ err = 1;
+ cur_err = 1;
+ }
remove_cache_extent(extent_cache, cache);
free_all_extent_backrefs(rec);
exit(1);
}
+ if (ctx.progress_enabled) {
+ ctx.tp = TASK_EXTENTS;
+ task_start(ctx.info);
+ }
+
again:
root1 = root->fs_info->tree_root;
level = btrfs_header_level(root1->node);
goto out;
}
- err = check_chunks(&chunk_cache, &block_group_cache,
+ ret = check_chunks(&chunk_cache, &block_group_cache,
&dev_extent_cache, NULL, NULL, NULL, 0);
- if (err) {
- if (err == -EAGAIN)
+ if (ret) {
+ if (ret == -EAGAIN)
goto loop;
- if (!ret)
- ret = err;
+ err = ret;
}
ret = check_extent_refs(root, &extent_cache);
goto out;
}
- err = check_devices(&dev_cache, &dev_extent_cache);
- if (err && !ret)
+ ret = check_devices(&dev_cache, &dev_extent_cache);
+ if (ret && err)
ret = err;
out:
+ task_stop(ctx.info);
if (repair) {
free_corrupt_blocks_tree(root->fs_info->corrupt_blocks);
extent_io_tree_cleanup(&excluded_extents);
return ret;
}
-static int fill_csum_tree(struct btrfs_trans_handle *trans,
- struct btrfs_root *csum_root)
+static int fill_csum_tree_from_one_fs_root(struct btrfs_trans_handle *trans,
+ struct btrfs_root *csum_root,
+ struct btrfs_root *cur_root)
+{
+ struct btrfs_path *path;
+ struct btrfs_key key;
+ struct extent_buffer *node;
+ struct btrfs_file_extent_item *fi;
+ char *buf = NULL;
+ u64 start = 0;
+ u64 len = 0;
+ int slot = 0;
+ int ret = 0;
+
+ path = btrfs_alloc_path();
+ if (!path)
+ return -ENOMEM;
+ buf = malloc(cur_root->fs_info->csum_root->sectorsize);
+ if (!buf) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ key.objectid = 0;
+ key.offset = 0;
+ key.type = 0;
+
+ ret = btrfs_search_slot(NULL, cur_root, &key, path, 0, 0);
+ if (ret < 0)
+ goto out;
+ /* Iterate all regular file extents and fill its csum */
+ while (1) {
+ btrfs_item_key_to_cpu(path->nodes[0], &key, path->slots[0]);
+
+ if (key.type != BTRFS_EXTENT_DATA_KEY)
+ goto next;
+ node = path->nodes[0];
+ slot = path->slots[0];
+ fi = btrfs_item_ptr(node, slot, struct btrfs_file_extent_item);
+ if (btrfs_file_extent_type(node, fi) != BTRFS_FILE_EXTENT_REG)
+ goto next;
+ start = btrfs_file_extent_disk_bytenr(node, fi);
+ len = btrfs_file_extent_disk_num_bytes(node, fi);
+
+ ret = populate_csum(trans, csum_root, buf, start, len);
+ if (ret == -EEXIST)
+ ret = 0;
+ if (ret < 0)
+ goto out;
+next:
+ /*
+ * TODO: if next leaf is corrupted, jump to nearest next valid
+ * leaf.
+ */
+ ret = btrfs_next_item(cur_root, path);
+ if (ret < 0)
+ goto out;
+ if (ret > 0) {
+ ret = 0;
+ goto out;
+ }
+ }
+
+out:
+ btrfs_free_path(path);
+ free(buf);
+ return ret;
+}
+
+static int fill_csum_tree_from_fs(struct btrfs_trans_handle *trans,
+ struct btrfs_root *csum_root)
+{
+ struct btrfs_fs_info *fs_info = csum_root->fs_info;
+ struct btrfs_path *path;
+ struct btrfs_root *tree_root = fs_info->tree_root;
+ struct btrfs_root *cur_root;
+ struct extent_buffer *node;
+ struct btrfs_key key;
+ int slot = 0;
+ int ret = 0;
+
+ path = btrfs_alloc_path();
+ if (!path)
+ return -ENOMEM;
+
+ key.objectid = BTRFS_FS_TREE_OBJECTID;
+ key.offset = 0;
+ key.type = BTRFS_ROOT_ITEM_KEY;
+
+ ret = btrfs_search_slot(NULL, tree_root, &key, path, 0, 0);
+ if (ret < 0)
+ goto out;
+ if (ret > 0) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ while (1) {
+ node = path->nodes[0];
+ slot = path->slots[0];
+ btrfs_item_key_to_cpu(node, &key, slot);
+ if (key.objectid > BTRFS_LAST_FREE_OBJECTID)
+ goto out;
+ if (key.type != BTRFS_ROOT_ITEM_KEY)
+ goto next;
+ if (!is_fstree(key.objectid))
+ goto next;
+ key.offset = (u64)-1;
+
+ cur_root = btrfs_read_fs_root(fs_info, &key);
+ if (IS_ERR(cur_root) || !cur_root) {
+ fprintf(stderr, "Fail to read fs/subvol tree: %lld\n",
+ key.objectid);
+ goto out;
+ }
+ ret = fill_csum_tree_from_one_fs_root(trans, csum_root,
+ cur_root);
+ if (ret < 0)
+ goto out;
+next:
+ ret = btrfs_next_item(tree_root, path);
+ if (ret > 0) {
+ ret = 0;
+ goto out;
+ }
+ if (ret < 0)
+ goto out;
+ }
+
+out:
+ btrfs_free_path(path);
+ return ret;
+}
+
+static int fill_csum_tree_from_extent(struct btrfs_trans_handle *trans,
+ struct btrfs_root *csum_root)
{
struct btrfs_root *extent_root = csum_root->fs_info->extent_root;
struct btrfs_path *path;
return ret;
}
+/*
+ * Recalculate the csum and put it into the csum tree.
+ *
+ * Extent tree init will wipe out all the extent info, so in that case, we
+ * can't depend on extent tree, but use fs tree. If search_fs_tree is set, we
+ * will use fs/subvol trees to init the csum tree.
+ */
+static int fill_csum_tree(struct btrfs_trans_handle *trans,
+ struct btrfs_root *csum_root,
+ int search_fs_tree)
+{
+ if (search_fs_tree)
+ return fill_csum_tree_from_fs(trans, csum_root);
+ else
+ return fill_csum_tree_from_extent(trans, csum_root);
+}
+
struct root_item_info {
/* level of the root */
u8 level;
iref = (struct btrfs_extent_inline_ref *)(ei + 1);
level = found_key.offset;
} else {
- struct btrfs_tree_block_info *info;
+ struct btrfs_tree_block_info *binfo;
- info = (struct btrfs_tree_block_info *)(ei + 1);
- iref = (struct btrfs_extent_inline_ref *)(info + 1);
- level = btrfs_tree_block_level(leaf, info);
+ binfo = (struct btrfs_tree_block_info *)(ei + 1);
+ iref = (struct btrfs_extent_inline_ref *)(binfo + 1);
+ level = btrfs_tree_block_level(leaf, binfo);
}
/*
ret = 0;
out:
free_roots_info_cache();
- if (path)
- btrfs_free_path(path);
+ btrfs_free_path(path);
if (trans)
btrfs_commit_transaction(trans, info->tree_root);
if (ret < 0)
const char * const cmd_check_usage[] = {
"btrfs check [options] <device>",
- "Check an unmounted btrfs filesystem.",
+ "Check structural inegrity of a filesystem (unmounted).",
+ "Check structural inegrity of an unmounted filesystem. Verify internal",
+ "trees' consistency and item connectivity. In the repair mode try to",
+ "fix the problems found.",
+ "WARNING: the repair mode is considered dangerous",
"",
"-s|--super <superblock> use this superblock copy",
"-b|--backup use the backup root copy",
"--repair try to repair the filesystem",
+ "--readonly run in read-only mode (default)",
"--init-csum-tree create a new CRC tree",
"--init-extent-tree create a new extent tree",
"--check-data-csum verify checkums of data blocks",
- "--qgroup-report print a report on qgroup consistency",
- "--subvol-extents <subvolid> print subvolume extents and sharing state",
- "--tree-root <bytenr> use the given bytenr for the tree root",
+ "-Q|--qgroup-report print a report on qgroup consistency",
+ "-E|--subvol-extents <subvolid>",
+ " print subvolume extents and sharing state",
+ "-r|--tree-root <bytenr> use the given bytenr for the tree root",
+ "-c|--chunk-root <bytenr> use the given bytenr for the chunk tree root",
+ "-p|--progress indicate progress",
NULL
};
u64 bytenr = 0;
u64 subvolid = 0;
u64 tree_root_bytenr = 0;
+ u64 chunk_root_bytenr = 0;
char uuidbuf[BTRFS_UUID_UNPARSED_SIZE];
int ret;
u64 num;
while(1) {
int c;
- int option_index = 0;
enum { OPT_REPAIR = 257, OPT_INIT_CSUM, OPT_INIT_EXTENT,
OPT_CHECK_CSUM, OPT_READONLY };
static const struct option long_options[] = {
- { "super", 1, NULL, 's' },
- { "repair", 0, NULL, OPT_REPAIR },
- { "readonly", 0, NULL, OPT_READONLY },
- { "init-csum-tree", 0, NULL, OPT_INIT_CSUM },
- { "init-extent-tree", 0, NULL, OPT_INIT_EXTENT },
- { "check-data-csum", 0, NULL, OPT_CHECK_CSUM },
- { "backup", 0, NULL, 'b' },
- { "subvol-extents", 1, NULL, 'E' },
- { "qgroup-report", 0, NULL, 'Q' },
- { "tree-root", 1, NULL, 'r' },
+ { "super", required_argument, NULL, 's' },
+ { "repair", no_argument, NULL, OPT_REPAIR },
+ { "readonly", no_argument, NULL, OPT_READONLY },
+ { "init-csum-tree", no_argument, NULL, OPT_INIT_CSUM },
+ { "init-extent-tree", no_argument, NULL, OPT_INIT_EXTENT },
+ { "check-data-csum", no_argument, NULL, OPT_CHECK_CSUM },
+ { "backup", no_argument, NULL, 'b' },
+ { "subvol-extents", required_argument, NULL, 'E' },
+ { "qgroup-report", no_argument, NULL, 'Q' },
+ { "tree-root", required_argument, NULL, 'r' },
+ { "chunk-root", required_argument, NULL, 'c' },
+ { "progress", no_argument, NULL, 'p' },
{ NULL, 0, NULL, 0}
};
- c = getopt_long(argc, argv, "as:br:", long_options,
- &option_index);
+ c = getopt_long(argc, argv, "as:br:pc:", long_options, NULL);
if (c < 0)
break;
switch(c) {
case 'r':
tree_root_bytenr = arg_strtou64(optarg);
break;
+ case 'c':
+ chunk_root_bytenr = arg_strtou64(optarg);
+ break;
+ case 'p':
+ ctx.progress_enabled = true;
+ break;
case '?':
case 'h':
usage(cmd_check_usage);
break;
}
}
- argc = argc - optind;
- if (check_argc_exact(argc, 1))
+ if (check_argc_exact(argc - optind, 1))
usage(cmd_check_usage);
+ if (ctx.progress_enabled) {
+ ctx.tp = TASK_NOTHING;
+ ctx.info = task_init(print_status_check, print_status_return, &ctx);
+ }
+
/* This check is the only reason for --readonly to exist */
if (readonly && repair) {
fprintf(stderr, "Repair options are not compatible with --readonly\n");
ctree_flags |= OPEN_CTREE_PARTIAL;
info = open_ctree_fs_info(argv[optind], bytenr, tree_root_bytenr,
- ctree_flags);
+ chunk_root_bytenr, ctree_flags);
if (!info) {
fprintf(stderr, "Couldn't open file system\n");
ret = -EIO;
goto err_out;
}
+ global_info = info;
root = info->fs_root;
/*
goto close_out;
}
- ret = fill_csum_tree(trans, info->csum_root);
+ ret = fill_csum_tree(trans, info->csum_root,
+ init_extent_tree);
if (ret) {
fprintf(stderr, "crc refilling failed\n");
return -EIO;
goto close_out;
}
- fprintf(stderr, "checking extents\n");
+ if (!ctx.progress_enabled)
+ fprintf(stderr, "checking extents\n");
ret = check_chunks_and_extents(root);
if (ret)
fprintf(stderr, "Errors found in extent allocation tree or chunk allocation\n");
goto close_out;
}
- fprintf(stderr, "checking free space cache\n");
+ if (!ctx.progress_enabled) {
+ if (btrfs_fs_compat_ro(info, BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE))
+ fprintf(stderr, "checking free space tree\n");
+ else
+ fprintf(stderr, "checking free space cache\n");
+ }
ret = check_space_cache(root);
if (ret)
goto out;
*/
no_holes = btrfs_fs_incompat(root->fs_info,
BTRFS_FEATURE_INCOMPAT_NO_HOLES);
- fprintf(stderr, "checking fs roots\n");
+ if (!ctx.progress_enabled)
+ fprintf(stderr, "checking fs roots\n");
ret = check_fs_roots(root, &root_cache);
if (ret)
goto out;
printf("file data blocks allocated: %llu\n referenced %llu\n",
(unsigned long long)data_bytes_allocated,
(unsigned long long)data_bytes_referenced);
- printf("%s\n", PACKAGE_STRING);
free_root_recs_tree(&root_cache);
close_out:
close_ctree(root);
err_out:
+ if (ctx.progress_enabled)
+ task_deinit(ctx.info);
+
return ret;
}