btrfs-progs: convert: Fix a bug leads to discontinuous extents
[btrfs-progs-unstable/devel.git] / cmds-replace.c
blob6036e2f91bdb6294a5582f82eeadb8da7f7546c3
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"
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)
49 switch (result) {
50 case BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR:
51 return "no error";
52 case BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED:
53 return "not started";
54 case BTRFS_IOCTL_DEV_REPLACE_RESULT_ALREADY_STARTED:
55 return "already started";
56 case BTRFS_IOCTL_DEV_REPLACE_RESULT_SCRUB_INPROGRESS:
57 return "scrub is in progress";
58 default:
59 return "<illegal result value>";
63 static const char * const replace_cmd_group_usage[] = {
64 "btrfs replace <command> [<args>]",
65 NULL
68 static int dev_replace_cancel_fd = -1;
69 static void dev_replace_sigint_handler(int signal)
71 int ret;
72 struct btrfs_ioctl_dev_replace_args args = {0};
74 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
75 ret = ioctl(dev_replace_cancel_fd, BTRFS_IOC_DEV_REPLACE, &args);
76 if (ret < 0)
77 perror("Device replace cancel failed");
80 static int dev_replace_handle_sigint(int fd)
82 struct sigaction sa = {
83 .sa_handler = fd == -1 ? SIG_DFL : dev_replace_sigint_handler
86 dev_replace_cancel_fd = fd;
87 return sigaction(SIGINT, &sa, NULL);
90 static const char *const cmd_replace_start_usage[] = {
91 "btrfs replace start [-Bfr] <srcdev>|<devid> <targetdev> <mount_point>",
92 "Replace device of a btrfs filesystem.",
93 "On a live filesystem, duplicate the data to the target device which",
94 "is currently stored on the source device. If the source device is not",
95 "available anymore, or if the -r option is set, the data is built",
96 "only using the RAID redundancy mechanisms. After completion of the",
97 "operation, the source device is removed from the filesystem.",
98 "If the <srcdev> is a numerical value, it is assumed to be the device id",
99 "of the filesystem which is mounted at <mount_point>, otherwise it is",
100 "the path to the source device. If the source device is disconnected,",
101 "from the system, you have to use the <devid> parameter format.",
102 "The <targetdev> needs to be same size or larger than the <srcdev>.",
104 "-r only read from <srcdev> if no other zero-defect mirror exists",
105 " (enable this if your drive has lots of read errors, the access",
106 " would be very slow)",
107 "-f force using and overwriting <targetdev> even if it looks like",
108 " containing a valid btrfs filesystem. A valid filesystem is",
109 " assumed if a btrfs superblock is found which contains a",
110 " correct checksum. Devices which are currently mounted are",
111 " never allowed to be used as the <targetdev>",
112 "-B do not background",
113 NULL
116 static int cmd_replace_start(int argc, char **argv)
118 struct btrfs_ioctl_dev_replace_args start_args = {0};
119 struct btrfs_ioctl_dev_replace_args status_args = {0};
120 int ret;
121 int i;
122 int c;
123 int fdmnt = -1;
124 int fddstdev = -1;
125 char *path;
126 char *srcdev;
127 char *dstdev = NULL;
128 int avoid_reading_from_srcdev = 0;
129 int force_using_targetdev = 0;
130 u64 dstdev_block_count;
131 int do_not_background = 0;
132 DIR *dirstream = NULL;
133 u64 srcdev_size;
134 u64 dstdev_size;
136 while ((c = getopt(argc, argv, "Brf")) != -1) {
137 switch (c) {
138 case 'B':
139 do_not_background = 1;
140 break;
141 case 'r':
142 avoid_reading_from_srcdev = 1;
143 break;
144 case 'f':
145 force_using_targetdev = 1;
146 break;
147 case '?':
148 default:
149 usage(cmd_replace_start_usage);
153 start_args.start.cont_reading_from_srcdev_mode =
154 avoid_reading_from_srcdev ?
155 BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_AVOID :
156 BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_ALWAYS;
157 if (check_argc_exact(argc - optind, 3))
158 usage(cmd_replace_start_usage);
159 path = argv[optind + 2];
161 fdmnt = open_path_or_dev_mnt(path, &dirstream, 1);
162 if (fdmnt < 0)
163 goto leave_with_error;
165 /* check for possible errors before backgrounding */
166 status_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
167 status_args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
168 ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &status_args);
169 if (ret < 0) {
170 fprintf(stderr,
171 "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s",
172 path, strerror(errno));
173 if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
174 fprintf(stderr, ", %s\n",
175 replace_dev_result2string(status_args.result));
176 else
177 fprintf(stderr, "\n");
178 goto leave_with_error;
181 if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
182 error("ioctl(DEV_REPLACE_STATUS) on '%s' returns error: %s",
183 path, replace_dev_result2string(status_args.result));
184 goto leave_with_error;
187 if (status_args.status.replace_state ==
188 BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED) {
189 error("device replace on '%s' already started", path);
190 goto leave_with_error;
193 srcdev = argv[optind];
194 dstdev = canonicalize_path(argv[optind + 1]);
195 if (!dstdev) {
196 error("cannot canonicalize path '%s': %s",
197 argv[optind + 1], strerror(errno));
198 goto leave_with_error;
201 if (string_is_numerical(srcdev)) {
202 struct btrfs_ioctl_fs_info_args fi_args;
203 struct btrfs_ioctl_dev_info_args *di_args = NULL;
205 start_args.start.srcdevid = arg_strtou64(srcdev);
207 ret = get_fs_info(path, &fi_args, &di_args);
208 if (ret) {
209 error("failed to get device info: %s", strerror(-ret));
210 free(di_args);
211 goto leave_with_error;
213 if (!fi_args.num_devices) {
214 error("no devices found");
215 free(di_args);
216 goto leave_with_error;
219 for (i = 0; i < fi_args.num_devices; i++)
220 if (start_args.start.srcdevid == di_args[i].devid)
221 break;
222 srcdev_size = di_args[i].total_bytes;
223 free(di_args);
224 if (i == fi_args.num_devices) {
225 error("'%s' is not a valid devid for filesystem '%s'",
226 srcdev, path);
227 goto leave_with_error;
229 } else if (is_block_device(srcdev) > 0) {
230 strncpy((char *)start_args.start.srcdev_name, srcdev,
231 BTRFS_DEVICE_PATH_NAME_MAX);
232 start_args.start.srcdevid = 0;
233 srcdev_size = get_partition_size(srcdev);
234 } else {
235 error("source device must be a block device or a devid");
236 goto leave_with_error;
239 ret = test_dev_for_mkfs(dstdev, force_using_targetdev);
240 if (ret)
241 goto leave_with_error;
243 dstdev_size = get_partition_size(dstdev);
244 if (srcdev_size > dstdev_size) {
245 error("target device smaller than source device (required %llu bytes)",
246 srcdev_size);
247 goto leave_with_error;
250 fddstdev = open(dstdev, O_RDWR);
251 if (fddstdev < 0) {
252 error("unable to open %s: %s", dstdev, strerror(errno));
253 goto leave_with_error;
255 strncpy((char *)start_args.start.tgtdev_name, dstdev,
256 BTRFS_DEVICE_PATH_NAME_MAX);
257 ret = btrfs_prepare_device(fddstdev, dstdev, 1, &dstdev_block_count, 0,
259 if (ret)
260 goto leave_with_error;
262 close(fddstdev);
263 fddstdev = -1;
264 free(dstdev);
265 dstdev = NULL;
267 dev_replace_handle_sigint(fdmnt);
268 if (!do_not_background) {
269 if (daemon(0, 0) < 0) {
270 error("backgrounding failed: %s", strerror(errno));
271 goto leave_with_error;
275 start_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_START;
276 start_args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
277 ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &start_args);
278 if (do_not_background) {
279 if (ret < 0) {
280 fprintf(stderr,
281 "ERROR: ioctl(DEV_REPLACE_START) failed on \"%s\": %s",
282 path, strerror(errno));
283 if (start_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
284 fprintf(stderr, ", %s\n",
285 replace_dev_result2string(start_args.result));
286 else
287 fprintf(stderr, "\n");
289 if (errno == EOPNOTSUPP)
290 warning("device replace of RAID5/6 not supported with this kernel");
292 goto leave_with_error;
295 if (start_args.result !=
296 BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
297 error("ioctl(DEV_REPLACE_START) on '%s' returns error: %s",
298 path,
299 replace_dev_result2string(start_args.result));
300 goto leave_with_error;
303 close_file_or_dir(fdmnt, dirstream);
304 return 0;
306 leave_with_error:
307 if (dstdev)
308 free(dstdev);
309 if (fdmnt != -1)
310 close(fdmnt);
311 if (fddstdev != -1)
312 close(fddstdev);
313 return 1;
316 static const char *const cmd_replace_status_usage[] = {
317 "btrfs replace status [-1] <mount_point>",
318 "Print status and progress information of a running device replace",
319 "operation",
321 "-1 print once instead of print continuously until the replace",
322 " operation finishes (or is canceled)",
323 NULL
326 static int cmd_replace_status(int argc, char **argv)
328 int fd;
329 int c;
330 char *path;
331 int once = 0;
332 int ret;
333 DIR *dirstream = NULL;
335 while ((c = getopt(argc, argv, "1")) != -1) {
336 switch (c) {
337 case '1':
338 once = 1;
339 break;
340 case '?':
341 default:
342 usage(cmd_replace_status_usage);
346 if (check_argc_exact(argc - optind, 1))
347 usage(cmd_replace_status_usage);
349 path = argv[optind];
350 fd = btrfs_open_dir(path, &dirstream, 1);
351 if (fd < 0)
352 return 1;
354 ret = print_replace_status(fd, path, once);
355 close_file_or_dir(fd, dirstream);
356 return !!ret;
359 static int print_replace_status(int fd, const char *path, int once)
361 struct btrfs_ioctl_dev_replace_args args = {0};
362 struct btrfs_ioctl_dev_replace_status_params *status;
363 int ret;
364 int prevent_loop = 0;
365 int skip_stats;
366 int num_chars;
367 char string1[80];
368 char string2[80];
369 char string3[80];
371 for (;;) {
372 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
373 args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
374 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
375 if (ret < 0) {
376 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s",
377 path, strerror(errno));
378 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
379 fprintf(stderr, ", %s\n",
380 replace_dev_result2string(args.result));
381 else
382 fprintf(stderr, "\n");
383 return ret;
386 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
387 error("ioctl(DEV_REPLACE_STATUS) on '%s' returns error: %s",
388 path,
389 replace_dev_result2string(args.result));
390 return -1;
393 status = &args.status;
395 skip_stats = 0;
396 num_chars = 0;
397 switch (status->replace_state) {
398 case BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED:
399 num_chars =
400 printf("%s done",
401 progress2string(string3,
402 sizeof(string3),
403 status->progress_1000));
404 break;
405 case BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED:
406 prevent_loop = 1;
407 printf("Started on %s, finished on %s",
408 time2string(string1, sizeof(string1),
409 status->time_started),
410 time2string(string2, sizeof(string2),
411 status->time_stopped));
412 break;
413 case BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED:
414 prevent_loop = 1;
415 printf("Started on %s, canceled on %s at %s",
416 time2string(string1, sizeof(string1),
417 status->time_started),
418 time2string(string2, sizeof(string2),
419 status->time_stopped),
420 progress2string(string3, sizeof(string3),
421 status->progress_1000));
422 break;
423 case BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED:
424 prevent_loop = 1;
425 printf("Started on %s, suspended on %s at %s",
426 time2string(string1, sizeof(string1),
427 status->time_started),
428 time2string(string2, sizeof(string2),
429 status->time_stopped),
430 progress2string(string3, sizeof(string3),
431 status->progress_1000));
432 break;
433 case BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED:
434 prevent_loop = 1;
435 skip_stats = 1;
436 printf("Never started");
437 break;
438 default:
439 error("unknown status from ioctl DEV_REPLACE_STATUS on '%s': %llu\n",
440 path, status->replace_state);
441 return -EINVAL;
444 if (!skip_stats)
445 num_chars += printf(
446 ", %llu write errs, %llu uncorr. read errs",
447 (unsigned long long)status->num_write_errors,
448 (unsigned long long)
449 status->num_uncorrectable_read_errors);
450 if (once || prevent_loop) {
451 printf("\n");
452 break;
455 fflush(stdout);
456 sleep(1);
457 while (num_chars > 0) {
458 putchar('\b');
459 num_chars--;
463 return 0;
466 static char *
467 time2string(char *buf, size_t s, __u64 t)
469 struct tm t_tm;
470 time_t t_time_t;
472 t_time_t = (time_t)t;
473 assert((__u64)t_time_t == t);
474 localtime_r(&t_time_t, &t_tm);
475 strftime(buf, s, "%e.%b %T", &t_tm);
476 return buf;
479 static char *
480 progress2string(char *buf, size_t s, int progress_1000)
482 snprintf(buf, s, "%d.%01d%%", progress_1000 / 10, progress_1000 % 10);
483 assert(s > 0);
484 buf[s - 1] = '\0';
485 return buf;
488 static const char *const cmd_replace_cancel_usage[] = {
489 "btrfs replace cancel <mount_point>",
490 "Cancel a running device replace operation.",
491 NULL
494 static int cmd_replace_cancel(int argc, char **argv)
496 struct btrfs_ioctl_dev_replace_args args = {0};
497 int ret;
498 int c;
499 int fd;
500 int e;
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 e = errno;
524 close_file_or_dir(fd, dirstream);
525 if (ret < 0) {
526 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_CANCEL) failed on \"%s\": %s",
527 path, strerror(e));
528 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
529 fprintf(stderr, ", %s\n",
530 replace_dev_result2string(args.result));
531 else
532 fprintf(stderr, "\n");
533 return 1;
535 if (args.result == BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED) {
536 printf("INFO: ioctl(DEV_REPLACE_CANCEL)\"%s\": %s\n",
537 path, replace_dev_result2string(args.result));
538 return 2;
540 return 0;
543 static const char replace_cmd_group_info[] =
544 "replace a device in the filesystem";
546 const struct cmd_group replace_cmd_group = {
547 replace_cmd_group_usage, replace_cmd_group_info, {
548 { "start", cmd_replace_start, cmd_replace_start_usage, NULL,
549 0 },
550 { "status", cmd_replace_status, cmd_replace_status_usage, NULL,
551 0 },
552 { "cancel", cmd_replace_cancel, cmd_replace_cancel_usage, NULL,
553 0 },
554 NULL_CMD_STRUCT
558 int cmd_replace(int argc, char **argv)
560 return handle_command_group(&replace_cmd_group, argc, argv);