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);
178 "ERROR: '%s' is not a mounted btrfs device\n",
181 fprintf(stderr, "ERROR: can't access '%s': %s\n",
182 path, strerror(errno));
183 goto leave_with_error;
186 /* check for possible errors before backgrounding */
187 status_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
188 status_args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
189 ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &status_args);
192 "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s",
193 path, strerror(errno));
194 if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
195 fprintf(stderr, ", %s\n",
196 replace_dev_result2string(status_args.result));
198 fprintf(stderr, "\n");
199 goto leave_with_error;
202 if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
204 "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
205 path, replace_dev_result2string(status_args.result));
206 goto leave_with_error;
209 if (status_args.status.replace_state ==
210 BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED) {
212 "ERROR: btrfs replace on \"%s\" already started!\n",
214 goto leave_with_error;
217 srcdev = argv[optind];
218 dstdev = canonicalize_path(argv[optind + 1]);
221 "ERROR: Could not canonicalize path '%s': %s\n",
222 argv[optind + 1], strerror(errno));
223 goto leave_with_error;
226 if (is_numerical(srcdev)) {
227 struct btrfs_ioctl_fs_info_args fi_args;
228 struct btrfs_ioctl_dev_info_args *di_args = NULL;
230 start_args.start.srcdevid = arg_strtou64(srcdev);
232 ret = get_fs_info(path, &fi_args, &di_args);
234 fprintf(stderr, "ERROR: getting dev info for devstats failed: "
235 "%s\n", strerror(-ret));
237 goto leave_with_error;
239 if (!fi_args.num_devices) {
240 fprintf(stderr, "ERROR: no devices found\n");
242 goto leave_with_error;
245 for (i = 0; i < fi_args.num_devices; i++)
246 if (start_args.start.srcdevid == di_args[i].devid)
248 srcdev_size = di_args[i].total_bytes;
250 if (i == fi_args.num_devices) {
251 fprintf(stderr, "Error: '%s' is not a valid devid for filesystem '%s'\n",
253 goto leave_with_error;
255 } else if (is_block_device(srcdev) > 0) {
256 strncpy((char *)start_args.start.srcdev_name, srcdev,
257 BTRFS_DEVICE_PATH_NAME_MAX);
258 start_args.start.srcdevid = 0;
259 srcdev_size = get_partition_size(srcdev);
261 fprintf(stderr, "ERROR: source device must be a block device or a devid\n");
262 goto leave_with_error;
265 ret = test_dev_for_mkfs(dstdev, force_using_targetdev);
267 goto leave_with_error;
269 dstdev_size = get_partition_size(dstdev);
270 if (srcdev_size > dstdev_size) {
271 fprintf(stderr, "ERROR: target device smaller than source device (required %llu bytes)\n",
273 goto leave_with_error;
276 fddstdev = open(dstdev, O_RDWR);
278 fprintf(stderr, "Unable to open %s\n", dstdev);
279 goto leave_with_error;
281 strncpy((char *)start_args.start.tgtdev_name, dstdev,
282 BTRFS_DEVICE_PATH_NAME_MAX);
283 ret = btrfs_prepare_device(fddstdev, dstdev, 1, &dstdev_block_count, 0,
286 goto leave_with_error;
293 dev_replace_handle_sigint(fdmnt);
294 if (!do_not_background) {
295 if (daemon(0, 0) < 0) {
296 fprintf(stderr, "ERROR, backgrounding failed: %s\n",
298 goto leave_with_error;
302 start_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_START;
303 start_args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
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",
309 path, strerror(errno));
310 if (start_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
311 fprintf(stderr, ", %s\n",
312 replace_dev_result2string(start_args.result));
314 fprintf(stderr, "\n");
316 if (errno == EOPNOTSUPP)
318 "WARNING: dev_replace does not yet handle RAID5/6\n");
320 goto leave_with_error;
323 if (start_args.result !=
324 BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
326 "ERROR: ioctl(DEV_REPLACE_START) on \"%s\" returns error: %s\n",
328 replace_dev_result2string(start_args.result));
329 goto leave_with_error;
332 close_file_or_dir(fdmnt, dirstream);
333 btrfs_close_all_devices();
343 btrfs_close_all_devices();
347 static const char *const cmd_replace_status_usage[] = {
348 "btrfs replace status [-1] <mount_point>",
349 "Print status and progress information of a running device replace",
352 "-1 print once instead of print continuously until the replace",
353 " operation finishes (or is canceled)",
357 static int cmd_replace_status(int argc, char **argv)
365 DIR *dirstream = NULL;
367 while ((c = getopt(argc, argv, "1")) != -1) {
374 usage(cmd_replace_status_usage);
378 if (check_argc_exact(argc - optind, 1))
379 usage(cmd_replace_status_usage);
382 fd = open_file_or_dir(path, &dirstream);
385 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
390 ret = print_replace_status(fd, path, once);
391 close_file_or_dir(fd, dirstream);
395 static int print_replace_status(int fd, const char *path, int once)
397 struct btrfs_ioctl_dev_replace_args args = {0};
398 struct btrfs_ioctl_dev_replace_status_params *status;
400 int prevent_loop = 0;
408 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
409 args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
410 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
412 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s",
413 path, strerror(errno));
414 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
415 fprintf(stderr, ", %s\n",
416 replace_dev_result2string(args.result));
418 fprintf(stderr, "\n");
422 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
423 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
425 replace_dev_result2string(args.result));
429 status = &args.status;
433 switch (status->replace_state) {
434 case BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED:
437 progress2string(string3,
439 status->progress_1000));
441 case BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED:
443 printf("Started on %s, finished on %s",
444 time2string(string1, sizeof(string1),
445 status->time_started),
446 time2string(string2, sizeof(string2),
447 status->time_stopped));
449 case BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED:
451 printf("Started on %s, canceled 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_SUSPENDED:
461 printf("Started on %s, suspended on %s at %s",
462 time2string(string1, sizeof(string1),
463 status->time_started),
464 time2string(string2, sizeof(string2),
465 status->time_stopped),
466 progress2string(string3, sizeof(string3),
467 status->progress_1000));
469 case BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED:
472 printf("Never started");
476 "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" got unknown status: %llu\n",
477 path, status->replace_state);
483 ", %llu write errs, %llu uncorr. read errs",
484 (unsigned long long)status->num_write_errors,
486 status->num_uncorrectable_read_errors);
487 if (once || prevent_loop) {
494 while (num_chars > 0) {
504 time2string(char *buf, size_t s, __u64 t)
509 t_time_t = (time_t)t;
510 assert((__u64)t_time_t == t);
511 localtime_r(&t_time_t, &t_tm);
512 strftime(buf, s, "%e.%b %T", &t_tm);
517 progress2string(char *buf, size_t s, int progress_1000)
519 snprintf(buf, s, "%d.%01d%%", progress_1000 / 10, progress_1000 % 10);
525 static const char *const cmd_replace_cancel_usage[] = {
526 "btrfs replace cancel <mount_point>",
527 "Cancel a running device replace operation.",
531 static int cmd_replace_cancel(int argc, char **argv)
533 struct btrfs_ioctl_dev_replace_args args = {0};
539 DIR *dirstream = NULL;
541 while ((c = getopt(argc, argv, "")) != -1) {
545 usage(cmd_replace_cancel_usage);
549 if (check_argc_exact(argc - optind, 1))
550 usage(cmd_replace_cancel_usage);
553 fd = open_file_or_dir(path, &dirstream);
555 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
556 path, strerror(errno));
560 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
561 args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
562 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
564 close_file_or_dir(fd, dirstream);
566 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_CANCEL) failed on \"%s\": %s",
568 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
569 fprintf(stderr, ", %s\n",
570 replace_dev_result2string(args.result));
572 fprintf(stderr, "\n");
575 if (args.result == BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED) {
576 printf("INFO: ioctl(DEV_REPLACE_CANCEL)\"%s\": %s\n",
577 path, replace_dev_result2string(args.result));
583 static const char replace_cmd_group_info[] =
584 "replace a device in the filesystem";
586 const struct cmd_group replace_cmd_group = {
587 replace_cmd_group_usage, replace_cmd_group_info, {
588 { "start", cmd_replace_start, cmd_replace_start_usage, NULL,
590 { "status", cmd_replace_status, cmd_replace_status_usage, NULL,
592 { "cancel", cmd_replace_cancel, cmd_replace_cancel_usage, NULL,
598 int cmd_replace(int argc, char **argv)
600 return handle_command_group(&replace_cmd_group, argc, argv);