1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (C) 2017-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_format.h" |
10 | #include "xfs_trans_resv.h" |
11 | #include "xfs_mount.h" |
12 | #include "xfs_btree.h" |
13 | #include "xfs_inode.h" |
14 | #include "xfs_log_format.h" |
15 | #include "xfs_trans.h" |
16 | #include "xfs_rtbitmap.h" |
17 | #include "xfs_bit.h" |
18 | #include "xfs_bmap.h" |
19 | #include "xfs_sb.h" |
20 | #include "scrub/scrub.h" |
21 | #include "scrub/common.h" |
22 | #include "scrub/trace.h" |
23 | #include "scrub/xfile.h" |
24 | |
25 | /* |
26 | * Realtime Summary |
27 | * ================ |
28 | * |
29 | * We check the realtime summary by scanning the realtime bitmap file to create |
30 | * a new summary file incore, and then we compare the computed version against |
31 | * the ondisk version. We use the 'xfile' functionality to store this |
32 | * (potentially large) amount of data in pageable memory. |
33 | */ |
34 | |
35 | struct xchk_rtsummary { |
36 | struct xfs_rtalloc_args args; |
37 | |
38 | uint64_t rextents; |
39 | uint64_t rbmblocks; |
40 | uint64_t rsumsize; |
41 | unsigned int rsumlevels; |
42 | |
43 | /* Memory buffer for the summary comparison. */ |
44 | union xfs_suminfo_raw words[]; |
45 | }; |
46 | |
47 | /* Set us up to check the rtsummary file. */ |
48 | int |
49 | xchk_setup_rtsummary( |
50 | struct xfs_scrub *sc) |
51 | { |
52 | struct xfs_mount *mp = sc->mp; |
53 | char *descr; |
54 | struct xchk_rtsummary *rts; |
55 | int error; |
56 | |
57 | rts = kvzalloc(struct_size(rts, words, mp->m_blockwsize), |
58 | XCHK_GFP_FLAGS); |
59 | if (!rts) |
60 | return -ENOMEM; |
61 | sc->buf = rts; |
62 | |
63 | /* |
64 | * Create an xfile to construct a new rtsummary file. The xfile allows |
65 | * us to avoid pinning kernel memory for this purpose. |
66 | */ |
67 | descr = xchk_xfile_descr(sc, "realtime summary file" ); |
68 | error = xfile_create(descr, mp->m_rsumsize, &sc->xfile); |
69 | kfree(descr); |
70 | if (error) |
71 | return error; |
72 | |
73 | error = xchk_trans_alloc(sc, 0); |
74 | if (error) |
75 | return error; |
76 | |
77 | error = xchk_install_live_inode(sc, mp->m_rsumip); |
78 | if (error) |
79 | return error; |
80 | |
81 | error = xchk_ino_dqattach(sc); |
82 | if (error) |
83 | return error; |
84 | |
85 | /* |
86 | * Locking order requires us to take the rtbitmap first. We must be |
87 | * careful to unlock it ourselves when we are done with the rtbitmap |
88 | * file since the scrub infrastructure won't do that for us. Only |
89 | * then we can lock the rtsummary inode. |
90 | */ |
91 | xfs_ilock(mp->m_rbmip, XFS_ILOCK_SHARED | XFS_ILOCK_RTBITMAP); |
92 | xchk_ilock(sc, XFS_ILOCK_EXCL | XFS_ILOCK_RTSUM); |
93 | |
94 | /* |
95 | * Now that we've locked the rtbitmap and rtsummary, we can't race with |
96 | * growfsrt trying to expand the summary or change the size of the rt |
97 | * volume. Hence it is safe to compute and check the geometry values. |
98 | */ |
99 | if (mp->m_sb.sb_rblocks) { |
100 | xfs_filblks_t rsumblocks; |
101 | int rextslog; |
102 | |
103 | rts->rextents = xfs_rtb_to_rtx(mp, mp->m_sb.sb_rblocks); |
104 | rextslog = xfs_compute_rextslog(rts->rextents); |
105 | rts->rsumlevels = rextslog + 1; |
106 | rts->rbmblocks = xfs_rtbitmap_blockcount(mp, rts->rextents); |
107 | rsumblocks = xfs_rtsummary_blockcount(mp, rts->rsumlevels, |
108 | rts->rbmblocks); |
109 | rts->rsumsize = XFS_FSB_TO_B(mp, rsumblocks); |
110 | } |
111 | return 0; |
112 | } |
113 | |
114 | /* Helper functions to record suminfo words in an xfile. */ |
115 | |
116 | static inline int |
117 | xfsum_load( |
118 | struct xfs_scrub *sc, |
119 | xfs_rtsumoff_t sumoff, |
120 | union xfs_suminfo_raw *rawinfo) |
121 | { |
122 | return xfile_load(sc->xfile, rawinfo, |
123 | sizeof(union xfs_suminfo_raw), |
124 | sumoff << XFS_WORDLOG); |
125 | } |
126 | |
127 | static inline int |
128 | xfsum_store( |
129 | struct xfs_scrub *sc, |
130 | xfs_rtsumoff_t sumoff, |
131 | const union xfs_suminfo_raw rawinfo) |
132 | { |
133 | return xfile_store(sc->xfile, &rawinfo, |
134 | sizeof(union xfs_suminfo_raw), |
135 | sumoff << XFS_WORDLOG); |
136 | } |
137 | |
138 | static inline int |
139 | xfsum_copyout( |
140 | struct xfs_scrub *sc, |
141 | xfs_rtsumoff_t sumoff, |
142 | union xfs_suminfo_raw *rawinfo, |
143 | unsigned int nr_words) |
144 | { |
145 | return xfile_load(sc->xfile, rawinfo, nr_words << XFS_WORDLOG, |
146 | sumoff << XFS_WORDLOG); |
147 | } |
148 | |
149 | static inline xfs_suminfo_t |
150 | xchk_rtsum_inc( |
151 | struct xfs_mount *mp, |
152 | union xfs_suminfo_raw *v) |
153 | { |
154 | v->old += 1; |
155 | return v->old; |
156 | } |
157 | |
158 | /* Update the summary file to reflect the free extent that we've accumulated. */ |
159 | STATIC int |
160 | xchk_rtsum_record_free( |
161 | struct xfs_mount *mp, |
162 | struct xfs_trans *tp, |
163 | const struct xfs_rtalloc_rec *rec, |
164 | void *priv) |
165 | { |
166 | struct xfs_scrub *sc = priv; |
167 | xfs_fileoff_t rbmoff; |
168 | xfs_rtblock_t rtbno; |
169 | xfs_filblks_t rtlen; |
170 | xfs_rtsumoff_t offs; |
171 | unsigned int lenlog; |
172 | union xfs_suminfo_raw v; |
173 | xfs_suminfo_t value; |
174 | int error = 0; |
175 | |
176 | if (xchk_should_terminate(sc, &error)) |
177 | return error; |
178 | |
179 | /* Compute the relevant location in the rtsum file. */ |
180 | rbmoff = xfs_rtx_to_rbmblock(mp, rec->ar_startext); |
181 | lenlog = xfs_highbit64(rec->ar_extcount); |
182 | offs = xfs_rtsumoffs(mp, lenlog, rbmoff); |
183 | |
184 | rtbno = xfs_rtx_to_rtb(mp, rec->ar_startext); |
185 | rtlen = xfs_rtx_to_rtb(mp, rec->ar_extcount); |
186 | |
187 | if (!xfs_verify_rtbext(mp, rtbno, rtlen)) { |
188 | xchk_ino_xref_set_corrupt(sc, mp->m_rbmip->i_ino); |
189 | return -EFSCORRUPTED; |
190 | } |
191 | |
192 | /* Bump the summary count. */ |
193 | error = xfsum_load(sc, offs, &v); |
194 | if (error) |
195 | return error; |
196 | |
197 | value = xchk_rtsum_inc(sc->mp, &v); |
198 | trace_xchk_rtsum_record_free(mp, rec->ar_startext, rec->ar_extcount, |
199 | lenlog, offs, value); |
200 | |
201 | return xfsum_store(sc, offs, v); |
202 | } |
203 | |
204 | /* Compute the realtime summary from the realtime bitmap. */ |
205 | STATIC int |
206 | xchk_rtsum_compute( |
207 | struct xfs_scrub *sc) |
208 | { |
209 | struct xfs_mount *mp = sc->mp; |
210 | unsigned long long rtbmp_blocks; |
211 | |
212 | /* If the bitmap size doesn't match the computed size, bail. */ |
213 | rtbmp_blocks = xfs_rtbitmap_blockcount(mp, mp->m_sb.sb_rextents); |
214 | if (XFS_FSB_TO_B(mp, rtbmp_blocks) != mp->m_rbmip->i_disk_size) |
215 | return -EFSCORRUPTED; |
216 | |
217 | return xfs_rtalloc_query_all(sc->mp, sc->tp, xchk_rtsum_record_free, |
218 | sc); |
219 | } |
220 | |
221 | /* Compare the rtsummary file against the one we computed. */ |
222 | STATIC int |
223 | xchk_rtsum_compare( |
224 | struct xfs_scrub *sc) |
225 | { |
226 | struct xfs_bmbt_irec map; |
227 | struct xfs_iext_cursor icur; |
228 | |
229 | struct xfs_mount *mp = sc->mp; |
230 | struct xfs_inode *ip = sc->ip; |
231 | struct xchk_rtsummary *rts = sc->buf; |
232 | xfs_fileoff_t off = 0; |
233 | xfs_fileoff_t endoff; |
234 | xfs_rtsumoff_t sumoff = 0; |
235 | int error = 0; |
236 | |
237 | rts->args.mp = sc->mp; |
238 | rts->args.tp = sc->tp; |
239 | |
240 | /* Mappings may not cross or lie beyond EOF. */ |
241 | endoff = XFS_B_TO_FSB(mp, ip->i_disk_size); |
242 | if (xfs_iext_lookup_extent(ip, &ip->i_df, endoff, &icur, &map)) { |
243 | xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, endoff); |
244 | return 0; |
245 | } |
246 | |
247 | while (off < endoff) { |
248 | int nmap = 1; |
249 | |
250 | if (xchk_should_terminate(sc, &error)) |
251 | return error; |
252 | if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT) |
253 | return 0; |
254 | |
255 | /* Make sure we have a written extent. */ |
256 | error = xfs_bmapi_read(ip, off, endoff - off, &map, &nmap, |
257 | XFS_DATA_FORK); |
258 | if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, off, &error)) |
259 | return error; |
260 | |
261 | if (nmap != 1 || !xfs_bmap_is_written_extent(&map)) { |
262 | xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, off); |
263 | return 0; |
264 | } |
265 | |
266 | off += map.br_blockcount; |
267 | } |
268 | |
269 | for (off = 0; off < endoff; off++) { |
270 | union xfs_suminfo_raw *ondisk_info; |
271 | |
272 | /* Read a block's worth of ondisk rtsummary file. */ |
273 | error = xfs_rtsummary_read_buf(&rts->args, off); |
274 | if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, off, &error)) |
275 | return error; |
276 | |
277 | /* Read a block's worth of computed rtsummary file. */ |
278 | error = xfsum_copyout(sc, sumoff, rts->words, mp->m_blockwsize); |
279 | if (error) { |
280 | xfs_rtbuf_cache_relse(&rts->args); |
281 | return error; |
282 | } |
283 | |
284 | ondisk_info = xfs_rsumblock_infoptr(&rts->args, 0); |
285 | if (memcmp(ondisk_info, rts->words, |
286 | mp->m_blockwsize << XFS_WORDLOG) != 0) { |
287 | xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, off); |
288 | xfs_rtbuf_cache_relse(&rts->args); |
289 | return error; |
290 | } |
291 | |
292 | xfs_rtbuf_cache_relse(&rts->args); |
293 | sumoff += mp->m_blockwsize; |
294 | } |
295 | |
296 | return 0; |
297 | } |
298 | |
299 | /* Scrub the realtime summary. */ |
300 | int |
301 | xchk_rtsummary( |
302 | struct xfs_scrub *sc) |
303 | { |
304 | struct xfs_mount *mp = sc->mp; |
305 | struct xchk_rtsummary *rts = sc->buf; |
306 | int error = 0; |
307 | |
308 | /* Is sb_rextents correct? */ |
309 | if (mp->m_sb.sb_rextents != rts->rextents) { |
310 | xchk_ino_set_corrupt(sc, mp->m_rbmip->i_ino); |
311 | goto out_rbm; |
312 | } |
313 | |
314 | /* Is m_rsumlevels correct? */ |
315 | if (mp->m_rsumlevels != rts->rsumlevels) { |
316 | xchk_ino_set_corrupt(sc, mp->m_rsumip->i_ino); |
317 | goto out_rbm; |
318 | } |
319 | |
320 | /* Is m_rsumsize correct? */ |
321 | if (mp->m_rsumsize != rts->rsumsize) { |
322 | xchk_ino_set_corrupt(sc, mp->m_rsumip->i_ino); |
323 | goto out_rbm; |
324 | } |
325 | |
326 | /* The summary file length must be aligned to an fsblock. */ |
327 | if (mp->m_rsumip->i_disk_size & mp->m_blockmask) { |
328 | xchk_ino_set_corrupt(sc, mp->m_rsumip->i_ino); |
329 | goto out_rbm; |
330 | } |
331 | |
332 | /* |
333 | * Is the summary file itself large enough to handle the rt volume? |
334 | * growfsrt expands the summary file before updating sb_rextents, so |
335 | * the file can be larger than rsumsize. |
336 | */ |
337 | if (mp->m_rsumip->i_disk_size < rts->rsumsize) { |
338 | xchk_ino_set_corrupt(sc, mp->m_rsumip->i_ino); |
339 | goto out_rbm; |
340 | } |
341 | |
342 | /* Invoke the fork scrubber. */ |
343 | error = xchk_metadata_inode_forks(sc); |
344 | if (error || (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)) |
345 | goto out_rbm; |
346 | |
347 | /* Construct the new summary file from the rtbitmap. */ |
348 | error = xchk_rtsum_compute(sc); |
349 | if (error == -EFSCORRUPTED) { |
350 | /* |
351 | * EFSCORRUPTED means the rtbitmap is corrupt, which is an xref |
352 | * error since we're checking the summary file. |
353 | */ |
354 | xchk_ino_xref_set_corrupt(sc, mp->m_rbmip->i_ino); |
355 | error = 0; |
356 | goto out_rbm; |
357 | } |
358 | if (error) |
359 | goto out_rbm; |
360 | |
361 | /* Does the computed summary file match the actual rtsummary file? */ |
362 | error = xchk_rtsum_compare(sc); |
363 | |
364 | out_rbm: |
365 | /* Unlock the rtbitmap since we're done with it. */ |
366 | xfs_iunlock(mp->m_rbmip, XFS_ILOCK_SHARED | XFS_ILOCK_RTBITMAP); |
367 | return error; |
368 | } |
369 | |