| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Copyright (C) 2018 Oracle. All Rights Reserved. |
| * Author: Darrick J. Wong <darrick.wong@oracle.com> |
| */ |
| #include "xfs.h" |
| #include <pthread.h> |
| #include <sys/statvfs.h> |
| #include <syslog.h> |
| #include "platform_defs.h" |
| #include "libfrog/paths.h" |
| #include "xfs_scrub.h" |
| #include "common.h" |
| #include "progress.h" |
| |
| extern char *progname; |
| |
| /* |
| * Reporting Status to the Console |
| * |
| * We aim for a roughly standard reporting format -- the severity of the |
| * status being reported, a textual description of the object being |
| * reported, and whatever the status happens to be. |
| * |
| * Errors are the most severe and reflect filesystem corruption. |
| * Warnings indicate that something is amiss and needs the attention of |
| * the administrator, but does not constitute a corruption. Information |
| * is merely advisory. |
| */ |
| |
| /* Too many errors? Bail out. */ |
| bool |
| scrub_excessive_errors( |
| struct scrub_ctx *ctx) |
| { |
| unsigned long long errors_seen; |
| |
| /* |
| * We only set max_errors at the start of the program, so it's safe to |
| * access it locklessly. |
| */ |
| if (ctx->max_errors == 0) |
| return false; |
| |
| pthread_mutex_lock(&ctx->lock); |
| errors_seen = ctx->corruptions_found + ctx->unfixable_errors; |
| pthread_mutex_unlock(&ctx->lock); |
| |
| return errors_seen >= ctx->max_errors; |
| } |
| |
| static struct { |
| const char *string; |
| int loglevel; |
| } err_levels[] = { |
| [S_ERROR] = { |
| .string = "Error", |
| .loglevel = LOG_ERR, |
| }, |
| [S_CORRUPT] = { |
| .string = "Corruption", |
| .loglevel = LOG_ERR, |
| }, |
| [S_UNFIXABLE] = { |
| .string = "Unfixable Error", |
| .loglevel = LOG_ERR, |
| }, |
| [S_WARN] = { |
| .string = "Warning", |
| .loglevel = LOG_WARNING, |
| }, |
| [S_INFO] = { |
| .string = "Info", |
| .loglevel = LOG_INFO, |
| }, |
| [S_REPAIR] = { |
| .string = "Repaired", |
| .loglevel = LOG_INFO, |
| }, |
| [S_PREEN] = { |
| .string = "Optimized", |
| .loglevel = LOG_INFO, |
| }, |
| }; |
| |
| /* If stream is a tty, clear to end of line to clean up progress bar. */ |
| static inline const char *stream_start(FILE *stream) |
| { |
| if (stream == stderr) |
| return stderr_isatty ? CLEAR_EOL : ""; |
| return stdout_isatty ? CLEAR_EOL : ""; |
| } |
| |
| /* Print a warning string and some warning text. */ |
| void |
| __str_out( |
| struct scrub_ctx *ctx, |
| const char *descr, |
| enum error_level level, |
| int error, |
| const char *file, |
| int line, |
| const char *format, |
| ...) |
| { |
| FILE *stream = stderr; |
| va_list args; |
| char buf[DESCR_BUFSZ]; |
| |
| /* print strerror or format of choice but not both */ |
| assert(!(error && format)); |
| |
| if (level >= S_INFO) |
| stream = stdout; |
| |
| pthread_mutex_lock(&ctx->lock); |
| |
| /* We only want to hear about optimizing when in debug/verbose mode. */ |
| if (level == S_PREEN && !debug && !verbose) |
| goto out_record; |
| |
| fprintf(stream, "%s%s: %s: ", stream_start(stream), |
| _(err_levels[level].string), descr); |
| if (error) { |
| fprintf(stream, _("%s."), strerror_r(error, buf, DESCR_BUFSZ)); |
| } else { |
| va_start(args, format); |
| vfprintf(stream, format, args); |
| va_end(args); |
| } |
| |
| if (debug) |
| fprintf(stream, _(" (%s line %d)"), file, line); |
| fprintf(stream, "\n"); |
| if (stream == stdout) |
| fflush(stream); |
| |
| out_record: |
| if (error || level == S_ERROR) /* A syscall failed */ |
| ctx->runtime_errors++; |
| else if (level == S_CORRUPT) |
| ctx->corruptions_found++; |
| else if (level == S_UNFIXABLE) |
| ctx->unfixable_errors++; |
| else if (level == S_WARN) |
| ctx->warnings_found++; |
| else if (level == S_REPAIR) |
| ctx->repairs++; |
| else if (level == S_PREEN) |
| ctx->preens++; |
| |
| pthread_mutex_unlock(&ctx->lock); |
| } |
| |
| /* Log a message to syslog. */ |
| #define LOG_BUFSZ 4096 |
| #define LOGNAME_BUFSZ 256 |
| void |
| __str_log( |
| struct scrub_ctx *ctx, |
| enum error_level level, |
| const char *format, |
| ...) |
| { |
| va_list args; |
| char logname[LOGNAME_BUFSZ]; |
| char buf[LOG_BUFSZ]; |
| int sz; |
| |
| /* We only want to hear about optimizing when in debug/verbose mode. */ |
| if (level == S_PREEN && !debug && !verbose) |
| return; |
| |
| /* |
| * Skip logging if we're being run as a service (presumably the |
| * service will log stdout/stderr); if we're being run in a non |
| * interactive manner (assume we're a service); or if we're in |
| * debug mode. |
| */ |
| if (is_service || !isatty(fileno(stdin)) || debug) |
| return; |
| |
| snprintf(logname, LOGNAME_BUFSZ, "%s@%s", progname, ctx->mntpoint); |
| openlog(logname, LOG_PID, LOG_DAEMON); |
| |
| sz = snprintf(buf, LOG_BUFSZ, "%s: ", _(err_levels[level].string)); |
| va_start(args, format); |
| vsnprintf(buf + sz, LOG_BUFSZ - sz, format, args); |
| va_end(args); |
| syslog(err_levels[level].loglevel, "%s", buf); |
| |
| closelog(); |
| } |
| |
| double |
| timeval_subtract( |
| struct timeval *tv1, |
| struct timeval *tv2) |
| { |
| return ((tv1->tv_sec - tv2->tv_sec) + |
| ((float) (tv1->tv_usec - tv2->tv_usec)) / 1000000); |
| } |
| |
| /* Produce human readable disk space output. */ |
| double |
| auto_space_units( |
| unsigned long long bytes, |
| char **units) |
| { |
| if (debug > 1) |
| goto no_prefix; |
| if (bytes > (1ULL << 40)) { |
| *units = "TiB"; |
| return (double)bytes / (1ULL << 40); |
| } else if (bytes > (1ULL << 30)) { |
| *units = "GiB"; |
| return (double)bytes / (1ULL << 30); |
| } else if (bytes > (1ULL << 20)) { |
| *units = "MiB"; |
| return (double)bytes / (1ULL << 20); |
| } else if (bytes > (1ULL << 10)) { |
| *units = "KiB"; |
| return (double)bytes / (1ULL << 10); |
| } |
| |
| no_prefix: |
| *units = "B"; |
| return bytes; |
| } |
| |
| /* Produce human readable discrete number output. */ |
| double |
| auto_units( |
| unsigned long long number, |
| char **units, |
| int *precision) |
| { |
| if (debug > 1) |
| goto no_prefix; |
| *precision = 1; |
| if (number > 1000000000000ULL) { |
| *units = "T"; |
| return number / 1000000000000.0; |
| } else if (number > 1000000000ULL) { |
| *units = "G"; |
| return number / 1000000000.0; |
| } else if (number > 1000000ULL) { |
| *units = "M"; |
| return number / 1000000.0; |
| } else if (number > 1000ULL) { |
| *units = "K"; |
| return number / 1000.0; |
| } |
| |
| no_prefix: |
| *units = ""; |
| *precision = 0; |
| return number; |
| } |
| |
| /* How many threads to kick off? */ |
| unsigned int |
| scrub_nproc( |
| struct scrub_ctx *ctx) |
| { |
| if (force_nr_threads) |
| return force_nr_threads; |
| return ctx->nr_io_threads; |
| } |
| |
| /* |
| * How many threads to kick off for a workqueue? If we only want one |
| * thread, save ourselves the overhead and just run it in the main thread. |
| */ |
| unsigned int |
| scrub_nproc_workqueue( |
| struct scrub_ctx *ctx) |
| { |
| unsigned int x; |
| |
| x = scrub_nproc(ctx); |
| if (x == 1) |
| x = 0; |
| return x; |
| } |
| |
| /* |
| * Sleep for 100us * however many -b we got past the initial one. |
| * This is an (albeit clumsy) way to throttle scrub activity. |
| */ |
| void |
| background_sleep(void) |
| { |
| unsigned long long time_ns; |
| struct timespec tv; |
| |
| if (bg_mode < 2) |
| return; |
| |
| time_ns = 100 * NSEC_PER_USEC * (bg_mode - 1); |
| tv.tv_sec = time_ns / NSEC_PER_SEC; |
| tv.tv_nsec = time_ns % NSEC_PER_SEC; |
| nanosleep(&tv, NULL); |
| } |
| |
| /* |
| * Return the input string with non-printing bytes escaped. |
| * Caller must free the buffer. |
| */ |
| char * |
| string_escape( |
| const char *in) |
| { |
| char *str; |
| const char *p; |
| char *q; |
| int x; |
| |
| str = malloc(strlen(in) * 4); |
| if (!str) |
| return NULL; |
| for (p = in, q = str; *p != '\0'; p++) { |
| if (isprint(*p)) { |
| *q = *p; |
| q++; |
| } else { |
| x = sprintf(q, "\\x%02x", *p); |
| q += x; |
| } |
| } |
| *q = '\0'; |
| return str; |
| } |
| |
| /* |
| * Record another naming warning, and decide if it's worth |
| * complaining about. |
| */ |
| bool |
| should_warn_about_name( |
| struct scrub_ctx *ctx) |
| { |
| bool whine; |
| bool res; |
| |
| pthread_mutex_lock(&ctx->lock); |
| ctx->naming_warnings++; |
| whine = ctx->naming_warnings == TOO_MANY_NAME_WARNINGS; |
| res = ctx->naming_warnings < TOO_MANY_NAME_WARNINGS; |
| pthread_mutex_unlock(&ctx->lock); |
| |
| if (whine && !(debug || verbose)) |
| str_info(ctx, ctx->mntpoint, |
| _("More than %u naming warnings, shutting up."), |
| TOO_MANY_NAME_WARNINGS); |
| |
| return debug || verbose || res; |
| } |
| |
| /* Decide if a value is within +/- (n/d) of a desired value. */ |
| bool |
| within_range( |
| struct scrub_ctx *ctx, |
| unsigned long long value, |
| unsigned long long desired, |
| unsigned long long abs_threshold, |
| unsigned int n, |
| unsigned int d, |
| const char *descr) |
| { |
| assert(n < d); |
| |
| /* Don't complain if difference does not exceed an absolute value. */ |
| if (value < desired && desired - value < abs_threshold) |
| return true; |
| if (value > desired && value - desired < abs_threshold) |
| return true; |
| |
| /* Complain if the difference exceeds a certain percentage. */ |
| if (value < desired * (d - n) / d) |
| return false; |
| if (value > desired * (d + n) / d) |
| return false; |
| |
| return true; |
| } |
| |
| /* |
| * Render an inode number into a buffer in a format suitable for use in |
| * log messages. The buffer will be filled with: |
| * "inode <inode number> (<ag number>/<ag inode number>)" |
| * If the @format argument is non-NULL, it will be rendered into the buffer |
| * after the inode representation and a single space. |
| */ |
| int |
| scrub_render_ino_descr( |
| const struct scrub_ctx *ctx, |
| char *buf, |
| size_t buflen, |
| uint64_t ino, |
| uint32_t gen, |
| const char *format, |
| ...) |
| { |
| va_list args; |
| uint32_t agno; |
| uint32_t agino; |
| int ret; |
| |
| agno = cvt_ino_to_agno(&ctx->mnt, ino); |
| agino = cvt_ino_to_agino(&ctx->mnt, ino); |
| ret = snprintf(buf, buflen, _("inode %"PRIu64" (%"PRIu32"/%"PRIu32")%s"), |
| ino, agno, agino, format ? " " : ""); |
| if (ret < 0 || ret >= buflen || format == NULL) |
| return ret; |
| |
| va_start(args, format); |
| ret += vsnprintf(buf + ret, buflen - ret, format, args); |
| va_end(args); |
| return ret; |
| } |