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