diff options
Diffstat (limited to 'ubifs-utils/fsck.ubifs/check_space.c')
-rw-r--r-- | ubifs-utils/fsck.ubifs/check_space.c | 576 |
1 files changed, 572 insertions, 4 deletions
diff --git a/ubifs-utils/fsck.ubifs/check_space.c b/ubifs-utils/fsck.ubifs/check_space.c index f758bf1..afe6ba0 100644 --- a/ubifs-utils/fsck.ubifs/check_space.c +++ b/ubifs-utils/fsck.ubifs/check_space.c @@ -44,18 +44,20 @@ int get_free_leb(struct ubifs_info *c) * build_lpt - construct LPT and write it into flash. * @c: UBIFS file-system description object * @calculate_lp_cb: callback function to calculate the properties for given LEB + * @free_ltab: %true means to release c->ltab after creating lpt * * This function builds LPT according to the calculated results by * @calculate_lp_cb and writes LPT into flash. Returns zero in case of success, * a negative error code in case of failure. */ -int build_lpt(struct ubifs_info *c, calculate_lp_callback calculate_lp_cb) +int build_lpt(struct ubifs_info *c, calculate_lp_callback calculate_lp_cb, + bool free_ltab) { int i, err, lnum, free, dirty; u8 hash_lpt[UBIFS_HASH_ARR_SZ]; memset(&c->lst, 0, sizeof(struct ubifs_lp_stats)); - /* Set gc lnum. */ + /* Set gc lnum, equivalent to ubifs_rcvry_gc_commit/take_gc_lnum. */ lnum = get_free_leb(c); if (lnum < 0) return lnum; @@ -63,7 +65,7 @@ int build_lpt(struct ubifs_info *c, calculate_lp_callback calculate_lp_cb) /* Update LPT. */ for (i = 0; i < c->main_lebs; i++) { - err = calculate_lp_cb(c, i, &free, &dirty); + err = calculate_lp_cb(c, i, &free, &dirty, NULL); if (err) return err; @@ -87,8 +89,574 @@ int build_lpt(struct ubifs_info *c, calculate_lp_callback calculate_lp_cb) c->lst.total_dark += ubifs_calc_dark(c, spc); c->lst.total_used += c->leb_size - spc; } + + dbg_fsck("build properties for LEB %d, free %d dirty %d is_idx %d, in %s", + i + c->main_first, free, dirty, + FSCK(c)->lpts[i].flags & LPROPS_INDEX ? 1 : 0, + c->dev_name); } /* Write LPT. */ - return ubifs_create_lpt(c, FSCK(c)->lpts, c->main_lebs, hash_lpt); + return ubifs_create_lpt(c, FSCK(c)->lpts, c->main_lebs, hash_lpt, free_ltab); +} + +static int scan_get_lp(struct ubifs_info *c, int index, int *free, int *dirty, + int *is_idx) +{ + struct ubifs_scan_leb *sleb; + struct ubifs_scan_node *snod; + int used, idx_leb, lnum = index + c->main_first, err = 0; + bool is_build_lpt = FSCK(c)->lpt_status & FR_LPT_CORRUPTED; + + if (is_build_lpt) { + if (!test_bit(index, FSCK(c)->used_lebs) || c->gc_lnum == lnum) { + *free = c->leb_size; + *dirty = 0; + return 0; + } + } else { + if (!test_bit(index, FSCK(c)->used_lebs)) { + *free = c->leb_size; + *dirty = 0; + return 0; + } + } + + sleb = ubifs_scan(c, lnum, 0, c->sbuf, 0); + if (IS_ERR(sleb)) { + /* All TNC LEBs have passed ubifs_scan in previous steps. */ + ubifs_assert(c, !get_failure_reason_callback(c)); + return PTR_ERR(sleb); + } + + idx_leb = -1; + used = 0; + list_for_each_entry(snod, &sleb->nodes, list) { + int found, level = 0; + + if (idx_leb == -1) + idx_leb = (snod->type == UBIFS_IDX_NODE) ? 1 : 0; + + if (idx_leb) + /* + * Previous steps have ensured that every TNC LEB + * contains only index nodes or non-index nodes. + */ + ubifs_assert(c, snod->type == UBIFS_IDX_NODE); + + 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, idx_leb); + if (found) { + if (found < 0) { + err = found; + /* + * TNC traversing is finished in previous steps, + * any TNC path is accessible. + */ + ubifs_assert(c, !get_failure_reason_callback(c)); + goto out; + } + used += ALIGN(snod->len, 8); + } + } + + if (is_build_lpt && !used) { + *free = c->leb_size; + *dirty = 0; + } else { + *free = c->leb_size - sleb->endpt; + *dirty = sleb->endpt - used; + if (idx_leb == 1) { + if (is_build_lpt) + FSCK(c)->lpts[index].flags = LPROPS_INDEX; + else + *is_idx = 1; + } + } + +out: + ubifs_scan_destroy(sleb); + return err; +} + +static void clear_buds(struct ubifs_info *c) +{ + int i; + + /* + * Since lpt is invalid, space statistics cannot be trusted, the buds + * were used to trace taken LEBs(LPT related), and fsck makes sure that + * there will be no new journal writings(no space allocations) before + * committing, so we should clear buds to prevent wrong lpt updating in + * committing stage(eg. ubifs_return_leb operation for @c->old_buds). + */ + free_buds(c, true); + for (i = 0; i < c->jhead_cnt; i++) { + c->jheads[i].wbuf.lnum = -1; + c->jheads[i].wbuf.offs = -1; + } +} + +static void clear_lp_lists_and_heaps(struct ubifs_info *c) +{ + int i; + + /* + * Since lpt is invalid, clear in-memory fast accessing paths (lp + * lists & heaps). + */ + c->freeable_cnt = 0; + c->in_a_category_cnt = 0; + for (i = 0; i < LPROPS_HEAP_CNT; i++) { + memset(c->lpt_heap[i].arr, 0, LPT_HEAP_SZ * sizeof(void *)); + c->lpt_heap[i].cnt = 0; + c->lpt_heap[i].max_cnt = LPT_HEAP_SZ; + } + memset(c->dirty_idx.arr, 0, LPT_HEAP_SZ * sizeof(void *)); + c->dirty_idx.cnt = 0; + c->dirty_idx.max_cnt = LPT_HEAP_SZ; + INIT_LIST_HEAD(&c->uncat_list); + INIT_LIST_HEAD(&c->empty_list); + INIT_LIST_HEAD(&c->freeable_list); + INIT_LIST_HEAD(&c->frdi_idx_list); +} + +static int retake_ihead(struct ubifs_info *c) +{ + int err = take_ihead(c); + + if (err < 0) { + /* All LPT nodes must be accessible. */ + ubifs_assert(c, !get_failure_reason_callback(c)); + ubifs_assert(c, FSCK(c)->lpt_status == 0); + } else + err = 0; + + return err; +} + +static int rebuild_lpt(struct ubifs_info *c) +{ + int err; + + /* Clear buds. */ + clear_buds(c); + /* Clear stale in-memory lpt data. */ + c->lpt_drty_flgs = 0; + c->dirty_nn_cnt = 0; + c->dirty_pn_cnt = 0; + clear_lp_lists_and_heaps(c); + ubifs_free_lpt_nodes(c); + kfree(c->ltab); + c->ltab = NULL; + + FSCK(c)->lpts = kzalloc(sizeof(struct ubifs_lprops) * c->main_lebs, + GFP_KERNEL); + if (!FSCK(c)->lpts) { + log_err(c, errno, "can not allocate lpts"); + return -ENOMEM; + } + + err = build_lpt(c, scan_get_lp, false); + if (err) + goto out; + + err = retake_ihead(c); + if (err) + goto out; + + FSCK(c)->lpt_status = 0; + +out: + kfree(FSCK(c)->lpts); + return err; +} + +static void check_and_correct_nnode(struct ubifs_info *c, + struct ubifs_nnode *nnode, + struct ubifs_nnode *parent_nnode, + int row, int col, int *corrected) +{ + int num = ubifs_calc_nnode_num(row, col); + + if (nnode->num != num) { + struct nnode_problem nnp = { + .nnode = nnode, + .parent_nnode = parent_nnode, + .num = num, + }; + + /* + * The nnode number is read from disk in big lpt mode, which + * could lead to the wrong nnode number, otherwise, ther nnode + * number cannot be wrong. + */ + ubifs_assert(c, c->big_lpt); + FSCK(c)->lpt_status |= FR_LPT_INCORRECT; + if (fix_problem(c, NNODE_INCORRECT, &nnp)) { + nnode->num = num; + *corrected = 1; + } + } +} + +static int check_and_correct_pnode(struct ubifs_info *c, + struct ubifs_pnode *pnode, int col, + struct ubifs_lp_stats *lst, + int *freeable_cnt, int *corrected) +{ + int i, index, lnum; + const int lp_cnt = UBIFS_LPT_FANOUT; + + if (pnode->num != col) { + struct pnode_problem pnp = { + .pnode = pnode, + .num = col, + }; + + /* + * The pnode number is read from disk in big lpt mode, which + * could lead to the wrong pnode number, otherwise, ther pnode + * number cannot be wrong. + */ + ubifs_assert(c, c->big_lpt); + FSCK(c)->lpt_status |= FR_LPT_INCORRECT; + if (fix_problem(c, PNODE_INCORRECT, &pnp)) { + pnode->num = col; + *corrected = 1; + } + } + + index = pnode->num << UBIFS_LPT_FANOUT_SHIFT; + lnum = index + c->main_first; + for (i = 0; i < lp_cnt && lnum < c->leb_cnt; i++, index++, lnum++) { + int err, cat, free, dirty, is_idx = 0; + struct ubifs_lprops *lp = &pnode->lprops[i]; + + err = scan_get_lp(c, index, &free, &dirty, &is_idx); + if (err) + return err; + + dbg_fsck("calculate properties for LEB %d, free %d dirty %d is_idx %d, in %s", + lnum, free, dirty, is_idx, c->dev_name); + + if (!FSCK(c)->lpt_status && lp->free + lp->dirty == c->leb_size + && !test_bit(index, FSCK(c)->used_lebs)) { + /* + * Some LEBs may become freeable in the following cases: + * a. LEBs become freeable after replaying the journal. + * b. Unclean reboot while doing gc for a freeable + * non-index LEB + * c. Freeable index LEBs in an uncompleted commit due + * to an unclean unmount. + * , which makes that these LEBs won't be accounted into + * the FSCK(c)->used_lebs, but they actually have + * free/dirty space statistics. So we should skip + * checking space for these LEBs. + */ + free = lp->free; + dirty = lp->dirty; + is_idx = (lp->flags & LPROPS_INDEX) ? 1 : 0; + } + if (lnum != lp->lnum || + free != lp->free || dirty != lp->dirty || + (is_idx && !(lp->flags & LPROPS_INDEX)) || + (!is_idx && (lp->flags & LPROPS_INDEX))) { + struct lp_problem lpp = { + .lnum = lnum, + .lp = lp, + .free = free, + .dirty = dirty, + .is_idx = is_idx, + }; + + FSCK(c)->lpt_status |= FR_LPT_INCORRECT; + if (fix_problem(c, LP_INCORRECT, &lpp)) { + lp->lnum = lnum; + lp->free = free; + lp->dirty = dirty; + lp->flags = is_idx ? LPROPS_INDEX : 0; + *corrected = 1; + } + } + + cat = ubifs_categorize_lprops(c, lp); + if (cat != (lp->flags & LPROPS_CAT_MASK)) { + if (FSCK(c)->lpt_status & FR_LPT_INCORRECT) { + lp->flags &= ~LPROPS_CAT_MASK; + lp->flags |= cat; + } else { + /* lp could be in the heap or un-categorized(add heap failed). */ + ubifs_assert(c, (lp->flags & LPROPS_CAT_MASK) == LPROPS_UNCAT); + } + } + if (cat == LPROPS_FREEABLE) + *freeable_cnt = *freeable_cnt + 1; + if ((lp->flags & LPROPS_TAKEN) && free == c->leb_size) + lst->taken_empty_lebs += 1; + + lst->total_free += free; + lst->total_dirty += dirty; + + if (free == c->leb_size) + lst->empty_lebs++; + + if (is_idx) { + lst->idx_lebs += 1; + } else { + int spc; + + spc = free + dirty; + if (spc < c->dead_wm) + lst->total_dead += spc; + else + lst->total_dark += ubifs_calc_dark(c, spc); + lst->total_used += c->leb_size - spc; + } + } + + return 0; +} + +static int check_and_correct_lpt(struct ubifs_info *c, int *lpt_corrected) +{ + int err, i, cnt, iip, row, col, corrected, lnum, max_num, freeable_cnt; + struct ubifs_cnode *cn, *cnode; + struct ubifs_nnode *nnode, *nn; + struct ubifs_pnode *pnode; + struct ubifs_lp_stats lst; + + max_num = 0; + freeable_cnt = 0; + memset(&lst, 0, sizeof(struct ubifs_lp_stats)); + + /* Load the entire LPT tree, check whether there are corrupted nodes. */ + cnt = DIV_ROUND_UP(c->main_lebs, UBIFS_LPT_FANOUT); + for (i = 0; i < cnt; i++) { + pnode = ubifs_pnode_lookup(c, i); + if (IS_ERR(pnode)) + return PTR_ERR(pnode); + if (pnode->num > max_num) + max_num = pnode->num; + } + + /* Check whether there are pnodes exceeding the 'c->main_lebs'. */ + pnode = ubifs_pnode_lookup(c, 0); + if (IS_ERR(pnode)) + return PTR_ERR(pnode); + while (pnode) { + if (pnode->num > max_num) { + ubifs_err(c, "pnode(%d) exceeds max number(%d)", + pnode->num, max_num); + set_failure_reason_callback(c, FR_LPT_CORRUPTED); + return -EINVAL; + } + pnode = ubifs_find_next_pnode(c, pnode); + if (IS_ERR(pnode)) + return PTR_ERR(pnode); + } + + /* Check & correct nnodes and pnodes(including LEB properties). */ + row = col = iip = 0; + cnode = (struct ubifs_cnode *)c->nroot; + while (cnode) { + ubifs_assert(c, row >= 0); + nnode = cnode->parent; + if (cnode->level) { + corrected = 0; + /* cnode is a nnode */ + nn = (struct ubifs_nnode *)cnode; + check_and_correct_nnode(c, nn, nnode, row, col, + &corrected); + if (corrected) + ubifs_make_nnode_dirty(c, nn); + while (iip < UBIFS_LPT_FANOUT) { + cn = nn->nbranch[iip].cnode; + if (cn) { + /* Go down */ + row += 1; + col <<= UBIFS_LPT_FANOUT_SHIFT; + col += iip; + iip = 0; + cnode = cn; + break; + } + /* Go right */ + iip += 1; + } + if (iip < UBIFS_LPT_FANOUT) + continue; + } else { + corrected = 0; + /* cnode is a pnode */ + pnode = (struct ubifs_pnode *)cnode; + err = check_and_correct_pnode(c, pnode, col, &lst, + &freeable_cnt, &corrected); + if (err) + return err; + if (corrected) + ubifs_make_pnode_dirty(c, pnode); + } + /* Go up and to the right */ + row -= 1; + col >>= UBIFS_LPT_FANOUT_SHIFT; + iip = cnode->iip + 1; + cnode = (struct ubifs_cnode *)nnode; + } + + dbg_fsck("empty_lebs %d, idx_lebs %d, total_free %lld, total_dirty %lld," + " total_used %lld, total_dead %lld, total_dark %lld," + " taken_empty_lebs %d, freeable_cnt %d, in %s", + lst.empty_lebs, lst.idx_lebs, lst.total_free, lst.total_dirty, + lst.total_used, lst.total_dead, lst.total_dark, + lst.taken_empty_lebs, freeable_cnt, c->dev_name); + + /* Check & correct the global space statistics. */ + 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 || + lst.total_dead != c->lst.total_dead || + lst.total_dark != c->lst.total_dark) { + struct space_stat_problem ssp = { + .lst = &c->lst, + .calc_lst = &lst, + }; + + FSCK(c)->lpt_status |= FR_LPT_INCORRECT; + if (fix_problem(c, SPACE_STAT_INCORRECT, &ssp)) { + c->lst.empty_lebs = lst.empty_lebs; + c->lst.idx_lebs = lst.idx_lebs; + c->lst.total_free = lst.total_free; + c->lst.total_dirty = lst.total_dirty; + c->lst.total_used = lst.total_used; + c->lst.total_dead = lst.total_dead; + c->lst.total_dark = lst.total_dark; + } + } + + /* Check & correct the lprops table information. */ + for (lnum = c->lpt_first; lnum <= c->lpt_last; lnum++) { + err = dbg_check_ltab_lnum(c, lnum); + if (err) + return err; + } + + if (FSCK(c)->lpt_status & FR_LPT_INCORRECT) { + /* Reset the taken_empty_lebs. */ + c->lst.taken_empty_lebs = 0; + /* Clear buds. */ + clear_buds(c); + /* Clear lp lists & heaps. */ + clear_lp_lists_and_heaps(c); + /* + * Build lp lists & heaps, subsequent steps could recover + * disconnected files by allocating free space. + */ + for (lnum = c->main_first; lnum < c->leb_cnt; lnum++) { + int cat; + struct ubifs_lprops *lp = ubifs_lpt_lookup(c, lnum); + if (IS_ERR(lp)) + return PTR_ERR(lp); + + /* Clear %LPROPS_TAKEN flag for all LEBs. */ + lp->flags &= ~LPROPS_TAKEN; + cat = lp->flags & LPROPS_CAT_MASK; + ubifs_add_to_cat(c, lp, cat); + } + /* + * The %LPROPS_TAKEN flag is cleared in LEB properties, just + * remark it for c->ihead_lnum LEB. + */ + err = retake_ihead(c); + if (err) + return err; + + *lpt_corrected = 1; + FSCK(c)->lpt_status &= ~FR_LPT_INCORRECT; + } else { + ubifs_assert(c, c->freeable_cnt == freeable_cnt); + ubifs_assert(c, c->lst.taken_empty_lebs == lst.taken_empty_lebs); + ubifs_assert(c, c->in_a_category_cnt == c->main_lebs); + } + + return 0; +} + +/** + * check_and_correct_space - check & correct the space statistics. + * @c: UBIFS file-system description object + * + * This function does following things: + * 1. Check fsck mode, exit program if current mode is check mode. + * 2. Check space statistics by comparing lpt records with scanning results + * for all main LEBs. There could be following problems: + * a) comparison result is inconsistent: correct the lpt records by LEB + * scanning results. + * b) lpt is corrupted: rebuild lpt. + * 3. Set the gc lnum. + * Returns zero in case of success, a negative error code in case of failure. + */ +int check_and_correct_space(struct ubifs_info *c) +{ + int err, lpt_corrected = 0; + + if (FSCK(c)->mode == CHECK_MODE) { + /* + * The check mode will exit, because unclean LEBs are not + * rewritten for readonly mode in previous steps. + */ + if (FSCK(c)->lpt_status) + exit_code |= FSCK_UNCORRECTED; + dbg_fsck("skip checking & correcting space%s, in %s", + mode_name(c), c->dev_name); + exit(exit_code); + } + + log_out(c, "Check and correct the space statistics"); + + if (FSCK(c)->lpt_status & FR_LPT_CORRUPTED) { +rebuild: + if (fix_problem(c, LPT_CORRUPTED, NULL)) + return rebuild_lpt(c); + } + + err = check_and_correct_lpt(c, &lpt_corrected); + if (err) { + if (test_and_clear_failure_reason_callback(c, FR_LPT_CORRUPTED)) + goto rebuild; + return err; + } + + /* Set gc lnum. */ + if (c->need_recovery || lpt_corrected) { + err = ubifs_rcvry_gc_commit(c); + if (err) { + /* All LPT nodes must be accessible. */ + ubifs_assert(c, !get_failure_reason_callback(c)); + ubifs_assert(c, FSCK(c)->lpt_status == 0); + return err; + } + } else { + err = take_gc_lnum(c); + if (err) { + /* All LPT nodes must be accessible. */ + ubifs_assert(c, !get_failure_reason_callback(c)); + ubifs_assert(c, FSCK(c)->lpt_status == 0); + return err; + } + err = ubifs_leb_unmap(c, c->gc_lnum); + if (err) + return err; + } + + return err; } |