X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=btrfs-image.c;h=af5437b445b64d55c288f48250c900df3790fc8c;hb=dad817d3bad44619c355d11e39512b413da6939c;hp=9a0a2493808283f03dd4bdbe5a3f5400686d2d7e;hpb=7b20da8d525d11dabc98bdd49efef7b8be5576ab;p=platform%2Fupstream%2Fbtrfs-progs.git diff --git a/btrfs-image.c b/btrfs-image.c index 9a0a249..af5437b 100644 --- a/btrfs-image.c +++ b/btrfs-image.c @@ -16,8 +16,6 @@ * Boston, MA 021110-1307, USA. */ -#define _XOPEN_SOURCE 500 -#define _GNU_SOURCE 1 #include #include #include @@ -27,14 +25,16 @@ #include #include #include +#include + #include "kerncompat.h" #include "crc32c.h" #include "ctree.h" #include "disk-io.h" #include "transaction.h" #include "utils.h" -#include "version.h" #include "volumes.h" +#include "extent_io.h" #define HEADER_MAGIC 0xbd5c25e27295668bULL #define MAX_PENDING_SIZE (256 * 1024) @@ -65,6 +65,22 @@ struct meta_cluster { #define ITEMS_PER_CLUSTER ((BLOCK_SIZE - sizeof(struct meta_cluster)) / \ sizeof(struct meta_cluster_item)) +struct fs_chunk { + u64 logical; + u64 physical; + /* + * physical_dup only store additonal physical for BTRFS_BLOCK_GROUP_DUP + * currently restore only support single and DUP + * TODO: modify this structure and the function related to this + * structure for support RAID* + */ + u64 physical_dup; + u64 bytes; + struct rb_node l; + struct rb_node p; + struct list_head list; +}; + struct async_work { struct list_head list; struct list_head ordered; @@ -85,6 +101,7 @@ struct metadump_struct { size_t num_threads; pthread_mutex_t mutex; pthread_cond_t cond; + struct rb_root name_tree; struct list_head list; struct list_head ordered; @@ -97,6 +114,16 @@ struct metadump_struct { int compress_level; int done; int data; + int sanitize_names; + + int error; +}; + +struct name { + struct rb_node n; + char *val; + char *sub; + u32 len; }; struct mdrestore_struct { @@ -108,10 +135,15 @@ struct mdrestore_struct { pthread_mutex_t mutex; pthread_cond_t cond; + struct rb_root chunk_tree; + struct rb_root physical_tree; struct list_head list; + struct list_head overlapping_chunks; size_t num_items; - u64 leafsize; + u32 nodesize; u64 devid; + u64 alloced_chunks; + u64 last_physical_offset; u8 uuid[BTRFS_UUID_SIZE]; u8 fsid[BTRFS_FSID_SIZE]; @@ -119,8 +151,16 @@ struct mdrestore_struct { int done; int error; int old_restore; + int fixup_offset; + int multi_devices; + int clear_space_cache; + struct btrfs_fs_info *info; }; +static int search_for_chunk_blocks(struct mdrestore_struct *mdres, + u64 search, u64 cluster_bytenr); +static struct extent_buffer *alloc_dummy_eb(u64 bytenr, u32 size); + static void csum_block(u8 *buf, size_t len) { char result[BTRFS_CRC32_SIZE]; @@ -130,10 +170,412 @@ static void csum_block(u8 *buf, size_t len) memcpy(buf, result, BTRFS_CRC32_SIZE); } +static int has_name(struct btrfs_key *key) +{ + switch (key->type) { + case BTRFS_DIR_ITEM_KEY: + case BTRFS_DIR_INDEX_KEY: + case BTRFS_INODE_REF_KEY: + case BTRFS_INODE_EXTREF_KEY: + case BTRFS_XATTR_ITEM_KEY: + return 1; + default: + break; + } + + return 0; +} + +static char *generate_garbage(u32 name_len) +{ + char *buf = malloc(name_len); + int i; + + if (!buf) + return NULL; + + for (i = 0; i < name_len; i++) { + char c = rand_range(94) + 33; + + if (c == '/') + c++; + buf[i] = c; + } + + return buf; +} + +static int name_cmp(struct rb_node *a, struct rb_node *b, int fuzz) +{ + struct name *entry = rb_entry(a, struct name, n); + struct name *ins = rb_entry(b, struct name, n); + u32 len; + + len = min(ins->len, entry->len); + return memcmp(ins->val, entry->val, len); +} + +static int chunk_cmp(struct rb_node *a, struct rb_node *b, int fuzz) +{ + struct fs_chunk *entry = rb_entry(a, struct fs_chunk, l); + struct fs_chunk *ins = rb_entry(b, struct fs_chunk, l); + + if (fuzz && ins->logical >= entry->logical && + ins->logical < entry->logical + entry->bytes) + return 0; + + if (ins->logical < entry->logical) + return -1; + else if (ins->logical > entry->logical) + return 1; + return 0; +} + +static int physical_cmp(struct rb_node *a, struct rb_node *b, int fuzz) +{ + struct fs_chunk *entry = rb_entry(a, struct fs_chunk, p); + struct fs_chunk *ins = rb_entry(b, struct fs_chunk, p); + + if (fuzz && ins->physical >= entry->physical && + ins->physical < entry->physical + entry->bytes) + return 0; + + if (fuzz && entry->physical >= ins->physical && + entry->physical < ins->physical + ins->bytes) + return 0; + + if (ins->physical < entry->physical) + return -1; + else if (ins->physical > entry->physical) + return 1; + return 0; +} + +static void tree_insert(struct rb_root *root, struct rb_node *ins, + int (*cmp)(struct rb_node *a, struct rb_node *b, + int fuzz)) +{ + struct rb_node ** p = &root->rb_node; + struct rb_node * parent = NULL; + int dir; + + while(*p) { + parent = *p; + + dir = cmp(*p, ins, 1); + if (dir < 0) + p = &(*p)->rb_left; + else if (dir > 0) + p = &(*p)->rb_right; + else + BUG(); + } + + rb_link_node(ins, parent, p); + rb_insert_color(ins, root); +} + +static struct rb_node *tree_search(struct rb_root *root, + struct rb_node *search, + int (*cmp)(struct rb_node *a, + struct rb_node *b, int fuzz), + int fuzz) +{ + struct rb_node *n = root->rb_node; + int dir; + + while (n) { + dir = cmp(n, search, fuzz); + if (dir < 0) + n = n->rb_left; + else if (dir > 0) + n = n->rb_right; + else + return n; + } + + return NULL; +} + +static u64 logical_to_physical(struct mdrestore_struct *mdres, u64 logical, + u64 *size, u64 *physical_dup) +{ + struct fs_chunk *fs_chunk; + struct rb_node *entry; + struct fs_chunk search; + u64 offset; + + if (logical == BTRFS_SUPER_INFO_OFFSET) + return logical; + + search.logical = logical; + entry = tree_search(&mdres->chunk_tree, &search.l, chunk_cmp, 1); + if (!entry) { + if (mdres->in != stdin) + printf("Couldn't find a chunk, using logical\n"); + return logical; + } + fs_chunk = rb_entry(entry, struct fs_chunk, l); + if (fs_chunk->logical > logical || fs_chunk->logical + fs_chunk->bytes < logical) + BUG(); + offset = search.logical - fs_chunk->logical; + + if (physical_dup) { + /* Only in dup case, physical_dup is not equal to 0 */ + if (fs_chunk->physical_dup) + *physical_dup = fs_chunk->physical_dup + offset; + else + *physical_dup = 0; + } + + *size = min(*size, fs_chunk->bytes + fs_chunk->logical - logical); + return fs_chunk->physical + offset; +} + + +static char *find_collision(struct metadump_struct *md, char *name, + u32 name_len) +{ + struct name *val; + struct rb_node *entry; + struct name tmp; + unsigned long checksum; + int found = 0; + int i; + + tmp.val = name; + tmp.len = name_len; + entry = tree_search(&md->name_tree, &tmp.n, name_cmp, 0); + if (entry) { + val = rb_entry(entry, struct name, n); + free(name); + return val->sub; + } + + val = malloc(sizeof(struct name)); + if (!val) { + fprintf(stderr, "Couldn't sanitize name, enomem\n"); + free(name); + return NULL; + } + + memset(val, 0, sizeof(*val)); + + val->val = name; + val->len = name_len; + val->sub = malloc(name_len); + if (!val->sub) { + fprintf(stderr, "Couldn't sanitize name, enomem\n"); + free(val); + free(name); + return NULL; + } + + checksum = crc32c(~1, val->val, name_len); + memset(val->sub, ' ', name_len); + i = 0; + while (1) { + if (crc32c(~1, val->sub, name_len) == checksum && + memcmp(val->sub, val->val, val->len)) { + found = 1; + break; + } + + if (val->sub[i] == 127) { + do { + i++; + if (i >= name_len) + break; + } while (val->sub[i] == 127); + + if (i >= name_len) + break; + val->sub[i]++; + if (val->sub[i] == '/') + val->sub[i]++; + memset(val->sub, ' ', i); + i = 0; + continue; + } else { + val->sub[i]++; + if (val->sub[i] == '/') + val->sub[i]++; + } + } + + if (!found) { + fprintf(stderr, "Couldn't find a collision for '%.*s', " + "generating normal garbage, it won't match indexes\n", + val->len, val->val); + for (i = 0; i < name_len; i++) { + char c = rand_range(94) + 33; + + if (c == '/') + c++; + val->sub[i] = c; + } + } + + tree_insert(&md->name_tree, &val->n, name_cmp); + return val->sub; +} + +static void sanitize_dir_item(struct metadump_struct *md, struct extent_buffer *eb, + int slot) +{ + struct btrfs_dir_item *dir_item; + char *buf; + char *garbage; + unsigned long name_ptr; + u32 total_len; + u32 cur = 0; + u32 this_len; + u32 name_len; + int free_garbage = (md->sanitize_names == 1); + + dir_item = btrfs_item_ptr(eb, slot, struct btrfs_dir_item); + total_len = btrfs_item_size_nr(eb, slot); + while (cur < total_len) { + this_len = sizeof(*dir_item) + + btrfs_dir_name_len(eb, dir_item) + + btrfs_dir_data_len(eb, dir_item); + name_ptr = (unsigned long)(dir_item + 1); + name_len = btrfs_dir_name_len(eb, dir_item); + + if (md->sanitize_names > 1) { + buf = malloc(name_len); + if (!buf) { + fprintf(stderr, "Couldn't sanitize name, " + "enomem\n"); + return; + } + read_extent_buffer(eb, buf, name_ptr, name_len); + garbage = find_collision(md, buf, name_len); + } else { + garbage = generate_garbage(name_len); + } + if (!garbage) { + fprintf(stderr, "Couldn't sanitize name, enomem\n"); + return; + } + write_extent_buffer(eb, garbage, name_ptr, name_len); + cur += this_len; + dir_item = (struct btrfs_dir_item *)((char *)dir_item + + this_len); + if (free_garbage) + free(garbage); + } +} + +static void sanitize_inode_ref(struct metadump_struct *md, + struct extent_buffer *eb, int slot, int ext) +{ + struct btrfs_inode_extref *extref; + struct btrfs_inode_ref *ref; + char *garbage, *buf; + unsigned long ptr; + unsigned long name_ptr; + u32 item_size; + u32 cur_offset = 0; + int len; + int free_garbage = (md->sanitize_names == 1); + + item_size = btrfs_item_size_nr(eb, slot); + ptr = btrfs_item_ptr_offset(eb, slot); + while (cur_offset < item_size) { + if (ext) { + extref = (struct btrfs_inode_extref *)(ptr + + cur_offset); + name_ptr = (unsigned long)(&extref->name); + len = btrfs_inode_extref_name_len(eb, extref); + cur_offset += sizeof(*extref); + } else { + ref = (struct btrfs_inode_ref *)(ptr + cur_offset); + len = btrfs_inode_ref_name_len(eb, ref); + name_ptr = (unsigned long)(ref + 1); + cur_offset += sizeof(*ref); + } + cur_offset += len; + + if (md->sanitize_names > 1) { + buf = malloc(len); + if (!buf) { + fprintf(stderr, "Couldn't sanitize name, " + "enomem\n"); + return; + } + read_extent_buffer(eb, buf, name_ptr, len); + garbage = find_collision(md, buf, len); + } else { + garbage = generate_garbage(len); + } + + if (!garbage) { + fprintf(stderr, "Couldn't sanitize name, enomem\n"); + return; + } + write_extent_buffer(eb, garbage, name_ptr, len); + if (free_garbage) + free(garbage); + } +} + +static void sanitize_xattr(struct metadump_struct *md, + struct extent_buffer *eb, int slot) +{ + struct btrfs_dir_item *dir_item; + unsigned long data_ptr; + u32 data_len; + + dir_item = btrfs_item_ptr(eb, slot, struct btrfs_dir_item); + data_len = btrfs_dir_data_len(eb, dir_item); + + data_ptr = (unsigned long)((char *)(dir_item + 1) + + btrfs_dir_name_len(eb, dir_item)); + memset_extent_buffer(eb, 0, data_ptr, data_len); +} + +static void sanitize_name(struct metadump_struct *md, u8 *dst, + struct extent_buffer *src, struct btrfs_key *key, + int slot) +{ + struct extent_buffer *eb; + + eb = alloc_dummy_eb(src->start, src->len); + if (!eb) { + fprintf(stderr, "Couldn't sanitize name, no memory\n"); + return; + } + + memcpy(eb->data, dst, eb->len); + + switch (key->type) { + case BTRFS_DIR_ITEM_KEY: + case BTRFS_DIR_INDEX_KEY: + sanitize_dir_item(md, eb, slot); + break; + case BTRFS_INODE_REF_KEY: + sanitize_inode_ref(md, eb, slot, 0); + break; + case BTRFS_INODE_EXTREF_KEY: + sanitize_inode_ref(md, eb, slot, 1); + break; + case BTRFS_XATTR_ITEM_KEY: + sanitize_xattr(md, eb, slot); + break; + default: + break; + } + + memcpy(dst, eb->data, eb->len); + free(eb); +} + /* * zero inline extents and csum items */ -static void zero_items(u8 *dst, struct extent_buffer *src) +static void zero_items(struct metadump_struct *md, u8 *dst, + struct extent_buffer *src) { struct btrfs_file_extent_item *fi; struct btrfs_item *item; @@ -144,7 +586,7 @@ static void zero_items(u8 *dst, struct extent_buffer *src) int i, extent_type; for (i = 0; i < nritems; i++) { - item = btrfs_item_nr(src, i); + item = btrfs_item_nr(i); btrfs_item_key_to_cpu(src, &key, i); if (key.type == BTRFS_CSUM_ITEM_KEY) { size = btrfs_item_size_nr(src, i); @@ -152,6 +594,12 @@ static void zero_items(u8 *dst, struct extent_buffer *src) btrfs_item_offset_nr(src, i), 0, size); continue; } + + if (md->sanitize_names && has_name(&key)) { + sanitize_name(md, dst, src, &key, i); + continue; + } + if (key.type != BTRFS_EXTENT_DATA_KEY) continue; @@ -169,7 +617,8 @@ static void zero_items(u8 *dst, struct extent_buffer *src) /* * copy buffer and zero useless data in the buffer */ -static void copy_buffer(u8 *dst, struct extent_buffer *src) +static void copy_buffer(struct metadump_struct *md, u8 *dst, + struct extent_buffer *src) { int level; size_t size; @@ -190,7 +639,7 @@ static void copy_buffer(u8 *dst, struct extent_buffer *src) btrfs_item_offset_nr(src, nritems - 1) - btrfs_item_nr_offset(nritems); memset(dst + btrfs_item_nr_offset(nritems), 0, size); - zero_items(dst, src); + zero_items(md, dst, src); } else { size = offsetof(struct btrfs_node, ptrs) + sizeof(struct btrfs_key_ptr) * nritems; @@ -223,6 +672,14 @@ static void *dump_worker(void *data) async->bufsize = compressBound(async->size); async->buffer = malloc(async->bufsize); + if (!async->buffer) { + fprintf(stderr, "Error allocating buffer\n"); + pthread_mutex_lock(&md->mutex); + if (!md->error) + md->error = -ENOMEM; + pthread_mutex_unlock(&md->mutex); + pthread_exit(NULL); + } ret = compress2(async->buffer, (unsigned long *)&async->bufsize, @@ -256,81 +713,81 @@ static void meta_cluster_init(struct metadump_struct *md, u64 start) COMPRESS_ZLIB : COMPRESS_NONE; } +static void metadump_destroy(struct metadump_struct *md, int num_threads) +{ + int i; + struct rb_node *n; + + pthread_mutex_lock(&md->mutex); + md->done = 1; + pthread_cond_broadcast(&md->cond); + pthread_mutex_unlock(&md->mutex); + + for (i = 0; i < num_threads; i++) + pthread_join(md->threads[i], NULL); + + pthread_cond_destroy(&md->cond); + pthread_mutex_destroy(&md->mutex); + + while ((n = rb_first(&md->name_tree))) { + struct name *name; + + name = rb_entry(n, struct name, n); + rb_erase(n, &md->name_tree); + free(name->val); + free(name->sub); + free(name); + } + free(md->threads); + free(md->cluster); +} + static int metadump_init(struct metadump_struct *md, struct btrfs_root *root, - FILE *out, int num_threads, int compress_level) + FILE *out, int num_threads, int compress_level, + int sanitize_names) { int i, ret = 0; memset(md, 0, sizeof(*md)); - pthread_cond_init(&md->cond, NULL); - pthread_mutex_init(&md->mutex, NULL); + md->cluster = calloc(1, BLOCK_SIZE); + if (!md->cluster) + return -ENOMEM; + md->threads = calloc(num_threads, sizeof(pthread_t)); + if (!md->threads) { + free(md->cluster); + return -ENOMEM; + } INIT_LIST_HEAD(&md->list); INIT_LIST_HEAD(&md->ordered); md->root = root; md->out = out; md->pending_start = (u64)-1; md->compress_level = compress_level; - md->cluster = calloc(1, BLOCK_SIZE); - if (!md->cluster) { - pthread_cond_destroy(&md->cond); - pthread_mutex_destroy(&md->mutex); - return -ENOMEM; - } + md->sanitize_names = sanitize_names; + if (sanitize_names > 1) + crc32c_optimization_init(); + md->name_tree.rb_node = NULL; + md->num_threads = num_threads; + pthread_cond_init(&md->cond, NULL); + pthread_mutex_init(&md->mutex, NULL); meta_cluster_init(md, 0); + if (!num_threads) return 0; - md->num_threads = num_threads; - md->threads = calloc(num_threads, sizeof(pthread_t)); - if (!md->threads) { - free(md->cluster); - pthread_cond_destroy(&md->cond); - pthread_mutex_destroy(&md->mutex); - return -ENOMEM; - } - for (i = 0; i < num_threads; i++) { ret = pthread_create(md->threads + i, NULL, dump_worker, md); if (ret) break; } - if (ret) { - pthread_mutex_lock(&md->mutex); - md->done = 1; - pthread_cond_broadcast(&md->cond); - pthread_mutex_unlock(&md->mutex); - - for (i--; i >= 0; i--) - pthread_join(md->threads[i], NULL); - - pthread_cond_destroy(&md->cond); - pthread_mutex_destroy(&md->mutex); - free(md->cluster); - free(md->threads); - } + if (ret) + metadump_destroy(md, i + 1); return ret; } -static void metadump_destroy(struct metadump_struct *md) -{ - int i; - pthread_mutex_lock(&md->mutex); - md->done = 1; - pthread_cond_broadcast(&md->cond); - pthread_mutex_unlock(&md->mutex); - - for (i = 0; i < md->num_threads; i++) - pthread_join(md->threads[i], NULL); - - pthread_cond_destroy(&md->cond); - pthread_mutex_destroy(&md->mutex); - free(md->threads); - free(md->cluster); -} - static int write_zero(FILE *out, size_t size) { static char zero[BLOCK_SIZE]; @@ -351,7 +808,7 @@ static int write_buffers(struct metadump_struct *md, u64 *next) goto out; /* wait until all buffers are compressed */ - while (md->num_items > md->num_ready) { + while (!err && md->num_items > md->num_ready) { struct timespec ts = { .tv_sec = 0, .tv_nsec = 10000000, @@ -359,6 +816,13 @@ static int write_buffers(struct metadump_struct *md, u64 *next) pthread_mutex_unlock(&md->mutex); nanosleep(&ts, NULL); pthread_mutex_lock(&md->mutex); + err = md->error; + } + + if (err) { + fprintf(stderr, "One of the threads errored out %s\n", + strerror(err)); + goto out; } /* setup and write index block */ @@ -418,57 +882,46 @@ out: static int read_data_extent(struct metadump_struct *md, struct async_work *async) { - struct btrfs_multi_bio *multi = NULL; - struct btrfs_device *device; + struct btrfs_root *root = md->root; u64 bytes_left = async->size; u64 logical = async->start; u64 offset = 0; - u64 bytenr; u64 read_len; - ssize_t done; - int fd; + int num_copies; + int cur_mirror; int ret; - while (bytes_left) { - read_len = bytes_left; - ret = btrfs_map_block(&md->root->fs_info->mapping_tree, READ, - logical, &read_len, &multi, 0, NULL); - if (ret) { - fprintf(stderr, "Couldn't map data block %d\n", ret); - return ret; - } - - device = multi->stripes[0].dev; + num_copies = btrfs_num_copies(&root->fs_info->mapping_tree, logical, + bytes_left); - if (device->fd == 0) { - fprintf(stderr, - "Device we need to read from is not open\n"); - free(multi); - return -EIO; - } - fd = device->fd; - bytenr = multi->stripes[0].physical; - free(multi); - - read_len = min(read_len, bytes_left); - done = pread64(fd, async->buffer+offset, read_len, bytenr); - if (done < read_len) { - if (done < 0) - fprintf(stderr, "Error reading extent %d\n", - errno); - else - fprintf(stderr, "Short read\n"); - return -EIO; + /* Try our best to read data, just like read_tree_block() */ + for (cur_mirror = 0; cur_mirror < num_copies; cur_mirror++) { + while (bytes_left) { + read_len = bytes_left; + ret = read_extent_data(root, + (char *)(async->buffer + offset), + logical, &read_len, cur_mirror); + if (ret < 0) + break; + offset += read_len; + logical += read_len; + bytes_left -= read_len; } - - bytes_left -= done; - offset += done; - logical += done; } - + if (bytes_left) + return -EIO; return 0; } +static int get_dev_fd(struct btrfs_root *root) +{ + struct btrfs_device *dev; + + dev = list_first_entry(&root->fs_info->fs_devices->devices, + struct btrfs_device, dev_list); + return dev->fd; +} + static int flush_pending(struct metadump_struct *md, int done) { struct async_work *async = NULL; @@ -505,20 +958,39 @@ static int flush_pending(struct metadump_struct *md, int done) } } + /* + * Balance can make the mapping not cover the super block, so + * just copy directly from one of the devices. + */ + if (start == BTRFS_SUPER_INFO_OFFSET) { + int fd = get_dev_fd(md->root); + + ret = pread64(fd, async->buffer, size, start); + if (ret < size) { + free(async->buffer); + free(async); + fprintf(stderr, "Error reading superblock\n"); + return -EIO; + } + size = 0; + ret = 0; + } + while (!md->data && size > 0) { - eb = read_tree_block(md->root, start, blocksize, 0); - if (!eb) { + u64 this_read = min(blocksize, size); + eb = read_tree_block(md->root, start, this_read, 0); + if (!extent_buffer_uptodate(eb)) { free(async->buffer); free(async); fprintf(stderr, "Error reading metadata block\n"); return -EIO; } - copy_buffer(async->buffer + offset, eb); + copy_buffer(md, async->buffer + offset, eb); free_extent_buffer(eb); - start += blocksize; - offset += blocksize; - size -= blocksize; + start += this_read; + offset += this_read; + size -= this_read; } md->pending_start = (u64)-1; @@ -605,9 +1077,8 @@ static int is_tree_block(struct btrfs_root *extent_root, } #endif -static int copy_log_blocks(struct btrfs_root *root, struct extent_buffer *eb, - struct metadump_struct *metadump, - int log_root_tree) +static int copy_tree_blocks(struct btrfs_root *root, struct extent_buffer *eb, + struct metadump_struct *metadump, int root_tree) { struct extent_buffer *tmp; struct btrfs_root_item *ri; @@ -618,13 +1089,13 @@ static int copy_log_blocks(struct btrfs_root *root, struct extent_buffer *eb, int i = 0; int ret; - ret = add_extent(btrfs_header_bytenr(eb), root->leafsize, metadump, 0); + ret = add_extent(btrfs_header_bytenr(eb), root->nodesize, metadump, 0); if (ret) { fprintf(stderr, "Error adding metadata block\n"); return ret; } - if (btrfs_header_level(eb) == 0 && !log_root_tree) + if (btrfs_header_level(eb) == 0 && !root_tree) return 0; level = btrfs_header_level(eb); @@ -636,25 +1107,24 @@ static int copy_log_blocks(struct btrfs_root *root, struct extent_buffer *eb, continue; ri = btrfs_item_ptr(eb, i, struct btrfs_root_item); bytenr = btrfs_disk_root_bytenr(eb, ri); - tmp = read_tree_block(root, bytenr, root->leafsize, 0); - if (!tmp) { + tmp = read_tree_block(root, bytenr, root->nodesize, 0); + if (!extent_buffer_uptodate(tmp)) { fprintf(stderr, "Error reading log root block\n"); return -EIO; } - ret = copy_log_blocks(root, tmp, metadump, 0); + ret = copy_tree_blocks(root, tmp, metadump, 0); free_extent_buffer(tmp); if (ret) return ret; } else { bytenr = btrfs_node_blockptr(eb, i); - tmp = read_tree_block(root, bytenr, root->leafsize, 0); - if (!tmp) { + tmp = read_tree_block(root, bytenr, root->nodesize, 0); + if (!extent_buffer_uptodate(tmp)) { fprintf(stderr, "Error reading log block\n"); return -EIO; } - ret = copy_log_blocks(root, tmp, metadump, - log_root_tree); + ret = copy_tree_blocks(root, tmp, metadump, root_tree); free_extent_buffer(tmp); if (ret) return ret; @@ -679,8 +1149,8 @@ static int copy_log_trees(struct btrfs_root *root, return -EIO; } - return copy_log_blocks(root, root->fs_info->log_root_tree->node, - metadump, 1); + return copy_tree_blocks(root, root->fs_info->log_root_tree->node, + metadump, 1); } static int copy_space_cache(struct btrfs_root *root, @@ -706,8 +1176,9 @@ static int copy_space_cache(struct btrfs_root *root, return ret; } + leaf = path->nodes[0]; + while (1) { - leaf = path->nodes[0]; if (path->slots[0] >= btrfs_header_nritems(leaf)) { ret = btrfs_next_leaf(root, path); if (ret < 0) { @@ -740,7 +1211,7 @@ static int copy_space_cache(struct btrfs_root *root, if (ret) { fprintf(stderr, "Error adding space cache blocks %d\n", ret); - btrfs_release_path(root, path); + btrfs_release_path(path); return ret; } path->slots[0]++; @@ -749,75 +1220,44 @@ static int copy_space_cache(struct btrfs_root *root, return 0; } -static int create_metadump(const char *input, FILE *out, int num_threads, - int compress_level) +static int copy_from_extent_tree(struct metadump_struct *metadump, + struct btrfs_path *path) { - struct btrfs_root *root; struct btrfs_root *extent_root; - struct btrfs_path *path = NULL; struct extent_buffer *leaf; struct btrfs_extent_item *ei; struct btrfs_key key; - struct metadump_struct metadump; u64 bytenr; u64 num_bytes; int ret; - int err = 0; - - root = open_ctree(input, 0, 0); - if (!root) { - fprintf(stderr, "Open ctree failed\n"); - return -EIO; - } - BUG_ON(root->nodesize != root->leafsize); - - ret = metadump_init(&metadump, root, out, num_threads, - compress_level); - if (ret) { - fprintf(stderr, "Error initing metadump %d\n", ret); - close_ctree(root); - return ret; - } - - ret = add_extent(BTRFS_SUPER_INFO_OFFSET, 4096, &metadump, 0); - if (ret) { - fprintf(stderr, "Error adding metadata %d\n", ret); - err = ret; - goto out; - } - - extent_root = root->fs_info->extent_root; - path = btrfs_alloc_path(); - if (!path) { - fprintf(stderr, "Out of memory allocing path\n"); - err = -ENOMEM; - goto out; - } - bytenr = BTRFS_SUPER_INFO_OFFSET + 4096; - key.objectid = bytenr; - key.type = BTRFS_EXTENT_ITEM_KEY; - key.offset = 0; + extent_root = metadump->root->fs_info->extent_root; + bytenr = BTRFS_SUPER_INFO_OFFSET + BTRFS_SUPER_INFO_SIZE; + key.objectid = bytenr; + key.type = BTRFS_EXTENT_ITEM_KEY; + key.offset = 0; ret = btrfs_search_slot(NULL, extent_root, &key, path, 0, 0); if (ret < 0) { fprintf(stderr, "Error searching extent root %d\n", ret); - err = ret; - goto out; + return ret; } + ret = 0; + + leaf = path->nodes[0]; while (1) { - leaf = path->nodes[0]; if (path->slots[0] >= btrfs_header_nritems(leaf)) { ret = btrfs_next_leaf(extent_root, path); if (ret < 0) { fprintf(stderr, "Error going to next leaf %d" "\n", ret); - err = ret; - goto out; + break; } - if (ret > 0) + if (ret > 0) { + ret = 0; break; + } leaf = path->nodes[0]; } @@ -831,22 +1271,21 @@ static int create_metadump(const char *input, FILE *out, int num_threads, bytenr = key.objectid; if (key.type == BTRFS_METADATA_ITEM_KEY) - num_bytes = key.offset; + num_bytes = extent_root->nodesize; else - num_bytes = root->leafsize; + num_bytes = key.offset; if (btrfs_item_size_nr(leaf, path->slots[0]) > sizeof(*ei)) { ei = btrfs_item_ptr(leaf, path->slots[0], struct btrfs_extent_item); if (btrfs_extent_flags(leaf, ei) & BTRFS_EXTENT_FLAG_TREE_BLOCK) { - ret = add_extent(bytenr, num_bytes, &metadump, + ret = add_extent(bytenr, num_bytes, metadump, 0); if (ret) { fprintf(stderr, "Error adding block " "%d\n", ret); - err = ret; - goto out; + break; } } } else { @@ -855,31 +1294,93 @@ static int create_metadump(const char *input, FILE *out, int num_threads, if (ret < 0) { fprintf(stderr, "Error checking tree block " "%d\n", ret); - err = ret; - goto out; + break; } if (ret) { - ret = add_extent(bytenr, num_bytes, &metadump, + ret = add_extent(bytenr, num_bytes, metadump, 0); if (ret) { fprintf(stderr, "Error adding block " "%d\n", ret); - err = ret; - goto out; + break; } } + ret = 0; #else fprintf(stderr, "Either extent tree corruption or " "you haven't built with V0 support\n"); - err = -EIO; - goto out; + ret = -EIO; + break; #endif } bytenr += num_bytes; } - btrfs_release_path(root, path); + btrfs_release_path(path); + + return ret; +} + +static int create_metadump(const char *input, FILE *out, int num_threads, + int compress_level, int sanitize, int walk_trees) +{ + struct btrfs_root *root; + struct btrfs_path *path = NULL; + struct metadump_struct metadump; + int ret; + int err = 0; + + root = open_ctree(input, 0, 0); + if (!root) { + fprintf(stderr, "Open ctree failed\n"); + return -EIO; + } + + ret = metadump_init(&metadump, root, out, num_threads, + compress_level, sanitize); + if (ret) { + fprintf(stderr, "Error initializing metadump %d\n", ret); + close_ctree(root); + return ret; + } + + ret = add_extent(BTRFS_SUPER_INFO_OFFSET, BTRFS_SUPER_INFO_SIZE, + &metadump, 0); + if (ret) { + fprintf(stderr, "Error adding metadata %d\n", ret); + err = ret; + goto out; + } + + path = btrfs_alloc_path(); + if (!path) { + fprintf(stderr, "Out of memory allocating path\n"); + err = -ENOMEM; + goto out; + } + + if (walk_trees) { + ret = copy_tree_blocks(root, root->fs_info->chunk_root->node, + &metadump, 1); + if (ret) { + err = ret; + goto out; + } + + ret = copy_tree_blocks(root, root->fs_info->tree_root->node, + &metadump, 1); + if (ret) { + err = ret; + goto out; + } + } else { + ret = copy_from_extent_tree(&metadump, path); + if (ret) { + err = ret; + goto out; + } + } ret = copy_log_trees(root, &metadump, path); if (ret) { @@ -892,11 +1393,11 @@ out: ret = flush_pending(&metadump, 1); if (ret) { if (!err) - ret = err; + err = ret; fprintf(stderr, "Error flushing pending %d\n", ret); } - metadump_destroy(&metadump); + metadump_destroy(&metadump, num_threads); btrfs_free_path(path); ret = close_ctree(root); @@ -924,7 +1425,7 @@ static void update_super_old(u8 *buffer) btrfs_set_stack_chunk_length(chunk, (u64)-1); btrfs_set_stack_chunk_owner(chunk, BTRFS_EXTENT_TREE_OBJECTID); - btrfs_set_stack_chunk_stripe_len(chunk, 64 * 1024); + btrfs_set_stack_chunk_stripe_len(chunk, BTRFS_STRIPE_LEN); btrfs_set_stack_chunk_type(chunk, BTRFS_BLOCK_GROUP_SYSTEM); btrfs_set_stack_chunk_io_align(chunk, sectorsize); btrfs_set_stack_chunk_io_width(chunk, sectorsize); @@ -932,22 +1433,22 @@ static void update_super_old(u8 *buffer) btrfs_set_stack_chunk_num_stripes(chunk, 1); btrfs_set_stack_chunk_sub_stripes(chunk, 0); chunk->stripe.devid = super->dev_item.devid; - chunk->stripe.offset = cpu_to_le64(0); + btrfs_set_stack_stripe_offset(&chunk->stripe, 0); memcpy(chunk->stripe.dev_uuid, super->dev_item.uuid, BTRFS_UUID_SIZE); btrfs_set_super_sys_array_size(super, sizeof(*key) + sizeof(*chunk)); - csum_block(buffer, 4096); + csum_block(buffer, BTRFS_SUPER_INFO_SIZE); } -static int update_super(u8 *buffer) +static int update_super(struct mdrestore_struct *mdres, 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; + u64 flags = btrfs_super_flags(super); u32 new_array_size = 0; u32 array_size; u32 cur = 0; - u32 new_cur = 0; u8 *ptr, *write_ptr; int old_num_stripes; @@ -964,24 +1465,34 @@ static int update_super(u8 *buffer) 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) { + u64 type, physical, physical_dup, size = 0; + 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); + type = btrfs_stack_chunk_type(chunk); + if (type & BTRFS_BLOCK_GROUP_DUP) { + new_array_size += sizeof(struct btrfs_stripe); + write_ptr += sizeof(struct btrfs_stripe); + } else { + btrfs_set_stack_chunk_num_stripes(chunk, 1); + 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); + physical = logical_to_physical(mdres, key.offset, + &size, &physical_dup); + if (size != (u64)-1) + btrfs_set_stack_stripe_offset(&chunk->stripe, + physical); 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); @@ -992,8 +1503,13 @@ static int update_super(u8 *buffer) cur += btrfs_chunk_item_size(old_num_stripes); } + if (mdres->clear_space_cache) + btrfs_set_super_cache_generation(super, 0); + + flags |= BTRFS_SUPER_FLAG_METADUMP_V2; + btrfs_set_super_flags(super, flags); btrfs_set_super_sys_array_size(super, new_array_size); - csum_block(buffer, 4096); + csum_block(buffer, BTRFS_SUPER_INFO_SIZE); return 0; } @@ -1002,10 +1518,9 @@ static struct extent_buffer *alloc_dummy_eb(u64 bytenr, u32 size) { struct extent_buffer *eb; - eb = malloc(sizeof(struct extent_buffer) + size); + eb = calloc(1, sizeof(struct extent_buffer) + size); if (!eb) return NULL; - memset(eb, 0, sizeof(struct extent_buffer) + size); eb->start = bytenr; eb->len = size; @@ -1034,7 +1549,7 @@ static void truncate_item(struct extent_buffer *eb, int slot, u32 new_size) for (i = slot; i < nritems; i++) { u32 ioff; - item = btrfs_item_nr(eb, i); + item = btrfs_item_nr(i); ioff = btrfs_item_offset(eb, item); btrfs_set_item_offset(eb, item, ioff + size_diff); } @@ -1042,7 +1557,7 @@ static void truncate_item(struct extent_buffer *eb, int slot, u32 new_size) 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); + item = btrfs_item_nr(slot); btrfs_set_item_size(eb, item, new_size); } @@ -1055,16 +1570,16 @@ static int fixup_chunk_tree_block(struct mdrestore_struct *mdres, u64 bytenr = async->start; int i; - if (size_left % mdres->leafsize) + if (size_left % mdres->nodesize) return 0; - eb = alloc_dummy_eb(bytenr, mdres->leafsize); + eb = alloc_dummy_eb(bytenr, mdres->nodesize); if (!eb) return -ENOMEM; while (size_left) { eb->start = bytenr; - memcpy(eb->data, buffer, mdres->leafsize); + memcpy(eb->data, buffer, mdres->nodesize); if (btrfs_header_bytenr(eb) != bytenr) break; @@ -1080,48 +1595,63 @@ static int fixup_chunk_tree_block(struct mdrestore_struct *mdres, goto next; for (i = 0; i < btrfs_header_nritems(eb); i++) { - struct btrfs_chunk chunk; + struct btrfs_chunk *chunk; struct btrfs_key key; - u64 type; + u64 type, physical, physical_dup, size = (u64)-1; 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)); + + size = 0; + physical = logical_to_physical(mdres, key.offset, + &size, &physical_dup); + + if (!physical_dup) + truncate_item(eb, i, sizeof(*chunk)); + chunk = btrfs_item_ptr(eb, i, struct btrfs_chunk); + /* Zero out the RAID profile */ - type = btrfs_stack_chunk_type(&chunk); + type = btrfs_chunk_type(eb, 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)); + BTRFS_BLOCK_GROUP_METADATA | + BTRFS_BLOCK_GROUP_DUP); + btrfs_set_chunk_type(eb, chunk, type); + + if (!physical_dup) + btrfs_set_chunk_num_stripes(eb, chunk, 1); + btrfs_set_chunk_sub_stripes(eb, chunk, 0); + btrfs_set_stripe_devid_nr(eb, chunk, 0, mdres->devid); + if (size != (u64)-1) + btrfs_set_stripe_offset_nr(eb, chunk, 0, + physical); + /* update stripe 2 offset */ + if (physical_dup) + btrfs_set_stripe_offset_nr(eb, chunk, 1, + physical_dup); + + write_extent_buffer(eb, mdres->uuid, + (unsigned long)btrfs_stripe_dev_uuid_nr( + chunk, 0), + BTRFS_UUID_SIZE); } memcpy(buffer, eb->data, eb->len); csum_block(buffer, eb->len); next: - size_left -= mdres->leafsize; - buffer += mdres->leafsize; - bytenr += mdres->leafsize; + size_left -= mdres->nodesize; + buffer += mdres->nodesize; + bytenr += mdres->nodesize; } + free(eb); return 0; } static void write_backup_supers(int fd, u8 *buf) { + struct btrfs_super_block *super = (struct btrfs_super_block *)buf; struct stat st; u64 size; u64 bytenr; @@ -1138,10 +1668,12 @@ static void write_backup_supers(int fd, u8 *buf) for (i = 1; i < BTRFS_SUPER_MIRROR_MAX; i++) { bytenr = btrfs_sb_offset(i); - if (bytenr + 4096 > size) + if (bytenr + BTRFS_SUPER_INFO_SIZE > size) break; - ret = pwrite64(fd, buf, 4096, bytenr); - if (ret < 4096) { + btrfs_set_super_bytenr(super, bytenr); + csum_block(buf, BTRFS_SUPER_INFO_SIZE); + ret = pwrite64(fd, buf, BTRFS_SUPER_INFO_SIZE, bytenr); + if (ret < BTRFS_SUPER_INFO_SIZE) { if (ret < 0) fprintf(stderr, "Problem writing out backup " "super block %d, err %d\n", i, errno); @@ -1162,23 +1694,26 @@ 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"); + fprintf(stderr, "Error allocating buffer\n"); pthread_mutex_lock(&mdres->mutex); if (!mdres->error) mdres->error = -ENOMEM; pthread_mutex_unlock(&mdres->mutex); - goto out; + pthread_exit(NULL); } while (1) { + u64 bytenr, physical_dup; + off_t offset = 0; int err = 0; pthread_mutex_lock(&mdres->mutex); - while (!mdres->leafsize || list_empty(&mdres->list)) { + while (!mdres->nodesize || list_empty(&mdres->list)) { if (mdres->done) { pthread_mutex_unlock(&mdres->mutex); goto out; @@ -1190,7 +1725,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) { @@ -1204,33 +1739,71 @@ 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(mdres, 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; } - ret = pwrite64(outfd, outbuf, size, async->start); - if (ret < size) { - if (ret < 0) { - fprintf(stderr, "Error writing to device %d\n", - errno); - err = errno; - } else { - fprintf(stderr, "Short write\n"); - err = -EIO; + if (!mdres->fixup_offset) { + while (size) { + u64 chunk_size = size; + physical_dup = 0; + if (!mdres->multi_devices && !mdres->old_restore) + bytenr = logical_to_physical(mdres, + async->start + offset, + &chunk_size, + &physical_dup); + else + bytenr = async->start + offset; + + ret = pwrite64(outfd, outbuf+offset, chunk_size, + bytenr); + if (ret != chunk_size) + goto error; + + if (physical_dup) + ret = pwrite64(outfd, outbuf+offset, + chunk_size, + physical_dup); + if (ret != chunk_size) + goto error; + + size -= chunk_size; + offset += chunk_size; + continue; + +error: + if (ret < 0) { + fprintf(stderr, "Error writing to device %d\n", + errno); + err = errno; + } else { + fprintf(stderr, "Short write\n"); + err = -EIO; + } + } + } 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); } } - 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); @@ -1247,15 +1820,25 @@ out: pthread_exit(NULL); } -static void mdrestore_destroy(struct mdrestore_struct *mdres) +static void mdrestore_destroy(struct mdrestore_struct *mdres, int num_threads) { + struct rb_node *n; int i; + + while ((n = rb_first(&mdres->chunk_tree))) { + struct fs_chunk *entry; + + entry = rb_entry(n, struct fs_chunk, l); + rb_erase(n, &mdres->chunk_tree); + rb_erase(&entry->p, &mdres->physical_tree); + free(entry); + } pthread_mutex_lock(&mdres->mutex); mdres->done = 1; pthread_cond_broadcast(&mdres->cond); pthread_mutex_unlock(&mdres->mutex); - for (i = 0; i < mdres->num_threads; i++) + for (i = 0; i < num_threads; i++) pthread_join(mdres->threads[i], NULL); pthread_cond_destroy(&mdres->cond); @@ -1265,7 +1848,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; @@ -1273,9 +1857,17 @@ static int mdrestore_init(struct mdrestore_struct *mdres, pthread_cond_init(&mdres->cond, NULL); pthread_mutex_init(&mdres->mutex, NULL); INIT_LIST_HEAD(&mdres->list); + INIT_LIST_HEAD(&mdres->overlapping_chunks); mdres->in = in; 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; + mdres->clear_space_cache = 0; + mdres->last_physical_offset = 0; + mdres->alloced_chunks = 0; if (!num_threads) return 0; @@ -1291,7 +1883,7 @@ static int mdrestore_init(struct mdrestore_struct *mdres, break; } if (ret) - mdrestore_destroy(mdres); + mdrestore_destroy(mdres, i + 1); return ret; } @@ -1303,6 +1895,10 @@ static int fill_mdres_info(struct mdrestore_struct *mdres, u8 *outbuf; int ret; + /* We've already been initialized */ + if (mdres->nodesize) + return 0; + if (mdres->compress_method == COMPRESS_ZLIB) { size_t size = MAX_PENDING_SIZE * 2; @@ -1322,7 +1918,7 @@ static int fill_mdres_info(struct mdrestore_struct *mdres, } super = (struct btrfs_super_block *)outbuf; - mdres->leafsize = btrfs_super_leafsize(super); + mdres->nodesize = btrfs_super_nodesize(super); memcpy(mdres->fsid, super->fsid, BTRFS_FSID_SIZE); memcpy(mdres->uuid, super->dev_item.uuid, BTRFS_UUID_SIZE); @@ -1341,7 +1937,6 @@ static int add_cluster(struct meta_cluster *cluster, u32 i, nritems; int ret; - BUG_ON(mdres->num_items); mdres->compress_method = header->compress; bytenr = le64_to_cpu(header->bytenr) + BLOCK_SIZE; @@ -1357,7 +1952,7 @@ static int add_cluster(struct meta_cluster *cluster, async->bufsize = le32_to_cpu(item->size); async->buffer = malloc(async->bufsize); if (!async->buffer) { - fprintf(stderr, "Error allocing async buffer\n"); + fprintf(stderr, "Error allocating async buffer\n"); free(async); return -ENOMEM; } @@ -1421,12 +2016,495 @@ static int wait_for_worker(struct mdrestore_struct *mdres) return ret; } +static int read_chunk_block(struct mdrestore_struct *mdres, u8 *buffer, + u64 bytenr, u64 item_bytenr, u32 bufsize, + u64 cluster_bytenr) +{ + struct extent_buffer *eb; + int ret = 0; + int i; + + eb = alloc_dummy_eb(bytenr, mdres->nodesize); + if (!eb) { + ret = -ENOMEM; + goto out; + } + + while (item_bytenr != bytenr) { + buffer += mdres->nodesize; + item_bytenr += mdres->nodesize; + } + + memcpy(eb->data, buffer, mdres->nodesize); + if (btrfs_header_bytenr(eb) != bytenr) { + fprintf(stderr, "Eb bytenr doesn't match found bytenr\n"); + ret = -EIO; + goto out; + } + + if (memcmp(mdres->fsid, eb->data + offsetof(struct btrfs_header, fsid), + BTRFS_FSID_SIZE)) { + fprintf(stderr, "Fsid doesn't match\n"); + ret = -EIO; + goto out; + } + + if (btrfs_header_owner(eb) != BTRFS_CHUNK_TREE_OBJECTID) { + fprintf(stderr, "Does not belong to the chunk tree\n"); + ret = -EIO; + goto out; + } + + for (i = 0; i < btrfs_header_nritems(eb); i++) { + struct btrfs_chunk *chunk; + struct fs_chunk *fs_chunk; + struct btrfs_key key; + u64 type; + + if (btrfs_header_level(eb)) { + u64 blockptr = btrfs_node_blockptr(eb, i); + + ret = search_for_chunk_blocks(mdres, blockptr, + cluster_bytenr); + if (ret) + break; + continue; + } + + /* Yay a leaf! We loves leafs! */ + btrfs_item_key_to_cpu(eb, &key, i); + if (key.type != BTRFS_CHUNK_ITEM_KEY) + continue; + + fs_chunk = malloc(sizeof(struct fs_chunk)); + if (!fs_chunk) { + fprintf(stderr, "Error allocating chunk\n"); + ret = -ENOMEM; + break; + } + memset(fs_chunk, 0, sizeof(*fs_chunk)); + chunk = btrfs_item_ptr(eb, i, struct btrfs_chunk); + + fs_chunk->logical = key.offset; + fs_chunk->physical = btrfs_stripe_offset_nr(eb, chunk, 0); + fs_chunk->bytes = btrfs_chunk_length(eb, chunk); + INIT_LIST_HEAD(&fs_chunk->list); + if (tree_search(&mdres->physical_tree, &fs_chunk->p, + physical_cmp, 1) != NULL) + list_add(&fs_chunk->list, &mdres->overlapping_chunks); + else + tree_insert(&mdres->physical_tree, &fs_chunk->p, + physical_cmp); + + type = btrfs_chunk_type(eb, chunk); + if (type & BTRFS_BLOCK_GROUP_DUP) { + fs_chunk->physical_dup = + btrfs_stripe_offset_nr(eb, chunk, 1); + } + + if (fs_chunk->physical_dup + fs_chunk->bytes > + mdres->last_physical_offset) + mdres->last_physical_offset = fs_chunk->physical_dup + + fs_chunk->bytes; + else if (fs_chunk->physical + fs_chunk->bytes > + mdres->last_physical_offset) + mdres->last_physical_offset = fs_chunk->physical + + fs_chunk->bytes; + mdres->alloced_chunks += fs_chunk->bytes; + /* in dup case, fs_chunk->bytes should add twice */ + if (fs_chunk->physical_dup) + mdres->alloced_chunks += fs_chunk->bytes; + tree_insert(&mdres->chunk_tree, &fs_chunk->l, chunk_cmp); + } +out: + free(eb); + return ret; +} + +/* If you have to ask you aren't worthy */ +static int search_for_chunk_blocks(struct mdrestore_struct *mdres, + u64 search, u64 cluster_bytenr) +{ + struct meta_cluster *cluster; + struct meta_cluster_header *header; + struct meta_cluster_item *item; + u64 current_cluster = cluster_bytenr, bytenr; + u64 item_bytenr; + u32 bufsize, nritems, i; + u32 max_size = MAX_PENDING_SIZE * 2; + u8 *buffer, *tmp = NULL; + int ret = 0; + + cluster = malloc(BLOCK_SIZE); + if (!cluster) { + fprintf(stderr, "Error allocating cluster\n"); + return -ENOMEM; + } + + buffer = malloc(max_size); + if (!buffer) { + fprintf(stderr, "Error allocating buffer\n"); + free(cluster); + return -ENOMEM; + } + + if (mdres->compress_method == COMPRESS_ZLIB) { + tmp = malloc(max_size); + if (!tmp) { + fprintf(stderr, "Error allocating tmp buffer\n"); + free(cluster); + free(buffer); + return -ENOMEM; + } + } + + bytenr = current_cluster; + while (1) { + if (fseek(mdres->in, current_cluster, SEEK_SET)) { + fprintf(stderr, "Error seeking: %d\n", errno); + ret = -EIO; + break; + } + + ret = fread(cluster, BLOCK_SIZE, 1, mdres->in); + if (ret == 0) { + if (cluster_bytenr != 0) { + cluster_bytenr = 0; + current_cluster = 0; + bytenr = 0; + continue; + } + printf("ok this is where we screwed up?\n"); + ret = -EIO; + break; + } else if (ret < 0) { + fprintf(stderr, "Error reading image\n"); + break; + } + ret = 0; + + header = &cluster->header; + if (le64_to_cpu(header->magic) != HEADER_MAGIC || + le64_to_cpu(header->bytenr) != current_cluster) { + fprintf(stderr, "bad header in metadump image\n"); + ret = -EIO; + break; + } + + bytenr += BLOCK_SIZE; + nritems = le32_to_cpu(header->nritems); + for (i = 0; i < nritems; i++) { + size_t size; + + item = &cluster->items[i]; + bufsize = le32_to_cpu(item->size); + item_bytenr = le64_to_cpu(item->bytenr); + + if (bufsize > max_size) { + fprintf(stderr, "item %u size %u too big\n", + i, bufsize); + ret = -EIO; + break; + } + + if (mdres->compress_method == COMPRESS_ZLIB) { + ret = fread(tmp, bufsize, 1, mdres->in); + if (ret != 1) { + fprintf(stderr, "Error reading: %d\n", + errno); + ret = -EIO; + break; + } + + size = max_size; + ret = uncompress(buffer, + (unsigned long *)&size, tmp, + bufsize); + if (ret != Z_OK) { + fprintf(stderr, "Error decompressing " + "%d\n", ret); + ret = -EIO; + break; + } + } else { + ret = fread(buffer, bufsize, 1, mdres->in); + if (ret != 1) { + fprintf(stderr, "Error reading: %d\n", + errno); + ret = -EIO; + break; + } + size = bufsize; + } + ret = 0; + + if (item_bytenr <= search && + item_bytenr + size > search) { + ret = read_chunk_block(mdres, buffer, search, + item_bytenr, size, + current_cluster); + if (!ret) + ret = 1; + break; + } + bytenr += bufsize; + } + if (ret) { + if (ret > 0) + ret = 0; + break; + } + if (bytenr & BLOCK_MASK) + bytenr += BLOCK_SIZE - (bytenr & BLOCK_MASK); + current_cluster = bytenr; + } + + free(tmp); + free(buffer); + free(cluster); + return ret; +} + +static int build_chunk_tree(struct mdrestore_struct *mdres, + struct meta_cluster *cluster) +{ + struct btrfs_super_block *super; + struct meta_cluster_header *header; + struct meta_cluster_item *item = NULL; + u64 chunk_root_bytenr = 0; + u32 i, nritems; + u64 bytenr = 0; + u8 *buffer; + int ret; + + /* We can't seek with stdin so don't bother doing this */ + if (mdres->in == stdin) + return 0; + + ret = fread(cluster, BLOCK_SIZE, 1, mdres->in); + if (ret <= 0) { + fprintf(stderr, "Error reading in cluster: %d\n", errno); + return -EIO; + } + ret = 0; + + header = &cluster->header; + if (le64_to_cpu(header->magic) != HEADER_MAGIC || + le64_to_cpu(header->bytenr) != 0) { + fprintf(stderr, "bad header in metadump image\n"); + return -EIO; + } + + bytenr += BLOCK_SIZE; + mdres->compress_method = header->compress; + nritems = le32_to_cpu(header->nritems); + for (i = 0; i < nritems; i++) { + item = &cluster->items[i]; + + if (le64_to_cpu(item->bytenr) == BTRFS_SUPER_INFO_OFFSET) + break; + bytenr += le32_to_cpu(item->size); + if (fseek(mdres->in, le32_to_cpu(item->size), SEEK_CUR)) { + fprintf(stderr, "Error seeking: %d\n", errno); + return -EIO; + } + } + + if (!item || le64_to_cpu(item->bytenr) != BTRFS_SUPER_INFO_OFFSET) { + fprintf(stderr, "Huh, didn't find the super?\n"); + return -EINVAL; + } + + buffer = malloc(le32_to_cpu(item->size)); + if (!buffer) { + fprintf(stderr, "Error allocating buffer\n"); + return -ENOMEM; + } + + ret = fread(buffer, le32_to_cpu(item->size), 1, mdres->in); + if (ret != 1) { + fprintf(stderr, "Error reading buffer: %d\n", errno); + free(buffer); + return -EIO; + } + + if (mdres->compress_method == COMPRESS_ZLIB) { + size_t size = MAX_PENDING_SIZE * 2; + u8 *tmp; + + tmp = malloc(MAX_PENDING_SIZE * 2); + if (!tmp) { + free(buffer); + return -ENOMEM; + } + ret = uncompress(tmp, (unsigned long *)&size, + buffer, le32_to_cpu(item->size)); + if (ret != Z_OK) { + fprintf(stderr, "Error decompressing %d\n", ret); + free(buffer); + free(tmp); + return -EIO; + } + free(buffer); + buffer = tmp; + } + + pthread_mutex_lock(&mdres->mutex); + super = (struct btrfs_super_block *)buffer; + chunk_root_bytenr = btrfs_super_chunk_root(super); + mdres->nodesize = btrfs_super_nodesize(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); + pthread_mutex_unlock(&mdres->mutex); + + return search_for_chunk_blocks(mdres, chunk_root_bytenr, 0); +} + +static int range_contains_super(u64 physical, u64 bytes) +{ + u64 super_bytenr; + int i; + + for (i = 0; i < BTRFS_SUPER_MIRROR_MAX; i++) { + super_bytenr = btrfs_sb_offset(i); + if (super_bytenr >= physical && + super_bytenr < physical + bytes) + return 1; + } + + return 0; +} + +static void remap_overlapping_chunks(struct mdrestore_struct *mdres) +{ + struct fs_chunk *fs_chunk; + + while (!list_empty(&mdres->overlapping_chunks)) { + fs_chunk = list_first_entry(&mdres->overlapping_chunks, + struct fs_chunk, list); + list_del_init(&fs_chunk->list); + if (range_contains_super(fs_chunk->physical, + fs_chunk->bytes)) { + fprintf(stderr, "Remapping a chunk that had a super " + "mirror inside of it, clearing space cache " + "so we don't end up with corruption\n"); + mdres->clear_space_cache = 1; + } + fs_chunk->physical = mdres->last_physical_offset; + tree_insert(&mdres->physical_tree, &fs_chunk->p, physical_cmp); + mdres->last_physical_offset += fs_chunk->bytes; + } +} + +static int fixup_devices(struct btrfs_fs_info *fs_info, + struct mdrestore_struct *mdres, off_t dev_size) +{ + struct btrfs_trans_handle *trans; + struct btrfs_dev_item *dev_item; + struct btrfs_path *path; + struct extent_buffer *leaf; + struct btrfs_root *root = fs_info->chunk_root; + struct btrfs_key key; + u64 devid, cur_devid; + int ret; + + path = btrfs_alloc_path(); + if (!path) { + fprintf(stderr, "Error allocating path\n"); + return -ENOMEM; + } + + trans = btrfs_start_transaction(fs_info->tree_root, 1); + if (IS_ERR(trans)) { + fprintf(stderr, "Error starting transaction %ld\n", + PTR_ERR(trans)); + btrfs_free_path(path); + return PTR_ERR(trans); + } + + dev_item = &fs_info->super_copy->dev_item; + + devid = btrfs_stack_device_id(dev_item); + + btrfs_set_stack_device_total_bytes(dev_item, dev_size); + btrfs_set_stack_device_bytes_used(dev_item, mdres->alloced_chunks); + + key.objectid = BTRFS_DEV_ITEMS_OBJECTID; + key.type = BTRFS_DEV_ITEM_KEY; + key.offset = 0; + +again: + ret = btrfs_search_slot(trans, root, &key, path, -1, 1); + if (ret < 0) { + fprintf(stderr, "search failed %d\n", ret); + exit(1); + } + + while (1) { + leaf = path->nodes[0]; + if (path->slots[0] >= btrfs_header_nritems(leaf)) { + ret = btrfs_next_leaf(root, path); + if (ret < 0) { + fprintf(stderr, "Error going to next leaf " + "%d\n", ret); + exit(1); + } + if (ret > 0) { + ret = 0; + break; + } + leaf = path->nodes[0]; + } + + btrfs_item_key_to_cpu(leaf, &key, path->slots[0]); + if (key.type > BTRFS_DEV_ITEM_KEY) + break; + if (key.type != BTRFS_DEV_ITEM_KEY) { + path->slots[0]++; + continue; + } + + dev_item = btrfs_item_ptr(leaf, path->slots[0], + struct btrfs_dev_item); + cur_devid = btrfs_device_id(leaf, dev_item); + if (devid != cur_devid) { + ret = btrfs_del_item(trans, root, path); + if (ret) { + fprintf(stderr, "Error deleting item %d\n", + ret); + exit(1); + } + btrfs_release_path(path); + goto again; + } + + btrfs_set_device_total_bytes(leaf, dev_item, dev_size); + btrfs_set_device_bytes_used(leaf, dev_item, + mdres->alloced_chunks); + btrfs_mark_buffer_dirty(leaf); + path->slots[0]++; + } + + btrfs_free_path(path); + ret = btrfs_commit_transaction(trans, fs_info->tree_root); + if (ret) { + fprintf(stderr, "Commit failed %d\n", ret); + return ret; + } + return 0; +} + static int restore_metadump(const char *input, FILE *out, int old_restore, - int num_threads) + 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; @@ -1441,24 +2519,48 @@ 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(target, 0, 0, 0, + OPEN_CTREE_WRITES | + OPEN_CTREE_RESTORE | + OPEN_CTREE_PARTIAL); + 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; + fprintf(stderr, "Error initializing mdrestore %d\n", ret); + goto failed_cluster; } - while (1) { + if (!multi_devices && !old_restore) { + ret = build_chunk_tree(&mdrestore, cluster); + if (ret) + goto out; + if (!list_empty(&mdrestore.overlapping_chunks)) + remap_overlapping_chunks(&mdrestore); + } + + if (in != stdin && fseek(in, 0, SEEK_SET)) { + fprintf(stderr, "Error seeking %d\n", errno); + goto out; + } + + while (!mdrestore.error) { ret = fread(cluster, BLOCK_SIZE, 1, in); if (!ret) break; @@ -1475,45 +2577,181 @@ static int restore_metadump(const char *input, FILE *out, int old_restore, fprintf(stderr, "Error adding cluster\n"); break; } + } + ret = wait_for_worker(&mdrestore); + + if (!ret && !multi_devices && !old_restore) { + struct btrfs_root *root; + struct stat st; + + root = open_ctree_fd(fileno(out), target, 0, + OPEN_CTREE_PARTIAL | + OPEN_CTREE_WRITES | + OPEN_CTREE_NO_DEVICES); + if (!root) { + fprintf(stderr, "unable to open %s\n", target); + ret = -EIO; + goto out; + } + info = root->fs_info; - ret = wait_for_worker(&mdrestore); - if (ret) { - fprintf(stderr, "One of the threads errored out %d\n", - ret); - break; + if (stat(target, &st)) { + fprintf(stderr, "statting %s failed\n", target); + close_ctree(info->chunk_root); + free(cluster); + return 1; } - } - mdrestore_destroy(&mdrestore); + ret = fixup_devices(info, &mdrestore, st.st_size); + close_ctree(info->chunk_root); + if (ret) + goto out; + } +out: + mdrestore_destroy(&mdrestore, num_threads); +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 void print_usage(void) +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[BTRFS_SUPER_INFO_SIZE]; + int fp = -1; + 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, "ERROR: search key failed\n"); + ret = -EIO; + goto out; + } + + 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("ERROR: devid %llu mismatch with %llu\n", devid, cur_devid); + ret = -EIO; + goto out; + } + + 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(&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, "ERROR: could not open %s\n", other_dev); + ret = -EIO; + goto out; + } + + 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) { + if (ret < 0) + fprintf(stderr, "ERROR: cannot write superblock: %s\n", strerror(ret)); + else + fprintf(stderr, "ERROR: cannot write superblock\n"); + ret = -EIO; + goto out; + } + + write_backup_supers(fp, (u8 *)buf); + +out: + if (fp != -1) + close(fp); + return ret; +} + +static void print_usage(int ret) { fprintf(stderr, "usage: btrfs-image [options] source target\n"); 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); + fprintf(stderr, "\t-s \tsanitize file names, use once to just use garbage, use twice if you want crc collisions\n"); + fprintf(stderr, "\t-w \twalk all trees instead of using extent tree, do this if your extent tree is broken\n"); + fprintf(stderr, "\t-m \trestore for multiple devices\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "\tIn the dump mode, source is the btrfs device and target is the output file (use '-' for stdout).\n"); + fprintf(stderr, "\tIn the restore mode, source is the dumped image and target is the btrfs device/file.\n"); + exit(ret); } int main(int argc, char *argv[]) { char *source; char *target; - int num_threads = 0; - int compress_level = 0; + 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; + int sanitize = 0; + int dev_cnt = 0; + int usage_error = 0; FILE *out; while (1) { - int c = getopt(argc, argv, "rc:t:o"); + 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:oswm", long_options, NULL); if (c < 0) break; switch (c) { @@ -1521,29 +2759,63 @@ int main(int argc, char *argv[]) create = 0; break; case 't': - num_threads = atoi(optarg); - if (num_threads <= 0 || num_threads > 32) - print_usage(); + num_threads = arg_strtou64(optarg); + if (num_threads > 32) + print_usage(1); break; case 'c': - compress_level = atoi(optarg); - if (compress_level < 0 || compress_level > 9) - print_usage(); + compress_level = arg_strtou64(optarg); + if (compress_level > 9) + print_usage(1); break; case 'o': old_restore = 1; break; + case 's': + sanitize++; + break; + case 'w': + walk_trees = 1; + break; + case 'm': + create = 0; + multi_devices = 1; + break; + case GETOPT_VAL_HELP: default: - print_usage(); + print_usage(c != GETOPT_VAL_HELP); } } - if (old_restore && create) - print_usage(); + set_argv0(argv); + if (check_argc_min(argc - optind, 2)) + print_usage(1); + + dev_cnt = argc - optind - 1; + + if (create) { + if (old_restore) { + fprintf(stderr, "Usage error: create and restore cannot be used at the same time\n"); + usage_error++; + } + } else { + if (walk_trees || sanitize || compress_level) { + fprintf(stderr, "Usage error: use -w, -s, -c options for restore makes no sense\n"); + usage_error++; + } + if (multi_devices && dev_cnt < 2) { + fprintf(stderr, "Usage error: not enough devices specified for -m option\n"); + usage_error++; + } + if (!multi_devices && dev_cnt != 1) { + fprintf(stderr, "Usage error: accepts only 1 device without -m option\n"); + usage_error++; + } + } + + if (usage_error) + print_usage(1); - argc = argc - optind; - if (argc != 2) - print_usage(); source = argv[optind]; target = argv[optind + 1]; @@ -1557,22 +2829,103 @@ int main(int argc, char *argv[]) } } - if (num_threads == 0 && compress_level > 0) { - num_threads = sysconf(_SC_NPROCESSORS_ONLN); - if (num_threads <= 0) - num_threads = 1; + if (compress_level > 0 || create == 0) { + if (num_threads == 0) { + long tmp = sysconf(_SC_NPROCESSORS_ONLN); + + if (tmp <= 0) + tmp = 1; + num_threads = tmp; + } + } else { + num_threads = 0; } - if (create) + if (create) { + ret = check_mounted(source); + if (ret < 0) { + fprintf(stderr, "Could not check mount status: %s\n", + strerror(-ret)); + exit(1); + } else if (ret) + fprintf(stderr, + "WARNING: The device is mounted. Make sure the filesystem is quiescent.\n"); + ret = create_metadump(source, out, num_threads, - compress_level); - else - ret = restore_metadump(source, out, old_restore, 1); + compress_level, sanitize, walk_trees); + } else { + ret = restore_metadump(source, out, old_restore, num_threads, + 0, target, 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(target, 0, 0, 0, + OPEN_CTREE_PARTIAL | + OPEN_CTREE_RESTORE); + if (!info) { + fprintf(stderr, "unable to open %s error = %s\n", + target, strerror(errno)); + 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); + } + } - if (out == stdout) + 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) { + fprintf(stderr, "fix metadump failed (error=%d)\n", + ret); + exit(1); + } + } +out: + if (out == stdout) { fflush(out); - else + } else { fclose(out); + if (ret && create) { + int unlink_ret; - return ret; + unlink_ret = unlink(target); + if (unlink_ret) + fprintf(stderr, + "unlink output file failed : %s\n", + strerror(errno)); + } + } + + btrfs_close_all_devices(); + + return !!ret; }