| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Copyright (c) 2020-2024 Oracle. All Rights Reserved. |
| * Author: Darrick J. Wong <djwong@kernel.org> |
| */ |
| #include "xfs.h" |
| #include "xfs_fs.h" |
| #include "xfs_shared.h" |
| #include "xfs_format.h" |
| #include "xfs_trans_resv.h" |
| #include "xfs_mount.h" |
| #include "xfs_log_format.h" |
| #include "xfs_trans.h" |
| #include "xfs_inode.h" |
| #include "xfs_quota.h" |
| #include "xfs_qm.h" |
| #include "xfs_icache.h" |
| #include "xfs_bmap_util.h" |
| #include "xfs_ialloc.h" |
| #include "xfs_ag.h" |
| #include "scrub/scrub.h" |
| #include "scrub/common.h" |
| #include "scrub/repair.h" |
| #include "scrub/xfile.h" |
| #include "scrub/xfarray.h" |
| #include "scrub/iscan.h" |
| #include "scrub/quota.h" |
| #include "scrub/quotacheck.h" |
| #include "scrub/trace.h" |
| |
| /* |
| * Live Quotacheck |
| * =============== |
| * |
| * Quota counters are "summary" metadata, in the sense that they are computed |
| * as the summation of the block usage counts for every file on the filesystem. |
| * Therefore, we compute the correct icount, bcount, and rtbcount values by |
| * creating a shadow quota counter structure and walking every inode. |
| */ |
| |
| /* Track the quota deltas for a dquot in a transaction. */ |
| struct xqcheck_dqtrx { |
| xfs_dqtype_t q_type; |
| xfs_dqid_t q_id; |
| |
| int64_t icount_delta; |
| |
| int64_t bcount_delta; |
| int64_t delbcnt_delta; |
| |
| int64_t rtbcount_delta; |
| int64_t delrtb_delta; |
| }; |
| |
| #define XQCHECK_MAX_NR_DQTRXS (XFS_QM_TRANS_DQTYPES * XFS_QM_TRANS_MAXDQS) |
| |
| /* |
| * Track the quota deltas for all dquots attached to a transaction if the |
| * quota deltas are being applied to an inode that we already scanned. |
| */ |
| struct xqcheck_dqacct { |
| struct rhash_head hash; |
| uintptr_t tx_id; |
| struct xqcheck_dqtrx dqtrx[XQCHECK_MAX_NR_DQTRXS]; |
| unsigned int refcount; |
| }; |
| |
| /* Free a shadow dquot accounting structure. */ |
| static void |
| xqcheck_dqacct_free( |
| void *ptr, |
| void *arg) |
| { |
| struct xqcheck_dqacct *dqa = ptr; |
| |
| kfree(dqa); |
| } |
| |
| /* Set us up to scrub quota counters. */ |
| int |
| xchk_setup_quotacheck( |
| struct xfs_scrub *sc) |
| { |
| if (!XFS_IS_QUOTA_ON(sc->mp)) |
| return -ENOENT; |
| |
| xchk_fsgates_enable(sc, XCHK_FSGATES_QUOTA); |
| |
| sc->buf = kzalloc(sizeof(struct xqcheck), XCHK_GFP_FLAGS); |
| if (!sc->buf) |
| return -ENOMEM; |
| |
| return xchk_setup_fs(sc); |
| } |
| |
| /* |
| * Part 1: Collecting dquot resource usage counts. For each xfs_dquot attached |
| * to each inode, we create a shadow dquot, and compute the inode count and add |
| * the data/rt block usage from what we see. |
| * |
| * To avoid false corruption reports in part 2, any failure in this part must |
| * set the INCOMPLETE flag even when a negative errno is returned. This care |
| * must be taken with certain errno values (i.e. EFSBADCRC, EFSCORRUPTED, |
| * ECANCELED) that are absorbed into a scrub state flag update by |
| * xchk_*_process_error. Scrub and repair share the same incore data |
| * structures, so the INCOMPLETE flag is critical to prevent a repair based on |
| * insufficient information. |
| * |
| * Because we are scanning a live filesystem, it's possible that another thread |
| * will try to update the quota counters for an inode that we've already |
| * scanned. This will cause our counts to be incorrect. Therefore, we hook |
| * the live transaction code in two places: (1) when the callers update the |
| * per-transaction dqtrx structure to log quota counter updates; and (2) when |
| * transaction commit actually logs those updates to the incore dquot. By |
| * shadowing transaction updates in this manner, live quotacheck can ensure |
| * by locking the dquot and the shadow structure that its own copies are not |
| * out of date. Because the hook code runs in a different process context from |
| * the scrub code and the scrub state flags are not accessed atomically, |
| * failures in the hook code must abort the iscan and the scrubber must notice |
| * the aborted scan and set the incomplete flag. |
| * |
| * Note that we use srcu notifier hooks to minimize the overhead when live |
| * quotacheck is /not/ running. |
| */ |
| |
| /* Update an incore dquot counter information from a live update. */ |
| static int |
| xqcheck_update_incore_counts( |
| struct xqcheck *xqc, |
| struct xfarray *counts, |
| xfs_dqid_t id, |
| int64_t inodes, |
| int64_t nblks, |
| int64_t rtblks) |
| { |
| struct xqcheck_dquot xcdq; |
| int error; |
| |
| error = xfarray_load_sparse(counts, id, &xcdq); |
| if (error) |
| return error; |
| |
| xcdq.flags |= XQCHECK_DQUOT_WRITTEN; |
| xcdq.icount += inodes; |
| xcdq.bcount += nblks; |
| xcdq.rtbcount += rtblks; |
| |
| error = xfarray_store(counts, id, &xcdq); |
| if (error == -EFBIG) { |
| /* |
| * EFBIG means we tried to store data at too high a byte offset |
| * in the sparse array. IOWs, we cannot complete the check and |
| * must notify userspace that the check was incomplete. |
| */ |
| error = -ECANCELED; |
| } |
| return error; |
| } |
| |
| /* Decide if this is the shadow dquot accounting structure for a transaction. */ |
| static int |
| xqcheck_dqacct_obj_cmpfn( |
| struct rhashtable_compare_arg *arg, |
| const void *obj) |
| { |
| const uintptr_t *tx_idp = arg->key; |
| const struct xqcheck_dqacct *dqa = obj; |
| |
| if (dqa->tx_id != *tx_idp) |
| return 1; |
| return 0; |
| } |
| |
| static const struct rhashtable_params xqcheck_dqacct_hash_params = { |
| .min_size = 32, |
| .key_len = sizeof(uintptr_t), |
| .key_offset = offsetof(struct xqcheck_dqacct, tx_id), |
| .head_offset = offsetof(struct xqcheck_dqacct, hash), |
| .automatic_shrinking = true, |
| .obj_cmpfn = xqcheck_dqacct_obj_cmpfn, |
| }; |
| |
| /* Find a shadow dqtrx slot for the given dquot. */ |
| STATIC struct xqcheck_dqtrx * |
| xqcheck_get_dqtrx( |
| struct xqcheck_dqacct *dqa, |
| xfs_dqtype_t q_type, |
| xfs_dqid_t q_id) |
| { |
| int i; |
| |
| for (i = 0; i < XQCHECK_MAX_NR_DQTRXS; i++) { |
| if (dqa->dqtrx[i].q_type == 0 || |
| (dqa->dqtrx[i].q_type == q_type && |
| dqa->dqtrx[i].q_id == q_id)) |
| return &dqa->dqtrx[i]; |
| } |
| |
| return NULL; |
| } |
| |
| /* |
| * Create and fill out a quota delta tracking structure to shadow the updates |
| * going on in the regular quota code. |
| */ |
| static int |
| xqcheck_mod_live_ino_dqtrx( |
| struct notifier_block *nb, |
| unsigned long action, |
| void *data) |
| { |
| struct xfs_mod_ino_dqtrx_params *p = data; |
| struct xqcheck *xqc; |
| struct xqcheck_dqacct *dqa; |
| struct xqcheck_dqtrx *dqtrx; |
| int error; |
| |
| xqc = container_of(nb, struct xqcheck, qhook.mod_hook.nb); |
| |
| /* Skip quota reservation fields. */ |
| switch (action) { |
| case XFS_TRANS_DQ_BCOUNT: |
| case XFS_TRANS_DQ_DELBCOUNT: |
| case XFS_TRANS_DQ_ICOUNT: |
| case XFS_TRANS_DQ_RTBCOUNT: |
| case XFS_TRANS_DQ_DELRTBCOUNT: |
| break; |
| default: |
| return NOTIFY_DONE; |
| } |
| |
| /* Ignore dqtrx updates for quota types we don't care about. */ |
| switch (p->q_type) { |
| case XFS_DQTYPE_USER: |
| if (!xqc->ucounts) |
| return NOTIFY_DONE; |
| break; |
| case XFS_DQTYPE_GROUP: |
| if (!xqc->gcounts) |
| return NOTIFY_DONE; |
| break; |
| case XFS_DQTYPE_PROJ: |
| if (!xqc->pcounts) |
| return NOTIFY_DONE; |
| break; |
| default: |
| return NOTIFY_DONE; |
| } |
| |
| /* Skip inodes that haven't been scanned yet. */ |
| if (!xchk_iscan_want_live_update(&xqc->iscan, p->ino)) |
| return NOTIFY_DONE; |
| |
| /* Make a shadow quota accounting tracker for this transaction. */ |
| mutex_lock(&xqc->lock); |
| dqa = rhashtable_lookup_fast(&xqc->shadow_dquot_acct, &p->tx_id, |
| xqcheck_dqacct_hash_params); |
| if (!dqa) { |
| dqa = kzalloc(sizeof(struct xqcheck_dqacct), XCHK_GFP_FLAGS); |
| if (!dqa) |
| goto out_abort; |
| |
| dqa->tx_id = p->tx_id; |
| error = rhashtable_insert_fast(&xqc->shadow_dquot_acct, |
| &dqa->hash, xqcheck_dqacct_hash_params); |
| if (error) |
| goto out_abort; |
| } |
| |
| /* Find the shadow dqtrx (or an empty slot) here. */ |
| dqtrx = xqcheck_get_dqtrx(dqa, p->q_type, p->q_id); |
| if (!dqtrx) |
| goto out_abort; |
| if (dqtrx->q_type == 0) { |
| dqtrx->q_type = p->q_type; |
| dqtrx->q_id = p->q_id; |
| dqa->refcount++; |
| } |
| |
| /* Update counter */ |
| switch (action) { |
| case XFS_TRANS_DQ_BCOUNT: |
| dqtrx->bcount_delta += p->delta; |
| break; |
| case XFS_TRANS_DQ_DELBCOUNT: |
| dqtrx->delbcnt_delta += p->delta; |
| break; |
| case XFS_TRANS_DQ_ICOUNT: |
| dqtrx->icount_delta += p->delta; |
| break; |
| case XFS_TRANS_DQ_RTBCOUNT: |
| dqtrx->rtbcount_delta += p->delta; |
| break; |
| case XFS_TRANS_DQ_DELRTBCOUNT: |
| dqtrx->delrtb_delta += p->delta; |
| break; |
| } |
| |
| mutex_unlock(&xqc->lock); |
| return NOTIFY_DONE; |
| |
| out_abort: |
| xchk_iscan_abort(&xqc->iscan); |
| mutex_unlock(&xqc->lock); |
| return NOTIFY_DONE; |
| } |
| |
| /* |
| * Apply the transaction quota deltas to our shadow quota accounting info when |
| * the regular quota code are doing the same. |
| */ |
| static int |
| xqcheck_apply_live_dqtrx( |
| struct notifier_block *nb, |
| unsigned long action, |
| void *data) |
| { |
| struct xfs_apply_dqtrx_params *p = data; |
| struct xqcheck *xqc; |
| struct xqcheck_dqacct *dqa; |
| struct xqcheck_dqtrx *dqtrx; |
| struct xfarray *counts; |
| int error; |
| |
| xqc = container_of(nb, struct xqcheck, qhook.apply_hook.nb); |
| |
| /* Map the dquot type to an incore counter object. */ |
| switch (p->q_type) { |
| case XFS_DQTYPE_USER: |
| counts = xqc->ucounts; |
| break; |
| case XFS_DQTYPE_GROUP: |
| counts = xqc->gcounts; |
| break; |
| case XFS_DQTYPE_PROJ: |
| counts = xqc->pcounts; |
| break; |
| default: |
| return NOTIFY_DONE; |
| } |
| |
| if (xchk_iscan_aborted(&xqc->iscan) || counts == NULL) |
| return NOTIFY_DONE; |
| |
| /* |
| * Find the shadow dqtrx for this transaction and dquot, if any deltas |
| * need to be applied here. If not, we're finished early. |
| */ |
| mutex_lock(&xqc->lock); |
| dqa = rhashtable_lookup_fast(&xqc->shadow_dquot_acct, &p->tx_id, |
| xqcheck_dqacct_hash_params); |
| if (!dqa) |
| goto out_unlock; |
| dqtrx = xqcheck_get_dqtrx(dqa, p->q_type, p->q_id); |
| if (!dqtrx || dqtrx->q_type == 0) |
| goto out_unlock; |
| |
| /* Update our shadow dquot if we're committing. */ |
| if (action == XFS_APPLY_DQTRX_COMMIT) { |
| error = xqcheck_update_incore_counts(xqc, counts, p->q_id, |
| dqtrx->icount_delta, |
| dqtrx->bcount_delta + dqtrx->delbcnt_delta, |
| dqtrx->rtbcount_delta + dqtrx->delrtb_delta); |
| if (error) |
| goto out_abort; |
| } |
| |
| /* Free the shadow accounting structure if that was the last user. */ |
| dqa->refcount--; |
| if (dqa->refcount == 0) { |
| error = rhashtable_remove_fast(&xqc->shadow_dquot_acct, |
| &dqa->hash, xqcheck_dqacct_hash_params); |
| if (error) |
| goto out_abort; |
| xqcheck_dqacct_free(dqa, NULL); |
| } |
| |
| mutex_unlock(&xqc->lock); |
| return NOTIFY_DONE; |
| |
| out_abort: |
| xchk_iscan_abort(&xqc->iscan); |
| out_unlock: |
| mutex_unlock(&xqc->lock); |
| return NOTIFY_DONE; |
| } |
| |
| /* Record this inode's quota usage in our shadow quota counter data. */ |
| STATIC int |
| xqcheck_collect_inode( |
| struct xqcheck *xqc, |
| struct xfs_inode *ip) |
| { |
| struct xfs_trans *tp = xqc->sc->tp; |
| xfs_filblks_t nblks, rtblks; |
| uint ilock_flags = 0; |
| xfs_dqid_t id; |
| bool isreg = S_ISREG(VFS_I(ip)->i_mode); |
| int error = 0; |
| |
| if (xfs_is_metadir_inode(ip) || |
| xfs_is_quota_inode(&tp->t_mountp->m_sb, ip->i_ino)) { |
| /* |
| * Quota files are never counted towards quota, so we do not |
| * need to take the lock. Files do not switch between the |
| * metadata and regular directory trees without a reallocation, |
| * so we do not need to ILOCK them either. |
| */ |
| xchk_iscan_mark_visited(&xqc->iscan, ip); |
| return 0; |
| } |
| |
| /* Figure out the data / rt device block counts. */ |
| xfs_ilock(ip, XFS_IOLOCK_SHARED); |
| if (isreg) |
| xfs_ilock(ip, XFS_MMAPLOCK_SHARED); |
| if (XFS_IS_REALTIME_INODE(ip)) { |
| /* |
| * Read in the data fork for rt files so that _count_blocks |
| * can count the number of blocks allocated from the rt volume. |
| * Inodes do not track that separately. |
| */ |
| ilock_flags = xfs_ilock_data_map_shared(ip); |
| error = xfs_iread_extents(tp, ip, XFS_DATA_FORK); |
| if (error) |
| goto out_abort; |
| } else { |
| ilock_flags = XFS_ILOCK_SHARED; |
| xfs_ilock(ip, XFS_ILOCK_SHARED); |
| } |
| xfs_inode_count_blocks(tp, ip, &nblks, &rtblks); |
| |
| if (xchk_iscan_aborted(&xqc->iscan)) { |
| error = -ECANCELED; |
| goto out_incomplete; |
| } |
| |
| /* Update the shadow dquot counters. */ |
| mutex_lock(&xqc->lock); |
| if (xqc->ucounts) { |
| id = xfs_qm_id_for_quotatype(ip, XFS_DQTYPE_USER); |
| error = xqcheck_update_incore_counts(xqc, xqc->ucounts, id, 1, |
| nblks, rtblks); |
| if (error) |
| goto out_mutex; |
| } |
| |
| if (xqc->gcounts) { |
| id = xfs_qm_id_for_quotatype(ip, XFS_DQTYPE_GROUP); |
| error = xqcheck_update_incore_counts(xqc, xqc->gcounts, id, 1, |
| nblks, rtblks); |
| if (error) |
| goto out_mutex; |
| } |
| |
| if (xqc->pcounts) { |
| id = xfs_qm_id_for_quotatype(ip, XFS_DQTYPE_PROJ); |
| error = xqcheck_update_incore_counts(xqc, xqc->pcounts, id, 1, |
| nblks, rtblks); |
| if (error) |
| goto out_mutex; |
| } |
| mutex_unlock(&xqc->lock); |
| |
| xchk_iscan_mark_visited(&xqc->iscan, ip); |
| goto out_ilock; |
| |
| out_mutex: |
| mutex_unlock(&xqc->lock); |
| out_abort: |
| xchk_iscan_abort(&xqc->iscan); |
| out_incomplete: |
| xchk_set_incomplete(xqc->sc); |
| out_ilock: |
| xfs_iunlock(ip, ilock_flags); |
| if (isreg) |
| xfs_iunlock(ip, XFS_MMAPLOCK_SHARED); |
| xfs_iunlock(ip, XFS_IOLOCK_SHARED); |
| return error; |
| } |
| |
| /* Walk all the allocated inodes and run a quota scan on them. */ |
| STATIC int |
| xqcheck_collect_counts( |
| struct xqcheck *xqc) |
| { |
| struct xfs_scrub *sc = xqc->sc; |
| struct xfs_inode *ip; |
| int error; |
| |
| /* |
| * Set up for a potentially lengthy filesystem scan by reducing our |
| * transaction resource usage for the duration. Specifically: |
| * |
| * Cancel the transaction to release the log grant space while we scan |
| * the filesystem. |
| * |
| * Create a new empty transaction to eliminate the possibility of the |
| * inode scan deadlocking on cyclical metadata. |
| * |
| * We pass the empty transaction to the file scanning function to avoid |
| * repeatedly cycling empty transactions. This can be done without |
| * risk of deadlock between sb_internal and the IOLOCK (we take the |
| * IOLOCK to quiesce the file before scanning) because empty |
| * transactions do not take sb_internal. |
| */ |
| xchk_trans_cancel(sc); |
| error = xchk_trans_alloc_empty(sc); |
| if (error) |
| return error; |
| |
| while ((error = xchk_iscan_iter(&xqc->iscan, &ip)) == 1) { |
| error = xqcheck_collect_inode(xqc, ip); |
| xchk_irele(sc, ip); |
| if (error) |
| break; |
| |
| if (xchk_should_terminate(sc, &error)) |
| break; |
| } |
| xchk_iscan_iter_finish(&xqc->iscan); |
| if (error) { |
| xchk_set_incomplete(sc); |
| /* |
| * If we couldn't grab an inode that was busy with a state |
| * change, change the error code so that we exit to userspace |
| * as quickly as possible. |
| */ |
| if (error == -EBUSY) |
| return -ECANCELED; |
| return error; |
| } |
| |
| /* |
| * Switch out for a real transaction in preparation for building a new |
| * tree. |
| */ |
| xchk_trans_cancel(sc); |
| return xchk_setup_fs(sc); |
| } |
| |
| /* |
| * Part 2: Comparing dquot resource counters. Walk each xfs_dquot, comparing |
| * the resource usage counters against our shadow dquots; and then walk each |
| * shadow dquot (that wasn't covered in the first part), comparing it against |
| * the xfs_dquot. |
| */ |
| |
| /* |
| * Check the dquot data against what we observed. Caller must hold the dquot |
| * lock. |
| */ |
| STATIC int |
| xqcheck_compare_dquot( |
| struct xqcheck *xqc, |
| xfs_dqtype_t dqtype, |
| struct xfs_dquot *dq) |
| { |
| struct xqcheck_dquot xcdq; |
| struct xfarray *counts = xqcheck_counters_for(xqc, dqtype); |
| int error; |
| |
| if (xchk_iscan_aborted(&xqc->iscan)) { |
| xchk_set_incomplete(xqc->sc); |
| return -ECANCELED; |
| } |
| |
| mutex_lock(&xqc->lock); |
| error = xfarray_load_sparse(counts, dq->q_id, &xcdq); |
| if (error) |
| goto out_unlock; |
| |
| if (xcdq.icount != dq->q_ino.count) |
| xchk_qcheck_set_corrupt(xqc->sc, dqtype, dq->q_id); |
| |
| if (xcdq.bcount != dq->q_blk.count) |
| xchk_qcheck_set_corrupt(xqc->sc, dqtype, dq->q_id); |
| |
| if (xcdq.rtbcount != dq->q_rtb.count) |
| xchk_qcheck_set_corrupt(xqc->sc, dqtype, dq->q_id); |
| |
| xcdq.flags |= (XQCHECK_DQUOT_COMPARE_SCANNED | XQCHECK_DQUOT_WRITTEN); |
| error = xfarray_store(counts, dq->q_id, &xcdq); |
| if (error == -EFBIG) { |
| /* |
| * EFBIG means we tried to store data at too high a byte offset |
| * in the sparse array. IOWs, we cannot complete the check and |
| * must notify userspace that the check was incomplete. This |
| * should never happen outside of the collection phase. |
| */ |
| xchk_set_incomplete(xqc->sc); |
| error = -ECANCELED; |
| } |
| mutex_unlock(&xqc->lock); |
| if (error) |
| return error; |
| |
| if (xqc->sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT) |
| return -ECANCELED; |
| |
| return 0; |
| |
| out_unlock: |
| mutex_unlock(&xqc->lock); |
| return error; |
| } |
| |
| /* |
| * Walk all the observed dquots, and make sure there's a matching incore |
| * dquot and that its counts match ours. |
| */ |
| STATIC int |
| xqcheck_walk_observations( |
| struct xqcheck *xqc, |
| xfs_dqtype_t dqtype) |
| { |
| struct xqcheck_dquot xcdq; |
| struct xfs_dquot *dq; |
| struct xfarray *counts = xqcheck_counters_for(xqc, dqtype); |
| xfarray_idx_t cur = XFARRAY_CURSOR_INIT; |
| int error; |
| |
| mutex_lock(&xqc->lock); |
| while ((error = xfarray_iter(counts, &cur, &xcdq)) == 1) { |
| xfs_dqid_t id = cur - 1; |
| |
| if (xcdq.flags & XQCHECK_DQUOT_COMPARE_SCANNED) |
| continue; |
| |
| mutex_unlock(&xqc->lock); |
| |
| error = xfs_qm_dqget(xqc->sc->mp, id, dqtype, false, &dq); |
| if (error == -ENOENT) { |
| xchk_qcheck_set_corrupt(xqc->sc, dqtype, id); |
| return 0; |
| } |
| if (error) |
| return error; |
| |
| error = xqcheck_compare_dquot(xqc, dqtype, dq); |
| xfs_qm_dqput(dq); |
| if (error) |
| return error; |
| |
| if (xchk_should_terminate(xqc->sc, &error)) |
| return error; |
| |
| mutex_lock(&xqc->lock); |
| } |
| mutex_unlock(&xqc->lock); |
| |
| return error; |
| } |
| |
| /* Compare the quota counters we observed against the live dquots. */ |
| STATIC int |
| xqcheck_compare_dqtype( |
| struct xqcheck *xqc, |
| xfs_dqtype_t dqtype) |
| { |
| struct xchk_dqiter cursor = { }; |
| struct xfs_scrub *sc = xqc->sc; |
| struct xfs_dquot *dq; |
| int error; |
| |
| if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT) |
| return 0; |
| |
| /* If the quota CHKD flag is cleared, we need to repair this quota. */ |
| if (!(xfs_quota_chkd_flag(dqtype) & sc->mp->m_qflags)) { |
| xchk_qcheck_set_corrupt(xqc->sc, dqtype, 0); |
| return 0; |
| } |
| |
| /* Compare what we observed against the actual dquots. */ |
| xchk_dqiter_init(&cursor, sc, dqtype); |
| while ((error = xchk_dquot_iter(&cursor, &dq)) == 1) { |
| error = xqcheck_compare_dquot(xqc, dqtype, dq); |
| xfs_qm_dqput(dq); |
| if (error) |
| break; |
| } |
| if (error) |
| return error; |
| |
| /* Walk all the observed dquots and compare to the incore ones. */ |
| return xqcheck_walk_observations(xqc, dqtype); |
| } |
| |
| /* Tear down everything associated with a quotacheck. */ |
| static void |
| xqcheck_teardown_scan( |
| void *priv) |
| { |
| struct xqcheck *xqc = priv; |
| struct xfs_quotainfo *qi = xqc->sc->mp->m_quotainfo; |
| |
| /* Discourage any hook functions that might be running. */ |
| xchk_iscan_abort(&xqc->iscan); |
| |
| /* |
| * As noted above, the apply hook is responsible for cleaning up the |
| * shadow dquot accounting data when a transaction completes. The mod |
| * hook must be removed before the apply hook so that we don't |
| * mistakenly leave an active shadow account for the mod hook to get |
| * its hands on. No hooks should be running after these functions |
| * return. |
| */ |
| xfs_dqtrx_hook_del(qi, &xqc->qhook); |
| |
| if (xqc->shadow_dquot_acct.key_len) { |
| rhashtable_free_and_destroy(&xqc->shadow_dquot_acct, |
| xqcheck_dqacct_free, NULL); |
| xqc->shadow_dquot_acct.key_len = 0; |
| } |
| |
| if (xqc->pcounts) { |
| xfarray_destroy(xqc->pcounts); |
| xqc->pcounts = NULL; |
| } |
| |
| if (xqc->gcounts) { |
| xfarray_destroy(xqc->gcounts); |
| xqc->gcounts = NULL; |
| } |
| |
| if (xqc->ucounts) { |
| xfarray_destroy(xqc->ucounts); |
| xqc->ucounts = NULL; |
| } |
| |
| xchk_iscan_teardown(&xqc->iscan); |
| mutex_destroy(&xqc->lock); |
| xqc->sc = NULL; |
| } |
| |
| /* |
| * Scan all inodes in the entire filesystem to generate quota counter data. |
| * If the scan is successful, the quota data will be left alive for a repair. |
| * If any error occurs, we'll tear everything down. |
| */ |
| STATIC int |
| xqcheck_setup_scan( |
| struct xfs_scrub *sc, |
| struct xqcheck *xqc) |
| { |
| char *descr; |
| struct xfs_quotainfo *qi = sc->mp->m_quotainfo; |
| unsigned long long max_dquots = XFS_DQ_ID_MAX + 1ULL; |
| int error; |
| |
| ASSERT(xqc->sc == NULL); |
| xqc->sc = sc; |
| |
| mutex_init(&xqc->lock); |
| |
| /* Retry iget every tenth of a second for up to 30 seconds. */ |
| xchk_iscan_start(sc, 30000, 100, &xqc->iscan); |
| |
| error = -ENOMEM; |
| if (xfs_this_quota_on(sc->mp, XFS_DQTYPE_USER)) { |
| descr = xchk_xfile_descr(sc, "user dquot records"); |
| error = xfarray_create(descr, max_dquots, |
| sizeof(struct xqcheck_dquot), &xqc->ucounts); |
| kfree(descr); |
| if (error) |
| goto out_teardown; |
| } |
| |
| if (xfs_this_quota_on(sc->mp, XFS_DQTYPE_GROUP)) { |
| descr = xchk_xfile_descr(sc, "group dquot records"); |
| error = xfarray_create(descr, max_dquots, |
| sizeof(struct xqcheck_dquot), &xqc->gcounts); |
| kfree(descr); |
| if (error) |
| goto out_teardown; |
| } |
| |
| if (xfs_this_quota_on(sc->mp, XFS_DQTYPE_PROJ)) { |
| descr = xchk_xfile_descr(sc, "project dquot records"); |
| error = xfarray_create(descr, max_dquots, |
| sizeof(struct xqcheck_dquot), &xqc->pcounts); |
| kfree(descr); |
| if (error) |
| goto out_teardown; |
| } |
| |
| /* |
| * Set up hash table to map transactions to our internal shadow dqtrx |
| * structures. |
| */ |
| error = rhashtable_init(&xqc->shadow_dquot_acct, |
| &xqcheck_dqacct_hash_params); |
| if (error) |
| goto out_teardown; |
| |
| /* |
| * Hook into the quota code. The hook only triggers for inodes that |
| * were already scanned, and the scanner thread takes each inode's |
| * ILOCK, which means that any in-progress inode updates will finish |
| * before we can scan the inode. |
| * |
| * The apply hook (which removes the shadow dquot accounting struct) |
| * must be installed before the mod hook so that we never fail to catch |
| * the end of a quota update sequence and leave stale shadow data. |
| */ |
| ASSERT(sc->flags & XCHK_FSGATES_QUOTA); |
| xfs_dqtrx_hook_setup(&xqc->qhook, xqcheck_mod_live_ino_dqtrx, |
| xqcheck_apply_live_dqtrx); |
| |
| error = xfs_dqtrx_hook_add(qi, &xqc->qhook); |
| if (error) |
| goto out_teardown; |
| |
| /* Use deferred cleanup to pass the quota count data to repair. */ |
| sc->buf_cleanup = xqcheck_teardown_scan; |
| return 0; |
| |
| out_teardown: |
| xqcheck_teardown_scan(xqc); |
| return error; |
| } |
| |
| /* Scrub all counters for a given quota type. */ |
| int |
| xchk_quotacheck( |
| struct xfs_scrub *sc) |
| { |
| struct xqcheck *xqc = sc->buf; |
| int error = 0; |
| |
| /* Check quota counters on the live filesystem. */ |
| error = xqcheck_setup_scan(sc, xqc); |
| if (error) |
| return error; |
| |
| /* Walk all inodes, picking up quota information. */ |
| error = xqcheck_collect_counts(xqc); |
| if (!xchk_xref_process_error(sc, 0, 0, &error)) |
| return error; |
| |
| /* Fail fast if we're not playing with a full dataset. */ |
| if (xchk_iscan_aborted(&xqc->iscan)) |
| xchk_set_incomplete(sc); |
| if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_INCOMPLETE) |
| return 0; |
| |
| /* Compare quota counters. */ |
| if (xqc->ucounts) { |
| error = xqcheck_compare_dqtype(xqc, XFS_DQTYPE_USER); |
| if (!xchk_xref_process_error(sc, 0, 0, &error)) |
| return error; |
| } |
| if (xqc->gcounts) { |
| error = xqcheck_compare_dqtype(xqc, XFS_DQTYPE_GROUP); |
| if (!xchk_xref_process_error(sc, 0, 0, &error)) |
| return error; |
| } |
| if (xqc->pcounts) { |
| error = xqcheck_compare_dqtype(xqc, XFS_DQTYPE_PROJ); |
| if (!xchk_xref_process_error(sc, 0, 0, &error)) |
| return error; |
| } |
| |
| /* Check one last time for an incomplete dataset. */ |
| if (xchk_iscan_aborted(&xqc->iscan)) |
| xchk_set_incomplete(sc); |
| |
| return 0; |
| } |