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 [-Bfr] <srcdev>|<devid> <targetdev> <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;
146 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_start_replace_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_start_replace_usage);
171 path = argv[optind + 2];
173 fdmnt = open_path_or_dev_mnt(path, &dirstream);
176 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
177 path, strerror(errno));
178 goto leave_with_error;
181 /* check for possible errors before backgrounding */
182 status_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
183 ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &status_args);
186 "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s, %s\n",
187 path, strerror(errno),
188 replace_dev_result2string(status_args.result));
189 goto leave_with_error;
192 if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
194 "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
195 path, replace_dev_result2string(status_args.result));
196 goto leave_with_error;
199 if (status_args.status.replace_state ==
200 BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED) {
202 "ERROR: btrfs replace on \"%s\" already started!\n",
204 goto leave_with_error;
207 srcdev = argv[optind];
208 dstdev = argv[optind + 1];
210 if (is_numerical(srcdev)) {
211 struct btrfs_ioctl_fs_info_args fi_args;
212 struct btrfs_ioctl_dev_info_args *di_args = NULL;
214 if (atoi(srcdev) == 0) {
215 fprintf(stderr, "Error: Failed to parse the numerical devid value '%s'\n",
217 goto leave_with_error;
219 start_args.start.srcdevid = (__u64)atoi(srcdev);
221 ret = get_fs_info(path, &fi_args, &di_args);
223 fprintf(stderr, "ERROR: getting dev info for devstats failed: "
224 "%s\n", strerror(-ret));
226 goto leave_with_error;
228 if (!fi_args.num_devices) {
229 fprintf(stderr, "ERROR: no devices found\n");
231 goto leave_with_error;
234 for (i = 0; i < fi_args.num_devices; i++)
235 if (start_args.start.srcdevid == di_args[i].devid)
238 if (i == fi_args.num_devices) {
239 fprintf(stderr, "Error: '%s' is not a valid devid for filesystem '%s'\n",
241 goto leave_with_error;
244 fdsrcdev = open(srcdev, O_RDWR);
246 fprintf(stderr, "Error: Unable to open device '%s'\n",
248 goto leave_with_error;
250 ret = fstat(fdsrcdev, &st);
252 fprintf(stderr, "Error: Unable to stat '%s'\n", srcdev);
253 goto leave_with_error;
255 if (!S_ISBLK(st.st_mode)) {
256 fprintf(stderr, "Error: '%s' is not a block device\n",
258 goto leave_with_error;
260 strncpy((char *)start_args.start.srcdev_name, srcdev,
261 BTRFS_DEVICE_PATH_NAME_MAX);
264 start_args.start.srcdevid = 0;
267 ret = check_mounted(dstdev);
269 fprintf(stderr, "Error checking %s mount status\n", dstdev);
270 goto leave_with_error;
274 "Error, target device %s is in use and currently mounted!\n",
276 goto leave_with_error;
278 fddstdev = open(dstdev, O_RDWR);
280 fprintf(stderr, "Unable to open %s\n", dstdev);
281 goto leave_with_error;
283 ret = btrfs_scan_one_device(fddstdev, dstdev, &fs_devices_mnt,
284 &total_devs, BTRFS_SUPER_INFO_OFFSET);
285 if (ret >= 0 && !force_using_targetdev) {
287 "Error, target device %s contains filesystem, use '-f' to force overwriting.\n",
289 goto leave_with_error;
291 ret = fstat(fddstdev, &st);
293 fprintf(stderr, "Error: Unable to stat '%s'\n", dstdev);
294 goto leave_with_error;
296 if (!S_ISBLK(st.st_mode)) {
297 fprintf(stderr, "Error: '%s' is not a block device\n", dstdev);
298 goto leave_with_error;
300 strncpy((char *)start_args.start.tgtdev_name, dstdev,
301 BTRFS_DEVICE_PATH_NAME_MAX);
302 if (btrfs_prepare_device(fddstdev, dstdev, 1, &dstdev_block_count, 0,
304 fprintf(stderr, "Error: Failed to prepare device '%s'\n",
306 goto leave_with_error;
311 dev_replace_handle_sigint(fdmnt);
312 if (!do_not_background) {
313 if (daemon(0, 0) < 0) {
314 fprintf(stderr, "ERROR, backgrounding failed: %s\n",
316 goto leave_with_error;
320 start_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_START;
321 ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &start_args);
322 if (do_not_background) {
325 "ERROR: ioctl(DEV_REPLACE_START) failed on \"%s\": %s, %s\n",
326 path, strerror(errno),
327 replace_dev_result2string(start_args.result));
328 goto leave_with_error;
331 if (start_args.result !=
332 BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
334 "ERROR: ioctl(DEV_REPLACE_START) on \"%s\" returns error: %s\n",
336 replace_dev_result2string(start_args.result));
337 goto leave_with_error;
340 close_file_or_dir(fdmnt, dirstream);
353 static const char *const cmd_status_replace_usage[] = {
354 "btrfs replace status [-1] <mount_point>",
355 "Print status and progress information of a running device replace",
358 "-1 print once instead of print continuously until the replace",
359 " operation finishes (or is canceled)",
363 static int cmd_status_replace(int argc, char **argv)
371 DIR *dirstream = NULL;
373 while ((c = getopt(argc, argv, "1")) != -1) {
380 usage(cmd_status_replace_usage);
384 if (check_argc_exact(argc - optind, 1))
385 usage(cmd_status_replace_usage);
388 fd = open_file_or_dir(path, &dirstream);
391 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
396 ret = print_replace_status(fd, path, once);
397 close_file_or_dir(fd, dirstream);
401 static int print_replace_status(int fd, const char *path, int once)
403 struct btrfs_ioctl_dev_replace_args args = {0};
404 struct btrfs_ioctl_dev_replace_status_params *status;
406 int prevent_loop = 0;
414 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
415 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
417 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s, %s\n",
418 path, strerror(errno),
419 replace_dev_result2string(args.result));
423 status = &args.status;
424 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
425 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
427 replace_dev_result2string(args.result));
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");
482 ", %llu write errs, %llu uncorr. read errs",
483 (unsigned long long)status->num_write_errors,
485 status->num_uncorrectable_read_errors);
486 if (once || prevent_loop) {
493 while (num_chars > 0) {
503 time2string(char *buf, size_t s, __u64 t)
508 t_time_t = (time_t)t;
509 assert((__u64)t_time_t == t);
510 localtime_r(&t_time_t, &t_tm);
511 strftime(buf, s, "%e.%b %T", &t_tm);
516 progress2string(char *buf, size_t s, int progress_1000)
518 snprintf(buf, s, "%d.%01d%%", progress_1000 / 10, progress_1000 % 10);
524 static const char *const cmd_cancel_replace_usage[] = {
525 "btrfs replace cancel <mount_point>",
526 "Cancel a running device replace operation.",
530 static int cmd_cancel_replace(int argc, char **argv)
532 struct btrfs_ioctl_dev_replace_args args = {0};
538 DIR *dirstream = NULL;
540 while ((c = getopt(argc, argv, "")) != -1) {
544 usage(cmd_cancel_replace_usage);
548 if (check_argc_exact(argc - optind, 1))
549 usage(cmd_cancel_replace_usage);
552 fd = open_file_or_dir(path, &dirstream);
554 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
555 path, strerror(errno));
559 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
560 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
562 close_file_or_dir(fd, dirstream);
564 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_CANCEL) failed on \"%s\": %s, %s\n",
566 replace_dev_result2string(args.result));
573 const struct cmd_group replace_cmd_group = {
574 replace_cmd_group_usage, NULL, {
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);