Btrfs-progs: add restriper commands
authorIlya Dryomov <idryomov@gmail.com>
Fri, 3 Feb 2012 19:02:30 +0000 (21:02 +0200)
committerIlya Dryomov <idryomov@gmail.com>
Fri, 3 Feb 2012 19:02:30 +0000 (21:02 +0200)
Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
cmds-balance.c

index d141b46..a6886a0 100644 (file)
@@ -18,6 +18,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
+#include <getopt.h>
 #include <sys/ioctl.h>
 #include <errno.h>
 
@@ -35,6 +36,240 @@ static const char balance_cmd_group_info[] =
        "'btrfs filesystem balance' command is deprecated, please use\n"
        "'btrfs balance start' command instead.";
 
+static int parse_one_profile(const char *profile, u64 *flags)
+{
+       if (!strcmp(profile, "raid0")) {
+               *flags |= BTRFS_BLOCK_GROUP_RAID0;
+       } else if (!strcmp(profile, "raid1")) {
+               *flags |= BTRFS_BLOCK_GROUP_RAID1;
+       } else if (!strcmp(profile, "raid10")) {
+               *flags |= BTRFS_BLOCK_GROUP_RAID10;
+       } else if (!strcmp(profile, "dup")) {
+               *flags |= BTRFS_BLOCK_GROUP_DUP;
+       } else if (!strcmp(profile, "single")) {
+               *flags |= BTRFS_AVAIL_ALLOC_BIT_SINGLE;
+       } else {
+               fprintf(stderr, "Unknown profile '%s'\n", profile);
+               return 1;
+       }
+
+       return 0;
+}
+
+static int parse_profiles(char *profiles, u64 *flags)
+{
+       char *this_char;
+       char *save_ptr;
+
+       for (this_char = strtok_r(profiles, "|", &save_ptr);
+            this_char != NULL;
+            this_char = strtok_r(NULL, "|", &save_ptr)) {
+               if (parse_one_profile(this_char, flags))
+                       return 1;
+       }
+
+       return 0;
+}
+
+static int parse_u64(const char *str, u64 *result)
+{
+       char *endptr;
+       u64 val;
+
+       val = strtoull(str, &endptr, 10);
+       if (*endptr)
+               return 1;
+
+       *result = val;
+       return 0;
+}
+
+static int parse_range(const char *range, u64 *start, u64 *end)
+{
+       char *dots;
+
+       dots = strstr(range, "..");
+       if (dots) {
+               const char *rest = dots + 2;
+               int skipped = 0;
+
+               *dots = 0;
+
+               if (!*rest) {
+                       *end = (u64)-1;
+                       skipped++;
+               } else {
+                       if (parse_u64(rest, end))
+                               return 1;
+               }
+               if (dots == range) {
+                       *start = 0;
+                       skipped++;
+               } else {
+                       if (parse_u64(range, start))
+                               return 1;
+               }
+
+               if (*start >= *end) {
+                       fprintf(stderr, "Range %llu..%llu doesn't make "
+                               "sense\n", (unsigned long long)*start,
+                               (unsigned long long)*end);
+                       return 1;
+               }
+
+               if (skipped <= 1)
+                       return 0;
+       }
+
+       return 1;
+}
+
+static int parse_filters(char *filters, struct btrfs_balance_args *args)
+{
+       char *this_char;
+       char *value;
+       char *save_ptr;
+
+       if (!filters)
+               return 0;
+
+       for (this_char = strtok_r(filters, ",", &save_ptr);
+            this_char != NULL;
+            this_char = strtok_r(NULL, ",", &save_ptr)) {
+               if ((value = strchr(this_char, '=')) != NULL)
+                       *value++ = 0;
+               if (!strcmp(this_char, "profiles")) {
+                       if (!value || !*value) {
+                               fprintf(stderr, "the profiles filter requires "
+                                      "an argument\n");
+                               return 1;
+                       }
+                       if (parse_profiles(value, &args->profiles)) {
+                               fprintf(stderr, "Invalid profiles argument\n");
+                               return 1;
+                       }
+                       args->flags |= BTRFS_BALANCE_ARGS_PROFILES;
+               } else if (!strcmp(this_char, "usage")) {
+                       if (!value || !*value) {
+                               fprintf(stderr, "the usage filter requires "
+                                      "an argument\n");
+                               return 1;
+                       }
+                       if (parse_u64(value, &args->usage) ||
+                           args->usage < 1 || args->usage > 100) {
+                               fprintf(stderr, "Invalid usage argument: %s\n",
+                                      value);
+                               return 1;
+                       }
+                       args->flags |= BTRFS_BALANCE_ARGS_USAGE;
+               } else if (!strcmp(this_char, "devid")) {
+                       if (!value || !*value) {
+                               fprintf(stderr, "the devid filter requires "
+                                      "an argument\n");
+                               return 1;
+                       }
+                       if (parse_u64(value, &args->devid) ||
+                           args->devid == 0) {
+                               fprintf(stderr, "Invalid devid argument: %s\n",
+                                      value);
+                               return 1;
+                       }
+                       args->flags |= BTRFS_BALANCE_ARGS_DEVID;
+               } else if (!strcmp(this_char, "drange")) {
+                       if (!value || !*value) {
+                               fprintf(stderr, "the drange filter requires "
+                                      "an argument\n");
+                               return 1;
+                       }
+                       if (parse_range(value, &args->pstart, &args->pend)) {
+                               fprintf(stderr, "Invalid drange argument\n");
+                               return 1;
+                       }
+                       args->flags |= BTRFS_BALANCE_ARGS_DRANGE;
+               } else if (!strcmp(this_char, "vrange")) {
+                       if (!value || !*value) {
+                               fprintf(stderr, "the vrange filter requires "
+                                      "an argument\n");
+                               return 1;
+                       }
+                       if (parse_range(value, &args->vstart, &args->vend)) {
+                               fprintf(stderr, "Invalid vrange argument\n");
+                               return 1;
+                       }
+                       args->flags |= BTRFS_BALANCE_ARGS_VRANGE;
+               } else if (!strcmp(this_char, "convert")) {
+                       if (!value || !*value) {
+                               fprintf(stderr, "the convert option requires "
+                                      "an argument\n");
+                               return 1;
+                       }
+                       if (parse_one_profile(value, &args->target)) {
+                               fprintf(stderr, "Invalid convert argument\n");
+                               return 1;
+                       }
+                       args->flags |= BTRFS_BALANCE_ARGS_CONVERT;
+               } else if (!strcmp(this_char, "soft")) {
+                       args->flags |= BTRFS_BALANCE_ARGS_SOFT;
+               } else {
+                       fprintf(stderr, "Unrecognized balance option '%s'\n",
+                               this_char);
+                       return 1;
+               }
+       }
+
+       return 0;
+}
+
+static void dump_balance_args(struct btrfs_balance_args *args)
+{
+       if (args->flags & BTRFS_BALANCE_ARGS_CONVERT) {
+               printf("converting, target=%llu, soft is %s",
+                      (unsigned long long)args->target,
+                      (args->flags & BTRFS_BALANCE_ARGS_SOFT) ? "on" : "off");
+       } else {
+               printf("balancing");
+       }
+
+       if (args->flags & BTRFS_BALANCE_ARGS_PROFILES)
+               printf(", profiles=%llu", (unsigned long long)args->profiles);
+       if (args->flags & BTRFS_BALANCE_ARGS_USAGE)
+               printf(", usage=%llu", (unsigned long long)args->usage);
+       if (args->flags & BTRFS_BALANCE_ARGS_DEVID)
+               printf(", devid=%llu", (unsigned long long)args->devid);
+       if (args->flags & BTRFS_BALANCE_ARGS_DRANGE)
+               printf(", drange=%llu..%llu",
+                      (unsigned long long)args->pstart,
+                      (unsigned long long)args->pend);
+       if (args->flags & BTRFS_BALANCE_ARGS_VRANGE)
+               printf(", vrange=%llu..%llu",
+                      (unsigned long long)args->vstart,
+                      (unsigned long long)args->vend);
+
+       printf("\n");
+}
+
+static void dump_ioctl_balance_args(struct btrfs_ioctl_balance_args *args)
+{
+       printf("Dumping filters: flags 0x%llx, state 0x%llx, force is %s\n",
+              (unsigned long long)args->flags, (unsigned long long)args->state,
+              (args->flags & BTRFS_BALANCE_FORCE) ? "on" : "off");
+       if (args->flags & BTRFS_BALANCE_DATA) {
+               printf("  DATA (flags 0x%llx): ",
+                      (unsigned long long)args->data.flags);
+               dump_balance_args(&args->data);
+       }
+       if (args->flags & BTRFS_BALANCE_METADATA) {
+               printf("  METADATA (flags 0x%llx): ",
+                      (unsigned long long)args->meta.flags);
+               dump_balance_args(&args->meta);
+       }
+       if (args->flags & BTRFS_BALANCE_SYSTEM) {
+               printf("  SYSTEM (flags 0x%llx): ",
+                      (unsigned long long)args->sys.flags);
+               dump_balance_args(&args->sys);
+       }
+}
+
 static int do_balance(const char *path, struct btrfs_ioctl_balance_args *args)
 {
        int fd;
@@ -74,8 +309,361 @@ static int do_balance(const char *path, struct btrfs_ioctl_balance_args *args)
        return 0;
 }
 
+static const char * const cmd_balance_start_usage[] = {
+       "btrfs [filesystem] balance start [options] <path>",
+       "Balance chunks across the devices",
+       "Balance and/or convert (change allocation profile of) chunks that",
+       "passed all filters in a comma-separated list of filters for a",
+       "particular chunk type.  If filter list is not given balance all",
+       "chunks of that type.  In case none of the -d, -m or -s options is",
+       "given balance all chunks in a filesystem.",
+       "",
+       "-d[filters]    act on data chunks",
+       "-m[filters]    act on metadata chunks",
+       "-s[filetrs]    act on system chunks (only under -f)",
+       "-v             be verbose",
+       "-f             force reducing of metadata integrity",
+       NULL
+};
+
+static int cmd_balance_start(int argc, char **argv)
+{
+       struct btrfs_ioctl_balance_args args;
+       struct btrfs_balance_args *ptrs[] = { &args.data, &args.sys,
+                                               &args.meta, NULL };
+       int force = 0;
+       int verbose = 0;
+       int nofilters = 1;
+       int i;
+
+       memset(&args, 0, sizeof(args));
+
+       optind = 1;
+       while (1) {
+               int longindex;
+               static struct option longopts[] = {
+                       { "data", optional_argument, NULL, 'd'},
+                       { "metadata", optional_argument, NULL, 'm' },
+                       { "system", optional_argument, NULL, 's' },
+                       { "force", no_argument, NULL, 'f' },
+                       { "verbose", no_argument, NULL, 'v' },
+                       { 0, 0, 0, 0 }
+               };
+
+               int opt = getopt_long(argc, argv, "d::s::m::fv", longopts,
+                                     &longindex);
+               if (opt < 0)
+                       break;
+
+               switch (opt) {
+               case 'd':
+                       nofilters = 0;
+                       args.flags |= BTRFS_BALANCE_DATA;
+
+                       if (parse_filters(optarg, &args.data))
+                               return 1;
+                       break;
+               case 's':
+                       nofilters = 0;
+                       args.flags |= BTRFS_BALANCE_SYSTEM;
+
+                       if (parse_filters(optarg, &args.sys))
+                               return 1;
+                       break;
+               case 'm':
+                       nofilters = 0;
+                       args.flags |= BTRFS_BALANCE_METADATA;
+
+                       if (parse_filters(optarg, &args.meta))
+                               return 1;
+                       break;
+               case 'f':
+                       force = 1;
+                       break;
+               case 'v':
+                       verbose = 1;
+                       break;
+               default:
+                       usage(cmd_balance_start_usage);
+               }
+       }
+
+       if (check_argc_exact(argc - optind, 1))
+               usage(cmd_balance_start_usage);
+
+       /*
+        * allow -s only under --force, otherwise do with system chunks
+        * the same thing we were ordered to do with meta chunks
+        */
+       if (args.flags & BTRFS_BALANCE_SYSTEM) {
+               if (!force) {
+                       fprintf(stderr,
+"Refusing to explicitly operate on system chunks.\n"
+"Pass --force if you really want to do that.\n");
+                       return 1;
+               }
+       } else if (args.flags & BTRFS_BALANCE_METADATA) {
+               args.flags |= BTRFS_BALANCE_SYSTEM;
+               memcpy(&args.sys, &args.meta,
+                       sizeof(struct btrfs_balance_args));
+       }
+
+       if (nofilters) {
+               /* relocate everything - no filters */
+               args.flags |= BTRFS_BALANCE_TYPE_MASK;
+       }
+
+       /* drange makes sense only when devid is set */
+       for (i = 0; ptrs[i]; i++) {
+               if ((ptrs[i]->flags & BTRFS_BALANCE_ARGS_DRANGE) &&
+                   !(ptrs[i]->flags & BTRFS_BALANCE_ARGS_DEVID)) {
+                       fprintf(stderr, "drange filter can be used only if "
+                               "devid filter is used\n");
+                       return 1;
+               }
+       }
+
+       /* soft makes sense only when convert for corresponding type is set */
+       for (i = 0; ptrs[i]; i++) {
+               if ((ptrs[i]->flags & BTRFS_BALANCE_ARGS_SOFT) &&
+                   !(ptrs[i]->flags & BTRFS_BALANCE_ARGS_CONVERT)) {
+                       fprintf(stderr, "'soft' option can be used only if "
+                               "changing profiles\n");
+                       return 1;
+               }
+       }
+
+       if (force)
+               args.flags |= BTRFS_BALANCE_FORCE;
+       if (verbose)
+               dump_ioctl_balance_args(&args);
+
+       return do_balance(argv[optind], &args);
+}
+
+static const char * const cmd_balance_pause_usage[] = {
+       "btrfs [filesystem] balance pause <path>",
+       "Pause running balance",
+       NULL
+};
+
+static int cmd_balance_pause(int argc, char **argv)
+{
+       const char *path;
+       int fd;
+       int ret;
+       int e;
+
+       if (check_argc_exact(argc, 2))
+               usage(cmd_balance_pause_usage);
+
+       path = argv[1];
+
+       fd = open_file_or_dir(path);
+       if (fd < 0) {
+               fprintf(stderr, "ERROR: can't access to '%s'\n", path);
+               return 12;
+       }
+
+       ret = ioctl(fd, BTRFS_IOC_BALANCE_CTL, BTRFS_BALANCE_CTL_PAUSE);
+       e = errno;
+       close(fd);
+
+       if (ret < 0) {
+               fprintf(stderr, "ERROR: balance pause on '%s' failed - %s\n",
+                       path, (e == ENOTCONN) ? "Not running" : strerror(e));
+               return 19;
+       }
+
+       return 0;
+}
+
+static const char * const cmd_balance_cancel_usage[] = {
+       "btrfs [filesystem] balance cancel <path>",
+       "Cancel running or paused balance",
+       NULL
+};
+
+static int cmd_balance_cancel(int argc, char **argv)
+{
+       const char *path;
+       int fd;
+       int ret;
+       int e;
+
+       if (check_argc_exact(argc, 2))
+               usage(cmd_balance_cancel_usage);
+
+       path = argv[1];
+
+       fd = open_file_or_dir(path);
+       if (fd < 0) {
+               fprintf(stderr, "ERROR: can't access to '%s'\n", path);
+               return 12;
+       }
+
+       ret = ioctl(fd, BTRFS_IOC_BALANCE_CTL, BTRFS_BALANCE_CTL_CANCEL);
+       e = errno;
+       close(fd);
+
+       if (ret < 0) {
+               fprintf(stderr, "ERROR: balance cancel on '%s' failed - %s\n",
+                       path, (e == ENOTCONN) ? "Not in progress" : strerror(e));
+               return 19;
+       }
+
+       return 0;
+}
+
+static const char * const cmd_balance_resume_usage[] = {
+       "btrfs [filesystem] balance resume <path>",
+       "Resume interrupted balance",
+       NULL
+};
+
+static int cmd_balance_resume(int argc, char **argv)
+{
+       struct btrfs_ioctl_balance_args args;
+       const char *path;
+       int fd;
+       int ret;
+       int e;
+
+       if (check_argc_exact(argc, 2))
+               usage(cmd_balance_resume_usage);
+
+       path = argv[1];
+
+       fd = open_file_or_dir(path);
+       if (fd < 0) {
+               fprintf(stderr, "ERROR: can't access to '%s'\n", path);
+               return 12;
+       }
+
+       memset(&args, 0, sizeof(args));
+       args.flags |= BTRFS_BALANCE_RESUME;
+
+       ret = ioctl(fd, BTRFS_IOC_BALANCE_V2, &args);
+       e = errno;
+       close(fd);
+
+       if (ret < 0) {
+               if (e == ECANCELED) {
+                       if (args.state & BTRFS_BALANCE_STATE_PAUSE_REQ)
+                               fprintf(stderr, "balance paused by user\n");
+                       if (args.state & BTRFS_BALANCE_STATE_CANCEL_REQ)
+                               fprintf(stderr, "balance canceled by user\n");
+               } else if (e == ENOTCONN || e == EINPROGRESS) {
+                       fprintf(stderr, "ERROR: balance resume on '%s' "
+                               "failed - %s\n", path,
+                               (e == ENOTCONN) ? "Not in progress" :
+                                                 "Already running");
+                       return 19;
+               } else {
+                       fprintf(stderr,
+"ERROR: error during balancing '%s' - %s\n"
+"There may be more info in syslog - try dmesg | tail\n", path, strerror(e));
+                       return 19;
+               }
+       } else {
+               printf("Done, had to relocate %llu out of %llu chunks\n",
+                      (unsigned long long)args.stat.completed,
+                      (unsigned long long)args.stat.considered);
+       }
+
+       return 0;
+}
+
+static const char * const cmd_balance_status_usage[] = {
+       "btrfs [filesystem] balance status [-v] <path>",
+       "Show status of running or paused balance",
+       "",
+       "-v     be verbose",
+       NULL
+};
+
+static int cmd_balance_status(int argc, char **argv)
+{
+       struct btrfs_ioctl_balance_args args;
+       const char *path;
+       int fd;
+       int verbose = 0;
+       int ret;
+       int e;
+
+       optind = 1;
+       while (1) {
+               int longindex;
+               static struct option longopts[] = {
+                       { "verbose", no_argument, NULL, 'v' },
+                       { 0, 0, 0, 0}
+               };
+
+               int opt = getopt_long(argc, argv, "v", longopts, &longindex);
+               if (opt < 0)
+                       break;
+
+               switch (opt) {
+               case 'v':
+                       verbose = 1;
+                       break;
+               default:
+                       usage(cmd_balance_status_usage);
+               }
+       }
+
+       if (check_argc_exact(argc - optind, 1))
+               usage(cmd_balance_status_usage);
+
+       path = argv[optind];
+
+       fd = open_file_or_dir(path);
+       if (fd < 0) {
+               fprintf(stderr, "ERROR: can't access to '%s'\n", path);
+               return 12;
+       }
+
+       ret = ioctl(fd, BTRFS_IOC_BALANCE_PROGRESS, &args);
+       e = errno;
+       close(fd);
+
+       if (ret < 0) {
+               fprintf(stderr, "ERROR: balance status on '%s' failed - %s\n",
+                       path, (e == ENOTCONN) ? "Not in progress" : strerror(e));
+               return 19;
+       }
+
+       if (args.state & BTRFS_BALANCE_STATE_RUNNING) {
+               printf("Balance on '%s' is running", path);
+               if (args.state & BTRFS_BALANCE_STATE_CANCEL_REQ)
+                       printf(", cancel requested\n");
+               else if (args.state & BTRFS_BALANCE_STATE_PAUSE_REQ)
+                       printf(", pause requested\n");
+               else
+                       printf("\n");
+       } else {
+               printf("Balance on '%s' is paused\n", path);
+       }
+
+       printf("%llu out of about %llu chunks balanced (%llu considered), "
+              "%3.f%% left\n", (unsigned long long)args.stat.completed,
+              (unsigned long long)args.stat.expected,
+              (unsigned long long)args.stat.considered,
+              100 * (1 - (float)args.stat.completed/args.stat.expected));
+
+       if (verbose)
+               dump_ioctl_balance_args(&args);
+
+       return 0;
+}
+
 const struct cmd_group balance_cmd_group = {
        balance_cmd_group_usage, balance_cmd_group_info, {
+               { "start", cmd_balance_start, cmd_balance_start_usage, NULL, 0 },
+               { "pause", cmd_balance_pause, cmd_balance_pause_usage, NULL, 0 },
+               { "cancel", cmd_balance_cancel, cmd_balance_cancel_usage, NULL, 0 },
+               { "resume", cmd_balance_resume, cmd_balance_resume_usage, NULL, 0 },
+               { "status", cmd_balance_status, cmd_balance_status_usage, NULL, 0 },
                { 0, 0, 0, 0, 0 }
        }
 };