btrfs-progs: remove a dead break before usage()
[platform/upstream/btrfs-progs.git] / cmds-scrub.c
index f06eb20..128537b 100644 (file)
@@ -24,6 +24,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>
@@ -60,6 +61,15 @@ struct scrub_stats {
        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;
@@ -69,6 +79,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 {
@@ -127,7 +139,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 +150,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");
@@ -273,7 +285,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++);
@@ -813,6 +825,14 @@ 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)
+               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;
@@ -847,7 +867,7 @@ static void *scrub_progress_cycle(void *ctx)
        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;
@@ -987,7 +1007,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;
@@ -998,13 +1018,34 @@ 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 const char * const cmd_scrub_start_usage[];
 static const char * const cmd_scrub_resume_usage[];
 
@@ -1029,6 +1070,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;
@@ -1046,15 +1089,18 @@ static int scrub_start(int argc, char **argv, int resume)
        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;
@@ -1073,6 +1119,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 :
@@ -1101,11 +1156,11 @@ static int scrub_start(int argc, char **argv, int resume)
 
        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);
@@ -1134,6 +1189,27 @@ static int scrub_start(int argc, char **argv, int resume)
                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));
@@ -1182,13 +1258,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;
        }
 
@@ -1423,25 +1501,32 @@ 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
 };
 
@@ -1461,13 +1546,14 @@ static int cmd_scrub_cancel(int argc, char **argv)
        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));
@@ -1480,7 +1566,10 @@ static int cmd_scrub_cancel(int argc, char **argv)
        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;
        }
 
@@ -1488,19 +1577,20 @@ static int cmd_scrub_cancel(int argc, char **argv)
        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
 };
 
@@ -1535,9 +1625,10 @@ static int cmd_scrub_status(int argc, char **argv)
        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) {
@@ -1559,11 +1650,11 @@ static int cmd_scrub_status(int argc, char **argv)
 
        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);
@@ -1646,8 +1737,9 @@ out:
        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 = {
@@ -1656,7 +1748,7 @@ 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
        }
 };