btrfs-progs: tests: add shell quotes to mkfs test scripts
[platform/upstream/btrfs-progs.git] / cmds-replace.c
index 9397396..032a44f 100644 (file)
@@ -37,7 +37,8 @@
 #include "disk-io.h"
 
 #include "commands.h"
-
+#include "help.h"
+#include "mkfs/common.h"
 
 static int print_replace_status(int fd, const char *path, int once);
 static char *time2string(char *buf, size_t s, __u64 t);
@@ -53,6 +54,8 @@ static const char *replace_dev_result2string(__u64 result)
                return "not started";
        case BTRFS_IOCTL_DEV_REPLACE_RESULT_ALREADY_STARTED:
                return "already started";
+       case BTRFS_IOCTL_DEV_REPLACE_RESULT_SCRUB_INPROGRESS:
+               return "scrub is in progress";
        default:
                return "<illegal result value>";
        }
@@ -63,24 +66,16 @@ static const char * const replace_cmd_group_usage[] = {
        NULL
 };
 
-static int is_numerical(const char *str)
-{
-       if (!(*str >= '0' && *str <= '9'))
-               return 0;
-       while (*str >= '0' && *str <= '9')
-               str++;
-       if (*str != '\0')
-               return 0;
-       return 1;
-}
-
 static int dev_replace_cancel_fd = -1;
 static void dev_replace_sigint_handler(int signal)
 {
+       int ret;
        struct btrfs_ioctl_dev_replace_args args = {0};
 
        args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
-       ioctl(dev_replace_cancel_fd, BTRFS_IOC_DEV_REPLACE, &args);
+       ret = ioctl(dev_replace_cancel_fd, BTRFS_IOC_DEV_REPLACE, &args);
+       if (ret < 0)
+               perror("Device replace cancel failed");
 }
 
 static int dev_replace_handle_sigint(int fd)
@@ -93,33 +88,33 @@ static int dev_replace_handle_sigint(int fd)
        return sigaction(SIGINT, &sa, NULL);
 }
 
-static const char *const cmd_start_replace_usage[] = {
-       "btrfs replace start srcdev|devid targetdev [-Bfr] mount_point",
+static const char *const cmd_replace_start_usage[] = {
+       "btrfs replace start [-Bfr] <srcdev>|<devid> <targetdev> <mount_point>",
        "Replace device of a btrfs filesystem.",
        "On a live filesystem, duplicate the data to the target device which",
        "is currently stored on the source device. If the source device is not",
        "available anymore, or if the -r option is set, the data is built",
        "only using the RAID redundancy mechanisms. After completion of the",
        "operation, the source device is removed from the filesystem.",
-       "If the srcdev is a numerical value, it is assumed to be the device id",
-       "of the filesystem which is mounted at mount_point, otherwise it is",
+       "If the <srcdev> is a numerical value, it is assumed to be the device id",
+       "of the filesystem which is mounted at <mount_point>, otherwise it is",
        "the path to the source device. If the source device is disconnected,",
-       "from the system, you have to use the devid parameter format.",
-       "The targetdev needs to be same size or larger than the srcdev.",
+       "from the system, you have to use the <devid> parameter format.",
+       "The <targetdev> needs to be same size or larger than the <srcdev>.",
        "",
-       "-r     only read from srcdev if no other zero-defect mirror exists",
+       "-r     only read from <srcdev> if no other zero-defect mirror exists",
        "       (enable this if your drive has lots of read errors, the access",
        "       would be very slow)",
-       "-f     force using and overwriting targetdev even if it looks like",
+       "-f     force using and overwriting <targetdev> even if it looks like",
        "       containing a valid btrfs filesystem. A valid filesystem is",
        "       assumed if a btrfs superblock is found which contains a",
        "       correct checksum. Devices which are currently mounted are",
-       "       never allowed to be used as the targetdev",
+       "       never allowed to be used as the <targetdev>",
        "-B     do not background",
        NULL
 };
 
-static int cmd_start_replace(int argc, char **argv)
+static int cmd_replace_start(int argc, char **argv)
 {
        struct btrfs_ioctl_dev_replace_args start_args = {0};
        struct btrfs_ioctl_dev_replace_args status_args = {0};
@@ -127,19 +122,17 @@ static int cmd_start_replace(int argc, char **argv)
        int i;
        int c;
        int fdmnt = -1;
-       int fdsrcdev = -1;
        int fddstdev = -1;
        char *path;
        char *srcdev;
-       char *dstdev;
+       char *dstdev = NULL;
        int avoid_reading_from_srcdev = 0;
        int force_using_targetdev = 0;
-       u64 total_devs = 1;
-       struct btrfs_fs_devices *fs_devices_mnt = NULL;
-       struct stat st;
        u64 dstdev_block_count;
        int do_not_background = 0;
-       int mixed = 0;
+       DIR *dirstream = NULL;
+       u64 srcdev_size;
+       u64 dstdev_size;
 
        while ((c = getopt(argc, argv, "Brf")) != -1) {
                switch (c) {
@@ -154,7 +147,7 @@ static int cmd_start_replace(int argc, char **argv)
                        break;
                case '?':
                default:
-                       usage(cmd_start_replace_usage);
+                       usage(cmd_replace_start_usage);
                }
        }
 
@@ -163,64 +156,63 @@ static int cmd_start_replace(int argc, char **argv)
                 BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_AVOID :
                 BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_ALWAYS;
        if (check_argc_exact(argc - optind, 3))
-               usage(cmd_start_replace_usage);
+               usage(cmd_replace_start_usage);
        path = argv[optind + 2];
-       fdmnt = open_file_or_dir(path);
-       if (fdmnt < 0) {
-               fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
-                       path, strerror(errno));
+
+       fdmnt = open_path_or_dev_mnt(path, &dirstream, 1);
+       if (fdmnt < 0)
                goto leave_with_error;
-       }
 
        /* check for possible errors before backgrounding */
        status_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
+       status_args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
        ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &status_args);
-       if (ret) {
+       if (ret < 0) {
                fprintf(stderr,
-                       "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s, %s\n",
-                       path, strerror(errno),
-                       replace_dev_result2string(status_args.result));
+                       "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %m",
+                       path);
+               if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
+                       fprintf(stderr, ", %s\n",
+                               replace_dev_result2string(status_args.result));
+               else
+                       fprintf(stderr, "\n");
                goto leave_with_error;
        }
 
        if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
-               fprintf(stderr,
-                       "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
+               error("ioctl(DEV_REPLACE_STATUS) on '%s' returns error: %s",
                        path, replace_dev_result2string(status_args.result));
                goto leave_with_error;
        }
 
        if (status_args.status.replace_state ==
            BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED) {
-               fprintf(stderr,
-                       "ERROR: btrfs replace on \"%s\" already started!\n",
-                       path);
+               error("device replace on '%s' already started", path);
                goto leave_with_error;
        }
 
        srcdev = argv[optind];
-       dstdev = argv[optind + 1];
+       dstdev = canonicalize_path(argv[optind + 1]);
+       if (!dstdev) {
+               error("cannot canonicalize path '%s': %m",
+                       argv[optind + 1]);
+               goto leave_with_error;
+       }
 
-       if (is_numerical(srcdev)) {
+       if (string_is_numerical(srcdev)) {
                struct btrfs_ioctl_fs_info_args fi_args;
                struct btrfs_ioctl_dev_info_args *di_args = NULL;
 
-               if (atoi(srcdev) == 0) {
-                       fprintf(stderr, "Error: Failed to parse the numerical devid value '%s'\n",
-                               srcdev);
-                       goto leave_with_error;
-               }
-               start_args.start.srcdevid = (__u64)atoi(srcdev);
+               start_args.start.srcdevid = arg_strtou64(srcdev);
 
-               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 devstats failed: "
-                                       "%s\n", strerror(-ret));
+                       error("failed to get device info: %s", strerror(-ret));
                        free(di_args);
                        goto leave_with_error;
                }
                if (!fi_args.num_devices) {
-                       fprintf(stderr, "ERROR: no devices found\n");
+                       error("no devices found");
                        free(di_args);
                        goto leave_with_error;
                }
@@ -228,139 +220,118 @@ static int cmd_start_replace(int argc, char **argv)
                for (i = 0; i < fi_args.num_devices; i++)
                        if (start_args.start.srcdevid == di_args[i].devid)
                                break;
+               srcdev_size = di_args[i].total_bytes;
+               free(di_args);
                if (i == fi_args.num_devices) {
-                       fprintf(stderr, "Error: '%s' is not a valid devid for filesystem '%s'\n",
+                       error("'%s' is not a valid devid for filesystem '%s'",
                                srcdev, path);
                        goto leave_with_error;
                }
-       } else {
-               fdsrcdev = open(srcdev, O_RDWR);
-               if (fdsrcdev < 0) {
-                       fprintf(stderr, "Error: Unable to open device '%s'\n",
-                               srcdev);
-                       goto leave_with_error;
-               }
-               ret = fstat(fdsrcdev, &st);
-               if (ret) {
-                       fprintf(stderr, "Error: Unable to stat '%s'\n", srcdev);
-                       goto leave_with_error;
-               }
-               if (!S_ISBLK(st.st_mode)) {
-                       fprintf(stderr, "Error: '%s' is not a block device\n",
-                               srcdev);
-                       goto leave_with_error;
-               }
+       } else if (is_block_device(srcdev) > 0) {
                strncpy((char *)start_args.start.srcdev_name, srcdev,
                        BTRFS_DEVICE_PATH_NAME_MAX);
-               close(fdsrcdev);
-               fdsrcdev = -1;
                start_args.start.srcdevid = 0;
+               srcdev_size = get_partition_size(srcdev);
+       } else {
+               error("source device must be a block device or a devid");
+               goto leave_with_error;
        }
 
-       ret = check_mounted(dstdev);
-       if (ret < 0) {
-               fprintf(stderr, "Error checking %s mount status\n", dstdev);
+       ret = test_dev_for_mkfs(dstdev, force_using_targetdev);
+       if (ret)
                goto leave_with_error;
-       }
-       if (ret == 1) {
-               fprintf(stderr,
-                       "Error, target device %s is in use and currently mounted!\n",
-                       dstdev);
+
+       dstdev_size = get_partition_size(dstdev);
+       if (srcdev_size > dstdev_size) {
+               error("target device smaller than source device (required %llu bytes)",
+                       srcdev_size);
                goto leave_with_error;
        }
+
        fddstdev = open(dstdev, O_RDWR);
        if (fddstdev < 0) {
-               fprintf(stderr, "Unable to open %s\n", dstdev);
-               goto leave_with_error;
-       }
-       ret = btrfs_scan_one_device(fddstdev, dstdev, &fs_devices_mnt,
-                                   &total_devs, BTRFS_SUPER_INFO_OFFSET);
-       if (ret >= 0 && !force_using_targetdev) {
-               fprintf(stderr,
-                       "Error, target device %s contains filesystem, use '-f' to force overwriting.\n",
-                       dstdev);
-               goto leave_with_error;
-       }
-       ret = fstat(fddstdev, &st);
-       if (ret) {
-               fprintf(stderr, "Error: Unable to stat '%s'\n", dstdev);
-               goto leave_with_error;
-       }
-       if (!S_ISBLK(st.st_mode)) {
-               fprintf(stderr, "Error: '%s' is not a block device\n", dstdev);
+               error("unable to open %s: %m", dstdev);
                goto leave_with_error;
        }
        strncpy((char *)start_args.start.tgtdev_name, dstdev,
                BTRFS_DEVICE_PATH_NAME_MAX);
-       if (btrfs_prepare_device(fddstdev, dstdev, 1, &dstdev_block_count, 0,
-                                &mixed, 0)) {
-               fprintf(stderr, "Error: Failed to prepare device '%s'\n",
-                       dstdev);
+       ret = btrfs_prepare_device(fddstdev, dstdev, &dstdev_block_count, 0,
+                       PREP_DEVICE_ZERO_END | PREP_DEVICE_VERBOSE);
+       if (ret)
                goto leave_with_error;
-       }
+
        close(fddstdev);
        fddstdev = -1;
+       free(dstdev);
+       dstdev = NULL;
 
        dev_replace_handle_sigint(fdmnt);
        if (!do_not_background) {
                if (daemon(0, 0) < 0) {
-                       fprintf(stderr, "ERROR, backgrounding failed: %s\n",
-                               strerror(errno));
+                       error("backgrounding failed: %m");
                        goto leave_with_error;
                }
        }
 
        start_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_START;
+       start_args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
        ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &start_args);
        if (do_not_background) {
-               if (ret) {
+               if (ret < 0) {
                        fprintf(stderr,
-                               "ERROR: ioctl(DEV_REPLACE_START) failed on \"%s\": %s, %s\n",
-                               path, strerror(errno),
-                               replace_dev_result2string(start_args.result));
+                               "ERROR: ioctl(DEV_REPLACE_START) failed on \"%s\": %m",
+                               path);
+                       if (start_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
+                               fprintf(stderr, ", %s\n",
+                                       replace_dev_result2string(start_args.result));
+                       else
+                               fprintf(stderr, "\n");
+
+                       if (errno == EOPNOTSUPP)
+                               warning("device replace of RAID5/6 not supported with this kernel");
+
                        goto leave_with_error;
                }
 
                if (start_args.result !=
                    BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
-                       fprintf(stderr,
-                               "ERROR: ioctl(DEV_REPLACE_START) on \"%s\" returns error: %s\n",
+                       error("ioctl(DEV_REPLACE_START) on '%s' returns error: %s",
                                path,
                                replace_dev_result2string(start_args.result));
                        goto leave_with_error;
                }
        }
-       close(fdmnt);
+       close_file_or_dir(fdmnt, dirstream);
        return 0;
 
 leave_with_error:
+       if (dstdev)
+               free(dstdev);
        if (fdmnt != -1)
                close(fdmnt);
-       if (fdsrcdev != -1)
-               close(fdsrcdev);
        if (fddstdev != -1)
                close(fddstdev);
-       return -1;
+       return 1;
 }
 
-static const char *const cmd_status_replace_usage[] = {
-       "btrfs replace status mount_point [-1]",
+static const char *const cmd_replace_status_usage[] = {
+       "btrfs replace status [-1] <mount_point>",
        "Print status and progress information of a running device replace",
        "operation",
        "",
-       "-1     print once instead of print continously until the replace",
+       "-1     print once instead of print continuously until the replace",
        "       operation finishes (or is canceled)",
        NULL
 };
 
-static int cmd_status_replace(int argc, char **argv)
+static int cmd_replace_status(int argc, char **argv)
 {
        int fd;
-       int e;
        int c;
        char *path;
        int once = 0;
        int ret;
+       DIR *dirstream = NULL;
 
        while ((c = getopt(argc, argv, "1")) != -1) {
                switch (c) {
@@ -369,25 +340,21 @@ static int cmd_status_replace(int argc, char **argv)
                        break;
                case '?':
                default:
-                       usage(cmd_status_replace_usage);
+                       usage(cmd_replace_status_usage);
                }
        }
 
        if (check_argc_exact(argc - optind, 1))
-               usage(cmd_status_replace_usage);
+               usage(cmd_replace_status_usage);
 
        path = argv[optind];
-       fd = open_file_or_dir(path);
-       e = errno;
-       if (fd < 0) {
-               fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
-                       path, strerror(e));
-               return -1;
-       }
+       fd = btrfs_open_dir(path, &dirstream, 1);
+       if (fd < 0)
+               return 1;
 
        ret = print_replace_status(fd, path, once);
-       close(fd);
-       return ret;
+       close_file_or_dir(fd, dirstream);
+       return !!ret;
 }
 
 static int print_replace_status(int fd, const char *path, int once)
@@ -404,22 +371,28 @@ static int print_replace_status(int fd, const char *path, int once)
 
        for (;;) {
                args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
+               args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
                ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
-               if (ret) {
-                       fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s, %s\n",
-                               path, strerror(errno),
-                               replace_dev_result2string(args.result));
+               if (ret < 0) {
+                       fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %m",
+                               path);
+                       if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
+                               fprintf(stderr, ", %s\n",
+                                       replace_dev_result2string(args.result));
+                       else
+                               fprintf(stderr, "\n");
                        return ret;
                }
 
-               status = &args.status;
                if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
-                       fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
+                       error("ioctl(DEV_REPLACE_STATUS) on '%s' returns error: %s",
                                path,
                                replace_dev_result2string(args.result));
                        return -1;
                }
 
+               status = &args.status;
+
                skip_stats = 0;
                num_chars = 0;
                switch (status->replace_state) {
@@ -464,9 +437,9 @@ static int print_replace_status(int fd, const char *path, int once)
                        printf("Never started");
                        break;
                default:
-                       prevent_loop = 1;
-                       assert(0);
-                       break;
+                       error("unknown status from ioctl DEV_REPLACE_STATUS on '%s': %llu",
+                                       path, status->replace_state);
+                       return -EINVAL;
                }
 
                if (!skip_stats)
@@ -477,7 +450,7 @@ static int print_replace_status(int fd, const char *path, int once)
                                 status->num_uncorrectable_read_errors);
                if (once || prevent_loop) {
                        printf("\n");
-                       return 0;
+                       break;
                }
 
                fflush(stdout);
@@ -513,63 +486,71 @@ progress2string(char *buf, size_t s, int progress_1000)
        return buf;
 }
 
-static const char *const cmd_cancel_replace_usage[] = {
-       "btrfs replace cancel mount_point",
+static const char *const cmd_replace_cancel_usage[] = {
+       "btrfs replace cancel <mount_point>",
        "Cancel a running device replace operation.",
        NULL
 };
 
-static int cmd_cancel_replace(int argc, char **argv)
+static int cmd_replace_cancel(int argc, char **argv)
 {
        struct btrfs_ioctl_dev_replace_args args = {0};
        int ret;
        int c;
        int fd;
-       int e;
        char *path;
+       DIR *dirstream = NULL;
 
        while ((c = getopt(argc, argv, "")) != -1) {
                switch (c) {
                case '?':
                default:
-                       usage(cmd_cancel_replace_usage);
+                       usage(cmd_replace_cancel_usage);
                }
        }
 
        if (check_argc_exact(argc - optind, 1))
-               usage(cmd_cancel_replace_usage);
+               usage(cmd_replace_cancel_usage);
 
        path = argv[optind];
-       fd = open_file_or_dir(path);
-       if (fd < 0) {
-               fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
-                       path, strerror(errno));
-               return -1;
-       }
+       fd = btrfs_open_dir(path, &dirstream, 1);
+       if (fd < 0)
+               return 1;
 
        args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
+       args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
        ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
-       e = errno;
-       close(fd);
-       if (ret) {
-               fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_CANCEL) failed on \"%s\": %s, %s\n",
-                       path, strerror(e),
-                       replace_dev_result2string(args.result));
-               return ret;
+       close_file_or_dir(fd, dirstream);
+       if (ret < 0) {
+               fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_CANCEL) failed on \"%s\": %m",
+                       path);
+               if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
+                       fprintf(stderr, ", %s\n",
+                               replace_dev_result2string(args.result));
+               else
+                       fprintf(stderr, "\n");
+               return 1;
+       }
+       if (args.result == BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED) {
+               printf("INFO: ioctl(DEV_REPLACE_CANCEL)\"%s\": %s\n",
+                       path, replace_dev_result2string(args.result));
+               return 2;
        }
-
        return 0;
 }
 
+static const char replace_cmd_group_info[] =
+"replace a device in the filesystem";
+
 const struct cmd_group replace_cmd_group = {
-       replace_cmd_group_usage, NULL, {
-               { "start", cmd_start_replace, cmd_start_replace_usage, NULL,
+       replace_cmd_group_usage, replace_cmd_group_info, {
+               { "start", cmd_replace_start, cmd_replace_start_usage, NULL,
                  0 },
-               { "status", cmd_status_replace, cmd_status_replace_usage, NULL,
+               { "status", cmd_replace_status, cmd_replace_status_usage, NULL,
                  0 },
-               { "cancel", cmd_cancel_replace, cmd_cancel_replace_usage, NULL,
+               { "cancel", cmd_replace_cancel, cmd_replace_cancel_usage, NULL,
                  0 },
-               { 0, 0, 0, 0, 0 }
+               NULL_CMD_STRUCT
        }
 };