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];
171 fdmnt = open_file_or_dir(path);
173 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
174 path, strerror(errno));
175 goto leave_with_error;
178 /* check for possible errors before backgrounding */
179 status_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
180 ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &status_args);
183 "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s, %s\n",
184 path, strerror(errno),
185 replace_dev_result2string(status_args.result));
186 goto leave_with_error;
189 if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
191 "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
192 path, replace_dev_result2string(status_args.result));
193 goto leave_with_error;
196 if (status_args.status.replace_state ==
197 BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED) {
199 "ERROR: btrfs replace on \"%s\" already started!\n",
201 goto leave_with_error;
204 srcdev = argv[optind];
205 dstdev = argv[optind + 1];
207 if (is_numerical(srcdev)) {
208 struct btrfs_ioctl_fs_info_args fi_args;
209 struct btrfs_ioctl_dev_info_args *di_args = NULL;
211 if (atoi(srcdev) == 0) {
212 fprintf(stderr, "Error: Failed to parse the numerical devid value '%s'\n",
214 goto leave_with_error;
216 start_args.start.srcdevid = (__u64)atoi(srcdev);
218 ret = get_fs_info(fdmnt, path, &fi_args, &di_args);
220 fprintf(stderr, "ERROR: getting dev info for devstats failed: "
221 "%s\n", strerror(-ret));
223 goto leave_with_error;
225 if (!fi_args.num_devices) {
226 fprintf(stderr, "ERROR: no devices found\n");
228 goto leave_with_error;
231 for (i = 0; i < fi_args.num_devices; i++)
232 if (start_args.start.srcdevid == di_args[i].devid)
235 if (i == fi_args.num_devices) {
236 fprintf(stderr, "Error: '%s' is not a valid devid for filesystem '%s'\n",
238 goto leave_with_error;
241 fdsrcdev = open(srcdev, O_RDWR);
243 fprintf(stderr, "Error: Unable to open device '%s'\n",
245 goto leave_with_error;
247 ret = fstat(fdsrcdev, &st);
249 fprintf(stderr, "Error: Unable to stat '%s'\n", srcdev);
250 goto leave_with_error;
252 if (!S_ISBLK(st.st_mode)) {
253 fprintf(stderr, "Error: '%s' is not a block device\n",
255 goto leave_with_error;
257 strncpy((char *)start_args.start.srcdev_name, srcdev,
258 BTRFS_DEVICE_PATH_NAME_MAX);
261 start_args.start.srcdevid = 0;
264 ret = check_mounted(dstdev);
266 fprintf(stderr, "Error checking %s mount status\n", dstdev);
267 goto leave_with_error;
271 "Error, target device %s is in use and currently mounted!\n",
273 goto leave_with_error;
275 fddstdev = open(dstdev, O_RDWR);
277 fprintf(stderr, "Unable to open %s\n", dstdev);
278 goto leave_with_error;
280 ret = btrfs_scan_one_device(fddstdev, dstdev, &fs_devices_mnt,
281 &total_devs, BTRFS_SUPER_INFO_OFFSET);
282 if (ret >= 0 && !force_using_targetdev) {
284 "Error, target device %s contains filesystem, use '-f' to force overwriting.\n",
286 goto leave_with_error;
288 ret = fstat(fddstdev, &st);
290 fprintf(stderr, "Error: Unable to stat '%s'\n", dstdev);
291 goto leave_with_error;
293 if (!S_ISBLK(st.st_mode)) {
294 fprintf(stderr, "Error: '%s' is not a block device\n", dstdev);
295 goto leave_with_error;
297 strncpy((char *)start_args.start.tgtdev_name, dstdev,
298 BTRFS_DEVICE_PATH_NAME_MAX);
299 if (btrfs_prepare_device(fddstdev, dstdev, 1, &dstdev_block_count, 0,
301 fprintf(stderr, "Error: Failed to prepare device '%s'\n",
303 goto leave_with_error;
308 dev_replace_handle_sigint(fdmnt);
309 if (!do_not_background) {
310 if (daemon(0, 0) < 0) {
311 fprintf(stderr, "ERROR, backgrounding failed: %s\n",
313 goto leave_with_error;
317 start_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_START;
318 ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &start_args);
319 if (do_not_background) {
322 "ERROR: ioctl(DEV_REPLACE_START) failed on \"%s\": %s, %s\n",
323 path, strerror(errno),
324 replace_dev_result2string(start_args.result));
325 goto leave_with_error;
328 if (start_args.result !=
329 BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
331 "ERROR: ioctl(DEV_REPLACE_START) on \"%s\" returns error: %s\n",
333 replace_dev_result2string(start_args.result));
334 goto leave_with_error;
350 static const char *const cmd_status_replace_usage[] = {
351 "btrfs replace status mount_point [-1]",
352 "Print status and progress information of a running device replace",
355 "-1 print once instead of print continously until the replace",
356 " operation finishes (or is canceled)",
360 static int cmd_status_replace(int argc, char **argv)
369 while ((c = getopt(argc, argv, "1")) != -1) {
376 usage(cmd_status_replace_usage);
380 if (check_argc_exact(argc - optind, 1))
381 usage(cmd_status_replace_usage);
384 fd = open_file_or_dir(path);
387 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
392 ret = print_replace_status(fd, path, once);
397 static int print_replace_status(int fd, const char *path, int once)
399 struct btrfs_ioctl_dev_replace_args args = {0};
400 struct btrfs_ioctl_dev_replace_status_params *status;
402 int prevent_loop = 0;
410 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
411 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
413 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s, %s\n",
414 path, strerror(errno),
415 replace_dev_result2string(args.result));
419 status = &args.status;
420 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
421 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
423 replace_dev_result2string(args.result));
429 switch (status->replace_state) {
430 case BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED:
433 progress2string(string3,
435 status->progress_1000));
437 case BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED:
439 printf("Started on %s, finished on %s",
440 time2string(string1, sizeof(string1),
441 status->time_started),
442 time2string(string2, sizeof(string2),
443 status->time_stopped));
445 case BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED:
447 printf("Started on %s, canceled on %s at %s",
448 time2string(string1, sizeof(string1),
449 status->time_started),
450 time2string(string2, sizeof(string2),
451 status->time_stopped),
452 progress2string(string3, sizeof(string3),
453 status->progress_1000));
455 case BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED:
457 printf("Started on %s, suspended on %s at %s",
458 time2string(string1, sizeof(string1),
459 status->time_started),
460 time2string(string2, sizeof(string2),
461 status->time_stopped),
462 progress2string(string3, sizeof(string3),
463 status->progress_1000));
465 case BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED:
468 printf("Never started");
478 ", %llu write errs, %llu uncorr. read errs",
479 (unsigned long long)status->num_write_errors,
481 status->num_uncorrectable_read_errors);
482 if (once || prevent_loop) {
489 while (num_chars > 0) {
499 time2string(char *buf, size_t s, __u64 t)
504 t_time_t = (time_t)t;
505 assert((__u64)t_time_t == t);
506 localtime_r(&t_time_t, &t_tm);
507 strftime(buf, s, "%e.%b %T", &t_tm);
512 progress2string(char *buf, size_t s, int progress_1000)
514 snprintf(buf, s, "%d.%01d%%", progress_1000 / 10, progress_1000 % 10);
520 static const char *const cmd_cancel_replace_usage[] = {
521 "btrfs replace cancel mount_point",
522 "Cancel a running device replace operation.",
526 static int cmd_cancel_replace(int argc, char **argv)
528 struct btrfs_ioctl_dev_replace_args args = {0};
535 while ((c = getopt(argc, argv, "")) != -1) {
539 usage(cmd_cancel_replace_usage);
543 if (check_argc_exact(argc - optind, 1))
544 usage(cmd_cancel_replace_usage);
547 fd = open_file_or_dir(path);
549 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
550 path, strerror(errno));
554 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
555 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
559 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_CANCEL) failed on \"%s\": %s, %s\n",
561 replace_dev_result2string(args.result));
568 const struct cmd_group replace_cmd_group = {
569 replace_cmd_group_usage, NULL, {
570 { "start", cmd_start_replace, cmd_start_replace_usage, NULL,
572 { "status", cmd_status_replace, cmd_status_replace_usage, NULL,
574 { "cancel", cmd_cancel_replace, cmd_cancel_replace_usage, NULL,
580 int cmd_replace(int argc, char **argv)
582 return handle_command_group(&replace_cmd_group, argc, argv);