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