+
+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;