// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) 2020-2024 Oracle. All Rights Reserved.
* Author: Darrick J. Wong <[email protected]>
*/
#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_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.
*/
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;
}