summaryrefslogtreecommitdiff
path: root/btrfs-image.c
diff options
context:
space:
mode:
Diffstat (limited to 'btrfs-image.c')
-rw-r--r--btrfs-image.c313
1 files changed, 262 insertions, 51 deletions
diff --git a/btrfs-image.c b/btrfs-image.c
index ebd283e5..82aa4f39 100644
--- a/btrfs-image.c
+++ b/btrfs-image.c
@@ -35,6 +35,7 @@
#include "utils.h"
#include "version.h"
#include "volumes.h"
+#include "extent_io.h"
#define HEADER_MAGIC 0xbd5c25e27295668bULL
#define MAX_PENDING_SIZE (256 * 1024)
@@ -136,6 +137,9 @@ struct mdrestore_struct {
int done;
int error;
int old_restore;
+ int fixup_offset;
+ int multi_devices;
+ struct btrfs_fs_info *info;
};
static int search_for_chunk_blocks(struct mdrestore_struct *mdres,
@@ -1589,9 +1593,10 @@ static void *restore_worker(void *data)
u8 *outbuf;
int outfd;
int ret;
+ int compress_size = MAX_PENDING_SIZE * 4;
outfd = fileno(mdres->out);
- buffer = malloc(MAX_PENDING_SIZE * 2);
+ buffer = malloc(compress_size);
if (!buffer) {
fprintf(stderr, "Error allocing buffer\n");
pthread_mutex_lock(&mdres->mutex);
@@ -1619,7 +1624,7 @@ static void *restore_worker(void *data)
pthread_mutex_unlock(&mdres->mutex);
if (mdres->compress_method == COMPRESS_ZLIB) {
- size = MAX_PENDING_SIZE * 2;
+ size = compress_size;
ret = uncompress(buffer, (unsigned long *)&size,
async->buffer, async->bufsize);
if (ret != Z_OK) {
@@ -1633,44 +1638,60 @@ static void *restore_worker(void *data)
size = async->bufsize;
}
- if (async->start == BTRFS_SUPER_INFO_OFFSET) {
- if (mdres->old_restore) {
- update_super_old(outbuf);
- } else {
- ret = update_super(outbuf);
+ if (!mdres->multi_devices) {
+ if (async->start == BTRFS_SUPER_INFO_OFFSET) {
+ if (mdres->old_restore) {
+ update_super_old(outbuf);
+ } else {
+ ret = update_super(outbuf);
+ if (ret)
+ err = ret;
+ }
+ } else if (!mdres->old_restore) {
+ ret = fixup_chunk_tree_block(mdres, async, outbuf, size);
if (ret)
err = ret;
}
- } else if (!mdres->old_restore) {
- ret = fixup_chunk_tree_block(mdres, async, outbuf, size);
- if (ret)
- err = ret;
- }
-
- while (size) {
- u64 chunk_size = size;
- bytenr = logical_to_physical(mdres,
- async->start + offset,
- &chunk_size);
- ret = pwrite64(outfd, outbuf+offset, chunk_size,
- bytenr);
- if (ret < chunk_size) {
- if (ret < 0) {
- fprintf(stderr, "Error writing to "
- "device %d\n", errno);
- err = errno;
- break;
- } else {
- fprintf(stderr, "Short write\n");
- err = -EIO;
- break;
+ }
+
+ if (!mdres->fixup_offset) {
+ while (size) {
+ u64 chunk_size = size;
+ if (!mdres->multi_devices)
+ bytenr = logical_to_physical(mdres,
+ async->start + offset,
+ &chunk_size);
+ else
+ bytenr = async->start + offset;
+
+ ret = pwrite64(outfd, outbuf+offset, chunk_size,
+ bytenr);
+ if (ret != chunk_size) {
+ if (ret < 0) {
+ fprintf(stderr, "Error writing to "
+ "device %d\n", errno);
+ err = errno;
+ break;
+ } else {
+ fprintf(stderr, "Short write\n");
+ err = -EIO;
+ break;
+ }
}
+ size -= chunk_size;
+ offset += chunk_size;
+ }
+ } else if (async->start != BTRFS_SUPER_INFO_OFFSET) {
+ ret = write_data_to_disk(mdres->info, outbuf, async->start, size, 0);
+ if (ret) {
+ printk("Error write data\n");
+ exit(1);
}
- size -= chunk_size;
- offset += chunk_size;
}
- if (async->start == BTRFS_SUPER_INFO_OFFSET)
+
+ /* backup super blocks are already there at fixup_offset stage */
+ if (!mdres->multi_devices && async->start == BTRFS_SUPER_INFO_OFFSET)
write_backup_supers(outfd, outbuf);
pthread_mutex_lock(&mdres->mutex);
@@ -1714,7 +1735,8 @@ static void mdrestore_destroy(struct mdrestore_struct *mdres)
static int mdrestore_init(struct mdrestore_struct *mdres,
FILE *in, FILE *out, int old_restore,
- int num_threads)
+ int num_threads, int fixup_offset,
+ struct btrfs_fs_info *info, int multi_devices)
{
int i, ret = 0;
@@ -1726,6 +1748,9 @@ static int mdrestore_init(struct mdrestore_struct *mdres,
mdres->out = out;
mdres->old_restore = old_restore;
mdres->chunk_tree.rb_node = NULL;
+ mdres->fixup_offset = fixup_offset;
+ mdres->info = info;
+ mdres->multi_devices = multi_devices;
if (!num_threads)
return 0;
@@ -2186,12 +2211,14 @@ static int build_chunk_tree(struct mdrestore_struct *mdres,
return search_for_chunk_blocks(mdres, chunk_root_bytenr, 0);
}
-static int restore_metadump(const char *input, FILE *out, int old_restore,
- int num_threads)
+static int __restore_metadump(const char *input, FILE *out, int old_restore,
+ int num_threads, int fixup_offset,
+ const char *target, int multi_devices)
{
struct meta_cluster *cluster = NULL;
struct meta_cluster_header *header;
struct mdrestore_struct mdrestore;
+ struct btrfs_fs_info *info = NULL;
u64 bytenr = 0;
FILE *in = NULL;
int ret = 0;
@@ -2206,26 +2233,36 @@ static int restore_metadump(const char *input, FILE *out, int old_restore,
}
}
+ /* NOTE: open with write mode */
+ if (fixup_offset) {
+ BUG_ON(!target);
+ info = open_ctree_fs_info_restore(target, 0, 0, 1, 1);
+ if (!info) {
+ fprintf(stderr, "%s: open ctree failed\n", __func__);
+ ret = -EIO;
+ goto failed_open;
+ }
+ }
+
cluster = malloc(BLOCK_SIZE);
if (!cluster) {
fprintf(stderr, "Error allocating cluster\n");
- if (in != stdin)
- fclose(in);
- return -ENOMEM;
+ ret = -ENOMEM;
+ goto failed_info;
}
- ret = mdrestore_init(&mdrestore, in, out, old_restore, num_threads);
+ ret = mdrestore_init(&mdrestore, in, out, old_restore, num_threads,
+ fixup_offset, info, multi_devices);
if (ret) {
fprintf(stderr, "Error initing mdrestore %d\n", ret);
- if (in != stdin)
- fclose(in);
- free(cluster);
- return ret;
+ goto failed_cluster;
}
- ret = build_chunk_tree(&mdrestore, cluster);
- if (ret)
- goto out;
+ if (!multi_devices) {
+ ret = build_chunk_tree(&mdrestore, cluster);
+ if (ret)
+ goto out;
+ }
if (in != stdin && fseek(in, 0, SEEK_SET)) {
fprintf(stderr, "Error seeking %d\n", errno);
@@ -2259,12 +2296,123 @@ static int restore_metadump(const char *input, FILE *out, int old_restore,
}
out:
mdrestore_destroy(&mdrestore);
+failed_cluster:
free(cluster);
+failed_info:
+ if (fixup_offset && info)
+ close_ctree(info->chunk_root);
+failed_open:
if (in != stdin)
fclose(in);
return ret;
}
+static int restore_metadump(const char *input, FILE *out, int old_restore,
+ int num_threads, int multi_devices)
+{
+ return __restore_metadump(input, out, old_restore, num_threads, 0, NULL,
+ multi_devices);
+}
+
+static int fixup_metadump(const char *input, FILE *out, int num_threads,
+ const char *target)
+{
+ return __restore_metadump(input, out, 0, num_threads, 1, target, 1);
+}
+
+static int update_disk_super_on_device(struct btrfs_fs_info *info,
+ const char *other_dev, u64 cur_devid)
+{
+ struct btrfs_key key;
+ struct extent_buffer *leaf;
+ struct btrfs_path path;
+ struct btrfs_dev_item *dev_item;
+ struct btrfs_super_block *disk_super;
+ char dev_uuid[BTRFS_UUID_SIZE];
+ char fs_uuid[BTRFS_UUID_SIZE];
+ u64 devid, type, io_align, io_width;
+ u64 sector_size, total_bytes, bytes_used;
+ char *buf;
+ int fp;
+ int ret;
+
+ key.objectid = BTRFS_DEV_ITEMS_OBJECTID;
+ key.type = BTRFS_DEV_ITEM_KEY;
+ key.offset = cur_devid;
+
+ btrfs_init_path(&path);
+ ret = btrfs_search_slot(NULL, info->chunk_root, &key, &path, 0, 0);
+ if (ret) {
+ fprintf(stderr, "search key fails\n");
+ exit(1);
+ }
+
+ leaf = path.nodes[0];
+ dev_item = btrfs_item_ptr(leaf, path.slots[0],
+ struct btrfs_dev_item);
+
+ devid = btrfs_device_id(leaf, dev_item);
+ if (devid != cur_devid) {
+ printk("devid %llu mismatch with %llu\n", devid, cur_devid);
+ exit(1);
+ }
+
+ type = btrfs_device_type(leaf, dev_item);
+ io_align = btrfs_device_io_align(leaf, dev_item);
+ io_width = btrfs_device_io_width(leaf, dev_item);
+ sector_size = btrfs_device_sector_size(leaf, dev_item);
+ total_bytes = btrfs_device_total_bytes(leaf, dev_item);
+ bytes_used = btrfs_device_bytes_used(leaf, dev_item);
+ read_extent_buffer(leaf, dev_uuid, (unsigned long)btrfs_device_uuid(dev_item), BTRFS_UUID_SIZE);
+ read_extent_buffer(leaf, fs_uuid, (unsigned long)btrfs_device_fsid(dev_item), BTRFS_UUID_SIZE);
+
+ btrfs_release_path(info->chunk_root, &path);
+
+ printk("update disk super on %s devid=%llu\n", other_dev, devid);
+
+ /* update other devices' super block */
+ fp = open(other_dev, O_CREAT | O_RDWR, 0600);
+ if (fp < 0) {
+ fprintf(stderr, "could not open %s\n", other_dev);
+ exit(1);
+ }
+
+ buf = malloc(BTRFS_SUPER_INFO_SIZE);
+ if (!buf) {
+ ret = -ENOMEM;
+ exit(1);
+ }
+
+ memcpy(buf, info->super_copy, BTRFS_SUPER_INFO_SIZE);
+
+ disk_super = (struct btrfs_super_block *)buf;
+ dev_item = &disk_super->dev_item;
+
+ btrfs_set_stack_device_type(dev_item, type);
+ btrfs_set_stack_device_id(dev_item, devid);
+ btrfs_set_stack_device_total_bytes(dev_item, total_bytes);
+ btrfs_set_stack_device_bytes_used(dev_item, bytes_used);
+ btrfs_set_stack_device_io_align(dev_item, io_align);
+ btrfs_set_stack_device_io_width(dev_item, io_width);
+ btrfs_set_stack_device_sector_size(dev_item, sector_size);
+ memcpy(dev_item->uuid, dev_uuid, BTRFS_UUID_SIZE);
+ memcpy(dev_item->fsid, fs_uuid, BTRFS_UUID_SIZE);
+ csum_block((u8 *)buf, BTRFS_SUPER_INFO_SIZE);
+
+ ret = pwrite64(fp, buf, BTRFS_SUPER_INFO_SIZE, BTRFS_SUPER_INFO_OFFSET);
+ if (ret != BTRFS_SUPER_INFO_SIZE) {
+ ret = -EIO;
+ goto out;
+ }
+
+ write_backup_supers(fp, (u8 *)buf);
+
+out:
+ free(buf);
+ close(fp);
+ return 0;
+}
+
static void print_usage(void)
{
fprintf(stderr, "usage: btrfs-image [options] source target\n");
@@ -2286,12 +2434,14 @@ int main(int argc, char *argv[])
int create = 1;
int old_restore = 0;
int walk_trees = 0;
+ int multi_devices = 0;
int ret;
int sanitize = 0;
+ int dev_cnt = 0;
FILE *out;
while (1) {
- int c = getopt(argc, argv, "rc:t:osw");
+ int c = getopt(argc, argv, "rc:t:oswm");
if (c < 0)
break;
switch (c) {
@@ -2317,17 +2467,26 @@ int main(int argc, char *argv[])
case 'w':
walk_trees = 1;
break;
+ case 'm':
+ create = 0;
+ multi_devices = 1;
+ break;
default:
print_usage();
}
}
- if (old_restore && create)
+ if ((old_restore) && create)
print_usage();
argc = argc - optind;
- if (argc != 2)
+ dev_cnt = argc - 1;
+
+ if (multi_devices && dev_cnt < 2)
+ print_usage();
+ if (!multi_devices && dev_cnt != 1)
print_usage();
+
source = argv[optind];
target = argv[optind + 1];
@@ -2351,8 +2510,60 @@ int main(int argc, char *argv[])
ret = create_metadump(source, out, num_threads,
compress_level, sanitize, walk_trees);
else
- ret = restore_metadump(source, out, old_restore, 1);
+ ret = restore_metadump(source, out, old_restore, 1,
+ multi_devices);
+ if (ret) {
+ printk("%s failed (%s)\n", (create) ? "create" : "restore",
+ strerror(errno));
+ goto out;
+ }
+
+ /* extended support for multiple devices */
+ if (!create && multi_devices) {
+ struct btrfs_fs_info *info;
+ u64 total_devs;
+ int i;
+
+ info = open_ctree_fs_info_restore(target, 0, 0, 0, 1);
+ if (!info) {
+ int e = errno;
+ fprintf(stderr, "unable to open %s error = %s\n",
+ target, strerror(e));
+ return 1;
+ }
+
+ total_devs = btrfs_super_num_devices(info->super_copy);
+ if (total_devs != dev_cnt) {
+ printk("it needs %llu devices but has only %d\n",
+ 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) {
+ printk("update disk super failed devid=%d (error=%d)\n",
+ i, ret);
+ close_ctree(info->chunk_root);
+ exit(1);
+ }
+ }
+
+ close_ctree(info->chunk_root);
+
+ /* fix metadata block to map correct chunk */
+ ret = fixup_metadump(source, out, 1, target);
+ if (ret) {
+ fprintf(stderr, "fix metadump failed (error=%d)\n",
+ ret);
+ exit(1);
+ }
+ }
+
+out:
if (out == stdout)
fflush(out);
else