diff options
author | Nicholas D Steeves <nsteeves@gmail.com> | 2016-04-23 00:41:30 +0100 |
---|---|---|
committer | Nicholas D Steeves <nsteeves@gmail.com> | 2016-04-23 00:41:30 +0100 |
commit | cec572daccafa1e912cbed363df6f84687778c6f (patch) | |
tree | 7d99ab9f73d25c1ed8eaf6393f6374edf5316b03 /cmds-restore.c |
btrfs-progs (4.4.1-1.1) unstable; urgency=medium
* Non-maintainer upload.
* New upstream release.
* Rename package to btrfs-progs (Closes: #780081)
* Update standards version to 3.9.7 (no changes needed).
* debian/control: Add "Breaks" per Gianfranco Costamagna's suggestion
* Change lintian override to reflect package rename
* Switch from using postinst and postrm to using triggers
per Christian Seiler's recommendation.
# imported from the archive
Diffstat (limited to 'cmds-restore.c')
-rw-r--r-- | cmds-restore.c | 1612 |
1 files changed, 1612 insertions, 0 deletions
diff --git a/cmds-restore.c b/cmds-restore.c new file mode 100644 index 00000000..dd0b2427 --- /dev/null +++ b/cmds-restore.c @@ -0,0 +1,1612 @@ +/* + * Copyright (C) 2011 Red Hat. 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 <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <lzo/lzoconf.h> +#include <lzo/lzo1x.h> +#include <zlib.h> +#include <regex.h> +#include <getopt.h> +#include <sys/types.h> +#include <sys/xattr.h> + +#include "ctree.h" +#include "disk-io.h" +#include "print-tree.h" +#include "transaction.h" +#include "list.h" +#include "volumes.h" +#include "utils.h" +#include "commands.h" + +static char fs_name[PATH_MAX]; +static char path_name[PATH_MAX]; +static char symlink_target[PATH_MAX]; +static int get_snaps = 0; +static int verbose = 0; +static int restore_metadata = 0; +static int restore_symlinks = 0; +static int ignore_errors = 0; +static int overwrite = 0; +static int get_xattrs = 0; +static int dry_run = 0; + +#define LZO_LEN 4 +#define PAGE_CACHE_SIZE 4096 +#define lzo1x_worst_compress(x) ((x) + ((x) / 16) + 64 + 3) + +static int decompress_zlib(char *inbuf, char *outbuf, u64 compress_len, + u64 decompress_len) +{ + z_stream strm; + int ret; + + memset(&strm, 0, sizeof(strm)); + ret = inflateInit(&strm); + if (ret != Z_OK) { + fprintf(stderr, "inflate init returnd %d\n", ret); + return -1; + } + + strm.avail_in = compress_len; + strm.next_in = (unsigned char *)inbuf; + strm.avail_out = decompress_len; + strm.next_out = (unsigned char *)outbuf; + ret = inflate(&strm, Z_NO_FLUSH); + if (ret != Z_STREAM_END) { + (void)inflateEnd(&strm); + fprintf(stderr, "failed to inflate: %d\n", ret); + return -1; + } + + (void)inflateEnd(&strm); + return 0; +} +static inline size_t read_compress_length(unsigned char *buf) +{ + __le32 dlen; + memcpy(&dlen, buf, LZO_LEN); + return le32_to_cpu(dlen); +} + +static int decompress_lzo(unsigned char *inbuf, char *outbuf, u64 compress_len, + u64 *decompress_len) +{ + size_t new_len; + size_t in_len; + size_t out_len = 0; + size_t tot_len; + size_t tot_in; + int ret; + + ret = lzo_init(); + if (ret != LZO_E_OK) { + fprintf(stderr, "lzo init returned %d\n", ret); + return -1; + } + + tot_len = read_compress_length(inbuf); + inbuf += LZO_LEN; + tot_in = LZO_LEN; + + while (tot_in < tot_len) { + size_t mod_page; + size_t rem_page; + in_len = read_compress_length(inbuf); + + if ((tot_in + LZO_LEN + in_len) > tot_len) { + fprintf(stderr, "bad compress length %lu\n", + (unsigned long)in_len); + return -1; + } + + inbuf += LZO_LEN; + tot_in += LZO_LEN; + + new_len = lzo1x_worst_compress(PAGE_CACHE_SIZE); + ret = lzo1x_decompress_safe((const unsigned char *)inbuf, in_len, + (unsigned char *)outbuf, + (void *)&new_len, NULL); + if (ret != LZO_E_OK) { + fprintf(stderr, "failed to inflate: %d\n", ret); + return -1; + } + out_len += new_len; + outbuf += new_len; + inbuf += in_len; + tot_in += in_len; + + /* + * If the 4 byte header does not fit to the rest of the page we + * have to move to the next one, unless we read some garbage + */ + mod_page = tot_in % PAGE_CACHE_SIZE; + rem_page = PAGE_CACHE_SIZE - mod_page; + if (rem_page < LZO_LEN) { + inbuf += rem_page; + tot_in += rem_page; + } + } + + *decompress_len = out_len; + + return 0; +} + +static int decompress(char *inbuf, char *outbuf, u64 compress_len, + u64 *decompress_len, int compress) +{ + switch (compress) { + case BTRFS_COMPRESS_ZLIB: + return decompress_zlib(inbuf, outbuf, compress_len, + *decompress_len); + case BTRFS_COMPRESS_LZO: + return decompress_lzo((unsigned char *)inbuf, outbuf, compress_len, + decompress_len); + default: + break; + } + + fprintf(stderr, "invalid compression type: %d\n", compress); + return -1; +} + +static int next_leaf(struct btrfs_root *root, struct btrfs_path *path) +{ + int slot; + int level = 1; + int offset = 1; + struct extent_buffer *c; + struct extent_buffer *next = NULL; + +again: + for (; level < BTRFS_MAX_LEVEL; level++) { + if (path->nodes[level]) + break; + } + + if (level >= BTRFS_MAX_LEVEL) + return 1; + + slot = path->slots[level] + 1; + + while(level < BTRFS_MAX_LEVEL) { + if (!path->nodes[level]) + return 1; + + slot = path->slots[level] + offset; + c = path->nodes[level]; + if (slot >= btrfs_header_nritems(c)) { + level++; + if (level == BTRFS_MAX_LEVEL) + return 1; + offset = 1; + continue; + } + + if (path->reada) + reada_for_search(root, path, level, slot, 0); + + next = read_node_slot(root, c, slot); + if (extent_buffer_uptodate(next)) + break; + offset++; + } + path->slots[level] = slot; + while(1) { + level--; + c = path->nodes[level]; + free_extent_buffer(c); + path->nodes[level] = next; + path->slots[level] = 0; + if (!level) + break; + if (path->reada) + reada_for_search(root, path, level, 0, 0); + next = read_node_slot(root, next, 0); + if (!extent_buffer_uptodate(next)) + goto again; + } + return 0; +} + +static int copy_one_inline(int fd, struct btrfs_path *path, u64 pos) +{ + struct extent_buffer *leaf = path->nodes[0]; + struct btrfs_file_extent_item *fi; + char buf[4096]; + char *outbuf; + u64 ram_size; + ssize_t done; + unsigned long ptr; + int ret; + int len; + int inline_item_len; + int compress; + + fi = btrfs_item_ptr(leaf, path->slots[0], + struct btrfs_file_extent_item); + ptr = btrfs_file_extent_inline_start(fi); + len = btrfs_file_extent_inline_len(leaf, path->slots[0], fi); + inline_item_len = btrfs_file_extent_inline_item_len(leaf, btrfs_item_nr(path->slots[0])); + read_extent_buffer(leaf, buf, ptr, inline_item_len); + + compress = btrfs_file_extent_compression(leaf, fi); + if (compress == BTRFS_COMPRESS_NONE) { + done = pwrite(fd, buf, len, pos); + if (done < len) { + fprintf(stderr, "Short inline write, wanted %d, did " + "%zd: %d\n", len, done, errno); + return -1; + } + return 0; + } + + ram_size = btrfs_file_extent_ram_bytes(leaf, fi); + outbuf = calloc(1, ram_size); + if (!outbuf) { + fprintf(stderr, "No memory\n"); + return -ENOMEM; + } + + ret = decompress(buf, outbuf, len, &ram_size, compress); + if (ret) { + free(outbuf); + return ret; + } + + done = pwrite(fd, outbuf, ram_size, pos); + free(outbuf); + if (done < ram_size) { + fprintf(stderr, "Short compressed inline write, wanted %Lu, " + "did %zd: %d\n", ram_size, done, errno); + return -1; + } + + return 0; +} + +static int copy_one_extent(struct btrfs_root *root, int fd, + struct extent_buffer *leaf, + struct btrfs_file_extent_item *fi, u64 pos) +{ + struct btrfs_multi_bio *multi = NULL; + struct btrfs_device *device; + char *inbuf, *outbuf = NULL; + ssize_t done, total = 0; + u64 bytenr; + u64 ram_size; + u64 disk_size; + u64 num_bytes; + u64 length; + u64 size_left; + u64 dev_bytenr; + u64 offset; + u64 count = 0; + int compress; + int ret; + int dev_fd; + int mirror_num = 1; + int num_copies; + + compress = btrfs_file_extent_compression(leaf, fi); + bytenr = btrfs_file_extent_disk_bytenr(leaf, fi); + disk_size = btrfs_file_extent_disk_num_bytes(leaf, fi); + ram_size = btrfs_file_extent_ram_bytes(leaf, fi); + offset = btrfs_file_extent_offset(leaf, fi); + num_bytes = btrfs_file_extent_num_bytes(leaf, fi); + size_left = disk_size; + if (compress == BTRFS_COMPRESS_NONE) + bytenr += offset; + + if (verbose && offset) + printf("offset is %Lu\n", offset); + /* we found a hole */ + if (disk_size == 0) + return 0; + + inbuf = malloc(size_left); + if (!inbuf) { + fprintf(stderr, "No memory\n"); + return -ENOMEM; + } + + if (compress != BTRFS_COMPRESS_NONE) { + outbuf = calloc(1, ram_size); + if (!outbuf) { + fprintf(stderr, "No memory\n"); + free(inbuf); + return -ENOMEM; + } + } +again: + length = size_left; + ret = btrfs_map_block(&root->fs_info->mapping_tree, READ, + bytenr, &length, &multi, mirror_num, NULL); + if (ret) { + fprintf(stderr, "Error mapping block %d\n", ret); + goto out; + } + device = multi->stripes[0].dev; + dev_fd = device->fd; + device->total_ios++; + dev_bytenr = multi->stripes[0].physical; + kfree(multi); + + if (size_left < length) + length = size_left; + + done = pread(dev_fd, inbuf+count, length, dev_bytenr); + /* Need both checks, or we miss negative values due to u64 conversion */ + if (done < 0 || done < length) { + num_copies = btrfs_num_copies(&root->fs_info->mapping_tree, + bytenr, length); + mirror_num++; + /* mirror_num is 1-indexed, so num_copies is a valid mirror. */ + if (mirror_num > num_copies) { + ret = -1; + fprintf(stderr, "Exhausted mirrors trying to read\n"); + goto out; + } + fprintf(stderr, "Trying another mirror\n"); + goto again; + } + + mirror_num = 1; + size_left -= length; + count += length; + bytenr += length; + if (size_left) + goto again; + + if (compress == BTRFS_COMPRESS_NONE) { + while (total < num_bytes) { + done = pwrite(fd, inbuf+total, num_bytes-total, + pos+total); + if (done < 0) { + ret = -1; + fprintf(stderr, "Error writing: %d %s\n", errno, strerror(errno)); + goto out; + } + total += done; + } + ret = 0; + goto out; + } + + ret = decompress(inbuf, outbuf, disk_size, &ram_size, compress); + if (ret) { + num_copies = btrfs_num_copies(&root->fs_info->mapping_tree, + bytenr, length); + mirror_num++; + if (mirror_num >= num_copies) { + ret = -1; + goto out; + } + fprintf(stderr, "Trying another mirror\n"); + goto again; + } + + while (total < num_bytes) { + done = pwrite(fd, outbuf + offset + total, + num_bytes - total, + pos + total); + if (done < 0) { + ret = -1; + goto out; + } + total += done; + } +out: + free(inbuf); + free(outbuf); + return ret; +} + +enum loop_response { + LOOP_STOP, + LOOP_CONTINUE, + LOOP_DONTASK +}; + +static enum loop_response ask_to_continue(const char *file) +{ + char buf[2]; + char *ret; + + printf("We seem to be looping a lot on %s, do you want to keep going " + "on ? (y/N/a): ", file); +again: + ret = fgets(buf, 2, stdin); + if (*ret == '\n' || tolower(*ret) == 'n') + return LOOP_STOP; + if (tolower(*ret) == 'a') + return LOOP_DONTASK; + if (tolower(*ret) != 'y') { + printf("Please enter one of 'y', 'n', or 'a': "); + goto again; + } + + return LOOP_CONTINUE; +} + + +static int set_file_xattrs(struct btrfs_root *root, u64 inode, + int fd, const char *file_name) +{ + struct btrfs_key key; + struct btrfs_path *path; + struct extent_buffer *leaf; + struct btrfs_dir_item *di; + u32 name_len = 0; + u32 data_len = 0; + u32 len = 0; + u32 cur, total_len; + char *name = NULL; + char *data = NULL; + int ret = 0; + + key.objectid = inode; + key.type = BTRFS_XATTR_ITEM_KEY; + key.offset = 0; + + path = btrfs_alloc_path(); + if (!path) + return -ENOMEM; + + ret = btrfs_search_slot(NULL, root, &key, path, 0, 0); + if (ret < 0) + goto out; + + leaf = path->nodes[0]; + while (1) { + if (path->slots[0] >= btrfs_header_nritems(leaf)) { + do { + ret = next_leaf(root, path); + if (ret < 0) { + fprintf(stderr, + "Error searching for extended attributes: %d\n", + ret); + goto out; + } else if (ret) { + /* No more leaves to search */ + ret = 0; + goto out; + } + leaf = path->nodes[0]; + } while (!leaf); + continue; + } + + btrfs_item_key_to_cpu(leaf, &key, path->slots[0]); + if (key.type != BTRFS_XATTR_ITEM_KEY || key.objectid != inode) + break; + cur = 0; + total_len = btrfs_item_size_nr(leaf, path->slots[0]); + di = btrfs_item_ptr(leaf, path->slots[0], + struct btrfs_dir_item); + + while (cur < total_len) { + len = btrfs_dir_name_len(leaf, di); + if (len > name_len) { + free(name); + name = (char *) malloc(len + 1); + if (!name) { + ret = -ENOMEM; + goto out; + } + } + read_extent_buffer(leaf, name, + (unsigned long)(di + 1), len); + name[len] = '\0'; + name_len = len; + + len = btrfs_dir_data_len(leaf, di); + if (len > data_len) { + free(data); + data = (char *) malloc(len); + if (!data) { + ret = -ENOMEM; + goto out; + } + } + read_extent_buffer(leaf, data, + (unsigned long)(di + 1) + name_len, + len); + data_len = len; + + if (fsetxattr(fd, name, data, data_len, 0)) + fprintf(stderr, + "Error setting extended attribute %s on file %s: %s\n", + name, file_name, strerror(errno)); + + len = sizeof(*di) + name_len + data_len; + cur += len; + di = (struct btrfs_dir_item *)((char *)di + len); + } + path->slots[0]++; + } + ret = 0; +out: + btrfs_free_path(path); + free(name); + free(data); + + return ret; +} + +static int copy_metadata(struct btrfs_root *root, int fd, + struct btrfs_key *key) +{ + struct btrfs_path *path; + struct btrfs_inode_item *inode_item; + int ret; + + path = btrfs_alloc_path(); + if (!path) { + fprintf(stderr, "ERROR: Ran out of memory\n"); + return -ENOMEM; + } + + ret = btrfs_lookup_inode(NULL, root, path, key, 0); + if (ret == 0) { + struct btrfs_timespec *bts; + struct timespec times[2]; + + inode_item = btrfs_item_ptr(path->nodes[0], path->slots[0], + struct btrfs_inode_item); + + ret = fchown(fd, btrfs_inode_uid(path->nodes[0], inode_item), + btrfs_inode_gid(path->nodes[0], inode_item)); + if (ret) { + fprintf(stderr, "ERROR: Failed to change owner: %s\n", + strerror(errno)); + goto out; + } + + ret = fchmod(fd, btrfs_inode_mode(path->nodes[0], inode_item)); + if (ret) { + fprintf(stderr, "ERROR: Failed to change mode: %s\n", + strerror(errno)); + goto out; + } + + bts = btrfs_inode_atime(inode_item); + times[0].tv_sec = btrfs_timespec_sec(path->nodes[0], bts); + times[0].tv_nsec = btrfs_timespec_nsec(path->nodes[0], bts); + + bts = btrfs_inode_mtime(inode_item); + times[1].tv_sec = btrfs_timespec_sec(path->nodes[0], bts); + times[1].tv_nsec = btrfs_timespec_nsec(path->nodes[0], bts); + + ret = futimens(fd, times); + if (ret) { + fprintf(stderr, "ERROR: Failed to set times: %s\n", + strerror(errno)); + goto out; + } + } +out: + btrfs_free_path(path); + return ret; +} + +static int copy_file(struct btrfs_root *root, int fd, struct btrfs_key *key, + const char *file) +{ + struct extent_buffer *leaf; + struct btrfs_path *path; + struct btrfs_file_extent_item *fi; + struct btrfs_inode_item *inode_item; + struct btrfs_timespec *bts; + struct btrfs_key found_key; + int ret; + int extent_type; + int compression; + int loops = 0; + u64 found_size = 0; + struct timespec times[2]; + int times_ok = 0; + + path = btrfs_alloc_path(); + if (!path) { + fprintf(stderr, "Ran out of memory\n"); + return -ENOMEM; + } + + ret = btrfs_lookup_inode(NULL, root, path, key, 0); + if (ret == 0) { + inode_item = btrfs_item_ptr(path->nodes[0], path->slots[0], + struct btrfs_inode_item); + found_size = btrfs_inode_size(path->nodes[0], inode_item); + + if (restore_metadata) { + /* + * Change the ownership and mode now, set times when + * copyout is finished. + */ + + ret = fchown(fd, btrfs_inode_uid(path->nodes[0], inode_item), + btrfs_inode_gid(path->nodes[0], inode_item)); + if (ret && !ignore_errors) + goto out; + + ret = fchmod(fd, btrfs_inode_mode(path->nodes[0], inode_item)); + if (ret && !ignore_errors) + goto out; + + bts = btrfs_inode_atime(inode_item); + times[0].tv_sec = btrfs_timespec_sec(path->nodes[0], bts); + times[0].tv_nsec = btrfs_timespec_nsec(path->nodes[0], bts); + + bts = btrfs_inode_mtime(inode_item); + times[1].tv_sec = btrfs_timespec_sec(path->nodes[0], bts); + times[1].tv_nsec = btrfs_timespec_nsec(path->nodes[0], bts); + times_ok = 1; + } + } + btrfs_release_path(path); + + key->offset = 0; + key->type = BTRFS_EXTENT_DATA_KEY; + + ret = btrfs_search_slot(NULL, root, key, path, 0, 0); + if (ret < 0) { + fprintf(stderr, "Error searching %d\n", ret); + goto out; + } + + leaf = path->nodes[0]; + while (!leaf) { + ret = next_leaf(root, path); + if (ret < 0) { + fprintf(stderr, "Error getting next leaf %d\n", + ret); + goto out; + } else if (ret > 0) { + /* No more leaves to search */ + ret = 0; + goto out; + } + leaf = path->nodes[0]; + } + + while (1) { + if (loops >= 0 && loops++ >= 1024) { + enum loop_response resp; + + resp = ask_to_continue(file); + if (resp == LOOP_STOP) + break; + else if (resp == LOOP_CONTINUE) + loops = 0; + else if (resp == LOOP_DONTASK) + loops = -1; + } + if (path->slots[0] >= btrfs_header_nritems(leaf)) { + do { + ret = next_leaf(root, path); + if (ret < 0) { + fprintf(stderr, "Error searching %d\n", ret); + goto out; + } else if (ret) { + /* No more leaves to search */ + btrfs_free_path(path); + goto set_size; + } + leaf = path->nodes[0]; + } while (!leaf); + continue; + } + btrfs_item_key_to_cpu(leaf, &found_key, path->slots[0]); + if (found_key.objectid != key->objectid) + break; + if (found_key.type != key->type) + break; + fi = btrfs_item_ptr(leaf, path->slots[0], + struct btrfs_file_extent_item); + extent_type = btrfs_file_extent_type(leaf, fi); + compression = btrfs_file_extent_compression(leaf, fi); + if (compression >= BTRFS_COMPRESS_LAST) { + fprintf(stderr, "Don't support compression yet %d\n", + compression); + ret = -1; + goto out; + } + + if (extent_type == BTRFS_FILE_EXTENT_PREALLOC) + goto next; + if (extent_type == BTRFS_FILE_EXTENT_INLINE) { + ret = copy_one_inline(fd, path, found_key.offset); + if (ret) + goto out; + } else if (extent_type == BTRFS_FILE_EXTENT_REG) { + ret = copy_one_extent(root, fd, leaf, fi, + found_key.offset); + if (ret) + goto out; + } else { + printf("Weird extent type %d\n", extent_type); + } +next: + path->slots[0]++; + } + + btrfs_free_path(path); +set_size: + if (found_size) { + ret = ftruncate(fd, (loff_t)found_size); + if (ret) + return ret; + } + if (get_xattrs) { + ret = set_file_xattrs(root, key->objectid, fd, file); + if (ret) + return ret; + } + if (restore_metadata && times_ok) { + ret = futimens(fd, times); + if (ret) + return ret; + } + return 0; + +out: + btrfs_free_path(path); + return ret; +} + +/* + * returns: + * 0 if the file exists and should be skipped. + * 1 if the file does NOT exist + * 2 if the file exists but is OK to overwrite + */ +static int overwrite_ok(const char * path) +{ + static int warn = 0; + struct stat st; + int ret; + + /* don't be fooled by symlinks */ + ret = fstatat(-1, path_name, &st, AT_SYMLINK_NOFOLLOW); + + if (!ret) { + if (overwrite) + return 2; + + if (verbose || !warn) + printf("Skipping existing file" + " %s\n", path); + if (!warn) + printf("If you wish to overwrite use -o\n"); + warn = 1; + return 0; + } + return 1; +} + +static int copy_symlink(struct btrfs_root *root, struct btrfs_key *key, + const char *file) +{ + struct btrfs_path *path; + struct extent_buffer *leaf; + struct btrfs_file_extent_item *extent_item; + struct btrfs_inode_item *inode_item; + u32 len; + u32 name_offset; + int ret; + struct btrfs_timespec *bts; + struct timespec times[2]; + + ret = overwrite_ok(path_name); + if (ret == 0) + return 0; /* skip this file */ + + /* symlink() can't overwrite, so unlink first */ + if (ret == 2) { + ret = unlink(path_name); + if (ret) { + fprintf(stderr, "failed to unlink '%s' for overwrite\n", + path_name); + return ret; + } + } + + key->type = BTRFS_EXTENT_DATA_KEY; + key->offset = 0; + + path = btrfs_alloc_path(); + if (!path) + return -ENOMEM; + + ret = btrfs_search_slot(NULL, root, key, path, 0, 0); + if (ret < 0) + goto out; + + leaf = path->nodes[0]; + if (!leaf) { + fprintf(stderr, "Error getting leaf for symlink '%s'\n", file); + ret = -1; + goto out; + } + + extent_item = btrfs_item_ptr(leaf, path->slots[0], + struct btrfs_file_extent_item); + + len = btrfs_file_extent_inline_item_len(leaf, + btrfs_item_nr(path->slots[0])); + if (len >= PATH_MAX) { + fprintf(stderr, "Symlink '%s' target length %d is longer than PATH_MAX\n", + fs_name, len); + ret = -1; + goto out; + } + + name_offset = (unsigned long) extent_item + + offsetof(struct btrfs_file_extent_item, disk_bytenr); + read_extent_buffer(leaf, symlink_target, name_offset, len); + + symlink_target[len] = 0; + + if (!dry_run) { + ret = symlink(symlink_target, path_name); + if (ret < 0) { + fprintf(stderr, "Failed to restore symlink '%s': %s\n", + path_name, strerror(errno)); + goto out; + } + } + printf("SYMLINK: '%s' => '%s'\n", path_name, symlink_target); + + ret = 0; + if (!restore_metadata) + goto out; + + /* + * Symlink metadata operates differently than files/directories, so do + * our own work here. + */ + key->type = BTRFS_INODE_ITEM_KEY; + key->offset = 0; + + btrfs_release_path(path); + + ret = btrfs_lookup_inode(NULL, root, path, key, 0); + if (ret) { + fprintf(stderr, "Failed to lookup inode for '%s'\n", file); + goto out; + } + + inode_item = btrfs_item_ptr(path->nodes[0], path->slots[0], + struct btrfs_inode_item); + + ret = fchownat(-1, file, btrfs_inode_uid(path->nodes[0], inode_item), + btrfs_inode_gid(path->nodes[0], inode_item), + AT_SYMLINK_NOFOLLOW); + if (ret) { + fprintf(stderr, "Failed to change owner: %s\n", + strerror(errno)); + goto out; + } + + bts = btrfs_inode_atime(inode_item); + times[0].tv_sec = btrfs_timespec_sec(path->nodes[0], bts); + times[0].tv_nsec = btrfs_timespec_nsec(path->nodes[0], bts); + + bts = btrfs_inode_mtime(inode_item); + times[1].tv_sec = btrfs_timespec_sec(path->nodes[0], bts); + times[1].tv_nsec = btrfs_timespec_nsec(path->nodes[0], bts); + + ret = utimensat(-1, file, times, AT_SYMLINK_NOFOLLOW); + if (ret) + fprintf(stderr, "Failed to set times: %s\n", strerror(errno)); +out: + btrfs_free_path(path); + return ret; +} + +static int search_dir(struct btrfs_root *root, struct btrfs_key *key, + const char *output_rootdir, const char *in_dir, + const regex_t *mreg) +{ + struct btrfs_path *path; + struct extent_buffer *leaf; + struct btrfs_dir_item *dir_item; + struct btrfs_key found_key, location; + char filename[BTRFS_NAME_LEN + 1]; + unsigned long name_ptr; + int name_len; + int ret = 0; + int fd; + int loops = 0; + u8 type; + + path = btrfs_alloc_path(); + if (!path) { + fprintf(stderr, "Ran out of memory\n"); + return -ENOMEM; + } + + key->offset = 0; + key->type = BTRFS_DIR_INDEX_KEY; + + ret = btrfs_search_slot(NULL, root, key, path, 0, 0); + if (ret < 0) { + fprintf(stderr, "Error searching %d\n", ret); + goto out; + } + + ret = 0; + + leaf = path->nodes[0]; + while (!leaf) { + if (verbose > 1) + printf("No leaf after search, looking for the next " + "leaf\n"); + ret = next_leaf(root, path); + if (ret < 0) { + fprintf(stderr, "Error getting next leaf %d\n", + ret); + goto out; + } else if (ret > 0) { + /* No more leaves to search */ + if (verbose) + printf("Reached the end of the tree looking " + "for the directory\n"); + ret = 0; + goto out; + } + leaf = path->nodes[0]; + } + + while (leaf) { + if (loops++ >= 1024) { + printf("We have looped trying to restore files in %s " + "too many times to be making progress, " + "stopping\n", in_dir); + break; + } + + if (path->slots[0] >= btrfs_header_nritems(leaf)) { + do { + ret = next_leaf(root, path); + if (ret < 0) { + fprintf(stderr, "Error searching %d\n", + ret); + goto out; + } else if (ret > 0) { + /* No more leaves to search */ + if (verbose) + printf("Reached the end of " + "the tree searching the" + " directory\n"); + ret = 0; + goto out; + } + leaf = path->nodes[0]; + } while (!leaf); + continue; + } + btrfs_item_key_to_cpu(leaf, &found_key, path->slots[0]); + if (found_key.objectid != key->objectid) { + if (verbose > 1) + printf("Found objectid=%Lu, key=%Lu\n", + found_key.objectid, key->objectid); + break; + } + if (found_key.type != key->type) { + if (verbose > 1) + printf("Found type=%u, want=%u\n", + found_key.type, key->type); + break; + } + dir_item = btrfs_item_ptr(leaf, path->slots[0], + struct btrfs_dir_item); + name_ptr = (unsigned long)(dir_item + 1); + name_len = btrfs_dir_name_len(leaf, dir_item); + read_extent_buffer(leaf, filename, name_ptr, name_len); + filename[name_len] = '\0'; + type = btrfs_dir_type(leaf, dir_item); + btrfs_dir_item_key_to_cpu(leaf, dir_item, &location); + + /* full path from root of btrfs being restored */ + snprintf(fs_name, PATH_MAX, "%s/%s", in_dir, filename); + + if (mreg && REG_NOMATCH == regexec(mreg, fs_name, 0, NULL, 0)) + goto next; + + /* full path from system root */ + snprintf(path_name, PATH_MAX, "%s%s", output_rootdir, fs_name); + + /* + * Restore directories, files, symlinks and metadata. + */ + if (type == BTRFS_FT_REG_FILE) { + if (!overwrite_ok(path_name)) + goto next; + + if (verbose) + printf("Restoring %s\n", path_name); + if (dry_run) + goto next; + fd = open(path_name, O_CREAT|O_WRONLY, 0644); + if (fd < 0) { + fprintf(stderr, "Error creating %s: %d\n", + path_name, errno); + if (ignore_errors) + goto next; + ret = -1; + goto out; + } + loops = 0; + ret = copy_file(root, fd, &location, path_name); + close(fd); + if (ret) { + fprintf(stderr, "Error copying data for %s\n", + path_name); + if (ignore_errors) + goto next; + goto out; + } + } else if (type == BTRFS_FT_DIR) { + struct btrfs_root *search_root = root; + char *dir = strdup(fs_name); + + if (!dir) { + fprintf(stderr, "Ran out of memory\n"); + ret = -ENOMEM; + goto out; + } + + if (location.type == BTRFS_ROOT_ITEM_KEY) { + /* + * If we are a snapshot and this is the index + * object to ourselves just skip it. + */ + if (location.objectid == + root->root_key.objectid) { + free(dir); + goto next; + } + + location.offset = (u64)-1; + search_root = btrfs_read_fs_root(root->fs_info, + &location); + if (IS_ERR(search_root)) { + free(dir); + fprintf(stderr, "Error reading " + "subvolume %s: %lu\n", + path_name, + PTR_ERR(search_root)); + if (ignore_errors) + goto next; + ret = PTR_ERR(search_root); + goto out; + } + + /* + * A subvolume will have a key.offset of 0, a + * snapshot will have key.offset of a transid. + */ + if (search_root->root_key.offset != 0 && + get_snaps == 0) { + free(dir); + printf("Skipping snapshot %s\n", + filename); + goto next; + } + location.objectid = BTRFS_FIRST_FREE_OBJECTID; + } + + if (verbose) + printf("Restoring %s\n", path_name); + + errno = 0; + if (dry_run) + ret = 0; + else + ret = mkdir(path_name, 0755); + if (ret && errno != EEXIST) { + free(dir); + fprintf(stderr, "Error mkdiring %s: %d\n", + path_name, errno); + if (ignore_errors) + goto next; + ret = -1; + goto out; + } + loops = 0; + ret = search_dir(search_root, &location, + output_rootdir, dir, mreg); + free(dir); + if (ret) { + fprintf(stderr, "Error searching %s\n", + path_name); + if (ignore_errors) + goto next; + goto out; + } + } else if (type == BTRFS_FT_SYMLINK) { + if (restore_symlinks) + ret = copy_symlink(root, &location, path_name); + if (ret < 0) { + if (ignore_errors) + goto next; + btrfs_free_path(path); + return ret; + } + } +next: + path->slots[0]++; + } + + if (restore_metadata) { + snprintf(path_name, PATH_MAX, "%s%s", output_rootdir, in_dir); + fd = open(path_name, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "ERROR: Failed to access %s to restore metadata\n", + path_name); + if (!ignore_errors) { + ret = -1; + goto out; + } + } else { + /* + * Set owner/mode/time on the directory as well + */ + key->type = BTRFS_INODE_ITEM_KEY; + ret = copy_metadata(root, fd, key); + close(fd); + if (ret && !ignore_errors) + goto out; + } + } + + if (verbose) + printf("Done searching %s\n", in_dir); +out: + btrfs_free_path(path); + return ret; +} + +static int do_list_roots(struct btrfs_root *root) +{ + struct btrfs_key key; + struct btrfs_key found_key; + struct btrfs_disk_key disk_key; + struct btrfs_path *path; + struct extent_buffer *leaf; + struct btrfs_root_item ri; + unsigned long offset; + int slot; + int ret; + + root = root->fs_info->tree_root; + path = btrfs_alloc_path(); + if (!path) { + fprintf(stderr, "Failed to alloc path\n"); + return -ENOMEM; + } + + key.offset = 0; + key.objectid = 0; + key.type = BTRFS_ROOT_ITEM_KEY; + + ret = btrfs_search_slot(NULL, root, &key, path, 0, 0); + if (ret < 0) { + fprintf(stderr, "Failed to do search %d\n", ret); + btrfs_free_path(path); + return -1; + } + + leaf = path->nodes[0]; + + while (1) { + slot = path->slots[0]; + if (slot >= btrfs_header_nritems(leaf)) { + ret = btrfs_next_leaf(root, path); + if (ret) + break; + leaf = path->nodes[0]; + slot = path->slots[0]; + } + btrfs_item_key(leaf, &disk_key, slot); + btrfs_disk_key_to_cpu(&found_key, &disk_key); + if (btrfs_key_type(&found_key) != BTRFS_ROOT_ITEM_KEY) { + path->slots[0]++; + continue; + } + + offset = btrfs_item_ptr_offset(leaf, slot); + read_extent_buffer(leaf, &ri, offset, sizeof(ri)); + printf(" tree "); + btrfs_print_key(&disk_key); + printf(" %Lu level %d\n", btrfs_root_bytenr(&ri), + btrfs_root_level(&ri)); + path->slots[0]++; + } + btrfs_free_path(path); + + return 0; +} + +static struct btrfs_root *open_fs(const char *dev, u64 root_location, + int super_mirror, int list_roots) +{ + struct btrfs_fs_info *fs_info = NULL; + struct btrfs_root *root = NULL; + u64 bytenr; + int i; + + for (i = super_mirror; i < BTRFS_SUPER_MIRROR_MAX; i++) { + bytenr = btrfs_sb_offset(i); + fs_info = open_ctree_fs_info(dev, bytenr, root_location, + OPEN_CTREE_PARTIAL); + if (fs_info) + break; + fprintf(stderr, "Could not open root, trying backup super\n"); + } + + if (!fs_info) + return NULL; + + /* + * All we really need to succeed is reading the chunk tree, everything + * else we can do by hand, since we only need to read the tree root and + * the fs_root. + */ + if (!extent_buffer_uptodate(fs_info->tree_root->node)) { + u64 generation; + + root = fs_info->tree_root; + if (!root_location) + root_location = btrfs_super_root(fs_info->super_copy); + generation = btrfs_super_generation(fs_info->super_copy); + root->node = read_tree_block(root, root_location, + root->leafsize, generation); + if (!extent_buffer_uptodate(root->node)) { + fprintf(stderr, "Error opening tree root\n"); + close_ctree(root); + return NULL; + } + } + + if (!list_roots && !fs_info->fs_root) { + struct btrfs_key key; + + key.objectid = BTRFS_FS_TREE_OBJECTID; + key.type = BTRFS_ROOT_ITEM_KEY; + key.offset = (u64)-1; + fs_info->fs_root = btrfs_read_fs_root_no_cache(fs_info, &key); + if (IS_ERR(fs_info->fs_root)) { + fprintf(stderr, "Couldn't read fs root: %ld\n", + PTR_ERR(fs_info->fs_root)); + close_ctree(fs_info->tree_root); + return NULL; + } + } + + if (list_roots && do_list_roots(fs_info->tree_root)) { + close_ctree(fs_info->tree_root); + return NULL; + } + + return fs_info->fs_root; +} + +static int find_first_dir(struct btrfs_root *root, u64 *objectid) +{ + struct btrfs_path *path; + struct btrfs_key found_key; + struct btrfs_key key; + int ret = -1; + int i; + + key.objectid = 0; + key.type = BTRFS_DIR_INDEX_KEY; + key.offset = 0; + + path = btrfs_alloc_path(); + if (!path) { + fprintf(stderr, "Ran out of memory\n"); + return ret; + } + + ret = btrfs_search_slot(NULL, root, &key, path, 0, 0); + if (ret < 0) { + fprintf(stderr, "Error searching %d\n", ret); + goto out; + } + + if (!path->nodes[0]) { + fprintf(stderr, "No leaf!\n"); + goto out; + } +again: + for (i = path->slots[0]; + i < btrfs_header_nritems(path->nodes[0]); i++) { + btrfs_item_key_to_cpu(path->nodes[0], &found_key, i); + if (found_key.type != key.type) + continue; + + printf("Using objectid %Lu for first dir\n", + found_key.objectid); + *objectid = found_key.objectid; + ret = 0; + goto out; + } + do { + ret = next_leaf(root, path); + if (ret < 0) { + fprintf(stderr, "Error getting next leaf %d\n", + ret); + goto out; + } else if (ret > 0) { + fprintf(stderr, "No more leaves\n"); + goto out; + } + } while (!path->nodes[0]); + if (path->nodes[0]) + goto again; + printf("Couldn't find a dir index item\n"); +out: + btrfs_free_path(path); + return ret; +} + +const char * const cmd_restore_usage[] = { + "btrfs restore [options] <device> <path> | -l <device>", + "Try to restore files from a damaged filesystem (unmounted)", + "", + "-s|--snapshots get snapshots", + "-x|--xattr get extended attributes", + "-m|--metadata restore owner, mode and times", + "-S|--symlinks restore symbolic links", + "-v|--verbose verbose", + "-i|--ignore-errors ignore errors", + "-o|--overwrite overwrite", + "-t <bytenr> tree location", + "-f <bytenr> filesystem location", + "-u|--super <mirror> super mirror", + "-r|--root <rootid> root objectid", + "-d find dir", + "-l|--list-roots list tree roots", + "-D|--dry-run dry run (only list files that would be recovered)", + "--path-regex <regex>", + " restore only filenames matching regex,", + " you have to use following syntax (possibly quoted):", + " ^/(|home(|/username(|/Desktop(|/.*))))$", + "-c ignore case (--path-regex only)", + NULL +}; + +int cmd_restore(int argc, char **argv) +{ + struct btrfs_root *root; + struct btrfs_key key; + char dir_name[PATH_MAX]; + u64 tree_location = 0; + u64 fs_location = 0; + u64 root_objectid = 0; + int len; + int ret; + int super_mirror = 0; + int find_dir = 0; + int list_roots = 0; + const char *match_regstr = NULL; + int match_cflags = REG_EXTENDED | REG_NOSUB | REG_NEWLINE; + regex_t match_reg, *mreg = NULL; + char reg_err[256]; + + while (1) { + int opt; + static const struct option long_options[] = { + { "path-regex", required_argument, NULL, 256}, + { "dry-run", no_argument, NULL, 'D'}, + { "metadata", no_argument, NULL, 'm'}, + { "symlinks", no_argument, NULL, 'S'}, + { "snapshots", no_argument, NULL, 's'}, + { "xattr", no_argument, NULL, 'x'}, + { "verbose", no_argument, NULL, 'v'}, + { "ignore-errors", no_argument, NULL, 'i'}, + { "overwrite", no_argument, NULL, 'o'}, + { "super", required_argument, NULL, 'u'}, + { "root", required_argument, NULL, 'r'}, + { "list-roots", no_argument, NULL, 'l'}, + { NULL, 0, NULL, 0} + }; + + opt = getopt_long(argc, argv, "sSxviot:u:dmf:r:lDc", long_options, + NULL); + if (opt < 0) + break; + + switch (opt) { + case 's': + get_snaps = 1; + break; + case 'v': + verbose++; + break; + case 'i': + ignore_errors = 1; + break; + case 'o': + overwrite = 1; + break; + case 't': + tree_location = arg_strtou64(optarg); + break; + case 'f': + fs_location = arg_strtou64(optarg); + break; + case 'u': + super_mirror = arg_strtou64(optarg); + if (super_mirror >= BTRFS_SUPER_MIRROR_MAX) { + fprintf(stderr, "Super mirror not " + "valid\n"); + exit(1); + } + break; + case 'd': + find_dir = 1; + break; + case 'r': + root_objectid = arg_strtou64(optarg); + if (!is_fstree(root_objectid)) { + fprintf(stderr, "objectid %llu is not a valid fs/file tree\n", + root_objectid); + exit(1); + } + break; + case 'l': + list_roots = 1; + break; + case 'm': + restore_metadata = 1; + break; + case 'S': + restore_symlinks = 1; + break; + case 'D': + dry_run = 1; + break; + case 'c': + match_cflags |= REG_ICASE; + break; + /* long option without single letter alternative */ + case 256: + match_regstr = optarg; + break; + case 'x': + get_xattrs = 1; + break; + default: + usage(cmd_restore_usage); + } + } + + if (!list_roots && check_argc_min(argc - optind, 2)) + usage(cmd_restore_usage); + else if (list_roots && check_argc_min(argc - optind, 1)) + usage(cmd_restore_usage); + + if (fs_location && root_objectid) { + fprintf(stderr, "don't use -f and -r at the same time.\n"); + return 1; + } + + if ((ret = check_mounted(argv[optind])) < 0) { + fprintf(stderr, "Could not check mount status: %s\n", + strerror(-ret)); + return 1; + } else if (ret) { + fprintf(stderr, "%s is currently mounted. Aborting.\n", argv[optind]); + return 1; + } + + root = open_fs(argv[optind], tree_location, super_mirror, list_roots); + if (root == NULL) + return 1; + + if (list_roots) + goto out; + + if (fs_location != 0) { + free_extent_buffer(root->node); + root->node = read_tree_block(root, fs_location, root->leafsize, 0); + if (!extent_buffer_uptodate(root->node)) { + fprintf(stderr, "Failed to read fs location\n"); + ret = 1; + goto out; + } + } + + memset(path_name, 0, PATH_MAX); + + if (strlen(argv[optind + 1]) >= PATH_MAX) { + fprintf(stderr, "ERROR: path too long\n"); + ret = 1; + goto out; + } + strncpy(dir_name, argv[optind + 1], sizeof dir_name); + dir_name[sizeof dir_name - 1] = 0; + + /* Strip the trailing / on the dir name */ + len = strlen(dir_name); + while (len && dir_name[--len] == '/') { + dir_name[len] = '\0'; + } + + if (root_objectid != 0) { + struct btrfs_root *orig_root = root; + + key.objectid = root_objectid; + key.type = BTRFS_ROOT_ITEM_KEY; + key.offset = (u64)-1; + root = btrfs_read_fs_root(orig_root->fs_info, &key); + if (IS_ERR(root)) { + fprintf(stderr, "fail to read root %llu: %s\n", + root_objectid, strerror(-PTR_ERR(root))); + root = orig_root; + ret = 1; + goto out; + } + key.type = 0; + key.offset = 0; + } + + if (find_dir) { + ret = find_first_dir(root, &key.objectid); + if (ret) + goto out; + } else { + key.objectid = BTRFS_FIRST_FREE_OBJECTID; + } + + if (match_regstr) { + ret = regcomp(&match_reg, match_regstr, match_cflags); + if (ret) { + regerror(ret, &match_reg, reg_err, sizeof(reg_err)); + fprintf(stderr, "Regex compile failed: %s\n", reg_err); + goto out; + } + mreg = &match_reg; + } + + if (dry_run) + printf("This is a dry-run, no files are going to be restored\n"); + + ret = search_dir(root, &key, dir_name, "", mreg); + +out: + if (mreg) + regfree(mreg); + close_ctree(root); + return !!ret; +} |