Btrfs progs v4.17.1
[btrfs-progs-unstable/devel.git] / cmds-replace.c
blob1fa802845f0ed92ea0edb28e8bc6c4f06aca355b
1 /*
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.
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <unistd.h>
23 #include <fcntl.h>
24 #include <sys/ioctl.h>
25 #include <errno.h>
26 #include <sys/stat.h>
27 #include <time.h>
28 #include <assert.h>
29 #include <inttypes.h>
30 #include <sys/wait.h>
32 #include "kerncompat.h"
33 #include "ctree.h"
34 #include "ioctl.h"
35 #include "utils.h"
36 #include "volumes.h"
37 #include "disk-io.h"
39 #include "commands.h"
40 #include "help.h"
41 #include "mkfs/common.h"
43 static int print_replace_status(int fd, const char *path, int once);
44 static char *time2string(char *buf, size_t s, __u64 t);
45 static char *progress2string(char *buf, size_t s, int progress_1000);
48 static const char *replace_dev_result2string(__u64 result)
50 switch (result) {
51 case BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR:
52 return "no error";
53 case BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED:
54 return "not started";
55 case BTRFS_IOCTL_DEV_REPLACE_RESULT_ALREADY_STARTED:
56 return "already started";
57 case BTRFS_IOCTL_DEV_REPLACE_RESULT_SCRUB_INPROGRESS:
58 return "scrub is in progress";
59 default:
60 return "<illegal result value>";
64 static const char * const replace_cmd_group_usage[] = {
65 "btrfs replace <command> [<args>]",
66 NULL
69 static int dev_replace_cancel_fd = -1;
70 static void dev_replace_sigint_handler(int signal)
72 int ret;
73 struct btrfs_ioctl_dev_replace_args args = {0};
75 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
76 ret = ioctl(dev_replace_cancel_fd, BTRFS_IOC_DEV_REPLACE, &args);
77 if (ret < 0)
78 perror("Device replace cancel failed");
81 static int dev_replace_handle_sigint(int fd)
83 struct sigaction sa = {
84 .sa_handler = fd == -1 ? SIG_DFL : dev_replace_sigint_handler
87 dev_replace_cancel_fd = fd;
88 return sigaction(SIGINT, &sa, NULL);
91 static const char *const cmd_replace_start_usage[] = {
92 "btrfs replace start [-Bfr] <srcdev>|<devid> <targetdev> <mount_point>",
93 "Replace device of a btrfs filesystem.",
94 "On a live filesystem, duplicate the data to the target device which",
95 "is currently stored on the source device. If the source device is not",
96 "available anymore, or if the -r option is set, the data is built",
97 "only using the RAID redundancy mechanisms. After completion of the",
98 "operation, the source device is removed from the filesystem.",
99 "If the <srcdev> is a numerical value, it is assumed to be the device id",
100 "of the filesystem which is mounted at <mount_point>, otherwise it is",
101 "the path to the source device. If the source device is disconnected,",
102 "from the system, you have to use the <devid> parameter format.",
103 "The <targetdev> needs to be same size or larger than the <srcdev>.",
105 "-r only read from <srcdev> if no other zero-defect mirror exists",
106 " (enable this if your drive has lots of read errors, the access",
107 " would be very slow)",
108 "-f force using and overwriting <targetdev> even if it looks like",
109 " containing a valid btrfs filesystem. A valid filesystem is",
110 " assumed if a btrfs superblock is found which contains a",
111 " correct checksum. Devices which are currently mounted are",
112 " never allowed to be used as the <targetdev>",
113 "-B do not background",
114 NULL
117 static int cmd_replace_start(int argc, char **argv)
119 struct btrfs_ioctl_dev_replace_args start_args = {0};
120 struct btrfs_ioctl_dev_replace_args status_args = {0};
121 int ret;
122 int i;
123 int c;
124 int fdmnt = -1;
125 int fddstdev = -1;
126 char *path;
127 char *srcdev;
128 char *dstdev = NULL;
129 int avoid_reading_from_srcdev = 0;
130 int force_using_targetdev = 0;
131 u64 dstdev_block_count;
132 int do_not_background = 0;
133 DIR *dirstream = NULL;
134 u64 srcdev_size;
135 u64 dstdev_size;
137 optind = 0;
138 while ((c = getopt(argc, argv, "Brf")) != -1) {
139 switch (c) {
140 case 'B':
141 do_not_background = 1;
142 break;
143 case 'r':
144 avoid_reading_from_srcdev = 1;
145 break;
146 case 'f':
147 force_using_targetdev = 1;
148 break;
149 case '?':
150 default:
151 usage(cmd_replace_start_usage);
155 start_args.start.cont_reading_from_srcdev_mode =
156 avoid_reading_from_srcdev ?
157 BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_AVOID :
158 BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_ALWAYS;
159 if (check_argc_exact(argc - optind, 3))
160 usage(cmd_replace_start_usage);
161 path = argv[optind + 2];
163 fdmnt = open_path_or_dev_mnt(path, &dirstream, 1);
164 if (fdmnt < 0)
165 goto leave_with_error;
167 /* check for possible errors before backgrounding */
168 status_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
169 status_args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
170 ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &status_args);
171 if (ret < 0) {
172 fprintf(stderr,
173 "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %m",
174 path);
175 if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
176 fprintf(stderr, ", %s\n",
177 replace_dev_result2string(status_args.result));
178 else
179 fprintf(stderr, "\n");
180 goto leave_with_error;
183 if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
184 error("ioctl(DEV_REPLACE_STATUS) on '%s' returns error: %s",
185 path, replace_dev_result2string(status_args.result));
186 goto leave_with_error;
189 if (status_args.status.replace_state ==
190 BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED) {
191 error("device replace on '%s' already started", path);
192 goto leave_with_error;
195 srcdev = argv[optind];
196 dstdev = canonicalize_path(argv[optind + 1]);
197 if (!dstdev) {
198 error("cannot canonicalize path '%s': %m",
199 argv[optind + 1]);
200 goto leave_with_error;
203 if (string_is_numerical(srcdev)) {
204 struct btrfs_ioctl_fs_info_args fi_args;
205 struct btrfs_ioctl_dev_info_args *di_args = NULL;
207 start_args.start.srcdevid = arg_strtou64(srcdev);
209 ret = get_fs_info(path, &fi_args, &di_args);
210 if (ret) {
211 error("failed to get device info: %s", strerror(-ret));
212 free(di_args);
213 goto leave_with_error;
215 if (!fi_args.num_devices) {
216 error("no devices found");
217 free(di_args);
218 goto leave_with_error;
221 for (i = 0; i < fi_args.num_devices; i++)
222 if (start_args.start.srcdevid == di_args[i].devid)
223 break;
224 srcdev_size = di_args[i].total_bytes;
225 free(di_args);
226 if (i == fi_args.num_devices) {
227 error("'%s' is not a valid devid for filesystem '%s'",
228 srcdev, path);
229 goto leave_with_error;
231 } else if (is_block_device(srcdev) > 0) {
232 strncpy((char *)start_args.start.srcdev_name, srcdev,
233 BTRFS_DEVICE_PATH_NAME_MAX);
234 start_args.start.srcdevid = 0;
235 srcdev_size = get_partition_size(srcdev);
236 } else {
237 error("source device must be a block device or a devid");
238 goto leave_with_error;
241 ret = test_dev_for_mkfs(dstdev, force_using_targetdev);
242 if (ret)
243 goto leave_with_error;
245 dstdev_size = get_partition_size(dstdev);
246 if (srcdev_size > dstdev_size) {
247 error("target device smaller than source device (required %llu bytes)",
248 srcdev_size);
249 goto leave_with_error;
252 fddstdev = open(dstdev, O_RDWR);
253 if (fddstdev < 0) {
254 error("unable to open %s: %m", dstdev);
255 goto leave_with_error;
257 strncpy((char *)start_args.start.tgtdev_name, dstdev,
258 BTRFS_DEVICE_PATH_NAME_MAX);
259 ret = btrfs_prepare_device(fddstdev, dstdev, &dstdev_block_count, 0,
260 PREP_DEVICE_ZERO_END | PREP_DEVICE_VERBOSE);
261 if (ret)
262 goto leave_with_error;
264 close(fddstdev);
265 fddstdev = -1;
266 free(dstdev);
267 dstdev = NULL;
269 dev_replace_handle_sigint(fdmnt);
270 if (!do_not_background) {
271 if (daemon(0, 0) < 0) {
272 error("backgrounding failed: %m");
273 goto leave_with_error;
277 start_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_START;
278 start_args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
279 ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &start_args);
280 if (do_not_background) {
281 if (ret < 0) {
282 fprintf(stderr,
283 "ERROR: ioctl(DEV_REPLACE_START) failed on \"%s\": %m",
284 path);
285 if (start_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
286 fprintf(stderr, ", %s\n",
287 replace_dev_result2string(start_args.result));
288 else
289 fprintf(stderr, "\n");
291 if (errno == EOPNOTSUPP)
292 warning("device replace of RAID5/6 not supported with this kernel");
294 goto leave_with_error;
297 if (start_args.result !=
298 BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
299 error("ioctl(DEV_REPLACE_START) on '%s' returns error: %s",
300 path,
301 replace_dev_result2string(start_args.result));
302 goto leave_with_error;
305 close_file_or_dir(fdmnt, dirstream);
306 return 0;
308 leave_with_error:
309 if (dstdev)
310 free(dstdev);
311 if (fdmnt != -1)
312 close(fdmnt);
313 if (fddstdev != -1)
314 close(fddstdev);
315 return 1;
318 static const char *const cmd_replace_status_usage[] = {
319 "btrfs replace status [-1] <mount_point>",
320 "Print status and progress information of a running device replace",
321 "operation",
323 "-1 print once instead of print continuously until the replace",
324 " operation finishes (or is canceled)",
325 NULL
328 static int cmd_replace_status(int argc, char **argv)
330 int fd;
331 int c;
332 char *path;
333 int once = 0;
334 int ret;
335 DIR *dirstream = NULL;
337 optind = 0;
338 while ((c = getopt(argc, argv, "1")) != -1) {
339 switch (c) {
340 case '1':
341 once = 1;
342 break;
343 case '?':
344 default:
345 usage(cmd_replace_status_usage);
349 if (check_argc_exact(argc - optind, 1))
350 usage(cmd_replace_status_usage);
352 path = argv[optind];
353 fd = btrfs_open_dir(path, &dirstream, 1);
354 if (fd < 0)
355 return 1;
357 ret = print_replace_status(fd, path, once);
358 close_file_or_dir(fd, dirstream);
359 return !!ret;
362 static int print_replace_status(int fd, const char *path, int once)
364 struct btrfs_ioctl_dev_replace_args args = {0};
365 struct btrfs_ioctl_dev_replace_status_params *status;
366 int ret;
367 int prevent_loop = 0;
368 int skip_stats;
369 int num_chars;
370 char string1[80];
371 char string2[80];
372 char string3[80];
374 for (;;) {
375 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
376 args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
377 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
378 if (ret < 0) {
379 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %m",
380 path);
381 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
382 fprintf(stderr, ", %s\n",
383 replace_dev_result2string(args.result));
384 else
385 fprintf(stderr, "\n");
386 return ret;
389 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
390 error("ioctl(DEV_REPLACE_STATUS) on '%s' returns error: %s",
391 path,
392 replace_dev_result2string(args.result));
393 return -1;
396 status = &args.status;
398 skip_stats = 0;
399 num_chars = 0;
400 switch (status->replace_state) {
401 case BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED:
402 num_chars =
403 printf("%s done",
404 progress2string(string3,
405 sizeof(string3),
406 status->progress_1000));
407 break;
408 case BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED:
409 prevent_loop = 1;
410 printf("Started on %s, finished on %s",
411 time2string(string1, sizeof(string1),
412 status->time_started),
413 time2string(string2, sizeof(string2),
414 status->time_stopped));
415 break;
416 case BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED:
417 prevent_loop = 1;
418 printf("Started on %s, canceled on %s at %s",
419 time2string(string1, sizeof(string1),
420 status->time_started),
421 time2string(string2, sizeof(string2),
422 status->time_stopped),
423 progress2string(string3, sizeof(string3),
424 status->progress_1000));
425 break;
426 case BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED:
427 prevent_loop = 1;
428 printf("Started on %s, suspended on %s at %s",
429 time2string(string1, sizeof(string1),
430 status->time_started),
431 time2string(string2, sizeof(string2),
432 status->time_stopped),
433 progress2string(string3, sizeof(string3),
434 status->progress_1000));
435 break;
436 case BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED:
437 prevent_loop = 1;
438 skip_stats = 1;
439 printf("Never started");
440 break;
441 default:
442 error("unknown status from ioctl DEV_REPLACE_STATUS on '%s': %llu",
443 path, status->replace_state);
444 return -EINVAL;
447 if (!skip_stats)
448 num_chars += printf(
449 ", %llu write errs, %llu uncorr. read errs",
450 (unsigned long long)status->num_write_errors,
451 (unsigned long long)
452 status->num_uncorrectable_read_errors);
453 if (once || prevent_loop) {
454 printf("\n");
455 break;
458 fflush(stdout);
459 sleep(1);
460 while (num_chars > 0) {
461 putchar('\b');
462 num_chars--;
466 return 0;
469 static char *
470 time2string(char *buf, size_t s, __u64 t)
472 struct tm t_tm;
473 time_t t_time_t;
475 t_time_t = (time_t)t;
476 assert((__u64)t_time_t == t);
477 localtime_r(&t_time_t, &t_tm);
478 strftime(buf, s, "%e.%b %T", &t_tm);
479 return buf;
482 static char *
483 progress2string(char *buf, size_t s, int progress_1000)
485 snprintf(buf, s, "%d.%01d%%", progress_1000 / 10, progress_1000 % 10);
486 assert(s > 0);
487 buf[s - 1] = '\0';
488 return buf;
491 static const char *const cmd_replace_cancel_usage[] = {
492 "btrfs replace cancel <mount_point>",
493 "Cancel a running device replace operation.",
494 NULL
497 static int cmd_replace_cancel(int argc, char **argv)
499 struct btrfs_ioctl_dev_replace_args args = {0};
500 int ret;
501 int c;
502 int fd;
503 char *path;
504 DIR *dirstream = NULL;
506 optind = 0;
507 while ((c = getopt(argc, argv, "")) != -1) {
508 switch (c) {
509 case '?':
510 default:
511 usage(cmd_replace_cancel_usage);
515 if (check_argc_exact(argc - optind, 1))
516 usage(cmd_replace_cancel_usage);
518 path = argv[optind];
519 fd = btrfs_open_dir(path, &dirstream, 1);
520 if (fd < 0)
521 return 1;
523 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
524 args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
525 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
526 close_file_or_dir(fd, dirstream);
527 if (ret < 0) {
528 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_CANCEL) failed on \"%s\": %m",
529 path);
530 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
531 fprintf(stderr, ", %s\n",
532 replace_dev_result2string(args.result));
533 else
534 fprintf(stderr, "\n");
535 return 1;
537 if (args.result == BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED) {
538 printf("INFO: ioctl(DEV_REPLACE_CANCEL)\"%s\": %s\n",
539 path, replace_dev_result2string(args.result));
540 return 2;
542 return 0;
545 static const char replace_cmd_group_info[] =
546 "replace a device in the filesystem";
548 const struct cmd_group replace_cmd_group = {
549 replace_cmd_group_usage, replace_cmd_group_info, {
550 { "start", cmd_replace_start, cmd_replace_start_usage, NULL,
551 0 },
552 { "status", cmd_replace_status, cmd_replace_status_usage, NULL,
553 0 },
554 { "cancel", cmd_replace_cancel, cmd_replace_cancel_usage, NULL,
555 0 },
556 NULL_CMD_STRUCT
560 int cmd_replace(int argc, char **argv)
562 return handle_command_group(&replace_cmd_group, argc, argv);