#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
+#include <sys/syscall.h>
#include <poll.h>
#include <sys/file.h>
#include <uuid/uuid.h>
u64 canceled;
};
+/* TBD: replace with #include "linux/ioprio.h" in some years */
+#if !defined (IOPRIO_H)
+#define IOPRIO_WHO_PROCESS 1
+#define IOPRIO_CLASS_SHIFT 13
+#define IOPRIO_PRIO_VALUE(class, data) \
+ (((class) << IOPRIO_CLASS_SHIFT) | (data))
+#define IOPRIO_CLASS_IDLE 3
+#endif
+
struct scrub_progress {
struct btrfs_ioctl_scrub_args scrub_args;
int fd;
struct scrub_file_record *resumed;
int ioctl_errno;
pthread_mutex_t progress_mutex;
+ int ioprio_class;
+ int ioprio_classdata;
};
struct scrub_file_record {
{
u64 err_cnt;
u64 err_cnt2;
- char *bytes;
err_cnt = p->read_errors +
p->csum_errors +
if (p->malloc_errors)
printf("*** WARNING: memory allocation failed while scrubbing. "
"results may be inaccurate\n");
- bytes = pretty_sizes(p->data_bytes_scrubbed + p->tree_bytes_scrubbed);
- printf("\ttotal bytes scrubbed: %s with %llu errors\n", bytes,
+
+ printf("\ttotal bytes scrubbed: %s with %llu errors\n",
+ pretty_size(p->data_bytes_scrubbed + p->tree_bytes_scrubbed),
max(err_cnt, err_cnt2));
- free(bytes);
+
if (err_cnt || err_cnt2) {
printf("\terror details:");
PRINT_SCRUB_ERROR(p->read_errors, "read");
static void free_history(struct scrub_file_record **last_scrubs)
{
struct scrub_file_record **l = last_scrubs;
- if (!l)
+ if (!l || IS_ERR(l))
return;
while (*l)
free(*l++);
sp->stats.duration = 0;
sp->stats.finished = 0;
+ ret = syscall(SYS_ioprio_set, IOPRIO_WHO_PROCESS, 0,
+ IOPRIO_PRIO_VALUE(sp->ioprio_class,
+ sp->ioprio_classdata));
+ if (ret)
+ fprintf(stderr,
+ "WARNING: setting ioprio failed: %s (ignored).\n",
+ strerror(errno));
+
ret = ioctl(sp->fd, BTRFS_IOC_SCRUB, &sp->scrub_args);
gettimeofday(&tv, NULL);
sp->ret = ret;
int perr = 0; /* positive / pthread error returns */
int old;
int i;
- char fsid[37];
+ char fsid[BTRFS_UUID_UNPARSED_SIZE];
struct scrub_progress *sp;
struct scrub_progress *sp_last;
struct scrub_progress *sp_shared;
return NULL;
}
-int mkdir_p(char *path)
+static int mkdir_p(char *path)
{
int i;
int ret;
path[i] = '\0';
ret = mkdir(path, 0777);
if (ret && errno != EEXIST)
- return 1;
+ return -errno;
path[i] = '/';
}
return 0;
}
+static int is_scrub_running_on_fs(struct btrfs_ioctl_fs_info_args *fi_args,
+ struct btrfs_ioctl_dev_info_args *di_args,
+ struct scrub_file_record **past_scrubs)
+{
+ int i;
+
+ if (!fi_args || !di_args || !past_scrubs)
+ return 0;
+
+ for (i = 0; i < fi_args->num_devices; i++) {
+ struct scrub_file_record *sfr =
+ last_dev_scrub(past_scrubs, di_args[i].devid);
+
+ if (!sfr)
+ continue;
+ if (!(sfr->stats.finished || sfr->stats.canceled))
+ return 1;
+ }
+ return 0;
+}
+
static const char * const cmd_scrub_start_usage[];
static const char * const cmd_scrub_resume_usage[];
int do_record = 1;
int readonly = 0;
int do_stats_per_dev = 0;
+ int ioprio_class = IOPRIO_CLASS_IDLE;
+ int ioprio_classdata = 0;
int n_start = 0;
int n_skip = 0;
int n_resume = 0;
struct scrub_file_record **past_scrubs = NULL;
struct scrub_file_record *last_scrub = NULL;
char *datafile = strdup(SCRUB_DATA_FILE);
- char fsid[37];
+ char fsid[BTRFS_UUID_UNPARSED_SIZE];
char sock_path[BTRFS_PATH_NAME_MAX + 1] = "";
struct scrub_progress_cycle spc;
pthread_mutex_t spc_write_mutex = PTHREAD_MUTEX_INITIALIZER;
void *terr;
u64 devid;
+ DIR *dirstream = NULL;
+ int force = 0;
+ int nothing_to_resume = 0;
optind = 1;
- while ((c = getopt(argc, argv, "BdqrR")) != -1) {
+ while ((c = getopt(argc, argv, "BdqrRc:n:f")) != -1) {
switch (c) {
case 'B':
do_background = 0;
case 'R':
print_raw = 1;
break;
+ case 'c':
+ ioprio_class = (int)strtol(optarg, NULL, 10);
+ break;
+ case 'n':
+ ioprio_classdata = (int)strtol(optarg, NULL, 10);
+ break;
+ case 'f':
+ force = 1;
+ break;
case '?':
default:
usage(resume ? cmd_scrub_resume_usage :
path = argv[optind];
- fdmnt = open_path_or_dev_mnt(path);
+ fdmnt = open_path_or_dev_mnt(path, &dirstream);
if (fdmnt < 0) {
ERR(!do_quiet, "ERROR: can't access '%s'\n", path);
- return 12;
+ return 1;
}
ret = get_fs_info(path, &fi_args, &di_args);
close(fdres);
}
+ /*
+ * check whether any involved device is already busy running a
+ * scrub. This would cause damaged status messages and the state
+ * "aborted" without the explanation that a scrub was already
+ * running. Therefore check it first, prevent it and give some
+ * feedback to the user if scrub is already running.
+ * Note that if scrub is started with a block device as the
+ * parameter, only that particular block device is checked. It
+ * is a normal mode of operation to start scrub on multiple
+ * single devices, there is no reason to prevent this.
+ */
+ if (!force && is_scrub_running_on_fs(&fi_args, di_args, past_scrubs)) {
+ ERR(!do_quiet,
+ "ERROR: scrub is already running.\n"
+ "To cancel use 'btrfs scrub cancel %s'.\n"
+ "To see the status use 'btrfs scrub status [-d] %s'.\n",
+ path, path);
+ err = 1;
+ goto out;
+ }
+
t_devs = malloc(fi_args.num_devices * sizeof(*t_devs));
sp = calloc(fi_args.num_devices, sizeof(*sp));
spc.progress = calloc(fi_args.num_devices * 2, sizeof(*spc.progress));
sp[i].skip = 0;
sp[i].scrub_args.end = (u64)-1ll;
sp[i].scrub_args.flags = readonly ? BTRFS_SCRUB_READONLY : 0;
+ sp[i].ioprio_class = ioprio_class;
+ sp[i].ioprio_classdata = ioprio_classdata;
}
if (!n_start && !n_resume) {
if (!do_quiet)
printf("scrub: nothing to resume for %s, fsid %s\n",
path, fsid);
- err = 0;
+ nothing_to_resume = 1;
goto out;
}
if (sock_path[0])
unlink(sock_path);
}
- close(fdmnt);
+ close_file_or_dir(fdmnt, dirstream);
+ if (nothing_to_resume)
+ return 2;
if (err)
return 1;
if (e_correctable)
- return 7;
+ return 3;
if (e_uncorrectable)
- return 8;
+ return 4;
return 0;
}
static const char * const cmd_scrub_start_usage[] = {
- "btrfs scrub start [-Bdqr] <path>|<device>",
+ "btrfs scrub start [-BdqrRf] [-c ioprio_class -n ioprio_classdata] <path>|<device>",
"Start a new scrub",
"",
"-B do not background",
"-d stats per device (-B only)",
"-q be quiet",
"-r read only mode",
+ "-R raw print mode, print full data instead of summary"
+ "-c set ioprio class (see ionice(1) manpage)",
+ "-n set ioprio classdata (see ionice(1) manpage)",
+ "-f force to skip checking whether scrub has started/resumed in userspace ",
+ " this is useful when scrub stats record file is damaged",
NULL
};
char *path;
int ret;
int fdmnt = -1;
+ DIR *dirstream = NULL;
if (check_argc_exact(argc, 2))
usage(cmd_scrub_cancel_usage);
path = argv[1];
- fdmnt = open_path_or_dev_mnt(path);
+ fdmnt = open_path_or_dev_mnt(path, &dirstream);
if (fdmnt < 0) {
fprintf(stderr, "ERROR: could not open %s: %s\n",
path, strerror(errno));
if (ret < 0) {
fprintf(stderr, "ERROR: scrub cancel failed on %s: %s\n", path,
errno == ENOTCONN ? "not running" : strerror(errno));
- ret = 1;
+ if (errno == ENOTCONN)
+ ret = 2;
+ else
+ ret = 1;
goto out;
}
printf("scrub cancelled\n");
out:
- if (fdmnt != -1)
- close(fdmnt);
+ close_file_or_dir(fdmnt, dirstream);
return ret;
}
static const char * const cmd_scrub_resume_usage[] = {
- "btrfs scrub resume [-Bdqr] <path>|<device>",
+ "btrfs scrub resume [-BdqrR] [-c ioprio_class -n ioprio_classdata] <path>|<device>",
"Resume previously canceled or interrupted scrub",
"",
"-B do not background",
"-d stats per device (-B only)",
"-q be quiet",
"-r read only mode",
+ "-c set ioprio class (see ionice(1) manpage)",
+ "-n set ioprio classdata (see ionice(1) manpage)",
NULL
};
int print_raw = 0;
int do_stats_per_dev = 0;
int c;
- char fsid[37];
+ char fsid[BTRFS_UUID_UNPARSED_SIZE];
int fdres = -1;
int err = 0;
+ DIR *dirstream = NULL;
optind = 1;
while ((c = getopt(argc, argv, "dR")) != -1) {
path = argv[optind];
- fdmnt = open_path_or_dev_mnt(path);
+ fdmnt = open_path_or_dev_mnt(path, &dirstream);
if (fdmnt < 0) {
- fprintf(stderr, "ERROR: can't access to '%s'\n", path);
- return 12;
+ fprintf(stderr, "ERROR: can't access '%s'\n", path);
+ return 1;
}
ret = get_fs_info(path, &fi_args, &di_args);
free(di_args);
if (fdres > -1)
close(fdres);
+ close_file_or_dir(fdmnt, dirstream);
- return err;
+ return !!err;
}
const struct cmd_group scrub_cmd_group = {
{ "cancel", cmd_scrub_cancel, cmd_scrub_cancel_usage, NULL, 0 },
{ "resume", cmd_scrub_resume, cmd_scrub_resume_usage, NULL, 0 },
{ "status", cmd_scrub_status, cmd_scrub_status_usage, NULL, 0 },
- { 0, 0, 0, 0, 0 }
+ NULL_CMD_STRUCT
}
};