315 lines
7.6 KiB
C
315 lines
7.6 KiB
C
/*
|
|
* 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 "kernel-shared/tree-checker.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;
|
|
}
|