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.
24 #include <sys/ioctl.h>
32 #include "kerncompat.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
)
51 case BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR
:
53 case BTRFS_IOCTL_DEV_REPLACE_RESULT_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";
60 return "<illegal result value>";
64 static const char * const replace_cmd_group_usage
[] = {
65 "btrfs replace <command> [<args>]",
69 static int dev_replace_cancel_fd
= -1;
70 static void dev_replace_sigint_handler(int signal
)
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
);
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",
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};
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
;
137 while ((c
= getopt(argc
, argv
, "Brf")) != -1) {
140 do_not_background
= 1;
143 avoid_reading_from_srcdev
= 1;
146 force_using_targetdev
= 1;
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);
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
);
172 "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %m",
174 if (status_args
.result
!= BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT
)
175 fprintf(stderr
, ", %s\n",
176 replace_dev_result2string(status_args
.result
));
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]);
197 error("cannot canonicalize path '%s': %m",
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
);
210 error("failed to get device info: %s", strerror(-ret
));
212 goto leave_with_error
;
214 if (!fi_args
.num_devices
) {
215 error("no devices found");
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
)
223 srcdev_size
= di_args
[i
].total_bytes
;
225 if (i
== fi_args
.num_devices
) {
226 error("'%s' is not a valid devid for filesystem '%s'",
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
);
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
);
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)",
248 goto leave_with_error
;
251 fddstdev
= open(dstdev
, O_RDWR
);
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
);
261 goto leave_with_error
;
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
) {
282 "ERROR: ioctl(DEV_REPLACE_START) failed on \"%s\": %m",
284 if (start_args
.result
!= BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT
)
285 fprintf(stderr
, ", %s\n",
286 replace_dev_result2string(start_args
.result
));
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",
300 replace_dev_result2string(start_args
.result
));
301 goto leave_with_error
;
304 close_file_or_dir(fdmnt
, dirstream
);
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",
322 "-1 print once instead of print continuously until the replace",
323 " operation finishes (or is canceled)",
327 static int cmd_replace_status(int argc
, char **argv
)
334 DIR *dirstream
= NULL
;
336 while ((c
= getopt(argc
, argv
, "1")) != -1) {
343 usage(cmd_replace_status_usage
);
347 if (check_argc_exact(argc
- optind
, 1))
348 usage(cmd_replace_status_usage
);
351 fd
= btrfs_open_dir(path
, &dirstream
, 1);
355 ret
= print_replace_status(fd
, path
, once
);
356 close_file_or_dir(fd
, dirstream
);
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
;
365 int prevent_loop
= 0;
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
);
377 fprintf(stderr
, "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %m",
379 if (args
.result
!= BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT
)
380 fprintf(stderr
, ", %s\n",
381 replace_dev_result2string(args
.result
));
383 fprintf(stderr
, "\n");
387 if (args
.result
!= BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR
) {
388 error("ioctl(DEV_REPLACE_STATUS) on '%s' returns error: %s",
390 replace_dev_result2string(args
.result
));
394 status
= &args
.status
;
398 switch (status
->replace_state
) {
399 case BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED
:
402 progress2string(string3
,
404 status
->progress_1000
));
406 case BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED
:
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
));
414 case BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED
:
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
));
424 case BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED
:
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
));
434 case BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED
:
437 printf("Never started");
440 error("unknown status from ioctl DEV_REPLACE_STATUS on '%s': %llu",
441 path
, status
->replace_state
);
447 ", %llu write errs, %llu uncorr. read errs",
448 (unsigned long long)status
->num_write_errors
,
450 status
->num_uncorrectable_read_errors
);
451 if (once
|| prevent_loop
) {
458 while (num_chars
> 0) {
468 time2string(char *buf
, size_t s
, __u64 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
);
481 progress2string(char *buf
, size_t s
, int progress_1000
)
483 snprintf(buf
, s
, "%d.%01d%%", progress_1000
/ 10, progress_1000
% 10);
489 static const char *const cmd_replace_cancel_usage
[] = {
490 "btrfs replace cancel <mount_point>",
491 "Cancel a running device replace operation.",
495 static int cmd_replace_cancel(int argc
, char **argv
)
497 struct btrfs_ioctl_dev_replace_args args
= {0};
502 DIR *dirstream
= NULL
;
504 while ((c
= getopt(argc
, argv
, "")) != -1) {
508 usage(cmd_replace_cancel_usage
);
512 if (check_argc_exact(argc
- optind
, 1))
513 usage(cmd_replace_cancel_usage
);
516 fd
= btrfs_open_dir(path
, &dirstream
, 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
);
525 fprintf(stderr
, "ERROR: ioctl(DEV_REPLACE_CANCEL) failed on \"%s\": %m",
527 if (args
.result
!= BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT
)
528 fprintf(stderr
, ", %s\n",
529 replace_dev_result2string(args
.result
));
531 fprintf(stderr
, "\n");
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
));
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
,
549 { "status", cmd_replace_status
, cmd_replace_status_usage
, NULL
,
551 { "cancel", cmd_replace_cancel
, cmd_replace_cancel_usage
, NULL
,
557 int cmd_replace(int argc
, char **argv
)
559 return handle_command_group(&replace_cmd_group
, argc
, argv
);