1 // SPDX-License-Identifier: GPL-2.0+
3 * Copyright (c) 2019 Oracle.
6 #include "platform_defs.h"
11 #include "libfrog/logging.h"
12 #include "libfrog/paths.h"
13 #include "libfrog/fsgeom.h"
14 #include "libfrog/bulkstat.h"
17 static cmdinfo_t health_cmd
;
18 static unsigned long long reported
;
19 static bool comprehensive
;
22 static bool has_realtime(const struct xfs_fsop_geom
*g
)
24 return g
->rtblocks
> 0;
27 static bool has_finobt(const struct xfs_fsop_geom
*g
)
29 return g
->flags
& XFS_FSOP_GEOM_FLAGS_FINOBT
;
32 static bool has_rmapbt(const struct xfs_fsop_geom
*g
)
34 return g
->flags
& XFS_FSOP_GEOM_FLAGS_RMAPBT
;
37 static bool has_reflink(const struct xfs_fsop_geom
*g
)
39 return g
->flags
& XFS_FSOP_GEOM_FLAGS_REFLINK
;
44 bool (*has_fn
)(const struct xfs_fsop_geom
*g
);
48 static const struct flag_map fs_flags
[] = {
50 .mask
= XFS_FSOP_GEOM_SICK_COUNTERS
,
51 .descr
= "summary counters",
54 .mask
= XFS_FSOP_GEOM_SICK_UQUOTA
,
55 .descr
= "user quota",
58 .mask
= XFS_FSOP_GEOM_SICK_GQUOTA
,
59 .descr
= "group quota",
62 .mask
= XFS_FSOP_GEOM_SICK_PQUOTA
,
63 .descr
= "project quota",
66 .mask
= XFS_FSOP_GEOM_SICK_RT_BITMAP
,
67 .descr
= "realtime bitmap",
68 .has_fn
= has_realtime
,
71 .mask
= XFS_FSOP_GEOM_SICK_RT_SUMMARY
,
72 .descr
= "realtime summary",
73 .has_fn
= has_realtime
,
78 static const struct flag_map ag_flags
[] = {
80 .mask
= XFS_AG_GEOM_SICK_SB
,
81 .descr
= "superblock",
84 .mask
= XFS_AG_GEOM_SICK_AGF
,
85 .descr
= "AGF header",
88 .mask
= XFS_AG_GEOM_SICK_AGFL
,
89 .descr
= "AGFL header",
92 .mask
= XFS_AG_GEOM_SICK_AGI
,
93 .descr
= "AGI header",
96 .mask
= XFS_AG_GEOM_SICK_BNOBT
,
97 .descr
= "free space by block btree",
100 .mask
= XFS_AG_GEOM_SICK_CNTBT
,
101 .descr
= "free space by length btree",
104 .mask
= XFS_AG_GEOM_SICK_INOBT
,
105 .descr
= "inode btree",
108 .mask
= XFS_AG_GEOM_SICK_FINOBT
,
109 .descr
= "free inode btree",
110 .has_fn
= has_finobt
,
113 .mask
= XFS_AG_GEOM_SICK_RMAPBT
,
114 .descr
= "reverse mappings btree",
115 .has_fn
= has_rmapbt
,
118 .mask
= XFS_AG_GEOM_SICK_REFCNTBT
,
119 .descr
= "reference count btree",
120 .has_fn
= has_reflink
,
125 static const struct flag_map inode_flags
[] = {
127 .mask
= XFS_BS_SICK_INODE
,
128 .descr
= "inode core",
131 .mask
= XFS_BS_SICK_BMBTD
,
132 .descr
= "data fork",
135 .mask
= XFS_BS_SICK_BMBTA
,
136 .descr
= "extended attribute fork",
139 .mask
= XFS_BS_SICK_BMBTC
,
140 .descr
= "copy on write fork",
143 .mask
= XFS_BS_SICK_DIR
,
144 .descr
= "directory",
147 .mask
= XFS_BS_SICK_XATTR
,
148 .descr
= "extended attributes",
151 .mask
= XFS_BS_SICK_SYMLINK
,
152 .descr
= "symbolic link target",
155 .mask
= XFS_BS_SICK_PARENT
,
156 .descr
= "parent pointers",
161 /* Convert a flag mask to a report. */
165 const struct flag_map
*maps
,
167 unsigned int checked
)
169 const struct flag_map
*f
;
172 for (f
= maps
; f
->mask
!= 0; f
++) {
173 if (f
->has_fn
&& !f
->has_fn(&file
->xfd
.fsgeom
))
175 bad
= sick
& f
->mask
;
176 if (!bad
&& !(checked
& f
->mask
))
181 printf("%s %s: %s\n", descr
, _(f
->descr
),
182 bad
? _("unhealthy") : _("ok"));
186 /* Report on an AG's health. */
191 struct xfs_ag_geometry ageo
= { 0 };
195 ret
= -xfrog_ag_geometry(file
->xfd
.fd
, agno
, &ageo
);
197 xfrog_perror(ret
, "ag_geometry");
200 snprintf(descr
, sizeof(descr
) - 1, _("AG %u"), agno
);
201 report_sick(descr
, ag_flags
, ageo
.ag_sick
, ageo
.ag_checked
);
205 /* Report on an inode's health. */
208 unsigned long long ino
,
211 struct xfs_bulkstat bs
;
216 snprintf(d
, sizeof(d
) - 1, _("inode %llu"), ino
);
220 ret
= xfrog_bulkstat_single(&file
->xfd
, ino
, 0, &bs
);
222 xfrog_perror(ret
, descr
);
226 report_sick(descr
, inode_flags
, bs
.bs_sick
, bs
.bs_checked
);
230 /* Report on a file's health. */
235 struct stat stata
, statb
;
238 ret
= lstat(path
, &statb
);
244 ret
= fstat(file
->xfd
.fd
, &stata
);
250 if (stata
.st_dev
!= statb
.st_dev
) {
251 fprintf(stderr
, _("%s: not on the open filesystem"), path
);
255 return report_inode_health(statb
.st_ino
, path
);
258 #define BULKSTAT_NR (128)
261 * Report on all files' health for a given @agno. If @agno is NULLAGNUMBER,
262 * report on all files in the filesystem.
265 report_bulkstat_health(
268 struct xfs_bulkstat_req
*breq
;
273 breq
= xfrog_bulkstat_alloc_req(BULKSTAT_NR
, 0);
275 perror("bulk alloc req");
280 if (agno
!= NULLAGNUMBER
)
281 xfrog_bulkstat_set_ag(breq
, agno
);
284 error
= xfrog_bulkstat(&file
->xfd
, breq
);
287 for (i
= 0; i
< breq
->hdr
.ocount
; i
++) {
288 snprintf(descr
, sizeof(descr
) - 1, _("inode %"PRIu64
),
289 breq
->bulkstat
[i
].bs_ino
);
290 report_sick(descr
, inode_flags
,
291 breq
->bulkstat
[i
].bs_sick
,
292 breq
->bulkstat
[i
].bs_checked
);
294 } while (breq
->hdr
.ocount
> 0);
297 xfrog_perror(error
, "bulkstat");
303 #define OPT_STRING ("a:cfi:q")
305 /* Report on health problems in XFS filesystem. */
311 unsigned long long x
;
313 bool default_report
= true;
319 if (file
->xfd
.fsgeom
.version
!= XFS_FSOP_GEOM_VERSION_V5
) {
324 /* Set our reporting options appropriately in the first pass. */
325 while ((c
= getopt(argc
, argv
, OPT_STRING
)) != EOF
) {
328 default_report
= false;
330 x
= strtoll(optarg
, NULL
, 10);
331 if (!errno
&& x
>= NULLAGNUMBER
)
339 comprehensive
= true;
342 default_report
= false;
345 default_report
= false;
347 x
= strtoll(optarg
, NULL
, 10);
349 perror("inode health");
357 return command_usage(&health_cmd
);
361 default_report
= false;
363 /* Reparse arguments, this time for reporting actions. */
365 while ((c
= getopt(argc
, argv
, OPT_STRING
)) != EOF
) {
368 agno
= strtoll(optarg
, NULL
, 10);
369 ret
= report_ag_sick(agno
);
370 if (!ret
&& comprehensive
)
371 ret
= report_bulkstat_health(agno
);
376 report_sick(_("filesystem"), fs_flags
,
377 file
->xfd
.fsgeom
.sick
,
378 file
->xfd
.fsgeom
.checked
);
380 ret
= report_bulkstat_health(NULLAGNUMBER
);
386 x
= strtoll(optarg
, NULL
, 10);
387 ret
= report_inode_health(x
, NULL
);
396 for (c
= optind
; c
< argc
; c
++) {
397 ret
= report_file_health(argv
[c
]);
402 /* No arguments gets us a summary of fs state. */
403 if (default_report
) {
404 report_sick(_("filesystem"), fs_flags
, file
->xfd
.fsgeom
.sick
,
405 file
->xfd
.fsgeom
.checked
);
407 for (agno
= 0; agno
< file
->xfd
.fsgeom
.agcount
; agno
++) {
408 ret
= report_ag_sick(agno
);
413 ret
= report_bulkstat_health(NULLAGNUMBER
);
421 _("Health status has not been collected for this filesystem.\n"));
423 _("Please run xfs_scrub(8) to remedy this situation.\n"));
434 "Report all observed filesystem health problems.\n"
436 " -a agno -- Report health of the given allocation group.\n"
437 " -c -- Report on the health of all inodes.\n"
438 " -f -- Report health of the overall filesystem.\n"
439 " -i inum -- Report health of a given inode number.\n"
440 " -q -- Only report unhealthy metadata.\n"
441 " paths -- Report health of the given file path.\n"
446 static cmdinfo_t health_cmd
= {
451 .args
= "[-a agno] [-c] [-f] [-i inum] [-q] [paths]",
452 .flags
= CMD_FLAG_ONESHOT
,
459 health_cmd
.oneline
= _("Report observed XFS health problems."),
460 add_command(&health_cmd
);