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";
57 return "<illegal result value>";
61 static const char * const replace_cmd_group_usage[] = {
62 "btrfs replace <command> [<args>]",
66 static int is_numerical(const char *str)
68 if (!(*str >= '0' && *str <= '9'))
70 while (*str >= '0' && *str <= '9')
77 static int dev_replace_cancel_fd = -1;
78 static void dev_replace_sigint_handler(int signal)
81 struct btrfs_ioctl_dev_replace_args args = {0};
83 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
84 ret = ioctl(dev_replace_cancel_fd, BTRFS_IOC_DEV_REPLACE, &args);
86 perror("Device replace cancel failed");
89 static int dev_replace_handle_sigint(int fd)
91 struct sigaction sa = {
92 .sa_handler = fd == -1 ? SIG_DFL : dev_replace_sigint_handler
95 dev_replace_cancel_fd = fd;
96 return sigaction(SIGINT, &sa, NULL);
99 static const char *const cmd_start_replace_usage[] = {
100 "btrfs replace start [-Bfr] <srcdev>|<devid> <targetdev> <mount_point>",
101 "Replace device of a btrfs filesystem.",
102 "On a live filesystem, duplicate the data to the target device which",
103 "is currently stored on the source device. If the source device is not",
104 "available anymore, or if the -r option is set, the data is built",
105 "only using the RAID redundancy mechanisms. After completion of the",
106 "operation, the source device is removed from the filesystem.",
107 "If the <srcdev> is a numerical value, it is assumed to be the device id",
108 "of the filesystem which is mounted at <mount_point>, otherwise it is",
109 "the path to the source device. If the source device is disconnected,",
110 "from the system, you have to use the <devid> parameter format.",
111 "The <targetdev> needs to be same size or larger than the <srcdev>.",
113 "-r only read from <srcdev> if no other zero-defect mirror exists",
114 " (enable this if your drive has lots of read errors, the access",
115 " would be very slow)",
116 "-f force using and overwriting <targetdev> even if it looks like",
117 " containing a valid btrfs filesystem. A valid filesystem is",
118 " assumed if a btrfs superblock is found which contains a",
119 " correct checksum. Devices which are currently mounted are",
120 " never allowed to be used as the <targetdev>",
121 "-B do not background",
125 static int cmd_start_replace(int argc, char **argv)
127 struct btrfs_ioctl_dev_replace_args start_args = {0};
128 struct btrfs_ioctl_dev_replace_args status_args = {0};
138 int avoid_reading_from_srcdev = 0;
139 int force_using_targetdev = 0;
141 u64 dstdev_block_count;
142 int do_not_background = 0;
144 DIR *dirstream = NULL;
145 char estr[100]; /* check test_dev_for_mkfs() for error string size*/
147 while ((c = getopt(argc, argv, "Brf")) != -1) {
150 do_not_background = 1;
153 avoid_reading_from_srcdev = 1;
156 force_using_targetdev = 1;
160 usage(cmd_start_replace_usage);
164 start_args.start.cont_reading_from_srcdev_mode =
165 avoid_reading_from_srcdev ?
166 BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_AVOID :
167 BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_ALWAYS;
168 if (check_argc_exact(argc - optind, 3))
169 usage(cmd_start_replace_usage);
170 path = argv[optind + 2];
172 fdmnt = open_path_or_dev_mnt(path, &dirstream);
177 "ERROR: '%s' is not a mounted btrfs device\n",
180 fprintf(stderr, "ERROR: can't access '%s': %s\n",
181 path, strerror(errno));
182 goto leave_with_error;
185 /* check for possible errors before backgrounding */
186 status_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
187 ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &status_args);
190 "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s, %s\n",
191 path, strerror(errno),
192 replace_dev_result2string(status_args.result));
193 goto leave_with_error;
196 if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
198 "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
199 path, replace_dev_result2string(status_args.result));
200 goto leave_with_error;
203 if (status_args.status.replace_state ==
204 BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED) {
206 "ERROR: btrfs replace on \"%s\" already started!\n",
208 goto leave_with_error;
211 srcdev = argv[optind];
212 dstdev = canonicalize_path(argv[optind + 1]);
215 "ERROR: Could not canonicalize path '%s': %s\n",
216 argv[optind + 1], strerror(errno));
219 if (is_numerical(srcdev)) {
220 struct btrfs_ioctl_fs_info_args fi_args;
221 struct btrfs_ioctl_dev_info_args *di_args = NULL;
223 start_args.start.srcdevid = arg_strtou64(srcdev);
225 ret = get_fs_info(path, &fi_args, &di_args);
227 fprintf(stderr, "ERROR: getting dev info for devstats failed: "
228 "%s\n", strerror(-ret));
230 goto leave_with_error;
232 if (!fi_args.num_devices) {
233 fprintf(stderr, "ERROR: no devices found\n");
235 goto leave_with_error;
238 for (i = 0; i < fi_args.num_devices; i++)
239 if (start_args.start.srcdevid == di_args[i].devid)
242 if (i == fi_args.num_devices) {
243 fprintf(stderr, "Error: '%s' is not a valid devid for filesystem '%s'\n",
245 goto leave_with_error;
248 fdsrcdev = open(srcdev, O_RDWR);
250 fprintf(stderr, "Error: Unable to open device '%s'\n",
252 fprintf(stderr, "\tTry using the devid instead of the path\n");
253 goto leave_with_error;
255 ret = fstat(fdsrcdev, &st);
257 fprintf(stderr, "Error: Unable to stat '%s'\n", srcdev);
258 goto leave_with_error;
260 if (!S_ISBLK(st.st_mode)) {
261 fprintf(stderr, "Error: '%s' is not a block device\n",
263 goto leave_with_error;
265 strncpy((char *)start_args.start.srcdev_name, srcdev,
266 BTRFS_DEVICE_PATH_NAME_MAX);
269 start_args.start.srcdevid = 0;
272 ret = test_dev_for_mkfs(dstdev, force_using_targetdev, estr);
274 fprintf(stderr, "%s", estr);
275 goto leave_with_error;
277 fddstdev = open(dstdev, O_RDWR);
279 fprintf(stderr, "Unable to open %s\n", dstdev);
280 goto leave_with_error;
282 strncpy((char *)start_args.start.tgtdev_name, dstdev,
283 BTRFS_DEVICE_PATH_NAME_MAX);
284 ret = btrfs_prepare_device(fddstdev, dstdev, 1, &dstdev_block_count, 0,
287 goto leave_with_error;
294 dev_replace_handle_sigint(fdmnt);
295 if (!do_not_background) {
296 if (daemon(0, 0) < 0) {
297 fprintf(stderr, "ERROR, backgrounding failed: %s\n",
299 goto leave_with_error;
303 start_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_START;
304 ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &start_args);
305 if (do_not_background) {
308 "ERROR: ioctl(DEV_REPLACE_START) failed on \"%s\": %s, %s\n",
309 path, strerror(errno),
310 replace_dev_result2string(start_args.result));
312 if (errno == EOPNOTSUPP)
314 "WARNING: dev_replace does not yet handle RAID5/6\n");
316 goto leave_with_error;
319 if (start_args.result !=
320 BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
322 "ERROR: ioctl(DEV_REPLACE_START) on \"%s\" returns error: %s\n",
324 replace_dev_result2string(start_args.result));
325 goto leave_with_error;
328 close_file_or_dir(fdmnt, dirstream);
343 static const char *const cmd_status_replace_usage[] = {
344 "btrfs replace status [-1] <mount_point>",
345 "Print status and progress information of a running device replace",
348 "-1 print once instead of print continuously until the replace",
349 " operation finishes (or is canceled)",
353 static int cmd_status_replace(int argc, char **argv)
361 DIR *dirstream = NULL;
363 while ((c = getopt(argc, argv, "1")) != -1) {
370 usage(cmd_status_replace_usage);
374 if (check_argc_exact(argc - optind, 1))
375 usage(cmd_status_replace_usage);
378 fd = open_file_or_dir(path, &dirstream);
381 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
386 ret = print_replace_status(fd, path, once);
387 close_file_or_dir(fd, dirstream);
391 static int print_replace_status(int fd, const char *path, int once)
393 struct btrfs_ioctl_dev_replace_args args = {0};
394 struct btrfs_ioctl_dev_replace_status_params *status;
396 int prevent_loop = 0;
404 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
405 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
407 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s, %s\n",
408 path, strerror(errno),
409 replace_dev_result2string(args.result));
413 status = &args.status;
414 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
415 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
417 replace_dev_result2string(args.result));
423 switch (status->replace_state) {
424 case BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED:
427 progress2string(string3,
429 status->progress_1000));
431 case BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED:
433 printf("Started on %s, finished on %s",
434 time2string(string1, sizeof(string1),
435 status->time_started),
436 time2string(string2, sizeof(string2),
437 status->time_stopped));
439 case BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED:
441 printf("Started on %s, canceled on %s at %s",
442 time2string(string1, sizeof(string1),
443 status->time_started),
444 time2string(string2, sizeof(string2),
445 status->time_stopped),
446 progress2string(string3, sizeof(string3),
447 status->progress_1000));
449 case BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED:
451 printf("Started on %s, suspended on %s at %s",
452 time2string(string1, sizeof(string1),
453 status->time_started),
454 time2string(string2, sizeof(string2),
455 status->time_stopped),
456 progress2string(string3, sizeof(string3),
457 status->progress_1000));
459 case BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED:
462 printf("Never started");
467 "Unknown btrfs dev replace status:%llu",
468 status->replace_state);
475 ", %llu write errs, %llu uncorr. read errs",
476 (unsigned long long)status->num_write_errors,
478 status->num_uncorrectable_read_errors);
479 if (once || prevent_loop || ret) {
486 while (num_chars > 0) {
496 time2string(char *buf, size_t s, __u64 t)
501 t_time_t = (time_t)t;
502 assert((__u64)t_time_t == t);
503 localtime_r(&t_time_t, &t_tm);
504 strftime(buf, s, "%e.%b %T", &t_tm);
509 progress2string(char *buf, size_t s, int progress_1000)
511 snprintf(buf, s, "%d.%01d%%", progress_1000 / 10, progress_1000 % 10);
517 static const char *const cmd_cancel_replace_usage[] = {
518 "btrfs replace cancel <mount_point>",
519 "Cancel a running device replace operation.",
523 static int cmd_cancel_replace(int argc, char **argv)
525 struct btrfs_ioctl_dev_replace_args args = {0};
531 DIR *dirstream = NULL;
533 while ((c = getopt(argc, argv, "")) != -1) {
537 usage(cmd_cancel_replace_usage);
541 if (check_argc_exact(argc - optind, 1))
542 usage(cmd_cancel_replace_usage);
545 fd = open_file_or_dir(path, &dirstream);
547 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
548 path, strerror(errno));
552 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
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, %s\n",
559 replace_dev_result2string(args.result));
562 if (args.result == BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED) {
563 printf("INFO: ioctl(DEV_REPLACE_CANCEL)\"%s\": %s\n",
564 path, replace_dev_result2string(args.result));
570 const struct cmd_group replace_cmd_group = {
571 replace_cmd_group_usage, NULL, {
572 { "start", cmd_start_replace, cmd_start_replace_usage, NULL,
574 { "status", cmd_status_replace, cmd_status_replace_usage, NULL,
576 { "cancel", cmd_cancel_replace, cmd_cancel_replace_usage, NULL,
582 int cmd_replace(int argc, char **argv)
584 return handle_command_group(&replace_cmd_group, argc, argv);