]>
Commit | Line | Data |
---|---|---|
959ef981 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
95b1e505 DW |
2 | /* |
3 | * Copyright (C) 2018 Oracle. All Rights Reserved. | |
95b1e505 | 4 | * Author: Darrick J. Wong <darrick.wong@oracle.com> |
95b1e505 | 5 | */ |
a440f877 | 6 | #include "xfs.h" |
f0585fce | 7 | #include <pthread.h> |
50a573a7 | 8 | #include <sys/statvfs.h> |
7c309151 | 9 | #include <syslog.h> |
f0585fce | 10 | #include "platform_defs.h" |
42b4c8e8 | 11 | #include "libfrog/paths.h" |
f0585fce | 12 | #include "xfs_scrub.h" |
95b1e505 | 13 | #include "common.h" |
ed60d210 | 14 | #include "progress.h" |
f0585fce | 15 | |
7c309151 DW |
16 | extern char *progname; |
17 | ||
f0585fce DW |
18 | /* |
19 | * Reporting Status to the Console | |
20 | * | |
21 | * We aim for a roughly standard reporting format -- the severity of the | |
22 | * status being reported, a textual description of the object being | |
23 | * reported, and whatever the status happens to be. | |
24 | * | |
25 | * Errors are the most severe and reflect filesystem corruption. | |
26 | * Warnings indicate that something is amiss and needs the attention of | |
27 | * the administrator, but does not constitute a corruption. Information | |
28 | * is merely advisory. | |
29 | */ | |
30 | ||
31 | /* Too many errors? Bail out. */ | |
32 | bool | |
33 | xfs_scrub_excessive_errors( | |
34 | struct scrub_ctx *ctx) | |
35 | { | |
05921544 DW |
36 | unsigned long long errors_seen; |
37 | ||
38 | /* | |
39 | * We only set max_errors at the start of the program, so it's safe to | |
40 | * access it locklessly. | |
41 | */ | |
42 | if (ctx->max_errors == 0) | |
43 | return false; | |
f0585fce DW |
44 | |
45 | pthread_mutex_lock(&ctx->lock); | |
49e05cb0 | 46 | errors_seen = ctx->corruptions_found + ctx->unfixable_errors; |
f0585fce DW |
47 | pthread_mutex_unlock(&ctx->lock); |
48 | ||
05921544 | 49 | return errors_seen >= ctx->max_errors; |
f0585fce DW |
50 | } |
51 | ||
25992d9c ES |
52 | static struct { |
53 | const char *string; | |
54 | int loglevel; | |
55 | } err_levels[] = { | |
420fad2d DW |
56 | [S_ERROR] = { |
57 | .string = "Error", | |
58 | .loglevel = LOG_ERR, | |
59 | }, | |
abc2e70d DW |
60 | [S_CORRUPT] = { |
61 | .string = "Corruption", | |
62 | .loglevel = LOG_ERR, | |
63 | }, | |
49e05cb0 DW |
64 | [S_UNFIXABLE] = { |
65 | .string = "Unfixable Error", | |
66 | .loglevel = LOG_ERR, | |
67 | }, | |
420fad2d DW |
68 | [S_WARN] = { |
69 | .string = "Warning", | |
70 | .loglevel = LOG_WARNING, | |
71 | }, | |
72 | [S_INFO] = { | |
73 | .string = "Info", | |
74 | .loglevel = LOG_INFO, | |
75 | }, | |
76 | [S_REPAIR] = { | |
77 | .string = "Repaired", | |
78 | .loglevel = LOG_INFO, | |
79 | }, | |
80 | [S_PREEN] = { | |
81 | .string = "Optimized", | |
82 | .loglevel = LOG_INFO, | |
83 | }, | |
7c309151 DW |
84 | }; |
85 | ||
ed60d210 DW |
86 | /* If stream is a tty, clear to end of line to clean up progress bar. */ |
87 | static inline const char *stream_start(FILE *stream) | |
88 | { | |
89 | if (stream == stderr) | |
90 | return stderr_isatty ? CLEAR_EOL : ""; | |
91 | return stdout_isatty ? CLEAR_EOL : ""; | |
92 | } | |
93 | ||
f0585fce DW |
94 | /* Print a warning string and some warning text. */ |
95 | void | |
96 | __str_out( | |
97 | struct scrub_ctx *ctx, | |
98 | const char *descr, | |
99 | enum error_level level, | |
100 | int error, | |
101 | const char *file, | |
102 | int line, | |
103 | const char *format, | |
104 | ...) | |
105 | { | |
106 | FILE *stream = stderr; | |
107 | va_list args; | |
108 | char buf[DESCR_BUFSZ]; | |
109 | ||
110 | /* print strerror or format of choice but not both */ | |
111 | assert(!(error && format)); | |
112 | ||
113 | if (level >= S_INFO) | |
114 | stream = stdout; | |
115 | ||
116 | pthread_mutex_lock(&ctx->lock); | |
19852474 DW |
117 | |
118 | /* We only want to hear about optimizing when in debug/verbose mode. */ | |
119 | if (level == S_PREEN && !debug && !verbose) | |
120 | goto out_record; | |
121 | ||
25992d9c ES |
122 | fprintf(stream, "%s%s: %s: ", stream_start(stream), |
123 | _(err_levels[level].string), descr); | |
f0585fce DW |
124 | if (error) { |
125 | fprintf(stream, _("%s."), strerror_r(error, buf, DESCR_BUFSZ)); | |
126 | } else { | |
127 | va_start(args, format); | |
128 | vfprintf(stream, format, args); | |
129 | va_end(args); | |
130 | } | |
131 | ||
132 | if (debug) | |
133 | fprintf(stream, _(" (%s line %d)"), file, line); | |
134 | fprintf(stream, "\n"); | |
135 | if (stream == stdout) | |
136 | fflush(stream); | |
137 | ||
19852474 | 138 | out_record: |
abc2e70d | 139 | if (error || level == S_ERROR) /* A syscall failed */ |
f0585fce | 140 | ctx->runtime_errors++; |
abc2e70d DW |
141 | else if (level == S_CORRUPT) |
142 | ctx->corruptions_found++; | |
49e05cb0 DW |
143 | else if (level == S_UNFIXABLE) |
144 | ctx->unfixable_errors++; | |
f0585fce DW |
145 | else if (level == S_WARN) |
146 | ctx->warnings_found++; | |
19852474 DW |
147 | else if (level == S_REPAIR) |
148 | ctx->repairs++; | |
149 | else if (level == S_PREEN) | |
150 | ctx->preens++; | |
f0585fce DW |
151 | |
152 | pthread_mutex_unlock(&ctx->lock); | |
153 | } | |
173a0283 | 154 | |
7c309151 DW |
155 | /* Log a message to syslog. */ |
156 | #define LOG_BUFSZ 4096 | |
157 | #define LOGNAME_BUFSZ 256 | |
158 | void | |
159 | __str_log( | |
160 | struct scrub_ctx *ctx, | |
161 | enum error_level level, | |
162 | const char *format, | |
163 | ...) | |
164 | { | |
165 | va_list args; | |
166 | char logname[LOGNAME_BUFSZ]; | |
167 | char buf[LOG_BUFSZ]; | |
168 | int sz; | |
169 | ||
170 | /* We only want to hear about optimizing when in debug/verbose mode. */ | |
171 | if (level == S_PREEN && !debug && !verbose) | |
172 | return; | |
173 | ||
174 | /* | |
175 | * Skip logging if we're being run as a service (presumably the | |
176 | * service will log stdout/stderr); if we're being run in a non | |
177 | * interactive manner (assume we're a service); or if we're in | |
178 | * debug mode. | |
179 | */ | |
180 | if (is_service || !isatty(fileno(stdin)) || debug) | |
181 | return; | |
182 | ||
183 | snprintf(logname, LOGNAME_BUFSZ, "%s@%s", progname, ctx->mntpoint); | |
184 | openlog(logname, LOG_PID, LOG_DAEMON); | |
185 | ||
25992d9c | 186 | sz = snprintf(buf, LOG_BUFSZ, "%s: ", _(err_levels[level].string)); |
7c309151 DW |
187 | va_start(args, format); |
188 | vsnprintf(buf + sz, LOG_BUFSZ - sz, format, args); | |
189 | va_end(args); | |
25992d9c | 190 | syslog(err_levels[level].loglevel, "%s", buf); |
7c309151 DW |
191 | |
192 | closelog(); | |
193 | } | |
194 | ||
173a0283 DW |
195 | double |
196 | timeval_subtract( | |
197 | struct timeval *tv1, | |
198 | struct timeval *tv2) | |
199 | { | |
200 | return ((tv1->tv_sec - tv2->tv_sec) + | |
201 | ((float) (tv1->tv_usec - tv2->tv_usec)) / 1000000); | |
202 | } | |
203 | ||
204 | /* Produce human readable disk space output. */ | |
205 | double | |
206 | auto_space_units( | |
207 | unsigned long long bytes, | |
208 | char **units) | |
209 | { | |
210 | if (debug > 1) | |
211 | goto no_prefix; | |
212 | if (bytes > (1ULL << 40)) { | |
213 | *units = "TiB"; | |
214 | return (double)bytes / (1ULL << 40); | |
215 | } else if (bytes > (1ULL << 30)) { | |
216 | *units = "GiB"; | |
217 | return (double)bytes / (1ULL << 30); | |
218 | } else if (bytes > (1ULL << 20)) { | |
219 | *units = "MiB"; | |
220 | return (double)bytes / (1ULL << 20); | |
221 | } else if (bytes > (1ULL << 10)) { | |
222 | *units = "KiB"; | |
223 | return (double)bytes / (1ULL << 10); | |
224 | } | |
225 | ||
226 | no_prefix: | |
227 | *units = "B"; | |
228 | return bytes; | |
229 | } | |
230 | ||
231 | /* Produce human readable discrete number output. */ | |
232 | double | |
233 | auto_units( | |
234 | unsigned long long number, | |
058f45da DW |
235 | char **units, |
236 | int *precision) | |
173a0283 DW |
237 | { |
238 | if (debug > 1) | |
239 | goto no_prefix; | |
058f45da | 240 | *precision = 1; |
173a0283 DW |
241 | if (number > 1000000000000ULL) { |
242 | *units = "T"; | |
243 | return number / 1000000000000.0; | |
244 | } else if (number > 1000000000ULL) { | |
245 | *units = "G"; | |
246 | return number / 1000000000.0; | |
247 | } else if (number > 1000000ULL) { | |
248 | *units = "M"; | |
249 | return number / 1000000.0; | |
250 | } else if (number > 1000ULL) { | |
251 | *units = "K"; | |
252 | return number / 1000.0; | |
253 | } | |
254 | ||
255 | no_prefix: | |
256 | *units = ""; | |
058f45da | 257 | *precision = 0; |
173a0283 DW |
258 | return number; |
259 | } | |
e031d90f DW |
260 | |
261 | /* How many threads to kick off? */ | |
262 | unsigned int | |
263 | scrub_nproc( | |
264 | struct scrub_ctx *ctx) | |
265 | { | |
32c6cc09 DW |
266 | if (force_nr_threads) |
267 | return force_nr_threads; | |
e031d90f DW |
268 | return ctx->nr_io_threads; |
269 | } | |
270 | ||
271 | /* | |
272 | * How many threads to kick off for a workqueue? If we only want one | |
273 | * thread, save ourselves the overhead and just run it in the main thread. | |
274 | */ | |
275 | unsigned int | |
276 | scrub_nproc_workqueue( | |
277 | struct scrub_ctx *ctx) | |
278 | { | |
279 | unsigned int x; | |
280 | ||
281 | x = scrub_nproc(ctx); | |
282 | if (x == 1) | |
283 | x = 0; | |
284 | return x; | |
285 | } | |
50a573a7 | 286 | |
fd7d73c0 | 287 | /* |
1500ee2c | 288 | * Sleep for 100us * however many -b we got past the initial one. |
fd7d73c0 DW |
289 | * This is an (albeit clumsy) way to throttle scrub activity. |
290 | */ | |
1500ee2c DW |
291 | #define NSEC_PER_SEC 1000000000ULL |
292 | #define NSEC_PER_USEC 1000ULL | |
fd7d73c0 DW |
293 | void |
294 | background_sleep(void) | |
295 | { | |
1500ee2c | 296 | unsigned long long time_ns; |
fd7d73c0 DW |
297 | struct timespec tv; |
298 | ||
299 | if (bg_mode < 2) | |
300 | return; | |
301 | ||
1500ee2c DW |
302 | time_ns = 100 * NSEC_PER_USEC * (bg_mode - 1); |
303 | tv.tv_sec = time_ns / NSEC_PER_SEC; | |
304 | tv.tv_nsec = time_ns % NSEC_PER_SEC; | |
fd7d73c0 DW |
305 | nanosleep(&tv, NULL); |
306 | } | |
396cd022 DW |
307 | |
308 | /* | |
309 | * Return the input string with non-printing bytes escaped. | |
310 | * Caller must free the buffer. | |
311 | */ | |
312 | char * | |
313 | string_escape( | |
314 | const char *in) | |
315 | { | |
316 | char *str; | |
317 | const char *p; | |
318 | char *q; | |
319 | int x; | |
320 | ||
321 | str = malloc(strlen(in) * 4); | |
322 | if (!str) | |
323 | return NULL; | |
324 | for (p = in, q = str; *p != '\0'; p++) { | |
325 | if (isprint(*p)) { | |
326 | *q = *p; | |
327 | q++; | |
328 | } else { | |
329 | x = sprintf(q, "\\x%02x", *p); | |
330 | q += x; | |
331 | } | |
332 | } | |
333 | *q = '\0'; | |
334 | return str; | |
335 | } | |
336 | ||
337 | /* | |
338 | * Record another naming warning, and decide if it's worth | |
339 | * complaining about. | |
340 | */ | |
341 | bool | |
342 | should_warn_about_name( | |
343 | struct scrub_ctx *ctx) | |
344 | { | |
345 | bool whine; | |
346 | bool res; | |
347 | ||
348 | pthread_mutex_lock(&ctx->lock); | |
349 | ctx->naming_warnings++; | |
350 | whine = ctx->naming_warnings == TOO_MANY_NAME_WARNINGS; | |
351 | res = ctx->naming_warnings < TOO_MANY_NAME_WARNINGS; | |
352 | pthread_mutex_unlock(&ctx->lock); | |
353 | ||
354 | if (whine && !(debug || verbose)) | |
355 | str_info(ctx, ctx->mntpoint, | |
356 | _("More than %u naming warnings, shutting up."), | |
357 | TOO_MANY_NAME_WARNINGS); | |
358 | ||
359 | return debug || verbose || res; | |
360 | } | |
698c6c7c DW |
361 | |
362 | /* Decide if a value is within +/- (n/d) of a desired value. */ | |
363 | bool | |
364 | within_range( | |
365 | struct scrub_ctx *ctx, | |
366 | unsigned long long value, | |
367 | unsigned long long desired, | |
368 | unsigned long long abs_threshold, | |
369 | unsigned int n, | |
370 | unsigned int d, | |
371 | const char *descr) | |
372 | { | |
373 | assert(n < d); | |
374 | ||
375 | /* Don't complain if difference does not exceed an absolute value. */ | |
376 | if (value < desired && desired - value < abs_threshold) | |
377 | return true; | |
378 | if (value > desired && value - desired < abs_threshold) | |
379 | return true; | |
380 | ||
381 | /* Complain if the difference exceeds a certain percentage. */ | |
382 | if (value < desired * (d - n) / d) | |
383 | return false; | |
384 | if (value > desired * (d + n) / d) | |
385 | return false; | |
386 | ||
387 | return true; | |
388 | } | |
15589f0a DW |
389 | |
390 | /* | |
391 | * Render an inode number into a buffer in a format suitable for use in | |
392 | * log messages. The buffer will be filled with: | |
393 | * "inode <inode number> (<ag number>/<ag inode number>)" | |
394 | * If the @format argument is non-NULL, it will be rendered into the buffer | |
395 | * after the inode representation and a single space. | |
396 | */ | |
397 | int | |
398 | scrub_render_ino_descr( | |
399 | const struct scrub_ctx *ctx, | |
400 | char *buf, | |
401 | size_t buflen, | |
402 | uint64_t ino, | |
403 | uint32_t gen, | |
404 | const char *format, | |
405 | ...) | |
406 | { | |
407 | va_list args; | |
408 | uint32_t agno; | |
409 | uint32_t agino; | |
410 | int ret; | |
411 | ||
412 | agno = cvt_ino_to_agno(&ctx->mnt, ino); | |
413 | agino = cvt_ino_to_agino(&ctx->mnt, ino); | |
414 | ret = snprintf(buf, buflen, _("inode %"PRIu64" (%"PRIu32"/%"PRIu32")%s"), | |
415 | ino, agno, agino, format ? " " : ""); | |
416 | if (ret < 0 || ret >= buflen || format == NULL) | |
417 | return ret; | |
418 | ||
419 | va_start(args, format); | |
420 | ret += vsnprintf(buf + ret, buflen - ret, format, args); | |
421 | va_end(args); | |
422 | return ret; | |
423 | } |