btrfs-progs: canonicalize pathnames for device commands
[btrfs-progs-unstable/devel.git] / cmds-replace.c
blob186a127fc1ee8005a58e8af1d8758b9a2655c436
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 default:
57 return "<illegal result value>";
61 static const char * const replace_cmd_group_usage[] = {
62 "btrfs replace <command> [<args>]",
63 NULL
66 static int is_numerical(const char *str)
68 if (!(*str >= '0' && *str <= '9'))
69 return 0;
70 while (*str >= '0' && *str <= '9')
71 str++;
72 if (*str != '\0')
73 return 0;
74 return 1;
77 static int dev_replace_cancel_fd = -1;
78 static void dev_replace_sigint_handler(int signal)
80 int ret;
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);
85 if (ret < 0)
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 [-Bfr] <srcdev>|<devid> <targetdev> <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",
122 NULL
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};
129 int ret;
130 int i;
131 int c;
132 int fdmnt = -1;
133 int fdsrcdev = -1;
134 int fddstdev = -1;
135 char *path;
136 char *srcdev;
137 char *dstdev = NULL;
138 int avoid_reading_from_srcdev = 0;
139 int force_using_targetdev = 0;
140 struct stat st;
141 u64 dstdev_block_count;
142 int do_not_background = 0;
143 int mixed = 0;
144 DIR *dirstream = NULL;
145 char estr[100]; /* check test_dev_for_mkfs() for error string size*/
147 while ((c = getopt(argc, argv, "Brf")) != -1) {
148 switch (c) {
149 case 'B':
150 do_not_background = 1;
151 break;
152 case 'r':
153 avoid_reading_from_srcdev = 1;
154 break;
155 case 'f':
156 force_using_targetdev = 1;
157 break;
158 case '?':
159 default:
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];
172 fdmnt = open_path_or_dev_mnt(path, &dirstream);
174 if (fdmnt < 0) {
175 if (errno == EINVAL)
176 fprintf(stderr,
177 "ERROR: '%s' is not a mounted btrfs device\n",
178 path);
179 else
180 fprintf(stderr, "ERROR: can't access '%s': %s\n",
181 path, strerror(errno));
182 goto leave_with_error;
185 /* check for possible errors before backgrounding */
186 status_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
187 ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &status_args);
188 if (ret) {
189 fprintf(stderr,
190 "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s, %s\n",
191 path, strerror(errno),
192 replace_dev_result2string(status_args.result));
193 goto leave_with_error;
196 if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
197 fprintf(stderr,
198 "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
199 path, replace_dev_result2string(status_args.result));
200 goto leave_with_error;
203 if (status_args.status.replace_state ==
204 BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED) {
205 fprintf(stderr,
206 "ERROR: btrfs replace on \"%s\" already started!\n",
207 path);
208 goto leave_with_error;
211 srcdev = argv[optind];
212 dstdev = canonicalize_path(argv[optind + 1]);
213 if (!dstdev) {
214 fprintf(stderr,
215 "ERROR: Could not canonicalize path '%s': %s\n",
216 argv[optind + 1], strerror(errno));
219 if (is_numerical(srcdev)) {
220 struct btrfs_ioctl_fs_info_args fi_args;
221 struct btrfs_ioctl_dev_info_args *di_args = NULL;
223 start_args.start.srcdevid = arg_strtou64(srcdev);
225 ret = get_fs_info(path, &fi_args, &di_args);
226 if (ret) {
227 fprintf(stderr, "ERROR: getting dev info for devstats failed: "
228 "%s\n", strerror(-ret));
229 free(di_args);
230 goto leave_with_error;
232 if (!fi_args.num_devices) {
233 fprintf(stderr, "ERROR: no devices found\n");
234 free(di_args);
235 goto leave_with_error;
238 for (i = 0; i < fi_args.num_devices; i++)
239 if (start_args.start.srcdevid == di_args[i].devid)
240 break;
241 free(di_args);
242 if (i == fi_args.num_devices) {
243 fprintf(stderr, "Error: '%s' is not a valid devid for filesystem '%s'\n",
244 srcdev, path);
245 goto leave_with_error;
247 } else {
248 fdsrcdev = open(srcdev, O_RDWR);
249 if (fdsrcdev < 0) {
250 fprintf(stderr, "Error: Unable to open device '%s'\n",
251 srcdev);
252 fprintf(stderr, "\tTry using the devid instead of the path\n");
253 goto leave_with_error;
255 ret = fstat(fdsrcdev, &st);
256 if (ret) {
257 fprintf(stderr, "Error: Unable to stat '%s'\n", srcdev);
258 goto leave_with_error;
260 if (!S_ISBLK(st.st_mode)) {
261 fprintf(stderr, "Error: '%s' is not a block device\n",
262 srcdev);
263 goto leave_with_error;
265 strncpy((char *)start_args.start.srcdev_name, srcdev,
266 BTRFS_DEVICE_PATH_NAME_MAX);
267 close(fdsrcdev);
268 fdsrcdev = -1;
269 start_args.start.srcdevid = 0;
272 ret = test_dev_for_mkfs(dstdev, force_using_targetdev, estr);
273 if (ret) {
274 fprintf(stderr, "%s", estr);
275 goto leave_with_error;
277 fddstdev = open(dstdev, O_RDWR);
278 if (fddstdev < 0) {
279 fprintf(stderr, "Unable to open %s\n", dstdev);
280 goto leave_with_error;
282 strncpy((char *)start_args.start.tgtdev_name, dstdev,
283 BTRFS_DEVICE_PATH_NAME_MAX);
284 ret = btrfs_prepare_device(fddstdev, dstdev, 1, &dstdev_block_count, 0,
285 &mixed, 0);
286 if (ret)
287 goto leave_with_error;
289 close(fddstdev);
290 fddstdev = -1;
291 free(dstdev);
292 dstdev = NULL;
294 dev_replace_handle_sigint(fdmnt);
295 if (!do_not_background) {
296 if (daemon(0, 0) < 0) {
297 fprintf(stderr, "ERROR, backgrounding failed: %s\n",
298 strerror(errno));
299 goto leave_with_error;
303 start_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_START;
304 ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &start_args);
305 if (do_not_background) {
306 if (ret) {
307 fprintf(stderr,
308 "ERROR: ioctl(DEV_REPLACE_START) failed on \"%s\": %s, %s\n",
309 path, strerror(errno),
310 replace_dev_result2string(start_args.result));
311 goto leave_with_error;
314 if (start_args.result !=
315 BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
316 fprintf(stderr,
317 "ERROR: ioctl(DEV_REPLACE_START) on \"%s\" returns error: %s\n",
318 path,
319 replace_dev_result2string(start_args.result));
320 goto leave_with_error;
323 close_file_or_dir(fdmnt, dirstream);
324 return 0;
326 leave_with_error:
327 if (dstdev)
328 free(dstdev);
329 if (fdmnt != -1)
330 close(fdmnt);
331 if (fdsrcdev != -1)
332 close(fdsrcdev);
333 if (fddstdev != -1)
334 close(fddstdev);
335 return 1;
338 static const char *const cmd_status_replace_usage[] = {
339 "btrfs replace status [-1] <mount_point>",
340 "Print status and progress information of a running device replace",
341 "operation",
343 "-1 print once instead of print continuously until the replace",
344 " operation finishes (or is canceled)",
345 NULL
348 static int cmd_status_replace(int argc, char **argv)
350 int fd;
351 int e;
352 int c;
353 char *path;
354 int once = 0;
355 int ret;
356 DIR *dirstream = NULL;
358 while ((c = getopt(argc, argv, "1")) != -1) {
359 switch (c) {
360 case '1':
361 once = 1;
362 break;
363 case '?':
364 default:
365 usage(cmd_status_replace_usage);
369 if (check_argc_exact(argc - optind, 1))
370 usage(cmd_status_replace_usage);
372 path = argv[optind];
373 fd = open_file_or_dir(path, &dirstream);
374 e = errno;
375 if (fd < 0) {
376 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
377 path, strerror(e));
378 return 1;
381 ret = print_replace_status(fd, path, once);
382 close_file_or_dir(fd, dirstream);
383 return !!ret;
386 static int print_replace_status(int fd, const char *path, int once)
388 struct btrfs_ioctl_dev_replace_args args = {0};
389 struct btrfs_ioctl_dev_replace_status_params *status;
390 int ret;
391 int prevent_loop = 0;
392 int skip_stats;
393 int num_chars;
394 char string1[80];
395 char string2[80];
396 char string3[80];
398 for (;;) {
399 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
400 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
401 if (ret) {
402 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s, %s\n",
403 path, strerror(errno),
404 replace_dev_result2string(args.result));
405 return ret;
408 status = &args.status;
409 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
410 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
411 path,
412 replace_dev_result2string(args.result));
413 return -1;
416 skip_stats = 0;
417 num_chars = 0;
418 switch (status->replace_state) {
419 case BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED:
420 num_chars =
421 printf("%s done",
422 progress2string(string3,
423 sizeof(string3),
424 status->progress_1000));
425 break;
426 case BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED:
427 prevent_loop = 1;
428 printf("Started on %s, finished on %s",
429 time2string(string1, sizeof(string1),
430 status->time_started),
431 time2string(string2, sizeof(string2),
432 status->time_stopped));
433 break;
434 case BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED:
435 prevent_loop = 1;
436 printf("Started on %s, canceled on %s at %s",
437 time2string(string1, sizeof(string1),
438 status->time_started),
439 time2string(string2, sizeof(string2),
440 status->time_stopped),
441 progress2string(string3, sizeof(string3),
442 status->progress_1000));
443 break;
444 case BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED:
445 prevent_loop = 1;
446 printf("Started on %s, suspended on %s at %s",
447 time2string(string1, sizeof(string1),
448 status->time_started),
449 time2string(string2, sizeof(string2),
450 status->time_stopped),
451 progress2string(string3, sizeof(string3),
452 status->progress_1000));
453 break;
454 case BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED:
455 prevent_loop = 1;
456 skip_stats = 1;
457 printf("Never started");
458 break;
459 default:
460 prevent_loop = 1;
461 fprintf(stderr,
462 "Unknown btrfs dev replace status:%llu",
463 status->replace_state);
464 ret = -EINVAL;
465 break;
468 if (!skip_stats)
469 num_chars += printf(
470 ", %llu write errs, %llu uncorr. read errs",
471 (unsigned long long)status->num_write_errors,
472 (unsigned long long)
473 status->num_uncorrectable_read_errors);
474 if (once || prevent_loop || ret) {
475 printf("\n");
476 return ret;
479 fflush(stdout);
480 sleep(1);
481 while (num_chars > 0) {
482 putchar('\b');
483 num_chars--;
487 return 0;
490 static char *
491 time2string(char *buf, size_t s, __u64 t)
493 struct tm t_tm;
494 time_t t_time_t;
496 t_time_t = (time_t)t;
497 assert((__u64)t_time_t == t);
498 localtime_r(&t_time_t, &t_tm);
499 strftime(buf, s, "%e.%b %T", &t_tm);
500 return buf;
503 static char *
504 progress2string(char *buf, size_t s, int progress_1000)
506 snprintf(buf, s, "%d.%01d%%", progress_1000 / 10, progress_1000 % 10);
507 assert(s > 0);
508 buf[s - 1] = '\0';
509 return buf;
512 static const char *const cmd_cancel_replace_usage[] = {
513 "btrfs replace cancel <mount_point>",
514 "Cancel a running device replace operation.",
515 NULL
518 static int cmd_cancel_replace(int argc, char **argv)
520 struct btrfs_ioctl_dev_replace_args args = {0};
521 int ret;
522 int c;
523 int fd;
524 int e;
525 char *path;
526 DIR *dirstream = NULL;
528 while ((c = getopt(argc, argv, "")) != -1) {
529 switch (c) {
530 case '?':
531 default:
532 usage(cmd_cancel_replace_usage);
536 if (check_argc_exact(argc - optind, 1))
537 usage(cmd_cancel_replace_usage);
539 path = argv[optind];
540 fd = open_file_or_dir(path, &dirstream);
541 if (fd < 0) {
542 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
543 path, strerror(errno));
544 return 1;
547 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
548 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
549 e = errno;
550 close_file_or_dir(fd, dirstream);
551 if (ret) {
552 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_CANCEL) failed on \"%s\": %s, %s\n",
553 path, strerror(e),
554 replace_dev_result2string(args.result));
555 return 1;
557 if (args.result == BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED) {
558 printf("INFO: ioctl(DEV_REPLACE_CANCEL)\"%s\": %s\n",
559 path, replace_dev_result2string(args.result));
560 return 2;
562 return 0;
565 const struct cmd_group replace_cmd_group = {
566 replace_cmd_group_usage, NULL, {
567 { "start", cmd_start_replace, cmd_start_replace_usage, NULL,
568 0 },
569 { "status", cmd_status_replace, cmd_status_replace_usage, NULL,
570 0 },
571 { "cancel", cmd_cancel_replace, cmd_cancel_replace_usage, NULL,
572 0 },
573 NULL_CMD_STRUCT
577 int cmd_replace(int argc, char **argv)
579 return handle_command_group(&replace_cmd_group, argc, argv);