2 * Copyright (C) 2012 STRATO. All rights reserved.
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public
6 * License v2 as published by the Free Software Foundation.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
13 * You should have received a copy of the GNU General Public
14 * License along with this program; if not, write to the
15 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
16 * Boston, MA 021110-1307, USA.
24 #include <sys/ioctl.h>
32 #include "kerncompat.h"
42 static int print_replace_status(int fd, const char *path, int once);
43 static char *time2string(char *buf, size_t s, __u64 t);
44 static char *progress2string(char *buf, size_t s, int progress_1000);
47 static const char *replace_dev_result2string(__u64 result)
50 case BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR:
52 case BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED:
54 case BTRFS_IOCTL_DEV_REPLACE_RESULT_ALREADY_STARTED:
55 return "already started";
56 case BTRFS_IOCTL_DEV_REPLACE_RESULT_SCRUB_INPROGRESS:
57 return "scrub is in progress";
59 return "<illegal result value>";
63 static const char * const replace_cmd_group_usage[] = {
64 "btrfs replace <command> [<args>]",
68 static int is_numerical(const char *str)
70 if (!(*str >= '0' && *str <= '9'))
72 while (*str >= '0' && *str <= '9')
79 static int dev_replace_cancel_fd = -1;
80 static void dev_replace_sigint_handler(int signal)
83 struct btrfs_ioctl_dev_replace_args args = {0};
85 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
86 ret = ioctl(dev_replace_cancel_fd, BTRFS_IOC_DEV_REPLACE, &args);
88 perror("Device replace cancel failed");
91 static int dev_replace_handle_sigint(int fd)
93 struct sigaction sa = {
94 .sa_handler = fd == -1 ? SIG_DFL : dev_replace_sigint_handler
97 dev_replace_cancel_fd = fd;
98 return sigaction(SIGINT, &sa, NULL);
101 static const char *const cmd_replace_start_usage[] = {
102 "btrfs replace start [-Bfr] <srcdev>|<devid> <targetdev> <mount_point>",
103 "Replace device of a btrfs filesystem.",
104 "On a live filesystem, duplicate the data to the target device which",
105 "is currently stored on the source device. If the source device is not",
106 "available anymore, or if the -r option is set, the data is built",
107 "only using the RAID redundancy mechanisms. After completion of the",
108 "operation, the source device is removed from the filesystem.",
109 "If the <srcdev> is a numerical value, it is assumed to be the device id",
110 "of the filesystem which is mounted at <mount_point>, otherwise it is",
111 "the path to the source device. If the source device is disconnected,",
112 "from the system, you have to use the <devid> parameter format.",
113 "The <targetdev> needs to be same size or larger than the <srcdev>.",
115 "-r only read from <srcdev> if no other zero-defect mirror exists",
116 " (enable this if your drive has lots of read errors, the access",
117 " would be very slow)",
118 "-f force using and overwriting <targetdev> even if it looks like",
119 " containing a valid btrfs filesystem. A valid filesystem is",
120 " assumed if a btrfs superblock is found which contains a",
121 " correct checksum. Devices which are currently mounted are",
122 " never allowed to be used as the <targetdev>",
123 "-B do not background",
127 static int cmd_replace_start(int argc, char **argv)
129 struct btrfs_ioctl_dev_replace_args start_args = {0};
130 struct btrfs_ioctl_dev_replace_args status_args = {0};
139 int avoid_reading_from_srcdev = 0;
140 int force_using_targetdev = 0;
141 u64 dstdev_block_count;
142 int do_not_background = 0;
144 DIR *dirstream = NULL;
148 while ((c = getopt(argc, argv, "Brf")) != -1) {
151 do_not_background = 1;
154 avoid_reading_from_srcdev = 1;
157 force_using_targetdev = 1;
161 usage(cmd_replace_start_usage);
165 start_args.start.cont_reading_from_srcdev_mode =
166 avoid_reading_from_srcdev ?
167 BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_AVOID :
168 BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_ALWAYS;
169 if (check_argc_exact(argc - optind, 3))
170 usage(cmd_replace_start_usage);
171 path = argv[optind + 2];
173 fdmnt = open_path_or_dev_mnt(path, &dirstream, 1);
175 goto leave_with_error;
177 /* check for possible errors before backgrounding */
178 status_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
179 status_args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
180 ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &status_args);
183 "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s",
184 path, strerror(errno));
185 if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
186 fprintf(stderr, ", %s\n",
187 replace_dev_result2string(status_args.result));
189 fprintf(stderr, "\n");
190 goto leave_with_error;
193 if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
195 "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
196 path, replace_dev_result2string(status_args.result));
197 goto leave_with_error;
200 if (status_args.status.replace_state ==
201 BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED) {
203 "ERROR: btrfs replace on \"%s\" already started!\n",
205 goto leave_with_error;
208 srcdev = argv[optind];
209 dstdev = canonicalize_path(argv[optind + 1]);
212 "ERROR: Could not canonicalize path '%s': %s\n",
213 argv[optind + 1], strerror(errno));
214 goto leave_with_error;
217 if (is_numerical(srcdev)) {
218 struct btrfs_ioctl_fs_info_args fi_args;
219 struct btrfs_ioctl_dev_info_args *di_args = NULL;
221 start_args.start.srcdevid = arg_strtou64(srcdev);
223 ret = get_fs_info(path, &fi_args, &di_args);
225 fprintf(stderr, "ERROR: getting dev info for devstats failed: "
226 "%s\n", strerror(-ret));
228 goto leave_with_error;
230 if (!fi_args.num_devices) {
231 fprintf(stderr, "ERROR: no devices found\n");
233 goto leave_with_error;
236 for (i = 0; i < fi_args.num_devices; i++)
237 if (start_args.start.srcdevid == di_args[i].devid)
239 srcdev_size = di_args[i].total_bytes;
241 if (i == fi_args.num_devices) {
242 fprintf(stderr, "Error: '%s' is not a valid devid for filesystem '%s'\n",
244 goto leave_with_error;
246 } else if (is_block_device(srcdev) > 0) {
247 strncpy((char *)start_args.start.srcdev_name, srcdev,
248 BTRFS_DEVICE_PATH_NAME_MAX);
249 start_args.start.srcdevid = 0;
250 srcdev_size = get_partition_size(srcdev);
252 fprintf(stderr, "ERROR: source device must be a block device or a devid\n");
253 goto leave_with_error;
256 ret = test_dev_for_mkfs(dstdev, force_using_targetdev);
258 goto leave_with_error;
260 dstdev_size = get_partition_size(dstdev);
261 if (srcdev_size > dstdev_size) {
262 fprintf(stderr, "ERROR: target device smaller than source device (required %llu bytes)\n",
264 goto leave_with_error;
267 fddstdev = open(dstdev, O_RDWR);
269 fprintf(stderr, "Unable to open %s\n", dstdev);
270 goto leave_with_error;
272 strncpy((char *)start_args.start.tgtdev_name, dstdev,
273 BTRFS_DEVICE_PATH_NAME_MAX);
274 ret = btrfs_prepare_device(fddstdev, dstdev, 1, &dstdev_block_count, 0,
277 goto leave_with_error;
284 dev_replace_handle_sigint(fdmnt);
285 if (!do_not_background) {
286 if (daemon(0, 0) < 0) {
287 fprintf(stderr, "ERROR, backgrounding failed: %s\n",
289 goto leave_with_error;
293 start_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_START;
294 start_args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
295 ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &start_args);
296 if (do_not_background) {
299 "ERROR: ioctl(DEV_REPLACE_START) failed on \"%s\": %s",
300 path, strerror(errno));
301 if (start_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
302 fprintf(stderr, ", %s\n",
303 replace_dev_result2string(start_args.result));
305 fprintf(stderr, "\n");
307 if (errno == EOPNOTSUPP)
309 "WARNING: dev_replace does not yet handle RAID5/6\n");
311 goto leave_with_error;
314 if (start_args.result !=
315 BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
317 "ERROR: ioctl(DEV_REPLACE_START) on \"%s\" returns error: %s\n",
319 replace_dev_result2string(start_args.result));
320 goto leave_with_error;
323 close_file_or_dir(fdmnt, dirstream);
324 btrfs_close_all_devices();
334 btrfs_close_all_devices();
338 static const char *const cmd_replace_status_usage[] = {
339 "btrfs replace status [-1] <mount_point>",
340 "Print status and progress information of a running device replace",
343 "-1 print once instead of print continuously until the replace",
344 " operation finishes (or is canceled)",
348 static int cmd_replace_status(int argc, char **argv)
356 DIR *dirstream = NULL;
358 while ((c = getopt(argc, argv, "1")) != -1) {
365 usage(cmd_replace_status_usage);
369 if (check_argc_exact(argc - optind, 1))
370 usage(cmd_replace_status_usage);
373 fd = open_file_or_dir(path, &dirstream);
376 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
381 ret = print_replace_status(fd, path, once);
382 close_file_or_dir(fd, dirstream);
386 static int print_replace_status(int fd, const char *path, int once)
388 struct btrfs_ioctl_dev_replace_args args = {0};
389 struct btrfs_ioctl_dev_replace_status_params *status;
391 int prevent_loop = 0;
399 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
400 args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
401 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
403 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s",
404 path, strerror(errno));
405 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
406 fprintf(stderr, ", %s\n",
407 replace_dev_result2string(args.result));
409 fprintf(stderr, "\n");
413 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
414 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
416 replace_dev_result2string(args.result));
420 status = &args.status;
424 switch (status->replace_state) {
425 case BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED:
428 progress2string(string3,
430 status->progress_1000));
432 case BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED:
434 printf("Started on %s, finished on %s",
435 time2string(string1, sizeof(string1),
436 status->time_started),
437 time2string(string2, sizeof(string2),
438 status->time_stopped));
440 case BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED:
442 printf("Started on %s, canceled on %s at %s",
443 time2string(string1, sizeof(string1),
444 status->time_started),
445 time2string(string2, sizeof(string2),
446 status->time_stopped),
447 progress2string(string3, sizeof(string3),
448 status->progress_1000));
450 case BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED:
452 printf("Started on %s, suspended on %s at %s",
453 time2string(string1, sizeof(string1),
454 status->time_started),
455 time2string(string2, sizeof(string2),
456 status->time_stopped),
457 progress2string(string3, sizeof(string3),
458 status->progress_1000));
460 case BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED:
463 printf("Never started");
467 "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" got unknown status: %llu\n",
468 path, status->replace_state);
474 ", %llu write errs, %llu uncorr. read errs",
475 (unsigned long long)status->num_write_errors,
477 status->num_uncorrectable_read_errors);
478 if (once || prevent_loop) {
485 while (num_chars > 0) {
495 time2string(char *buf, size_t s, __u64 t)
500 t_time_t = (time_t)t;
501 assert((__u64)t_time_t == t);
502 localtime_r(&t_time_t, &t_tm);
503 strftime(buf, s, "%e.%b %T", &t_tm);
508 progress2string(char *buf, size_t s, int progress_1000)
510 snprintf(buf, s, "%d.%01d%%", progress_1000 / 10, progress_1000 % 10);
516 static const char *const cmd_replace_cancel_usage[] = {
517 "btrfs replace cancel <mount_point>",
518 "Cancel a running device replace operation.",
522 static int cmd_replace_cancel(int argc, char **argv)
524 struct btrfs_ioctl_dev_replace_args args = {0};
530 DIR *dirstream = NULL;
532 while ((c = getopt(argc, argv, "")) != -1) {
536 usage(cmd_replace_cancel_usage);
540 if (check_argc_exact(argc - optind, 1))
541 usage(cmd_replace_cancel_usage);
544 fd = open_file_or_dir(path, &dirstream);
546 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
547 path, strerror(errno));
551 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
552 args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
553 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
555 close_file_or_dir(fd, dirstream);
557 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_CANCEL) failed on \"%s\": %s",
559 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
560 fprintf(stderr, ", %s\n",
561 replace_dev_result2string(args.result));
563 fprintf(stderr, "\n");
566 if (args.result == BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED) {
567 printf("INFO: ioctl(DEV_REPLACE_CANCEL)\"%s\": %s\n",
568 path, replace_dev_result2string(args.result));
574 static const char replace_cmd_group_info[] =
575 "replace a device in the filesystem";
577 const struct cmd_group replace_cmd_group = {
578 replace_cmd_group_usage, replace_cmd_group_info, {
579 { "start", cmd_replace_start, cmd_replace_start_usage, NULL,
581 { "status", cmd_replace_status, cmd_replace_status_usage, NULL,
583 { "cancel", cmd_replace_cancel, cmd_replace_cancel_usage, NULL,
589 int cmd_replace(int argc, char **argv)
591 return handle_command_group(&replace_cmd_group, argc, argv);