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