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;
133 DIR *dirstream = NULL;
137 while ((c = getopt(argc, argv, "Brf")) != -1) {
140 do_not_background = 1;
143 avoid_reading_from_srcdev = 1;
146 force_using_targetdev = 1;
150 usage(cmd_replace_start_usage);
154 start_args.start.cont_reading_from_srcdev_mode =
155 avoid_reading_from_srcdev ?
156 BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_AVOID :
157 BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_ALWAYS;
158 if (check_argc_exact(argc - optind, 3))
159 usage(cmd_replace_start_usage);
160 path = argv[optind + 2];
162 fdmnt = open_path_or_dev_mnt(path, &dirstream, 1);
164 goto leave_with_error;
166 /* check for possible errors before backgrounding */
167 status_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
168 status_args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
169 ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &status_args);
172 "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s",
173 path, strerror(errno));
174 if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
175 fprintf(stderr, ", %s\n",
176 replace_dev_result2string(status_args.result));
178 fprintf(stderr, "\n");
179 goto leave_with_error;
182 if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
184 "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
185 path, replace_dev_result2string(status_args.result));
186 goto leave_with_error;
189 if (status_args.status.replace_state ==
190 BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED) {
192 "ERROR: btrfs replace on \"%s\" already started!\n",
194 goto leave_with_error;
197 srcdev = argv[optind];
198 dstdev = canonicalize_path(argv[optind + 1]);
201 "ERROR: Could not canonicalize path '%s': %s\n",
202 argv[optind + 1], strerror(errno));
203 goto leave_with_error;
206 if (string_is_numerical(srcdev)) {
207 struct btrfs_ioctl_fs_info_args fi_args;
208 struct btrfs_ioctl_dev_info_args *di_args = NULL;
210 start_args.start.srcdevid = arg_strtou64(srcdev);
212 ret = get_fs_info(path, &fi_args, &di_args);
214 fprintf(stderr, "ERROR: getting dev info for devstats failed: "
215 "%s\n", strerror(-ret));
217 goto leave_with_error;
219 if (!fi_args.num_devices) {
220 fprintf(stderr, "ERROR: no devices found\n");
222 goto leave_with_error;
225 for (i = 0; i < fi_args.num_devices; i++)
226 if (start_args.start.srcdevid == di_args[i].devid)
228 srcdev_size = di_args[i].total_bytes;
230 if (i == fi_args.num_devices) {
231 fprintf(stderr, "Error: '%s' is not a valid devid for filesystem '%s'\n",
233 goto leave_with_error;
235 } else if (is_block_device(srcdev) > 0) {
236 strncpy((char *)start_args.start.srcdev_name, srcdev,
237 BTRFS_DEVICE_PATH_NAME_MAX);
238 start_args.start.srcdevid = 0;
239 srcdev_size = get_partition_size(srcdev);
241 fprintf(stderr, "ERROR: source device must be a block device or a devid\n");
242 goto leave_with_error;
245 ret = test_dev_for_mkfs(dstdev, force_using_targetdev);
247 goto leave_with_error;
249 dstdev_size = get_partition_size(dstdev);
250 if (srcdev_size > dstdev_size) {
251 fprintf(stderr, "ERROR: target device smaller than source device (required %llu bytes)\n",
253 goto leave_with_error;
256 fddstdev = open(dstdev, O_RDWR);
258 fprintf(stderr, "Unable to open %s\n", dstdev);
259 goto leave_with_error;
261 strncpy((char *)start_args.start.tgtdev_name, dstdev,
262 BTRFS_DEVICE_PATH_NAME_MAX);
263 ret = btrfs_prepare_device(fddstdev, dstdev, 1, &dstdev_block_count, 0,
266 goto leave_with_error;
273 dev_replace_handle_sigint(fdmnt);
274 if (!do_not_background) {
275 if (daemon(0, 0) < 0) {
276 fprintf(stderr, "ERROR, backgrounding failed: %s\n",
278 goto leave_with_error;
282 start_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_START;
283 start_args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
284 ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &start_args);
285 if (do_not_background) {
288 "ERROR: ioctl(DEV_REPLACE_START) failed on \"%s\": %s",
289 path, strerror(errno));
290 if (start_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
291 fprintf(stderr, ", %s\n",
292 replace_dev_result2string(start_args.result));
294 fprintf(stderr, "\n");
296 if (errno == EOPNOTSUPP)
298 "WARNING: dev_replace does not yet handle RAID5/6\n");
300 goto leave_with_error;
303 if (start_args.result !=
304 BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
306 "ERROR: ioctl(DEV_REPLACE_START) on \"%s\" returns error: %s\n",
308 replace_dev_result2string(start_args.result));
309 goto leave_with_error;
312 close_file_or_dir(fdmnt, dirstream);
313 btrfs_close_all_devices();
323 btrfs_close_all_devices();
327 static const char *const cmd_replace_status_usage[] = {
328 "btrfs replace status [-1] <mount_point>",
329 "Print status and progress information of a running device replace",
332 "-1 print once instead of print continuously until the replace",
333 " operation finishes (or is canceled)",
337 static int cmd_replace_status(int argc, char **argv)
344 DIR *dirstream = NULL;
346 while ((c = getopt(argc, argv, "1")) != -1) {
353 usage(cmd_replace_status_usage);
357 if (check_argc_exact(argc - optind, 1))
358 usage(cmd_replace_status_usage);
361 fd = btrfs_open_dir(path, &dirstream, 1);
365 ret = print_replace_status(fd, path, once);
366 close_file_or_dir(fd, dirstream);
370 static int print_replace_status(int fd, const char *path, int once)
372 struct btrfs_ioctl_dev_replace_args args = {0};
373 struct btrfs_ioctl_dev_replace_status_params *status;
375 int prevent_loop = 0;
383 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
384 args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
385 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
387 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s",
388 path, strerror(errno));
389 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
390 fprintf(stderr, ", %s\n",
391 replace_dev_result2string(args.result));
393 fprintf(stderr, "\n");
397 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
398 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
400 replace_dev_result2string(args.result));
404 status = &args.status;
408 switch (status->replace_state) {
409 case BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED:
412 progress2string(string3,
414 status->progress_1000));
416 case BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED:
418 printf("Started on %s, finished on %s",
419 time2string(string1, sizeof(string1),
420 status->time_started),
421 time2string(string2, sizeof(string2),
422 status->time_stopped));
424 case BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED:
426 printf("Started on %s, canceled on %s at %s",
427 time2string(string1, sizeof(string1),
428 status->time_started),
429 time2string(string2, sizeof(string2),
430 status->time_stopped),
431 progress2string(string3, sizeof(string3),
432 status->progress_1000));
434 case BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED:
436 printf("Started on %s, suspended on %s at %s",
437 time2string(string1, sizeof(string1),
438 status->time_started),
439 time2string(string2, sizeof(string2),
440 status->time_stopped),
441 progress2string(string3, sizeof(string3),
442 status->progress_1000));
444 case BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED:
447 printf("Never started");
451 "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" got unknown status: %llu\n",
452 path, status->replace_state);
458 ", %llu write errs, %llu uncorr. read errs",
459 (unsigned long long)status->num_write_errors,
461 status->num_uncorrectable_read_errors);
462 if (once || prevent_loop) {
469 while (num_chars > 0) {
479 time2string(char *buf, size_t s, __u64 t)
484 t_time_t = (time_t)t;
485 assert((__u64)t_time_t == t);
486 localtime_r(&t_time_t, &t_tm);
487 strftime(buf, s, "%e.%b %T", &t_tm);
492 progress2string(char *buf, size_t s, int progress_1000)
494 snprintf(buf, s, "%d.%01d%%", progress_1000 / 10, progress_1000 % 10);
500 static const char *const cmd_replace_cancel_usage[] = {
501 "btrfs replace cancel <mount_point>",
502 "Cancel a running device replace operation.",
506 static int cmd_replace_cancel(int argc, char **argv)
508 struct btrfs_ioctl_dev_replace_args args = {0};
514 DIR *dirstream = NULL;
516 while ((c = getopt(argc, argv, "")) != -1) {
520 usage(cmd_replace_cancel_usage);
524 if (check_argc_exact(argc - optind, 1))
525 usage(cmd_replace_cancel_usage);
528 fd = btrfs_open_dir(path, &dirstream, 1);
532 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
533 args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
534 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
536 close_file_or_dir(fd, dirstream);
538 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_CANCEL) failed on \"%s\": %s",
540 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
541 fprintf(stderr, ", %s\n",
542 replace_dev_result2string(args.result));
544 fprintf(stderr, "\n");
547 if (args.result == BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED) {
548 printf("INFO: ioctl(DEV_REPLACE_CANCEL)\"%s\": %s\n",
549 path, replace_dev_result2string(args.result));
555 static const char replace_cmd_group_info[] =
556 "replace a device in the filesystem";
558 const struct cmd_group replace_cmd_group = {
559 replace_cmd_group_usage, replace_cmd_group_info, {
560 { "start", cmd_replace_start, cmd_replace_start_usage, NULL,
562 { "status", cmd_replace_status, cmd_replace_status_usage, NULL,
564 { "cancel", cmd_replace_cancel, cmd_replace_cancel_usage, NULL,
570 int cmd_replace(int argc, char **argv)
572 return handle_command_group(&replace_cmd_group, argc, argv);