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. */ |
25 | void |
26 | xchk_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 | */ |
42 | STATIC int |
43 | xchk_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. */ |
85 | STATIC int |
86 | xchk_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 | */ |
143 | STATIC void |
144 | xchk_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 | */ |
170 | int |
171 | xchk_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 | |