linux-stable/fs/xfs/scrub/quotacheck.c
Darrick J. Wong 13af229ee0 xfs: do not count metadata directory files when doing online quotacheck
Previously, we stated that files in the metadata directory tree are not
counted in the dquot information.  Fix the online quotacheck code to
reflect this.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
2024-11-05 13:38:33 -08:00

871 lines
22 KiB
C

// 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;
}