]>
Commit | Line | Data |
---|---|---|
959ef981 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
fd7d73c0 DW |
2 | /* |
3 | * Copyright (C) 2018 Oracle. All Rights Reserved. | |
fd7d73c0 | 4 | * Author: Darrick J. Wong <darrick.wong@oracle.com> |
fd7d73c0 | 5 | */ |
a440f877 | 6 | #include "xfs.h" |
fd7d73c0 DW |
7 | #include <stdint.h> |
8 | #include <stdlib.h> | |
9 | #include <unistd.h> | |
10 | #include <string.h> | |
11 | #include <sys/types.h> | |
fd7d73c0 | 12 | #include <sys/statvfs.h> |
19852474 | 13 | #include "list.h" |
42b4c8e8 | 14 | #include "libfrog/paths.h" |
5ef3b66a DW |
15 | #include "libfrog/fsgeom.h" |
16 | #include "libfrog/scrub.h" | |
fd7d73c0 DW |
17 | #include "xfs_scrub.h" |
18 | #include "common.h" | |
ed60d210 | 19 | #include "progress.h" |
fd7d73c0 DW |
20 | #include "scrub.h" |
21 | #include "xfs_errortag.h" | |
ee310b0c | 22 | #include "repair.h" |
b3f76f94 | 23 | #include "descr.h" |
fd7d73c0 DW |
24 | |
25 | /* Online scrub and repair wrappers. */ | |
26 | ||
fd7d73c0 | 27 | /* Format a scrub description. */ |
b3f76f94 | 28 | static int |
fd7d73c0 | 29 | format_scrub_descr( |
15589f0a | 30 | struct scrub_ctx *ctx, |
fd7d73c0 DW |
31 | char *buf, |
32 | size_t buflen, | |
b3f76f94 | 33 | void *where) |
fd7d73c0 | 34 | { |
b3f76f94 | 35 | struct xfs_scrub_metadata *meta = where; |
15589f0a DW |
36 | const struct xfrog_scrub_descr *sc = &xfrog_scrubbers[meta->sm_type]; |
37 | ||
fd7d73c0 | 38 | switch (sc->type) { |
5ef3b66a DW |
39 | case XFROG_SCRUB_TYPE_AGHEADER: |
40 | case XFROG_SCRUB_TYPE_PERAG: | |
b3f76f94 | 41 | return snprintf(buf, buflen, _("AG %u %s"), meta->sm_agno, |
5ef3b66a | 42 | _(sc->descr)); |
fd7d73c0 | 43 | break; |
5ef3b66a | 44 | case XFROG_SCRUB_TYPE_INODE: |
b3f76f94 | 45 | return scrub_render_ino_descr(ctx, buf, buflen, |
15589f0a DW |
46 | meta->sm_ino, meta->sm_gen, "%s", |
47 | _(sc->descr)); | |
fd7d73c0 | 48 | break; |
5ef3b66a | 49 | case XFROG_SCRUB_TYPE_FS: |
b3f76f94 | 50 | return snprintf(buf, buflen, _("%s"), _(sc->descr)); |
fd7d73c0 | 51 | break; |
5ef3b66a | 52 | case XFROG_SCRUB_TYPE_NONE: |
fd7d73c0 DW |
53 | assert(0); |
54 | break; | |
55 | } | |
b3f76f94 | 56 | return -1; |
fd7d73c0 DW |
57 | } |
58 | ||
59 | /* Predicates for scrub flag state. */ | |
60 | ||
61 | static inline bool is_corrupt(struct xfs_scrub_metadata *sm) | |
62 | { | |
63 | return sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT; | |
64 | } | |
65 | ||
66 | static inline bool is_unoptimized(struct xfs_scrub_metadata *sm) | |
67 | { | |
68 | return sm->sm_flags & XFS_SCRUB_OFLAG_PREEN; | |
69 | } | |
70 | ||
71 | static inline bool xref_failed(struct xfs_scrub_metadata *sm) | |
72 | { | |
73 | return sm->sm_flags & XFS_SCRUB_OFLAG_XFAIL; | |
74 | } | |
75 | ||
76 | static inline bool xref_disagrees(struct xfs_scrub_metadata *sm) | |
77 | { | |
78 | return sm->sm_flags & XFS_SCRUB_OFLAG_XCORRUPT; | |
79 | } | |
80 | ||
81 | static inline bool is_incomplete(struct xfs_scrub_metadata *sm) | |
82 | { | |
83 | return sm->sm_flags & XFS_SCRUB_OFLAG_INCOMPLETE; | |
84 | } | |
85 | ||
86 | static inline bool is_suspicious(struct xfs_scrub_metadata *sm) | |
87 | { | |
88 | return sm->sm_flags & XFS_SCRUB_OFLAG_WARNING; | |
89 | } | |
90 | ||
91 | /* Should we fix it? */ | |
92 | static inline bool needs_repair(struct xfs_scrub_metadata *sm) | |
93 | { | |
94 | return is_corrupt(sm) || xref_disagrees(sm); | |
95 | } | |
96 | ||
97 | /* Warn about strange circumstances after scrub. */ | |
98 | static inline void | |
99 | xfs_scrub_warn_incomplete_scrub( | |
100 | struct scrub_ctx *ctx, | |
b3f76f94 | 101 | struct descr *dsc, |
fd7d73c0 DW |
102 | struct xfs_scrub_metadata *meta) |
103 | { | |
104 | if (is_incomplete(meta)) | |
b3f76f94 | 105 | str_info(ctx, descr_render(dsc), _("Check incomplete.")); |
fd7d73c0 DW |
106 | |
107 | if (is_suspicious(meta)) { | |
108 | if (debug) | |
b3f76f94 DW |
109 | str_info(ctx, descr_render(dsc), |
110 | _("Possibly suspect metadata.")); | |
fd7d73c0 | 111 | else |
b3f76f94 DW |
112 | str_warn(ctx, descr_render(dsc), |
113 | _("Possibly suspect metadata.")); | |
fd7d73c0 DW |
114 | } |
115 | ||
116 | if (xref_failed(meta)) | |
b3f76f94 DW |
117 | str_info(ctx, descr_render(dsc), |
118 | _("Cross-referencing failed.")); | |
fd7d73c0 DW |
119 | } |
120 | ||
121 | /* Do a read-only check of some metadata. */ | |
122 | static enum check_outcome | |
123 | xfs_check_metadata( | |
124 | struct scrub_ctx *ctx, | |
fd7d73c0 DW |
125 | struct xfs_scrub_metadata *meta, |
126 | bool is_inode) | |
127 | { | |
b3f76f94 | 128 | DEFINE_DESCR(dsc, ctx, format_scrub_descr); |
fd7d73c0 DW |
129 | unsigned int tries = 0; |
130 | int code; | |
131 | int error; | |
132 | ||
133 | assert(!debug_tweak_on("XFS_SCRUB_NO_KERNEL")); | |
134 | assert(meta->sm_type < XFS_SCRUB_TYPE_NR); | |
b3f76f94 | 135 | descr_set(&dsc, meta); |
fd7d73c0 | 136 | |
b3f76f94 | 137 | dbg_printf("check %s flags %xh\n", descr_render(&dsc), meta->sm_flags); |
fd7d73c0 | 138 | retry: |
5ef3b66a | 139 | error = xfrog_scrub_metadata(&ctx->mnt, meta); |
fd7d73c0 DW |
140 | if (debug_tweak_on("XFS_SCRUB_FORCE_REPAIR") && !error) |
141 | meta->sm_flags |= XFS_SCRUB_OFLAG_CORRUPT; | |
142 | if (error) { | |
143 | code = errno; | |
144 | switch (code) { | |
145 | case ENOENT: | |
146 | /* Metadata not present, just skip it. */ | |
147 | return CHECK_DONE; | |
148 | case ESHUTDOWN: | |
149 | /* FS already crashed, give up. */ | |
b3f76f94 | 150 | str_error(ctx, descr_render(&dsc), |
fd7d73c0 DW |
151 | _("Filesystem is shut down, aborting.")); |
152 | return CHECK_ABORT; | |
153 | case EIO: | |
154 | case ENOMEM: | |
155 | /* Abort on I/O errors or insufficient memory. */ | |
b3f76f94 | 156 | str_errno(ctx, descr_render(&dsc)); |
fd7d73c0 DW |
157 | return CHECK_ABORT; |
158 | case EDEADLOCK: | |
159 | case EBUSY: | |
160 | case EFSBADCRC: | |
161 | case EFSCORRUPTED: | |
162 | /* | |
163 | * The first two should never escape the kernel, | |
164 | * and the other two should be reported via sm_flags. | |
165 | */ | |
e458f3f1 | 166 | str_liberror(ctx, code, _("Kernel bug")); |
fd7d73c0 DW |
167 | /* fall through */ |
168 | default: | |
169 | /* Operational error. */ | |
b3f76f94 | 170 | str_errno(ctx, descr_render(&dsc)); |
fd7d73c0 DW |
171 | return CHECK_DONE; |
172 | } | |
173 | } | |
174 | ||
175 | /* | |
176 | * If the kernel says the test was incomplete or that there was | |
177 | * a cross-referencing discrepancy but no obvious corruption, | |
178 | * we'll try the scan again, just in case the fs was busy. | |
179 | * Only retry so many times. | |
180 | */ | |
181 | if (tries < 10 && (is_incomplete(meta) || | |
182 | (xref_disagrees(meta) && !is_corrupt(meta)))) { | |
183 | tries++; | |
184 | goto retry; | |
185 | } | |
186 | ||
187 | /* Complain about incomplete or suspicious metadata. */ | |
b3f76f94 | 188 | xfs_scrub_warn_incomplete_scrub(ctx, &dsc, meta); |
fd7d73c0 DW |
189 | |
190 | /* | |
191 | * If we need repairs or there were discrepancies, schedule a | |
192 | * repair if desired, otherwise complain. | |
193 | */ | |
194 | if (is_corrupt(meta) || xref_disagrees(meta)) { | |
195 | if (ctx->mode < SCRUB_MODE_REPAIR) { | |
b3f76f94 | 196 | str_corrupt(ctx, descr_render(&dsc), |
fd7d73c0 DW |
197 | _("Repairs are required.")); |
198 | return CHECK_DONE; | |
199 | } | |
200 | ||
201 | return CHECK_REPAIR; | |
202 | } | |
203 | ||
204 | /* | |
205 | * If we could optimize, schedule a repair if desired, | |
206 | * otherwise complain. | |
207 | */ | |
208 | if (is_unoptimized(meta)) { | |
1658224d | 209 | if (ctx->mode != SCRUB_MODE_REPAIR) { |
fd7d73c0 DW |
210 | if (!is_inode) { |
211 | /* AG or FS metadata, always warn. */ | |
b3f76f94 | 212 | str_info(ctx, descr_render(&dsc), |
fd7d73c0 DW |
213 | _("Optimization is possible.")); |
214 | } else if (!ctx->preen_triggers[meta->sm_type]) { | |
215 | /* File metadata, only warn once per type. */ | |
216 | pthread_mutex_lock(&ctx->lock); | |
217 | if (!ctx->preen_triggers[meta->sm_type]) | |
218 | ctx->preen_triggers[meta->sm_type] = true; | |
219 | pthread_mutex_unlock(&ctx->lock); | |
220 | } | |
221 | return CHECK_DONE; | |
222 | } | |
223 | ||
224 | return CHECK_REPAIR; | |
225 | } | |
226 | ||
227 | /* Everything is ok. */ | |
228 | return CHECK_DONE; | |
229 | } | |
230 | ||
231 | /* Bulk-notify user about things that could be optimized. */ | |
232 | void | |
233 | xfs_scrub_report_preen_triggers( | |
234 | struct scrub_ctx *ctx) | |
235 | { | |
236 | int i; | |
237 | ||
238 | for (i = 0; i < XFS_SCRUB_TYPE_NR; i++) { | |
239 | pthread_mutex_lock(&ctx->lock); | |
240 | if (ctx->preen_triggers[i]) { | |
241 | ctx->preen_triggers[i] = false; | |
242 | pthread_mutex_unlock(&ctx->lock); | |
243 | str_info(ctx, ctx->mntpoint, | |
5ef3b66a | 244 | _("Optimizations of %s are possible."), _(xfrog_scrubbers[i].descr)); |
fd7d73c0 DW |
245 | } else { |
246 | pthread_mutex_unlock(&ctx->lock); | |
247 | } | |
248 | } | |
249 | } | |
250 | ||
ee310b0c | 251 | /* Save a scrub context for later repairs. */ |
d22f2471 | 252 | static int |
ee310b0c DW |
253 | xfs_scrub_save_repair( |
254 | struct scrub_ctx *ctx, | |
83d2c80b | 255 | struct action_list *alist, |
ee310b0c DW |
256 | struct xfs_scrub_metadata *meta) |
257 | { | |
258 | struct action_item *aitem; | |
259 | ||
260 | /* Schedule this item for later repairs. */ | |
261 | aitem = malloc(sizeof(struct action_item)); | |
262 | if (!aitem) { | |
d22f2471 DW |
263 | str_errno(ctx, _("adding item to repair list")); |
264 | return errno; | |
ee310b0c | 265 | } |
d22f2471 | 266 | |
ee310b0c DW |
267 | memset(aitem, 0, sizeof(*aitem)); |
268 | aitem->type = meta->sm_type; | |
269 | aitem->flags = meta->sm_flags; | |
5ef3b66a DW |
270 | switch (xfrog_scrubbers[meta->sm_type].type) { |
271 | case XFROG_SCRUB_TYPE_AGHEADER: | |
272 | case XFROG_SCRUB_TYPE_PERAG: | |
ee310b0c DW |
273 | aitem->agno = meta->sm_agno; |
274 | break; | |
5ef3b66a | 275 | case XFROG_SCRUB_TYPE_INODE: |
ee310b0c DW |
276 | aitem->ino = meta->sm_ino; |
277 | aitem->gen = meta->sm_gen; | |
278 | break; | |
279 | default: | |
280 | break; | |
281 | } | |
282 | ||
83d2c80b | 283 | action_list_add(alist, aitem); |
d22f2471 | 284 | return 0; |
ee310b0c DW |
285 | } |
286 | ||
d22f2471 DW |
287 | /* |
288 | * Scrub a single XFS_SCRUB_TYPE_*, saving corruption reports for later. | |
289 | * | |
290 | * Returns 0 for success. If errors occur, this function will log them and | |
291 | * return a positive error code. | |
292 | */ | |
469f76cb DW |
293 | static int |
294 | xfs_scrub_meta_type( | |
295 | struct scrub_ctx *ctx, | |
296 | unsigned int type, | |
297 | xfs_agnumber_t agno, | |
83d2c80b | 298 | struct action_list *alist) |
469f76cb DW |
299 | { |
300 | struct xfs_scrub_metadata meta = { | |
301 | .sm_type = type, | |
302 | .sm_agno = agno, | |
303 | }; | |
304 | enum check_outcome fix; | |
d22f2471 | 305 | int ret; |
469f76cb DW |
306 | |
307 | background_sleep(); | |
308 | ||
309 | /* Check the item. */ | |
310 | fix = xfs_check_metadata(ctx, &meta, false); | |
311 | progress_add(1); | |
312 | ||
313 | switch (fix) { | |
314 | case CHECK_ABORT: | |
315 | return ECANCELED; | |
316 | case CHECK_REPAIR: | |
d22f2471 DW |
317 | ret = xfs_scrub_save_repair(ctx, alist, &meta); |
318 | if (ret) | |
319 | return ret; | |
469f76cb DW |
320 | /* fall through */ |
321 | case CHECK_DONE: | |
322 | return 0; | |
323 | default: | |
324 | /* CHECK_RETRY should never happen. */ | |
325 | abort(); | |
326 | } | |
327 | } | |
328 | ||
329 | /* | |
330 | * Scrub all metadata types that are assigned to the given XFROG_SCRUB_TYPE_*, | |
331 | * saving corruption reports for later. This should not be used for | |
332 | * XFROG_SCRUB_TYPE_INODE or for checking summary metadata. | |
333 | */ | |
fd7d73c0 | 334 | static bool |
469f76cb | 335 | xfs_scrub_all_types( |
fd7d73c0 | 336 | struct scrub_ctx *ctx, |
5ef3b66a | 337 | enum xfrog_scrub_type scrub_type, |
ee310b0c | 338 | xfs_agnumber_t agno, |
83d2c80b | 339 | struct action_list *alist) |
fd7d73c0 | 340 | { |
5ef3b66a | 341 | const struct xfrog_scrub_descr *sc; |
469f76cb | 342 | unsigned int type; |
fd7d73c0 | 343 | |
5ef3b66a | 344 | sc = xfrog_scrubbers; |
fd7d73c0 | 345 | for (type = 0; type < XFS_SCRUB_TYPE_NR; type++, sc++) { |
469f76cb DW |
346 | int ret; |
347 | ||
fd7d73c0 DW |
348 | if (sc->type != scrub_type) |
349 | continue; | |
8dd3922c DW |
350 | if (sc->flags & XFROG_SCRUB_DESCR_SUMMARY) |
351 | continue; | |
fd7d73c0 | 352 | |
469f76cb DW |
353 | ret = xfs_scrub_meta_type(ctx, type, agno, alist); |
354 | if (ret) | |
d22f2471 | 355 | return ret; |
fd7d73c0 DW |
356 | } |
357 | ||
d22f2471 | 358 | return 0; |
fd7d73c0 DW |
359 | } |
360 | ||
361 | /* | |
362 | * Scrub primary superblock. This will be useful if we ever need to hook | |
363 | * a filesystem-wide pre-scrub activity off of the sb 0 scrubber (which | |
d22f2471 DW |
364 | * currently does nothing). If errors occur, this function will log them and |
365 | * return nonzero. | |
fd7d73c0 | 366 | */ |
d22f2471 | 367 | int |
fd7d73c0 | 368 | xfs_scrub_primary_super( |
ee310b0c | 369 | struct scrub_ctx *ctx, |
83d2c80b | 370 | struct action_list *alist) |
fd7d73c0 | 371 | { |
d22f2471 | 372 | return xfs_scrub_meta_type(ctx, XFS_SCRUB_TYPE_SB, 0, alist); |
fd7d73c0 DW |
373 | } |
374 | ||
375 | /* Scrub each AG's header blocks. */ | |
d22f2471 | 376 | int |
fd7d73c0 DW |
377 | xfs_scrub_ag_headers( |
378 | struct scrub_ctx *ctx, | |
ee310b0c | 379 | xfs_agnumber_t agno, |
83d2c80b | 380 | struct action_list *alist) |
fd7d73c0 | 381 | { |
469f76cb | 382 | return xfs_scrub_all_types(ctx, XFROG_SCRUB_TYPE_AGHEADER, agno, alist); |
fd7d73c0 DW |
383 | } |
384 | ||
385 | /* Scrub each AG's metadata btrees. */ | |
d22f2471 | 386 | int |
fd7d73c0 DW |
387 | xfs_scrub_ag_metadata( |
388 | struct scrub_ctx *ctx, | |
ee310b0c | 389 | xfs_agnumber_t agno, |
83d2c80b | 390 | struct action_list *alist) |
fd7d73c0 | 391 | { |
469f76cb | 392 | return xfs_scrub_all_types(ctx, XFROG_SCRUB_TYPE_PERAG, agno, alist); |
fd7d73c0 DW |
393 | } |
394 | ||
395 | /* Scrub whole-FS metadata btrees. */ | |
d22f2471 | 396 | int |
fd7d73c0 | 397 | xfs_scrub_fs_metadata( |
ee310b0c | 398 | struct scrub_ctx *ctx, |
83d2c80b | 399 | struct action_list *alist) |
fd7d73c0 | 400 | { |
469f76cb | 401 | return xfs_scrub_all_types(ctx, XFROG_SCRUB_TYPE_FS, 0, alist); |
fd7d73c0 DW |
402 | } |
403 | ||
cbaf1c9d | 404 | /* Scrub FS summary metadata. */ |
d22f2471 | 405 | int |
cbaf1c9d DW |
406 | xfs_scrub_fs_summary( |
407 | struct scrub_ctx *ctx, | |
83d2c80b | 408 | struct action_list *alist) |
cbaf1c9d | 409 | { |
d22f2471 | 410 | return xfs_scrub_meta_type(ctx, XFS_SCRUB_TYPE_FSCOUNTERS, 0, alist); |
cbaf1c9d DW |
411 | } |
412 | ||
ed60d210 DW |
413 | /* How many items do we have to check? */ |
414 | unsigned int | |
d22f2471 | 415 | scrub_estimate_ag_work( |
ed60d210 DW |
416 | struct scrub_ctx *ctx) |
417 | { | |
5ef3b66a | 418 | const struct xfrog_scrub_descr *sc; |
ed60d210 DW |
419 | int type; |
420 | unsigned int estimate = 0; | |
421 | ||
5ef3b66a | 422 | sc = xfrog_scrubbers; |
ed60d210 DW |
423 | for (type = 0; type < XFS_SCRUB_TYPE_NR; type++, sc++) { |
424 | switch (sc->type) { | |
5ef3b66a DW |
425 | case XFROG_SCRUB_TYPE_AGHEADER: |
426 | case XFROG_SCRUB_TYPE_PERAG: | |
3f9efb2e | 427 | estimate += ctx->mnt.fsgeom.agcount; |
ed60d210 | 428 | break; |
5ef3b66a | 429 | case XFROG_SCRUB_TYPE_FS: |
ed60d210 DW |
430 | estimate++; |
431 | break; | |
432 | default: | |
433 | break; | |
434 | } | |
435 | } | |
436 | return estimate; | |
437 | } | |
438 | ||
d22f2471 DW |
439 | /* |
440 | * Scrub inode metadata. If errors occur, this function will log them and | |
441 | * return nonzero. | |
442 | */ | |
443 | static int | |
fd7d73c0 DW |
444 | __xfs_scrub_file( |
445 | struct scrub_ctx *ctx, | |
446 | uint64_t ino, | |
447 | uint32_t gen, | |
ee310b0c | 448 | unsigned int type, |
83d2c80b | 449 | struct action_list *alist) |
fd7d73c0 DW |
450 | { |
451 | struct xfs_scrub_metadata meta = {0}; | |
452 | enum check_outcome fix; | |
453 | ||
454 | assert(type < XFS_SCRUB_TYPE_NR); | |
5ef3b66a | 455 | assert(xfrog_scrubbers[type].type == XFROG_SCRUB_TYPE_INODE); |
fd7d73c0 DW |
456 | |
457 | meta.sm_type = type; | |
458 | meta.sm_ino = ino; | |
459 | meta.sm_gen = gen; | |
460 | ||
461 | /* Scrub the piece of metadata. */ | |
991e5a84 | 462 | fix = xfs_check_metadata(ctx, &meta, true); |
fd7d73c0 | 463 | if (fix == CHECK_ABORT) |
d22f2471 | 464 | return ECANCELED; |
fd7d73c0 | 465 | if (fix == CHECK_DONE) |
d22f2471 | 466 | return 0; |
fd7d73c0 | 467 | |
ee310b0c | 468 | return xfs_scrub_save_repair(ctx, alist, &meta); |
fd7d73c0 DW |
469 | } |
470 | ||
d22f2471 | 471 | int |
fd7d73c0 DW |
472 | xfs_scrub_inode_fields( |
473 | struct scrub_ctx *ctx, | |
474 | uint64_t ino, | |
475 | uint32_t gen, | |
83d2c80b | 476 | struct action_list *alist) |
fd7d73c0 | 477 | { |
991e5a84 | 478 | return __xfs_scrub_file(ctx, ino, gen, XFS_SCRUB_TYPE_INODE, alist); |
fd7d73c0 DW |
479 | } |
480 | ||
d22f2471 | 481 | int |
fd7d73c0 DW |
482 | xfs_scrub_data_fork( |
483 | struct scrub_ctx *ctx, | |
484 | uint64_t ino, | |
485 | uint32_t gen, | |
83d2c80b | 486 | struct action_list *alist) |
fd7d73c0 | 487 | { |
991e5a84 | 488 | return __xfs_scrub_file(ctx, ino, gen, XFS_SCRUB_TYPE_BMBTD, alist); |
fd7d73c0 DW |
489 | } |
490 | ||
d22f2471 | 491 | int |
fd7d73c0 DW |
492 | xfs_scrub_attr_fork( |
493 | struct scrub_ctx *ctx, | |
494 | uint64_t ino, | |
495 | uint32_t gen, | |
83d2c80b | 496 | struct action_list *alist) |
fd7d73c0 | 497 | { |
991e5a84 | 498 | return __xfs_scrub_file(ctx, ino, gen, XFS_SCRUB_TYPE_BMBTA, alist); |
fd7d73c0 DW |
499 | } |
500 | ||
d22f2471 | 501 | int |
fd7d73c0 DW |
502 | xfs_scrub_cow_fork( |
503 | struct scrub_ctx *ctx, | |
504 | uint64_t ino, | |
505 | uint32_t gen, | |
83d2c80b | 506 | struct action_list *alist) |
fd7d73c0 | 507 | { |
991e5a84 | 508 | return __xfs_scrub_file(ctx, ino, gen, XFS_SCRUB_TYPE_BMBTC, alist); |
fd7d73c0 DW |
509 | } |
510 | ||
d22f2471 | 511 | int |
fd7d73c0 DW |
512 | xfs_scrub_dir( |
513 | struct scrub_ctx *ctx, | |
514 | uint64_t ino, | |
515 | uint32_t gen, | |
83d2c80b | 516 | struct action_list *alist) |
fd7d73c0 | 517 | { |
991e5a84 | 518 | return __xfs_scrub_file(ctx, ino, gen, XFS_SCRUB_TYPE_DIR, alist); |
fd7d73c0 DW |
519 | } |
520 | ||
d22f2471 | 521 | int |
fd7d73c0 DW |
522 | xfs_scrub_attr( |
523 | struct scrub_ctx *ctx, | |
524 | uint64_t ino, | |
525 | uint32_t gen, | |
83d2c80b | 526 | struct action_list *alist) |
fd7d73c0 | 527 | { |
991e5a84 | 528 | return __xfs_scrub_file(ctx, ino, gen, XFS_SCRUB_TYPE_XATTR, alist); |
fd7d73c0 DW |
529 | } |
530 | ||
d22f2471 | 531 | int |
fd7d73c0 DW |
532 | xfs_scrub_symlink( |
533 | struct scrub_ctx *ctx, | |
534 | uint64_t ino, | |
535 | uint32_t gen, | |
83d2c80b | 536 | struct action_list *alist) |
fd7d73c0 | 537 | { |
991e5a84 | 538 | return __xfs_scrub_file(ctx, ino, gen, XFS_SCRUB_TYPE_SYMLINK, alist); |
fd7d73c0 DW |
539 | } |
540 | ||
d22f2471 | 541 | int |
fd7d73c0 DW |
542 | xfs_scrub_parent( |
543 | struct scrub_ctx *ctx, | |
544 | uint64_t ino, | |
545 | uint32_t gen, | |
83d2c80b | 546 | struct action_list *alist) |
fd7d73c0 | 547 | { |
991e5a84 | 548 | return __xfs_scrub_file(ctx, ino, gen, XFS_SCRUB_TYPE_PARENT, alist); |
fd7d73c0 DW |
549 | } |
550 | ||
d22f2471 DW |
551 | /* |
552 | * Test the availability of a kernel scrub command. If errors occur (or the | |
553 | * scrub ioctl is rejected) the errors will be logged and this function will | |
554 | * return false. | |
555 | */ | |
fd7d73c0 DW |
556 | static bool |
557 | __xfs_scrub_test( | |
558 | struct scrub_ctx *ctx, | |
559 | unsigned int type, | |
560 | bool repair) | |
561 | { | |
562 | struct xfs_scrub_metadata meta = {0}; | |
7a9d1ac4 | 563 | struct xfs_error_injection inject; |
19852474 | 564 | static bool injected; |
fd7d73c0 DW |
565 | int error; |
566 | ||
567 | if (debug_tweak_on("XFS_SCRUB_NO_KERNEL")) | |
568 | return false; | |
19852474 | 569 | if (debug_tweak_on("XFS_SCRUB_FORCE_REPAIR") && !injected) { |
3f9efb2e | 570 | inject.fd = ctx->mnt.fd; |
7a9d1ac4 | 571 | inject.errtag = XFS_ERRTAG_FORCE_SCRUB_REPAIR; |
3f9efb2e | 572 | error = ioctl(ctx->mnt.fd, XFS_IOC_ERROR_INJECTION, &inject); |
7a9d1ac4 DW |
573 | if (error == 0) |
574 | injected = true; | |
19852474 | 575 | } |
fd7d73c0 DW |
576 | |
577 | meta.sm_type = type; | |
578 | if (repair) | |
579 | meta.sm_flags |= XFS_SCRUB_IFLAG_REPAIR; | |
5ef3b66a | 580 | error = xfrog_scrub_metadata(&ctx->mnt, &meta); |
fd7d73c0 DW |
581 | if (!error) |
582 | return true; | |
583 | switch (errno) { | |
584 | case EROFS: | |
585 | str_info(ctx, ctx->mntpoint, | |
586 | _("Filesystem is mounted read-only; cannot proceed.")); | |
587 | return false; | |
588 | case ENOTRECOVERABLE: | |
589 | str_info(ctx, ctx->mntpoint, | |
590 | _("Filesystem is mounted norecovery; cannot proceed.")); | |
591 | return false; | |
592 | case EOPNOTSUPP: | |
593 | case ENOTTY: | |
594 | if (debug || verbose) | |
595 | str_info(ctx, ctx->mntpoint, | |
596 | _("Kernel %s %s facility not detected."), | |
5ef3b66a | 597 | _(xfrog_scrubbers[type].descr), |
fd7d73c0 DW |
598 | repair ? _("repair") : _("scrub")); |
599 | return false; | |
600 | case ENOENT: | |
601 | /* Scrubber says not present on this fs; that's fine. */ | |
602 | return true; | |
603 | default: | |
604 | str_info(ctx, ctx->mntpoint, "%s", strerror(errno)); | |
605 | return true; | |
606 | } | |
fd7d73c0 DW |
607 | } |
608 | ||
609 | bool | |
610 | xfs_can_scrub_fs_metadata( | |
611 | struct scrub_ctx *ctx) | |
612 | { | |
613 | return __xfs_scrub_test(ctx, XFS_SCRUB_TYPE_PROBE, false); | |
614 | } | |
615 | ||
616 | bool | |
617 | xfs_can_scrub_inode( | |
618 | struct scrub_ctx *ctx) | |
619 | { | |
620 | return __xfs_scrub_test(ctx, XFS_SCRUB_TYPE_INODE, false); | |
621 | } | |
622 | ||
623 | bool | |
624 | xfs_can_scrub_bmap( | |
625 | struct scrub_ctx *ctx) | |
626 | { | |
627 | return __xfs_scrub_test(ctx, XFS_SCRUB_TYPE_BMBTD, false); | |
628 | } | |
629 | ||
630 | bool | |
631 | xfs_can_scrub_dir( | |
632 | struct scrub_ctx *ctx) | |
633 | { | |
634 | return __xfs_scrub_test(ctx, XFS_SCRUB_TYPE_DIR, false); | |
635 | } | |
636 | ||
637 | bool | |
638 | xfs_can_scrub_attr( | |
639 | struct scrub_ctx *ctx) | |
640 | { | |
641 | return __xfs_scrub_test(ctx, XFS_SCRUB_TYPE_XATTR, false); | |
642 | } | |
643 | ||
644 | bool | |
645 | xfs_can_scrub_symlink( | |
646 | struct scrub_ctx *ctx) | |
647 | { | |
648 | return __xfs_scrub_test(ctx, XFS_SCRUB_TYPE_SYMLINK, false); | |
649 | } | |
650 | ||
651 | bool | |
652 | xfs_can_scrub_parent( | |
653 | struct scrub_ctx *ctx) | |
654 | { | |
655 | return __xfs_scrub_test(ctx, XFS_SCRUB_TYPE_PARENT, false); | |
656 | } | |
19852474 DW |
657 | |
658 | bool | |
659 | xfs_can_repair( | |
660 | struct scrub_ctx *ctx) | |
661 | { | |
662 | return __xfs_scrub_test(ctx, XFS_SCRUB_TYPE_PROBE, true); | |
663 | } | |
664 | ||
665 | /* General repair routines. */ | |
666 | ||
667 | /* Repair some metadata. */ | |
668 | enum check_outcome | |
669 | xfs_repair_metadata( | |
670 | struct scrub_ctx *ctx, | |
671 | int fd, | |
ee310b0c | 672 | struct action_item *aitem, |
19852474 DW |
673 | unsigned int repair_flags) |
674 | { | |
19852474 DW |
675 | struct xfs_scrub_metadata meta = { 0 }; |
676 | struct xfs_scrub_metadata oldm; | |
b3f76f94 | 677 | DEFINE_DESCR(dsc, ctx, format_scrub_descr); |
19852474 DW |
678 | int error; |
679 | ||
ee310b0c | 680 | assert(aitem->type < XFS_SCRUB_TYPE_NR); |
19852474 | 681 | assert(!debug_tweak_on("XFS_SCRUB_NO_KERNEL")); |
ee310b0c DW |
682 | meta.sm_type = aitem->type; |
683 | meta.sm_flags = aitem->flags | XFS_SCRUB_IFLAG_REPAIR; | |
5ef3b66a DW |
684 | switch (xfrog_scrubbers[aitem->type].type) { |
685 | case XFROG_SCRUB_TYPE_AGHEADER: | |
686 | case XFROG_SCRUB_TYPE_PERAG: | |
ee310b0c | 687 | meta.sm_agno = aitem->agno; |
19852474 | 688 | break; |
5ef3b66a | 689 | case XFROG_SCRUB_TYPE_INODE: |
ee310b0c DW |
690 | meta.sm_ino = aitem->ino; |
691 | meta.sm_gen = aitem->gen; | |
19852474 DW |
692 | break; |
693 | default: | |
694 | break; | |
695 | } | |
696 | ||
ee310b0c | 697 | if (!is_corrupt(&meta) && (repair_flags & XRM_REPAIR_ONLY)) |
19852474 DW |
698 | return CHECK_RETRY; |
699 | ||
700 | memcpy(&oldm, &meta, sizeof(oldm)); | |
b3f76f94 | 701 | descr_set(&dsc, &oldm); |
19852474 DW |
702 | |
703 | if (needs_repair(&meta)) | |
b3f76f94 | 704 | str_info(ctx, descr_render(&dsc), _("Attempting repair.")); |
19852474 | 705 | else if (debug || verbose) |
b3f76f94 DW |
706 | str_info(ctx, descr_render(&dsc), |
707 | _("Attempting optimization.")); | |
19852474 | 708 | |
5ef3b66a | 709 | error = xfrog_scrub_metadata(&ctx->mnt, &meta); |
19852474 DW |
710 | if (error) { |
711 | switch (errno) { | |
712 | case EDEADLOCK: | |
713 | case EBUSY: | |
714 | /* Filesystem is busy, try again later. */ | |
715 | if (debug || verbose) | |
b3f76f94 | 716 | str_info(ctx, descr_render(&dsc), |
19852474 DW |
717 | _("Filesystem is busy, deferring repair.")); |
718 | return CHECK_RETRY; | |
719 | case ESHUTDOWN: | |
720 | /* Filesystem is already shut down, abort. */ | |
b3f76f94 | 721 | str_error(ctx, descr_render(&dsc), |
19852474 DW |
722 | _("Filesystem is shut down, aborting.")); |
723 | return CHECK_ABORT; | |
724 | case ENOTTY: | |
725 | case EOPNOTSUPP: | |
c3a140f0 DW |
726 | /* |
727 | * If we're in no-complain mode, requeue the check for | |
728 | * later. It's possible that an error in another | |
729 | * component caused us to flag an error in this | |
730 | * component. Even if the kernel didn't think it | |
731 | * could fix this, it's at least worth trying the scan | |
732 | * again to see if another repair fixed it. | |
733 | */ | |
734 | if (!(repair_flags & XRM_COMPLAIN_IF_UNFIXED)) | |
735 | return CHECK_RETRY; | |
19852474 | 736 | /* |
b07c2ba5 DW |
737 | * If we forced repairs or this is a preen, don't |
738 | * error out if the kernel doesn't know how to fix. | |
19852474 | 739 | */ |
b07c2ba5 DW |
740 | if (is_unoptimized(&oldm) || |
741 | debug_tweak_on("XFS_SCRUB_FORCE_REPAIR")) | |
19852474 DW |
742 | return CHECK_DONE; |
743 | /* fall through */ | |
744 | case EINVAL: | |
745 | /* Kernel doesn't know how to repair this? */ | |
b3f76f94 | 746 | str_corrupt(ctx, descr_render(&dsc), |
19852474 DW |
747 | _("Don't know how to fix; offline repair required.")); |
748 | return CHECK_DONE; | |
749 | case EROFS: | |
750 | /* Read-only filesystem, can't fix. */ | |
751 | if (verbose || debug || needs_repair(&oldm)) | |
b3f76f94 | 752 | str_error(ctx, descr_render(&dsc), |
19852474 | 753 | _("Read-only filesystem; cannot make changes.")); |
e458f3f1 | 754 | return CHECK_ABORT; |
19852474 DW |
755 | case ENOENT: |
756 | /* Metadata not present, just skip it. */ | |
757 | return CHECK_DONE; | |
758 | case ENOMEM: | |
759 | case ENOSPC: | |
760 | /* Don't care if preen fails due to low resources. */ | |
761 | if (is_unoptimized(&oldm) && !needs_repair(&oldm)) | |
762 | return CHECK_DONE; | |
763 | /* fall through */ | |
764 | default: | |
c3a140f0 DW |
765 | /* |
766 | * Operational error. If the caller doesn't want us | |
767 | * to complain about repair failures, tell the caller | |
768 | * to requeue the repair for later and don't say a | |
769 | * thing. Otherwise, print error and bail out. | |
770 | */ | |
771 | if (!(repair_flags & XRM_COMPLAIN_IF_UNFIXED)) | |
772 | return CHECK_RETRY; | |
b3f76f94 | 773 | str_errno(ctx, descr_render(&dsc)); |
19852474 DW |
774 | return CHECK_DONE; |
775 | } | |
776 | } | |
06e49f3e | 777 | if (repair_flags & XRM_COMPLAIN_IF_UNFIXED) |
b3f76f94 | 778 | xfs_scrub_warn_incomplete_scrub(ctx, &dsc, &meta); |
19852474 | 779 | if (needs_repair(&meta)) { |
c3a140f0 DW |
780 | /* |
781 | * Still broken; if we've been told not to complain then we | |
782 | * just requeue this and try again later. Otherwise we | |
783 | * log the error loudly and don't try again. | |
784 | */ | |
785 | if (!(repair_flags & XRM_COMPLAIN_IF_UNFIXED)) | |
786 | return CHECK_RETRY; | |
b3f76f94 | 787 | str_corrupt(ctx, descr_render(&dsc), |
19852474 DW |
788 | _("Repair unsuccessful; offline repair required.")); |
789 | } else { | |
790 | /* Clean operation, no corruption detected. */ | |
791 | if (needs_repair(&oldm)) | |
b3f76f94 DW |
792 | record_repair(ctx, descr_render(&dsc), |
793 | _("Repairs successful.")); | |
19852474 | 794 | else |
b3f76f94 DW |
795 | record_preen(ctx, descr_render(&dsc), |
796 | _("Optimization successful.")); | |
19852474 DW |
797 | } |
798 | return CHECK_DONE; | |
799 | } |