+
+/*
+ * Everything below is related to debugging.
+ */
+
+/**
+ * dbg_check_cats - check category heaps and lists.
+ * @c: UBIFS file-system description object
+ *
+ * This function returns %0 on success and a negative error code on failure.
+ */
+int dbg_check_cats(struct ubifs_info *c)
+{
+ struct ubifs_lprops *lprops;
+ struct list_head *pos;
+ int i, cat;
+
+ if (!dbg_is_chk_gen(c) && !dbg_is_chk_lprops(c))
+ return 0;
+
+ list_for_each_entry(lprops, &c->empty_list, list) {
+ if (lprops->free != c->leb_size) {
+ ubifs_err(c, "non-empty LEB %d on empty list (free %d dirty %d flags %d)",
+ lprops->lnum, lprops->free, lprops->dirty,
+ lprops->flags);
+ return -EINVAL;
+ }
+ if (lprops->flags & LPROPS_TAKEN) {
+ ubifs_err(c, "taken LEB %d on empty list (free %d dirty %d flags %d)",
+ lprops->lnum, lprops->free, lprops->dirty,
+ lprops->flags);
+ return -EINVAL;
+ }
+ }
+
+ i = 0;
+ list_for_each_entry(lprops, &c->freeable_list, list) {
+ if (lprops->free + lprops->dirty != c->leb_size) {
+ ubifs_err(c, "non-freeable LEB %d on freeable list (free %d dirty %d flags %d)",
+ lprops->lnum, lprops->free, lprops->dirty,
+ lprops->flags);
+ return -EINVAL;
+ }
+ if (lprops->flags & LPROPS_TAKEN) {
+ ubifs_err(c, "taken LEB %d on freeable list (free %d dirty %d flags %d)",
+ lprops->lnum, lprops->free, lprops->dirty,
+ lprops->flags);
+ return -EINVAL;
+ }
+ i += 1;
+ }
+ if (i != c->freeable_cnt) {
+ ubifs_err(c, "freeable list count %d expected %d", i,
+ c->freeable_cnt);
+ return -EINVAL;
+ }
+
+ i = 0;
+ list_for_each(pos, &c->idx_gc)
+ i += 1;
+ if (i != c->idx_gc_cnt) {
+ ubifs_err(c, "idx_gc list count %d expected %d", i,
+ c->idx_gc_cnt);
+ return -EINVAL;
+ }
+
+ list_for_each_entry(lprops, &c->frdi_idx_list, list) {
+ if (lprops->free + lprops->dirty != c->leb_size) {
+ ubifs_err(c, "non-freeable LEB %d on frdi_idx list (free %d dirty %d flags %d)",
+ lprops->lnum, lprops->free, lprops->dirty,
+ lprops->flags);
+ return -EINVAL;
+ }
+ if (lprops->flags & LPROPS_TAKEN) {
+ ubifs_err(c, "taken LEB %d on frdi_idx list (free %d dirty %d flags %d)",
+ lprops->lnum, lprops->free, lprops->dirty,
+ lprops->flags);
+ return -EINVAL;
+ }
+ if (!(lprops->flags & LPROPS_INDEX)) {
+ ubifs_err(c, "non-index LEB %d on frdi_idx list (free %d dirty %d flags %d)",
+ lprops->lnum, lprops->free, lprops->dirty,
+ lprops->flags);
+ return -EINVAL;
+ }
+ }
+
+ for (cat = 1; cat <= LPROPS_HEAP_CNT; cat++) {
+ struct ubifs_lpt_heap *heap = &c->lpt_heap[cat - 1];
+
+ for (i = 0; i < heap->cnt; i++) {
+ lprops = heap->arr[i];
+ if (!lprops) {
+ ubifs_err(c, "null ptr in LPT heap cat %d", cat);
+ return -EINVAL;
+ }
+ if (lprops->hpos != i) {
+ ubifs_err(c, "bad ptr in LPT heap cat %d", cat);
+ return -EINVAL;
+ }
+ if (lprops->flags & LPROPS_TAKEN) {
+ ubifs_err(c, "taken LEB in LPT heap cat %d", cat);
+ return -EINVAL;
+ }
+ }
+ }
+
+ return 0;
+}
+
+void dbg_check_heap(struct ubifs_info *c, struct ubifs_lpt_heap *heap, int cat,
+ int add_pos)
+{
+ int i = 0, j, err = 0;
+
+ if (!dbg_is_chk_gen(c) && !dbg_is_chk_lprops(c))
+ return;
+
+ for (i = 0; i < heap->cnt; i++) {
+ struct ubifs_lprops *lprops = heap->arr[i];
+ struct ubifs_lprops *lp;
+
+ if (i != add_pos)
+ if ((lprops->flags & LPROPS_CAT_MASK) != cat) {
+ err = 1;
+ goto out;
+ }
+ if (lprops->hpos != i) {
+ err = 2;
+ goto out;
+ }
+ lp = ubifs_lpt_lookup(c, lprops->lnum);
+ if (IS_ERR(lp)) {
+ err = 3;
+ goto out;
+ }
+ if (lprops != lp) {
+ ubifs_err(c, "lprops %zx lp %zx lprops->lnum %d lp->lnum %d",
+ (size_t)lprops, (size_t)lp, lprops->lnum,
+ lp->lnum);
+ err = 4;
+ goto out;
+ }
+ for (j = 0; j < i; j++) {
+ lp = heap->arr[j];
+ if (lp == lprops) {
+ err = 5;
+ goto out;
+ }
+ if (lp->lnum == lprops->lnum) {
+ err = 6;
+ goto out;
+ }
+ }
+ }
+out:
+ if (err) {
+ ubifs_err(c, "failed cat %d hpos %d err %d", cat, i, err);
+ dump_stack();
+ ubifs_dump_heap(c, heap, cat);
+ }
+}
+
+/**
+ * scan_check_cb - scan callback.
+ * @c: the UBIFS file-system description object
+ * @lp: LEB properties to scan
+ * @in_tree: whether the LEB properties are in main memory
+ * @lst: lprops statistics to update
+ *
+ * This function returns a code that indicates whether the scan should continue
+ * (%LPT_SCAN_CONTINUE), whether the LEB properties should be added to the tree
+ * in main memory (%LPT_SCAN_ADD), or whether the scan should stop
+ * (%LPT_SCAN_STOP).
+ */
+static int scan_check_cb(struct ubifs_info *c,
+ const struct ubifs_lprops *lp, int in_tree,
+ struct ubifs_lp_stats *lst)
+{
+ struct ubifs_scan_leb *sleb;
+ struct ubifs_scan_node *snod;
+ int cat, lnum = lp->lnum, is_idx = 0, used = 0, free, dirty, ret;
+ void *buf = NULL;
+
+ cat = lp->flags & LPROPS_CAT_MASK;
+ if (cat != LPROPS_UNCAT) {
+ cat = ubifs_categorize_lprops(c, lp);
+ if (cat != (lp->flags & LPROPS_CAT_MASK)) {
+ ubifs_err(c, "bad LEB category %d expected %d",
+ (lp->flags & LPROPS_CAT_MASK), cat);
+ return -EINVAL;
+ }
+ }
+
+ /* Check lp is on its category list (if it has one) */
+ if (in_tree) {
+ struct list_head *list = NULL;
+
+ switch (cat) {
+ case LPROPS_EMPTY:
+ list = &c->empty_list;
+ break;
+ case LPROPS_FREEABLE:
+ list = &c->freeable_list;
+ break;
+ case LPROPS_FRDI_IDX:
+ list = &c->frdi_idx_list;
+ break;
+ case LPROPS_UNCAT:
+ list = &c->uncat_list;
+ break;
+ }
+ if (list) {
+ struct ubifs_lprops *lprops;
+ int found = 0;
+
+ list_for_each_entry(lprops, list, list) {
+ if (lprops == lp) {
+ found = 1;
+ break;
+ }
+ }
+ if (!found) {
+ ubifs_err(c, "bad LPT list (category %d)", cat);
+ return -EINVAL;
+ }
+ }
+ }
+
+ /* Check lp is on its category heap (if it has one) */
+ if (in_tree && cat > 0 && cat <= LPROPS_HEAP_CNT) {
+ struct ubifs_lpt_heap *heap = &c->lpt_heap[cat - 1];
+
+ if ((lp->hpos != -1 && heap->arr[lp->hpos]->lnum != lnum) ||
+ lp != heap->arr[lp->hpos]) {
+ ubifs_err(c, "bad LPT heap (category %d)", cat);
+ return -EINVAL;
+ }
+ }
+
+ buf = __vmalloc(c->leb_size, GFP_NOFS, PAGE_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ /*
+ * After an unclean unmount, empty and freeable LEBs
+ * may contain garbage - do not scan them.
+ */
+ if (lp->free == c->leb_size) {
+ lst->empty_lebs += 1;
+ lst->total_free += c->leb_size;
+ lst->total_dark += ubifs_calc_dark(c, c->leb_size);
+ return LPT_SCAN_CONTINUE;
+ }
+ if (lp->free + lp->dirty == c->leb_size &&
+ !(lp->flags & LPROPS_INDEX)) {
+ lst->total_free += lp->free;
+ lst->total_dirty += lp->dirty;
+ lst->total_dark += ubifs_calc_dark(c, c->leb_size);
+ return LPT_SCAN_CONTINUE;
+ }
+
+ sleb = ubifs_scan(c, lnum, 0, buf, 0);
+ if (IS_ERR(sleb)) {
+ ret = PTR_ERR(sleb);
+ if (ret == -EUCLEAN) {
+ ubifs_dump_lprops(c);
+ ubifs_dump_budg(c, &c->bi);
+ }
+ goto out;
+ }
+
+ is_idx = -1;
+ list_for_each_entry(snod, &sleb->nodes, list) {
+ int found, level = 0;
+
+ cond_resched();
+
+ if (is_idx == -1)
+ is_idx = (snod->type == UBIFS_IDX_NODE) ? 1 : 0;
+
+ if (is_idx && snod->type != UBIFS_IDX_NODE) {
+ ubifs_err(c, "indexing node in data LEB %d:%d",
+ lnum, snod->offs);
+ goto out_destroy;
+ }
+
+ if (snod->type == UBIFS_IDX_NODE) {
+ struct ubifs_idx_node *idx = snod->node;
+
+ key_read(c, ubifs_idx_key(c, idx), &snod->key);
+ level = le16_to_cpu(idx->level);
+ }
+
+ found = ubifs_tnc_has_node(c, &snod->key, level, lnum,
+ snod->offs, is_idx);
+ if (found) {
+ if (found < 0)
+ goto out_destroy;
+ used += ALIGN(snod->len, 8);
+ }
+ }
+
+ free = c->leb_size - sleb->endpt;
+ dirty = sleb->endpt - used;
+
+ if (free > c->leb_size || free < 0 || dirty > c->leb_size ||
+ dirty < 0) {
+ ubifs_err(c, "bad calculated accounting for LEB %d: free %d, dirty %d",
+ lnum, free, dirty);
+ goto out_destroy;
+ }
+
+ if (lp->free + lp->dirty == c->leb_size &&
+ free + dirty == c->leb_size)
+ if ((is_idx && !(lp->flags & LPROPS_INDEX)) ||
+ (!is_idx && free == c->leb_size) ||
+ lp->free == c->leb_size) {
+ /*
+ * Empty or freeable LEBs could contain index
+ * nodes from an uncompleted commit due to an
+ * unclean unmount. Or they could be empty for
+ * the same reason. Or it may simply not have been
+ * unmapped.
+ */
+ free = lp->free;
+ dirty = lp->dirty;
+ is_idx = 0;
+ }
+
+ if (is_idx && lp->free + lp->dirty == free + dirty &&
+ lnum != c->ihead_lnum) {
+ /*
+ * After an unclean unmount, an index LEB could have a different
+ * amount of free space than the value recorded by lprops. That
+ * is because the in-the-gaps method may use free space or
+ * create free space (as a side-effect of using ubi_leb_change
+ * and not writing the whole LEB). The incorrect free space
+ * value is not a problem because the index is only ever
+ * allocated empty LEBs, so there will never be an attempt to
+ * write to the free space at the end of an index LEB - except
+ * by the in-the-gaps method for which it is not a problem.
+ */
+ free = lp->free;
+ dirty = lp->dirty;
+ }
+
+ if (lp->free != free || lp->dirty != dirty)
+ goto out_print;
+
+ if (is_idx && !(lp->flags & LPROPS_INDEX)) {
+ if (free == c->leb_size)
+ /* Free but not unmapped LEB, it's fine */
+ is_idx = 0;
+ else {
+ ubifs_err(c, "indexing node without indexing flag");
+ goto out_print;
+ }
+ }
+
+ if (!is_idx && (lp->flags & LPROPS_INDEX)) {
+ ubifs_err(c, "data node with indexing flag");
+ goto out_print;
+ }
+
+ if (free == c->leb_size)
+ lst->empty_lebs += 1;
+
+ if (is_idx)
+ lst->idx_lebs += 1;
+
+ if (!(lp->flags & LPROPS_INDEX))
+ lst->total_used += c->leb_size - free - dirty;
+ lst->total_free += free;
+ lst->total_dirty += dirty;
+
+ if (!(lp->flags & LPROPS_INDEX)) {
+ int spc = free + dirty;
+
+ if (spc < c->dead_wm)
+ lst->total_dead += spc;
+ else
+ lst->total_dark += ubifs_calc_dark(c, spc);
+ }
+
+ ubifs_scan_destroy(sleb);
+ vfree(buf);
+ return LPT_SCAN_CONTINUE;
+
+out_print:
+ ubifs_err(c, "bad accounting of LEB %d: free %d, dirty %d flags %#x, should be free %d, dirty %d",
+ lnum, lp->free, lp->dirty, lp->flags, free, dirty);
+ ubifs_dump_leb(c, lnum);
+out_destroy:
+ ubifs_scan_destroy(sleb);
+ ret = -EINVAL;
+out:
+ vfree(buf);
+ return ret;
+}
+
+/**
+ * dbg_check_lprops - check all LEB properties.
+ * @c: UBIFS file-system description object
+ *
+ * This function checks all LEB properties and makes sure they are all correct.
+ * It returns zero if everything is fine, %-EINVAL if there is an inconsistency
+ * and other negative error codes in case of other errors. This function is
+ * called while the file system is locked (because of commit start), so no
+ * additional locking is required. Note that locking the LPT mutex would cause
+ * a circular lock dependency with the TNC mutex.
+ */
+int dbg_check_lprops(struct ubifs_info *c)
+{
+ int i, err;
+ struct ubifs_lp_stats lst;
+
+ if (!dbg_is_chk_lprops(c))
+ return 0;
+
+ /*
+ * As we are going to scan the media, the write buffers have to be
+ * synchronized.
+ */
+ for (i = 0; i < c->jhead_cnt; i++) {
+ err = ubifs_wbuf_sync(&c->jheads[i].wbuf);
+ if (err)
+ return err;
+ }
+
+ memset(&lst, 0, sizeof(struct ubifs_lp_stats));
+ err = ubifs_lpt_scan_nolock(c, c->main_first, c->leb_cnt - 1,
+ (ubifs_lpt_scan_callback)scan_check_cb,
+ &lst);
+ if (err && err != -ENOSPC)
+ goto out;
+
+ if (lst.empty_lebs != c->lst.empty_lebs ||
+ lst.idx_lebs != c->lst.idx_lebs ||
+ lst.total_free != c->lst.total_free ||
+ lst.total_dirty != c->lst.total_dirty ||
+ lst.total_used != c->lst.total_used) {
+ ubifs_err(c, "bad overall accounting");
+ ubifs_err(c, "calculated: empty_lebs %d, idx_lebs %d, total_free %lld, total_dirty %lld, total_used %lld",
+ lst.empty_lebs, lst.idx_lebs, lst.total_free,
+ lst.total_dirty, lst.total_used);
+ ubifs_err(c, "read from lprops: empty_lebs %d, idx_lebs %d, total_free %lld, total_dirty %lld, total_used %lld",
+ c->lst.empty_lebs, c->lst.idx_lebs, c->lst.total_free,
+ c->lst.total_dirty, c->lst.total_used);
+ err = -EINVAL;
+ goto out;
+ }
+
+ if (lst.total_dead != c->lst.total_dead ||
+ lst.total_dark != c->lst.total_dark) {
+ ubifs_err(c, "bad dead/dark space accounting");
+ ubifs_err(c, "calculated: total_dead %lld, total_dark %lld",
+ lst.total_dead, lst.total_dark);
+ ubifs_err(c, "read from lprops: total_dead %lld, total_dark %lld",
+ c->lst.total_dead, c->lst.total_dark);
+ err = -EINVAL;
+ goto out;
+ }
+
+ err = dbg_check_cats(c);
+out:
+ return err;
+}