blob: bbf97bf49f9df50afc93a5b4e17f9c8405ce6561 [file] [log] [blame]
/*
* Copyright (C) 2018 Oracle. All Rights Reserved.
*
* Author: Darrick J. Wong <darrick.wong@oracle.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it would be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "xfs.h"
#include <pthread.h>
#include <sys/statvfs.h>
#include <syslog.h>
#include "platform_defs.h"
#include "path.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
xfs_scrub_excessive_errors(
struct scrub_ctx *ctx)
{
bool ret;
pthread_mutex_lock(&ctx->lock);
ret = ctx->max_errors > 0 && ctx->errors_found >= ctx->max_errors;
pthread_mutex_unlock(&ctx->lock);
return ret;
}
static struct {
const char *string;
int loglevel;
} err_levels[] = {
[S_ERROR] = { .string = "Error", .loglevel = LOG_ERR },
[S_WARN] = { .string = "Warning", .loglevel = LOG_WARNING },
[S_REPAIR] = { .string = "Repaired", .loglevel = LOG_WARNING },
[S_INFO] = { .string = "Info", .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) /* A syscall failed */
ctx->runtime_errors++;
else if (level == S_ERROR)
ctx->errors_found++;
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)
{
if (debug > 1)
goto no_prefix;
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 = "";
return number;
}
/* How many threads to kick off? */
unsigned int
scrub_nproc(
struct scrub_ctx *ctx)
{
if (nr_threads)
return 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 100ms * 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;
struct timespec tv;
if (bg_mode < 2)
return;
time = 100000ULL * (bg_mode - 1);
tv.tv_sec = time / 1000000;
tv.tv_nsec = time % 1000000;
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;
}