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 dev_replace_cancel_fd = -1;
69 static void dev_replace_sigint_handler(int signal)
72 struct btrfs_ioctl_dev_replace_args args = {0};
74 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
75 ret = ioctl(dev_replace_cancel_fd, BTRFS_IOC_DEV_REPLACE, &args);
77 perror("Device replace cancel failed");
80 static int dev_replace_handle_sigint(int fd)
82 struct sigaction sa = {
83 .sa_handler = fd == -1 ? SIG_DFL : dev_replace_sigint_handler
86 dev_replace_cancel_fd = fd;
87 return sigaction(SIGINT, &sa, NULL);
90 static const char *const cmd_replace_start_usage[] = {
91 "btrfs replace start [-Bfr] <srcdev>|<devid> <targetdev> <mount_point>",
92 "Replace device of a btrfs filesystem.",
93 "On a live filesystem, duplicate the data to the target device which",
94 "is currently stored on the source device. If the source device is not",
95 "available anymore, or if the -r option is set, the data is built",
96 "only using the RAID redundancy mechanisms. After completion of the",
97 "operation, the source device is removed from the filesystem.",
98 "If the <srcdev> is a numerical value, it is assumed to be the device id",
99 "of the filesystem which is mounted at <mount_point>, otherwise it is",
100 "the path to the source device. If the source device is disconnected,",
101 "from the system, you have to use the <devid> parameter format.",
102 "The <targetdev> needs to be same size or larger than the <srcdev>.",
104 "-r only read from <srcdev> if no other zero-defect mirror exists",
105 " (enable this if your drive has lots of read errors, the access",
106 " would be very slow)",
107 "-f force using and overwriting <targetdev> even if it looks like",
108 " containing a valid btrfs filesystem. A valid filesystem is",
109 " assumed if a btrfs superblock is found which contains a",
110 " correct checksum. Devices which are currently mounted are",
111 " never allowed to be used as the <targetdev>",
112 "-B do not background",
116 static int cmd_replace_start(int argc, char **argv)
118 struct btrfs_ioctl_dev_replace_args start_args = {0};
119 struct btrfs_ioctl_dev_replace_args status_args = {0};
128 int avoid_reading_from_srcdev = 0;
129 int force_using_targetdev = 0;
130 u64 dstdev_block_count;
131 int do_not_background = 0;
132 DIR *dirstream = NULL;
136 while ((c = getopt(argc, argv, "Brf")) != -1) {
139 do_not_background = 1;
142 avoid_reading_from_srcdev = 1;
145 force_using_targetdev = 1;
149 usage(cmd_replace_start_usage);
153 start_args.start.cont_reading_from_srcdev_mode =
154 avoid_reading_from_srcdev ?
155 BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_AVOID :
156 BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_ALWAYS;
157 if (check_argc_exact(argc - optind, 3))
158 usage(cmd_replace_start_usage);
159 path = argv[optind + 2];
161 fdmnt = open_path_or_dev_mnt(path, &dirstream, 1);
163 goto leave_with_error;
165 /* check for possible errors before backgrounding */
166 status_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
167 status_args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
168 ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &status_args);
171 "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s",
172 path, strerror(errno));
173 if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
174 fprintf(stderr, ", %s\n",
175 replace_dev_result2string(status_args.result));
177 fprintf(stderr, "\n");
178 goto leave_with_error;
181 if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
183 "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
184 path, replace_dev_result2string(status_args.result));
185 goto leave_with_error;
188 if (status_args.status.replace_state ==
189 BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED) {
191 "ERROR: btrfs replace on \"%s\" already started!\n",
193 goto leave_with_error;
196 srcdev = argv[optind];
197 dstdev = canonicalize_path(argv[optind + 1]);
200 "ERROR: Could not canonicalize path '%s': %s\n",
201 argv[optind + 1], strerror(errno));
202 goto leave_with_error;
205 if (string_is_numerical(srcdev)) {
206 struct btrfs_ioctl_fs_info_args fi_args;
207 struct btrfs_ioctl_dev_info_args *di_args = NULL;
209 start_args.start.srcdevid = arg_strtou64(srcdev);
211 ret = get_fs_info(path, &fi_args, &di_args);
213 fprintf(stderr, "ERROR: getting dev info for devstats failed: "
214 "%s\n", strerror(-ret));
216 goto leave_with_error;
218 if (!fi_args.num_devices) {
219 fprintf(stderr, "ERROR: no devices found\n");
221 goto leave_with_error;
224 for (i = 0; i < fi_args.num_devices; i++)
225 if (start_args.start.srcdevid == di_args[i].devid)
227 srcdev_size = di_args[i].total_bytes;
229 if (i == fi_args.num_devices) {
230 fprintf(stderr, "Error: '%s' is not a valid devid for filesystem '%s'\n",
232 goto leave_with_error;
234 } else if (is_block_device(srcdev) > 0) {
235 strncpy((char *)start_args.start.srcdev_name, srcdev,
236 BTRFS_DEVICE_PATH_NAME_MAX);
237 start_args.start.srcdevid = 0;
238 srcdev_size = get_partition_size(srcdev);
240 fprintf(stderr, "ERROR: source device must be a block device or a devid\n");
241 goto leave_with_error;
244 ret = test_dev_for_mkfs(dstdev, force_using_targetdev);
246 goto leave_with_error;
248 dstdev_size = get_partition_size(dstdev);
249 if (srcdev_size > dstdev_size) {
250 fprintf(stderr, "ERROR: target device smaller than source device (required %llu bytes)\n",
252 goto leave_with_error;
255 fddstdev = open(dstdev, O_RDWR);
257 fprintf(stderr, "Unable to open %s\n", dstdev);
258 goto leave_with_error;
260 strncpy((char *)start_args.start.tgtdev_name, dstdev,
261 BTRFS_DEVICE_PATH_NAME_MAX);
262 ret = btrfs_prepare_device(fddstdev, dstdev, 1, &dstdev_block_count, 0,
265 goto leave_with_error;
272 dev_replace_handle_sigint(fdmnt);
273 if (!do_not_background) {
274 if (daemon(0, 0) < 0) {
275 fprintf(stderr, "ERROR, backgrounding failed: %s\n",
277 goto leave_with_error;
281 start_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_START;
282 start_args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
283 ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &start_args);
284 if (do_not_background) {
287 "ERROR: ioctl(DEV_REPLACE_START) failed on \"%s\": %s",
288 path, strerror(errno));
289 if (start_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
290 fprintf(stderr, ", %s\n",
291 replace_dev_result2string(start_args.result));
293 fprintf(stderr, "\n");
295 if (errno == EOPNOTSUPP)
297 "WARNING: dev_replace does not yet handle RAID5/6\n");
299 goto leave_with_error;
302 if (start_args.result !=
303 BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
305 "ERROR: ioctl(DEV_REPLACE_START) on \"%s\" returns error: %s\n",
307 replace_dev_result2string(start_args.result));
308 goto leave_with_error;
311 close_file_or_dir(fdmnt, dirstream);
324 static const char *const cmd_replace_status_usage[] = {
325 "btrfs replace status [-1] <mount_point>",
326 "Print status and progress information of a running device replace",
329 "-1 print once instead of print continuously until the replace",
330 " operation finishes (or is canceled)",
334 static int cmd_replace_status(int argc, char **argv)
341 DIR *dirstream = NULL;
343 while ((c = getopt(argc, argv, "1")) != -1) {
350 usage(cmd_replace_status_usage);
354 if (check_argc_exact(argc - optind, 1))
355 usage(cmd_replace_status_usage);
358 fd = btrfs_open_dir(path, &dirstream, 1);
362 ret = print_replace_status(fd, path, once);
363 close_file_or_dir(fd, dirstream);
367 static int print_replace_status(int fd, const char *path, int once)
369 struct btrfs_ioctl_dev_replace_args args = {0};
370 struct btrfs_ioctl_dev_replace_status_params *status;
372 int prevent_loop = 0;
380 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
381 args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
382 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
384 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s",
385 path, strerror(errno));
386 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
387 fprintf(stderr, ", %s\n",
388 replace_dev_result2string(args.result));
390 fprintf(stderr, "\n");
394 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
395 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
397 replace_dev_result2string(args.result));
401 status = &args.status;
405 switch (status->replace_state) {
406 case BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED:
409 progress2string(string3,
411 status->progress_1000));
413 case BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED:
415 printf("Started on %s, finished on %s",
416 time2string(string1, sizeof(string1),
417 status->time_started),
418 time2string(string2, sizeof(string2),
419 status->time_stopped));
421 case BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED:
423 printf("Started on %s, canceled on %s at %s",
424 time2string(string1, sizeof(string1),
425 status->time_started),
426 time2string(string2, sizeof(string2),
427 status->time_stopped),
428 progress2string(string3, sizeof(string3),
429 status->progress_1000));
431 case BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED:
433 printf("Started on %s, suspended on %s at %s",
434 time2string(string1, sizeof(string1),
435 status->time_started),
436 time2string(string2, sizeof(string2),
437 status->time_stopped),
438 progress2string(string3, sizeof(string3),
439 status->progress_1000));
441 case BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED:
444 printf("Never started");
448 "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" got unknown status: %llu\n",
449 path, status->replace_state);
455 ", %llu write errs, %llu uncorr. read errs",
456 (unsigned long long)status->num_write_errors,
458 status->num_uncorrectable_read_errors);
459 if (once || prevent_loop) {
466 while (num_chars > 0) {
476 time2string(char *buf, size_t s, __u64 t)
481 t_time_t = (time_t)t;
482 assert((__u64)t_time_t == t);
483 localtime_r(&t_time_t, &t_tm);
484 strftime(buf, s, "%e.%b %T", &t_tm);
489 progress2string(char *buf, size_t s, int progress_1000)
491 snprintf(buf, s, "%d.%01d%%", progress_1000 / 10, progress_1000 % 10);
497 static const char *const cmd_replace_cancel_usage[] = {
498 "btrfs replace cancel <mount_point>",
499 "Cancel a running device replace operation.",
503 static int cmd_replace_cancel(int argc, char **argv)
505 struct btrfs_ioctl_dev_replace_args args = {0};
511 DIR *dirstream = NULL;
513 while ((c = getopt(argc, argv, "")) != -1) {
517 usage(cmd_replace_cancel_usage);
521 if (check_argc_exact(argc - optind, 1))
522 usage(cmd_replace_cancel_usage);
525 fd = btrfs_open_dir(path, &dirstream, 1);
529 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
530 args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
531 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
533 close_file_or_dir(fd, dirstream);
535 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_CANCEL) failed on \"%s\": %s",
537 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
538 fprintf(stderr, ", %s\n",
539 replace_dev_result2string(args.result));
541 fprintf(stderr, "\n");
544 if (args.result == BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED) {
545 printf("INFO: ioctl(DEV_REPLACE_CANCEL)\"%s\": %s\n",
546 path, replace_dev_result2string(args.result));
552 static const char replace_cmd_group_info[] =
553 "replace a device in the filesystem";
555 const struct cmd_group replace_cmd_group = {
556 replace_cmd_group_usage, replace_cmd_group_info, {
557 { "start", cmd_replace_start, cmd_replace_start_usage, NULL,
559 { "status", cmd_replace_status, cmd_replace_status_usage, NULL,
561 { "cancel", cmd_replace_cancel, cmd_replace_cancel_usage, NULL,
567 int cmd_replace(int argc, char **argv)
569 return handle_command_group(&replace_cmd_group, argc, argv);