btrfs-progs: tests: add 010-convert-delete-ext2-subvol
[btrfs-progs-unstable/devel.git] / cmds-replace.c
blob4e0e9472f8d61124eb6f78a6c9ac41628085b2f5
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) {
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 fprintf(stderr,
183 "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
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 fprintf(stderr,
191 "ERROR: btrfs replace on \"%s\" already started!\n",
192 path);
193 goto leave_with_error;
196 srcdev = argv[optind];
197 dstdev = canonicalize_path(argv[optind + 1]);
198 if (!dstdev) {
199 fprintf(stderr,
200 "ERROR: Could not canonicalize path '%s': %s\n",
201 argv[optind + 1], strerror(errno));
202 goto leave_with_error;
205 if (string_is_numerical(srcdev)) {
206 struct btrfs_ioctl_fs_info_args fi_args;
207 struct btrfs_ioctl_dev_info_args *di_args = NULL;
209 start_args.start.srcdevid = arg_strtou64(srcdev);
211 ret = get_fs_info(path, &fi_args, &di_args);
212 if (ret) {
213 fprintf(stderr, "ERROR: getting dev info for devstats failed: "
214 "%s\n", strerror(-ret));
215 free(di_args);
216 goto leave_with_error;
218 if (!fi_args.num_devices) {
219 fprintf(stderr, "ERROR: no devices found\n");
220 free(di_args);
221 goto leave_with_error;
224 for (i = 0; i < fi_args.num_devices; i++)
225 if (start_args.start.srcdevid == di_args[i].devid)
226 break;
227 srcdev_size = di_args[i].total_bytes;
228 free(di_args);
229 if (i == fi_args.num_devices) {
230 fprintf(stderr, "Error: '%s' is not a valid devid for filesystem '%s'\n",
231 srcdev, path);
232 goto leave_with_error;
234 } else if (is_block_device(srcdev) > 0) {
235 strncpy((char *)start_args.start.srcdev_name, srcdev,
236 BTRFS_DEVICE_PATH_NAME_MAX);
237 start_args.start.srcdevid = 0;
238 srcdev_size = get_partition_size(srcdev);
239 } else {
240 fprintf(stderr, "ERROR: source device must be a block device or a devid\n");
241 goto leave_with_error;
244 ret = test_dev_for_mkfs(dstdev, force_using_targetdev);
245 if (ret)
246 goto leave_with_error;
248 dstdev_size = get_partition_size(dstdev);
249 if (srcdev_size > dstdev_size) {
250 fprintf(stderr, "ERROR: target device smaller than source device (required %llu bytes)\n",
251 srcdev_size);
252 goto leave_with_error;
255 fddstdev = open(dstdev, O_RDWR);
256 if (fddstdev < 0) {
257 fprintf(stderr, "Unable to open %s\n", dstdev);
258 goto leave_with_error;
260 strncpy((char *)start_args.start.tgtdev_name, dstdev,
261 BTRFS_DEVICE_PATH_NAME_MAX);
262 ret = btrfs_prepare_device(fddstdev, dstdev, 1, &dstdev_block_count, 0,
264 if (ret)
265 goto leave_with_error;
267 close(fddstdev);
268 fddstdev = -1;
269 free(dstdev);
270 dstdev = NULL;
272 dev_replace_handle_sigint(fdmnt);
273 if (!do_not_background) {
274 if (daemon(0, 0) < 0) {
275 fprintf(stderr, "ERROR, backgrounding failed: %s\n",
276 strerror(errno));
277 goto leave_with_error;
281 start_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_START;
282 start_args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
283 ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &start_args);
284 if (do_not_background) {
285 if (ret) {
286 fprintf(stderr,
287 "ERROR: ioctl(DEV_REPLACE_START) failed on \"%s\": %s",
288 path, strerror(errno));
289 if (start_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
290 fprintf(stderr, ", %s\n",
291 replace_dev_result2string(start_args.result));
292 else
293 fprintf(stderr, "\n");
295 if (errno == EOPNOTSUPP)
296 fprintf(stderr,
297 "WARNING: dev_replace does not yet handle RAID5/6\n");
299 goto leave_with_error;
302 if (start_args.result !=
303 BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
304 fprintf(stderr,
305 "ERROR: ioctl(DEV_REPLACE_START) on \"%s\" returns error: %s\n",
306 path,
307 replace_dev_result2string(start_args.result));
308 goto leave_with_error;
311 close_file_or_dir(fdmnt, dirstream);
312 btrfs_close_all_devices();
313 return 0;
315 leave_with_error:
316 if (dstdev)
317 free(dstdev);
318 if (fdmnt != -1)
319 close(fdmnt);
320 if (fddstdev != -1)
321 close(fddstdev);
322 btrfs_close_all_devices();
323 return 1;
326 static const char *const cmd_replace_status_usage[] = {
327 "btrfs replace status [-1] <mount_point>",
328 "Print status and progress information of a running device replace",
329 "operation",
331 "-1 print once instead of print continuously until the replace",
332 " operation finishes (or is canceled)",
333 NULL
336 static int cmd_replace_status(int argc, char **argv)
338 int fd;
339 int c;
340 char *path;
341 int once = 0;
342 int ret;
343 DIR *dirstream = NULL;
345 while ((c = getopt(argc, argv, "1")) != -1) {
346 switch (c) {
347 case '1':
348 once = 1;
349 break;
350 case '?':
351 default:
352 usage(cmd_replace_status_usage);
356 if (check_argc_exact(argc - optind, 1))
357 usage(cmd_replace_status_usage);
359 path = argv[optind];
360 fd = btrfs_open_dir(path, &dirstream, 1);
361 if (fd < 0)
362 return 1;
364 ret = print_replace_status(fd, path, once);
365 close_file_or_dir(fd, dirstream);
366 return !!ret;
369 static int print_replace_status(int fd, const char *path, int once)
371 struct btrfs_ioctl_dev_replace_args args = {0};
372 struct btrfs_ioctl_dev_replace_status_params *status;
373 int ret;
374 int prevent_loop = 0;
375 int skip_stats;
376 int num_chars;
377 char string1[80];
378 char string2[80];
379 char string3[80];
381 for (;;) {
382 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
383 args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
384 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
385 if (ret) {
386 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s",
387 path, strerror(errno));
388 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
389 fprintf(stderr, ", %s\n",
390 replace_dev_result2string(args.result));
391 else
392 fprintf(stderr, "\n");
393 return ret;
396 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
397 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
398 path,
399 replace_dev_result2string(args.result));
400 return -1;
403 status = &args.status;
405 skip_stats = 0;
406 num_chars = 0;
407 switch (status->replace_state) {
408 case BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED:
409 num_chars =
410 printf("%s done",
411 progress2string(string3,
412 sizeof(string3),
413 status->progress_1000));
414 break;
415 case BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED:
416 prevent_loop = 1;
417 printf("Started on %s, finished on %s",
418 time2string(string1, sizeof(string1),
419 status->time_started),
420 time2string(string2, sizeof(string2),
421 status->time_stopped));
422 break;
423 case BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED:
424 prevent_loop = 1;
425 printf("Started on %s, canceled 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_SUSPENDED:
434 prevent_loop = 1;
435 printf("Started on %s, suspended on %s at %s",
436 time2string(string1, sizeof(string1),
437 status->time_started),
438 time2string(string2, sizeof(string2),
439 status->time_stopped),
440 progress2string(string3, sizeof(string3),
441 status->progress_1000));
442 break;
443 case BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED:
444 prevent_loop = 1;
445 skip_stats = 1;
446 printf("Never started");
447 break;
448 default:
449 fprintf(stderr,
450 "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" got unknown status: %llu\n",
451 path, status->replace_state);
452 return -EINVAL;
455 if (!skip_stats)
456 num_chars += printf(
457 ", %llu write errs, %llu uncorr. read errs",
458 (unsigned long long)status->num_write_errors,
459 (unsigned long long)
460 status->num_uncorrectable_read_errors);
461 if (once || prevent_loop) {
462 printf("\n");
463 break;
466 fflush(stdout);
467 sleep(1);
468 while (num_chars > 0) {
469 putchar('\b');
470 num_chars--;
474 return 0;
477 static char *
478 time2string(char *buf, size_t s, __u64 t)
480 struct tm t_tm;
481 time_t t_time_t;
483 t_time_t = (time_t)t;
484 assert((__u64)t_time_t == t);
485 localtime_r(&t_time_t, &t_tm);
486 strftime(buf, s, "%e.%b %T", &t_tm);
487 return buf;
490 static char *
491 progress2string(char *buf, size_t s, int progress_1000)
493 snprintf(buf, s, "%d.%01d%%", progress_1000 / 10, progress_1000 % 10);
494 assert(s > 0);
495 buf[s - 1] = '\0';
496 return buf;
499 static const char *const cmd_replace_cancel_usage[] = {
500 "btrfs replace cancel <mount_point>",
501 "Cancel a running device replace operation.",
502 NULL
505 static int cmd_replace_cancel(int argc, char **argv)
507 struct btrfs_ioctl_dev_replace_args args = {0};
508 int ret;
509 int c;
510 int fd;
511 int e;
512 char *path;
513 DIR *dirstream = NULL;
515 while ((c = getopt(argc, argv, "")) != -1) {
516 switch (c) {
517 case '?':
518 default:
519 usage(cmd_replace_cancel_usage);
523 if (check_argc_exact(argc - optind, 1))
524 usage(cmd_replace_cancel_usage);
526 path = argv[optind];
527 fd = btrfs_open_dir(path, &dirstream, 1);
528 if (fd < 0)
529 return 1;
531 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
532 args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
533 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
534 e = errno;
535 close_file_or_dir(fd, dirstream);
536 if (ret) {
537 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_CANCEL) failed on \"%s\": %s",
538 path, strerror(e));
539 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
540 fprintf(stderr, ", %s\n",
541 replace_dev_result2string(args.result));
542 else
543 fprintf(stderr, "\n");
544 return 1;
546 if (args.result == BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED) {
547 printf("INFO: ioctl(DEV_REPLACE_CANCEL)\"%s\": %s\n",
548 path, replace_dev_result2string(args.result));
549 return 2;
551 return 0;
554 static const char replace_cmd_group_info[] =
555 "replace a device in the filesystem";
557 const struct cmd_group replace_cmd_group = {
558 replace_cmd_group_usage, replace_cmd_group_info, {
559 { "start", cmd_replace_start, cmd_replace_start_usage, NULL,
560 0 },
561 { "status", cmd_replace_status, cmd_replace_status_usage, NULL,
562 0 },
563 { "cancel", cmd_replace_cancel, cmd_replace_cancel_usage, NULL,
564 0 },
565 NULL_CMD_STRUCT
569 int cmd_replace(int argc, char **argv)
571 return handle_command_group(&replace_cmd_group, argc, argv);