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)
80 struct btrfs_ioctl_dev_replace_args args = {0};
82 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
83 ioctl(dev_replace_cancel_fd, BTRFS_IOC_DEV_REPLACE, &args);
86 static int dev_replace_handle_sigint(int fd)
88 struct sigaction sa = {
89 .sa_handler = fd == -1 ? SIG_DFL : dev_replace_sigint_handler
92 dev_replace_cancel_fd = fd;
93 return sigaction(SIGINT, &sa, NULL);
96 static const char *const cmd_start_replace_usage[] = {
97 "btrfs replace start srcdev|devid targetdev [-Bfr] mount_point",
98 "Replace device of a btrfs filesystem.",
99 "On a live filesystem, duplicate the data to the target device which",
100 "is currently stored on the source device. If the source device is not",
101 "available anymore, or if the -r option is set, the data is built",
102 "only using the RAID redundancy mechanisms. After completion of the",
103 "operation, the source device is removed from the filesystem.",
104 "If the srcdev is a numerical value, it is assumed to be the device id",
105 "of the filesystem which is mounted at mount_point, otherwise it is",
106 "the path to the source device. If the source device is disconnected,",
107 "from the system, you have to use the devid parameter format.",
108 "The targetdev needs to be same size or larger than the srcdev.",
110 "-r only read from srcdev if no other zero-defect mirror exists",
111 " (enable this if your drive has lots of read errors, the access",
112 " would be very slow)",
113 "-f force using and overwriting targetdev even if it looks like",
114 " containing a valid btrfs filesystem. A valid filesystem is",
115 " assumed if a btrfs superblock is found which contains a",
116 " correct checksum. Devices which are currently mounted are",
117 " never allowed to be used as the targetdev",
118 "-B do not background",
122 static int cmd_start_replace(int argc, char **argv)
124 struct btrfs_ioctl_dev_replace_args start_args = {0};
125 struct btrfs_ioctl_dev_replace_args status_args = {0};
135 int avoid_reading_from_srcdev = 0;
136 int force_using_targetdev = 0;
138 struct btrfs_fs_devices *fs_devices_mnt = NULL;
140 u64 dstdev_block_count;
141 int do_not_background = 0;
144 while ((c = getopt(argc, argv, "Brf")) != -1) {
147 do_not_background = 1;
150 avoid_reading_from_srcdev = 1;
153 force_using_targetdev = 1;
157 usage(cmd_start_replace_usage);
161 start_args.start.cont_reading_from_srcdev_mode =
162 avoid_reading_from_srcdev ?
163 BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_AVOID :
164 BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_ALWAYS;
165 if (check_argc_exact(argc - optind, 3))
166 usage(cmd_start_replace_usage);
167 path = argv[optind + 2];
168 fdmnt = open_file_or_dir(path);
170 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
171 path, strerror(errno));
172 goto leave_with_error;
175 /* check for possible errors before backgrounding */
176 status_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
177 ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &status_args);
180 "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s, %s\n",
181 path, strerror(errno),
182 replace_dev_result2string(status_args.result));
183 goto leave_with_error;
186 if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
188 "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
189 path, replace_dev_result2string(status_args.result));
190 goto leave_with_error;
193 if (status_args.status.replace_state ==
194 BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED) {
196 "ERROR: btrfs replace on \"%s\" already started!\n",
198 goto leave_with_error;
201 srcdev = argv[optind];
202 dstdev = argv[optind + 1];
204 if (is_numerical(srcdev)) {
205 struct btrfs_ioctl_fs_info_args fi_args;
206 struct btrfs_ioctl_dev_info_args *di_args = NULL;
208 if (atoi(srcdev) == 0) {
209 fprintf(stderr, "Error: Failed to parse the numerical devid value '%s'\n",
211 goto leave_with_error;
213 start_args.start.srcdevid = (__u64)atoi(srcdev);
215 ret = get_fs_info(fdmnt, path, &fi_args, &di_args);
217 fprintf(stderr, "ERROR: getting dev info for devstats failed: "
218 "%s\n", strerror(-ret));
220 goto leave_with_error;
222 if (!fi_args.num_devices) {
223 fprintf(stderr, "ERROR: no devices found\n");
225 goto leave_with_error;
228 for (i = 0; i < fi_args.num_devices; i++)
229 if (start_args.start.srcdevid == di_args[i].devid)
231 if (i == fi_args.num_devices) {
232 fprintf(stderr, "Error: '%s' is not a valid devid for filesystem '%s'\n",
234 goto leave_with_error;
237 fdsrcdev = open(srcdev, O_RDWR);
239 fprintf(stderr, "Error: Unable to open device '%s'\n",
241 goto leave_with_error;
243 ret = fstat(fdsrcdev, &st);
245 fprintf(stderr, "Error: Unable to stat '%s'\n", srcdev);
246 goto leave_with_error;
248 if (!S_ISBLK(st.st_mode)) {
249 fprintf(stderr, "Error: '%s' is not a block device\n",
251 goto leave_with_error;
253 strncpy((char *)start_args.start.srcdev_name, srcdev,
254 BTRFS_DEVICE_PATH_NAME_MAX);
257 start_args.start.srcdevid = 0;
260 ret = check_mounted(dstdev);
262 fprintf(stderr, "Error checking %s mount status\n", dstdev);
263 goto leave_with_error;
267 "Error, target device %s is in use and currently mounted!\n",
269 goto leave_with_error;
271 fddstdev = open(dstdev, O_RDWR);
273 fprintf(stderr, "Unable to open %s\n", dstdev);
274 goto leave_with_error;
276 ret = btrfs_scan_one_device(fddstdev, dstdev, &fs_devices_mnt,
277 &total_devs, BTRFS_SUPER_INFO_OFFSET);
278 if (ret >= 0 && !force_using_targetdev) {
280 "Error, target device %s contains filesystem, use '-f' to force overwriting.\n",
282 goto leave_with_error;
284 ret = fstat(fddstdev, &st);
286 fprintf(stderr, "Error: Unable to stat '%s'\n", dstdev);
287 goto leave_with_error;
289 if (!S_ISBLK(st.st_mode)) {
290 fprintf(stderr, "Error: '%s' is not a block device\n", dstdev);
291 goto leave_with_error;
293 strncpy((char *)start_args.start.tgtdev_name, dstdev,
294 BTRFS_DEVICE_PATH_NAME_MAX);
295 if (btrfs_prepare_device(fddstdev, dstdev, 1, &dstdev_block_count, 0,
297 fprintf(stderr, "Error: Failed to prepare device '%s'\n",
299 goto leave_with_error;
304 dev_replace_handle_sigint(fdmnt);
305 if (!do_not_background) {
306 if (daemon(0, 0) < 0) {
307 fprintf(stderr, "ERROR, backgrounding failed: %s\n",
309 goto leave_with_error;
313 start_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_START;
314 ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &start_args);
315 if (do_not_background) {
318 "ERROR: ioctl(DEV_REPLACE_START) failed on \"%s\": %s, %s\n",
319 path, strerror(errno),
320 replace_dev_result2string(start_args.result));
321 goto leave_with_error;
324 if (start_args.result !=
325 BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
327 "ERROR: ioctl(DEV_REPLACE_START) on \"%s\" returns error: %s\n",
329 replace_dev_result2string(start_args.result));
330 goto leave_with_error;
346 static const char *const cmd_status_replace_usage[] = {
347 "btrfs replace status mount_point [-1]",
348 "Print status and progress information of a running device replace",
351 "-1 print once instead of print continously until the replace",
352 " operation finishes (or is canceled)",
356 static int cmd_status_replace(int argc, char **argv)
365 while ((c = getopt(argc, argv, "1")) != -1) {
372 usage(cmd_status_replace_usage);
376 if (check_argc_exact(argc - optind, 1))
377 usage(cmd_status_replace_usage);
380 fd = open_file_or_dir(path);
383 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
388 ret = print_replace_status(fd, path, once);
393 static int print_replace_status(int fd, const char *path, int once)
395 struct btrfs_ioctl_dev_replace_args args = {0};
396 struct btrfs_ioctl_dev_replace_status_params *status;
398 int prevent_loop = 0;
406 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
407 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
409 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s, %s\n",
410 path, strerror(errno),
411 replace_dev_result2string(args.result));
415 status = &args.status;
416 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
417 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
419 replace_dev_result2string(args.result));
425 switch (status->replace_state) {
426 case BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED:
429 progress2string(string3,
431 status->progress_1000));
433 case BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED:
435 printf("Started on %s, finished on %s",
436 time2string(string1, sizeof(string1),
437 status->time_started),
438 time2string(string2, sizeof(string2),
439 status->time_stopped));
441 case BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED:
443 printf("Started on %s, canceled on %s at %s",
444 time2string(string1, sizeof(string1),
445 status->time_started),
446 time2string(string2, sizeof(string2),
447 status->time_stopped),
448 progress2string(string3, sizeof(string3),
449 status->progress_1000));
451 case BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED:
453 printf("Started on %s, suspended on %s at %s",
454 time2string(string1, sizeof(string1),
455 status->time_started),
456 time2string(string2, sizeof(string2),
457 status->time_stopped),
458 progress2string(string3, sizeof(string3),
459 status->progress_1000));
461 case BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED:
464 printf("Never started");
474 ", %llu write errs, %llu uncorr. read errs",
475 (unsigned long long)status->num_write_errors,
477 status->num_uncorrectable_read_errors);
478 if (once || prevent_loop) {
485 while (num_chars > 0) {
495 time2string(char *buf, size_t s, __u64 t)
500 t_time_t = (time_t)t;
501 assert((__u64)t_time_t == t);
502 localtime_r(&t_time_t, &t_tm);
503 strftime(buf, s, "%e.%b %T", &t_tm);
508 progress2string(char *buf, size_t s, int progress_1000)
510 snprintf(buf, s, "%d.%01d%%", progress_1000 / 10, progress_1000 % 10);
516 static const char *const cmd_cancel_replace_usage[] = {
517 "btrfs replace cancel mount_point",
518 "Cancel a running device replace operation.",
522 static int cmd_cancel_replace(int argc, char **argv)
524 struct btrfs_ioctl_dev_replace_args args = {0};
531 while ((c = getopt(argc, argv, "")) != -1) {
535 usage(cmd_cancel_replace_usage);
539 if (check_argc_exact(argc - optind, 1))
540 usage(cmd_cancel_replace_usage);
543 fd = open_file_or_dir(path);
545 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
546 path, strerror(errno));
550 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
551 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
555 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_CANCEL) failed on \"%s\": %s, %s\n",
557 replace_dev_result2string(args.result));
564 const struct cmd_group replace_cmd_group = {
565 replace_cmd_group_usage, NULL, {
566 { "start", cmd_start_replace, cmd_start_replace_usage, NULL,
568 { "status", cmd_status_replace, cmd_status_replace_usage, NULL,
570 { "cancel", cmd_cancel_replace, cmd_cancel_replace_usage, NULL,
576 int cmd_replace(int argc, char **argv)
578 return handle_command_group(&replace_cmd_group, argc, argv);