btrfs-progs/image/main.c

314 lines
7.6 KiB
C
Raw Normal View History

/*
* Copyright (C) 2008 Oracle. 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 "kerncompat.h"
#include <sys/stat.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <getopt.h>
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
#include <time.h>
#include <zlib.h>
#include "kernel-lib/list.h"
#include "kernel-lib/rbtree.h"
#include "kernel-lib/rbtree_types.h"
#include "kernel-lib/sizes.h"
#include "kernel-shared/uapi/btrfs.h"
#include "kernel-shared/ctree.h"
#include "kernel-shared/disk-io.h"
#include "kernel-shared/transaction.h"
#include "kernel-shared/volumes.h"
#include "kernel-shared/extent_io.h"
#include "kernel-shared/file-item.h"
#include "crypto/crc32c.h"
#include "crypto/hash.h"
#include "common/internal.h"
#include "common/messages.h"
#include "common/cpu-utils.h"
#include "common/box.h"
#include "common/utils.h"
#include "common/extent-cache.h"
#include "common/help.h"
#include "common/device-utils.h"
#include "common/open-utils.h"
#include "common/string-utils.h"
#include "cmds/commands.h"
#include "image/metadump.h"
#include "image/sanitize.h"
#include "image/common.h"
static const char * const image_usage[] = {
"btrfs-image [options] source target",
"Create or restore a filesystem image (metadata)",
"",
"Options:",
OPTLINE("-r", "restore metadump image"),
OPTLINE("-c value", "compression level (0 ~ 9)"),
OPTLINE("-t value", "number of threads (1 ~ 32)"),
OPTLINE("-o", "don't mess with the chunk tree when restoring"),
OPTLINE("-s", "sanitize file names, use once to just use garbage, use twice if you want crc collisions"),
OPTLINE("-w", "walk all trees instead of using extent tree, do this if your extent tree is broken"),
OPTLINE("-m", "restore for multiple devices"),
OPTLINE("-d", "also dump data, conflicts with -w"),
"",
"In the dump mode, source is the btrfs device and target is the output file (use '-' for stdout).",
"In the restore mode, source is the dumped image and target is the btrfs device/file.",
NULL
};
static const struct cmd_struct image_cmd = {
.usagestr = image_usage
};
int BOX_MAIN(image)(int argc, char *argv[])
{
char *source;
char *target;
u64 num_threads = 0;
u64 compress_level = 0;
int create = 1;
int old_restore = 0;
int walk_trees = 0;
int multi_devices = 0;
int ret;
enum sanitize_mode sanitize = SANITIZE_NONE;
int dev_cnt = 0;
bool dump_data = false;
int usage_error = 0;
FILE *out;
cpu_detect_flags();
hash_init_accel();
while (1) {
static const struct option long_options[] = {
{ "help", no_argument, NULL, GETOPT_VAL_HELP},
{ NULL, 0, NULL, 0 }
};
int c = getopt_long(argc, argv, "rc:t:oswmd", long_options, NULL);
if (c < 0)
break;
switch (c) {
case 'r':
create = 0;
break;
case 't':
num_threads = arg_strtou64(optarg);
if (num_threads > MAX_WORKER_THREADS) {
error("number of threads out of range: %llu > %d",
num_threads, MAX_WORKER_THREADS);
return 1;
}
break;
case 'c':
compress_level = arg_strtou64(optarg);
if (compress_level > 9) {
error("compression level out of range: %llu",
compress_level);
return 1;
}
break;
case 'o':
old_restore = 1;
break;
case 's':
if (sanitize == SANITIZE_NONE)
sanitize = SANITIZE_NAMES;
else if (sanitize == SANITIZE_NAMES)
sanitize = SANITIZE_COLLISIONS;
break;
case 'w':
walk_trees = 1;
break;
case 'm':
create = 0;
multi_devices = 1;
break;
case 'd':
btrfs_warn_experimental("Feature: dump image with data");
dump_data = true;
break;
case GETOPT_VAL_HELP:
default:
usage(&image_cmd, c != GETOPT_VAL_HELP);
}
}
set_argv0(argv);
if (check_argc_min(argc - optind, 2))
usage(&image_cmd, 1);
dev_cnt = argc - optind - 1;
#if !EXPERIMENTAL
if (dump_data) {
error(
"data dump feature is experimental and is not configured in this build");
usage(&image_cmd, 1);
}
#endif
if (create) {
if (old_restore) {
error(
"create and restore cannot be used at the same time");
usage_error++;
}
if (dump_data && walk_trees) {
error("-d conflicts with -w option");
usage_error++;
}
} else {
if (walk_trees || sanitize != SANITIZE_NONE || compress_level ||
dump_data) {
error(
"using -w, -s, -c, -d options for restore makes no sense");
usage_error++;
}
if (multi_devices && dev_cnt < 2) {
error("not enough devices specified for -m option");
usage_error++;
}
if (!multi_devices && dev_cnt != 1) {
error("accepts only 1 device without -m option");
usage_error++;
}
}
if (usage_error)
usage(&image_cmd, 1);
source = argv[optind];
target = argv[optind + 1];
if (create && !strcmp(target, "-")) {
out = stdout;
} else {
out = fopen(target, "w+");
if (!out) {
error("unable to create target file %s", target);
exit(1);
}
}
if (compress_level > 0 || create == 0) {
if (num_threads == 0) {
long tmp = sysconf(_SC_NPROCESSORS_ONLN);
if (tmp <= 0)
tmp = 1;
tmp = min_t(long, tmp, MAX_WORKER_THREADS);
num_threads = tmp;
}
} else {
num_threads = 0;
}
if (create) {
ret = check_mounted(source);
if (ret < 0) {
errno = -ret;
warning("unable to check mount status of: %m");
} else if (ret) {
warning("%s already mounted, results may be inaccurate",
source);
}
ret = create_metadump(source, out, num_threads,
compress_level, sanitize, walk_trees,
dump_data);
} else {
ret = restore_metadump(source, out, old_restore, num_threads,
0, target, multi_devices);
}
if (ret) {
error("%s failed: %d", (create) ? "create" : "restore", ret);
goto out;
}
/* extended support for multiple devices */
if (!create && multi_devices) {
struct open_ctree_args oca = { 0 };
struct btrfs_fs_info *info;
u64 total_devs;
int i;
oca.filename = target;
oca.flags = OPEN_CTREE_PARTIAL | OPEN_CTREE_RESTORE |
OPEN_CTREE_SKIP_LEAF_ITEM_CHECKS;
info = open_ctree_fs_info(&oca);
if (!info) {
error("open ctree failed at %s", target);
return 1;
}
total_devs = btrfs_super_num_devices(info->super_copy);
if (total_devs != dev_cnt) {
error("it needs %llu devices but has only %d",
total_devs, dev_cnt);
close_ctree(info->chunk_root);
goto out;
}
/* update super block on other disks */
for (i = 2; i <= dev_cnt; i++) {
ret = update_disk_super_on_device(info,
argv[optind + i], (u64)i);
if (ret) {
error("update disk superblock failed devid %d: %d",
i, ret);
close_ctree(info->chunk_root);
exit(1);
}
}
close_ctree(info->chunk_root);
/* fix metadata block to map correct chunk */
ret = restore_metadump(source, out, 0, num_threads, 1,
target, 1);
if (ret) {
error("unable to fixup metadump: %d", ret);
exit(1);
}
}
out:
if (out == stdout) {
fflush(out);
} else {
fclose(out);
if (ret && create) {
int unlink_ret;
unlink_ret = unlink(target);
if (unlink_ret)
error("unlink output file %s failed: %m",
target);
}
}
btrfs_close_all_devices();
return !!ret;
}