X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=cmds-restore.c;h=ade35f0f880fc77e22ea0639c013af5c0095ec7f;hb=df9158f20d8950b996a3e9b5a4ac0f9b2ea034d3;hp=1aae390298a14c6b2952d93b1dd8da8b6a503af4;hpb=e35450fa53139572afdd759619610cc7f5277626;p=platform%2Fupstream%2Fbtrfs-progs.git diff --git a/cmds-restore.c b/cmds-restore.c index 1aae390..ade35f0 100644 --- a/cmds-restore.c +++ b/cmds-restore.c @@ -16,8 +16,6 @@ * Boston, MA 021110-1307, USA. */ -#define _XOPEN_SOURCE 500 -#define _GNU_SOURCE 1 #include "kerncompat.h" @@ -31,27 +29,37 @@ #include #include #include +#if BTRFSRESTORE_ZSTD +#include +#endif #include +#include +#include +#include #include "ctree.h" #include "disk-io.h" #include "print-tree.h" #include "transaction.h" #include "list.h" -#include "version.h" #include "volumes.h" #include "utils.h" #include "commands.h" +#include "help.h" -static char fs_name[4096]; -static char path_name[4096]; +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, @@ -63,7 +71,7 @@ static int decompress_zlib(char *inbuf, char *outbuf, u64 compress_len, memset(&strm, 0, sizeof(strm)); ret = inflateInit(&strm); if (ret != Z_OK) { - fprintf(stderr, "inflate init returnd %d\n", ret); + error("zlib init returned %d", ret); return -1; } @@ -74,7 +82,7 @@ static int decompress_zlib(char *inbuf, char *outbuf, u64 compress_len, ret = inflate(&strm, Z_NO_FLUSH); if (ret != Z_STREAM_END) { (void)inflateEnd(&strm); - fprintf(stderr, "failed to inflate: %d\n", ret); + error("zlib inflate failed: %d", ret); return -1; } @@ -88,8 +96,8 @@ static inline size_t read_compress_length(unsigned char *buf) return le32_to_cpu(dlen); } -static int decompress_lzo(unsigned char *inbuf, char *outbuf, u64 compress_len, - u64 *decompress_len) +static int decompress_lzo(struct btrfs_root *root, unsigned char *inbuf, + char *outbuf, u64 compress_len, u64 *decompress_len) { size_t new_len; size_t in_len; @@ -100,7 +108,7 @@ static int decompress_lzo(unsigned char *inbuf, char *outbuf, u64 compress_len, ret = lzo_init(); if (ret != LZO_E_OK) { - fprintf(stderr, "lzo init returned %d\n", ret); + error("lzo init returned %d", ret); return -1; } @@ -109,22 +117,41 @@ static int decompress_lzo(unsigned char *inbuf, char *outbuf, u64 compress_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) { + error("bad compress length %lu", + (unsigned long)in_len); + return -1; + } + inbuf += LZO_LEN; tot_in += LZO_LEN; - - new_len = lzo1x_worst_compress(PAGE_CACHE_SIZE); + new_len = lzo1x_worst_compress(root->fs_info->sectorsize); 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); + error("lzo decompress failed: %d", 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 % root->fs_info->sectorsize; + rem_page = root->fs_info->sectorsize - mod_page; + if (rem_page < LZO_LEN) { + inbuf += rem_page; + tot_in += rem_page; + } } *decompress_len = out_len; @@ -132,31 +159,79 @@ static int decompress_lzo(unsigned char *inbuf, char *outbuf, u64 compress_len, return 0; } -static int decompress(char *inbuf, char *outbuf, u64 compress_len, - u64 *decompress_len, int compress) +static int decompress_zstd(const char *inbuf, char *outbuf, u64 compress_len, + u64 decompress_len) +{ +#if !BTRFSRESTORE_ZSTD + error("btrfs not compiled with zstd support"); + return -1; +#else + ZSTD_DStream *strm; + size_t zret; + int ret = 0; + ZSTD_inBuffer in = {inbuf, compress_len, 0}; + ZSTD_outBuffer out = {outbuf, decompress_len, 0}; + + strm = ZSTD_createDStream(); + if (!strm) { + error("zstd create failed"); + return -1; + } + + zret = ZSTD_initDStream(strm); + if (ZSTD_isError(zret)) { + error("zstd init failed: %s", ZSTD_getErrorName(zret)); + ret = -1; + goto out; + } + + zret = ZSTD_decompressStream(strm, &out, &in); + if (ZSTD_isError(zret)) { + error("zstd decompress failed %s\n", ZSTD_getErrorName(zret)); + ret = -1; + goto out; + } + if (zret != 0) { + error("zstd frame incomplete"); + ret = -1; + goto out; + } + +out: + ZSTD_freeDStream(strm); + return ret; +#endif +} + +static int decompress(struct btrfs_root *root, 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); + return decompress_lzo(root, (unsigned char *)inbuf, outbuf, + compress_len, decompress_len); + case BTRFS_COMPRESS_ZSTD: + return decompress_zstd(inbuf, outbuf, compress_len, + *decompress_len); default: break; } - fprintf(stderr, "invalid compression type: %d\n", compress); + error("invalid compression type: %d", compress); return -1; } -int next_leaf(struct btrfs_root *root, struct btrfs_path *path) +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; + struct btrfs_fs_info *fs_info = root->fs_info; again: for (; level < BTRFS_MAX_LEVEL; level++) { @@ -164,7 +239,7 @@ again: break; } - if (level == BTRFS_MAX_LEVEL) + if (level >= BTRFS_MAX_LEVEL) return 1; slot = path->slots[level] + 1; @@ -179,14 +254,15 @@ again: 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 (next) + next = read_node_slot(fs_info, c, slot); + if (extent_buffer_uptodate(next)) break; offset++; } @@ -201,14 +277,15 @@ again: break; if (path->reada) reada_for_search(root, path, level, 0, 0); - next = read_node_slot(root, next, 0); - if (!next) + next = read_node_slot(fs_info, next, 0); + if (!extent_buffer_uptodate(next)) goto again; } return 0; } -static int copy_one_inline(int fd, struct btrfs_path *path, u64 pos) +static int copy_one_inline(struct btrfs_root *root, int fd, + struct btrfs_path *path, u64 pos) { struct extent_buffer *leaf = path->nodes[0]; struct btrfs_file_extent_item *fi; @@ -219,14 +296,15 @@ static int copy_one_inline(int fd, struct btrfs_path *path, u64 pos) 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_item_len(leaf, - btrfs_item_nr(leaf, path->slots[0])); - read_extent_buffer(leaf, buf, ptr, len); + 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) { @@ -240,13 +318,13 @@ static int copy_one_inline(int fd, struct btrfs_path *path, u64 pos) } ram_size = btrfs_file_extent_ram_bytes(leaf, fi); - outbuf = malloc(ram_size); + outbuf = calloc(1, ram_size); if (!outbuf) { - fprintf(stderr, "No memory\n"); - return -1; + error("not enough memory"); + return -ENOMEM; } - ret = decompress(buf, outbuf, len, &ram_size, compress); + ret = decompress(root, buf, outbuf, len, &ram_size, compress); if (ret) { free(outbuf); return ret; @@ -274,6 +352,7 @@ static int copy_one_extent(struct btrfs_root *root, int fd, u64 bytenr; u64 ram_size; u64 disk_size; + u64 num_bytes; u64 length; u64 size_left; u64 dev_bytenr; @@ -290,41 +369,46 @@ static int copy_one_extent(struct btrfs_root *root, int fd, 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 (offset) + if (verbose && offset) printf("offset is %Lu\n", offset); /* we found a hole */ if (disk_size == 0) return 0; - inbuf = malloc(disk_size); + inbuf = malloc(size_left); if (!inbuf) { - fprintf(stderr, "No memory\n"); - return -1; + error("not enough memory"); + return -ENOMEM; } if (compress != BTRFS_COMPRESS_NONE) { - outbuf = malloc(ram_size); + outbuf = calloc(1, ram_size); if (!outbuf) { - fprintf(stderr, "No memory\n"); + error("not enough memory"); free(inbuf); - return -1; + return -ENOMEM; } } again: length = size_left; - ret = btrfs_map_block(&root->fs_info->mapping_tree, READ, - bytenr, &length, &multi, mirror_num, NULL); + ret = btrfs_map_block(root->fs_info, READ, bytenr, &length, &multi, + mirror_num, NULL); if (ret) { - fprintf(stderr, "Error mapping block %d\n", ret); + error("cannot map block logical %llu length %llu: %d", + (unsigned long long)bytenr, + (unsigned long long)length, ret); goto out; } device = multi->stripes[0].dev; dev_fd = device->fd; device->total_ios++; dev_bytenr = multi->stripes[0].physical; - kfree(multi); + free(multi); if (size_left < length) length = size_left; @@ -332,13 +416,13 @@ again: 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); + num_copies = btrfs_num_copies(root->fs_info, 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"); + error("exhausted mirrors trying to read (%d > %d)", + mirror_num, num_copies); goto out; } fprintf(stderr, "Trying another mirror\n"); @@ -353,12 +437,12 @@ again: goto again; if (compress == BTRFS_COMPRESS_NONE) { - while (total < ram_size) { - done = pwrite(fd, inbuf+total, ram_size-total, + 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)); + error("cannot write data: %d %m", errno); goto out; } total += done; @@ -367,10 +451,9 @@ again: goto out; } - ret = decompress(inbuf, outbuf, disk_size, &ram_size, compress); + ret = decompress(root, inbuf, outbuf, disk_size, &ram_size, compress); if (ret) { - num_copies = btrfs_num_copies(&root->fs_info->mapping_tree, - bytenr, length); + num_copies = btrfs_num_copies(root->fs_info, bytenr, length); mirror_num++; if (mirror_num >= num_copies) { ret = -1; @@ -380,8 +463,10 @@ again: goto again; } - while (total < ram_size) { - done = pwrite(fd, outbuf+total, ram_size-total, pos+total); + while (total < num_bytes) { + done = pwrite(fd, outbuf + offset + total, + num_bytes - total, + pos + total); if (done < 0) { ret = -1; goto out; @@ -394,237 +479,561 @@ out: return ret; } -static int ask_to_continue(const char *file) +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): ", file); + "on ? (y/N/a): ", file); again: ret = fgets(buf, 2, stdin); if (*ret == '\n' || tolower(*ret) == 'n') - return 1; + return LOOP_STOP; + if (tolower(*ret) == 'a') + return LOOP_DONTASK; if (tolower(*ret) != 'y') { - printf("Please enter either 'y' or 'n': "); + printf("Please enter one of 'y', 'n', or 'a': "); goto again; } - return 0; + 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; + + btrfs_init_path(&path); + key.objectid = inode; + key.type = BTRFS_XATTR_ITEM_KEY; + key.offset = 0; + 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) { + error("searching for extended attributes: %d", + 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)) + error("setting extended attribute %s on file %s: %m", + name, file_name); + + len = sizeof(*di) + name_len + data_len; + cur += len; + di = (struct btrfs_dir_item *)((char *)di + len); + } + path.slots[0]++; + } + ret = 0; +out: + btrfs_release_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; + + btrfs_init_path(&path); + 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) { + error("failed to change owner: %m"); + goto out; + } + + ret = fchmod(fd, btrfs_inode_mode(path.nodes[0], inode_item)); + if (ret) { + error("failed to change mode: %m"); + 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) { + error("failed to set times: %m"); + goto out; + } + } +out: + btrfs_release_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_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 -1; - } - path->skip_locking = 1; - - ret = btrfs_lookup_inode(NULL, root, path, key, 0); + btrfs_init_path(&path); + ret = btrfs_lookup_inode(NULL, root, &path, key, 0); if (ret == 0) { - inode_item = btrfs_item_ptr(path->nodes[0], path->slots[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); + 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(root, path); + btrfs_release_path(&path); key->offset = 0; key->type = BTRFS_EXTENT_DATA_KEY; - ret = btrfs_search_slot(NULL, root, key, path, 0, 0); + ret = btrfs_search_slot(NULL, root, key, &path, 0, 0); if (ret < 0) { - fprintf(stderr, "Error searching %d\n", ret); - btrfs_free_path(path); - return ret; + error("searching extent data returned %d", ret); + goto out; } - leaf = path->nodes[0]; + leaf = path.nodes[0]; while (!leaf) { - ret = next_leaf(root, path); + ret = next_leaf(root, &path); if (ret < 0) { - fprintf(stderr, "Error getting next leaf %d\n", - ret); - btrfs_free_path(path); - return ret; + error("cannot get next leaf: %d", ret); + goto out; } else if (ret > 0) { /* No more leaves to search */ - btrfs_free_path(path); - return 0; + ret = 0; + goto out; } - leaf = path->nodes[0]; + leaf = path.nodes[0]; } while (1) { - if (loops++ >= 1024) { - ret = ask_to_continue(file); - if (ret) + if (loops >= 0 && loops++ >= 1024) { + enum loop_response resp; + + resp = ask_to_continue(file); + if (resp == LOOP_STOP) break; - loops = 0; + else if (resp == LOOP_CONTINUE) + loops = 0; + else if (resp == LOOP_DONTASK) + loops = -1; } - if (path->slots[0] >= btrfs_header_nritems(leaf)) { + if (path.slots[0] >= btrfs_header_nritems(leaf)) { do { - ret = next_leaf(root, path); + ret = next_leaf(root, &path); if (ret < 0) { fprintf(stderr, "Error searching %d\n", ret); - btrfs_free_path(path); - return ret; + goto out; } else if (ret) { /* No more leaves to search */ - btrfs_free_path(path); + btrfs_release_path(&path); goto set_size; } - leaf = path->nodes[0]; + leaf = path.nodes[0]; } while (!leaf); continue; } - btrfs_item_key_to_cpu(leaf, &found_key, path->slots[0]); + 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], + 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", + warning("compression type %d not supported", compression); - btrfs_free_path(path); - return -1; + 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) { - btrfs_free_path(path); - return -1; - } + ret = copy_one_inline(root, 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) { - btrfs_free_path(path); - return ret; - } + if (ret) + goto out; } else { - printf("Weird extent type %d\n", extent_type); + warning("weird extent type %d", extent_type); } next: - path->slots[0]++; + path.slots[0]++; } - btrfs_free_path(path); + btrfs_release_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_release_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; + } + } + + btrfs_init_path(&path); + key->type = BTRFS_EXTENT_DATA_KEY; + key->offset = 0; + 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': %m\n", + path_name); + 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: %m\n"); + 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: %m\n"); +out: + btrfs_release_path(&path); + return ret; } static int search_dir(struct btrfs_root *root, struct btrfs_key *key, - const char *output_rootdir, const char *dir, + const char *output_rootdir, const char *in_dir, const regex_t *mreg) { - struct btrfs_path *path; + 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; + int ret = 0; int fd; int loops = 0; u8 type; - path = btrfs_alloc_path(); - if (!path) { - fprintf(stderr, "Ran out of memory\n"); - return -1; - } - path->skip_locking = 1; - + btrfs_init_path(&path); key->offset = 0; key->type = BTRFS_DIR_INDEX_KEY; - - ret = btrfs_search_slot(NULL, root, key, path, 0, 0); + ret = btrfs_search_slot(NULL, root, key, &path, 0, 0); if (ret < 0) { fprintf(stderr, "Error searching %d\n", ret); - btrfs_free_path(path); - return ret; + goto out; } - leaf = path->nodes[0]; + 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); + ret = next_leaf(root, &path); if (ret < 0) { fprintf(stderr, "Error getting next leaf %d\n", ret); - btrfs_free_path(path); - return 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"); - btrfs_free_path(path); - return 0; + ret = 0; + goto out; } - leaf = path->nodes[0]; + 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", dir); + "stopping\n", in_dir); break; } - if (path->slots[0] >= btrfs_header_nritems(leaf)) { + if (path.slots[0] >= btrfs_header_nritems(leaf)) { do { - ret = next_leaf(root, path); + ret = next_leaf(root, &path); if (ret < 0) { fprintf(stderr, "Error searching %d\n", ret); - btrfs_free_path(path); - return 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"); - btrfs_free_path(path); - return 0; + ret = 0; + goto out; } - leaf = path->nodes[0]; + leaf = path.nodes[0]; } while (!leaf); continue; } - btrfs_item_key_to_cpu(leaf, &found_key, path->slots[0]); + 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", @@ -637,7 +1046,7 @@ static int search_dir(struct btrfs_root *root, struct btrfs_key *key, found_key.type, key->type); break; } - dir_item = btrfs_item_ptr(leaf, path->slots[0], + 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); @@ -647,57 +1056,43 @@ static int search_dir(struct btrfs_root *root, struct btrfs_key *key, btrfs_dir_item_key_to_cpu(leaf, dir_item, &location); /* full path from root of btrfs being restored */ - snprintf(fs_name, 4096, "%s/%s", dir, filename); + snprintf(fs_name, PATH_MAX, "%s/%s", in_dir, filename); - if (REG_NOMATCH == regexec(mreg, fs_name, 0, NULL, 0)) + if (mreg && REG_NOMATCH == regexec(mreg, fs_name, 0, NULL, 0)) goto next; /* full path from system root */ - snprintf(path_name, 4096, "%s%s", output_rootdir, fs_name); + snprintf(path_name, PATH_MAX, "%s%s", output_rootdir, fs_name); /* - * At this point we're only going to restore directories and - * files, no symlinks or anything else. + * Restore directories, files, symlinks and metadata. */ if (type == BTRFS_FT_REG_FILE) { - if (!overwrite) { - static int warn = 0; - struct stat st; - - ret = stat(path_name, &st); - if (!ret) { - loops = 0; - if (verbose || !warn) - printf("Skipping existing file" - " %s\n", path_name); - if (warn) - goto next; - printf("If you wish to overwrite use " - "the -o option to overwrite\n"); - warn = 1; - goto next; - } - ret = 0; - } + 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; - btrfs_free_path(path); - return -1; + 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; - btrfs_free_path(path); - return ret; + goto out; } } else if (type == BTRFS_FT_DIR) { struct btrfs_root *search_root = root; @@ -705,8 +1100,8 @@ static int search_dir(struct btrfs_root *root, struct btrfs_key *key, if (!dir) { fprintf(stderr, "Ran out of memory\n"); - btrfs_free_path(path); - return -1; + ret = -ENOMEM; + goto out; } if (location.type == BTRFS_ROOT_ITEM_KEY) { @@ -731,8 +1126,8 @@ static int search_dir(struct btrfs_root *root, struct btrfs_key *key, PTR_ERR(search_root)); if (ignore_errors) goto next; - btrfs_free_path(path); - return PTR_ERR(search_root); + ret = PTR_ERR(search_root); + goto out; } /* @@ -753,35 +1148,71 @@ static int search_dir(struct btrfs_root *root, struct btrfs_key *key, printf("Restoring %s\n", path_name); errno = 0; - ret = mkdir(path_name, 0755); + 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; - btrfs_free_path(path); - return -1; + 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; - btrfs_free_path(path); + 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_release_path(&path); return ret; } } next: - path->slots[0]++; + 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", dir); - btrfs_free_path(path); - return 0; + printf("Done searching %s\n", in_dir); +out: + btrfs_release_path(&path); + return ret; } static int do_list_roots(struct btrfs_root *root) @@ -789,7 +1220,7 @@ 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 btrfs_path path; struct extent_buffer *leaf; struct btrfs_root_item ri; unsigned long offset; @@ -797,37 +1228,33 @@ static int do_list_roots(struct btrfs_root *root) int ret; root = root->fs_info->tree_root; - path = btrfs_alloc_path(); - if (!path) { - fprintf(stderr, "Failed to alloc path\n"); - return -1; - } + btrfs_init_path(&path); key.offset = 0; key.objectid = 0; key.type = BTRFS_ROOT_ITEM_KEY; - - ret = btrfs_search_slot(NULL, root, &key, path, 0, 0); + 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); + btrfs_release_path(&path); return -1; } + leaf = path.nodes[0]; + while (1) { - leaf = path->nodes[0]; - slot = path->slots[0]; + slot = path.slots[0]; if (slot >= btrfs_header_nritems(leaf)) { - ret = btrfs_next_leaf(root, path); + ret = btrfs_next_leaf(root, &path); if (ret) break; - leaf = path->nodes[0]; - slot = path->slots[0]; + 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]++; + if (found_key.type != BTRFS_ROOT_ITEM_KEY) { + path.slots[0]++; continue; } @@ -837,9 +1264,9 @@ static int do_list_roots(struct btrfs_root *root) btrfs_print_key(&disk_key); printf(" %Lu level %d\n", btrfs_root_bytenr(&ri), btrfs_root_level(&ri)); - path->slots[0]++; + path.slots[0]++; } - btrfs_free_path(path); + btrfs_release_path(&path); return 0; } @@ -854,7 +1281,8 @@ static struct btrfs_root *open_fs(const char *dev, u64 root_location, 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, 0, 1); + fs_info = open_ctree_fs_info(dev, bytenr, root_location, 0, + OPEN_CTREE_PARTIAL); if (fs_info) break; fprintf(stderr, "Could not open root, trying backup super\n"); @@ -875,8 +1303,8 @@ static struct btrfs_root *open_fs(const char *dev, u64 root_location, 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); + root->node = read_tree_block(fs_info, root_location, + generation); if (!extent_buffer_uptodate(root->node)) { fprintf(stderr, "Error opening tree root\n"); close_ctree(root); @@ -909,36 +1337,30 @@ static struct btrfs_root *open_fs(const char *dev, u64 root_location, static int find_first_dir(struct btrfs_root *root, u64 *objectid) { - struct btrfs_path *path; + struct btrfs_path path; struct btrfs_key found_key; struct btrfs_key key; int ret = -1; int i; + btrfs_init_path(&path); 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); + 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]) { + 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); + 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; @@ -949,7 +1371,7 @@ again: goto out; } do { - ret = next_leaf(root, path); + ret = next_leaf(root, &path); if (ret < 0) { fprintf(stderr, "Error getting next leaf %d\n", ret); @@ -958,27 +1380,38 @@ again: fprintf(stderr, "No more leaves\n"); goto out; } - } while (!path->nodes[0]); - if (path->nodes[0]) + } while (!path.nodes[0]); + if (path.nodes[0]) goto again; printf("Couldn't find a dir index item\n"); out: - btrfs_free_path(path); + btrfs_release_path(&path); return ret; } const char * const cmd_restore_usage[] = { - "btrfs restore [options] ", + "btrfs restore [options] | -l ", "Try to restore files from a damaged filesystem (unmounted)", "", - "-s get snapshots", - "-v verbose", - "-i ignore errors", - "-o overwrite", - "-t tree location", - "-f filesystem location", - "-u super mirror", - "-d find dir", + "-s|--snapshots get snapshots", + "-x|--xattr restore extended attributes", + "-m|--metadata restore owner, mode and times", + "-S|--symlink restore symbolic links", + "-v|--verbose verbose", + "-i|--ignore-errors ignore errors", + "-o|--overwrite overwrite", + "-t tree location", + "-f filesystem location", + "-u|--super super mirror", + "-r|--root 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 ", + " restore only filenames matching regex,", + " you have to use following syntax (possibly quoted):", + " ^/(|home(|/username(|/Desktop(|/.*))))$", + "-c ignore case (--path-regex only)", NULL }; @@ -986,13 +1419,12 @@ int cmd_restore(int argc, char **argv) { struct btrfs_root *root; struct btrfs_key key; - char dir_name[128]; + char dir_name[PATH_MAX]; u64 tree_location = 0; u64 fs_location = 0; u64 root_objectid = 0; int len; int ret; - int opt; int super_mirror = 0; int find_dir = 0; int list_roots = 0; @@ -1001,7 +1433,30 @@ int cmd_restore(int argc, char **argv) regex_t match_reg, *mreg = NULL; char reg_err[256]; - while ((opt = getopt(argc, argv, "sviot:u:df:r:lcm:")) != -1) { + while (1) { + int opt; + enum { GETOPT_VAL_PATH_REGEX = 256 }; + static const struct option long_options[] = { + { "path-regex", required_argument, NULL, + GETOPT_VAL_PATH_REGEX }, + { "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': @@ -1017,26 +1472,14 @@ int cmd_restore(int argc, char **argv) overwrite = 1; break; case 't': - errno = 0; - tree_location = (u64)strtoll(optarg, NULL, 10); - if (errno != 0) { - fprintf(stderr, "Tree location not valid\n"); - exit(1); - } + tree_location = arg_strtou64(optarg); break; case 'f': - errno = 0; - fs_location = (u64)strtoll(optarg, NULL, 10); - if (errno != 0) { - fprintf(stderr, "Fs location not valid\n"); - exit(1); - } + fs_location = arg_strtou64(optarg); break; case 'u': - errno = 0; - super_mirror = (int)strtol(optarg, NULL, 10); - if (errno != 0 || - super_mirror >= BTRFS_SUPER_MIRROR_MAX) { + super_mirror = arg_strtou64(optarg); + if (super_mirror >= BTRFS_SUPER_MIRROR_MAX) { fprintf(stderr, "Super mirror not " "valid\n"); exit(1); @@ -1046,36 +1489,53 @@ int cmd_restore(int argc, char **argv) find_dir = 1; break; case 'r': - errno = 0; - root_objectid = (u64)strtoll(optarg, NULL, 10); - if (errno != 0) { - fprintf(stderr, "Root objectid not valid\n"); + 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; - case 'm': + case GETOPT_VAL_PATH_REGEX: match_regstr = optarg; break; + case 'x': + get_xattrs = 1; + break; default: usage(cmd_restore_usage); } } - if (!list_roots && optind + 1 >= argc) + if (!list_roots && check_argc_min(argc - optind, 2)) usage(cmd_restore_usage); - else if (list_roots && optind >= argc) + 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 ret; + return 1; } else if (ret) { fprintf(stderr, "%s is currently mounted. Aborting.\n", argv[optind]); return 1; @@ -1090,15 +1550,21 @@ int cmd_restore(int argc, char **argv) if (fs_location != 0) { free_extent_buffer(root->node); - root->node = read_tree_block(root, fs_location, root->leafsize, 0); - if (!root->node) { + root->node = read_tree_block(root->fs_info, fs_location, 0); + if (!extent_buffer_uptodate(root->node)) { fprintf(stderr, "Failed to read fs location\n"); + ret = 1; goto out; } } - memset(path_name, 0, 4096); + 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; @@ -1116,7 +1582,8 @@ int cmd_restore(int argc, char **argv) key.offset = (u64)-1; root = btrfs_read_fs_root(orig_root->fs_info, &key); if (IS_ERR(root)) { - fprintf(stderr, "Error reading root\n"); + fprintf(stderr, "fail to read root %llu: %s\n", + root_objectid, strerror(-PTR_ERR(root))); root = orig_root; ret = 1; goto out; @@ -1143,11 +1610,14 @@ int cmd_restore(int argc, char **argv) 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; + return !!ret; }