diff --git a/btrfs-image.c b/btrfs-image.c index 265cbe62..9e13978c 100644 --- a/btrfs-image.c +++ b/btrfs-image.c @@ -110,10 +110,15 @@ struct mdrestore_struct { struct list_head list; size_t num_items; + u64 leafsize; + u64 devid; + u8 uuid[BTRFS_UUID_SIZE]; + u8 fsid[BTRFS_FSID_SIZE]; int compress_method; int done; int error; + int old_restore; }; static void csum_block(u8 *buf, size_t len) @@ -254,7 +259,7 @@ static void meta_cluster_init(struct metadump_struct *md, u64 start) static int metadump_init(struct metadump_struct *md, struct btrfs_root *root, FILE *out, int num_threads, int compress_level) { - int i, ret; + int i, ret = 0; memset(md, 0, sizeof(*md)); pthread_cond_init(&md->cond, NULL); @@ -894,7 +899,7 @@ out: return err ? err : ret; } -static void update_super(u8 *buffer) +static void update_super_old(u8 *buffer) { struct btrfs_super_block *super = (struct btrfs_super_block *)buffer; struct btrfs_chunk *chunk; @@ -929,6 +934,221 @@ static void update_super(u8 *buffer) csum_block(buffer, 4096); } +static int update_super(u8 *buffer) +{ + struct btrfs_super_block *super = (struct btrfs_super_block *)buffer; + struct btrfs_chunk *chunk; + struct btrfs_disk_key *disk_key; + struct btrfs_key key; + u32 new_array_size = 0; + u32 array_size; + u32 cur = 0; + u32 new_cur = 0; + u8 *ptr, *write_ptr; + int old_num_stripes; + + write_ptr = ptr = super->sys_chunk_array; + array_size = btrfs_super_sys_array_size(super); + + while (cur < array_size) { + disk_key = (struct btrfs_disk_key *)ptr; + btrfs_disk_key_to_cpu(&key, disk_key); + + new_array_size += sizeof(*disk_key); + memmove(write_ptr, ptr, sizeof(*disk_key)); + + write_ptr += sizeof(*disk_key); + ptr += sizeof(*disk_key); + cur += sizeof(*disk_key); + new_cur += sizeof(*disk_key); + + if (key.type == BTRFS_CHUNK_ITEM_KEY) { + chunk = (struct btrfs_chunk *)ptr; + old_num_stripes = btrfs_stack_chunk_num_stripes(chunk); + chunk = (struct btrfs_chunk *)write_ptr; + + memmove(write_ptr, ptr, sizeof(*chunk)); + btrfs_set_stack_chunk_num_stripes(chunk, 1); + btrfs_set_stack_chunk_sub_stripes(chunk, 0); + btrfs_set_stack_chunk_type(chunk, + BTRFS_BLOCK_GROUP_SYSTEM); + chunk->stripe.devid = super->dev_item.devid; + chunk->stripe.offset = cpu_to_le64(key.offset); + memcpy(chunk->stripe.dev_uuid, super->dev_item.uuid, + BTRFS_UUID_SIZE); + new_array_size += sizeof(*chunk); + new_cur += sizeof(*chunk); + } else { + fprintf(stderr, "Bogus key in the sys chunk array " + "%d\n", key.type); + return -EIO; + } + write_ptr += sizeof(*chunk); + ptr += btrfs_chunk_item_size(old_num_stripes); + cur += btrfs_chunk_item_size(old_num_stripes); + } + + btrfs_set_super_sys_array_size(super, new_array_size); + csum_block(buffer, 4096); + + return 0; +} + +static struct extent_buffer *alloc_dummy_eb(u64 bytenr, u32 size) +{ + struct extent_buffer *eb; + + eb = malloc(sizeof(struct extent_buffer) + size); + if (!eb) + return NULL; + memset(eb, 0, sizeof(struct extent_buffer) + size); + + eb->start = bytenr; + eb->len = size; + return eb; +} + +static void truncate_item(struct extent_buffer *eb, int slot, u32 new_size) +{ + struct btrfs_item *item; + u32 nritems; + u32 old_size; + u32 old_data_start; + u32 size_diff; + u32 data_end; + int i; + + old_size = btrfs_item_size_nr(eb, slot); + if (old_size == new_size) + return; + + nritems = btrfs_header_nritems(eb); + data_end = btrfs_item_offset_nr(eb, nritems - 1); + + old_data_start = btrfs_item_offset_nr(eb, slot); + size_diff = old_size - new_size; + + for (i = slot; i < nritems; i++) { + u32 ioff; + item = btrfs_item_nr(eb, i); + ioff = btrfs_item_offset(eb, item); + btrfs_set_item_offset(eb, item, ioff + size_diff); + } + + memmove_extent_buffer(eb, btrfs_leaf_data(eb) + data_end + size_diff, + btrfs_leaf_data(eb) + data_end, + old_data_start + new_size - data_end); + item = btrfs_item_nr(eb, slot); + btrfs_set_item_size(eb, item, new_size); +} + +static int fixup_chunk_tree_block(struct mdrestore_struct *mdres, + struct async_work *async, u8 *buffer, + size_t size) +{ + struct extent_buffer *eb; + size_t size_left = size; + u64 bytenr = async->start; + int i; + + if (size_left % mdres->leafsize) + return 0; + + eb = alloc_dummy_eb(bytenr, mdres->leafsize); + if (!eb) + return -ENOMEM; + + while (size_left) { + eb->start = bytenr; + memcpy(eb->data, buffer, mdres->leafsize); + + if (btrfs_header_bytenr(eb) != bytenr) + break; + if (memcmp(mdres->fsid, + eb->data + offsetof(struct btrfs_header, fsid), + BTRFS_FSID_SIZE)) + break; + + if (btrfs_header_owner(eb) != BTRFS_CHUNK_TREE_OBJECTID) + goto next; + + if (btrfs_header_level(eb) != 0) + goto next; + + for (i = 0; i < btrfs_header_nritems(eb); i++) { + struct btrfs_chunk chunk; + struct btrfs_key key; + u64 type; + + btrfs_item_key_to_cpu(eb, &key, i); + if (key.type != BTRFS_CHUNK_ITEM_KEY) + continue; + truncate_item(eb, i, sizeof(chunk)); + read_extent_buffer(eb, &chunk, + btrfs_item_ptr_offset(eb, i), + sizeof(chunk)); + + /* Zero out the RAID profile */ + type = btrfs_stack_chunk_type(&chunk); + type &= (BTRFS_BLOCK_GROUP_DATA | + BTRFS_BLOCK_GROUP_SYSTEM | + BTRFS_BLOCK_GROUP_METADATA); + btrfs_set_stack_chunk_type(&chunk, type); + + btrfs_set_stack_chunk_num_stripes(&chunk, 1); + btrfs_set_stack_chunk_sub_stripes(&chunk, 0); + btrfs_set_stack_stripe_devid(&chunk.stripe, mdres->devid); + btrfs_set_stack_stripe_offset(&chunk.stripe, key.offset); + memcpy(chunk.stripe.dev_uuid, mdres->uuid, + BTRFS_UUID_SIZE); + write_extent_buffer(eb, &chunk, + btrfs_item_ptr_offset(eb, i), + sizeof(chunk)); + } + memcpy(buffer, eb->data, eb->len); + csum_block(buffer, eb->len); +next: + size_left -= mdres->leafsize; + buffer += mdres->leafsize; + bytenr += mdres->leafsize; + } + + return 0; +} + +static void write_backup_supers(int fd, u8 *buf) +{ + struct stat st; + u64 size; + u64 bytenr; + int i; + int ret; + + if (fstat(fd, &st)) { + fprintf(stderr, "Couldn't stat restore point, won't be able " + "to write backup supers: %d\n", errno); + return; + } + + size = btrfs_device_size(fd, &st); + + for (i = 1; i < BTRFS_SUPER_MIRROR_MAX; i++) { + bytenr = btrfs_sb_offset(i); + if (bytenr + 4096 > size) + break; + ret = pwrite64(fd, buf, 4096, bytenr); + if (ret < 4096) { + if (ret < 0) + fprintf(stderr, "Problem writing out backup " + "super block %d, err %d\n", i, errno); + else + fprintf(stderr, "Short write writing out " + "backup super block\n"); + break; + } + } +} + static void *restore_worker(void *data) { struct mdrestore_struct *mdres = (struct mdrestore_struct *)data; @@ -954,7 +1174,7 @@ static void *restore_worker(void *data) int err = 0; pthread_mutex_lock(&mdres->mutex); - while (list_empty(&mdres->list)) { + while (!mdres->leafsize || list_empty(&mdres->list)) { if (mdres->done) { pthread_mutex_unlock(&mdres->mutex); goto out; @@ -980,8 +1200,19 @@ static void *restore_worker(void *data) size = async->bufsize; } - if (async->start == BTRFS_SUPER_INFO_OFFSET) - update_super(outbuf); + 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; + } ret = pwrite64(outfd, outbuf, size, async->start); if (ret < size) { @@ -995,6 +1226,9 @@ static void *restore_worker(void *data) } } + if (async->start == BTRFS_SUPER_INFO_OFFSET) + write_backup_supers(outfd, outbuf); + pthread_mutex_lock(&mdres->mutex); if (err && !mdres->error) mdres->error = err; @@ -1026,7 +1260,8 @@ static void mdrestore_destroy(struct mdrestore_struct *mdres) } static int mdrestore_init(struct mdrestore_struct *mdres, - FILE *in, FILE *out, int num_threads) + FILE *in, FILE *out, int old_restore, + int num_threads) { int i, ret = 0; @@ -1036,6 +1271,7 @@ static int mdrestore_init(struct mdrestore_struct *mdres, INIT_LIST_HEAD(&mdres->list); mdres->in = in; mdres->out = out; + mdres->old_restore = old_restore; if (!num_threads) return 0; @@ -1055,6 +1291,42 @@ static int mdrestore_init(struct mdrestore_struct *mdres, return ret; } +static int fill_mdres_info(struct mdrestore_struct *mdres, + struct async_work *async) +{ + struct btrfs_super_block *super; + u8 *buffer = NULL; + u8 *outbuf; + int ret; + + if (mdres->compress_method == COMPRESS_ZLIB) { + size_t size = MAX_PENDING_SIZE * 2; + + buffer = malloc(MAX_PENDING_SIZE * 2); + if (!buffer) + return -ENOMEM; + ret = uncompress(buffer, (unsigned long *)&size, + async->buffer, async->bufsize); + if (ret != Z_OK) { + fprintf(stderr, "Error decompressing %d\n", ret); + free(buffer); + return -EIO; + } + outbuf = buffer; + } else { + outbuf = async->buffer; + } + + super = (struct btrfs_super_block *)outbuf; + mdres->leafsize = btrfs_super_leafsize(super); + memcpy(mdres->fsid, super->fsid, BTRFS_FSID_SIZE); + memcpy(mdres->uuid, super->dev_item.uuid, + BTRFS_UUID_SIZE); + mdres->devid = le64_to_cpu(super->dev_item.devid); + free(buffer); + return 0; +} + static int add_cluster(struct meta_cluster *cluster, struct mdrestore_struct *mdres, u64 *next) { @@ -1095,6 +1367,16 @@ static int add_cluster(struct meta_cluster *cluster, bytenr += async->bufsize; pthread_mutex_lock(&mdres->mutex); + if (async->start == BTRFS_SUPER_INFO_OFFSET) { + ret = fill_mdres_info(mdres, async); + if (ret) { + fprintf(stderr, "Error setting up restore\n"); + pthread_mutex_unlock(&mdres->mutex); + free(async->buffer); + free(async); + return ret; + } + } list_add_tail(&async->list, &mdres->list); mdres->num_items++; pthread_cond_signal(&mdres->cond); @@ -1135,7 +1417,8 @@ static int wait_for_worker(struct mdrestore_struct *mdres) return ret; } -static int restore_metadump(const char *input, FILE *out, int num_threads) +static int restore_metadump(const char *input, FILE *out, int old_restore, + int num_threads) { struct meta_cluster *cluster = NULL; struct meta_cluster_header *header; @@ -1162,7 +1445,7 @@ static int restore_metadump(const char *input, FILE *out, int num_threads) return -ENOMEM; } - ret = mdrestore_init(&mdrestore, in, out, num_threads); + ret = mdrestore_init(&mdrestore, in, out, old_restore, num_threads); if (ret) { fprintf(stderr, "Error initing mdrestore %d\n", ret); if (in != stdin) @@ -1210,6 +1493,7 @@ static void print_usage(void) fprintf(stderr, "\t-r \trestore metadump image\n"); fprintf(stderr, "\t-c value\tcompression level (0 ~ 9)\n"); fprintf(stderr, "\t-t value\tnumber of threads (1 ~ 32)\n"); + fprintf(stderr, "\t-o \tdon't mess with the chunk tree when restoring\n"); exit(1); } @@ -1220,11 +1504,12 @@ int main(int argc, char *argv[]) int num_threads = 0; int compress_level = 0; int create = 1; + int old_restore = 0; int ret; FILE *out; while (1) { - int c = getopt(argc, argv, "rc:t:"); + int c = getopt(argc, argv, "rc:t:o"); if (c < 0) break; switch (c) { @@ -1241,11 +1526,17 @@ int main(int argc, char *argv[]) if (compress_level < 0 || compress_level > 9) print_usage(); break; + case 'o': + old_restore = 1; + break; default: print_usage(); } } + if (old_restore && create) + print_usage(); + argc = argc - optind; if (argc != 2) print_usage(); @@ -1272,7 +1563,7 @@ int main(int argc, char *argv[]) ret = create_metadump(source, out, num_threads, compress_level); else - ret = restore_metadump(source, out, 1); + ret = restore_metadump(source, out, old_restore, 1); if (out == stdout) fflush(out); diff --git a/man/btrfs-image.8.in b/man/btrfs-image.8.in index f095cbd7..b738f672 100644 --- a/man/btrfs-image.8.in +++ b/man/btrfs-image.8.in @@ -24,6 +24,10 @@ compression level (0 ~ 9). .TP \fB\-t\fR \fIvalue\fP number of threads (1 ~ 32) to be used to process the image dump or restore. +.TP +\fB\-o\fP +use the old restore method, this does not fixup the chunk tree so the restored +file system will not be able to be mounted. .SH AVAILABILITY .B btrfs-image is part of btrfs-progs. Btrfs is currently under heavy development, diff --git a/utils.c b/utils.c index 7b028a79..a4f7b069 100644 --- a/utils.c +++ b/utils.c @@ -415,7 +415,7 @@ int make_btrfs(int fd, const char *device, const char *label, return 0; } -static u64 device_size(int fd, struct stat *st) +u64 btrfs_device_size(int fd, struct stat *st) { u64 size; if (S_ISREG(st->st_mode)) { @@ -555,7 +555,7 @@ int btrfs_prepare_device(int fd, char *file, int zero_end, u64 *block_count_ret, exit(1); } - block_count = device_size(fd, &st); + block_count = btrfs_device_size(fd, &st); if (block_count == 0) { fprintf(stderr, "unable to find %s size\n", file); exit(1); diff --git a/utils.h b/utils.h index 885b9c54..4dcdc31a 100644 --- a/utils.h +++ b/utils.h @@ -19,6 +19,7 @@ #ifndef __UTILS__ #define __UTILS__ +#include #include "ctree.h" #define BTRFS_MKFS_SYSTEM_GROUP_SIZE (4 * 1024 * 1024) @@ -60,6 +61,7 @@ int is_block_device(const char *file); int get_btrfs_mount(const char *path, char *mp, size_t mp_size); int open_path_or_dev_mnt(const char *path); int is_swap_device(const char *file); +u64 btrfs_device_size(int fd, struct stat *st); /* Helper to always get proper size of the destination string */ #define strncpy_null(dest, src) __strncpy__null(dest, src, sizeof(dest))