btrfs-progs: check: Remove ext_ref param from walk_down_tree
[btrfs-progs-unstable/devel.git] / cmds-replace.c
blob032a44fcda3c6620ac75235c84f5b659191124b4
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 while ((c = getopt(argc, argv, "Brf")) != -1) {
138 switch (c) {
139 case 'B':
140 do_not_background = 1;
141 break;
142 case 'r':
143 avoid_reading_from_srcdev = 1;
144 break;
145 case 'f':
146 force_using_targetdev = 1;
147 break;
148 case '?':
149 default:
150 usage(cmd_replace_start_usage);
154 start_args.start.cont_reading_from_srcdev_mode =
155 avoid_reading_from_srcdev ?
156 BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_AVOID :
157 BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_ALWAYS;
158 if (check_argc_exact(argc - optind, 3))
159 usage(cmd_replace_start_usage);
160 path = argv[optind + 2];
162 fdmnt = open_path_or_dev_mnt(path, &dirstream, 1);
163 if (fdmnt < 0)
164 goto leave_with_error;
166 /* check for possible errors before backgrounding */
167 status_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
168 status_args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
169 ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &status_args);
170 if (ret < 0) {
171 fprintf(stderr,
172 "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %m",
173 path);
174 if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
175 fprintf(stderr, ", %s\n",
176 replace_dev_result2string(status_args.result));
177 else
178 fprintf(stderr, "\n");
179 goto leave_with_error;
182 if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
183 error("ioctl(DEV_REPLACE_STATUS) on '%s' returns error: %s",
184 path, replace_dev_result2string(status_args.result));
185 goto leave_with_error;
188 if (status_args.status.replace_state ==
189 BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED) {
190 error("device replace on '%s' already started", path);
191 goto leave_with_error;
194 srcdev = argv[optind];
195 dstdev = canonicalize_path(argv[optind + 1]);
196 if (!dstdev) {
197 error("cannot canonicalize path '%s': %m",
198 argv[optind + 1]);
199 goto leave_with_error;
202 if (string_is_numerical(srcdev)) {
203 struct btrfs_ioctl_fs_info_args fi_args;
204 struct btrfs_ioctl_dev_info_args *di_args = NULL;
206 start_args.start.srcdevid = arg_strtou64(srcdev);
208 ret = get_fs_info(path, &fi_args, &di_args);
209 if (ret) {
210 error("failed to get device info: %s", strerror(-ret));
211 free(di_args);
212 goto leave_with_error;
214 if (!fi_args.num_devices) {
215 error("no devices found");
216 free(di_args);
217 goto leave_with_error;
220 for (i = 0; i < fi_args.num_devices; i++)
221 if (start_args.start.srcdevid == di_args[i].devid)
222 break;
223 srcdev_size = di_args[i].total_bytes;
224 free(di_args);
225 if (i == fi_args.num_devices) {
226 error("'%s' is not a valid devid for filesystem '%s'",
227 srcdev, path);
228 goto leave_with_error;
230 } else if (is_block_device(srcdev) > 0) {
231 strncpy((char *)start_args.start.srcdev_name, srcdev,
232 BTRFS_DEVICE_PATH_NAME_MAX);
233 start_args.start.srcdevid = 0;
234 srcdev_size = get_partition_size(srcdev);
235 } else {
236 error("source device must be a block device or a devid");
237 goto leave_with_error;
240 ret = test_dev_for_mkfs(dstdev, force_using_targetdev);
241 if (ret)
242 goto leave_with_error;
244 dstdev_size = get_partition_size(dstdev);
245 if (srcdev_size > dstdev_size) {
246 error("target device smaller than source device (required %llu bytes)",
247 srcdev_size);
248 goto leave_with_error;
251 fddstdev = open(dstdev, O_RDWR);
252 if (fddstdev < 0) {
253 error("unable to open %s: %m", dstdev);
254 goto leave_with_error;
256 strncpy((char *)start_args.start.tgtdev_name, dstdev,
257 BTRFS_DEVICE_PATH_NAME_MAX);
258 ret = btrfs_prepare_device(fddstdev, dstdev, &dstdev_block_count, 0,
259 PREP_DEVICE_ZERO_END | PREP_DEVICE_VERBOSE);
260 if (ret)
261 goto leave_with_error;
263 close(fddstdev);
264 fddstdev = -1;
265 free(dstdev);
266 dstdev = NULL;
268 dev_replace_handle_sigint(fdmnt);
269 if (!do_not_background) {
270 if (daemon(0, 0) < 0) {
271 error("backgrounding failed: %m");
272 goto leave_with_error;
276 start_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_START;
277 start_args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
278 ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &start_args);
279 if (do_not_background) {
280 if (ret < 0) {
281 fprintf(stderr,
282 "ERROR: ioctl(DEV_REPLACE_START) failed on \"%s\": %m",
283 path);
284 if (start_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
285 fprintf(stderr, ", %s\n",
286 replace_dev_result2string(start_args.result));
287 else
288 fprintf(stderr, "\n");
290 if (errno == EOPNOTSUPP)
291 warning("device replace of RAID5/6 not supported with this kernel");
293 goto leave_with_error;
296 if (start_args.result !=
297 BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
298 error("ioctl(DEV_REPLACE_START) on '%s' returns error: %s",
299 path,
300 replace_dev_result2string(start_args.result));
301 goto leave_with_error;
304 close_file_or_dir(fdmnt, dirstream);
305 return 0;
307 leave_with_error:
308 if (dstdev)
309 free(dstdev);
310 if (fdmnt != -1)
311 close(fdmnt);
312 if (fddstdev != -1)
313 close(fddstdev);
314 return 1;
317 static const char *const cmd_replace_status_usage[] = {
318 "btrfs replace status [-1] <mount_point>",
319 "Print status and progress information of a running device replace",
320 "operation",
322 "-1 print once instead of print continuously until the replace",
323 " operation finishes (or is canceled)",
324 NULL
327 static int cmd_replace_status(int argc, char **argv)
329 int fd;
330 int c;
331 char *path;
332 int once = 0;
333 int ret;
334 DIR *dirstream = NULL;
336 while ((c = getopt(argc, argv, "1")) != -1) {
337 switch (c) {
338 case '1':
339 once = 1;
340 break;
341 case '?':
342 default:
343 usage(cmd_replace_status_usage);
347 if (check_argc_exact(argc - optind, 1))
348 usage(cmd_replace_status_usage);
350 path = argv[optind];
351 fd = btrfs_open_dir(path, &dirstream, 1);
352 if (fd < 0)
353 return 1;
355 ret = print_replace_status(fd, path, once);
356 close_file_or_dir(fd, dirstream);
357 return !!ret;
360 static int print_replace_status(int fd, const char *path, int once)
362 struct btrfs_ioctl_dev_replace_args args = {0};
363 struct btrfs_ioctl_dev_replace_status_params *status;
364 int ret;
365 int prevent_loop = 0;
366 int skip_stats;
367 int num_chars;
368 char string1[80];
369 char string2[80];
370 char string3[80];
372 for (;;) {
373 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
374 args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
375 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
376 if (ret < 0) {
377 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %m",
378 path);
379 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
380 fprintf(stderr, ", %s\n",
381 replace_dev_result2string(args.result));
382 else
383 fprintf(stderr, "\n");
384 return ret;
387 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
388 error("ioctl(DEV_REPLACE_STATUS) on '%s' returns error: %s",
389 path,
390 replace_dev_result2string(args.result));
391 return -1;
394 status = &args.status;
396 skip_stats = 0;
397 num_chars = 0;
398 switch (status->replace_state) {
399 case BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED:
400 num_chars =
401 printf("%s done",
402 progress2string(string3,
403 sizeof(string3),
404 status->progress_1000));
405 break;
406 case BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED:
407 prevent_loop = 1;
408 printf("Started on %s, finished on %s",
409 time2string(string1, sizeof(string1),
410 status->time_started),
411 time2string(string2, sizeof(string2),
412 status->time_stopped));
413 break;
414 case BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED:
415 prevent_loop = 1;
416 printf("Started on %s, canceled on %s at %s",
417 time2string(string1, sizeof(string1),
418 status->time_started),
419 time2string(string2, sizeof(string2),
420 status->time_stopped),
421 progress2string(string3, sizeof(string3),
422 status->progress_1000));
423 break;
424 case BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED:
425 prevent_loop = 1;
426 printf("Started on %s, suspended on %s at %s",
427 time2string(string1, sizeof(string1),
428 status->time_started),
429 time2string(string2, sizeof(string2),
430 status->time_stopped),
431 progress2string(string3, sizeof(string3),
432 status->progress_1000));
433 break;
434 case BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED:
435 prevent_loop = 1;
436 skip_stats = 1;
437 printf("Never started");
438 break;
439 default:
440 error("unknown status from ioctl DEV_REPLACE_STATUS on '%s': %llu",
441 path, status->replace_state);
442 return -EINVAL;
445 if (!skip_stats)
446 num_chars += printf(
447 ", %llu write errs, %llu uncorr. read errs",
448 (unsigned long long)status->num_write_errors,
449 (unsigned long long)
450 status->num_uncorrectable_read_errors);
451 if (once || prevent_loop) {
452 printf("\n");
453 break;
456 fflush(stdout);
457 sleep(1);
458 while (num_chars > 0) {
459 putchar('\b');
460 num_chars--;
464 return 0;
467 static char *
468 time2string(char *buf, size_t s, __u64 t)
470 struct tm t_tm;
471 time_t t_time_t;
473 t_time_t = (time_t)t;
474 assert((__u64)t_time_t == t);
475 localtime_r(&t_time_t, &t_tm);
476 strftime(buf, s, "%e.%b %T", &t_tm);
477 return buf;
480 static char *
481 progress2string(char *buf, size_t s, int progress_1000)
483 snprintf(buf, s, "%d.%01d%%", progress_1000 / 10, progress_1000 % 10);
484 assert(s > 0);
485 buf[s - 1] = '\0';
486 return buf;
489 static const char *const cmd_replace_cancel_usage[] = {
490 "btrfs replace cancel <mount_point>",
491 "Cancel a running device replace operation.",
492 NULL
495 static int cmd_replace_cancel(int argc, char **argv)
497 struct btrfs_ioctl_dev_replace_args args = {0};
498 int ret;
499 int c;
500 int fd;
501 char *path;
502 DIR *dirstream = NULL;
504 while ((c = getopt(argc, argv, "")) != -1) {
505 switch (c) {
506 case '?':
507 default:
508 usage(cmd_replace_cancel_usage);
512 if (check_argc_exact(argc - optind, 1))
513 usage(cmd_replace_cancel_usage);
515 path = argv[optind];
516 fd = btrfs_open_dir(path, &dirstream, 1);
517 if (fd < 0)
518 return 1;
520 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
521 args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
522 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
523 close_file_or_dir(fd, dirstream);
524 if (ret < 0) {
525 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_CANCEL) failed on \"%s\": %m",
526 path);
527 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
528 fprintf(stderr, ", %s\n",
529 replace_dev_result2string(args.result));
530 else
531 fprintf(stderr, "\n");
532 return 1;
534 if (args.result == BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED) {
535 printf("INFO: ioctl(DEV_REPLACE_CANCEL)\"%s\": %s\n",
536 path, replace_dev_result2string(args.result));
537 return 2;
539 return 0;
542 static const char replace_cmd_group_info[] =
543 "replace a device in the filesystem";
545 const struct cmd_group replace_cmd_group = {
546 replace_cmd_group_usage, replace_cmd_group_info, {
547 { "start", cmd_replace_start, cmd_replace_start_usage, NULL,
548 0 },
549 { "status", cmd_replace_status, cmd_replace_status_usage, NULL,
550 0 },
551 { "cancel", cmd_replace_cancel, cmd_replace_cancel_usage, NULL,
552 0 },
553 NULL_CMD_STRUCT
557 int cmd_replace(int argc, char **argv)
559 return handle_command_group(&replace_cmd_group, argc, argv);