mirror of
https://github.com/torvalds/linux.git
synced 2024-11-26 22:21:42 +00:00
96ed2ae4a9
Use the shadow quota counters that live quotacheck creates to reset the incore dquot counters. Signed-off-by: Darrick J. Wong <djwong@kernel.org> Reviewed-by: Christoph Hellwig <hch@lst.de>
868 lines
22 KiB
C
868 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_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;
|
|
}
|