]> git.ipfire.org Git - thirdparty/xfsprogs-dev.git/blob - spaceman/health.c
8fd985a291fc74608819eefefe7d02da0bb526fd
[thirdparty/xfsprogs-dev.git] / spaceman / health.c
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3 * Copyright (c) 2019 Oracle.
4 * All Rights Reserved.
5 */
6 #include "platform_defs.h"
7 #include "libxfs.h"
8 #include "command.h"
9 #include "init.h"
10 #include "input.h"
11 #include "libfrog/paths.h"
12 #include "libfrog/fsgeom.h"
13 #include "libfrog/bulkstat.h"
14 #include "space.h"
15
16 static cmdinfo_t health_cmd;
17 static unsigned long long reported;
18 static bool comprehensive;
19 static bool quiet;
20
21 static bool has_realtime(const struct xfs_fsop_geom *g)
22 {
23 return g->rtblocks > 0;
24 }
25
26 static bool has_finobt(const struct xfs_fsop_geom *g)
27 {
28 return g->flags & XFS_FSOP_GEOM_FLAGS_FINOBT;
29 }
30
31 static bool has_rmapbt(const struct xfs_fsop_geom *g)
32 {
33 return g->flags & XFS_FSOP_GEOM_FLAGS_RMAPBT;
34 }
35
36 static bool has_reflink(const struct xfs_fsop_geom *g)
37 {
38 return g->flags & XFS_FSOP_GEOM_FLAGS_REFLINK;
39 }
40
41 struct flag_map {
42 unsigned int mask;
43 bool (*has_fn)(const struct xfs_fsop_geom *g);
44 const char *descr;
45 };
46
47 static const struct flag_map fs_flags[] = {
48 {
49 .mask = XFS_FSOP_GEOM_SICK_COUNTERS,
50 .descr = "summary counters",
51 },
52 {
53 .mask = XFS_FSOP_GEOM_SICK_UQUOTA,
54 .descr = "user quota",
55 },
56 {
57 .mask = XFS_FSOP_GEOM_SICK_GQUOTA,
58 .descr = "group quota",
59 },
60 {
61 .mask = XFS_FSOP_GEOM_SICK_PQUOTA,
62 .descr = "project quota",
63 },
64 {
65 .mask = XFS_FSOP_GEOM_SICK_RT_BITMAP,
66 .descr = "realtime bitmap",
67 .has_fn = has_realtime,
68 },
69 {
70 .mask = XFS_FSOP_GEOM_SICK_RT_SUMMARY,
71 .descr = "realtime summary",
72 .has_fn = has_realtime,
73 },
74 {0},
75 };
76
77 static const struct flag_map ag_flags[] = {
78 {
79 .mask = XFS_AG_GEOM_SICK_SB,
80 .descr = "superblock",
81 },
82 {
83 .mask = XFS_AG_GEOM_SICK_AGF,
84 .descr = "AGF header",
85 },
86 {
87 .mask = XFS_AG_GEOM_SICK_AGFL,
88 .descr = "AGFL header",
89 },
90 {
91 .mask = XFS_AG_GEOM_SICK_AGI,
92 .descr = "AGI header",
93 },
94 {
95 .mask = XFS_AG_GEOM_SICK_BNOBT,
96 .descr = "free space by block btree",
97 },
98 {
99 .mask = XFS_AG_GEOM_SICK_CNTBT,
100 .descr = "free space by length btree",
101 },
102 {
103 .mask = XFS_AG_GEOM_SICK_INOBT,
104 .descr = "inode btree",
105 },
106 {
107 .mask = XFS_AG_GEOM_SICK_FINOBT,
108 .descr = "free inode btree",
109 .has_fn = has_finobt,
110 },
111 {
112 .mask = XFS_AG_GEOM_SICK_RMAPBT,
113 .descr = "reverse mappings btree",
114 .has_fn = has_rmapbt,
115 },
116 {
117 .mask = XFS_AG_GEOM_SICK_REFCNTBT,
118 .descr = "reference count btree",
119 .has_fn = has_reflink,
120 },
121 {0},
122 };
123
124 static const struct flag_map inode_flags[] = {
125 {
126 .mask = XFS_BS_SICK_INODE,
127 .descr = "inode core",
128 },
129 {
130 .mask = XFS_BS_SICK_BMBTD,
131 .descr = "data fork",
132 },
133 {
134 .mask = XFS_BS_SICK_BMBTA,
135 .descr = "extended attribute fork",
136 },
137 {
138 .mask = XFS_BS_SICK_BMBTC,
139 .descr = "copy on write fork",
140 },
141 {
142 .mask = XFS_BS_SICK_DIR,
143 .descr = "directory",
144 },
145 {
146 .mask = XFS_BS_SICK_XATTR,
147 .descr = "extended attributes",
148 },
149 {
150 .mask = XFS_BS_SICK_SYMLINK,
151 .descr = "symbolic link target",
152 },
153 {
154 .mask = XFS_BS_SICK_PARENT,
155 .descr = "parent pointers",
156 },
157 {0},
158 };
159
160 /* Convert a flag mask to a report. */
161 static void
162 report_sick(
163 const char *descr,
164 const struct flag_map *maps,
165 unsigned int sick,
166 unsigned int checked)
167 {
168 const struct flag_map *f;
169 bool bad;
170
171 for (f = maps; f->mask != 0; f++) {
172 if (f->has_fn && !f->has_fn(&file->xfd.fsgeom))
173 continue;
174 if (!(checked & f->mask))
175 continue;
176 reported++;
177 bad = sick & f->mask;
178 if (!bad && quiet)
179 continue;
180 printf("%s %s: %s\n", descr, _(f->descr),
181 bad ? _("unhealthy") : _("ok"));
182 }
183 }
184
185 /* Report on an AG's health. */
186 static int
187 report_ag_sick(
188 xfs_agnumber_t agno)
189 {
190 struct xfs_ag_geometry ageo = { 0 };
191 char descr[256];
192 int ret;
193
194 ret = xfrog_ag_geometry(file->xfd.fd, agno, &ageo);
195 if (ret) {
196 errno = ret;
197 perror("ag_geometry");
198 return 1;
199 }
200 snprintf(descr, sizeof(descr) - 1, _("AG %u"), agno);
201 report_sick(descr, ag_flags, ageo.ag_sick, ageo.ag_checked);
202 return 0;
203 }
204
205 /* Report on an inode's health. */
206 static int
207 report_inode_health(
208 unsigned long long ino,
209 const char *descr)
210 {
211 struct xfs_bulkstat bs;
212 char d[256];
213 int ret;
214
215 if (!descr) {
216 snprintf(d, sizeof(d) - 1, _("inode %llu"), ino);
217 descr = d;
218 }
219
220 ret = xfrog_bulkstat_single(&file->xfd, ino, 0, &bs);
221 if (ret) {
222 errno = ret;
223 perror(descr);
224 return 1;
225 }
226
227 report_sick(descr, inode_flags, bs.bs_sick, bs.bs_checked);
228 return 0;
229 }
230
231 /* Report on a file's health. */
232 static int
233 report_file_health(
234 const char *path)
235 {
236 struct stat stata, statb;
237 int ret;
238
239 ret = lstat(path, &statb);
240 if (ret) {
241 perror(path);
242 return 1;
243 }
244
245 ret = fstat(file->xfd.fd, &stata);
246 if (ret) {
247 perror(file->name);
248 return 1;
249 }
250
251 if (stata.st_dev != statb.st_dev) {
252 fprintf(stderr, _("%s: not on the open filesystem"), path);
253 return 1;
254 }
255
256 return report_inode_health(statb.st_ino, path);
257 }
258
259 #define BULKSTAT_NR (128)
260
261 /*
262 * Report on all files' health for a given @agno. If @agno is NULLAGNUMBER,
263 * report on all files in the filesystem.
264 */
265 static int
266 report_bulkstat_health(
267 xfs_agnumber_t agno)
268 {
269 struct xfs_bulkstat_req *breq;
270 char descr[256];
271 uint32_t i;
272 int error;
273
274 breq = xfrog_bulkstat_alloc_req(BULKSTAT_NR, 0);
275 if (!breq) {
276 perror("bulk alloc req");
277 exitcode = 1;
278 return 1;
279 }
280
281 if (agno != NULLAGNUMBER)
282 xfrog_bulkstat_set_ag(breq, agno);
283
284 do {
285 error = xfrog_bulkstat(&file->xfd, breq);
286 if (error)
287 break;
288 for (i = 0; i < breq->hdr.ocount; i++) {
289 snprintf(descr, sizeof(descr) - 1, _("inode %"PRIu64),
290 breq->bulkstat[i].bs_ino);
291 report_sick(descr, inode_flags,
292 breq->bulkstat[i].bs_sick,
293 breq->bulkstat[i].bs_checked);
294 }
295 } while (breq->hdr.ocount > 0);
296
297 if (error) {
298 errno = error;
299 perror("bulkstat");
300 }
301
302 free(breq);
303 return error;
304 }
305
306 #define OPT_STRING ("a:cfi:q")
307
308 /* Report on health problems in XFS filesystem. */
309 static int
310 health_f(
311 int argc,
312 char **argv)
313 {
314 unsigned long long x;
315 xfs_agnumber_t agno;
316 bool default_report = true;
317 int c;
318 int ret;
319
320 reported = 0;
321
322 if (file->xfd.fsgeom.version != XFS_FSOP_GEOM_VERSION_V5) {
323 perror("health");
324 return 1;
325 }
326
327 /* Set our reporting options appropriately in the first pass. */
328 while ((c = getopt(argc, argv, OPT_STRING)) != EOF) {
329 switch (c) {
330 case 'a':
331 default_report = false;
332 errno = 0;
333 x = strtoll(optarg, NULL, 10);
334 if (!errno && x >= NULLAGNUMBER)
335 errno = ERANGE;
336 if (errno) {
337 perror("ag health");
338 return 1;
339 }
340 break;
341 case 'c':
342 comprehensive = true;
343 break;
344 case 'f':
345 default_report = false;
346 break;
347 case 'i':
348 default_report = false;
349 errno = 0;
350 x = strtoll(optarg, NULL, 10);
351 if (errno) {
352 perror("inode health");
353 return 1;
354 }
355 break;
356 case 'q':
357 quiet = true;
358 break;
359 default:
360 return command_usage(&health_cmd);
361 }
362 }
363 if (optind < argc)
364 default_report = false;
365
366 /* Reparse arguments, this time for reporting actions. */
367 optind = 1;
368 while ((c = getopt(argc, argv, OPT_STRING)) != EOF) {
369 switch (c) {
370 case 'a':
371 agno = strtoll(optarg, NULL, 10);
372 ret = report_ag_sick(agno);
373 if (!ret && comprehensive)
374 ret = report_bulkstat_health(agno);
375 if (ret)
376 return 1;
377 break;
378 case 'f':
379 report_sick(_("filesystem"), fs_flags,
380 file->xfd.fsgeom.sick,
381 file->xfd.fsgeom.checked);
382 if (comprehensive) {
383 ret = report_bulkstat_health(NULLAGNUMBER);
384 if (ret)
385 return 1;
386 }
387 break;
388 case 'i':
389 x = strtoll(optarg, NULL, 10);
390 ret = report_inode_health(x, NULL);
391 if (ret)
392 return 1;
393 break;
394 default:
395 break;
396 }
397 }
398
399 for (c = optind; c < argc; c++) {
400 ret = report_file_health(argv[c]);
401 if (ret)
402 return 1;
403 }
404
405 /* No arguments gets us a summary of fs state. */
406 if (default_report) {
407 report_sick(_("filesystem"), fs_flags, file->xfd.fsgeom.sick,
408 file->xfd.fsgeom.checked);
409
410 for (agno = 0; agno < file->xfd.fsgeom.agcount; agno++) {
411 ret = report_ag_sick(agno);
412 if (ret)
413 return 1;
414 }
415 if (comprehensive) {
416 ret = report_bulkstat_health(NULLAGNUMBER);
417 if (ret)
418 return 1;
419 }
420 }
421
422 if (!reported) {
423 fprintf(stderr,
424 _("Health status has not been collected for this filesystem.\n"));
425 fprintf(stderr,
426 _("Please run xfs_scrub(8) to remedy this situation.\n"));
427 }
428
429 return 0;
430 }
431
432 static void
433 health_help(void)
434 {
435 printf(_(
436 "\n"
437 "Report all observed filesystem health problems.\n"
438 "\n"
439 " -a agno -- Report health of the given allocation group.\n"
440 " -c -- Report on the health of all inodes.\n"
441 " -f -- Report health of the overall filesystem.\n"
442 " -i inum -- Report health of a given inode number.\n"
443 " -q -- Only report unhealthy metadata.\n"
444 " paths -- Report health of the given file path.\n"
445 "\n"));
446
447 }
448
449 static cmdinfo_t health_cmd = {
450 .name = "health",
451 .cfunc = health_f,
452 .argmin = 0,
453 .argmax = -1,
454 .args = "[-a agno] [-c] [-f] [-i inum] [-q] [paths]",
455 .flags = CMD_FLAG_ONESHOT,
456 .help = health_help,
457 };
458
459 void
460 health_init(void)
461 {
462 health_cmd.oneline = _("Report observed XFS health problems."),
463 add_command(&health_cmd);
464 }