forked from Mirrors/btrfs-progs
Btrfs-progs: add support for device replace procedure
This is the user mode part of the device replace patch series. The command group "btrfs replace" is added with three commands: - btrfs replace start srcdev|srcdevid targetdev [-Bfr] mount_point - btrfs replace status mount_point [-1] - btrfs replace cancel mount_point Signed-off-by: Stefan Behrens <sbehrens@giantdisaster.de>master
parent
5b8826ddfd
commit
7e08a9116d
2
Makefile
2
Makefile
|
@ -8,7 +8,7 @@ objects = ctree.o disk-io.o radix-tree.o extent-tree.o print-tree.o \
|
|||
send-stream.o send-utils.o qgroup.o
|
||||
cmds_objects = cmds-subvolume.o cmds-filesystem.o cmds-device.o cmds-scrub.o \
|
||||
cmds-inspect.o cmds-balance.o cmds-send.o cmds-receive.o \
|
||||
cmds-quota.o cmds-qgroup.o
|
||||
cmds-quota.o cmds-qgroup.o cmds-replace.o
|
||||
|
||||
CHECKFLAGS= -D__linux__ -Dlinux -D__STDC__ -Dunix -D__unix__ -Wbitwise \
|
||||
-Wuninitialized -Wshadow -Wundef
|
||||
|
|
1
btrfs.c
1
btrfs.c
|
@ -251,6 +251,7 @@ const struct cmd_group btrfs_cmd_group = {
|
|||
{ "receive", cmd_receive, NULL, &receive_cmd_group, 0 },
|
||||
{ "quota", cmd_quota, NULL, "a_cmd_group, 0 },
|
||||
{ "qgroup", cmd_qgroup, NULL, &qgroup_cmd_group, 0 },
|
||||
{ "replace", cmd_replace, NULL, &replace_cmd_group, 0 },
|
||||
{ "help", cmd_help, cmd_help_usage, NULL, 0 },
|
||||
{ "version", cmd_version, cmd_version_usage, NULL, 0 },
|
||||
{ 0, 0, 0, 0, 0 }
|
||||
|
|
|
@ -0,0 +1,579 @@
|
|||
/*
|
||||
* Copyright (C) 2012 STRATO. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include "kerncompat.h"
|
||||
#include "ctree.h"
|
||||
#include "ioctl.h"
|
||||
#include "utils.h"
|
||||
#include "volumes.h"
|
||||
#include "disk-io.h"
|
||||
|
||||
#include "commands.h"
|
||||
|
||||
|
||||
static int print_replace_status(int fd, const char *path, int once);
|
||||
static char *time2string(char *buf, size_t s, __u64 t);
|
||||
static char *progress2string(char *buf, size_t s, int progress_1000);
|
||||
|
||||
|
||||
static const char *replace_dev_result2string(__u64 result)
|
||||
{
|
||||
switch (result) {
|
||||
case BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR:
|
||||
return "no error";
|
||||
case BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED:
|
||||
return "not started";
|
||||
case BTRFS_IOCTL_DEV_REPLACE_RESULT_ALREADY_STARTED:
|
||||
return "already started";
|
||||
default:
|
||||
return "<illegal result value>";
|
||||
}
|
||||
}
|
||||
|
||||
static const char * const replace_cmd_group_usage[] = {
|
||||
"btrfs replace <command> [<args>]",
|
||||
NULL
|
||||
};
|
||||
|
||||
static int is_numerical(const char *str)
|
||||
{
|
||||
if (!(*str >= '0' && *str <= '9'))
|
||||
return 0;
|
||||
while (*str >= '0' && *str <= '9')
|
||||
str++;
|
||||
if (*str != '\0')
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int dev_replace_cancel_fd = -1;
|
||||
static void dev_replace_sigint_handler(int signal)
|
||||
{
|
||||
struct btrfs_ioctl_dev_replace_args args = {0};
|
||||
|
||||
args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
|
||||
ioctl(dev_replace_cancel_fd, BTRFS_IOC_DEV_REPLACE, &args);
|
||||
}
|
||||
|
||||
static int dev_replace_handle_sigint(int fd)
|
||||
{
|
||||
struct sigaction sa = {
|
||||
.sa_handler = fd == -1 ? SIG_DFL : dev_replace_sigint_handler
|
||||
};
|
||||
|
||||
dev_replace_cancel_fd = fd;
|
||||
return sigaction(SIGINT, &sa, NULL);
|
||||
}
|
||||
|
||||
static const char *const cmd_start_replace_usage[] = {
|
||||
"btrfs replace start srcdev|devid targetdev [-Bfr] mount_point",
|
||||
"Replace device of a btrfs filesystem.",
|
||||
"On a live filesystem, duplicate the data to the target device which",
|
||||
"is currently stored on the source device. If the source device is not",
|
||||
"available anymore, or if the -r option is set, the data is built",
|
||||
"only using the RAID redundancy mechanisms. After completion of the",
|
||||
"operation, the source device is removed from the filesystem.",
|
||||
"If the srcdev is a numerical value, it is assumed to be the device id",
|
||||
"of the filesystem which is mounted at mount_point, otherwise it is",
|
||||
"the path to the source device. If the source device is disconnected,",
|
||||
"from the system, you have to use the devid parameter format.",
|
||||
"The targetdev needs to be same size or larger than the srcdev.",
|
||||
"",
|
||||
"-r only read from srcdev if no other zero-defect mirror exists",
|
||||
" (enable this if your drive has lots of read errors, the access",
|
||||
" would be very slow)",
|
||||
"-f force using and overwriting targetdev even if it looks like",
|
||||
" containing a valid btrfs filesystem. A valid filesystem is",
|
||||
" assumed if a btrfs superblock is found which contains a",
|
||||
" correct checksum. Devices which are currently mounted are",
|
||||
" never allowed to be used as the targetdev",
|
||||
"-B do not background",
|
||||
NULL
|
||||
};
|
||||
|
||||
static int cmd_start_replace(int argc, char **argv)
|
||||
{
|
||||
struct btrfs_ioctl_dev_replace_args start_args = {0};
|
||||
struct btrfs_ioctl_dev_replace_args status_args = {0};
|
||||
int ret;
|
||||
int i;
|
||||
int c;
|
||||
int fdmnt = -1;
|
||||
int fdsrcdev = -1;
|
||||
int fddstdev = -1;
|
||||
char *path;
|
||||
char *srcdev;
|
||||
char *dstdev;
|
||||
int avoid_reading_from_srcdev = 0;
|
||||
int force_using_targetdev = 0;
|
||||
u64 total_devs = 1;
|
||||
struct btrfs_fs_devices *fs_devices_mnt = NULL;
|
||||
struct stat st;
|
||||
u64 dstdev_block_count;
|
||||
int do_not_background = 0;
|
||||
int mixed = 0;
|
||||
|
||||
while ((c = getopt(argc, argv, "Brf")) != -1) {
|
||||
switch (c) {
|
||||
case 'B':
|
||||
do_not_background = 1;
|
||||
break;
|
||||
case 'r':
|
||||
avoid_reading_from_srcdev = 1;
|
||||
break;
|
||||
case 'f':
|
||||
force_using_targetdev = 1;
|
||||
break;
|
||||
case '?':
|
||||
default:
|
||||
usage(cmd_start_replace_usage);
|
||||
}
|
||||
}
|
||||
|
||||
start_args.start.cont_reading_from_srcdev_mode =
|
||||
avoid_reading_from_srcdev ?
|
||||
BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_AVOID :
|
||||
BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_ALWAYS;
|
||||
if (check_argc_exact(argc - optind, 3))
|
||||
usage(cmd_start_replace_usage);
|
||||
path = argv[optind + 2];
|
||||
fdmnt = open_file_or_dir(path);
|
||||
if (fdmnt < 0) {
|
||||
fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
|
||||
path, strerror(errno));
|
||||
goto leave_with_error;
|
||||
}
|
||||
|
||||
/* check for possible errors before backgrounding */
|
||||
status_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
|
||||
ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &status_args);
|
||||
if (ret) {
|
||||
fprintf(stderr,
|
||||
"ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s, %s\n",
|
||||
path, strerror(errno),
|
||||
replace_dev_result2string(status_args.result));
|
||||
goto leave_with_error;
|
||||
}
|
||||
|
||||
if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
|
||||
fprintf(stderr,
|
||||
"ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
|
||||
path, replace_dev_result2string(status_args.result));
|
||||
goto leave_with_error;
|
||||
}
|
||||
|
||||
if (status_args.status.replace_state ==
|
||||
BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED) {
|
||||
fprintf(stderr,
|
||||
"ERROR: btrfs replace on \"%s\" already started!\n",
|
||||
path);
|
||||
goto leave_with_error;
|
||||
}
|
||||
|
||||
srcdev = argv[optind];
|
||||
dstdev = argv[optind + 1];
|
||||
|
||||
if (is_numerical(srcdev)) {
|
||||
struct btrfs_ioctl_fs_info_args fi_args;
|
||||
struct btrfs_ioctl_dev_info_args *di_args = NULL;
|
||||
|
||||
if (atoi(srcdev) == 0) {
|
||||
fprintf(stderr, "Error: Failed to parse the numerical devid value '%s'\n",
|
||||
srcdev);
|
||||
goto leave_with_error;
|
||||
}
|
||||
start_args.start.srcdevid = (__u64)atoi(srcdev);
|
||||
|
||||
ret = get_fs_info(fdmnt, path, &fi_args, &di_args);
|
||||
if (ret) {
|
||||
fprintf(stderr, "ERROR: getting dev info for devstats failed: "
|
||||
"%s\n", strerror(-ret));
|
||||
free(di_args);
|
||||
goto leave_with_error;
|
||||
}
|
||||
if (!fi_args.num_devices) {
|
||||
fprintf(stderr, "ERROR: no devices found\n");
|
||||
free(di_args);
|
||||
goto leave_with_error;
|
||||
}
|
||||
|
||||
for (i = 0; i < fi_args.num_devices; i++)
|
||||
if (start_args.start.srcdevid == di_args[i].devid)
|
||||
break;
|
||||
if (i == fi_args.num_devices) {
|
||||
fprintf(stderr, "Error: '%s' is not a valid devid for filesystem '%s'\n",
|
||||
srcdev, path);
|
||||
goto leave_with_error;
|
||||
}
|
||||
} else {
|
||||
fdsrcdev = open(srcdev, O_RDWR);
|
||||
if (!fdsrcdev) {
|
||||
fprintf(stderr, "Error: Unable to open device '%s'\n",
|
||||
srcdev);
|
||||
goto leave_with_error;
|
||||
}
|
||||
ret = fstat(fdsrcdev, &st);
|
||||
if (ret) {
|
||||
fprintf(stderr, "Error: Unable to stat '%s'\n", srcdev);
|
||||
goto leave_with_error;
|
||||
}
|
||||
if (!S_ISBLK(st.st_mode)) {
|
||||
fprintf(stderr, "Error: '%s' is not a block device\n",
|
||||
srcdev);
|
||||
goto leave_with_error;
|
||||
}
|
||||
strncpy((char *)start_args.start.srcdev_name, srcdev,
|
||||
BTRFS_DEVICE_PATH_NAME_MAX);
|
||||
close(fdsrcdev);
|
||||
fdsrcdev = -1;
|
||||
start_args.start.srcdevid = 0;
|
||||
}
|
||||
|
||||
ret = check_mounted(dstdev);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "Error checking %s mount status\n", dstdev);
|
||||
goto leave_with_error;
|
||||
}
|
||||
if (ret == 1) {
|
||||
fprintf(stderr,
|
||||
"Error, target device %s is in use and currently mounted!\n",
|
||||
dstdev);
|
||||
goto leave_with_error;
|
||||
}
|
||||
fddstdev = open(dstdev, O_RDWR);
|
||||
if (fddstdev < 0) {
|
||||
fprintf(stderr, "Unable to open %s\n", dstdev);
|
||||
goto leave_with_error;
|
||||
}
|
||||
ret = btrfs_scan_one_device(fddstdev, dstdev, &fs_devices_mnt,
|
||||
&total_devs, BTRFS_SUPER_INFO_OFFSET);
|
||||
if (ret >= 0 && !force_using_targetdev) {
|
||||
fprintf(stderr,
|
||||
"Error, target device %s contains filesystem, use '-f' to force overwriting.\n",
|
||||
dstdev);
|
||||
goto leave_with_error;
|
||||
}
|
||||
ret = fstat(fddstdev, &st);
|
||||
if (ret) {
|
||||
fprintf(stderr, "Error: Unable to stat '%s'\n", dstdev);
|
||||
goto leave_with_error;
|
||||
}
|
||||
if (!S_ISBLK(st.st_mode)) {
|
||||
fprintf(stderr, "Error: '%s' is not a block device\n", dstdev);
|
||||
goto leave_with_error;
|
||||
}
|
||||
strncpy((char *)start_args.start.tgtdev_name, dstdev,
|
||||
BTRFS_DEVICE_PATH_NAME_MAX);
|
||||
if (btrfs_prepare_device(fddstdev, dstdev, 1, &dstdev_block_count, 0,
|
||||
&mixed, 0)) {
|
||||
fprintf(stderr, "Error: Failed to prepare device '%s'\n",
|
||||
dstdev);
|
||||
goto leave_with_error;
|
||||
}
|
||||
close(fddstdev);
|
||||
fddstdev = -1;
|
||||
|
||||
dev_replace_handle_sigint(fdmnt);
|
||||
if (!do_not_background) {
|
||||
if (daemon(0, 0) < 0) {
|
||||
fprintf(stderr, "ERROR, backgrounding failed: %s\n",
|
||||
strerror(errno));
|
||||
goto leave_with_error;
|
||||
}
|
||||
}
|
||||
|
||||
start_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_START;
|
||||
ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &start_args);
|
||||
if (do_not_background) {
|
||||
if (ret) {
|
||||
fprintf(stderr,
|
||||
"ERROR: ioctl(DEV_REPLACE_START) failed on \"%s\": %s, %s\n",
|
||||
path, strerror(errno),
|
||||
replace_dev_result2string(start_args.result));
|
||||
goto leave_with_error;
|
||||
}
|
||||
|
||||
if (start_args.result !=
|
||||
BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
|
||||
fprintf(stderr,
|
||||
"ERROR: ioctl(DEV_REPLACE_START) on \"%s\" returns error: %s\n",
|
||||
path,
|
||||
replace_dev_result2string(start_args.result));
|
||||
goto leave_with_error;
|
||||
}
|
||||
}
|
||||
close(fdmnt);
|
||||
return 0;
|
||||
|
||||
leave_with_error:
|
||||
if (fdmnt != -1)
|
||||
close(fdmnt);
|
||||
if (fdsrcdev != -1)
|
||||
close(fdsrcdev);
|
||||
if (fddstdev != -1)
|
||||
close(fddstdev);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static const char *const cmd_status_replace_usage[] = {
|
||||
"btrfs replace status mount_point [-1]",
|
||||
"Print status and progress information of a running device replace",
|
||||
"operation",
|
||||
"",
|
||||
"-1 print once instead of print continously until the replace",
|
||||
" operation finishes (or is canceled)",
|
||||
NULL
|
||||
};
|
||||
|
||||
static int cmd_status_replace(int argc, char **argv)
|
||||
{
|
||||
int fd;
|
||||
int e;
|
||||
int c;
|
||||
char *path;
|
||||
int once = 0;
|
||||
int ret;
|
||||
|
||||
while ((c = getopt(argc, argv, "1")) != -1) {
|
||||
switch (c) {
|
||||
case '1':
|
||||
once = 1;
|
||||
break;
|
||||
case '?':
|
||||
default:
|
||||
usage(cmd_status_replace_usage);
|
||||
}
|
||||
}
|
||||
|
||||
if (check_argc_exact(argc - optind, 1))
|
||||
usage(cmd_status_replace_usage);
|
||||
|
||||
path = argv[optind];
|
||||
fd = open_file_or_dir(path);
|
||||
e = errno;
|
||||
if (fd < 0) {
|
||||
fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
|
||||
path, strerror(e));
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = print_replace_status(fd, path, once);
|
||||
close(fd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int print_replace_status(int fd, const char *path, int once)
|
||||
{
|
||||
struct btrfs_ioctl_dev_replace_args args = {0};
|
||||
struct btrfs_ioctl_dev_replace_status_params *status;
|
||||
int ret;
|
||||
int prevent_loop = 0;
|
||||
int skip_stats;
|
||||
int num_chars;
|
||||
char string1[80];
|
||||
char string2[80];
|
||||
char string3[80];
|
||||
|
||||
for (;;) {
|
||||
args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
|
||||
ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
|
||||
if (ret) {
|
||||
fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s, %s\n",
|
||||
path, strerror(errno),
|
||||
replace_dev_result2string(args.result));
|
||||
return ret;
|
||||
}
|
||||
|
||||
status = &args.status;
|
||||
if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
|
||||
fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
|
||||
path,
|
||||
replace_dev_result2string(args.result));
|
||||
return -1;
|
||||
}
|
||||
|
||||
skip_stats = 0;
|
||||
num_chars = 0;
|
||||
switch (status->replace_state) {
|
||||
case BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED:
|
||||
num_chars =
|
||||
printf("%s done",
|
||||
progress2string(string3,
|
||||
sizeof(string3),
|
||||
status->progress_1000));
|
||||
break;
|
||||
case BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED:
|
||||
prevent_loop = 1;
|
||||
printf("Started on %s, finished on %s",
|
||||
time2string(string1, sizeof(string1),
|
||||
status->time_started),
|
||||
time2string(string2, sizeof(string2),
|
||||
status->time_stopped));
|
||||
break;
|
||||
case BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED:
|
||||
prevent_loop = 1;
|
||||
printf("Started on %s, canceled on %s at %s",
|
||||
time2string(string1, sizeof(string1),
|
||||
status->time_started),
|
||||
time2string(string2, sizeof(string2),
|
||||
status->time_stopped),
|
||||
progress2string(string3, sizeof(string3),
|
||||
status->progress_1000));
|
||||
break;
|
||||
case BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED:
|
||||
prevent_loop = 1;
|
||||
printf("Started on %s, suspended on %s at %s",
|
||||
time2string(string1, sizeof(string1),
|
||||
status->time_started),
|
||||
time2string(string2, sizeof(string2),
|
||||
status->time_stopped),
|
||||
progress2string(string3, sizeof(string3),
|
||||
status->progress_1000));
|
||||
break;
|
||||
case BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED:
|
||||
prevent_loop = 1;
|
||||
skip_stats = 1;
|
||||
printf("Never started");
|
||||
break;
|
||||
default:
|
||||
prevent_loop = 1;
|
||||
assert(0);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!skip_stats)
|
||||
num_chars += printf(
|
||||
", %llu write errs, %llu uncorr. read errs",
|
||||
(unsigned long long)status->num_write_errors,
|
||||
(unsigned long long)
|
||||
status->num_uncorrectable_read_errors);
|
||||
if (once || prevent_loop) {
|
||||
printf("\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
fflush(stdout);
|
||||
sleep(1);
|
||||
while (num_chars > 0) {
|
||||
putchar('\b');
|
||||
num_chars--;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char *
|
||||
time2string(char *buf, size_t s, __u64 t)
|
||||
{
|
||||
struct tm t_tm;
|
||||
time_t t_time_t;
|
||||
|
||||
t_time_t = (time_t)t;
|
||||
assert((__u64)t_time_t == t);
|
||||
localtime_r(&t_time_t, &t_tm);
|
||||
strftime(buf, s, "%e.%b %T", &t_tm);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static char *
|
||||
progress2string(char *buf, size_t s, int progress_1000)
|
||||
{
|
||||
snprintf(buf, s, "%d.%01d%%", progress_1000 / 10, progress_1000 % 10);
|
||||
assert(s > 0);
|
||||
buf[s - 1] = '\0';
|
||||
return buf;
|
||||
}
|
||||
|
||||
static const char *const cmd_cancel_replace_usage[] = {
|
||||
"btrfs replace cancel mount_point",
|
||||
"Cancel a running device replace operation.",
|
||||
NULL
|
||||
};
|
||||
|
||||
static int cmd_cancel_replace(int argc, char **argv)
|
||||
{
|
||||
struct btrfs_ioctl_dev_replace_args args = {0};
|
||||
int ret;
|
||||
int c;
|
||||
int fd;
|
||||
int e;
|
||||
char *path;
|
||||
|
||||
while ((c = getopt(argc, argv, "")) != -1) {
|
||||
switch (c) {
|
||||
case '?':
|
||||
default:
|
||||
usage(cmd_cancel_replace_usage);
|
||||
}
|
||||
}
|
||||
|
||||
if (check_argc_exact(argc - optind, 1))
|
||||
usage(cmd_cancel_replace_usage);
|
||||
|
||||
path = argv[optind];
|
||||
fd = open_file_or_dir(path);
|
||||
if (fd < 0) {
|
||||
fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
|
||||
path, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
|
||||
ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
|
||||
e = errno;
|
||||
close(fd);
|
||||
if (ret) {
|
||||
fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_CANCEL) failed on \"%s\": %s, %s\n",
|
||||
path, strerror(e),
|
||||
replace_dev_result2string(args.result));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct cmd_group replace_cmd_group = {
|
||||
replace_cmd_group_usage, NULL, {
|
||||
{ "start", cmd_start_replace, cmd_start_replace_usage, NULL,
|
||||
0 },
|
||||
{ "status", cmd_status_replace, cmd_status_replace_usage, NULL,
|
||||
0 },
|
||||
{ "cancel", cmd_cancel_replace, cmd_cancel_replace_usage, NULL,
|
||||
0 },
|
||||
{ 0, 0, 0, 0, 0 }
|
||||
}
|
||||
};
|
||||
|
||||
int cmd_replace(int argc, char **argv)
|
||||
{
|
||||
return handle_command_group(&replace_cmd_group, argc, argv);
|
||||
}
|
|
@ -89,6 +89,7 @@ extern const struct cmd_group send_cmd_group;
|
|||
extern const struct cmd_group receive_cmd_group;
|
||||
extern const struct cmd_group quota_cmd_group;
|
||||
extern const struct cmd_group qgroup_cmd_group;
|
||||
extern const struct cmd_group replace_cmd_group;
|
||||
|
||||
int cmd_subvolume(int argc, char **argv);
|
||||
int cmd_filesystem(int argc, char **argv);
|
||||
|
@ -100,6 +101,7 @@ int cmd_send(int argc, char **argv);
|
|||
int cmd_receive(int argc, char **argv);
|
||||
int cmd_quota(int argc, char **argv);
|
||||
int cmd_qgroup(int argc, char **argv);
|
||||
int cmd_replace(int argc, char **argv);
|
||||
|
||||
/* subvolume exported functions */
|
||||
int test_issubvolume(char *path);
|
||||
|
|
6
ctree.h
6
ctree.h
|
@ -1050,6 +1050,12 @@ struct btrfs_root {
|
|||
*/
|
||||
#define BTRFS_DEV_STATS_KEY 249
|
||||
|
||||
/*
|
||||
* Persistently stores the device replace state in the device tree.
|
||||
* The key is built like this: (0, BTRFS_DEV_REPLACE_KEY, 0).
|
||||
*/
|
||||
#define BTRFS_DEV_REPLACE_KEY 250
|
||||
|
||||
/*
|
||||
* string items are for debugging. They just store a short string of
|
||||
* data in the FS
|
||||
|
|
47
ioctl.h
47
ioctl.h
|
@ -32,6 +32,8 @@ struct btrfs_ioctl_vol_args {
|
|||
char name[BTRFS_PATH_NAME_MAX + 1];
|
||||
};
|
||||
|
||||
#define BTRFS_DEVICE_PATH_NAME_MAX 1024
|
||||
|
||||
#define BTRFS_SUBVOL_CREATE_ASYNC (1ULL << 0)
|
||||
#define BTRFS_SUBVOL_RDONLY (1ULL << 1)
|
||||
#define BTRFS_SUBVOL_QGROUP_INHERIT (1ULL << 2)
|
||||
|
@ -108,7 +110,48 @@ struct btrfs_ioctl_scrub_args {
|
|||
__u64 unused[(1024-32-sizeof(struct btrfs_scrub_progress))/8];
|
||||
};
|
||||
|
||||
#define BTRFS_DEVICE_PATH_NAME_MAX 1024
|
||||
#define BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_ALWAYS 0
|
||||
#define BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_AVOID 1
|
||||
struct btrfs_ioctl_dev_replace_start_params {
|
||||
__u64 srcdevid; /* in, if 0, use srcdev_name instead */
|
||||
__u64 cont_reading_from_srcdev_mode; /* in, see #define
|
||||
* above */
|
||||
__u8 srcdev_name[BTRFS_DEVICE_PATH_NAME_MAX + 1]; /* in */
|
||||
__u8 tgtdev_name[BTRFS_DEVICE_PATH_NAME_MAX + 1]; /* in */
|
||||
};
|
||||
|
||||
#define BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED 0
|
||||
#define BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED 1
|
||||
#define BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED 2
|
||||
#define BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED 3
|
||||
#define BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED 4
|
||||
struct btrfs_ioctl_dev_replace_status_params {
|
||||
__u64 replace_state; /* out, see #define above */
|
||||
__u64 progress_1000; /* out, 0 <= x <= 1000 */
|
||||
__u64 time_started; /* out, seconds since 1-Jan-1970 */
|
||||
__u64 time_stopped; /* out, seconds since 1-Jan-1970 */
|
||||
__u64 num_write_errors; /* out */
|
||||
__u64 num_uncorrectable_read_errors; /* out */
|
||||
};
|
||||
|
||||
#define BTRFS_IOCTL_DEV_REPLACE_CMD_START 0
|
||||
#define BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS 1
|
||||
#define BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL 2
|
||||
#define BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR 0
|
||||
#define BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED 1
|
||||
#define BTRFS_IOCTL_DEV_REPLACE_RESULT_ALREADY_STARTED 2
|
||||
struct btrfs_ioctl_dev_replace_args {
|
||||
__u64 cmd; /* in */
|
||||
__u64 result; /* out */
|
||||
|
||||
union {
|
||||
struct btrfs_ioctl_dev_replace_start_params start;
|
||||
struct btrfs_ioctl_dev_replace_status_params status;
|
||||
}; /* in/out */
|
||||
|
||||
__u64 spare[64];
|
||||
};
|
||||
|
||||
struct btrfs_ioctl_dev_info_args {
|
||||
__u64 devid; /* in/out */
|
||||
__u8 uuid[BTRFS_UUID_SIZE]; /* in/out */
|
||||
|
@ -468,5 +511,7 @@ struct btrfs_ioctl_clone_range_args {
|
|||
struct btrfs_ioctl_qgroup_limit_args)
|
||||
#define BTRFS_IOC_GET_DEV_STATS _IOWR(BTRFS_IOCTL_MAGIC, 52, \
|
||||
struct btrfs_ioctl_get_dev_stats)
|
||||
#define BTRFS_IOC_DEV_REPLACE _IOWR(BTRFS_IOCTL_MAGIC, 53, \
|
||||
struct btrfs_ioctl_dev_replace_args)
|
||||
|
||||
#endif
|
||||
|
|
|
@ -39,6 +39,12 @@ btrfs \- control a btrfs filesystem
|
|||
.PP
|
||||
\fBbtrfs\fP \fBdevice delete\fP\fI <device> [<device>...] <path> \fP
|
||||
.PP
|
||||
\fBbtrfs\fP \fBreplace start\fP \fI[-Bfr] <srcdev>|<devid> <targetdev> <path>\fP
|
||||
.PP
|
||||
\fBbtrfs\fP \fBreplace status\fP \fI[-1] <path>\fP
|
||||
.PP
|
||||
\fBbtrfs\fP \fBreplace cancel\fP \fI<path>\fP
|
||||
.PP
|
||||
\fBbtrfs\fP \fBscrub start\fP [-Bdqru] {\fI<path>\fP|\fI<device>\fP}
|
||||
.PP
|
||||
\fBbtrfs\fP \fBscrub cancel\fP {\fI<path>\fP|\fI<device>\fP}
|
||||
|
@ -282,6 +288,54 @@ Finally, if \fB--all-devices\fP is passed, all the devices under /dev are
|
|||
scanned.
|
||||
.TP
|
||||
|
||||
\fBreplace start\fR \fI[-Bfr] <srcdev>|<devid> <targetdev> <path>\fR
|
||||
Replace device of a btrfs filesystem.
|
||||
On a live filesystem, duplicate the data to the target device which
|
||||
is currently stored on the source device. If the source device is not
|
||||
available anymore, or if the \fB-r\fR option is set, the data is built
|
||||
only using the RAID redundancy mechanisms. After completion of the
|
||||
operation, the source device is removed from the filesystem.
|
||||
If the \fIsrcdev\fR is a numerical value, it is assumed to be the device id
|
||||
of the filesystem which is mounted at mount_point, otherwise is is
|
||||
the path to the source device. If the source device is disconnected,
|
||||
from the system, you have to use the \fIdevid\fR parameter format.
|
||||
The targetdev needs to be same size or larger than the \fIsrcdev\fR.
|
||||
|
||||
.RS
|
||||
\fIOptions\fR
|
||||
.TP
|
||||
.B -r
|
||||
only read from \fIsrcdev\fR if no other zero-defect mirror exists (enable
|
||||
this if your drive has lots of read errors, the access would be very slow)
|
||||
.TP
|
||||
.B -f
|
||||
force using and overwriting \fItargetdev\fR even if it looks like
|
||||
containing a valid btrfs filesystem. A valid filesystem is
|
||||
assumed if a btrfs superblock is found which contains a
|
||||
correct checksum. Devices which are currently mounted are
|
||||
never allowed to be used as the \fItargetdev\fR
|
||||
.TP
|
||||
.B -B
|
||||
do not background
|
||||
.RE
|
||||
.TP
|
||||
|
||||
\fBreplace status\fR \fI[-1] <path>\fR
|
||||
Print status and progress information of a running device replace operation.
|
||||
|
||||
.RS
|
||||
\fIOptions\fR
|
||||
.TP
|
||||
.B -1
|
||||
print once instead of print continously until the replace
|
||||
operation finishes (or is canceled)
|
||||
.RE
|
||||
.TP
|
||||
|
||||
\fBreplace cancel\fR \fI<path>\fR
|
||||
Cancel a running device replace operation.
|
||||
.TP
|
||||
|
||||
\fBscrub start\fP [-Bdqru] {\fI<path>\fP|\fI<device>\fP}
|
||||
Start a scrub on all devices of the filesystem identified by \fI<path>\fR or on
|
||||
a single \fI<device>\fR. Without options, scrub is started as a background
|
||||
|
|
|
@ -440,6 +440,9 @@ static void print_key_type(u64 objectid, u8 type)
|
|||
case BTRFS_BALANCE_ITEM_KEY:
|
||||
printf("BALANCE_ITEM");
|
||||
break;
|
||||
case BTRFS_DEV_REPLACE_KEY:
|
||||
printf("DEV_REPLACE_ITEM");
|
||||
break;
|
||||
case BTRFS_STRING_ITEM_KEY:
|
||||
printf("STRING_ITEM");
|
||||
break;
|
||||
|
|
16
volumes.c
16
volumes.c
|
@ -1219,6 +1219,22 @@ struct btrfs_device *btrfs_find_device(struct btrfs_root *root, u64 devid,
|
|||
return NULL;
|
||||
}
|
||||
|
||||
struct btrfs_device *btrfs_find_device_by_devid(struct btrfs_root *root,
|
||||
u64 devid, int instance)
|
||||
{
|
||||
struct list_head *head = &root->fs_info->fs_devices->devices;
|
||||
struct btrfs_device *dev;
|
||||
struct list_head *cur;
|
||||
int num_found = 0;
|
||||
|
||||
list_for_each(cur, head) {
|
||||
dev = list_entry(cur, struct btrfs_device, dev_list);
|
||||
if (dev->devid == devid && num_found++ == instance)
|
||||
return dev;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int btrfs_bootstrap_super_map(struct btrfs_mapping_tree *map_tree,
|
||||
struct btrfs_fs_devices *fs_devices)
|
||||
{
|
||||
|
|
|
@ -182,4 +182,6 @@ int btrfs_add_system_chunk(struct btrfs_trans_handle *trans,
|
|||
struct btrfs_root *root, struct btrfs_key *key,
|
||||
struct btrfs_chunk *chunk, int item_size);
|
||||
int btrfs_chunk_readonly(struct btrfs_root *root, u64 chunk_offset);
|
||||
struct btrfs_device *btrfs_find_device_by_devid(struct btrfs_root *root,
|
||||
u64 devid, int instance);
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue