#include "disk-io.h"
#include "commands.h"
-
+#include "help.h"
+#include "mkfs/common.h"
static int print_replace_status(int fd, const char *path, int once);
static char *time2string(char *buf, size_t s, __u64 t);
return "not started";
case BTRFS_IOCTL_DEV_REPLACE_RESULT_ALREADY_STARTED:
return "already started";
+ case BTRFS_IOCTL_DEV_REPLACE_RESULT_SCRUB_INPROGRESS:
+ return "scrub is in progress";
default:
return "<illegal result value>";
}
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)
{
return sigaction(SIGINT, &sa, NULL);
}
-static const char *const cmd_start_replace_usage[] = {
- "btrfs replace start srcdev|devid targetdev [-Bfr] mount_point",
+static const char *const cmd_replace_start_usage[] = {
+ "btrfs replace start [-Bfr] <srcdev>|<devid> <targetdev> <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",
+ "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.",
+ "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",
+ "-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",
+ "-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",
+ " never allowed to be used as the <targetdev>",
"-B do not background",
NULL
};
-static int cmd_start_replace(int argc, char **argv)
+static int cmd_replace_start(int argc, char **argv)
{
struct btrfs_ioctl_dev_replace_args start_args = {0};
struct btrfs_ioctl_dev_replace_args status_args = {0};
int i;
int c;
int fdmnt = -1;
- int fdsrcdev = -1;
int fddstdev = -1;
char *path;
char *srcdev;
- char *dstdev;
+ char *dstdev = NULL;
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;
+ DIR *dirstream = NULL;
+ u64 srcdev_size;
+ u64 dstdev_size;
while ((c = getopt(argc, argv, "Brf")) != -1) {
switch (c) {
break;
case '?':
default:
- usage(cmd_start_replace_usage);
+ usage(cmd_replace_start_usage);
}
}
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);
+ usage(cmd_replace_start_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));
+
+ fdmnt = open_path_or_dev_mnt(path, &dirstream, 1);
+ if (fdmnt < 0)
goto leave_with_error;
- }
/* check for possible errors before backgrounding */
status_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
+ status_args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &status_args);
- if (ret) {
+ if (ret < 0) {
fprintf(stderr,
- "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s, %s\n",
- path, strerror(errno),
- replace_dev_result2string(status_args.result));
+ "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %m",
+ path);
+ if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
+ fprintf(stderr, ", %s\n",
+ replace_dev_result2string(status_args.result));
+ else
+ fprintf(stderr, "\n");
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",
+ error("ioctl(DEV_REPLACE_STATUS) on '%s' returns error: %s",
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);
+ error("device replace on '%s' already started", path);
goto leave_with_error;
}
srcdev = argv[optind];
- dstdev = argv[optind + 1];
+ dstdev = canonicalize_path(argv[optind + 1]);
+ if (!dstdev) {
+ error("cannot canonicalize path '%s': %m",
+ argv[optind + 1]);
+ goto leave_with_error;
+ }
- if (is_numerical(srcdev)) {
+ if (string_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);
+ start_args.start.srcdevid = arg_strtou64(srcdev);
- ret = get_fs_info(fdmnt, path, &fi_args, &di_args);
+ ret = get_fs_info(path, &fi_args, &di_args);
if (ret) {
- fprintf(stderr, "ERROR: getting dev info for devstats failed: "
- "%s\n", strerror(-ret));
+ error("failed to get device info: %s", strerror(-ret));
free(di_args);
goto leave_with_error;
}
if (!fi_args.num_devices) {
- fprintf(stderr, "ERROR: no devices found\n");
+ error("no devices found");
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;
+ srcdev_size = di_args[i].total_bytes;
free(di_args);
if (i == fi_args.num_devices) {
- fprintf(stderr, "Error: '%s' is not a valid devid for filesystem '%s'\n",
+ error("'%s' is not a valid devid for filesystem '%s'",
srcdev, path);
goto leave_with_error;
}
- } else {
- fdsrcdev = open(srcdev, O_RDWR);
- if (fdsrcdev < 0) {
- 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;
- }
+ } else if (is_block_device(srcdev) > 0) {
strncpy((char *)start_args.start.srcdev_name, srcdev,
BTRFS_DEVICE_PATH_NAME_MAX);
- close(fdsrcdev);
- fdsrcdev = -1;
start_args.start.srcdevid = 0;
+ srcdev_size = get_partition_size(srcdev);
+ } else {
+ error("source device must be a block device or a devid");
+ goto leave_with_error;
}
- ret = check_mounted(dstdev);
- if (ret < 0) {
- fprintf(stderr, "Error checking %s mount status\n", dstdev);
+ ret = test_dev_for_mkfs(dstdev, force_using_targetdev);
+ if (ret)
goto leave_with_error;
- }
- if (ret == 1) {
- fprintf(stderr,
- "Error, target device %s is in use and currently mounted!\n",
- dstdev);
+
+ dstdev_size = get_partition_size(dstdev);
+ if (srcdev_size > dstdev_size) {
+ error("target device smaller than source device (required %llu bytes)",
+ srcdev_size);
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);
+ error("unable to open %s: %m", 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);
+ ret = btrfs_prepare_device(fddstdev, dstdev, &dstdev_block_count, 0,
+ PREP_DEVICE_ZERO_END | PREP_DEVICE_VERBOSE);
+ if (ret)
goto leave_with_error;
- }
+
close(fddstdev);
fddstdev = -1;
+ free(dstdev);
+ dstdev = NULL;
dev_replace_handle_sigint(fdmnt);
if (!do_not_background) {
if (daemon(0, 0) < 0) {
- fprintf(stderr, "ERROR, backgrounding failed: %s\n",
- strerror(errno));
+ error("backgrounding failed: %m");
goto leave_with_error;
}
}
start_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_START;
+ start_args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &start_args);
if (do_not_background) {
- if (ret) {
+ if (ret < 0) {
fprintf(stderr,
- "ERROR: ioctl(DEV_REPLACE_START) failed on \"%s\": %s, %s\n",
- path, strerror(errno),
- replace_dev_result2string(start_args.result));
+ "ERROR: ioctl(DEV_REPLACE_START) failed on \"%s\": %m",
+ path);
+ if (start_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
+ fprintf(stderr, ", %s\n",
+ replace_dev_result2string(start_args.result));
+ else
+ fprintf(stderr, "\n");
+
+ if (errno == EOPNOTSUPP)
+ warning("device replace of RAID5/6 not supported with this kernel");
+
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",
+ error("ioctl(DEV_REPLACE_START) on '%s' returns error: %s",
path,
replace_dev_result2string(start_args.result));
goto leave_with_error;
}
}
- close(fdmnt);
+ close_file_or_dir(fdmnt, dirstream);
return 0;
leave_with_error:
+ if (dstdev)
+ free(dstdev);
if (fdmnt != -1)
close(fdmnt);
- if (fdsrcdev != -1)
- close(fdsrcdev);
if (fddstdev != -1)
close(fddstdev);
- return -1;
+ return 1;
}
-static const char *const cmd_status_replace_usage[] = {
- "btrfs replace status mount_point [-1]",
+static const char *const cmd_replace_status_usage[] = {
+ "btrfs replace status [-1] <mount_point>",
"Print status and progress information of a running device replace",
"operation",
"",
- "-1 print once instead of print continously until the replace",
+ "-1 print once instead of print continuously until the replace",
" operation finishes (or is canceled)",
NULL
};
-static int cmd_status_replace(int argc, char **argv)
+static int cmd_replace_status(int argc, char **argv)
{
int fd;
- int e;
int c;
char *path;
int once = 0;
int ret;
+ DIR *dirstream = NULL;
while ((c = getopt(argc, argv, "1")) != -1) {
switch (c) {
break;
case '?':
default:
- usage(cmd_status_replace_usage);
+ usage(cmd_replace_status_usage);
}
}
if (check_argc_exact(argc - optind, 1))
- usage(cmd_status_replace_usage);
+ usage(cmd_replace_status_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;
- }
+ fd = btrfs_open_dir(path, &dirstream, 1);
+ if (fd < 0)
+ return 1;
ret = print_replace_status(fd, path, once);
- close(fd);
- return ret;
+ close_file_or_dir(fd, dirstream);
+ return !!ret;
}
static int print_replace_status(int fd, const char *path, int once)
for (;;) {
args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
+ args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
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));
+ if (ret < 0) {
+ fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %m",
+ path);
+ if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
+ fprintf(stderr, ", %s\n",
+ replace_dev_result2string(args.result));
+ else
+ fprintf(stderr, "\n");
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",
+ error("ioctl(DEV_REPLACE_STATUS) on '%s' returns error: %s",
path,
replace_dev_result2string(args.result));
return -1;
}
+ status = &args.status;
+
skip_stats = 0;
num_chars = 0;
switch (status->replace_state) {
printf("Never started");
break;
default:
- prevent_loop = 1;
- assert(0);
- break;
+ error("unknown status from ioctl DEV_REPLACE_STATUS on '%s': %llu",
+ path, status->replace_state);
+ return -EINVAL;
}
if (!skip_stats)
status->num_uncorrectable_read_errors);
if (once || prevent_loop) {
printf("\n");
- return 0;
+ break;
}
fflush(stdout);
return buf;
}
-static const char *const cmd_cancel_replace_usage[] = {
- "btrfs replace cancel mount_point",
+static const char *const cmd_replace_cancel_usage[] = {
+ "btrfs replace cancel <mount_point>",
"Cancel a running device replace operation.",
NULL
};
-static int cmd_cancel_replace(int argc, char **argv)
+static int cmd_replace_cancel(int argc, char **argv)
{
struct btrfs_ioctl_dev_replace_args args = {0};
int ret;
int c;
int fd;
- int e;
char *path;
+ DIR *dirstream = NULL;
while ((c = getopt(argc, argv, "")) != -1) {
switch (c) {
case '?':
default:
- usage(cmd_cancel_replace_usage);
+ usage(cmd_replace_cancel_usage);
}
}
if (check_argc_exact(argc - optind, 1))
- usage(cmd_cancel_replace_usage);
+ usage(cmd_replace_cancel_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;
- }
+ fd = btrfs_open_dir(path, &dirstream, 1);
+ if (fd < 0)
+ return 1;
args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
+ args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
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;
+ close_file_or_dir(fd, dirstream);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_CANCEL) failed on \"%s\": %m",
+ path);
+ if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
+ fprintf(stderr, ", %s\n",
+ replace_dev_result2string(args.result));
+ else
+ fprintf(stderr, "\n");
+ return 1;
+ }
+ if (args.result == BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED) {
+ printf("INFO: ioctl(DEV_REPLACE_CANCEL)\"%s\": %s\n",
+ path, replace_dev_result2string(args.result));
+ return 2;
}
-
return 0;
}
+static const char replace_cmd_group_info[] =
+"replace a device in the filesystem";
+
const struct cmd_group replace_cmd_group = {
- replace_cmd_group_usage, NULL, {
- { "start", cmd_start_replace, cmd_start_replace_usage, NULL,
+ replace_cmd_group_usage, replace_cmd_group_info, {
+ { "start", cmd_replace_start, cmd_replace_start_usage, NULL,
0 },
- { "status", cmd_status_replace, cmd_status_replace_usage, NULL,
+ { "status", cmd_replace_status, cmd_replace_status_usage, NULL,
0 },
- { "cancel", cmd_cancel_replace, cmd_cancel_replace_usage, NULL,
+ { "cancel", cmd_replace_cancel, cmd_replace_cancel_usage, NULL,
0 },
- { 0, 0, 0, 0, 0 }
+ NULL_CMD_STRUCT
}
};