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_start_replace_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_start_replace(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;
146 while ((c = getopt(argc, argv, "Brf")) != -1) {
149 do_not_background = 1;
152 avoid_reading_from_srcdev = 1;
155 force_using_targetdev = 1;
159 usage(cmd_start_replace_usage);
163 start_args.start.cont_reading_from_srcdev_mode =
164 avoid_reading_from_srcdev ?
165 BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_AVOID :
166 BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_ALWAYS;
167 if (check_argc_exact(argc - optind, 3))
168 usage(cmd_start_replace_usage);
169 path = argv[optind + 2];
171 fdmnt = open_path_or_dev_mnt(path, &dirstream);
176 "ERROR: '%s' is not a mounted btrfs device\n",
179 fprintf(stderr, "ERROR: can't access '%s': %s\n",
180 path, strerror(errno));
181 goto leave_with_error;
184 /* check for possible errors before backgrounding */
185 status_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
186 status_args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
187 ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &status_args);
190 "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s",
191 path, strerror(errno));
192 if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
193 fprintf(stderr, ", %s\n",
194 replace_dev_result2string(status_args.result));
196 fprintf(stderr, "\n");
197 goto leave_with_error;
200 if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
202 "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
203 path, replace_dev_result2string(status_args.result));
204 goto leave_with_error;
207 if (status_args.status.replace_state ==
208 BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED) {
210 "ERROR: btrfs replace on \"%s\" already started!\n",
212 goto leave_with_error;
215 srcdev = argv[optind];
216 dstdev = canonicalize_path(argv[optind + 1]);
219 "ERROR: Could not canonicalize path '%s': %s\n",
220 argv[optind + 1], strerror(errno));
221 goto leave_with_error;
224 if (is_numerical(srcdev)) {
225 struct btrfs_ioctl_fs_info_args fi_args;
226 struct btrfs_ioctl_dev_info_args *di_args = NULL;
228 start_args.start.srcdevid = arg_strtou64(srcdev);
230 ret = get_fs_info(path, &fi_args, &di_args);
232 fprintf(stderr, "ERROR: getting dev info for devstats failed: "
233 "%s\n", strerror(-ret));
235 goto leave_with_error;
237 if (!fi_args.num_devices) {
238 fprintf(stderr, "ERROR: no devices found\n");
240 goto leave_with_error;
243 for (i = 0; i < fi_args.num_devices; i++)
244 if (start_args.start.srcdevid == di_args[i].devid)
247 if (i == fi_args.num_devices) {
248 fprintf(stderr, "Error: '%s' is not a valid devid for filesystem '%s'\n",
250 goto leave_with_error;
252 } else if (is_block_device(srcdev)) {
253 strncpy((char *)start_args.start.srcdev_name, srcdev,
254 BTRFS_DEVICE_PATH_NAME_MAX);
255 start_args.start.srcdevid = 0;
258 ret = test_dev_for_mkfs(dstdev, force_using_targetdev);
260 goto leave_with_error;
262 fddstdev = open(dstdev, O_RDWR);
264 fprintf(stderr, "Unable to open %s\n", dstdev);
265 goto leave_with_error;
267 strncpy((char *)start_args.start.tgtdev_name, dstdev,
268 BTRFS_DEVICE_PATH_NAME_MAX);
269 ret = btrfs_prepare_device(fddstdev, dstdev, 1, &dstdev_block_count, 0,
272 goto leave_with_error;
279 dev_replace_handle_sigint(fdmnt);
280 if (!do_not_background) {
281 if (daemon(0, 0) < 0) {
282 fprintf(stderr, "ERROR, backgrounding failed: %s\n",
284 goto leave_with_error;
288 start_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_START;
289 start_args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
290 ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &start_args);
291 if (do_not_background) {
294 "ERROR: ioctl(DEV_REPLACE_START) failed on \"%s\": %s",
295 path, strerror(errno));
296 if (start_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
297 fprintf(stderr, ", %s\n",
298 replace_dev_result2string(start_args.result));
300 fprintf(stderr, "\n");
302 if (errno == EOPNOTSUPP)
304 "WARNING: dev_replace does not yet handle RAID5/6\n");
306 goto leave_with_error;
309 if (start_args.result !=
310 BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
312 "ERROR: ioctl(DEV_REPLACE_START) on \"%s\" returns error: %s\n",
314 replace_dev_result2string(start_args.result));
315 goto leave_with_error;
318 close_file_or_dir(fdmnt, dirstream);
331 static const char *const cmd_status_replace_usage[] = {
332 "btrfs replace status [-1] <mount_point>",
333 "Print status and progress information of a running device replace",
336 "-1 print once instead of print continuously until the replace",
337 " operation finishes (or is canceled)",
341 static int cmd_status_replace(int argc, char **argv)
349 DIR *dirstream = NULL;
351 while ((c = getopt(argc, argv, "1")) != -1) {
358 usage(cmd_status_replace_usage);
362 if (check_argc_exact(argc - optind, 1))
363 usage(cmd_status_replace_usage);
366 fd = open_file_or_dir(path, &dirstream);
369 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
374 ret = print_replace_status(fd, path, once);
375 close_file_or_dir(fd, dirstream);
379 static int print_replace_status(int fd, const char *path, int once)
381 struct btrfs_ioctl_dev_replace_args args = {0};
382 struct btrfs_ioctl_dev_replace_status_params *status;
384 int prevent_loop = 0;
392 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
393 args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
394 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
396 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s",
397 path, strerror(errno));
398 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
399 fprintf(stderr, ", %s\n",
400 replace_dev_result2string(args.result));
402 fprintf(stderr, "\n");
406 status = &args.status;
407 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
408 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
410 replace_dev_result2string(args.result));
416 switch (status->replace_state) {
417 case BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED:
420 progress2string(string3,
422 status->progress_1000));
424 case BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED:
426 printf("Started on %s, finished on %s",
427 time2string(string1, sizeof(string1),
428 status->time_started),
429 time2string(string2, sizeof(string2),
430 status->time_stopped));
432 case BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED:
434 printf("Started on %s, canceled on %s at %s",
435 time2string(string1, sizeof(string1),
436 status->time_started),
437 time2string(string2, sizeof(string2),
438 status->time_stopped),
439 progress2string(string3, sizeof(string3),
440 status->progress_1000));
442 case BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED:
444 printf("Started on %s, suspended on %s at %s",
445 time2string(string1, sizeof(string1),
446 status->time_started),
447 time2string(string2, sizeof(string2),
448 status->time_stopped),
449 progress2string(string3, sizeof(string3),
450 status->progress_1000));
452 case BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED:
455 printf("Never started");
460 "Unknown btrfs dev replace status:%llu",
461 status->replace_state);
468 ", %llu write errs, %llu uncorr. read errs",
469 (unsigned long long)status->num_write_errors,
471 status->num_uncorrectable_read_errors);
472 if (once || prevent_loop || ret) {
479 while (num_chars > 0) {
489 time2string(char *buf, size_t s, __u64 t)
494 t_time_t = (time_t)t;
495 assert((__u64)t_time_t == t);
496 localtime_r(&t_time_t, &t_tm);
497 strftime(buf, s, "%e.%b %T", &t_tm);
502 progress2string(char *buf, size_t s, int progress_1000)
504 snprintf(buf, s, "%d.%01d%%", progress_1000 / 10, progress_1000 % 10);
510 static const char *const cmd_cancel_replace_usage[] = {
511 "btrfs replace cancel <mount_point>",
512 "Cancel a running device replace operation.",
516 static int cmd_cancel_replace(int argc, char **argv)
518 struct btrfs_ioctl_dev_replace_args args = {0};
524 DIR *dirstream = NULL;
526 while ((c = getopt(argc, argv, "")) != -1) {
530 usage(cmd_cancel_replace_usage);
534 if (check_argc_exact(argc - optind, 1))
535 usage(cmd_cancel_replace_usage);
538 fd = open_file_or_dir(path, &dirstream);
540 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
541 path, strerror(errno));
545 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
546 args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
547 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
549 close_file_or_dir(fd, dirstream);
551 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_CANCEL) failed on \"%s\": %s",
553 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
554 fprintf(stderr, ", %s\n",
555 replace_dev_result2string(args.result));
557 fprintf(stderr, "\n");
560 if (args.result == BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED) {
561 printf("INFO: ioctl(DEV_REPLACE_CANCEL)\"%s\": %s\n",
562 path, replace_dev_result2string(args.result));
568 static const char replace_cmd_group_info[] =
569 "replace a device in the filesystem";
571 const struct cmd_group replace_cmd_group = {
572 replace_cmd_group_usage, replace_cmd_group_info, {
573 { "start", cmd_start_replace, cmd_start_replace_usage, NULL,
575 { "status", cmd_status_replace, cmd_status_replace_usage, NULL,
577 { "cancel", cmd_cancel_replace, cmd_cancel_replace_usage, NULL,
583 int cmd_replace(int argc, char **argv)
585 return handle_command_group(&replace_cmd_group, argc, argv);