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 srcdev|devid targetdev [-Bfr] 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 struct btrfs_fs_devices *fs_devices_mnt = NULL;
143 u64 dstdev_block_count;
144 int do_not_background = 0;
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);
175 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
176 path, strerror(errno));
177 goto leave_with_error;
180 /* check for possible errors before backgrounding */
181 status_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
182 ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &status_args);
185 "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s, %s\n",
186 path, strerror(errno),
187 replace_dev_result2string(status_args.result));
188 goto leave_with_error;
191 if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
193 "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
194 path, replace_dev_result2string(status_args.result));
195 goto leave_with_error;
198 if (status_args.status.replace_state ==
199 BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED) {
201 "ERROR: btrfs replace on \"%s\" already started!\n",
203 goto leave_with_error;
206 srcdev = argv[optind];
207 dstdev = argv[optind + 1];
209 if (is_numerical(srcdev)) {
210 struct btrfs_ioctl_fs_info_args fi_args;
211 struct btrfs_ioctl_dev_info_args *di_args = NULL;
213 if (atoi(srcdev) == 0) {
214 fprintf(stderr, "Error: Failed to parse the numerical devid value '%s'\n",
216 goto leave_with_error;
218 start_args.start.srcdevid = (__u64)atoi(srcdev);
220 ret = get_fs_info(path, &fi_args, &di_args);
222 fprintf(stderr, "ERROR: getting dev info for devstats failed: "
223 "%s\n", strerror(-ret));
225 goto leave_with_error;
227 if (!fi_args.num_devices) {
228 fprintf(stderr, "ERROR: no devices found\n");
230 goto leave_with_error;
233 for (i = 0; i < fi_args.num_devices; i++)
234 if (start_args.start.srcdevid == di_args[i].devid)
237 if (i == fi_args.num_devices) {
238 fprintf(stderr, "Error: '%s' is not a valid devid for filesystem '%s'\n",
240 goto leave_with_error;
243 fdsrcdev = open(srcdev, O_RDWR);
245 fprintf(stderr, "Error: Unable to open device '%s'\n",
247 goto leave_with_error;
249 ret = fstat(fdsrcdev, &st);
251 fprintf(stderr, "Error: Unable to stat '%s'\n", srcdev);
252 goto leave_with_error;
254 if (!S_ISBLK(st.st_mode)) {
255 fprintf(stderr, "Error: '%s' is not a block device\n",
257 goto leave_with_error;
259 strncpy((char *)start_args.start.srcdev_name, srcdev,
260 BTRFS_DEVICE_PATH_NAME_MAX);
263 start_args.start.srcdevid = 0;
266 ret = check_mounted(dstdev);
268 fprintf(stderr, "Error checking %s mount status\n", dstdev);
269 goto leave_with_error;
273 "Error, target device %s is in use and currently mounted!\n",
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 ret = btrfs_scan_one_device(fddstdev, dstdev, &fs_devices_mnt,
283 &total_devs, BTRFS_SUPER_INFO_OFFSET);
284 if (ret >= 0 && !force_using_targetdev) {
286 "Error, target device %s contains filesystem, use '-f' to force overwriting.\n",
288 goto leave_with_error;
290 ret = fstat(fddstdev, &st);
292 fprintf(stderr, "Error: Unable to stat '%s'\n", dstdev);
293 goto leave_with_error;
295 if (!S_ISBLK(st.st_mode)) {
296 fprintf(stderr, "Error: '%s' is not a block device\n", dstdev);
297 goto leave_with_error;
299 strncpy((char *)start_args.start.tgtdev_name, dstdev,
300 BTRFS_DEVICE_PATH_NAME_MAX);
301 if (btrfs_prepare_device(fddstdev, dstdev, 1, &dstdev_block_count, 0,
303 fprintf(stderr, "Error: Failed to prepare device '%s'\n",
305 goto leave_with_error;
310 dev_replace_handle_sigint(fdmnt);
311 if (!do_not_background) {
312 if (daemon(0, 0) < 0) {
313 fprintf(stderr, "ERROR, backgrounding failed: %s\n",
315 goto leave_with_error;
319 start_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_START;
320 ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &start_args);
321 if (do_not_background) {
324 "ERROR: ioctl(DEV_REPLACE_START) failed on \"%s\": %s, %s\n",
325 path, strerror(errno),
326 replace_dev_result2string(start_args.result));
327 goto leave_with_error;
330 if (start_args.result !=
331 BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
333 "ERROR: ioctl(DEV_REPLACE_START) on \"%s\" returns error: %s\n",
335 replace_dev_result2string(start_args.result));
336 goto leave_with_error;
352 static const char *const cmd_status_replace_usage[] = {
353 "btrfs replace status mount_point [-1]",
354 "Print status and progress information of a running device replace",
357 "-1 print once instead of print continously until the replace",
358 " operation finishes (or is canceled)",
362 static int cmd_status_replace(int argc, char **argv)
371 while ((c = getopt(argc, argv, "1")) != -1) {
378 usage(cmd_status_replace_usage);
382 if (check_argc_exact(argc - optind, 1))
383 usage(cmd_status_replace_usage);
386 fd = open_file_or_dir(path);
389 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
394 ret = print_replace_status(fd, path, once);
399 static int print_replace_status(int fd, const char *path, int once)
401 struct btrfs_ioctl_dev_replace_args args = {0};
402 struct btrfs_ioctl_dev_replace_status_params *status;
404 int prevent_loop = 0;
412 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
413 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
415 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s, %s\n",
416 path, strerror(errno),
417 replace_dev_result2string(args.result));
421 status = &args.status;
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));
431 switch (status->replace_state) {
432 case BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED:
435 progress2string(string3,
437 status->progress_1000));
439 case BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED:
441 printf("Started on %s, finished on %s",
442 time2string(string1, sizeof(string1),
443 status->time_started),
444 time2string(string2, sizeof(string2),
445 status->time_stopped));
447 case BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED:
449 printf("Started on %s, canceled on %s at %s",
450 time2string(string1, sizeof(string1),
451 status->time_started),
452 time2string(string2, sizeof(string2),
453 status->time_stopped),
454 progress2string(string3, sizeof(string3),
455 status->progress_1000));
457 case BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED:
459 printf("Started on %s, suspended on %s at %s",
460 time2string(string1, sizeof(string1),
461 status->time_started),
462 time2string(string2, sizeof(string2),
463 status->time_stopped),
464 progress2string(string3, sizeof(string3),
465 status->progress_1000));
467 case BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED:
470 printf("Never started");
480 ", %llu write errs, %llu uncorr. read errs",
481 (unsigned long long)status->num_write_errors,
483 status->num_uncorrectable_read_errors);
484 if (once || prevent_loop) {
491 while (num_chars > 0) {
501 time2string(char *buf, size_t s, __u64 t)
506 t_time_t = (time_t)t;
507 assert((__u64)t_time_t == t);
508 localtime_r(&t_time_t, &t_tm);
509 strftime(buf, s, "%e.%b %T", &t_tm);
514 progress2string(char *buf, size_t s, int progress_1000)
516 snprintf(buf, s, "%d.%01d%%", progress_1000 / 10, progress_1000 % 10);
522 static const char *const cmd_cancel_replace_usage[] = {
523 "btrfs replace cancel mount_point",
524 "Cancel a running device replace operation.",
528 static int cmd_cancel_replace(int argc, char **argv)
530 struct btrfs_ioctl_dev_replace_args args = {0};
537 while ((c = getopt(argc, argv, "")) != -1) {
541 usage(cmd_cancel_replace_usage);
545 if (check_argc_exact(argc - optind, 1))
546 usage(cmd_cancel_replace_usage);
549 fd = open_file_or_dir(path);
551 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
552 path, strerror(errno));
556 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
557 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
561 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_CANCEL) failed on \"%s\": %s, %s\n",
563 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);