]>
Commit | Line | Data |
---|---|---|
1 | // SPDX-License-Identifier: GPL-2.0+ | |
2 | /* | |
3 | * Copyright (C) 2018 Oracle. All Rights Reserved. | |
4 | * Author: Darrick J. Wong <darrick.wong@oracle.com> | |
5 | */ | |
6 | #include "xfs.h" | |
7 | #include <stdint.h> | |
8 | #include <stdlib.h> | |
9 | #include <unistd.h> | |
10 | #include <string.h> | |
11 | #include <sys/types.h> | |
12 | #include <sys/statvfs.h> | |
13 | #include "list.h" | |
14 | #include "libfrog/paths.h" | |
15 | #include "libfrog/fsgeom.h" | |
16 | #include "libfrog/scrub.h" | |
17 | #include "xfs_scrub.h" | |
18 | #include "common.h" | |
19 | #include "progress.h" | |
20 | #include "scrub.h" | |
21 | #include "xfs_errortag.h" | |
22 | #include "repair.h" | |
23 | ||
24 | /* Online scrub and repair wrappers. */ | |
25 | ||
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, | |
32 | const struct xfrog_scrub_descr *sc) | |
33 | { | |
34 | switch (sc->type) { | |
35 | case XFROG_SCRUB_TYPE_AGHEADER: | |
36 | case XFROG_SCRUB_TYPE_PERAG: | |
37 | snprintf(buf, buflen, _("AG %u %s"), meta->sm_agno, | |
38 | _(sc->descr)); | |
39 | break; | |
40 | case XFROG_SCRUB_TYPE_INODE: | |
41 | snprintf(buf, buflen, _("Inode %"PRIu64" %s"), | |
42 | (uint64_t)meta->sm_ino, _(sc->descr)); | |
43 | break; | |
44 | case XFROG_SCRUB_TYPE_FS: | |
45 | snprintf(buf, buflen, _("%s"), _(sc->descr)); | |
46 | break; | |
47 | case XFROG_SCRUB_TYPE_NONE: | |
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, | |
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); | |
126 | format_scrub_descr(buf, DESCR_BUFSZ, meta, | |
127 | &xfrog_scrubbers[meta->sm_type]); | |
128 | ||
129 | dbg_printf("check %s flags %xh\n", buf, meta->sm_flags); | |
130 | retry: | |
131 | error = xfrog_scrub_metadata(&ctx->mnt, meta); | |
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. */ | |
142 | str_info(ctx, buf, | |
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 | */ | |
158 | str_info(ctx, buf, | |
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)) { | |
202 | if (ctx->mode != SCRUB_MODE_REPAIR) { | |
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, | |
237 | _("Optimizations of %s are possible."), _(xfrog_scrubbers[i].descr)); | |
238 | } else { | |
239 | pthread_mutex_unlock(&ctx->lock); | |
240 | } | |
241 | } | |
242 | } | |
243 | ||
244 | /* Save a scrub context for later repairs. */ | |
245 | static bool | |
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; | |
262 | switch (xfrog_scrubbers[meta->sm_type].type) { | |
263 | case XFROG_SCRUB_TYPE_AGHEADER: | |
264 | case XFROG_SCRUB_TYPE_PERAG: | |
265 | aitem->agno = meta->sm_agno; | |
266 | break; | |
267 | case XFROG_SCRUB_TYPE_INODE: | |
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 | ||
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 | */ | |
319 | static bool | |
320 | xfs_scrub_all_types( | |
321 | struct scrub_ctx *ctx, | |
322 | enum xfrog_scrub_type scrub_type, | |
323 | xfs_agnumber_t agno, | |
324 | struct xfs_action_list *alist) | |
325 | { | |
326 | const struct xfrog_scrub_descr *sc; | |
327 | unsigned int type; | |
328 | ||
329 | sc = xfrog_scrubbers; | |
330 | for (type = 0; type < XFS_SCRUB_TYPE_NR; type++, sc++) { | |
331 | int ret; | |
332 | ||
333 | if (sc->type != scrub_type) | |
334 | continue; | |
335 | if (sc->flags & XFROG_SCRUB_DESCR_SUMMARY) | |
336 | continue; | |
337 | ||
338 | ret = xfs_scrub_meta_type(ctx, type, agno, alist); | |
339 | if (ret) | |
340 | return false; | |
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( | |
353 | struct scrub_ctx *ctx, | |
354 | struct xfs_action_list *alist) | |
355 | { | |
356 | int ret; | |
357 | ||
358 | ret = xfs_scrub_meta_type(ctx, XFS_SCRUB_TYPE_SB, 0, alist); | |
359 | return ret == 0; | |
360 | } | |
361 | ||
362 | /* Scrub each AG's header blocks. */ | |
363 | bool | |
364 | xfs_scrub_ag_headers( | |
365 | struct scrub_ctx *ctx, | |
366 | xfs_agnumber_t agno, | |
367 | struct xfs_action_list *alist) | |
368 | { | |
369 | return xfs_scrub_all_types(ctx, XFROG_SCRUB_TYPE_AGHEADER, agno, alist); | |
370 | } | |
371 | ||
372 | /* Scrub each AG's metadata btrees. */ | |
373 | bool | |
374 | xfs_scrub_ag_metadata( | |
375 | struct scrub_ctx *ctx, | |
376 | xfs_agnumber_t agno, | |
377 | struct xfs_action_list *alist) | |
378 | { | |
379 | return xfs_scrub_all_types(ctx, XFROG_SCRUB_TYPE_PERAG, agno, alist); | |
380 | } | |
381 | ||
382 | /* Scrub whole-FS metadata btrees. */ | |
383 | bool | |
384 | xfs_scrub_fs_metadata( | |
385 | struct scrub_ctx *ctx, | |
386 | struct xfs_action_list *alist) | |
387 | { | |
388 | return xfs_scrub_all_types(ctx, XFROG_SCRUB_TYPE_FS, 0, alist); | |
389 | } | |
390 | ||
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 | ||
403 | /* How many items do we have to check? */ | |
404 | unsigned int | |
405 | xfs_scrub_estimate_ag_work( | |
406 | struct scrub_ctx *ctx) | |
407 | { | |
408 | const struct xfrog_scrub_descr *sc; | |
409 | int type; | |
410 | unsigned int estimate = 0; | |
411 | ||
412 | sc = xfrog_scrubbers; | |
413 | for (type = 0; type < XFS_SCRUB_TYPE_NR; type++, sc++) { | |
414 | switch (sc->type) { | |
415 | case XFROG_SCRUB_TYPE_AGHEADER: | |
416 | case XFROG_SCRUB_TYPE_PERAG: | |
417 | estimate += ctx->mnt.fsgeom.agcount; | |
418 | break; | |
419 | case XFROG_SCRUB_TYPE_FS: | |
420 | estimate++; | |
421 | break; | |
422 | default: | |
423 | break; | |
424 | } | |
425 | } | |
426 | return estimate; | |
427 | } | |
428 | ||
429 | /* Scrub inode metadata. */ | |
430 | static bool | |
431 | __xfs_scrub_file( | |
432 | struct scrub_ctx *ctx, | |
433 | uint64_t ino, | |
434 | uint32_t gen, | |
435 | unsigned int type, | |
436 | struct xfs_action_list *alist) | |
437 | { | |
438 | struct xfs_scrub_metadata meta = {0}; | |
439 | enum check_outcome fix; | |
440 | ||
441 | assert(type < XFS_SCRUB_TYPE_NR); | |
442 | assert(xfrog_scrubbers[type].type == XFROG_SCRUB_TYPE_INODE); | |
443 | ||
444 | meta.sm_type = type; | |
445 | meta.sm_ino = ino; | |
446 | meta.sm_gen = gen; | |
447 | ||
448 | /* Scrub the piece of metadata. */ | |
449 | fix = xfs_check_metadata(ctx, &meta, true); | |
450 | if (fix == CHECK_ABORT) | |
451 | return false; | |
452 | if (fix == CHECK_DONE) | |
453 | return true; | |
454 | ||
455 | return xfs_scrub_save_repair(ctx, alist, &meta); | |
456 | } | |
457 | ||
458 | bool | |
459 | xfs_scrub_inode_fields( | |
460 | struct scrub_ctx *ctx, | |
461 | uint64_t ino, | |
462 | uint32_t gen, | |
463 | struct xfs_action_list *alist) | |
464 | { | |
465 | return __xfs_scrub_file(ctx, ino, gen, XFS_SCRUB_TYPE_INODE, alist); | |
466 | } | |
467 | ||
468 | bool | |
469 | xfs_scrub_data_fork( | |
470 | struct scrub_ctx *ctx, | |
471 | uint64_t ino, | |
472 | uint32_t gen, | |
473 | struct xfs_action_list *alist) | |
474 | { | |
475 | return __xfs_scrub_file(ctx, ino, gen, XFS_SCRUB_TYPE_BMBTD, alist); | |
476 | } | |
477 | ||
478 | bool | |
479 | xfs_scrub_attr_fork( | |
480 | struct scrub_ctx *ctx, | |
481 | uint64_t ino, | |
482 | uint32_t gen, | |
483 | struct xfs_action_list *alist) | |
484 | { | |
485 | return __xfs_scrub_file(ctx, ino, gen, XFS_SCRUB_TYPE_BMBTA, alist); | |
486 | } | |
487 | ||
488 | bool | |
489 | xfs_scrub_cow_fork( | |
490 | struct scrub_ctx *ctx, | |
491 | uint64_t ino, | |
492 | uint32_t gen, | |
493 | struct xfs_action_list *alist) | |
494 | { | |
495 | return __xfs_scrub_file(ctx, ino, gen, XFS_SCRUB_TYPE_BMBTC, alist); | |
496 | } | |
497 | ||
498 | bool | |
499 | xfs_scrub_dir( | |
500 | struct scrub_ctx *ctx, | |
501 | uint64_t ino, | |
502 | uint32_t gen, | |
503 | struct xfs_action_list *alist) | |
504 | { | |
505 | return __xfs_scrub_file(ctx, ino, gen, XFS_SCRUB_TYPE_DIR, alist); | |
506 | } | |
507 | ||
508 | bool | |
509 | xfs_scrub_attr( | |
510 | struct scrub_ctx *ctx, | |
511 | uint64_t ino, | |
512 | uint32_t gen, | |
513 | struct xfs_action_list *alist) | |
514 | { | |
515 | return __xfs_scrub_file(ctx, ino, gen, XFS_SCRUB_TYPE_XATTR, alist); | |
516 | } | |
517 | ||
518 | bool | |
519 | xfs_scrub_symlink( | |
520 | struct scrub_ctx *ctx, | |
521 | uint64_t ino, | |
522 | uint32_t gen, | |
523 | struct xfs_action_list *alist) | |
524 | { | |
525 | return __xfs_scrub_file(ctx, ino, gen, XFS_SCRUB_TYPE_SYMLINK, alist); | |
526 | } | |
527 | ||
528 | bool | |
529 | xfs_scrub_parent( | |
530 | struct scrub_ctx *ctx, | |
531 | uint64_t ino, | |
532 | uint32_t gen, | |
533 | struct xfs_action_list *alist) | |
534 | { | |
535 | return __xfs_scrub_file(ctx, ino, gen, XFS_SCRUB_TYPE_PARENT, alist); | |
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}; | |
546 | struct xfs_error_injection inject; | |
547 | static bool injected; | |
548 | int error; | |
549 | ||
550 | if (debug_tweak_on("XFS_SCRUB_NO_KERNEL")) | |
551 | return false; | |
552 | if (debug_tweak_on("XFS_SCRUB_FORCE_REPAIR") && !injected) { | |
553 | inject.fd = ctx->mnt.fd; | |
554 | inject.errtag = XFS_ERRTAG_FORCE_SCRUB_REPAIR; | |
555 | error = ioctl(ctx->mnt.fd, XFS_IOC_ERROR_INJECTION, &inject); | |
556 | if (error == 0) | |
557 | injected = true; | |
558 | } | |
559 | ||
560 | meta.sm_type = type; | |
561 | if (repair) | |
562 | meta.sm_flags |= XFS_SCRUB_IFLAG_REPAIR; | |
563 | error = xfrog_scrub_metadata(&ctx->mnt, &meta); | |
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."), | |
580 | _(xfrog_scrubbers[type].descr), | |
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 | } | |
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 | } | |
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, | |
655 | struct action_item *aitem, | |
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 | ||
663 | assert(aitem->type < XFS_SCRUB_TYPE_NR); | |
664 | assert(!debug_tweak_on("XFS_SCRUB_NO_KERNEL")); | |
665 | meta.sm_type = aitem->type; | |
666 | meta.sm_flags = aitem->flags | XFS_SCRUB_IFLAG_REPAIR; | |
667 | switch (xfrog_scrubbers[aitem->type].type) { | |
668 | case XFROG_SCRUB_TYPE_AGHEADER: | |
669 | case XFROG_SCRUB_TYPE_PERAG: | |
670 | meta.sm_agno = aitem->agno; | |
671 | break; | |
672 | case XFROG_SCRUB_TYPE_INODE: | |
673 | meta.sm_ino = aitem->ino; | |
674 | meta.sm_gen = aitem->gen; | |
675 | break; | |
676 | default: | |
677 | break; | |
678 | } | |
679 | ||
680 | if (!is_corrupt(&meta) && (repair_flags & XRM_REPAIR_ONLY)) | |
681 | return CHECK_RETRY; | |
682 | ||
683 | memcpy(&oldm, &meta, sizeof(oldm)); | |
684 | format_scrub_descr(buf, DESCR_BUFSZ, &meta, | |
685 | &xfrog_scrubbers[meta.sm_type]); | |
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 | ||
692 | error = xfrog_scrub_metadata(&ctx->mnt, &meta); | |
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. */ | |
704 | str_info(ctx, buf, | |
705 | _("Filesystem is shut down, aborting.")); | |
706 | return CHECK_ABORT; | |
707 | case ENOTTY: | |
708 | case EOPNOTSUPP: | |
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; | |
719 | /* | |
720 | * If we forced repairs or this is a preen, don't | |
721 | * error out if the kernel doesn't know how to fix. | |
722 | */ | |
723 | if (is_unoptimized(&oldm) || | |
724 | debug_tweak_on("XFS_SCRUB_FORCE_REPAIR")) | |
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: | |
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; | |
756 | str_errno(ctx, buf); | |
757 | return CHECK_DONE; | |
758 | } | |
759 | } | |
760 | if (repair_flags & XRM_COMPLAIN_IF_UNFIXED) | |
761 | xfs_scrub_warn_incomplete_scrub(ctx, buf, &meta); | |
762 | if (needs_repair(&meta)) { | |
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, | |
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 | } |