X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=cmds-restore.c;h=ade35f0f880fc77e22ea0639c013af5c0095ec7f;hb=df9158f20d8950b996a3e9b5a4ac0f9b2ea034d3;hp=b52d5c8b411e9ccae53f97a897dc163d5cf6e78b;hpb=6d6a3e79917487fd3c8945b82e785f77ac84882b;p=platform%2Fupstream%2Fbtrfs-progs.git diff --git a/cmds-restore.c b/cmds-restore.c index b52d5c8..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,6 +29,9 @@ #include #include #include +#if BTRFSRESTORE_ZSTD +#include +#endif #include #include #include @@ -41,22 +42,24 @@ #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, @@ -68,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; } @@ -79,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; } @@ -93,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; @@ -105,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; } @@ -114,29 +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) { - fprintf(stderr, "bad compress length %lu\n", + 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; @@ -144,21 +159,68 @@ 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; } @@ -169,6 +231,7 @@ static int next_leaf(struct btrfs_root *root, struct btrfs_path *path) 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++) { @@ -176,7 +239,7 @@ again: break; } - if (level == BTRFS_MAX_LEVEL) + if (level >= BTRFS_MAX_LEVEL) return 1; slot = path->slots[level] + 1; @@ -198,8 +261,8 @@ again: 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++; } @@ -214,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; @@ -256,11 +320,11 @@ static int copy_one_inline(int fd, struct btrfs_path *path, u64 pos) ram_size = btrfs_file_extent_ram_bytes(leaf, fi); outbuf = calloc(1, ram_size); if (!outbuf) { - fprintf(stderr, "No memory\n"); + 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; @@ -318,31 +382,33 @@ static int copy_one_extent(struct btrfs_root *root, int fd, inbuf = malloc(size_left); if (!inbuf) { - fprintf(stderr, "No memory\n"); + error("not enough memory"); return -ENOMEM; } if (compress != BTRFS_COMPRESS_NONE) { outbuf = calloc(1, ram_size); if (!outbuf) { - fprintf(stderr, "No memory\n"); + error("not enough memory"); free(inbuf); 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; @@ -350,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"); @@ -376,7 +442,7 @@ again: 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; @@ -385,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; @@ -446,7 +511,7 @@ 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 btrfs_path path; struct extent_buffer *leaf; struct btrfs_dir_item *di; u32 name_len = 0; @@ -457,26 +522,21 @@ static int set_file_xattrs(struct btrfs_root *root, u64 inode, char *data = NULL; int ret = 0; + btrfs_init_path(&path); 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); + ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0); if (ret < 0) goto out; - leaf = path->nodes[0]; + leaf = path.nodes[0]; while (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 for extended attributes: %d\n", + error("searching for extended attributes: %d", ret); goto out; } else if (ret) { @@ -484,17 +544,17 @@ static int set_file_xattrs(struct btrfs_root *root, u64 inode, ret = 0; goto out; } - leaf = path->nodes[0]; + leaf = path.nodes[0]; } while (!leaf); continue; } - btrfs_item_key_to_cpu(leaf, &key, path->slots[0]); + 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], + 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) { @@ -526,82 +586,145 @@ static int set_file_xattrs(struct btrfs_root *root, u64 inode, len); data_len = len; - if (fsetxattr(fd, name, data, data_len, 0)) { - int err = errno; - - fprintf(stderr, - "Error setting extended attribute %s on file %s: %s\n", - name, file_name, strerror(err)); - } + 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]++; + path.slots[0]++; } ret = 0; out: - btrfs_free_path(path); + 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 -ENOMEM; - } - - 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(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) { @@ -616,61 +739,56 @@ static int copy_file(struct btrfs_root *root, int fd, struct btrfs_key *key, 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); @@ -682,61 +800,209 @@ set_size: 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 *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 -ENOMEM; - } - + 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) { @@ -747,28 +1013,27 @@ static int search_dir(struct btrfs_root *root, struct btrfs_key *key, 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", @@ -781,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); @@ -791,38 +1056,21 @@ 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", in_dir, filename); + 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, 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) @@ -833,8 +1081,8 @@ static int search_dir(struct btrfs_root *root, struct btrfs_key *key, 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); @@ -844,8 +1092,7 @@ static int search_dir(struct btrfs_root *root, struct btrfs_key *key, 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; @@ -853,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 -ENOMEM; + ret = -ENOMEM; + goto out; } if (location.type == BTRFS_ROOT_ITEM_KEY) { @@ -879,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; } /* @@ -911,8 +1158,8 @@ static int search_dir(struct btrfs_root *root, struct btrfs_key *key, 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, @@ -923,18 +1170,49 @@ static int search_dir(struct btrfs_root *root, struct btrfs_key *key, 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", in_dir); - btrfs_free_path(path); - return 0; +out: + btrfs_release_path(&path); + return ret; } static int do_list_roots(struct btrfs_root *root) @@ -942,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; @@ -950,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 -ENOMEM; - } + 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; } @@ -990,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; } @@ -1007,7 +1281,7 @@ 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, + fs_info = open_ctree_fs_info(dev, bytenr, root_location, 0, OPEN_CTREE_PARTIAL); if (fs_info) break; @@ -1029,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); @@ -1063,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; @@ -1103,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); @@ -1112,42 +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; } -static struct option long_options[] = { - { "path-regex", 1, NULL, 256}, - { "dry-run", 0, NULL, 'D'}, - { NULL, 0, NULL, 0} -}; - const char * const cmd_restore_usage[] = { "btrfs restore [options] | -l ", "Try to restore files from a damaged filesystem (unmounted)", "", - "-s get snapshots", - "-x get extended attributes", - "-v verbose", - "-i ignore errors", - "-o overwrite", - "-t tree location", - "-f filesystem location", - "-u super mirror", - "-r root objectid", - "-d find dir", - "-l list tree roots", - "-D|--dry-run dry run (only list files that would be recovered)", + "-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-regrex only)", + " restore only filenames matching regex,", + " you have to use following syntax (possibly quoted):", + " ^/(|home(|/username(|/Desktop(|/.*))))$", + "-c ignore case (--path-regex only)", NULL }; @@ -1155,14 +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 option_index = 0; int super_mirror = 0; int find_dir = 0; int list_roots = 0; @@ -1171,8 +1433,30 @@ int cmd_restore(int argc, char **argv) regex_t match_reg, *mreg = NULL; char reg_err[256]; - while ((opt = getopt_long(argc, argv, "sxviot:u:df:r:lDc", long_options, - &option_index)) != -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': @@ -1215,14 +1499,19 @@ int cmd_restore(int argc, char **argv) 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: + case GETOPT_VAL_PATH_REGEX: match_regstr = optarg; break; case 'x': @@ -1261,16 +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;