]>
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 | ||
fd7d73c0 DW |
279 | /* Scrub metadata, saving corruption reports for later. */ |
280 | static bool | |
281 | xfs_scrub_metadata( | |
282 | struct scrub_ctx *ctx, | |
5ef3b66a | 283 | enum xfrog_scrub_type scrub_type, |
ee310b0c DW |
284 | xfs_agnumber_t agno, |
285 | struct xfs_action_list *alist) | |
fd7d73c0 DW |
286 | { |
287 | struct xfs_scrub_metadata meta = {0}; | |
5ef3b66a | 288 | const struct xfrog_scrub_descr *sc; |
fd7d73c0 DW |
289 | enum check_outcome fix; |
290 | int type; | |
291 | ||
5ef3b66a | 292 | sc = xfrog_scrubbers; |
fd7d73c0 DW |
293 | for (type = 0; type < XFS_SCRUB_TYPE_NR; type++, sc++) { |
294 | if (sc->type != scrub_type) | |
295 | continue; | |
8dd3922c DW |
296 | if (sc->flags & XFROG_SCRUB_DESCR_SUMMARY) |
297 | continue; | |
fd7d73c0 DW |
298 | |
299 | meta.sm_type = type; | |
300 | meta.sm_flags = 0; | |
301 | meta.sm_agno = agno; | |
302 | background_sleep(); | |
303 | ||
304 | /* Check the item. */ | |
991e5a84 | 305 | fix = xfs_check_metadata(ctx, &meta, false); |
ed60d210 | 306 | progress_add(1); |
fd7d73c0 DW |
307 | switch (fix) { |
308 | case CHECK_ABORT: | |
309 | return false; | |
310 | case CHECK_REPAIR: | |
ee310b0c DW |
311 | if (!xfs_scrub_save_repair(ctx, alist, &meta)) |
312 | return false; | |
fd7d73c0 DW |
313 | /* fall through */ |
314 | case CHECK_DONE: | |
315 | continue; | |
316 | case CHECK_RETRY: | |
317 | abort(); | |
318 | break; | |
319 | } | |
320 | } | |
321 | ||
322 | return true; | |
323 | } | |
324 | ||
325 | /* | |
326 | * Scrub primary superblock. This will be useful if we ever need to hook | |
327 | * a filesystem-wide pre-scrub activity off of the sb 0 scrubber (which | |
328 | * currently does nothing). | |
329 | */ | |
330 | bool | |
331 | xfs_scrub_primary_super( | |
ee310b0c DW |
332 | struct scrub_ctx *ctx, |
333 | struct xfs_action_list *alist) | |
fd7d73c0 DW |
334 | { |
335 | struct xfs_scrub_metadata meta = { | |
336 | .sm_type = XFS_SCRUB_TYPE_SB, | |
337 | }; | |
338 | enum check_outcome fix; | |
339 | ||
340 | /* Check the item. */ | |
991e5a84 | 341 | fix = xfs_check_metadata(ctx, &meta, false); |
fd7d73c0 DW |
342 | switch (fix) { |
343 | case CHECK_ABORT: | |
344 | return false; | |
345 | case CHECK_REPAIR: | |
ee310b0c DW |
346 | if (!xfs_scrub_save_repair(ctx, alist, &meta)) |
347 | return false; | |
fd7d73c0 DW |
348 | /* fall through */ |
349 | case CHECK_DONE: | |
350 | return true; | |
351 | case CHECK_RETRY: | |
352 | abort(); | |
353 | break; | |
354 | } | |
355 | ||
356 | return true; | |
357 | } | |
358 | ||
359 | /* Scrub each AG's header blocks. */ | |
360 | bool | |
361 | xfs_scrub_ag_headers( | |
362 | struct scrub_ctx *ctx, | |
ee310b0c DW |
363 | xfs_agnumber_t agno, |
364 | struct xfs_action_list *alist) | |
fd7d73c0 | 365 | { |
5ef3b66a | 366 | return xfs_scrub_metadata(ctx, XFROG_SCRUB_TYPE_AGHEADER, agno, alist); |
fd7d73c0 DW |
367 | } |
368 | ||
369 | /* Scrub each AG's metadata btrees. */ | |
370 | bool | |
371 | xfs_scrub_ag_metadata( | |
372 | struct scrub_ctx *ctx, | |
ee310b0c DW |
373 | xfs_agnumber_t agno, |
374 | struct xfs_action_list *alist) | |
fd7d73c0 | 375 | { |
5ef3b66a | 376 | return xfs_scrub_metadata(ctx, XFROG_SCRUB_TYPE_PERAG, agno, alist); |
fd7d73c0 DW |
377 | } |
378 | ||
379 | /* Scrub whole-FS metadata btrees. */ | |
380 | bool | |
381 | xfs_scrub_fs_metadata( | |
ee310b0c DW |
382 | struct scrub_ctx *ctx, |
383 | struct xfs_action_list *alist) | |
fd7d73c0 | 384 | { |
5ef3b66a | 385 | return xfs_scrub_metadata(ctx, XFROG_SCRUB_TYPE_FS, 0, alist); |
fd7d73c0 DW |
386 | } |
387 | ||
ed60d210 DW |
388 | /* How many items do we have to check? */ |
389 | unsigned int | |
390 | xfs_scrub_estimate_ag_work( | |
391 | struct scrub_ctx *ctx) | |
392 | { | |
5ef3b66a | 393 | const struct xfrog_scrub_descr *sc; |
ed60d210 DW |
394 | int type; |
395 | unsigned int estimate = 0; | |
396 | ||
5ef3b66a | 397 | sc = xfrog_scrubbers; |
ed60d210 DW |
398 | for (type = 0; type < XFS_SCRUB_TYPE_NR; type++, sc++) { |
399 | switch (sc->type) { | |
5ef3b66a DW |
400 | case XFROG_SCRUB_TYPE_AGHEADER: |
401 | case XFROG_SCRUB_TYPE_PERAG: | |
3f9efb2e | 402 | estimate += ctx->mnt.fsgeom.agcount; |
ed60d210 | 403 | break; |
5ef3b66a | 404 | case XFROG_SCRUB_TYPE_FS: |
ed60d210 DW |
405 | estimate++; |
406 | break; | |
407 | default: | |
408 | break; | |
409 | } | |
410 | } | |
411 | return estimate; | |
412 | } | |
413 | ||
fd7d73c0 DW |
414 | /* Scrub inode metadata. */ |
415 | static bool | |
416 | __xfs_scrub_file( | |
417 | struct scrub_ctx *ctx, | |
418 | uint64_t ino, | |
419 | uint32_t gen, | |
ee310b0c DW |
420 | unsigned int type, |
421 | struct xfs_action_list *alist) | |
fd7d73c0 DW |
422 | { |
423 | struct xfs_scrub_metadata meta = {0}; | |
424 | enum check_outcome fix; | |
425 | ||
426 | assert(type < XFS_SCRUB_TYPE_NR); | |
5ef3b66a | 427 | assert(xfrog_scrubbers[type].type == XFROG_SCRUB_TYPE_INODE); |
fd7d73c0 DW |
428 | |
429 | meta.sm_type = type; | |
430 | meta.sm_ino = ino; | |
431 | meta.sm_gen = gen; | |
432 | ||
433 | /* Scrub the piece of metadata. */ | |
991e5a84 | 434 | fix = xfs_check_metadata(ctx, &meta, true); |
fd7d73c0 DW |
435 | if (fix == CHECK_ABORT) |
436 | return false; | |
437 | if (fix == CHECK_DONE) | |
438 | return true; | |
439 | ||
ee310b0c | 440 | return xfs_scrub_save_repair(ctx, alist, &meta); |
fd7d73c0 DW |
441 | } |
442 | ||
443 | bool | |
444 | xfs_scrub_inode_fields( | |
445 | struct scrub_ctx *ctx, | |
446 | uint64_t ino, | |
447 | uint32_t gen, | |
ee310b0c | 448 | struct xfs_action_list *alist) |
fd7d73c0 | 449 | { |
991e5a84 | 450 | return __xfs_scrub_file(ctx, ino, gen, XFS_SCRUB_TYPE_INODE, alist); |
fd7d73c0 DW |
451 | } |
452 | ||
453 | bool | |
454 | xfs_scrub_data_fork( | |
455 | struct scrub_ctx *ctx, | |
456 | uint64_t ino, | |
457 | uint32_t gen, | |
ee310b0c | 458 | struct xfs_action_list *alist) |
fd7d73c0 | 459 | { |
991e5a84 | 460 | return __xfs_scrub_file(ctx, ino, gen, XFS_SCRUB_TYPE_BMBTD, alist); |
fd7d73c0 DW |
461 | } |
462 | ||
463 | bool | |
464 | xfs_scrub_attr_fork( | |
465 | struct scrub_ctx *ctx, | |
466 | uint64_t ino, | |
467 | uint32_t gen, | |
ee310b0c | 468 | struct xfs_action_list *alist) |
fd7d73c0 | 469 | { |
991e5a84 | 470 | return __xfs_scrub_file(ctx, ino, gen, XFS_SCRUB_TYPE_BMBTA, alist); |
fd7d73c0 DW |
471 | } |
472 | ||
473 | bool | |
474 | xfs_scrub_cow_fork( | |
475 | struct scrub_ctx *ctx, | |
476 | uint64_t ino, | |
477 | uint32_t gen, | |
ee310b0c | 478 | struct xfs_action_list *alist) |
fd7d73c0 | 479 | { |
991e5a84 | 480 | return __xfs_scrub_file(ctx, ino, gen, XFS_SCRUB_TYPE_BMBTC, alist); |
fd7d73c0 DW |
481 | } |
482 | ||
483 | bool | |
484 | xfs_scrub_dir( | |
485 | struct scrub_ctx *ctx, | |
486 | uint64_t ino, | |
487 | uint32_t gen, | |
ee310b0c | 488 | struct xfs_action_list *alist) |
fd7d73c0 | 489 | { |
991e5a84 | 490 | return __xfs_scrub_file(ctx, ino, gen, XFS_SCRUB_TYPE_DIR, alist); |
fd7d73c0 DW |
491 | } |
492 | ||
493 | bool | |
494 | xfs_scrub_attr( | |
495 | struct scrub_ctx *ctx, | |
496 | uint64_t ino, | |
497 | uint32_t gen, | |
ee310b0c | 498 | struct xfs_action_list *alist) |
fd7d73c0 | 499 | { |
991e5a84 | 500 | return __xfs_scrub_file(ctx, ino, gen, XFS_SCRUB_TYPE_XATTR, alist); |
fd7d73c0 DW |
501 | } |
502 | ||
503 | bool | |
504 | xfs_scrub_symlink( | |
505 | struct scrub_ctx *ctx, | |
506 | uint64_t ino, | |
507 | uint32_t gen, | |
ee310b0c | 508 | struct xfs_action_list *alist) |
fd7d73c0 | 509 | { |
991e5a84 | 510 | return __xfs_scrub_file(ctx, ino, gen, XFS_SCRUB_TYPE_SYMLINK, alist); |
fd7d73c0 DW |
511 | } |
512 | ||
513 | bool | |
514 | xfs_scrub_parent( | |
515 | struct scrub_ctx *ctx, | |
516 | uint64_t ino, | |
517 | uint32_t gen, | |
ee310b0c | 518 | struct xfs_action_list *alist) |
fd7d73c0 | 519 | { |
991e5a84 | 520 | return __xfs_scrub_file(ctx, ino, gen, XFS_SCRUB_TYPE_PARENT, alist); |
fd7d73c0 DW |
521 | } |
522 | ||
523 | /* Test the availability of a kernel scrub command. */ | |
524 | static bool | |
525 | __xfs_scrub_test( | |
526 | struct scrub_ctx *ctx, | |
527 | unsigned int type, | |
528 | bool repair) | |
529 | { | |
530 | struct xfs_scrub_metadata meta = {0}; | |
7a9d1ac4 | 531 | struct xfs_error_injection inject; |
19852474 | 532 | static bool injected; |
fd7d73c0 DW |
533 | int error; |
534 | ||
535 | if (debug_tweak_on("XFS_SCRUB_NO_KERNEL")) | |
536 | return false; | |
19852474 | 537 | if (debug_tweak_on("XFS_SCRUB_FORCE_REPAIR") && !injected) { |
3f9efb2e | 538 | inject.fd = ctx->mnt.fd; |
7a9d1ac4 | 539 | inject.errtag = XFS_ERRTAG_FORCE_SCRUB_REPAIR; |
3f9efb2e | 540 | error = ioctl(ctx->mnt.fd, XFS_IOC_ERROR_INJECTION, &inject); |
7a9d1ac4 DW |
541 | if (error == 0) |
542 | injected = true; | |
19852474 | 543 | } |
fd7d73c0 DW |
544 | |
545 | meta.sm_type = type; | |
546 | if (repair) | |
547 | meta.sm_flags |= XFS_SCRUB_IFLAG_REPAIR; | |
5ef3b66a | 548 | error = xfrog_scrub_metadata(&ctx->mnt, &meta); |
fd7d73c0 DW |
549 | if (!error) |
550 | return true; | |
551 | switch (errno) { | |
552 | case EROFS: | |
553 | str_info(ctx, ctx->mntpoint, | |
554 | _("Filesystem is mounted read-only; cannot proceed.")); | |
555 | return false; | |
556 | case ENOTRECOVERABLE: | |
557 | str_info(ctx, ctx->mntpoint, | |
558 | _("Filesystem is mounted norecovery; cannot proceed.")); | |
559 | return false; | |
560 | case EOPNOTSUPP: | |
561 | case ENOTTY: | |
562 | if (debug || verbose) | |
563 | str_info(ctx, ctx->mntpoint, | |
564 | _("Kernel %s %s facility not detected."), | |
5ef3b66a | 565 | _(xfrog_scrubbers[type].descr), |
fd7d73c0 DW |
566 | repair ? _("repair") : _("scrub")); |
567 | return false; | |
568 | case ENOENT: | |
569 | /* Scrubber says not present on this fs; that's fine. */ | |
570 | return true; | |
571 | default: | |
572 | str_info(ctx, ctx->mntpoint, "%s", strerror(errno)); | |
573 | return true; | |
574 | } | |
fd7d73c0 DW |
575 | } |
576 | ||
577 | bool | |
578 | xfs_can_scrub_fs_metadata( | |
579 | struct scrub_ctx *ctx) | |
580 | { | |
581 | return __xfs_scrub_test(ctx, XFS_SCRUB_TYPE_PROBE, false); | |
582 | } | |
583 | ||
584 | bool | |
585 | xfs_can_scrub_inode( | |
586 | struct scrub_ctx *ctx) | |
587 | { | |
588 | return __xfs_scrub_test(ctx, XFS_SCRUB_TYPE_INODE, false); | |
589 | } | |
590 | ||
591 | bool | |
592 | xfs_can_scrub_bmap( | |
593 | struct scrub_ctx *ctx) | |
594 | { | |
595 | return __xfs_scrub_test(ctx, XFS_SCRUB_TYPE_BMBTD, false); | |
596 | } | |
597 | ||
598 | bool | |
599 | xfs_can_scrub_dir( | |
600 | struct scrub_ctx *ctx) | |
601 | { | |
602 | return __xfs_scrub_test(ctx, XFS_SCRUB_TYPE_DIR, false); | |
603 | } | |
604 | ||
605 | bool | |
606 | xfs_can_scrub_attr( | |
607 | struct scrub_ctx *ctx) | |
608 | { | |
609 | return __xfs_scrub_test(ctx, XFS_SCRUB_TYPE_XATTR, false); | |
610 | } | |
611 | ||
612 | bool | |
613 | xfs_can_scrub_symlink( | |
614 | struct scrub_ctx *ctx) | |
615 | { | |
616 | return __xfs_scrub_test(ctx, XFS_SCRUB_TYPE_SYMLINK, false); | |
617 | } | |
618 | ||
619 | bool | |
620 | xfs_can_scrub_parent( | |
621 | struct scrub_ctx *ctx) | |
622 | { | |
623 | return __xfs_scrub_test(ctx, XFS_SCRUB_TYPE_PARENT, false); | |
624 | } | |
19852474 DW |
625 | |
626 | bool | |
627 | xfs_can_repair( | |
628 | struct scrub_ctx *ctx) | |
629 | { | |
630 | return __xfs_scrub_test(ctx, XFS_SCRUB_TYPE_PROBE, true); | |
631 | } | |
632 | ||
633 | /* General repair routines. */ | |
634 | ||
635 | /* Repair some metadata. */ | |
636 | enum check_outcome | |
637 | xfs_repair_metadata( | |
638 | struct scrub_ctx *ctx, | |
639 | int fd, | |
ee310b0c | 640 | struct action_item *aitem, |
19852474 DW |
641 | unsigned int repair_flags) |
642 | { | |
643 | char buf[DESCR_BUFSZ]; | |
644 | struct xfs_scrub_metadata meta = { 0 }; | |
645 | struct xfs_scrub_metadata oldm; | |
646 | int error; | |
647 | ||
ee310b0c | 648 | assert(aitem->type < XFS_SCRUB_TYPE_NR); |
19852474 | 649 | assert(!debug_tweak_on("XFS_SCRUB_NO_KERNEL")); |
ee310b0c DW |
650 | meta.sm_type = aitem->type; |
651 | meta.sm_flags = aitem->flags | XFS_SCRUB_IFLAG_REPAIR; | |
5ef3b66a DW |
652 | switch (xfrog_scrubbers[aitem->type].type) { |
653 | case XFROG_SCRUB_TYPE_AGHEADER: | |
654 | case XFROG_SCRUB_TYPE_PERAG: | |
ee310b0c | 655 | meta.sm_agno = aitem->agno; |
19852474 | 656 | break; |
5ef3b66a | 657 | case XFROG_SCRUB_TYPE_INODE: |
ee310b0c DW |
658 | meta.sm_ino = aitem->ino; |
659 | meta.sm_gen = aitem->gen; | |
19852474 DW |
660 | break; |
661 | default: | |
662 | break; | |
663 | } | |
664 | ||
ee310b0c | 665 | if (!is_corrupt(&meta) && (repair_flags & XRM_REPAIR_ONLY)) |
19852474 DW |
666 | return CHECK_RETRY; |
667 | ||
668 | memcpy(&oldm, &meta, sizeof(oldm)); | |
5ef3b66a DW |
669 | format_scrub_descr(buf, DESCR_BUFSZ, &meta, |
670 | &xfrog_scrubbers[meta.sm_type]); | |
19852474 DW |
671 | |
672 | if (needs_repair(&meta)) | |
673 | str_info(ctx, buf, _("Attempting repair.")); | |
674 | else if (debug || verbose) | |
675 | str_info(ctx, buf, _("Attempting optimization.")); | |
676 | ||
5ef3b66a | 677 | error = xfrog_scrub_metadata(&ctx->mnt, &meta); |
19852474 DW |
678 | if (error) { |
679 | switch (errno) { | |
680 | case EDEADLOCK: | |
681 | case EBUSY: | |
682 | /* Filesystem is busy, try again later. */ | |
683 | if (debug || verbose) | |
684 | str_info(ctx, buf, | |
685 | _("Filesystem is busy, deferring repair.")); | |
686 | return CHECK_RETRY; | |
687 | case ESHUTDOWN: | |
688 | /* Filesystem is already shut down, abort. */ | |
82377bde | 689 | str_info(ctx, buf, |
19852474 DW |
690 | _("Filesystem is shut down, aborting.")); |
691 | return CHECK_ABORT; | |
692 | case ENOTTY: | |
693 | case EOPNOTSUPP: | |
c3a140f0 DW |
694 | /* |
695 | * If we're in no-complain mode, requeue the check for | |
696 | * later. It's possible that an error in another | |
697 | * component caused us to flag an error in this | |
698 | * component. Even if the kernel didn't think it | |
699 | * could fix this, it's at least worth trying the scan | |
700 | * again to see if another repair fixed it. | |
701 | */ | |
702 | if (!(repair_flags & XRM_COMPLAIN_IF_UNFIXED)) | |
703 | return CHECK_RETRY; | |
19852474 | 704 | /* |
b07c2ba5 DW |
705 | * If we forced repairs or this is a preen, don't |
706 | * error out if the kernel doesn't know how to fix. | |
19852474 | 707 | */ |
b07c2ba5 DW |
708 | if (is_unoptimized(&oldm) || |
709 | debug_tweak_on("XFS_SCRUB_FORCE_REPAIR")) | |
19852474 DW |
710 | return CHECK_DONE; |
711 | /* fall through */ | |
712 | case EINVAL: | |
713 | /* Kernel doesn't know how to repair this? */ | |
714 | str_error(ctx, buf, | |
715 | _("Don't know how to fix; offline repair required.")); | |
716 | return CHECK_DONE; | |
717 | case EROFS: | |
718 | /* Read-only filesystem, can't fix. */ | |
719 | if (verbose || debug || needs_repair(&oldm)) | |
720 | str_info(ctx, buf, | |
721 | _("Read-only filesystem; cannot make changes.")); | |
722 | return CHECK_DONE; | |
723 | case ENOENT: | |
724 | /* Metadata not present, just skip it. */ | |
725 | return CHECK_DONE; | |
726 | case ENOMEM: | |
727 | case ENOSPC: | |
728 | /* Don't care if preen fails due to low resources. */ | |
729 | if (is_unoptimized(&oldm) && !needs_repair(&oldm)) | |
730 | return CHECK_DONE; | |
731 | /* fall through */ | |
732 | default: | |
c3a140f0 DW |
733 | /* |
734 | * Operational error. If the caller doesn't want us | |
735 | * to complain about repair failures, tell the caller | |
736 | * to requeue the repair for later and don't say a | |
737 | * thing. Otherwise, print error and bail out. | |
738 | */ | |
739 | if (!(repair_flags & XRM_COMPLAIN_IF_UNFIXED)) | |
740 | return CHECK_RETRY; | |
19852474 DW |
741 | str_errno(ctx, buf); |
742 | return CHECK_DONE; | |
743 | } | |
744 | } | |
06e49f3e | 745 | if (repair_flags & XRM_COMPLAIN_IF_UNFIXED) |
19852474 DW |
746 | xfs_scrub_warn_incomplete_scrub(ctx, buf, &meta); |
747 | if (needs_repair(&meta)) { | |
c3a140f0 DW |
748 | /* |
749 | * Still broken; if we've been told not to complain then we | |
750 | * just requeue this and try again later. Otherwise we | |
751 | * log the error loudly and don't try again. | |
752 | */ | |
753 | if (!(repair_flags & XRM_COMPLAIN_IF_UNFIXED)) | |
754 | return CHECK_RETRY; | |
755 | str_error(ctx, buf, | |
19852474 DW |
756 | _("Repair unsuccessful; offline repair required.")); |
757 | } else { | |
758 | /* Clean operation, no corruption detected. */ | |
759 | if (needs_repair(&oldm)) | |
760 | record_repair(ctx, buf, _("Repairs successful.")); | |
761 | else | |
762 | record_preen(ctx, buf, _("Optimization successful.")); | |
763 | } | |
764 | return CHECK_DONE; | |
765 | } |