summaryrefslogtreecommitdiff
path: root/cmds-replace.c
diff options
context:
space:
mode:
Diffstat (limited to 'cmds-replace.c')
-rw-r--r--cmds-replace.c561
1 files changed, 561 insertions, 0 deletions
diff --git a/cmds-replace.c b/cmds-replace.c
new file mode 100644
index 00000000..6036e2f9
--- /dev/null
+++ b/cmds-replace.c
@@ -0,0 +1,561 @@
+/*
+ * Copyright (C) 2012 STRATO. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License v2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 021110-1307, USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <assert.h>
+#include <inttypes.h>
+#include <sys/wait.h>
+
+#include "kerncompat.h"
+#include "ctree.h"
+#include "ioctl.h"
+#include "utils.h"
+#include "volumes.h"
+#include "disk-io.h"
+
+#include "commands.h"
+
+
+static int print_replace_status(int fd, const char *path, int once);
+static char *time2string(char *buf, size_t s, __u64 t);
+static char *progress2string(char *buf, size_t s, int progress_1000);
+
+
+static const char *replace_dev_result2string(__u64 result)
+{
+ switch (result) {
+ case BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR:
+ return "no error";
+ case BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED:
+ 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>";
+ }
+}
+
+static const char * const replace_cmd_group_usage[] = {
+ "btrfs replace <command> [<args>]",
+ NULL
+};
+
+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;
+ 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)
+{
+ struct sigaction sa = {
+ .sa_handler = fd == -1 ? SIG_DFL : dev_replace_sigint_handler
+ };
+
+ dev_replace_cancel_fd = fd;
+ return sigaction(SIGINT, &sa, NULL);
+}
+
+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",
+ "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>.",
+ "",
+ "-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",
+ " 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>",
+ "-B do not background",
+ NULL
+};
+
+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};
+ int ret;
+ int i;
+ int c;
+ int fdmnt = -1;
+ int fddstdev = -1;
+ char *path;
+ char *srcdev;
+ char *dstdev = NULL;
+ int avoid_reading_from_srcdev = 0;
+ int force_using_targetdev = 0;
+ u64 dstdev_block_count;
+ int do_not_background = 0;
+ DIR *dirstream = NULL;
+ u64 srcdev_size;
+ u64 dstdev_size;
+
+ while ((c = getopt(argc, argv, "Brf")) != -1) {
+ switch (c) {
+ case 'B':
+ do_not_background = 1;
+ break;
+ case 'r':
+ avoid_reading_from_srcdev = 1;
+ break;
+ case 'f':
+ force_using_targetdev = 1;
+ break;
+ case '?':
+ default:
+ usage(cmd_replace_start_usage);
+ }
+ }
+
+ start_args.start.cont_reading_from_srcdev_mode =
+ avoid_reading_from_srcdev ?
+ 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_replace_start_usage);
+ path = argv[optind + 2];
+
+ 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 < 0) {
+ fprintf(stderr,
+ "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s",
+ path, strerror(errno));
+ 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) {
+ 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) {
+ error("device replace on '%s' already started", path);
+ goto leave_with_error;
+ }
+
+ srcdev = argv[optind];
+ dstdev = canonicalize_path(argv[optind + 1]);
+ if (!dstdev) {
+ error("cannot canonicalize path '%s': %s",
+ argv[optind + 1], strerror(errno));
+ goto leave_with_error;
+ }
+
+ if (string_is_numerical(srcdev)) {
+ struct btrfs_ioctl_fs_info_args fi_args;
+ struct btrfs_ioctl_dev_info_args *di_args = NULL;
+
+ start_args.start.srcdevid = arg_strtou64(srcdev);
+
+ ret = get_fs_info(path, &fi_args, &di_args);
+ if (ret) {
+ error("failed to get device info: %s", strerror(-ret));
+ free(di_args);
+ goto leave_with_error;
+ }
+ if (!fi_args.num_devices) {
+ error("no devices found");
+ free(di_args);
+ goto leave_with_error;
+ }
+
+ 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) {
+ error("'%s' is not a valid devid for filesystem '%s'",
+ srcdev, path);
+ goto leave_with_error;
+ }
+ } else if (is_block_device(srcdev) > 0) {
+ strncpy((char *)start_args.start.srcdev_name, srcdev,
+ BTRFS_DEVICE_PATH_NAME_MAX);
+ 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 = test_dev_for_mkfs(dstdev, force_using_targetdev);
+ if (ret)
+ goto leave_with_error;
+
+ 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) {
+ error("unable to open %s: %s", dstdev, strerror(errno));
+ goto leave_with_error;
+ }
+ strncpy((char *)start_args.start.tgtdev_name, dstdev,
+ BTRFS_DEVICE_PATH_NAME_MAX);
+ ret = btrfs_prepare_device(fddstdev, dstdev, 1, &dstdev_block_count, 0,
+ 0);
+ 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) {
+ error("backgrounding failed: %s", strerror(errno));
+ 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 < 0) {
+ fprintf(stderr,
+ "ERROR: ioctl(DEV_REPLACE_START) failed on \"%s\": %s",
+ path, strerror(errno));
+ 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) {
+ error("ioctl(DEV_REPLACE_START) on '%s' returns error: %s",
+ path,
+ replace_dev_result2string(start_args.result));
+ goto leave_with_error;
+ }
+ }
+ close_file_or_dir(fdmnt, dirstream);
+ return 0;
+
+leave_with_error:
+ if (dstdev)
+ free(dstdev);
+ if (fdmnt != -1)
+ close(fdmnt);
+ if (fddstdev != -1)
+ close(fddstdev);
+ return 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 continuously until the replace",
+ " operation finishes (or is canceled)",
+ NULL
+};
+
+static int cmd_replace_status(int argc, char **argv)
+{
+ int fd;
+ int c;
+ char *path;
+ int once = 0;
+ int ret;
+ DIR *dirstream = NULL;
+
+ while ((c = getopt(argc, argv, "1")) != -1) {
+ switch (c) {
+ case '1':
+ once = 1;
+ break;
+ case '?':
+ default:
+ usage(cmd_replace_status_usage);
+ }
+ }
+
+ if (check_argc_exact(argc - optind, 1))
+ usage(cmd_replace_status_usage);
+
+ path = argv[optind];
+ fd = btrfs_open_dir(path, &dirstream, 1);
+ if (fd < 0)
+ return 1;
+
+ ret = print_replace_status(fd, path, once);
+ close_file_or_dir(fd, dirstream);
+ return !!ret;
+}
+
+static int print_replace_status(int fd, const char *path, int once)
+{
+ struct btrfs_ioctl_dev_replace_args args = {0};
+ struct btrfs_ioctl_dev_replace_status_params *status;
+ int ret;
+ int prevent_loop = 0;
+ int skip_stats;
+ int num_chars;
+ char string1[80];
+ char string2[80];
+ char string3[80];
+
+ 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 < 0) {
+ fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s",
+ path, strerror(errno));
+ 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;
+ }
+
+ if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
+ 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) {
+ case BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED:
+ num_chars =
+ printf("%s done",
+ progress2string(string3,
+ sizeof(string3),
+ status->progress_1000));
+ break;
+ case BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED:
+ prevent_loop = 1;
+ printf("Started on %s, finished on %s",
+ time2string(string1, sizeof(string1),
+ status->time_started),
+ time2string(string2, sizeof(string2),
+ status->time_stopped));
+ break;
+ case BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED:
+ prevent_loop = 1;
+ printf("Started on %s, canceled on %s at %s",
+ time2string(string1, sizeof(string1),
+ status->time_started),
+ time2string(string2, sizeof(string2),
+ status->time_stopped),
+ progress2string(string3, sizeof(string3),
+ status->progress_1000));
+ break;
+ case BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED:
+ prevent_loop = 1;
+ printf("Started on %s, suspended on %s at %s",
+ time2string(string1, sizeof(string1),
+ status->time_started),
+ time2string(string2, sizeof(string2),
+ status->time_stopped),
+ progress2string(string3, sizeof(string3),
+ status->progress_1000));
+ break;
+ case BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED:
+ prevent_loop = 1;
+ skip_stats = 1;
+ printf("Never started");
+ break;
+ default:
+ error("unknown status from ioctl DEV_REPLACE_STATUS on '%s': %llu\n",
+ path, status->replace_state);
+ return -EINVAL;
+ }
+
+ if (!skip_stats)
+ num_chars += printf(
+ ", %llu write errs, %llu uncorr. read errs",
+ (unsigned long long)status->num_write_errors,
+ (unsigned long long)
+ status->num_uncorrectable_read_errors);
+ if (once || prevent_loop) {
+ printf("\n");
+ break;
+ }
+
+ fflush(stdout);
+ sleep(1);
+ while (num_chars > 0) {
+ putchar('\b');
+ num_chars--;
+ }
+ }
+
+ return 0;
+}
+
+static char *
+time2string(char *buf, size_t s, __u64 t)
+{
+ struct tm t_tm;
+ time_t t_time_t;
+
+ t_time_t = (time_t)t;
+ assert((__u64)t_time_t == t);
+ localtime_r(&t_time_t, &t_tm);
+ strftime(buf, s, "%e.%b %T", &t_tm);
+ return buf;
+}
+
+static char *
+progress2string(char *buf, size_t s, int progress_1000)
+{
+ snprintf(buf, s, "%d.%01d%%", progress_1000 / 10, progress_1000 % 10);
+ assert(s > 0);
+ buf[s - 1] = '\0';
+ return buf;
+}
+
+static const char *const cmd_replace_cancel_usage[] = {
+ "btrfs replace cancel <mount_point>",
+ "Cancel a running device replace operation.",
+ NULL
+};
+
+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_replace_cancel_usage);
+ }
+ }
+
+ if (check_argc_exact(argc - optind, 1))
+ usage(cmd_replace_cancel_usage);
+
+ path = argv[optind];
+ 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_file_or_dir(fd, dirstream);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_CANCEL) failed on \"%s\": %s",
+ path, strerror(e));
+ 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, replace_cmd_group_info, {
+ { "start", cmd_replace_start, cmd_replace_start_usage, NULL,
+ 0 },
+ { "status", cmd_replace_status, cmd_replace_status_usage, NULL,
+ 0 },
+ { "cancel", cmd_replace_cancel, cmd_replace_cancel_usage, NULL,
+ 0 },
+ NULL_CMD_STRUCT
+ }
+};
+
+int cmd_replace(int argc, char **argv)
+{
+ return handle_command_group(&replace_cmd_group, argc, argv);
+}