1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Copyright (C) 2023 Oracle.  All Rights Reserved.
4 * Author: Darrick J. Wong <djwong@kernel.org>
5 */
6#include "xfs.h"
7#include "xfs_fs.h"
8#include "xfs_shared.h"
9#include "xfs_bit.h"
10#include "xfs_format.h"
11#include "xfs_trans_resv.h"
12#include "xfs_mount.h"
13#include "xfs_log_format.h"
14#include "xfs_trans.h"
15#include "xfs_inode.h"
16#include "xfs_quota.h"
17#include "xfs_qm.h"
18#include "xfs_bmap.h"
19#include "scrub/scrub.h"
20#include "scrub/common.h"
21#include "scrub/quota.h"
22#include "scrub/trace.h"
23
24/* Initialize a dquot iteration cursor. */
25void
26xchk_dqiter_init(
27	struct xchk_dqiter	*cursor,
28	struct xfs_scrub	*sc,
29	xfs_dqtype_t		dqtype)
30{
31	cursor->sc = sc;
32	cursor->bmap.br_startoff = NULLFILEOFF;
33	cursor->dqtype = dqtype & XFS_DQTYPE_REC_MASK;
34	cursor->quota_ip = xfs_quota_inode(sc->mp, cursor->dqtype);
35	cursor->id = 0;
36}
37
38/*
39 * Ensure that the cached data fork mapping for the dqiter cursor is fresh and
40 * covers the dquot pointed to by the scan cursor.
41 */
42STATIC int
43xchk_dquot_iter_revalidate_bmap(
44	struct xchk_dqiter	*cursor)
45{
46	struct xfs_quotainfo	*qi = cursor->sc->mp->m_quotainfo;
47	struct xfs_ifork	*ifp = xfs_ifork_ptr(cursor->quota_ip,
48								XFS_DATA_FORK);
49	xfs_fileoff_t		fileoff;
50	xfs_dqid_t		this_id = cursor->id;
51	int			nmaps = 1;
52	int			error;
53
54	fileoff = this_id / qi->qi_dqperchunk;
55
56	/*
57	 * If we have a mapping for cursor->id and it's still fresh, there's
58	 * no need to reread the bmbt.
59	 */
60	if (cursor->bmap.br_startoff != NULLFILEOFF &&
61	    cursor->if_seq == ifp->if_seq &&
62	    cursor->bmap.br_startoff + cursor->bmap.br_blockcount > fileoff)
63		return 0;
64
65	/* Look up the data fork mapping for the dquot id of interest. */
66	error = xfs_bmapi_read(cursor->quota_ip, fileoff,
67			XFS_MAX_FILEOFF - fileoff, &cursor->bmap, &nmaps, 0);
68	if (error)
69		return error;
70	if (!nmaps) {
71		ASSERT(nmaps > 0);
72		return -EFSCORRUPTED;
73	}
74	if (cursor->bmap.br_startoff > fileoff) {
75		ASSERT(cursor->bmap.br_startoff == fileoff);
76		return -EFSCORRUPTED;
77	}
78
79	cursor->if_seq = ifp->if_seq;
80	trace_xchk_dquot_iter_revalidate_bmap(cursor, cursor->id);
81	return 0;
82}
83
84/* Advance the dqiter cursor to the next non-sparse region of the quota file. */
85STATIC int
86xchk_dquot_iter_advance_bmap(
87	struct xchk_dqiter	*cursor,
88	uint64_t		*next_ondisk_id)
89{
90	struct xfs_quotainfo	*qi = cursor->sc->mp->m_quotainfo;
91	struct xfs_ifork	*ifp = xfs_ifork_ptr(cursor->quota_ip,
92								XFS_DATA_FORK);
93	xfs_fileoff_t		fileoff;
94	uint64_t		next_id;
95	int			nmaps = 1;
96	int			error;
97
98	/* Find the dquot id for the next non-hole mapping. */
99	do {
100		fileoff = cursor->bmap.br_startoff + cursor->bmap.br_blockcount;
101		if (fileoff > XFS_DQ_ID_MAX / qi->qi_dqperchunk) {
102			/* The hole goes beyond the max dquot id, we're done */
103			*next_ondisk_id = -1ULL;
104			return 0;
105		}
106
107		error = xfs_bmapi_read(cursor->quota_ip, fileoff,
108				XFS_MAX_FILEOFF - fileoff, &cursor->bmap,
109				&nmaps, 0);
110		if (error)
111			return error;
112		if (!nmaps) {
113			/* Must have reached the end of the mappings. */
114			*next_ondisk_id = -1ULL;
115			return 0;
116		}
117		if (cursor->bmap.br_startoff > fileoff) {
118			ASSERT(cursor->bmap.br_startoff == fileoff);
119			return -EFSCORRUPTED;
120		}
121	} while (!xfs_bmap_is_real_extent(&cursor->bmap));
122
123	next_id = cursor->bmap.br_startoff * qi->qi_dqperchunk;
124	if (next_id > XFS_DQ_ID_MAX) {
125		/* The hole goes beyond the max dquot id, we're done */
126		*next_ondisk_id = -1ULL;
127		return 0;
128	}
129
130	/* Propose jumping forward to the dquot in the next allocated block. */
131	*next_ondisk_id = next_id;
132	cursor->if_seq = ifp->if_seq;
133	trace_xchk_dquot_iter_advance_bmap(cursor, *next_ondisk_id);
134	return 0;
135}
136
137/*
138 * Find the id of the next highest incore dquot.  Normally this will correspond
139 * exactly with the quota file block mappings, but repair might have erased a
140 * mapping because it was crosslinked; in that case, we need to re-allocate the
141 * space so that we can reset q_blkno.
142 */
143STATIC void
144xchk_dquot_iter_advance_incore(
145	struct xchk_dqiter	*cursor,
146	uint64_t		*next_incore_id)
147{
148	struct xfs_quotainfo	*qi = cursor->sc->mp->m_quotainfo;
149	struct radix_tree_root	*tree = xfs_dquot_tree(qi, cursor->dqtype);
150	struct xfs_dquot	*dq;
151	unsigned int		nr_found;
152
153	*next_incore_id = -1ULL;
154
155	mutex_lock(&qi->qi_tree_lock);
156	nr_found = radix_tree_gang_lookup(tree, (void **)&dq, cursor->id, 1);
157	if (nr_found)
158		*next_incore_id = dq->q_id;
159	mutex_unlock(&qi->qi_tree_lock);
160
161	trace_xchk_dquot_iter_advance_incore(cursor, *next_incore_id);
162}
163
164/*
165 * Walk all incore dquots of this filesystem.  Caller must set *@cursorp to
166 * zero before the first call, and must not hold the quota file ILOCK.
167 * Returns 1 and a valid *@dqpp; 0 and *@dqpp == NULL when there are no more
168 * dquots to iterate; or a negative errno.
169 */
170int
171xchk_dquot_iter(
172	struct xchk_dqiter	*cursor,
173	struct xfs_dquot	**dqpp)
174{
175	struct xfs_mount	*mp = cursor->sc->mp;
176	struct xfs_dquot	*dq = NULL;
177	uint64_t		next_ondisk, next_incore = -1ULL;
178	unsigned int		lock_mode;
179	int			error = 0;
180
181	if (cursor->id > XFS_DQ_ID_MAX)
182		return 0;
183	next_ondisk = cursor->id;
184
185	/* Revalidate and/or advance the cursor. */
186	lock_mode = xfs_ilock_data_map_shared(cursor->quota_ip);
187	error = xchk_dquot_iter_revalidate_bmap(cursor);
188	if (!error && !xfs_bmap_is_real_extent(&cursor->bmap))
189		error = xchk_dquot_iter_advance_bmap(cursor, &next_ondisk);
190	xfs_iunlock(cursor->quota_ip, lock_mode);
191	if (error)
192		return error;
193
194	if (next_ondisk > cursor->id)
195		xchk_dquot_iter_advance_incore(cursor, &next_incore);
196
197	/* Pick the next dquot in the sequence and return it. */
198	cursor->id = min(next_ondisk, next_incore);
199	if (cursor->id > XFS_DQ_ID_MAX)
200		return 0;
201
202	trace_xchk_dquot_iter(cursor, cursor->id);
203
204	error = xfs_qm_dqget(mp, cursor->id, cursor->dqtype, false, &dq);
205	if (error)
206		return error;
207
208	cursor->id = dq->q_id + 1;
209	*dqpp = dq;
210	return 1;
211}
212