btrfs-progs: gitignore: Ignore patches
[platform/upstream/btrfs-progs.git] / cmds-scrub.c
index b984e96..dabe7d9 100644 (file)
@@ -17,6 +17,7 @@
  */
 
 #include "kerncompat.h"
+#include "androidcompat.h"
 
 #include <sys/ioctl.h>
 #include <sys/wait.h>
@@ -24,6 +25,7 @@
 #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>
@@ -33,6 +35,7 @@
 #include <ctype.h>
 #include <signal.h>
 #include <stdarg.h>
+#include <limits.h>
 
 #include "ctree.h"
 #include "ioctl.h"
@@ -41,6 +44,7 @@
 #include "disk-io.h"
 
 #include "commands.h"
+#include "help.h"
 
 static const char * const scrub_cmd_group_usage[] = {
        "btrfs scrub <command> [options] <path>|<device>",
@@ -58,8 +62,18 @@ struct scrub_stats {
        u64 duration;
        u64 finished;
        u64 canceled;
+       int in_progress;
 };
 
+/* 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;
@@ -69,6 +83,8 @@ struct scrub_progress {
        struct scrub_file_record *resumed;
        int ioctl_errno;
        pthread_mutex_t progress_mutex;
+       int ioprio_class;
+       int ioprio_classdata;
 };
 
 struct scrub_file_record {
@@ -113,11 +129,6 @@ static void print_scrub_full(struct btrfs_scrub_progress *sp)
        printf("\tlast_physical: %lld\n", sp->last_physical);
 }
 
-#define ERR(test, ...) do {                    \
-       if (test)                               \
-               fprintf(stderr, __VA_ARGS__);   \
-} while (0)
-
 #define PRINT_SCRUB_ERROR(test, desc) do {     \
        if (test)                               \
                printf(" %s=%llu", desc, test); \
@@ -127,7 +138,6 @@ static void print_scrub_summary(struct btrfs_scrub_progress *p)
 {
        u64 err_cnt;
        u64 err_cnt2;
-       char *bytes;
 
        err_cnt = p->read_errors +
                        p->csum_errors +
@@ -139,10 +149,11 @@ static void print_scrub_summary(struct btrfs_scrub_progress *p)
        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");
@@ -216,6 +227,8 @@ static void _print_scrub_ss(struct scrub_stats *ss)
 {
        char t[4096];
        struct tm tm;
+       time_t seconds;
+       unsigned hours;
 
        if (!ss || !ss->t_start) {
                printf("\tno stats available\n");
@@ -232,15 +245,20 @@ static void _print_scrub_ss(struct scrub_stats *ss)
                t[sizeof(t) - 1] = '\0';
                printf("\tscrub started at %s", t);
        }
-       if (ss->finished && !ss->canceled) {
-               printf(" and finished after %llu seconds\n",
-                      ss->duration);
-       } else if (ss->canceled) {
-               printf(" and was aborted after %llu seconds\n",
-                      ss->duration);
-       } else {
-               printf(", running for %llu seconds\n", ss->duration);
-       }
+
+       seconds = ss->duration;
+       hours = ss->duration / (60 * 60);
+       gmtime_r(&seconds, &tm);
+       strftime(t, sizeof(t), "%M:%S", &tm);
+       if (ss->in_progress)
+               printf(", running for %02u:%s\n", hours, t);
+       else if (ss->canceled)
+               printf(" and was aborted after %02u:%s\n", hours, t);
+       else if (ss->finished)
+               printf(" and finished after %02u:%s\n", hours, t);
+       else
+               printf(", interrupted after %02u:%s, not running\n",
+                      hours, t);
 }
 
 static void print_scrub_dev(struct btrfs_ioctl_dev_info_args *di,
@@ -273,7 +291,7 @@ static void print_fs_stat(struct scrub_fs_stat *fs_stat, int raw)
 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++);
@@ -287,7 +305,11 @@ static void free_history(struct scrub_file_record **last_scrubs)
 static int cancel_fd = -1;
 static void scrub_sigint_record_progress(int signal)
 {
-       ioctl(cancel_fd, BTRFS_IOC_SCRUB_CANCEL, NULL);
+       int ret;
+
+       ret = ioctl(cancel_fd, BTRFS_IOC_SCRUB_CANCEL, NULL);
+       if (ret < 0)
+               perror("Scrub cancel failed");
 }
 
 static int scrub_handle_sigint_parent(void)
@@ -364,7 +386,7 @@ static int scrub_open_file(const char *datafile, int m)
 static int scrub_open_file_r(const char *fn_base, const char *fn_local)
 {
        int ret;
-       char datafile[BTRFS_PATH_NAME_MAX + 1];
+       char datafile[PATH_MAX];
        ret = scrub_datafile(fn_base, fn_local, NULL,
                                datafile, sizeof(datafile));
        if (ret < 0)
@@ -376,7 +398,7 @@ static int scrub_open_file_w(const char *fn_base, const char *fn_local,
                                const char *tmp)
 {
        int ret;
-       char datafile[BTRFS_PATH_NAME_MAX + 1];
+       char datafile[PATH_MAX];
        ret = scrub_datafile(fn_base, fn_local, tmp,
                                datafile, sizeof(datafile));
        if (ret < 0)
@@ -388,8 +410,8 @@ static int scrub_rename_file(const char *fn_base, const char *fn_local,
                                const char *tmp)
 {
        int ret;
-       char datafile_old[BTRFS_PATH_NAME_MAX + 1];
-       char datafile_new[BTRFS_PATH_NAME_MAX + 1];
+       char datafile_old[PATH_MAX];
+       char datafile_new[PATH_MAX];
        ret = scrub_datafile(fn_base, fn_local, tmp,
                                datafile_old, sizeof(datafile_old));
        if (ret < 0)
@@ -409,7 +431,7 @@ static int scrub_rename_file(const char *fn_base, const char *fn_local,
 /*
  * returns 0 if the key did not match (nothing was read)
  *         1 if the key did match (success)
- *        -1 if the key did match and an error occured
+ *        -1 if the key did match and an error occurred
  */
 static int scrub_kvread(int *i, int len, int avail, const char *buf,
                        const char *key, u64 *dest)
@@ -435,8 +457,8 @@ static int scrub_kvread(int *i, int len, int avail, const char *buf,
 
 #define _SCRUB_INVALID do {                                            \
        if (report_errors)                                              \
-               fprintf(stderr, "WARNING: invalid data in line %d pos " \
-                       "%d state %d (near \"%.*s\") at %s:%d\n",       \
+               warning("invalid data on line %d pos "                  \
+                       "%d state %d (near \"%.*s\") at %s:%d",         \
                        lineno, i, state, 20 > avail ? avail : 20,      \
                        l + i,  __FILE__, __LINE__);                    \
        goto skip;                                                      \
@@ -446,7 +468,7 @@ static struct scrub_file_record **scrub_read_file(int fd, int report_errors)
 {
        int avail = 0;
        int old_avail = 0;
-       char l[16 * 1024];
+       char l[SZ_16K];
        int state = 0;
        int curr = -1;
        int i = 0;
@@ -458,12 +480,12 @@ static struct scrub_file_record **scrub_read_file(int fd, int report_errors)
        char empty_uuid[BTRFS_FSID_SIZE] = {0};
        struct scrub_file_record **p = NULL;
 
-       if (fd < 0)
-               return ERR_PTR(-EINVAL);
-
 again:
        old_avail = avail - i;
-       BUG_ON(old_avail < 0);
+       if (old_avail < 0) {
+               error("scrub record file corrupted near byte %d", i);
+               return ERR_PTR(-EINVAL);
+       }
        if (old_avail)
                memmove(l, l + i, old_avail);
        avail = read(fd, l + old_avail, sizeof(l) - old_avail);
@@ -478,12 +500,16 @@ again:
                }
                return p;
        }
-       if (avail == -1)
+       if (avail == -1) {
+               free_history(p);
                return ERR_PTR(-errno);
+       }
        avail += old_avail;
 
        i = 0;
        while (i < avail) {
+               void *tmp;
+
                switch (state) {
                case 0: /* start of file */
                        ret = scrub_kvread(&i,
@@ -510,11 +536,17 @@ again:
                                continue;
                        }
                        ++curr;
+                       tmp = p;
                        p = realloc(p, (curr + 2) * sizeof(*p));
-                       if (p)
-                               p[curr] = malloc(sizeof(**p));
-                       if (!p || !p[curr])
+                       if (!p) {
+                               free_history(tmp);
+                               return ERR_PTR(-errno);
+                       }
+                       p[curr] = malloc(sizeof(**p));
+                       if (!p[curr]) {
+                               free_history(p);
                                return ERR_PTR(-errno);
+                       }
                        memset(p[curr], 0, sizeof(**p));
                        p[curr + 1] = NULL;
                        ++state;
@@ -537,7 +569,7 @@ again:
                                ;
                        if (i + j + 1 >= avail)
                                _SCRUB_INVALID;
-                       if (j != 36)
+                       if (j != BTRFS_UUID_UNPARSED_SIZE - 1)
                                _SCRUB_INVALID;
                        l[i + j] = '\0';
                        ret = uuid_parse(l + i, p[curr]->fsid);
@@ -622,7 +654,9 @@ skip:
                        } while (i < avail);
                        continue;
                }
-               BUG();
+               error("internal error: unknown parser state %d near byte %d",
+                               state, i);
+               return ERR_PTR(-EINVAL);
        }
        goto again;
 }
@@ -757,31 +791,31 @@ static int scrub_write_progress(pthread_mutex_t *m, const char *fsid,
        int fd = -1;
        int old;
 
-       ret = pthread_mutex_lock(m);
+       ret = pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old);
        if (ret) {
-               err = -errno;
-               goto out;
+               err = -ret;
+               goto out3;
        }
 
-       ret = pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old);
+       ret = pthread_mutex_lock(m);
        if (ret) {
                err = -ret;
-               goto out;
+               goto out2;
        }
 
        fd = scrub_open_file_w(SCRUB_DATA_FILE, fsid, "tmp");
        if (fd < 0) {
                err = fd;
-               goto out;
+               goto out1;
        }
        err = scrub_write_file(fd, fsid, data, n);
        if (err)
-               goto out;
+               goto out1;
        err = scrub_rename_file(SCRUB_DATA_FILE, fsid, "tmp");
        if (err)
-               goto out;
+               goto out1;
 
-out:
+out1:
        if (fd >= 0) {
                ret = close(fd);
                if (ret)
@@ -792,10 +826,12 @@ out:
        if (ret && !err)
                err = -ret;
 
+out2:
        ret = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &old);
        if (ret && !err)
                err = -ret;
 
+out3:
        return err;
 }
 
@@ -809,6 +845,12 @@ static void *scrub_one_dev(void *ctx)
        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)
+               warning("setting ioprio failed: %m (ignored)");
+
        ret = ioctl(sp->fd, BTRFS_IOC_SCRUB, &sp->scrub_args);
        gettimeofday(&tv, NULL);
        sp->ret = ret;
@@ -836,12 +878,14 @@ static void *progress_one_dev(void *ctx)
        return NULL;
 }
 
+/* nb: returns a negative errno via ERR_PTR */
 static void *scrub_progress_cycle(void *ctx)
 {
-       int ret;
+       int ret = 0;
+       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;
@@ -863,9 +907,9 @@ static void *scrub_progress_cycle(void *ctx)
        struct sockaddr_un peer;
        socklen_t peer_size = sizeof(peer);
 
-       ret = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &old);
-       if (ret)
-               return ERR_PTR(-ret);
+       perr = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &old);
+       if (perr)
+               goto out;
 
        uuid_unparse(spc->fi->fsid, fsid);
 
@@ -886,8 +930,10 @@ static void *scrub_progress_cycle(void *ctx)
 
        while (1) {
                ret = poll(&accept_poll_fd, 1, 5 * 1000);
-               if (ret == -1)
-                       return ERR_PTR(-errno);
+               if (ret == -1) {
+                       ret = -errno;
+                       goto out;
+               }
                if (ret)
                        peer_fd = accept(spc->prg_fd, (struct sockaddr *)&peer,
                                         &peer_size);
@@ -905,42 +951,58 @@ static void *scrub_progress_cycle(void *ctx)
                        if (!sp->ret)
                                continue;
                        if (sp->ioctl_errno != ENOTCONN &&
-                           sp->ioctl_errno != ENODEV)
-                               return ERR_PTR(-sp->ioctl_errno);
+                           sp->ioctl_errno != ENODEV) {
+                               ret = -sp->ioctl_errno;
+                               goto out;
+                       }
                        /*
                         * scrub finished or device removed, check the
                         * finished flag. if unset, just use the last
                         * result we got for the current write and go
                         * on. flag should be set on next cycle, then.
                         */
-                       ret = pthread_mutex_lock(&sp_shared->progress_mutex);
-                       if (ret)
-                               return ERR_PTR(-ret);
+                       perr = pthread_setcancelstate(
+                                       PTHREAD_CANCEL_DISABLE, &old);
+                       if (perr)
+                               goto out;
+                       perr = pthread_mutex_lock(&sp_shared->progress_mutex);
+                       if (perr)
+                               goto out;
                        if (!sp_shared->stats.finished) {
-                               ret = pthread_mutex_unlock(
+                               perr = pthread_mutex_unlock(
                                                &sp_shared->progress_mutex);
-                               if (ret)
-                                       return ERR_PTR(-ret);
+                               if (perr)
+                                       goto out;
+                               perr = pthread_setcancelstate(
+                                               PTHREAD_CANCEL_ENABLE, &old);
+                               if (perr)
+                                       goto out;
                                memcpy(sp, sp_last, sizeof(*sp));
                                continue;
                        }
-                       ret = pthread_mutex_unlock(&sp_shared->progress_mutex);
-                       if (ret)
-                               return ERR_PTR(-ret);
+                       perr = pthread_mutex_unlock(&sp_shared->progress_mutex);
+                       if (perr)
+                               goto out;
+                       perr = pthread_setcancelstate(
+                                       PTHREAD_CANCEL_ENABLE, &old);
+                       if (perr)
+                               goto out;
                        memcpy(sp, sp_shared, sizeof(*sp));
                        memcpy(sp_last, sp_shared, sizeof(*sp));
                }
                if (peer_fd != -1) {
                        write_poll_fd.fd = peer_fd;
                        ret = poll(&write_poll_fd, 1, 0);
-                       if (ret == -1)
-                               return ERR_PTR(-errno);
+                       if (ret == -1) {
+                               ret = -errno;
+                               goto out;
+                       }
                        if (ret) {
                                ret = scrub_write_file(
                                        peer_fd, fsid,
                                        &spc->progress[this * ndev], ndev);
                                if (ret)
-                                       return ERR_PTR(ret);
+                                       goto out;
                        }
                        close(peer_fd);
                        peer_fd = -1;
@@ -950,8 +1012,14 @@ static void *scrub_progress_cycle(void *ctx)
                ret = scrub_write_progress(spc->write_mutex, fsid,
                                           &spc->progress[this * ndev], ndev);
                if (ret)
-                       return ERR_PTR(ret);
+                       goto out;
        }
+out:
+       if (peer_fd != -1)
+               close(peer_fd);
+       if (perr)
+               ret = -perr;
+       return ERR_PTR(ret);
 }
 
 static struct scrub_file_record *last_dev_scrub(
@@ -969,7 +1037,7 @@ static struct scrub_file_record *last_dev_scrub(
        return NULL;
 }
 
-int mkdir_p(char *path)
+static int mkdir_p(char *path)
 {
        int i;
        int ret;
@@ -980,13 +1048,52 @@ int mkdir_p(char *path)
                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 int is_scrub_running_in_kernel(int fd,
+               struct btrfs_ioctl_dev_info_args *di_args, u64 max_devices)
+{
+       struct scrub_progress sp;
+       int i;
+       int ret;
+
+       for (i = 0; i < max_devices; i++) {
+               memset(&sp, 0, sizeof(sp));
+               sp.scrub_args.devid = di_args[i].devid;
+               ret = ioctl(fd, BTRFS_IOC_SCRUB_PROGRESS, &sp.scrub_args);
+               if (!ret)
+                       return 1;
+       }
+
+       return 0;
+}
+
 static const char * const cmd_scrub_start_usage[];
 static const char * const cmd_scrub_resume_usage[];
 
@@ -1011,6 +1118,8 @@ static int scrub_start(int argc, char **argv, int resume)
        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;
@@ -1024,19 +1133,20 @@ static int scrub_start(int argc, char **argv, int resume)
        };
        pthread_t *t_devs = NULL;
        pthread_t t_prog;
-       pthread_attr_t t_attr;
        struct scrub_file_record **past_scrubs = NULL;
        struct scrub_file_record *last_scrub = NULL;
        char *datafile = strdup(SCRUB_DATA_FILE);
-       char fsid[37];
-       char sock_path[BTRFS_PATH_NAME_MAX + 1] = "";
+       char fsid[BTRFS_UUID_UNPARSED_SIZE];
+       char sock_path[PATH_MAX] = "";
        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;
@@ -1055,6 +1165,15 @@ static int scrub_start(int argc, char **argv, int resume)
                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 :
@@ -1074,30 +1193,29 @@ static int scrub_start(int argc, char **argv, int resume)
                do_print = 0;
 
        if (mkdir_p(datafile)) {
-               ERR(!do_quiet, "WARNING: cannot create scrub data "
-                              "file, mkdir %s failed: %s. Status recording "
-                              "disabled\n", datafile, strerror(errno));
+               warning_on(!do_quiet,
+    "cannot create scrub data file, mkdir %s failed: %m. Status recording disabled",
+                       datafile);
                do_record = 0;
        }
        free(datafile);
 
        path = argv[optind];
 
-       fdmnt = open_file_or_dir(path);
-       if (fdmnt < 0) {
-               ERR(!do_quiet, "ERROR: can't access '%s'\n", path);
-               return 12;
-       }
+       fdmnt = open_path_or_dev_mnt(path, &dirstream, !do_quiet);
+       if (fdmnt < 0)
+               return 1;
 
-       ret = get_fs_info(fdmnt, path, &fi_args, &di_args);
+       ret = get_fs_info(path, &fi_args, &di_args);
        if (ret) {
-               ERR(!do_quiet, "ERROR: getting dev info for scrub failed: "
-                   "%s\n", strerror(-ret));
+               error_on(!do_quiet,
+                       "getting dev info for scrub failed: %s",
+                        strerror(-ret));
                err = 1;
                goto out;
        }
        if (!fi_args.num_devices) {
-               ERR(!do_quiet, "ERROR: no devices found\n");
+               error_on(!do_quiet, "no devices found");
                err = 1;
                goto out;
        }
@@ -1105,30 +1223,50 @@ static int scrub_start(int argc, char **argv, int resume)
        uuid_unparse(fi_args.fsid, fsid);
        fdres = scrub_open_file_r(SCRUB_DATA_FILE, fsid);
        if (fdres < 0 && fdres != -ENOENT) {
-               ERR(!do_quiet, "WARNING: failed to open status file: "
-                   "%s\n", strerror(-fdres));
+               warning_on(!do_quiet, "failed to open status file: %s",
+                       strerror(-fdres));
        } else if (fdres >= 0) {
                past_scrubs = scrub_read_file(fdres, !do_quiet);
                if (IS_ERR(past_scrubs))
-                       ERR(!do_quiet, "WARNING: failed to read status file: "
-                           "%s\n", strerror(-PTR_ERR(past_scrubs)));
+                       warning_on(!do_quiet, "failed to read status file: %s",
+                               strerror(-PTR_ERR(past_scrubs)));
                close(fdres);
        }
 
+       /*
+        * Check for stale information in the status file, ie. if it's
+        * canceled=0, finished=0 but no scrub is running.
+        */
+       if (!is_scrub_running_in_kernel(fdmnt, di_args, fi_args.num_devices))
+               force = 1;
+
+       /*
+        * 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)) {
+               error_on(!do_quiet,
+                       "Scrub is already running.\n"
+                       "To cancel use 'btrfs scrub cancel %s'.\n"
+                       "To see the status use 'btrfs scrub status [-d] %s'",
+                       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));
 
        if (!t_devs || !sp || !spc.progress) {
-               ERR(!do_quiet, "ERROR: scrub failed: %s", strerror(errno));
-               err = 1;
-               goto out;
-       }
-
-       ret = pthread_attr_init(&t_attr);
-       if (ret) {
-               ERR(!do_quiet, "ERROR: pthread_attr_init failed: %s\n",
-                   strerror(ret));
+               error_on(!do_quiet, "scrub failed: %m");
                err = 1;
                goto out;
        }
@@ -1137,8 +1275,8 @@ static int scrub_start(int argc, char **argv, int resume)
                devid = di_args[i].devid;
                ret = pthread_mutex_init(&sp[i].progress_mutex, NULL);
                if (ret) {
-                       ERR(!do_quiet, "ERROR: pthread_mutex_init failed: "
-                           "%s\n", strerror(ret));
+                       error_on(!do_quiet, "pthread_mutex_init failed: %s",
+                               strerror(ret));
                        err = 1;
                        goto out;
                }
@@ -1163,13 +1301,15 @@ static int scrub_start(int argc, char **argv, int resume)
                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;
        }
 
@@ -1190,7 +1330,7 @@ static int scrub_start(int argc, char **argv, int resume)
                ret = connect(prg_fd, (struct sockaddr *)&addr, sizeof(addr));
                if (!ret || errno != ECONNREFUSED) {
                        /* ... yes, so scrub must be running. error out */
-                       fprintf(stderr, "ERROR: scrub already running\n");
+                       error("scrub already running");
                        close(prg_fd);
                        prg_fd = -1;
                        goto out;
@@ -1204,10 +1344,10 @@ static int scrub_start(int argc, char **argv, int resume)
        if (ret != -1)
                ret = listen(prg_fd, 100);
        if (ret == -1) {
-               ERR(!do_quiet, "WARNING: failed to open the progress status "
-                   "socket at %s: %s. Progress cannot be queried\n",
-                   sock_path[0] ? sock_path : SCRUB_PROGRESS_SOCKET_PATH,
-                   strerror(errno));
+               warning_on(!do_quiet,
+   "failed to open the progress status socket at %s: %m. Progress cannot be queried",
+                       sock_path[0] ? sock_path :
+                       SCRUB_PROGRESS_SOCKET_PATH);
                if (prg_fd != -1) {
                        close(prg_fd);
                        prg_fd = -1;
@@ -1221,9 +1361,9 @@ static int scrub_start(int argc, char **argv, int resume)
                ret = scrub_write_progress(&spc_write_mutex, fsid, sp,
                                           fi_args.num_devices);
                if (ret) {
-                       ERR(!do_quiet, "WARNING: failed to write the progress "
-                           "status file: %s. Status recording disabled\n",
-                           strerror(-ret));
+                       warning_on(!do_quiet,
+   "failed to write the progress status file: %s. Status recording disabled",
+                               strerror(-ret));
                        do_record = 0;
                }
        }
@@ -1231,8 +1371,7 @@ static int scrub_start(int argc, char **argv, int resume)
        if (do_background) {
                pid = fork();
                if (pid == -1) {
-                       ERR(!do_quiet, "ERROR: cannot scrub, fork failed: "
-                                       "%s\n", strerror(errno));
+                       error_on(!do_quiet, "cannot scrub, fork failed: %m");
                        err = 1;
                        goto out;
                }
@@ -1250,13 +1389,13 @@ static int scrub_start(int argc, char **argv, int resume)
                        }
                        ret = wait(&stat);
                        if (ret != pid) {
-                               ERR(!do_quiet, "ERROR: wait failed: (ret=%d) "
-                                   "%s\n", ret, strerror(errno));
+                               error_on(!do_quiet, "wait failed (ret=%d): %m",
+                                       ret);
                                err = 1;
                                goto out;
                        }
                        if (!WIFEXITED(stat) || WEXITSTATUS(stat)) {
-                               ERR(!do_quiet, "ERROR: scrub process failed\n");
+                               error_on(!do_quiet, "scrub process failed");
                                err = WIFEXITED(stat) ? WEXITSTATUS(stat) : -1;
                                goto out;
                        }
@@ -1278,13 +1417,12 @@ static int scrub_start(int argc, char **argv, int resume)
                devid = di_args[i].devid;
                gettimeofday(&tv, NULL);
                sp[i].stats.t_start = tv.tv_sec;
-               ret = pthread_create(&t_devs[i], &t_attr,
+               ret = pthread_create(&t_devs[i], NULL,
                                        scrub_one_dev, &sp[i]);
                if (ret) {
                        if (do_print)
-                               fprintf(stderr, "ERROR: creating "
-                                       "scrub_one_dev[%llu] thread failed: "
-                                       "%s\n", devid, strerror(ret));
+                       error("creating scrub_one_dev[%llu] thread failed: %s",
+                               devid, strerror(ret));
                        err = 1;
                        goto out;
                }
@@ -1296,11 +1434,11 @@ static int scrub_start(int argc, char **argv, int resume)
        spc.write_mutex = &spc_write_mutex;
        spc.shared_progress = sp;
        spc.fi = &fi_args;
-       ret = pthread_create(&t_prog, &t_attr, scrub_progress_cycle, &spc);
+       ret = pthread_create(&t_prog, NULL, scrub_progress_cycle, &spc);
        if (ret) {
                if (do_print)
-                       fprintf(stderr, "ERROR: creating progress thread "
-                               "failed: %s\n", strerror(ret));
+                       error("creating progress thread failed: %s",
+                               strerror(ret));
                err = 1;
                goto out;
        }
@@ -1313,27 +1451,30 @@ static int scrub_start(int argc, char **argv, int resume)
                ret = pthread_join(t_devs[i], NULL);
                if (ret) {
                        if (do_print)
-                               fprintf(stderr, "ERROR: pthread_join failed "
-                                       "for scrub_one_dev[%llu]: %s\n", devid,
-                                       strerror(ret));
+                         error("pthread_join failed for scrub_one_dev[%llu]: %s",
+                               devid, strerror(ret));
                        ++err;
                        continue;
                }
-               if (sp[i].ret && sp[i].ioctl_errno == ENODEV) {
-                       if (do_print)
-                               fprintf(stderr, "WARNING: device %lld not "
-                                       "present\n", devid);
-                       continue;
-               }
-               if (sp[i].ret && sp[i].ioctl_errno == ECANCELED) {
-                       ++err;
-               } else if (sp[i].ret) {
-                       if (do_print)
-                               fprintf(stderr, "ERROR: scrubbing %s failed "
-                                       "for device id %lld (%s)\n", path,
-                                       devid, strerror(sp[i].ioctl_errno));
-                       ++err;
-                       continue;
+               if (sp[i].ret) {
+                       switch (sp[i].ioctl_errno) {
+                       case ENODEV:
+                               if (do_print)
+                                       warning("device %lld not present",
+                                               devid);
+                               continue;
+                       case ECANCELED:
+                               ++err;
+                               break;
+                       default:
+                               if (do_print)
+               error("scrubbing %s failed for device id %lld: ret=%d, errno=%d (%s)",
+                                       path, devid,
+                                       sp[i].ret, sp[i].ioctl_errno,
+                                       strerror(sp[i].ioctl_errno));
+                               ++err;
+                               continue;
+                       }
                }
                if (sp[i].scrub_args.progress.uncorrectable_errors > 0)
                        e_uncorrectable++;
@@ -1369,23 +1510,24 @@ static int scrub_start(int argc, char **argv, int resume)
        ret = pthread_cancel(t_prog);
        if (!ret)
                ret = pthread_join(t_prog, &terr);
+
+       /* check for errors from the handling of the progress thread */
        if (do_print && ret) {
-               fprintf(stderr, "ERROR: progress thead handling failed: %s\n",
+               error("progress thread handling failed: %s",
                        strerror(ret));
        }
 
-       if (do_print && terr && terr != PTHREAD_CANCELED) {
-               fprintf(stderr, "ERROR: recording progress "
-                       "failed: %s\n", strerror(-PTR_ERR(terr)));
-       }
+       /* check for errors returned from the progress thread itself */
+       if (do_print && terr && terr != PTHREAD_CANCELED)
+               error("recording progress failed: %s",
+                       strerror(-PTR_ERR(terr)));
 
        if (do_record) {
                ret = scrub_write_progress(&spc_write_mutex, fsid, sp,
                                           fi_args.num_devices);
-               if (ret && do_print) {
-                       fprintf(stderr, "ERROR: failed to record the result: "
-                               "%s\n", strerror(-ret));
-               }
+               if (ret && do_print)
+                       error("failed to record the result: %s",
+                               strerror(-ret));
        }
 
        scrub_handle_sigint_child(-1);
@@ -1401,25 +1543,36 @@ out:
                if (sock_path[0])
                        unlink(sock_path);
        }
-       close(fdmnt);
+       close_file_or_dir(fdmnt, dirstream);
 
        if (err)
                return 1;
+       if (nothing_to_resume)
+               return 2;
+       if (e_uncorrectable) {
+               error_on(!do_quiet, "there are uncorrectable errors");
+               return 3;
+       }
        if (e_correctable)
-               return 7;
-       if (e_uncorrectable)
-               return 8;
+               warning_on(!do_quiet,
+                       "errors detected during scrubbing, corrected");
+
        return 0;
 }
 
 static const char * const cmd_scrub_start_usage[] = {
-       "btrfs scrub start [-Bdqr] <path>|<device>",
-       "Start a new scrub",
+       "btrfs scrub start [-BdqrRf] [-c ioprio_class -n ioprio_classdata] <path>|<device>",
+       "Start a new scrub. If a scrub is already running, the new one fails.",
        "",
        "-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 starting new scrub even if a scrub is already running",
+       "       this is useful when scrub stats record file is damaged",
        NULL
 };
 
@@ -1438,62 +1591,53 @@ static int cmd_scrub_cancel(int argc, char **argv)
 {
        char *path;
        int ret;
-       int fdmnt;
-       int err;
-       char mp[BTRFS_PATH_NAME_MAX + 1];
-       struct btrfs_fs_devices *fs_devices_mnt = NULL;
+       int fdmnt = -1;
+       DIR *dirstream = NULL;
 
-       if (check_argc_exact(argc, 2))
+       clean_args_no_options(argc, argv, cmd_scrub_cancel_usage);
+
+       if (check_argc_exact(argc - optind, 1))
                usage(cmd_scrub_cancel_usage);
 
-       path = argv[1];
+       path = argv[optind];
 
-       fdmnt = open_file_or_dir(path);
+       fdmnt = open_path_or_dev_mnt(path, &dirstream, 1);
        if (fdmnt < 0) {
-               fprintf(stderr, "ERROR: scrub cancel failed\n");
-               return 12;
+               ret = 1;
+               goto out;
        }
 
-again:
        ret = ioctl(fdmnt, BTRFS_IOC_SCRUB_CANCEL, NULL);
-       err = errno;
-
-       if (ret && err == EINVAL) {
-               /* path is not a btrfs mount point.  See if it's a device. */
-               ret = check_mounted_where(fdmnt, path, mp, sizeof(mp),
-                                         &fs_devices_mnt);
-               if (ret) {
-                       /* It is a device; open the mountpoint. */
-                       close(fdmnt);
-                       fdmnt = open_file_or_dir(mp);
-                       if (fdmnt >= 0) {
-                               path = mp;
-                               goto again;
-                       }
-               }
-       }
 
-       close(fdmnt);
-
-       if (ret) {
-               fprintf(stderr, "ERROR: scrub cancel failed on %s: %s\n", path,
-                       err == ENOTCONN ? "not running" : strerror(errno));
-               return 1;
+       if (ret < 0) {
+               error("scrub cancel failed on %s: %s", path,
+                       errno == ENOTCONN ? "not running" : strerror(errno));
+               if (errno == ENOTCONN)
+                       ret = 2;
+               else
+                       ret = 1;
+               goto out;
        }
 
+       ret = 0;
        printf("scrub cancelled\n");
 
-       return 0;
+out:
+       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",
+       "-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)",
        NULL
 };
 
@@ -1522,17 +1666,18 @@ static int cmd_scrub_status(int argc, char **argv)
        struct sockaddr_un addr = {
                .sun_family = AF_UNIX,
        };
+       int in_progress;
        int ret;
        int i;
        int fdmnt;
        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) {
                switch (c) {
                case 'd':
@@ -1552,21 +1697,19 @@ static int cmd_scrub_status(int argc, char **argv)
 
        path = argv[optind];
 
-       fdmnt = open_file_or_dir(path);
-       if (fdmnt < 0) {
-               fprintf(stderr, "ERROR: can't access to '%s'\n", path);
-               return 12;
-       }
+       fdmnt = open_path_or_dev_mnt(path, &dirstream, 1);
+       if (fdmnt < 0)
+               return 1;
 
-       ret = get_fs_info(fdmnt, path, &fi_args, &di_args);
+       ret = get_fs_info(path, &fi_args, &di_args);
        if (ret) {
-               fprintf(stderr, "ERROR: getting dev info for scrub failed: "
-                               "%s\n", strerror(-ret));
+               error("getting dev info for scrub failed: %s",
+                       strerror(-ret));
                err = 1;
                goto out;
        }
        if (!fi_args.num_devices) {
-               fprintf(stderr, "ERROR: no devices found\n");
+               error("no devices found");
                err = 1;
                goto out;
        }
@@ -1575,9 +1718,7 @@ static int cmd_scrub_status(int argc, char **argv)
 
        fdres = socket(AF_UNIX, SOCK_STREAM, 0);
        if (fdres == -1) {
-               fprintf(stderr, "ERROR: failed to create socket to "
-                       "receive progress information: %s\n",
-                       strerror(errno));
+               error("failed to create socket to receive progress information: %m");
                err = 1;
                goto out;
        }
@@ -1590,8 +1731,8 @@ static int cmd_scrub_status(int argc, char **argv)
                close(fdres);
                fdres = scrub_open_file_r(SCRUB_DATA_FILE, fsid);
                if (fdres < 0 && fdres != -ENOENT) {
-                       fprintf(stderr, "WARNING: failed to open status file: "
-                               "%s\n", strerror(-fdres));
+                       warning("failed to open status file: %s",
+                               strerror(-fdres));
                        err = 1;
                        goto out;
                }
@@ -1600,9 +1741,10 @@ static int cmd_scrub_status(int argc, char **argv)
        if (fdres >= 0) {
                past_scrubs = scrub_read_file(fdres, 1);
                if (IS_ERR(past_scrubs))
-                       fprintf(stderr, "WARNING: failed to read status: %s\n",
+                       warning("failed to read status: %s",
                                strerror(-PTR_ERR(past_scrubs)));
        }
+       in_progress = is_scrub_running_in_kernel(fdmnt, di_args, fi_args.num_devices);
 
        printf("scrub status for %s\n", fsid);
 
@@ -1615,6 +1757,7 @@ static int cmd_scrub_status(int argc, char **argv)
                                                NULL, NULL);
                                continue;
                        }
+                       last_scrub->stats.in_progress = in_progress;
                        print_scrub_dev(&di_args[i], &last_scrub->p, print_raw,
                                        last_scrub->stats.finished ?
                                                        "history" : "status",
@@ -1622,6 +1765,7 @@ static int cmd_scrub_status(int argc, char **argv)
                }
        } else {
                init_fs_stat(&fs_stat);
+               fs_stat.s.in_progress = in_progress;
                for (i = 0; i < fi_args.num_devices; ++i) {
                        last_scrub = last_dev_scrub(past_scrubs,
                                                        di_args[i].devid);
@@ -1638,17 +1782,21 @@ out:
        free(di_args);
        if (fdres > -1)
                close(fdres);
+       close_file_or_dir(fdmnt, dirstream);
 
-       return err;
+       return !!err;
 }
 
+static const char scrub_cmd_group_info[] =
+"verify checksums of data and metadata";
+
 const struct cmd_group scrub_cmd_group = {
-       scrub_cmd_group_usage, NULL, {
+       scrub_cmd_group_usage, scrub_cmd_group_info, {
                { "start", cmd_scrub_start, cmd_scrub_start_usage, NULL, 0 },
                { "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
        }
 };