// SPDX-License-Identifier: GPL-2.0-only /* * This file is part of UBIFS. * * Copyright (C) 2006-2008 Nokia Corporation * * Authors: Artem Bityutskiy (Битюцкий Артём) * Adrian Hunter
*/
/* * This file implements most of the debugging stuff which is compiled in only * when it is enabled. But some debugging check functions are implemented in * corresponding subsystem, just because they are closely related and utilize * various local functions of those subsystems.
*/
staticconstchar *get_key_hash(int hash)
{ switch (hash) { case UBIFS_KEY_HASH_R5: return"R5"; case UBIFS_KEY_HASH_TEST: return"test"; default: return"unknown/invalid name hash";
}
}
staticconstchar *get_key_type(int type)
{ switch (type) { case UBIFS_INO_KEY: return"inode"; case UBIFS_DENT_KEY: return"direntry"; case UBIFS_XENT_KEY: return"xentry"; case UBIFS_DATA_KEY: return"data"; case UBIFS_TRUN_KEY: return"truncate"; default: return"unknown/invalid key";
}
}
staticconstchar *get_dent_type(int type)
{ switch (type) { case UBIFS_ITYPE_REG: return"file"; case UBIFS_ITYPE_DIR: return"dir"; case UBIFS_ITYPE_LNK: return"symlink"; case UBIFS_ITYPE_BLK: return"blkdev"; case UBIFS_ITYPE_CHR: return"char dev"; case UBIFS_ITYPE_FIFO: return"fifo"; case UBIFS_ITYPE_SOCK: return"socket"; default: return"unknown/invalid type";
}
}
constchar *dbg_snprintf_key(conststruct ubifs_info *c, constunion ubifs_key *key, char *buffer, int len)
{ char *p = buffer; int type = key_type(c, key);
if (c->key_fmt == UBIFS_SIMPLE_KEY_FMT) { switch (type) { case UBIFS_INO_KEY:
len -= snprintf(p, len, "(%lu, %s)",
(unsignedlong)key_inum(c, key),
get_key_type(type)); break; case UBIFS_DENT_KEY: case UBIFS_XENT_KEY:
len -= snprintf(p, len, "(%lu, %s, %#08x)",
(unsignedlong)key_inum(c, key),
get_key_type(type), key_hash(c, key)); break; case UBIFS_DATA_KEY:
len -= snprintf(p, len, "(%lu, %s, %u)",
(unsignedlong)key_inum(c, key),
get_key_type(type), key_block(c, key)); break; case UBIFS_TRUN_KEY:
len -= snprintf(p, len, "(%lu, %s)",
(unsignedlong)key_inum(c, key),
get_key_type(type)); break; default:
len -= snprintf(p, len, "(bad key type: %#08x, %#08x)",
key->u32[0], key->u32[1]);
}
} else
len -= snprintf(p, len, "bad key format %d", c->key_fmt);
ubifs_assert(c, len > 0); return p;
}
constchar *dbg_ntype(int type)
{ switch (type) { case UBIFS_PAD_NODE: return"padding node"; case UBIFS_SB_NODE: return"superblock node"; case UBIFS_MST_NODE: return"master node"; case UBIFS_REF_NODE: return"reference node"; case UBIFS_INO_NODE: return"inode node"; case UBIFS_DENT_NODE: return"direntry node"; case UBIFS_XENT_NODE: return"xentry node"; case UBIFS_DATA_NODE: return"data node"; case UBIFS_TRUN_NODE: return"truncate node"; case UBIFS_IDX_NODE: return"indexing node"; case UBIFS_CS_NODE: return"commit start node"; case UBIFS_ORPH_NODE: return"orphan node"; case UBIFS_AUTH_NODE: return"auth node"; default: return"unknown node";
}
}
staticconstchar *dbg_gtype(int type)
{ switch (type) { case UBIFS_NO_NODE_GROUP: return"no node group"; case UBIFS_IN_NODE_GROUP: return"in node group"; case UBIFS_LAST_OF_NODE_GROUP: return"last of node group"; default: return"unknown";
}
}
constchar *dbg_cstate(int cmt_state)
{ switch (cmt_state) { case COMMIT_RESTING: return"commit resting"; case COMMIT_BACKGROUND: return"background commit requested"; case COMMIT_REQUIRED: return"commit required"; case COMMIT_RUNNING_BACKGROUND: return"BACKGROUND commit running"; case COMMIT_RUNNING_REQUIRED: return"commit running and required"; case COMMIT_BROKEN: return"broken commit"; default: return"unknown commit state";
}
}
constchar *dbg_jhead(int jhead)
{ switch (jhead) { case GCHD: return"0 (GC)"; case BASEHD: return"1 (base)"; case DATAHD: return"2 (data)"; default: return"unknown journal head";
}
}
void ubifs_dump_node(conststruct ubifs_info *c, constvoid *node, int node_len)
{ int i, n, type, safe_len, max_node_len, min_node_len; union ubifs_key key; conststruct ubifs_ch *ch = node; char key_buf[DBG_KEY_BUF_LEN];
/* If the magic is incorrect, just hexdump the first bytes */ if (le32_to_cpu(ch->magic) != UBIFS_NODE_MAGIC) {
pr_err("Not a node, first %zu bytes:", UBIFS_CH_SZ);
print_hex_dump(KERN_ERR, "", DUMP_PREFIX_OFFSET, 32, 1,
(void *)node, UBIFS_CH_SZ, 1); return;
}
/* Skip dumping unknown type node */
type = ch->node_type; if (type < 0 || type >= UBIFS_NODE_TYPES_CNT) {
pr_err("node type %d was not recognized\n", type); return;
}
if (nlen > UBIFS_MAX_NLEN ||
nlen > safe_len - UBIFS_DENT_NODE_SZ)
pr_err("(bad name length, not printing, bad or corrupted node)"); else { for (i = 0; i < nlen && dent->name[i]; i++)
pr_cont("%c", isprint(dent->name[i]) ?
dent->name[i] : '?');
}
pr_cont("\n");
break;
} case UBIFS_DATA_NODE:
{ conststruct ubifs_data_node *dn = node;
if (bi != &c->bi) /* * If we are dumping saved budgeting data, do not print * additional information which is about the current state, not * the old one which corresponded to the saved budgeting data.
*/ goto out_unlock;
if (lp->flags & LPROPS_TAKEN) { if (lp->flags & LPROPS_INDEX)
pr_cont("index, taken"); else
pr_cont("taken");
} else { constchar *s;
if (lp->flags & LPROPS_INDEX) { switch (lp->flags & LPROPS_CAT_MASK) { case LPROPS_DIRTY_IDX:
s = "dirty index"; break; case LPROPS_FRDI_IDX:
s = "freeable index"; break; default:
s = "index";
}
} else { switch (lp->flags & LPROPS_CAT_MASK) { case LPROPS_UNCAT:
s = "not categorized"; break; case LPROPS_DIRTY:
s = "dirty"; break; case LPROPS_FREE:
s = "free"; break; case LPROPS_EMPTY:
s = "empty"; break; case LPROPS_FREEABLE:
s = "freeable"; break; default:
s = NULL; break;
}
}
pr_cont("%s", s);
}
for (rb = rb_first((struct rb_root *)&c->buds); rb; rb = rb_next(rb)) {
bud = rb_entry(rb, struct ubifs_bud, rb); if (bud->lnum == lp->lnum) { int head = 0; for (i = 0; i < c->jhead_cnt; i++) { /* * Note, if we are in R/O mode or in the middle * of mounting/re-mounting, the write-buffers do * not exist.
*/ if (c->jheads &&
lp->lnum == c->jheads[i].wbuf.lnum) {
pr_cont(", jhead %s", dbg_jhead(i));
head = 1;
}
} if (!head)
pr_cont(", bud of jhead %s",
dbg_jhead(bud->jhead));
}
} if (lp->lnum == c->gc_lnum)
pr_cont(", GC LEB");
pr_cont(")\n");
}
/** * ubifs_dump_index - dump the on-flash index. * @c: UBIFS file-system description object * * This function dumps whole UBIFS indexing B-tree, unlike 'ubifs_dump_tnc()' * which dumps only in-memory znodes and does not read znodes which from flash.
*/ void ubifs_dump_index(struct ubifs_info *c)
{
dbg_walk_index(c, NULL, dump_znode, NULL);
}
/** * dbg_save_space_info - save information about flash space. * @c: UBIFS file-system description object * * This function saves information about UBIFS free space, dirty space, etc, in * order to check it later.
*/ void dbg_save_space_info(struct ubifs_info *c)
{ struct ubifs_debug_info *d = c->dbg; int freeable_cnt;
/* * We use a dirty hack here and zero out @c->freeable_cnt, because it * affects the free space calculations, and UBIFS might not know about * all freeable eraseblocks. Indeed, we know about freeable eraseblocks * only when we read their lprops, and we do this only lazily, upon the * need. So at any given point of time @c->freeable_cnt might be not * exactly accurate. * * Just one example about the issue we hit when we did not zero * @c->freeable_cnt. * 1. The file-system is mounted R/O, c->freeable_cnt is %0. We save the * amount of free space in @d->saved_free * 2. We re-mount R/W, which makes UBIFS to read the "lsave" * information from flash, where we cache LEBs from various * categories ('ubifs_remount_fs()' -> 'ubifs_lpt_init()' * -> 'lpt_init_wr()' -> 'read_lsave()' -> 'ubifs_lpt_lookup()' * -> 'ubifs_get_pnode()' -> 'update_cats()' * -> 'ubifs_add_to_cat()'). * 3. Lsave contains a freeable eraseblock, and @c->freeable_cnt * becomes %1. * 4. We calculate the amount of free space when the re-mount is * finished in 'dbg_check_space_info()' and it does not match * @d->saved_free.
*/
freeable_cnt = c->freeable_cnt;
c->freeable_cnt = 0;
d->saved_free = ubifs_get_free_space_nolock(c);
c->freeable_cnt = freeable_cnt;
spin_unlock(&c->space_lock);
}
/** * dbg_check_space_info - check flash space information. * @c: UBIFS file-system description object * * This function compares current flash space information with the information * which was saved when the 'dbg_save_space_info()' function was called. * Returns zero if the information has not changed, and %-EINVAL if it has * changed.
*/ int dbg_check_space_info(struct ubifs_info *c)
{ struct ubifs_debug_info *d = c->dbg; struct ubifs_lp_stats lst; longlong free; int freeable_cnt;
/** * dbg_check_synced_i_size - check synchronized inode size. * @c: UBIFS file-system description object * @inode: inode to check * * If inode is clean, synchronized inode size has to be equivalent to current * inode size. This function has to be called only for locked inodes (@i_mutex * has to be locked). Returns %0 if synchronized inode size if correct, and * %-EINVAL if not.
*/ int dbg_check_synced_i_size(conststruct ubifs_info *c, struct inode *inode)
{ int err = 0; struct ubifs_inode *ui = ubifs_inode(inode);
if (!dbg_is_chk_gen(c)) return 0; if (!S_ISREG(inode->i_mode)) return 0;
mutex_lock(&ui->ui_mutex);
spin_lock(&ui->ui_lock); if (ui->ui_size != ui->synced_i_size && !ui->dirty) {
ubifs_err(c, "ui_size is %lld, synced_i_size is %lld, but inode is clean",
ui->ui_size, ui->synced_i_size);
ubifs_err(c, "i_ino %lu, i_mode %#x, i_size %lld", inode->i_ino,
inode->i_mode, i_size_read(inode));
dump_stack();
err = -EINVAL;
}
spin_unlock(&ui->ui_lock);
mutex_unlock(&ui->ui_mutex); return err;
}
/* * dbg_check_dir - check directory inode size and link count. * @c: UBIFS file-system description object * @dir: the directory to calculate size for * @size: the result is returned here * * This function makes sure that directory size and link count are correct. * Returns zero in case of success and a negative error code in case of * failure. * * Note, it is good idea to make sure the @dir->i_mutex is locked before * calling this function.
*/ int dbg_check_dir(struct ubifs_info *c, conststruct inode *dir)
{ unsignedint nlink = 2; union ubifs_key key; struct ubifs_dent_node *dent, *pdent = NULL; struct fscrypt_name nm = {0};
loff_t size = UBIFS_INO_NODE_SZ;
if (!dbg_is_chk_gen(c)) return 0;
if (!S_ISDIR(dir->i_mode)) return 0;
lowest_dent_key(c, &key, dir->i_ino); while (1) { int err;
dent = ubifs_tnc_next_ent(c, &key, &nm); if (IS_ERR(dent)) {
err = PTR_ERR(dent); if (err == -ENOENT) break;
kfree(pdent); return err;
}
if (i_size_read(dir) != size) {
ubifs_err(c, "directory inode %lu has size %llu, but calculated size is %llu",
dir->i_ino, (unsignedlonglong)i_size_read(dir),
(unsignedlonglong)size);
ubifs_dump_inode(c, dir);
dump_stack(); return -EINVAL;
} if (dir->i_nlink != nlink) {
ubifs_err(c, "directory inode %lu has nlink %u, but calculated nlink is %u",
dir->i_ino, dir->i_nlink, nlink);
ubifs_dump_inode(c, dir);
dump_stack(); return -EINVAL;
}
return 0;
}
/** * dbg_check_key_order - make sure that colliding keys are properly ordered. * @c: UBIFS file-system description object * @zbr1: first zbranch * @zbr2: following zbranch * * In UBIFS indexing B-tree colliding keys has to be sorted in binary order of * names of the direntries/xentries which are referred by the keys. This * function reads direntries/xentries referred by @zbr1 and @zbr2 and makes * sure the name of direntry/xentry referred by @zbr1 is less than * direntry/xentry referred by @zbr2. Returns zero if this is true, %1 if not, * and a negative error code in case of failure.
*/ staticint dbg_check_key_order(struct ubifs_info *c, struct ubifs_zbranch *zbr1, struct ubifs_zbranch *zbr2)
{ int err, nlen1, nlen2, cmp; struct ubifs_dent_node *dent1, *dent2; union ubifs_key key; char key_buf[DBG_KEY_BUF_LEN];
err = ubifs_tnc_read_node(c, zbr1, dent1); if (err) goto out_free;
err = ubifs_validate_entry(c, dent1); if (err) goto out_free;
err = ubifs_tnc_read_node(c, zbr2, dent2); if (err) goto out_free;
err = ubifs_validate_entry(c, dent2); if (err) goto out_free;
/* Make sure node keys are the same as in zbranch */
err = 1;
key_read(c, &dent1->key, &key); if (keys_cmp(c, &zbr1->key, &key)) {
ubifs_err(c, "1st entry at %d:%d has key %s", zbr1->lnum,
zbr1->offs, dbg_snprintf_key(c, &key, key_buf,
DBG_KEY_BUF_LEN));
ubifs_err(c, "but it should have key %s according to tnc",
dbg_snprintf_key(c, &zbr1->key, key_buf,
DBG_KEY_BUF_LEN));
ubifs_dump_node(c, dent1, UBIFS_MAX_DENT_NODE_SZ); goto out_free;
}
key_read(c, &dent2->key, &key); if (keys_cmp(c, &zbr2->key, &key)) {
ubifs_err(c, "2nd entry at %d:%d has key %s", zbr1->lnum,
zbr1->offs, dbg_snprintf_key(c, &key, key_buf,
DBG_KEY_BUF_LEN));
ubifs_err(c, "but it should have key %s according to tnc",
dbg_snprintf_key(c, &zbr2->key, key_buf,
DBG_KEY_BUF_LEN));
ubifs_dump_node(c, dent2, UBIFS_MAX_DENT_NODE_SZ); goto out_free;
}
/** * dbg_check_znode - check if znode is all right. * @c: UBIFS file-system description object * @zbr: zbranch which points to this znode * * This function makes sure that znode referred to by @zbr is all right. * Returns zero if it is, and %-EINVAL if it is not.
*/ staticint dbg_check_znode(struct ubifs_info *c, struct ubifs_zbranch *zbr)
{ struct ubifs_znode *znode = zbr->znode; struct ubifs_znode *zp = znode->parent; int n, err, cmp;
if (zbr->len == 0) /* Only dirty zbranch may have no on-flash nodes */ if (!ubifs_zn_dirty(znode)) {
err = 4; goto out;
}
if (ubifs_zn_dirty(znode)) { /* * If znode is dirty, its parent has to be dirty as well. The * order of the operation is important, so we have to have * memory barriers.
*/
smp_mb(); if (zp && !ubifs_zn_dirty(zp)) { /* * The dirty flag is atomic and is cleared outside the * TNC mutex, so znode's dirty flag may now have * been cleared. The child is always cleared before the * parent, so we just need to check again.
*/
smp_mb(); if (ubifs_zn_dirty(znode)) {
err = 5; goto out;
}
}
}
/* Make sure the 'parent' pointer in our znode is correct */
err = ubifs_search_zbranch(c, zp, &zbr->key, &n); if (!err) { /* This zbranch does not exist in the parent */
err = 7; goto out;
}
if (znode->iip != n) { /* This may happen only in case of collisions */ if (keys_cmp(c, &zp->zbranch[n].key,
&zp->zbranch[znode->iip].key)) {
err = 9; goto out;
}
n = znode->iip;
}
/* * Make sure that the first key in our znode is greater than or * equal to the key in the pointing zbranch.
*/
min = &zbr->key;
cmp = keys_cmp(c, min, &znode->zbranch[0].key); if (cmp == 1) {
err = 10; goto out;
}
if (n + 1 < zp->child_cnt) {
max = &zp->zbranch[n + 1].key;
/* * Make sure the last key in our znode is less or * equivalent than the key in the zbranch which goes * after our pointing zbranch.
*/
cmp = keys_cmp(c, max,
&znode->zbranch[znode->child_cnt - 1].key); if (cmp == -1) {
err = 11; goto out;
}
}
} else { /* This may only be root znode */ if (zbr != &c->zroot) {
err = 12; goto out;
}
}
/* * Make sure that next key is greater or equivalent then the previous * one.
*/ for (n = 1; n < znode->child_cnt; n++) {
cmp = keys_cmp(c, &znode->zbranch[n - 1].key,
&znode->zbranch[n].key); if (cmp > 0) {
err = 13; goto out;
} if (cmp == 0) { /* This can only be keys with colliding hash */ if (!is_hash_key(c, &znode->zbranch[n].key)) {
err = 14; goto out;
}
if (znode->level != 0 || c->replaying) continue;
/* * Colliding keys should follow binary order of * corresponding xentry/dentry names.
*/
err = dbg_check_key_order(c, &znode->zbranch[n - 1],
&znode->zbranch[n]); if (err < 0) return err; if (err) {
err = 15; goto out;
}
}
}
for (n = 0; n < znode->child_cnt; n++) { if (!znode->zbranch[n].znode &&
(znode->zbranch[n].lnum == 0 ||
znode->zbranch[n].len == 0)) {
err = 16; goto out;
}
if (znode->level != 0 && znode->zbranch[n].znode) if (znode->zbranch[n].znode->parent != znode) {
err = 20; goto out;
}
}
return 0;
out:
ubifs_err(c, "failed, error %d", err);
ubifs_msg(c, "dump of the znode");
ubifs_dump_znode(c, znode); if (zp) {
ubifs_msg(c, "dump of the parent znode");
ubifs_dump_znode(c, zp);
}
dump_stack(); return -EINVAL;
}
/** * dbg_check_tnc - check TNC tree. * @c: UBIFS file-system description object * @extra: do extra checks that are possible at start commit * * This function traverses whole TNC tree and checks every znode. Returns zero * if everything is all right and %-EINVAL if something is wrong with TNC.
*/ int dbg_check_tnc(struct ubifs_info *c, int extra)
{ struct ubifs_znode *znode; long clean_cnt = 0, dirty_cnt = 0; int err, last;
if (!dbg_is_chk_index(c)) return 0;
ubifs_assert(c, mutex_is_locked(&c->tnc_mutex)); if (!c->zroot.znode) return 0;
if (!znode->parent)
zbr = &c->zroot; else
zbr = &znode->parent->zbranch[znode->iip];
err = dbg_check_znode(c, zbr); if (err) return err;
if (extra) { if (ubifs_zn_dirty(znode))
dirty_cnt += 1; else
clean_cnt += 1;
}
prev = znode;
znode = ubifs_tnc_postorder_next(c, znode); if (!znode) break;
/* * If the last key of this znode is equivalent to the first key * of the next znode (collision), then check order of the keys.
*/
last = prev->child_cnt - 1; if (prev->level == 0 && znode->level == 0 && !c->replaying &&
!keys_cmp(c, &prev->zbranch[last].key,
&znode->zbranch[0].key)) {
err = dbg_check_key_order(c, &prev->zbranch[last],
&znode->zbranch[0]); if (err < 0) return err; if (err) {
ubifs_msg(c, "first znode");
ubifs_dump_znode(c, prev);
ubifs_msg(c, "second znode");
ubifs_dump_znode(c, znode); return -EINVAL;
}
}
}
/** * dbg_walk_index - walk the on-flash index. * @c: UBIFS file-system description object * @leaf_cb: called for each leaf node * @znode_cb: called for each indexing node * @priv: private data which is passed to callbacks * * This function walks the UBIFS index and calls the @leaf_cb for each leaf * node and @znode_cb for each indexing node. Returns zero in case of success * and a negative error code in case of failure. * * It would be better if this function removed every znode it pulled to into * the TNC, so that the behavior more closely matched the non-debugging * behavior.
*/ int dbg_walk_index(struct ubifs_info *c, dbg_leaf_callback leaf_cb,
dbg_znode_callback znode_cb, void *priv)
{ int err; struct ubifs_zbranch *zbr; struct ubifs_znode *znode, *child;
mutex_lock(&c->tnc_mutex); /* If the root indexing node is not in TNC - pull it */ if (!c->zroot.znode) {
c->zroot.znode = ubifs_load_znode(c, &c->zroot, NULL, 0); if (IS_ERR(c->zroot.znode)) {
err = PTR_ERR(c->zroot.znode);
c->zroot.znode = NULL; goto out_unlock;
}
}
/* * We are going to traverse the indexing tree in the postorder manner. * Go down and find the leftmost indexing node where we are going to * start from.
*/
znode = c->zroot.znode; while (znode->level > 0) {
zbr = &znode->zbranch[0];
child = zbr->znode; if (!child) {
child = ubifs_load_znode(c, zbr, znode, 0); if (IS_ERR(child)) {
err = PTR_ERR(child); goto out_unlock;
}
}
znode = child;
}
/* Iterate over all indexing nodes */ while (1) { int idx;
cond_resched();
if (znode_cb) {
err = znode_cb(c, znode, priv); if (err) {
ubifs_err(c, "znode checking function returned error %d",
err);
ubifs_dump_znode(c, znode); goto out_dump;
}
} if (leaf_cb && znode->level == 0) { for (idx = 0; idx < znode->child_cnt; idx++) {
zbr = &znode->zbranch[idx];
err = leaf_cb(c, zbr, priv); if (err) {
ubifs_err(c, "leaf checking function returned error %d, for leaf at LEB %d:%d",
err, zbr->lnum, zbr->offs); goto out_dump;
}
}
}
if (!znode->parent) break;
idx = znode->iip + 1;
znode = znode->parent; if (idx < znode->child_cnt) { /* Switch to the next index in the parent */
zbr = &znode->zbranch[idx];
child = zbr->znode; if (!child) {
child = ubifs_load_znode(c, zbr, znode, idx); if (IS_ERR(child)) {
err = PTR_ERR(child); goto out_unlock;
}
zbr->znode = child;
}
znode = child;
} else /* * This is the last child, switch to the parent and * continue.
*/ continue;
/* Go to the lowest leftmost znode in the new sub-tree */ while (znode->level > 0) {
zbr = &znode->zbranch[0];
child = zbr->znode; if (!child) {
child = ubifs_load_znode(c, zbr, znode, 0); if (IS_ERR(child)) {
err = PTR_ERR(child); goto out_unlock;
}
zbr->znode = child;
}
znode = child;
}
}
mutex_unlock(&c->tnc_mutex); return 0;
out_dump: if (znode->parent)
zbr = &znode->parent->zbranch[znode->iip]; else
zbr = &c->zroot;
ubifs_msg(c, "dump of znode at LEB %d:%d", zbr->lnum, zbr->offs);
ubifs_dump_znode(c, znode);
out_unlock:
mutex_unlock(&c->tnc_mutex); return err;
}
/** * add_size - add znode size to partially calculated index size. * @c: UBIFS file-system description object * @znode: znode to add size for * @priv: partially calculated index size * * This is a helper function for 'dbg_check_idx_size()' which is called for * every indexing node and adds its size to the 'long long' variable pointed to * by @priv.
*/ staticint add_size(struct ubifs_info *c, struct ubifs_znode *znode, void *priv)
{ longlong *idx_size = priv; int add;
/** * dbg_check_idx_size - check index size. * @c: UBIFS file-system description object * @idx_size: size to check * * This function walks the UBIFS index, calculates its size and checks that the * size is equivalent to @idx_size. Returns zero in case of success and a * negative error code in case of failure.
*/ int dbg_check_idx_size(struct ubifs_info *c, longlong idx_size)
{ int err; longlong calc = 0;
if (!dbg_is_chk_index(c)) return 0;
err = dbg_walk_index(c, NULL, add_size, &calc); if (err) {
ubifs_err(c, "error %d while walking the index", err); goto out_err;
}
if (calc != idx_size) {
ubifs_err(c, "index size check failed: calculated size is %lld, should be %lld",
calc, idx_size);
dump_stack();
err = -EINVAL; goto out_err;
}
return 0;
out_err:
ubifs_destroy_tnc_tree(c); return err;
}
/** * struct fsck_inode - information about an inode used when checking the file-system. * @rb: link in the RB-tree of inodes * @inum: inode number * @mode: inode type, permissions, etc * @nlink: inode link count * @xattr_cnt: count of extended attributes * @references: how many directory/xattr entries refer this inode (calculated * while walking the index) * @calc_cnt: for directory inode count of child directories * @size: inode size (read from on-flash inode) * @xattr_sz: summary size of all extended attributes (read from on-flash * inode) * @calc_sz: for directories calculated directory size * @calc_xcnt: count of extended attributes * @calc_xsz: calculated summary size of all extended attributes * @xattr_nms: sum of lengths of all extended attribute names belonging to this * inode (read from on-flash inode) * @calc_xnms: calculated sum of lengths of all extended attribute names
*/ struct fsck_inode { struct rb_node rb;
ino_t inum;
umode_t mode; unsignedint nlink; unsignedint xattr_cnt; int references; int calc_cnt; longlong size; unsignedint xattr_sz; longlong calc_sz; longlong calc_xcnt; longlong calc_xsz; unsignedint xattr_nms; longlong calc_xnms;
};
/** * add_inode - add inode information to RB-tree of inodes. * @c: UBIFS file-system description object * @fsckd: FS checking information * @ino: raw UBIFS inode to add * * This is a helper function for 'check_leaf()' which adds information about * inode @ino to the RB-tree of inodes. Returns inode information pointer in * case of success and a negative error code in case of failure.
*/ staticstruct fsck_inode *add_inode(struct ubifs_info *c, struct fsck_data *fsckd, struct ubifs_ino_node *ino)
{ struct rb_node **p, *parent = NULL; struct fsck_inode *fscki;
ino_t inum = key_inum_flash(c, &ino->key); struct inode *inode; struct ubifs_inode *ui;
p = &fsckd->inodes.rb_node; while (*p) {
parent = *p;
fscki = rb_entry(parent, struct fsck_inode, rb); if (inum < fscki->inum)
p = &(*p)->rb_left; elseif (inum > fscki->inum)
p = &(*p)->rb_right; else return fscki;
}
if (inum > c->highest_inum) {
ubifs_err(c, "too high inode number, max. is %lu",
(unsignedlong)c->highest_inum); return ERR_PTR(-EINVAL);
}
fscki = kzalloc(sizeof(struct fsck_inode), GFP_NOFS); if (!fscki) return ERR_PTR(-ENOMEM);
inode = ilookup(c->vfs_sb, inum);
fscki->inum = inum; /* * If the inode is present in the VFS inode cache, use it instead of * the on-flash inode which might be out-of-date. E.g., the size might * be out-of-date. If we do not do this, the following may happen, for * example: * 1. A power cut happens * 2. We mount the file-system R/O, the replay process fixes up the * inode size in the VFS cache, but on on-flash. * 3. 'check_leaf()' fails because it hits a data node beyond inode * size.
*/ if (!inode) {
fscki->nlink = le32_to_cpu(ino->nlink);
fscki->size = le64_to_cpu(ino->size);
fscki->xattr_cnt = le32_to_cpu(ino->xattr_cnt);
fscki->xattr_sz = le32_to_cpu(ino->xattr_size);
fscki->xattr_nms = le32_to_cpu(ino->xattr_names);
fscki->mode = le32_to_cpu(ino->mode);
} else {
ui = ubifs_inode(inode);
fscki->nlink = inode->i_nlink;
fscki->size = inode->i_size;
fscki->xattr_cnt = ui->xattr_cnt;
fscki->xattr_sz = ui->xattr_size;
fscki->xattr_nms = ui->xattr_names;
fscki->mode = inode->i_mode;
iput(inode);
}
if (S_ISDIR(fscki->mode)) {
fscki->calc_sz = UBIFS_INO_NODE_SZ;
fscki->calc_cnt = 2;
}
/** * search_inode - search inode in the RB-tree of inodes. * @fsckd: FS checking information * @inum: inode number to search * * This is a helper function for 'check_leaf()' which searches inode @inum in * the RB-tree of inodes and returns an inode information pointer or %NULL if * the inode was not found.
*/ staticstruct fsck_inode *search_inode(struct fsck_data *fsckd, ino_t inum)
{ struct rb_node *p; struct fsck_inode *fscki;
p = fsckd->inodes.rb_node; while (p) {
fscki = rb_entry(p, struct fsck_inode, rb); if (inum < fscki->inum)
p = p->rb_left; elseif (inum > fscki->inum)
p = p->rb_right; else return fscki;
} return NULL;
}
/** * read_add_inode - read inode node and add it to RB-tree of inodes. * @c: UBIFS file-system description object * @fsckd: FS checking information * @inum: inode number to read * * This is a helper function for 'check_leaf()' which finds inode node @inum in * the index, reads it, and adds it to the RB-tree of inodes. Returns inode * information pointer in case of success and a negative error code in case of * failure.
*/ staticstruct fsck_inode *read_add_inode(struct ubifs_info *c, struct fsck_data *fsckd, ino_t inum)
{ int n, err; union ubifs_key key; struct ubifs_znode *znode; struct ubifs_zbranch *zbr; struct ubifs_ino_node *ino; struct fsck_inode *fscki;
fscki = search_inode(fsckd, inum); if (fscki) return fscki;
ino_key_init(c, &key, inum);
err = ubifs_lookup_level0(c, &key, &znode, &n); if (!err) {
ubifs_err(c, "inode %lu not found in index", (unsignedlong)inum); return ERR_PTR(-ENOENT);
} elseif (err < 0) {
ubifs_err(c, "error %d while looking up inode %lu",
err, (unsignedlong)inum); return ERR_PTR(err);
}
/** * check_leaf - check leaf node. * @c: UBIFS file-system description object * @zbr: zbranch of the leaf node to check * @priv: FS checking information * * This is a helper function for 'dbg_check_filesystem()' which is called for * every single leaf node while walking the indexing tree. It checks that the * leaf node referred from the indexing tree exists, has correct CRC, and does * some other basic validation. This function is also responsible for building * an RB-tree of inodes - it adds all inodes into the RB-tree. It also * calculates reference count, size, etc for each inode in order to later * compare them to the information stored inside the inodes and detect possible * inconsistencies. Returns zero in case of success and a negative error code * in case of failure.
*/ staticint check_leaf(struct ubifs_info *c, struct ubifs_zbranch *zbr, void *priv)
{
ino_t inum; void *node; struct ubifs_ch *ch; int err, type = key_type(c, &zbr->key); struct fsck_inode *fscki;
/* If this is an inode node, add it to RB-tree of inodes */ if (type == UBIFS_INO_KEY) {
fscki = add_inode(c, priv, node); if (IS_ERR(fscki)) {
err = PTR_ERR(fscki);
ubifs_err(c, "error %d while adding inode node", err); goto out_dump;
} goto out;
}
if (type != UBIFS_DENT_KEY && type != UBIFS_XENT_KEY &&
type != UBIFS_DATA_KEY) {
ubifs_err(c, "unexpected node type %d at LEB %d:%d",
type, zbr->lnum, zbr->offs);
err = -EINVAL; goto out_free;
}
ch = node; if (le64_to_cpu(ch->sqnum) > c->max_sqnum) {
ubifs_err(c, "too high sequence number, max. is %llu",
c->max_sqnum);
err = -EINVAL; goto out_dump;
}
/* * Search the inode node this data node belongs to and insert * it to the RB-tree of inodes.
*/
inum = key_inum_flash(c, &dn->key);
fscki = read_add_inode(c, priv, inum); if (IS_ERR(fscki)) {
err = PTR_ERR(fscki);
ubifs_err(c, "error %d while processing data node and trying to find inode node %lu",
err, (unsignedlong)inum); goto out_dump;
}
/* Make sure the data node is within inode size */
blk_offs = key_block_flash(c, &dn->key);
blk_offs <<= UBIFS_BLOCK_SHIFT;
blk_offs += le32_to_cpu(dn->size); if (blk_offs > fscki->size) {
ubifs_err(c, "data node at LEB %d:%d is not within inode size %lld",
zbr->lnum, zbr->offs, fscki->size);
err = -EINVAL; goto out_dump;
}
} else { int nlen; struct ubifs_dent_node *dent = node; struct fsck_inode *fscki1;
ubifs_assert(c, zbr->len >= UBIFS_DENT_NODE_SZ);
err = ubifs_validate_entry(c, dent); if (err) goto out_dump;
/* * Search the inode node this entry refers to and the parent * inode node and insert them to the RB-tree of inodes.
*/
inum = le64_to_cpu(dent->inum);
fscki = read_add_inode(c, priv, inum); if (IS_ERR(fscki)) {
err = PTR_ERR(fscki);
ubifs_err(c, "error %d while processing entry node and trying to find inode node %lu",
err, (unsignedlong)inum); goto out_dump;
}
/* Count how many direntries or xentries refers this inode */
fscki->references += 1;
inum = key_inum_flash(c, &dent->key);
fscki1 = read_add_inode(c, priv, inum); if (IS_ERR(fscki1)) {
err = PTR_ERR(fscki1);
ubifs_err(c, "error %d while processing entry node and trying to find parent inode node %lu",
err, (unsignedlong)inum); goto out_dump;
}
rbtree_postorder_for_each_entry_safe(fscki, n, &fsckd->inodes, rb)
kfree(fscki);
}
/** * check_inodes - checks all inodes. * @c: UBIFS file-system description object * @fsckd: FS checking information * * This is a helper function for 'dbg_check_filesystem()' which walks the * RB-tree of inodes after the index scan has been finished, and checks that * inode nlink, size, etc are correct. Returns zero if inodes are fine, * %-EINVAL if not, and a negative error code in case of failure.
*/ staticint check_inodes(struct ubifs_info *c, struct fsck_data *fsckd)
{ int n, err; union ubifs_key key; struct ubifs_znode *znode; struct ubifs_zbranch *zbr; struct ubifs_ino_node *ino; struct fsck_inode *fscki; struct rb_node *this = rb_first(&fsckd->inodes);
while (this) {
fscki = rb_entry(this, struct fsck_inode, rb); this = rb_next(this);
if (S_ISDIR(fscki->mode)) { /* * Directories have to have exactly one reference (they * cannot have hardlinks), although root inode is an * exception.
*/ if (fscki->inum != UBIFS_ROOT_INO &&
fscki->references != 1) {
ubifs_err(c, "directory inode %lu has %d direntries which refer it, but should be 1",
(unsignedlong)fscki->inum,
fscki->references); goto out_dump;
} if (fscki->inum == UBIFS_ROOT_INO &&
fscki->references != 0) {
ubifs_err(c, "root inode %lu has non-zero (%d) direntries which refer it",
(unsignedlong)fscki->inum,
fscki->references); goto out_dump;
} if (fscki->calc_sz != fscki->size) {
ubifs_err(c, "directory inode %lu size is %lld, but calculated size is %lld",
(unsignedlong)fscki->inum,
fscki->size, fscki->calc_sz); goto out_dump;
} if (fscki->calc_cnt != fscki->nlink) {
ubifs_err(c, "directory inode %lu nlink is %d, but calculated nlink is %d",
(unsignedlong)fscki->inum,
fscki->nlink, fscki->calc_cnt); goto out_dump;
}
} else { if (fscki->references != fscki->nlink) {
ubifs_err(c, "inode %lu nlink is %d, but calculated nlink is %d",
(unsignedlong)fscki->inum,
fscki->nlink, fscki->references); goto out_dump;
}
} if (fscki->xattr_sz != fscki->calc_xsz) {
ubifs_err(c, "inode %lu has xattr size %u, but calculated size is %lld",
(unsignedlong)fscki->inum, fscki->xattr_sz,
fscki->calc_xsz); goto out_dump;
} if (fscki->xattr_cnt != fscki->calc_xcnt) {
ubifs_err(c, "inode %lu has %u xattrs, but calculated count is %lld",
(unsignedlong)fscki->inum,
fscki->xattr_cnt, fscki->calc_xcnt); goto out_dump;
} if (fscki->xattr_nms != fscki->calc_xnms) {
ubifs_err(c, "inode %lu has xattr names' size %u, but calculated names' size is %lld",
(unsignedlong)fscki->inum, fscki->xattr_nms,
fscki->calc_xnms); goto out_dump;
}
}
return 0;
out_dump: /* Read the bad inode and dump it */
ino_key_init(c, &key, fscki->inum);
err = ubifs_lookup_level0(c, &key, &znode, &n); if (!err) {
ubifs_err(c, "inode %lu not found in index",
(unsignedlong)fscki->inum); return -ENOENT;
} elseif (err < 0) {
ubifs_err(c, "error %d while looking up inode %lu",
err, (unsignedlong)fscki->inum); return err;
}
ubifs_msg(c, "dump of the inode %lu sitting in LEB %d:%d",
(unsignedlong)fscki->inum, zbr->lnum, zbr->offs);
ubifs_dump_node(c, ino, zbr->len);
kfree(ino); return -EINVAL;
}
/** * dbg_check_filesystem - check the file-system. * @c: UBIFS file-system description object * * This function checks the file system, namely: * o makes sure that all leaf nodes exist and their CRCs are correct; * o makes sure inode nlink, size, xattr size/count are correct (for all * inodes). * * The function reads whole indexing tree and all nodes, so it is pretty * heavy-weight. Returns zero if the file-system is consistent, %-EINVAL if * not, and a negative error code in case of failure.
*/ int dbg_check_filesystem(struct ubifs_info *c)
{ int err; struct fsck_data fsckd;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.