212 lines
5.7 KiB
C
212 lines
5.7 KiB
C
// 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;
|
|
}
|