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