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;
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 status_args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
188 ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &status_args);
191 "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s",
192 path, strerror(errno));
193 if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
194 fprintf(stderr, ", %s\n",
195 replace_dev_result2string(status_args.result));
197 fprintf(stderr, "\n");
198 goto leave_with_error;
201 if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
203 "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
204 path, replace_dev_result2string(status_args.result));
205 goto leave_with_error;
208 if (status_args.status.replace_state ==
209 BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED) {
211 "ERROR: btrfs replace on \"%s\" already started!\n",
213 goto leave_with_error;
216 srcdev = argv[optind];
217 dstdev = canonicalize_path(argv[optind + 1]);
220 "ERROR: Could not canonicalize path '%s': %s\n",
221 argv[optind + 1], strerror(errno));
222 goto leave_with_error;
225 if (is_numerical(srcdev)) {
226 struct btrfs_ioctl_fs_info_args fi_args;
227 struct btrfs_ioctl_dev_info_args *di_args = NULL;
229 start_args.start.srcdevid = arg_strtou64(srcdev);
231 ret = get_fs_info(path, &fi_args, &di_args);
233 fprintf(stderr, "ERROR: getting dev info for devstats failed: "
234 "%s\n", strerror(-ret));
236 goto leave_with_error;
238 if (!fi_args.num_devices) {
239 fprintf(stderr, "ERROR: no devices found\n");
241 goto leave_with_error;
244 for (i = 0; i < fi_args.num_devices; i++)
245 if (start_args.start.srcdevid == di_args[i].devid)
248 if (i == fi_args.num_devices) {
249 fprintf(stderr, "Error: '%s' is not a valid devid for filesystem '%s'\n",
251 goto leave_with_error;
253 } else if (is_block_device(srcdev)) {
254 strncpy((char *)start_args.start.srcdev_name, srcdev,
255 BTRFS_DEVICE_PATH_NAME_MAX);
256 start_args.start.srcdevid = 0;
259 ret = test_dev_for_mkfs(dstdev, force_using_targetdev, estr);
261 fprintf(stderr, "%s", estr);
262 goto leave_with_error;
264 fddstdev = open(dstdev, O_RDWR);
266 fprintf(stderr, "Unable to open %s\n", dstdev);
267 goto leave_with_error;
269 strncpy((char *)start_args.start.tgtdev_name, dstdev,
270 BTRFS_DEVICE_PATH_NAME_MAX);
271 ret = btrfs_prepare_device(fddstdev, dstdev, 1, &dstdev_block_count, 0,
274 goto leave_with_error;
281 dev_replace_handle_sigint(fdmnt);
282 if (!do_not_background) {
283 if (daemon(0, 0) < 0) {
284 fprintf(stderr, "ERROR, backgrounding failed: %s\n",
286 goto leave_with_error;
290 start_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_START;
291 start_args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
292 ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &start_args);
293 if (do_not_background) {
296 "ERROR: ioctl(DEV_REPLACE_START) failed on \"%s\": %s",
297 path, strerror(errno));
298 if (start_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
299 fprintf(stderr, ", %s\n",
300 replace_dev_result2string(start_args.result));
302 fprintf(stderr, "\n");
304 if (errno == EOPNOTSUPP)
306 "WARNING: dev_replace does not yet handle RAID5/6\n");
308 goto leave_with_error;
311 if (start_args.result !=
312 BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
314 "ERROR: ioctl(DEV_REPLACE_START) on \"%s\" returns error: %s\n",
316 replace_dev_result2string(start_args.result));
317 goto leave_with_error;
320 close_file_or_dir(fdmnt, dirstream);
333 static const char *const cmd_status_replace_usage[] = {
334 "btrfs replace status [-1] <mount_point>",
335 "Print status and progress information of a running device replace",
338 "-1 print once instead of print continuously until the replace",
339 " operation finishes (or is canceled)",
343 static int cmd_status_replace(int argc, char **argv)
351 DIR *dirstream = NULL;
353 while ((c = getopt(argc, argv, "1")) != -1) {
360 usage(cmd_status_replace_usage);
364 if (check_argc_exact(argc - optind, 1))
365 usage(cmd_status_replace_usage);
368 fd = open_file_or_dir(path, &dirstream);
371 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
376 ret = print_replace_status(fd, path, once);
377 close_file_or_dir(fd, dirstream);
381 static int print_replace_status(int fd, const char *path, int once)
383 struct btrfs_ioctl_dev_replace_args args = {0};
384 struct btrfs_ioctl_dev_replace_status_params *status;
386 int prevent_loop = 0;
394 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
395 args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
396 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
398 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s",
399 path, strerror(errno));
400 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
401 fprintf(stderr, ", %s\n",
402 replace_dev_result2string(args.result));
404 fprintf(stderr, "\n");
408 status = &args.status;
409 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
410 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
412 replace_dev_result2string(args.result));
418 switch (status->replace_state) {
419 case BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED:
422 progress2string(string3,
424 status->progress_1000));
426 case BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED:
428 printf("Started on %s, finished on %s",
429 time2string(string1, sizeof(string1),
430 status->time_started),
431 time2string(string2, sizeof(string2),
432 status->time_stopped));
434 case BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED:
436 printf("Started on %s, canceled 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_SUSPENDED:
446 printf("Started on %s, suspended on %s at %s",
447 time2string(string1, sizeof(string1),
448 status->time_started),
449 time2string(string2, sizeof(string2),
450 status->time_stopped),
451 progress2string(string3, sizeof(string3),
452 status->progress_1000));
454 case BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED:
457 printf("Never started");
462 "Unknown btrfs dev replace status:%llu",
463 status->replace_state);
470 ", %llu write errs, %llu uncorr. read errs",
471 (unsigned long long)status->num_write_errors,
473 status->num_uncorrectable_read_errors);
474 if (once || prevent_loop || ret) {
481 while (num_chars > 0) {
491 time2string(char *buf, size_t s, __u64 t)
496 t_time_t = (time_t)t;
497 assert((__u64)t_time_t == t);
498 localtime_r(&t_time_t, &t_tm);
499 strftime(buf, s, "%e.%b %T", &t_tm);
504 progress2string(char *buf, size_t s, int progress_1000)
506 snprintf(buf, s, "%d.%01d%%", progress_1000 / 10, progress_1000 % 10);
512 static const char *const cmd_cancel_replace_usage[] = {
513 "btrfs replace cancel <mount_point>",
514 "Cancel a running device replace operation.",
518 static int cmd_cancel_replace(int argc, char **argv)
520 struct btrfs_ioctl_dev_replace_args args = {0};
526 DIR *dirstream = NULL;
528 while ((c = getopt(argc, argv, "")) != -1) {
532 usage(cmd_cancel_replace_usage);
536 if (check_argc_exact(argc - optind, 1))
537 usage(cmd_cancel_replace_usage);
540 fd = open_file_or_dir(path, &dirstream);
542 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
543 path, strerror(errno));
547 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
548 args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
549 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
551 close_file_or_dir(fd, dirstream);
553 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_CANCEL) failed on \"%s\": %s",
555 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
556 fprintf(stderr, ", %s\n",
557 replace_dev_result2string(args.result));
559 fprintf(stderr, "\n");
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 static const char replace_cmd_group_info[] =
571 "replace a device in the filesystem";
573 const struct cmd_group replace_cmd_group = {
574 replace_cmd_group_usage, replace_cmd_group_info, {
575 { "start", cmd_start_replace, cmd_start_replace_usage, NULL,
577 { "status", cmd_status_replace, cmd_status_replace_usage, NULL,
579 { "cancel", cmd_cancel_replace, cmd_cancel_replace_usage, NULL,
585 int cmd_replace(int argc, char **argv)
587 return handle_command_group(&replace_cmd_group, argc, argv);