Commit 98bdbf60 authored by Chandan Babu R's avatar Chandan Babu R
Browse files

Merge tag 'repair-quota-6.8_2023-12-15' of...

Merge tag 'repair-quota-6.8_2023-12-15' of https://git.kernel.org/pub/scm/linux/kernel/git/djwong/xfs-linux

 into xfs-6.8-mergeB

xfs: online repair of quota and rt metadata files

XFS stores quota records and free space bitmap information in files.
Add the necessary infrastructure to enable repairing metadata inodes and
their forks, and then make it so that we can repair the file metadata
for the rtbitmap.  Repairing the bitmap contents (and the summary file)
is left for subsequent patchsets.

We also add the ability to repair file metadata the quota files.  As
part of these repairs, we also reinitialize the ondisk dquot records as
necessary to get the incore dquots working.  We can also correct
obviously bad dquot record attributes, but we leave checking the
resource usage counts for the next patchsets.

Signed-off-by: default avatarDarrick J. Wong <djwong@kernel.org>
Signed-off-by: default avatarChandan Babu R <chandanbabu@kernel.org>

* tag 'repair-quota-6.8_2023-12-15' of https://git.kernel.org/pub/scm/linux/kernel/git/djwong/xfs-linux:
  xfs: repair quotas
  xfs: improve dquot iteration for scrub
  xfs: check dquot resource timers
  xfs: check the ondisk space mapping behind a dquot
parents 5bb4ad95 a5b91555
Loading
Loading
Loading
Loading
+8 −1
Original line number Diff line number Diff line
@@ -176,7 +176,10 @@ xfs-$(CONFIG_XFS_RT) += $(addprefix scrub/, \
				   rtsummary.o \
				   )

xfs-$(CONFIG_XFS_QUOTA)		+= scrub/quota.o
xfs-$(CONFIG_XFS_QUOTA)		+= $(addprefix scrub/, \
				   dqiterate.o \
				   quota.o \
				   )

# online repair
ifeq ($(CONFIG_XFS_ONLINE_REPAIR),y)
@@ -196,5 +199,9 @@ xfs-y += $(addprefix scrub/, \
xfs-$(CONFIG_XFS_RT)		+= $(addprefix scrub/, \
				   rtbitmap_repair.o \
				   )

xfs-$(CONFIG_XFS_QUOTA)		+= $(addprefix scrub/, \
				   quota_repair.o \
				   )
endif
endif
+3 −0
Original line number Diff line number Diff line
@@ -1272,6 +1272,9 @@ static inline time64_t xfs_dq_bigtime_to_unix(uint32_t ondisk_seconds)
#define XFS_DQ_GRACE_MIN		((int64_t)0)
#define XFS_DQ_GRACE_MAX		((int64_t)U32_MAX)

/* Maximum id value for a quota record */
#define XFS_DQ_ID_MAX			(U32_MAX)

/*
 * This is the main portion of the on-disk representation of quota information
 * for a user.  We pad this with some more expansion room to construct the on
+211 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (C) 2023 Oracle.  All Rights Reserved.
 * Author: Darrick J. Wong <djwong@kernel.org>
 */
#include "xfs.h"
#include "xfs_fs.h"
#include "xfs_shared.h"
#include "xfs_bit.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_bmap.h"
#include "scrub/scrub.h"
#include "scrub/common.h"
#include "scrub/quota.h"
#include "scrub/trace.h"

/* Initialize a dquot iteration cursor. */
void
xchk_dqiter_init(
	struct xchk_dqiter	*cursor,
	struct xfs_scrub	*sc,
	xfs_dqtype_t		dqtype)
{
	cursor->sc = sc;
	cursor->bmap.br_startoff = NULLFILEOFF;
	cursor->dqtype = dqtype & XFS_DQTYPE_REC_MASK;
	cursor->quota_ip = xfs_quota_inode(sc->mp, cursor->dqtype);
	cursor->id = 0;
}

/*
 * Ensure that the cached data fork mapping for the dqiter cursor is fresh and
 * covers the dquot pointed to by the scan cursor.
 */
STATIC int
xchk_dquot_iter_revalidate_bmap(
	struct xchk_dqiter	*cursor)
{
	struct xfs_quotainfo	*qi = cursor->sc->mp->m_quotainfo;
	struct xfs_ifork	*ifp = xfs_ifork_ptr(cursor->quota_ip,
								XFS_DATA_FORK);
	xfs_fileoff_t		fileoff;
	xfs_dqid_t		this_id = cursor->id;
	int			nmaps = 1;
	int			error;

	fileoff = this_id / qi->qi_dqperchunk;

	/*
	 * If we have a mapping for cursor->id and it's still fresh, there's
	 * no need to reread the bmbt.
	 */
	if (cursor->bmap.br_startoff != NULLFILEOFF &&
	    cursor->if_seq == ifp->if_seq &&
	    cursor->bmap.br_startoff + cursor->bmap.br_blockcount > fileoff)
		return 0;

	/* Look up the data fork mapping for the dquot id of interest. */
	error = xfs_bmapi_read(cursor->quota_ip, fileoff,
			XFS_MAX_FILEOFF - fileoff, &cursor->bmap, &nmaps, 0);
	if (error)
		return error;
	if (!nmaps) {
		ASSERT(nmaps > 0);
		return -EFSCORRUPTED;
	}
	if (cursor->bmap.br_startoff > fileoff) {
		ASSERT(cursor->bmap.br_startoff == fileoff);
		return -EFSCORRUPTED;
	}

	cursor->if_seq = ifp->if_seq;
	trace_xchk_dquot_iter_revalidate_bmap(cursor, cursor->id);
	return 0;
}

/* Advance the dqiter cursor to the next non-sparse region of the quota file. */
STATIC int
xchk_dquot_iter_advance_bmap(
	struct xchk_dqiter	*cursor,
	uint64_t		*next_ondisk_id)
{
	struct xfs_quotainfo	*qi = cursor->sc->mp->m_quotainfo;
	struct xfs_ifork	*ifp = xfs_ifork_ptr(cursor->quota_ip,
								XFS_DATA_FORK);
	xfs_fileoff_t		fileoff;
	uint64_t		next_id;
	int			nmaps = 1;
	int			error;

	/* Find the dquot id for the next non-hole mapping. */
	do {
		fileoff = cursor->bmap.br_startoff + cursor->bmap.br_blockcount;
		if (fileoff > XFS_DQ_ID_MAX / qi->qi_dqperchunk) {
			/* The hole goes beyond the max dquot id, we're done */
			*next_ondisk_id = -1ULL;
			return 0;
		}

		error = xfs_bmapi_read(cursor->quota_ip, fileoff,
				XFS_MAX_FILEOFF - fileoff, &cursor->bmap,
				&nmaps, 0);
		if (error)
			return error;
		if (!nmaps) {
			/* Must have reached the end of the mappings. */
			*next_ondisk_id = -1ULL;
			return 0;
		}
		if (cursor->bmap.br_startoff > fileoff) {
			ASSERT(cursor->bmap.br_startoff == fileoff);
			return -EFSCORRUPTED;
		}
	} while (!xfs_bmap_is_real_extent(&cursor->bmap));

	next_id = cursor->bmap.br_startoff * qi->qi_dqperchunk;
	if (next_id > XFS_DQ_ID_MAX) {
		/* The hole goes beyond the max dquot id, we're done */
		*next_ondisk_id = -1ULL;
		return 0;
	}

	/* Propose jumping forward to the dquot in the next allocated block. */
	*next_ondisk_id = next_id;
	cursor->if_seq = ifp->if_seq;
	trace_xchk_dquot_iter_advance_bmap(cursor, *next_ondisk_id);
	return 0;
}

/*
 * Find the id of the next highest incore dquot.  Normally this will correspond
 * exactly with the quota file block mappings, but repair might have erased a
 * mapping because it was crosslinked; in that case, we need to re-allocate the
 * space so that we can reset q_blkno.
 */
STATIC void
xchk_dquot_iter_advance_incore(
	struct xchk_dqiter	*cursor,
	uint64_t		*next_incore_id)
{
	struct xfs_quotainfo	*qi = cursor->sc->mp->m_quotainfo;
	struct radix_tree_root	*tree = xfs_dquot_tree(qi, cursor->dqtype);
	struct xfs_dquot	*dq;
	unsigned int		nr_found;

	*next_incore_id = -1ULL;

	mutex_lock(&qi->qi_tree_lock);
	nr_found = radix_tree_gang_lookup(tree, (void **)&dq, cursor->id, 1);
	if (nr_found)
		*next_incore_id = dq->q_id;
	mutex_unlock(&qi->qi_tree_lock);

	trace_xchk_dquot_iter_advance_incore(cursor, *next_incore_id);
}

/*
 * Walk all incore dquots of this filesystem.  Caller must set *@cursorp to
 * zero before the first call, and must not hold the quota file ILOCK.
 * Returns 1 and a valid *@dqpp; 0 and *@dqpp == NULL when there are no more
 * dquots to iterate; or a negative errno.
 */
int
xchk_dquot_iter(
	struct xchk_dqiter	*cursor,
	struct xfs_dquot	**dqpp)
{
	struct xfs_mount	*mp = cursor->sc->mp;
	struct xfs_dquot	*dq = NULL;
	uint64_t		next_ondisk, next_incore = -1ULL;
	unsigned int		lock_mode;
	int			error = 0;

	if (cursor->id > XFS_DQ_ID_MAX)
		return 0;
	next_ondisk = cursor->id;

	/* Revalidate and/or advance the cursor. */
	lock_mode = xfs_ilock_data_map_shared(cursor->quota_ip);
	error = xchk_dquot_iter_revalidate_bmap(cursor);
	if (!error && !xfs_bmap_is_real_extent(&cursor->bmap))
		error = xchk_dquot_iter_advance_bmap(cursor, &next_ondisk);
	xfs_iunlock(cursor->quota_ip, lock_mode);
	if (error)
		return error;

	if (next_ondisk > cursor->id)
		xchk_dquot_iter_advance_incore(cursor, &next_incore);

	/* Pick the next dquot in the sequence and return it. */
	cursor->id = min(next_ondisk, next_incore);
	if (cursor->id > XFS_DQ_ID_MAX)
		return 0;

	trace_xchk_dquot_iter(cursor, cursor->id);

	error = xfs_qm_dqget(mp, cursor->id, cursor->dqtype, false, &dq);
	if (error)
		return error;

	cursor->id = dq->q_id + 1;
	*dqpp = dq;
	return 1;
}
+96 −11
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@
#include "xfs.h"
#include "xfs_fs.h"
#include "xfs_shared.h"
#include "xfs_bit.h"
#include "xfs_format.h"
#include "xfs_trans_resv.h"
#include "xfs_mount.h"
@@ -17,9 +18,10 @@
#include "xfs_bmap.h"
#include "scrub/scrub.h"
#include "scrub/common.h"
#include "scrub/quota.h"

/* Convert a scrub type code to a DQ flag, or return 0 if error. */
static inline xfs_dqtype_t
xfs_dqtype_t
xchk_quota_to_dqtype(
	struct xfs_scrub	*sc)
{
@@ -75,14 +77,70 @@ struct xchk_quota_info {
	xfs_dqid_t		last_id;
};

/* There's a written block backing this dquot, right? */
STATIC int
xchk_quota_item_bmap(
	struct xfs_scrub	*sc,
	struct xfs_dquot	*dq,
	xfs_fileoff_t		offset)
{
	struct xfs_bmbt_irec	irec;
	struct xfs_mount	*mp = sc->mp;
	int			nmaps = 1;
	int			error;

	if (!xfs_verify_fileoff(mp, offset)) {
		xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset);
		return 0;
	}

	if (dq->q_fileoffset != offset) {
		xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset);
		return 0;
	}

	error = xfs_bmapi_read(sc->ip, offset, 1, &irec, &nmaps, 0);
	if (error)
		return error;

	if (nmaps != 1) {
		xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset);
		return 0;
	}

	if (!xfs_verify_fsbno(mp, irec.br_startblock))
		xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset);
	if (XFS_FSB_TO_DADDR(mp, irec.br_startblock) != dq->q_blkno)
		xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset);
	if (!xfs_bmap_is_written_extent(&irec))
		xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset);

	return 0;
}

/* Complain if a quota timer is incorrectly set. */
static inline void
xchk_quota_item_timer(
	struct xfs_scrub		*sc,
	xfs_fileoff_t			offset,
	const struct xfs_dquot_res	*res)
{
	if ((res->softlimit && res->count > res->softlimit) ||
	    (res->hardlimit && res->count > res->hardlimit)) {
		if (!res->timer)
			xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset);
	} else {
		if (res->timer)
			xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset);
	}
}

/* Scrub the fields in an individual quota item. */
STATIC int
xchk_quota_item(
	struct xfs_dquot	*dq,
	xfs_dqtype_t		dqtype,
	void			*priv)
	struct xchk_quota_info	*sqi,
	struct xfs_dquot	*dq)
{
	struct xchk_quota_info	*sqi = priv;
	struct xfs_scrub	*sc = sqi->sc;
	struct xfs_mount	*mp = sc->mp;
	struct xfs_quotainfo	*qi = mp->m_quotainfo;
@@ -93,6 +151,17 @@ xchk_quota_item(
	if (xchk_should_terminate(sc, &error))
		return error;

	/*
	 * We want to validate the bmap record for the storage backing this
	 * dquot, so we need to lock the dquot and the quota file.  For quota
	 * operations, the locking order is first the ILOCK and then the dquot.
	 * However, dqiterate gave us a locked dquot, so drop the dquot lock to
	 * get the ILOCK.
	 */
	xfs_dqunlock(dq);
	xchk_ilock(sc, XFS_ILOCK_SHARED);
	xfs_dqlock(dq);

	/*
	 * Except for the root dquot, the actual dquot we got must either have
	 * the same or higher id as we saw before.
@@ -103,6 +172,11 @@ xchk_quota_item(

	sqi->last_id = dq->q_id;

	error = xchk_quota_item_bmap(sc, dq, offset);
	xchk_iunlock(sc, XFS_ILOCK_SHARED);
	if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, offset, &error))
		return error;

	/*
	 * Warn if the hard limits are larger than the fs.
	 * Administrators can do this, though in production this seems
@@ -166,6 +240,10 @@ xchk_quota_item(
	    dq->q_rtb.count > dq->q_rtb.hardlimit)
		xchk_fblock_set_warning(sc, XFS_DATA_FORK, offset);

	xchk_quota_item_timer(sc, offset, &dq->q_blk);
	xchk_quota_item_timer(sc, offset, &dq->q_ino);
	xchk_quota_item_timer(sc, offset, &dq->q_rtb);

out:
	if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
		return -ECANCELED;
@@ -191,7 +269,7 @@ xchk_quota_data_fork(
		return error;

	/* Check for data fork problems that apply only to quota files. */
	max_dqid_off = ((xfs_dqid_t)-1) / qi->qi_dqperchunk;
	max_dqid_off = XFS_DQ_ID_MAX / qi->qi_dqperchunk;
	ifp = xfs_ifork_ptr(sc->ip, XFS_DATA_FORK);
	for_each_xfs_iext(ifp, &icur, &irec) {
		if (xchk_should_terminate(sc, &error))
@@ -218,9 +296,11 @@ int
xchk_quota(
	struct xfs_scrub	*sc)
{
	struct xchk_quota_info	sqi;
	struct xchk_dqiter	cursor = { };
	struct xchk_quota_info	sqi = { .sc = sc };
	struct xfs_mount	*mp = sc->mp;
	struct xfs_quotainfo	*qi = mp->m_quotainfo;
	struct xfs_dquot	*dq;
	xfs_dqtype_t		dqtype;
	int			error = 0;

@@ -239,10 +319,15 @@ xchk_quota(
	 * functions.
	 */
	xchk_iunlock(sc, sc->ilock_flags);
	sqi.sc = sc;
	sqi.last_id = 0;
	error = xfs_qm_dqiterate(mp, dqtype, xchk_quota_item, &sqi);
	xchk_ilock(sc, XFS_ILOCK_EXCL);

	/* Now look for things that the quota verifiers won't complain about. */
	xchk_dqiter_init(&cursor, sc, dqtype);
	while ((error = xchk_dquot_iter(&cursor, &dq)) == 1) {
		error = xchk_quota_item(&sqi, dq);
		xfs_qm_dqput(dq);
		if (error)
			break;
	}
	if (error == -ECANCELED)
		error = 0;
	if (!xchk_fblock_process_error(sc, XFS_DATA_FORK,

fs/xfs/scrub/quota.h

0 → 100644
+36 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (C) 2018-2023 Oracle.  All Rights Reserved.
 * Author: Darrick J. Wong <djwong@kernel.org>
 */
#ifndef __XFS_SCRUB_QUOTA_H__
#define __XFS_SCRUB_QUOTA_H__

xfs_dqtype_t xchk_quota_to_dqtype(struct xfs_scrub *sc);

/* dquot iteration code */

struct xchk_dqiter {
	struct xfs_scrub	*sc;

	/* Quota file that we're walking. */
	struct xfs_inode	*quota_ip;

	/* Cached data fork mapping for the dquot. */
	struct xfs_bmbt_irec	bmap;

	/* The next dquot to scan. */
	uint64_t		id;

	/* Quota type (user/group/project). */
	xfs_dqtype_t		dqtype;

	/* Data fork sequence number to detect stale mappings. */
	unsigned int		if_seq;
};

void xchk_dqiter_init(struct xchk_dqiter *cursor, struct xfs_scrub *sc,
		xfs_dqtype_t dqtype);
int xchk_dquot_iter(struct xchk_dqiter *cursor, struct xfs_dquot **dqpp);

#endif /* __XFS_SCRUB_QUOTA_H__ */
Loading