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