diff --git a/Makefile b/Makefile index 64a8558e..7cc4bb4a 100644 --- a/Makefile +++ b/Makefile @@ -234,8 +234,8 @@ libbtrfs_objects = \ common/path-utils.o \ common/rbtree-utils.o \ common/repair.o \ - common/send-stream.o \ - common/send-utils.o \ + libbtrfs/send-stream.o \ + libbtrfs/send-utils.o \ common/units.o \ common/utils-lib.o \ common/utils.o \ @@ -246,7 +246,7 @@ libbtrfs_objects = \ crypto/xxhash.o \ $(CRYPTO_OBJECTS) \ -libbtrfs_headers = common/send-stream.h common/send-utils.h kernel-shared/send.h kernel-lib/rbtree.h \ +libbtrfs_headers = libbtrfs/send-stream.h libbtrfs/send-utils.h kernel-shared/send.h kernel-lib/rbtree.h \ crypto/crc32c.h kernel-lib/list.h kerncompat.h \ kernel-lib/radix-tree.h kernel-lib/sizes.h \ common/extent-cache.h kernel-shared/extent_io.h ioctl.h \ diff --git a/libbtrfs/send-stream.c b/libbtrfs/send-stream.c new file mode 100644 index 00000000..d07748ce --- /dev/null +++ b/libbtrfs/send-stream.c @@ -0,0 +1,530 @@ +/* + * Copyright (C) 2012 Alexander Block. 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 +#include + +#include "kernel-shared/send.h" +#include "common/send-stream.h" +#include "crypto/crc32c.h" +#include "common/utils.h" + +struct btrfs_send_stream { + char read_buf[BTRFS_SEND_BUF_SIZE]; + int fd; + + int cmd; + struct btrfs_cmd_header *cmd_hdr; + struct btrfs_tlv_header *cmd_attrs[BTRFS_SEND_A_MAX + 1]; + u32 version; + + /* + * end of last successful read, equivalent to start of current + * malformed part of block + */ + size_t stream_pos; + + struct btrfs_send_ops *ops; + void *user; +} __attribute__((aligned(64))); + +/* + * Read len bytes to buf. + * Return: + * 0 - success + * < 0 - negative errno in case of error + * > 0 - no data read, EOF + */ +static int read_buf(struct btrfs_send_stream *sctx, char *buf, size_t len) +{ + int ret; + size_t pos = 0; + + while (pos < len) { + ssize_t rbytes; + + rbytes = read(sctx->fd, buf + pos, len - pos); + if (rbytes < 0) { + ret = -errno; + error("read from stream failed: %m"); + goto out; + } + if (rbytes == 0) { + ret = 1; + goto out_eof; + } + pos += rbytes; + } + ret = 0; + +out_eof: + if (0 < pos && pos < len) { + error("short read from stream: expected %zu read %zu", len, pos); + ret = -EIO; + } else { + sctx->stream_pos += pos; + } + +out: + return ret; +} + +/* + * Reads a single command from kernel space and decodes the TLV's into + * sctx->cmd_attrs + * + * Returns: + * 0 - success + * < 0 - an error in the command + */ +static int read_cmd(struct btrfs_send_stream *sctx) +{ + int ret; + u16 cmd; + u32 cmd_len; + char *data; + u32 pos; + u32 crc; + u32 crc2; + + memset(sctx->cmd_attrs, 0, sizeof(sctx->cmd_attrs)); + + ASSERT(sizeof(*sctx->cmd_hdr) <= sizeof(sctx->read_buf)); + ret = read_buf(sctx, sctx->read_buf, sizeof(*sctx->cmd_hdr)); + if (ret < 0) + goto out; + if (ret) { + ret = -EINVAL; + error("unexpected EOF in stream"); + goto out; + } + + sctx->cmd_hdr = (struct btrfs_cmd_header *)sctx->read_buf; + cmd = le16_to_cpu(sctx->cmd_hdr->cmd); + cmd_len = le32_to_cpu(sctx->cmd_hdr->len); + + if (cmd_len + sizeof(*sctx->cmd_hdr) >= sizeof(sctx->read_buf)) { + ret = -EINVAL; + error("command length %u too big for buffer %zu", + cmd_len, sizeof(sctx->read_buf)); + goto out; + } + + data = sctx->read_buf + sizeof(*sctx->cmd_hdr); + ret = read_buf(sctx, data, cmd_len); + if (ret < 0) + goto out; + if (ret) { + ret = -EINVAL; + error("unexpected EOF in stream"); + goto out; + } + + crc = le32_to_cpu(sctx->cmd_hdr->crc); + sctx->cmd_hdr->crc = 0; + + crc2 = crc32c(0, (unsigned char*)sctx->read_buf, + sizeof(*sctx->cmd_hdr) + cmd_len); + + if (crc != crc2) { + ret = -EINVAL; + error("crc32 mismatch in command"); + goto out; + } + + pos = 0; + while (pos < cmd_len) { + struct btrfs_tlv_header *tlv_hdr; + u16 tlv_type; + u16 tlv_len; + + tlv_hdr = (struct btrfs_tlv_header *)data; + tlv_type = le16_to_cpu(tlv_hdr->tlv_type); + tlv_len = le16_to_cpu(tlv_hdr->tlv_len); + + if (tlv_type == 0 || tlv_type > BTRFS_SEND_A_MAX) { + error("invalid tlv in cmd tlv_type = %hu, tlv_len = %hu", + tlv_type, tlv_len); + ret = -EINVAL; + goto out; + } + + sctx->cmd_attrs[tlv_type] = tlv_hdr; + + data += sizeof(*tlv_hdr) + tlv_len; + pos += sizeof(*tlv_hdr) + tlv_len; + } + + sctx->cmd = cmd; + ret = 0; + +out: + return ret; +} + +static int tlv_get(struct btrfs_send_stream *sctx, int attr, void **data, int *len) +{ + int ret; + struct btrfs_tlv_header *hdr; + + if (attr <= 0 || attr > BTRFS_SEND_A_MAX) { + error("invalid attribute requested, attr = %d", attr); + ret = -EINVAL; + goto out; + } + + hdr = sctx->cmd_attrs[attr]; + if (!hdr) { + error("attribute %d requested but not present", attr); + ret = -ENOENT; + goto out; + } + + *len = le16_to_cpu(hdr->tlv_len); + *data = hdr + 1; + + ret = 0; + +out: + return ret; +} + +#define __TLV_GOTO_FAIL(expr) \ + if ((ret = expr) < 0) \ + goto tlv_get_failed; + +#define __TLV_DO_WHILE_GOTO_FAIL(expr) \ + do { \ + __TLV_GOTO_FAIL(expr) \ + } while (0) + + +#define TLV_GET(s, attr, data, len) \ + __TLV_DO_WHILE_GOTO_FAIL(tlv_get(s, attr, data, len)) + +#define TLV_CHECK_LEN(expected, got) \ + do { \ + if (expected != got) { \ + error("invalid size for attribute, " \ + "expected = %d, got = %d", \ + (int)expected, (int)got); \ + ret = -EINVAL; \ + goto tlv_get_failed; \ + } \ + } while (0) + +#define TLV_GET_INT(s, attr, bits, v) \ + do { \ + __le##bits *__tmp; \ + int __len; \ + TLV_GET(s, attr, (void**)&__tmp, &__len); \ + TLV_CHECK_LEN(sizeof(*__tmp), __len); \ + *v = get_unaligned_le##bits(__tmp); \ + } while (0) + +#define TLV_GET_U8(s, attr, v) TLV_GET_INT(s, attr, 8, v) +#define TLV_GET_U16(s, attr, v) TLV_GET_INT(s, attr, 16, v) +#define TLV_GET_U32(s, attr, v) TLV_GET_INT(s, attr, 32, v) +#define TLV_GET_U64(s, attr, v) TLV_GET_INT(s, attr, 64, v) + +static int tlv_get_string(struct btrfs_send_stream *sctx, int attr, char **str) +{ + int ret; + void *data; + int len = 0; + + TLV_GET(sctx, attr, &data, &len); + + *str = malloc(len + 1); + if (!*str) + return -ENOMEM; + + memcpy(*str, data, len); + (*str)[len] = 0; + ret = 0; + +tlv_get_failed: + return ret; +} +#define TLV_GET_STRING(s, attr, str) \ + __TLV_DO_WHILE_GOTO_FAIL(tlv_get_string(s, attr, str)) + +static int tlv_get_timespec(struct btrfs_send_stream *sctx, + int attr, struct timespec *ts) +{ + int ret; + int len; + struct btrfs_timespec *bts; + + TLV_GET(sctx, attr, (void**)&bts, &len); + TLV_CHECK_LEN(sizeof(*bts), len); + + ts->tv_sec = le64_to_cpu(bts->sec); + ts->tv_nsec = le32_to_cpu(bts->nsec); + ret = 0; + +tlv_get_failed: + return ret; +} +#define TLV_GET_TIMESPEC(s, attr, ts) \ + __TLV_DO_WHILE_GOTO_FAIL(tlv_get_timespec(s, attr, ts)) + +static int tlv_get_uuid(struct btrfs_send_stream *sctx, int attr, u8 *uuid) +{ + int ret; + int len; + void *data; + + TLV_GET(sctx, attr, &data, &len); + TLV_CHECK_LEN(BTRFS_UUID_SIZE, len); + memcpy(uuid, data, BTRFS_UUID_SIZE); + + ret = 0; + +tlv_get_failed: + return ret; +} +#define TLV_GET_UUID(s, attr, uuid) \ + __TLV_DO_WHILE_GOTO_FAIL(tlv_get_uuid(s, attr, uuid)) + +static int read_and_process_cmd(struct btrfs_send_stream *sctx) +{ + int ret; + char *path = NULL; + char *path_to = NULL; + char *clone_path = NULL; + char *xattr_name = NULL; + void *xattr_data = NULL; + void *data = NULL; + struct timespec at; + struct timespec ct; + struct timespec mt; + u8 uuid[BTRFS_UUID_SIZE]; + u8 clone_uuid[BTRFS_UUID_SIZE]; + u64 tmp; + u64 tmp2; + u64 ctransid; + u64 clone_ctransid; + u64 mode; + u64 dev; + u64 clone_offset; + u64 offset; + int len; + int xattr_len; + + ret = read_cmd(sctx); + if (ret) + goto out; + + switch (sctx->cmd) { + case BTRFS_SEND_C_SUBVOL: + TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path); + TLV_GET_UUID(sctx, BTRFS_SEND_A_UUID, uuid); + TLV_GET_U64(sctx, BTRFS_SEND_A_CTRANSID, &ctransid); + ret = sctx->ops->subvol(path, uuid, ctransid, sctx->user); + break; + case BTRFS_SEND_C_SNAPSHOT: + TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path); + TLV_GET_UUID(sctx, BTRFS_SEND_A_UUID, uuid); + TLV_GET_U64(sctx, BTRFS_SEND_A_CTRANSID, &ctransid); + TLV_GET_UUID(sctx, BTRFS_SEND_A_CLONE_UUID, clone_uuid); + TLV_GET_U64(sctx, BTRFS_SEND_A_CLONE_CTRANSID, &clone_ctransid); + ret = sctx->ops->snapshot(path, uuid, ctransid, clone_uuid, + clone_ctransid, sctx->user); + break; + case BTRFS_SEND_C_MKFILE: + TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path); + ret = sctx->ops->mkfile(path, sctx->user); + break; + case BTRFS_SEND_C_MKDIR: + TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path); + ret = sctx->ops->mkdir(path, sctx->user); + break; + case BTRFS_SEND_C_MKNOD: + TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path); + TLV_GET_U64(sctx, BTRFS_SEND_A_MODE, &mode); + TLV_GET_U64(sctx, BTRFS_SEND_A_RDEV, &dev); + ret = sctx->ops->mknod(path, mode, dev, sctx->user); + break; + case BTRFS_SEND_C_MKFIFO: + TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path); + ret = sctx->ops->mkfifo(path, sctx->user); + break; + case BTRFS_SEND_C_MKSOCK: + TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path); + ret = sctx->ops->mksock(path, sctx->user); + break; + case BTRFS_SEND_C_SYMLINK: + TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path); + TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH_LINK, &path_to); + ret = sctx->ops->symlink(path, path_to, sctx->user); + break; + case BTRFS_SEND_C_RENAME: + TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path); + TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH_TO, &path_to); + ret = sctx->ops->rename(path, path_to, sctx->user); + break; + case BTRFS_SEND_C_LINK: + TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path); + TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH_LINK, &path_to); + ret = sctx->ops->link(path, path_to, sctx->user); + break; + case BTRFS_SEND_C_UNLINK: + TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path); + ret = sctx->ops->unlink(path, sctx->user); + break; + case BTRFS_SEND_C_RMDIR: + TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path); + ret = sctx->ops->rmdir(path, sctx->user); + break; + case BTRFS_SEND_C_WRITE: + TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path); + TLV_GET_U64(sctx, BTRFS_SEND_A_FILE_OFFSET, &offset); + TLV_GET(sctx, BTRFS_SEND_A_DATA, &data, &len); + ret = sctx->ops->write(path, data, offset, len, sctx->user); + break; + case BTRFS_SEND_C_CLONE: + TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path); + TLV_GET_U64(sctx, BTRFS_SEND_A_FILE_OFFSET, &offset); + TLV_GET_U64(sctx, BTRFS_SEND_A_CLONE_LEN, &len); + TLV_GET_UUID(sctx, BTRFS_SEND_A_CLONE_UUID, clone_uuid); + TLV_GET_U64(sctx, BTRFS_SEND_A_CLONE_CTRANSID, &clone_ctransid); + TLV_GET_STRING(sctx, BTRFS_SEND_A_CLONE_PATH, &clone_path); + TLV_GET_U64(sctx, BTRFS_SEND_A_CLONE_OFFSET, &clone_offset); + ret = sctx->ops->clone(path, offset, len, clone_uuid, + clone_ctransid, clone_path, clone_offset, + sctx->user); + break; + case BTRFS_SEND_C_SET_XATTR: + TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path); + TLV_GET_STRING(sctx, BTRFS_SEND_A_XATTR_NAME, &xattr_name); + TLV_GET(sctx, BTRFS_SEND_A_XATTR_DATA, &xattr_data, &xattr_len); + ret = sctx->ops->set_xattr(path, xattr_name, xattr_data, + xattr_len, sctx->user); + break; + case BTRFS_SEND_C_REMOVE_XATTR: + TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path); + TLV_GET_STRING(sctx, BTRFS_SEND_A_XATTR_NAME, &xattr_name); + ret = sctx->ops->remove_xattr(path, xattr_name, sctx->user); + break; + case BTRFS_SEND_C_TRUNCATE: + TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path); + TLV_GET_U64(sctx, BTRFS_SEND_A_SIZE, &tmp); + ret = sctx->ops->truncate(path, tmp, sctx->user); + break; + case BTRFS_SEND_C_CHMOD: + TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path); + TLV_GET_U64(sctx, BTRFS_SEND_A_MODE, &tmp); + ret = sctx->ops->chmod(path, tmp, sctx->user); + break; + case BTRFS_SEND_C_CHOWN: + TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path); + TLV_GET_U64(sctx, BTRFS_SEND_A_UID, &tmp); + TLV_GET_U64(sctx, BTRFS_SEND_A_GID, &tmp2); + ret = sctx->ops->chown(path, tmp, tmp2, sctx->user); + break; + case BTRFS_SEND_C_UTIMES: + TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path); + TLV_GET_TIMESPEC(sctx, BTRFS_SEND_A_ATIME, &at); + TLV_GET_TIMESPEC(sctx, BTRFS_SEND_A_MTIME, &mt); + TLV_GET_TIMESPEC(sctx, BTRFS_SEND_A_CTIME, &ct); + ret = sctx->ops->utimes(path, &at, &mt, &ct, sctx->user); + break; + case BTRFS_SEND_C_UPDATE_EXTENT: + TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path); + TLV_GET_U64(sctx, BTRFS_SEND_A_FILE_OFFSET, &offset); + TLV_GET_U64(sctx, BTRFS_SEND_A_SIZE, &tmp); + ret = sctx->ops->update_extent(path, offset, tmp, sctx->user); + break; + case BTRFS_SEND_C_END: + ret = 1; + break; + } + +tlv_get_failed: +out: + free(path); + free(path_to); + free(clone_path); + free(xattr_name); + return ret; +} + +/* + * If max_errors is 0, then don't stop processing the stream if one of the + * callbacks in btrfs_send_ops structure returns an error. If greater than + * zero, stop after max_errors errors happened. + */ +int btrfs_read_and_process_send_stream(int fd, + struct btrfs_send_ops *ops, void *user, + int honor_end_cmd, + u64 max_errors) +{ + int ret; + struct btrfs_send_stream sctx; + struct btrfs_stream_header hdr; + u64 errors = 0; + int last_err = 0; + + sctx.fd = fd; + sctx.ops = ops; + sctx.user = user; + sctx.stream_pos = 0; + + ret = read_buf(&sctx, (char*)&hdr, sizeof(hdr)); + if (ret < 0) + goto out; + if (ret) { + ret = -ENODATA; + goto out; + } + + if (strcmp(hdr.magic, BTRFS_SEND_STREAM_MAGIC)) { + ret = -EINVAL; + error("unexpected header"); + goto out; + } + + sctx.version = le32_to_cpu(hdr.version); + if (sctx.version > BTRFS_SEND_STREAM_VERSION) { + ret = -EINVAL; + error("stream version %d not supported, please use newer version", + sctx.version); + goto out; + } + + while (1) { + ret = read_and_process_cmd(&sctx); + if (ret < 0) { + last_err = ret; + errors++; + if (max_errors > 0 && errors >= max_errors) + goto out; + } else if (ret > 0) { + if (!honor_end_cmd) + ret = 0; + goto out; + } + } + +out: + if (last_err && !ret) + ret = last_err; + + return ret; +} diff --git a/libbtrfs/send-stream.h b/libbtrfs/send-stream.h new file mode 100644 index 00000000..39901f86 --- /dev/null +++ b/libbtrfs/send-stream.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2012 Alexander Block. 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. + */ + +#ifndef __BTRFS_SEND_STREAM_H__ +#define __BTRFS_SEND_STREAM_H__ + +/* + * NOTE: this file is public API, any incompatible change has to update + * library version + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#if BTRFS_FLAT_INCLUDES +#include "kerncompat.h" +#else +#include +#endif /* BTRFS_FLAT_INCLUDES */ + +struct btrfs_send_ops { + int (*subvol)(const char *path, const u8 *uuid, u64 ctransid, + void *user); + int (*snapshot)(const char *path, const u8 *uuid, u64 ctransid, + const u8 *parent_uuid, u64 parent_ctransid, + void *user); + int (*mkfile)(const char *path, void *user); + int (*mkdir)(const char *path, void *user); + int (*mknod)(const char *path, u64 mode, u64 dev, void *user); + int (*mkfifo)(const char *path, void *user); + int (*mksock)(const char *path, void *user); + int (*symlink)(const char *path, const char *lnk, void *user); + int (*rename)(const char *from, const char *to, void *user); + int (*link)(const char *path, const char *lnk, void *user); + int (*unlink)(const char *path, void *user); + int (*rmdir)(const char *path, void *user); + int (*write)(const char *path, const void *data, u64 offset, u64 len, + void *user); + int (*clone)(const char *path, u64 offset, u64 len, + const u8 *clone_uuid, u64 clone_ctransid, + const char *clone_path, u64 clone_offset, + void *user); + int (*set_xattr)(const char *path, const char *name, const void *data, + int len, void *user); + int (*remove_xattr)(const char *path, const char *name, void *user); + int (*truncate)(const char *path, u64 size, void *user); + int (*chmod)(const char *path, u64 mode, void *user); + int (*chown)(const char *path, u64 uid, u64 gid, void *user); + int (*utimes)(const char *path, struct timespec *at, + struct timespec *mt, struct timespec *ct, + void *user); + int (*update_extent)(const char *path, u64 offset, u64 len, void *user); +}; + +int btrfs_read_and_process_send_stream(int fd, + struct btrfs_send_ops *ops, void *user, + int honor_end_cmd, + u64 max_errors); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libbtrfs/send-utils.c b/libbtrfs/send-utils.c new file mode 100644 index 00000000..a4f824e8 --- /dev/null +++ b/libbtrfs/send-utils.c @@ -0,0 +1,757 @@ +/* + * Copyright (C) 2012 Alexander Block. 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 +#include +#include +#include +#include +#include + +#include "kernel-shared/ctree.h" +#include "common/send-utils.h" +#include "common/messages.h" +#include "common/utils.h" +#include "ioctl.h" +#include "btrfs-list.h" + +static int btrfs_subvolid_resolve_sub(int fd, char *path, size_t *path_len, + u64 subvol_id); + +static int btrfs_get_root_id_by_sub_path(int mnt_fd, const char *sub_path, + u64 *root_id) +{ + int ret; + int subvol_fd; + + subvol_fd = openat(mnt_fd, sub_path, O_RDONLY); + if (subvol_fd < 0) { + ret = -errno; + fprintf(stderr, "ERROR: open %s failed: %m\n", sub_path); + return ret; + } + + ret = lookup_path_rootid(subvol_fd, root_id); + if (ret) { + errno = -ret; + error("cannot resolve rootid for path: %m"); + } + close(subvol_fd); + return ret; +} + +static int btrfs_read_root_item_raw(int mnt_fd, u64 root_id, size_t buf_len, + u32 *read_len, void *buf) +{ + int ret; + struct btrfs_ioctl_search_args args; + struct btrfs_ioctl_search_key *sk = &args.key; + struct btrfs_ioctl_search_header *sh; + unsigned long off = 0; + int found = 0; + int i; + + *read_len = 0; + memset(&args, 0, sizeof(args)); + + sk->tree_id = BTRFS_ROOT_TREE_OBJECTID; + + /* + * there may be more than one ROOT_ITEM key if there are + * snapshots pending deletion, we have to loop through + * them. + */ + sk->min_objectid = root_id; + sk->max_objectid = root_id; + sk->max_type = BTRFS_ROOT_ITEM_KEY; + sk->min_type = BTRFS_ROOT_ITEM_KEY; + sk->max_offset = (u64)-1; + sk->max_transid = (u64)-1; + sk->nr_items = 4096; + + while (1) { + ret = ioctl(mnt_fd, BTRFS_IOC_TREE_SEARCH, &args); + if (ret < 0) { + fprintf(stderr, + "ERROR: can't perform the search - %m\n"); + return 0; + } + /* the ioctl returns the number of item it found in nr_items */ + if (sk->nr_items == 0) + break; + + off = 0; + for (i = 0; i < sk->nr_items; i++) { + struct btrfs_root_item *item; + sh = (struct btrfs_ioctl_search_header *)(args.buf + + off); + + off += sizeof(*sh); + item = (struct btrfs_root_item *)(args.buf + off); + off += btrfs_search_header_len(sh); + + sk->min_objectid = btrfs_search_header_objectid(sh); + sk->min_type = btrfs_search_header_type(sh); + sk->min_offset = btrfs_search_header_offset(sh); + + if (btrfs_search_header_objectid(sh) > root_id) + break; + + if (btrfs_search_header_objectid(sh) == root_id && + btrfs_search_header_type(sh) == BTRFS_ROOT_ITEM_KEY) { + if (btrfs_search_header_len(sh) > buf_len) { + /* btrfs-progs is too old for kernel */ + fprintf(stderr, + "ERROR: buf for read_root_item_raw() is too small, get newer btrfs tools!\n"); + return -EOVERFLOW; + } + memcpy(buf, item, btrfs_search_header_len(sh)); + *read_len = btrfs_search_header_len(sh); + found = 1; + } + } + if (sk->min_offset < (u64)-1) + sk->min_offset++; + else + break; + + if (sk->min_type != BTRFS_ROOT_ITEM_KEY || + sk->min_objectid != root_id) + break; + } + + return found ? 0 : -ENOENT; +} + +/* + * Read a root item from the tree. In case we detect a root item smaller then + * sizeof(root_item), we know it's an old version of the root structure and + * initialize all new fields to zero. The same happens if we detect mismatching + * generation numbers as then we know the root was once mounted with an older + * kernel that was not aware of the root item structure change. + */ +static int btrfs_read_root_item(int mnt_fd, u64 root_id, + struct btrfs_root_item *item) +{ + int ret; + u32 read_len; + + ret = btrfs_read_root_item_raw(mnt_fd, root_id, sizeof(*item), + &read_len, item); + if (ret) + return ret; + + if (read_len < sizeof(*item) || + btrfs_root_generation(item) != btrfs_root_generation_v2(item)) { + /* + * Workaround for gcc9 that warns that memset over + * generation_v2 overflows, which is what we want but would + * be otherwise a bug + * + * The below is &item->generation_v2 + */ + char *start = (char *)item + offsetof(struct btrfs_root_item, + generation_v2); + + memset(start, 0, + sizeof(*item) - offsetof(struct btrfs_root_item, + generation_v2)); + } + + return 0; +} + +#ifdef BTRFS_COMPAT_SEND_NO_UUID_TREE +static struct rb_node *tree_insert(struct rb_root *root, + struct subvol_info *si, + enum subvol_search_type type) +{ + struct rb_node **p = &root->rb_node; + struct rb_node *parent = NULL; + struct subvol_info *entry; + __s64 comp; + + while (*p) { + parent = *p; + if (type == subvol_search_by_received_uuid) { + entry = rb_entry(parent, struct subvol_info, + rb_received_node); + + comp = memcmp(entry->received_uuid, si->received_uuid, + BTRFS_UUID_SIZE); + if (!comp) { + if (entry->stransid < si->stransid) + comp = -1; + else if (entry->stransid > si->stransid) + comp = 1; + else + comp = 0; + } + } else if (type == subvol_search_by_uuid) { + entry = rb_entry(parent, struct subvol_info, + rb_local_node); + comp = memcmp(entry->uuid, si->uuid, BTRFS_UUID_SIZE); + } else if (type == subvol_search_by_root_id) { + entry = rb_entry(parent, struct subvol_info, + rb_root_id_node); + comp = entry->root_id - si->root_id; + } else if (type == subvol_search_by_path) { + entry = rb_entry(parent, struct subvol_info, + rb_path_node); + comp = strcmp(entry->path, si->path); + } else { + BUG(); + } + + if (comp < 0) + p = &(*p)->rb_left; + else if (comp > 0) + p = &(*p)->rb_right; + else + return parent; + } + + if (type == subvol_search_by_received_uuid) { + rb_link_node(&si->rb_received_node, parent, p); + rb_insert_color(&si->rb_received_node, root); + } else if (type == subvol_search_by_uuid) { + rb_link_node(&si->rb_local_node, parent, p); + rb_insert_color(&si->rb_local_node, root); + } else if (type == subvol_search_by_root_id) { + rb_link_node(&si->rb_root_id_node, parent, p); + rb_insert_color(&si->rb_root_id_node, root); + } else if (type == subvol_search_by_path) { + rb_link_node(&si->rb_path_node, parent, p); + rb_insert_color(&si->rb_path_node, root); + } + return NULL; +} +#endif + +int btrfs_subvolid_resolve(int fd, char *path, size_t path_len, u64 subvol_id) +{ + if (path_len < 1) + return -EOVERFLOW; + path[0] = '\0'; + path_len--; + path[path_len] = '\0'; + return btrfs_subvolid_resolve_sub(fd, path, &path_len, subvol_id); +} + +static int btrfs_subvolid_resolve_sub(int fd, char *path, size_t *path_len, + u64 subvol_id) +{ + int ret; + struct btrfs_ioctl_search_args search_arg; + struct btrfs_ioctl_ino_lookup_args ino_lookup_arg; + struct btrfs_ioctl_search_header *search_header; + struct btrfs_root_ref *backref_item; + + if (subvol_id == BTRFS_FS_TREE_OBJECTID) { + if (*path_len < 1) + return -EOVERFLOW; + *path = '\0'; + (*path_len)--; + return 0; + } + + memset(&search_arg, 0, sizeof(search_arg)); + search_arg.key.tree_id = BTRFS_ROOT_TREE_OBJECTID; + search_arg.key.min_objectid = subvol_id; + search_arg.key.max_objectid = subvol_id; + search_arg.key.min_type = BTRFS_ROOT_BACKREF_KEY; + search_arg.key.max_type = BTRFS_ROOT_BACKREF_KEY; + search_arg.key.max_offset = (u64)-1; + search_arg.key.max_transid = (u64)-1; + search_arg.key.nr_items = 1; + ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &search_arg); + if (ret < 0) { + fprintf(stderr, + "ioctl(BTRFS_IOC_TREE_SEARCH, subvol_id %llu) ret=%d, error: %m\n", + (unsigned long long)subvol_id, ret); + return ret; + } + + if (search_arg.key.nr_items < 1) { + fprintf(stderr, + "failed to lookup subvol_id %llu!\n", + (unsigned long long)subvol_id); + return -ENOENT; + } + search_header = (struct btrfs_ioctl_search_header *)search_arg.buf; + backref_item = (struct btrfs_root_ref *)(search_header + 1); + if (btrfs_search_header_offset(search_header) + != BTRFS_FS_TREE_OBJECTID) { + int sub_ret; + + sub_ret = btrfs_subvolid_resolve_sub(fd, path, path_len, + btrfs_search_header_offset(search_header)); + if (sub_ret) + return sub_ret; + if (*path_len < 1) + return -EOVERFLOW; + strcat(path, "/"); + (*path_len)--; + } + + if (btrfs_stack_root_ref_dirid(backref_item) != + BTRFS_FIRST_FREE_OBJECTID) { + int len; + + memset(&ino_lookup_arg, 0, sizeof(ino_lookup_arg)); + ino_lookup_arg.treeid = + btrfs_search_header_offset(search_header); + ino_lookup_arg.objectid = + btrfs_stack_root_ref_dirid(backref_item); + ret = ioctl(fd, BTRFS_IOC_INO_LOOKUP, &ino_lookup_arg); + if (ret < 0) { + fprintf(stderr, + "ioctl(BTRFS_IOC_INO_LOOKUP) ret=%d, error: %m\n", + ret); + return ret; + } + + len = strlen(ino_lookup_arg.name); + if (*path_len < len) + return -EOVERFLOW; + strcat(path, ino_lookup_arg.name); + (*path_len) -= len; + } + + if (*path_len < btrfs_stack_root_ref_name_len(backref_item)) + return -EOVERFLOW; + strncat(path, (char *)(backref_item + 1), + btrfs_stack_root_ref_name_len(backref_item)); + (*path_len) -= btrfs_stack_root_ref_name_len(backref_item); + return 0; +} + +#ifdef BTRFS_COMPAT_SEND_NO_UUID_TREE +static int count_bytes(void *buf, int len, char b) +{ + int cnt = 0; + int i; + + for (i = 0; i < len; i++) { + if (((char *)buf)[i] == b) + cnt++; + } + return cnt; +} + +void subvol_uuid_search_add(struct subvol_uuid_search *s, + struct subvol_info *si) +{ + int cnt; + + tree_insert(&s->root_id_subvols, si, subvol_search_by_root_id); + tree_insert(&s->path_subvols, si, subvol_search_by_path); + + cnt = count_bytes(si->uuid, BTRFS_UUID_SIZE, 0); + if (cnt != BTRFS_UUID_SIZE) + tree_insert(&s->local_subvols, si, subvol_search_by_uuid); + cnt = count_bytes(si->received_uuid, BTRFS_UUID_SIZE, 0); + if (cnt != BTRFS_UUID_SIZE) + tree_insert(&s->received_subvols, si, + subvol_search_by_received_uuid); +} + +static struct subvol_info *tree_search(struct rb_root *root, + u64 root_id, const u8 *uuid, + u64 stransid, const char *path, + enum subvol_search_type type) +{ + struct rb_node *n = root->rb_node; + struct subvol_info *entry; + __s64 comp; + + while (n) { + if (type == subvol_search_by_received_uuid) { + entry = rb_entry(n, struct subvol_info, + rb_received_node); + comp = memcmp(entry->received_uuid, uuid, + BTRFS_UUID_SIZE); + if (!comp) { + if (entry->stransid < stransid) + comp = -1; + else if (entry->stransid > stransid) + comp = 1; + else + comp = 0; + } + } else if (type == subvol_search_by_uuid) { + entry = rb_entry(n, struct subvol_info, rb_local_node); + comp = memcmp(entry->uuid, uuid, BTRFS_UUID_SIZE); + } else if (type == subvol_search_by_root_id) { + entry = rb_entry(n, struct subvol_info, + rb_root_id_node); + comp = entry->root_id - root_id; + } else if (type == subvol_search_by_path) { + entry = rb_entry(n, struct subvol_info, rb_path_node); + comp = strcmp(entry->path, path); + } else { + BUG(); + } + if (comp < 0) + n = n->rb_left; + else if (comp > 0) + n = n->rb_right; + else + return entry; + } + return NULL; +} + +/* + * this function will be only called if kernel doesn't support uuid tree. + */ +static struct subvol_info *subvol_uuid_search_old(struct subvol_uuid_search *s, + u64 root_id, const u8 *uuid, u64 transid, + const char *path, + enum subvol_search_type type) +{ + struct rb_root *root; + if (type == subvol_search_by_received_uuid) + root = &s->received_subvols; + else if (type == subvol_search_by_uuid) + root = &s->local_subvols; + else if (type == subvol_search_by_root_id) + root = &s->root_id_subvols; + else if (type == subvol_search_by_path) + root = &s->path_subvols; + else + return NULL; + return tree_search(root, root_id, uuid, transid, path, type); +} +#else +void subvol_uuid_search_add(struct subvol_uuid_search *s, + struct subvol_info *si) +{ + if (si) { + free(si->path); + free(si); + } +} +#endif + +struct subvol_info *subvol_uuid_search(struct subvol_uuid_search *s, + u64 root_id, const u8 *uuid, u64 transid, + const char *path, + enum subvol_search_type type) +{ + struct subvol_info *si; + + si = subvol_uuid_search2(s, root_id, uuid, transid, path, type); + if (IS_ERR(si)) + return NULL; + return si; +} + +struct subvol_info *subvol_uuid_search2(struct subvol_uuid_search *s, + u64 root_id, const u8 *uuid, u64 transid, + const char *path, + enum subvol_search_type type) +{ + int ret = 0; + struct btrfs_root_item root_item; + struct subvol_info *info = NULL; + +#ifdef BTRFS_COMPAT_SEND_NO_UUID_TREE + if (!s->uuid_tree_existed) + return subvol_uuid_search_old(s, root_id, uuid, transid, + path, type); +#endif + switch (type) { + case subvol_search_by_received_uuid: + ret = btrfs_lookup_uuid_received_subvol_item(s->mnt_fd, uuid, + &root_id); + break; + case subvol_search_by_uuid: + ret = btrfs_lookup_uuid_subvol_item(s->mnt_fd, uuid, &root_id); + break; + case subvol_search_by_root_id: + break; + case subvol_search_by_path: + ret = btrfs_get_root_id_by_sub_path(s->mnt_fd, path, &root_id); + break; + default: + ret = -EINVAL; + break; + } + + if (ret) + goto out; + + ret = btrfs_read_root_item(s->mnt_fd, root_id, &root_item); + if (ret) + goto out; + + info = calloc(1, sizeof(*info)); + if (!info) { + ret = -ENOMEM; + goto out; + } + info->root_id = root_id; + memcpy(info->uuid, root_item.uuid, BTRFS_UUID_SIZE); + memcpy(info->received_uuid, root_item.received_uuid, BTRFS_UUID_SIZE); + memcpy(info->parent_uuid, root_item.parent_uuid, BTRFS_UUID_SIZE); + info->ctransid = btrfs_root_ctransid(&root_item); + info->otransid = btrfs_root_otransid(&root_item); + info->stransid = btrfs_root_stransid(&root_item); + info->rtransid = btrfs_root_rtransid(&root_item); + if (type == subvol_search_by_path) { + info->path = strdup(path); + if (!info->path) { + ret = -ENOMEM; + goto out; + } + } else { + info->path = malloc(PATH_MAX); + if (!info->path) { + ret = -ENOMEM; + goto out; + } + ret = btrfs_subvolid_resolve(s->mnt_fd, info->path, + PATH_MAX, root_id); + } + +out: + if (ret) { + if (info) { + free(info->path); + free(info); + } + return ERR_PTR(ret); + } + + return info; +} + +#ifdef BTRFS_COMPAT_SEND_NO_UUID_TREE +static int is_uuid_tree_supported(int fd) +{ + int ret; + struct btrfs_ioctl_search_args args; + struct btrfs_ioctl_search_key *sk = &args.key; + + memset(&args, 0, sizeof(args)); + + sk->tree_id = BTRFS_ROOT_TREE_OBJECTID; + + sk->min_objectid = BTRFS_UUID_TREE_OBJECTID; + sk->max_objectid = BTRFS_UUID_TREE_OBJECTID; + sk->max_type = BTRFS_ROOT_ITEM_KEY; + sk->min_type = BTRFS_ROOT_ITEM_KEY; + sk->max_offset = (u64)-1; + sk->max_transid = (u64)-1; + sk->nr_items = 1; + + ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args); + if (ret < 0) + return ret; + + /* the ioctl returns the number of item it found in nr_items */ + if (sk->nr_items == 0) + return 0; + + return 1; +} + +/* + * this function is mainly used to read all root items + * it will be only used when we use older kernel which uuid + * tree is not supported yet + */ +int subvol_uuid_search_init(int mnt_fd, struct subvol_uuid_search *s) +{ + int ret; + struct btrfs_ioctl_search_args args; + struct btrfs_ioctl_search_key *sk = &args.key; + struct btrfs_ioctl_search_header *sh; + struct btrfs_root_item *root_item_ptr; + struct btrfs_root_item root_item = {}; + struct subvol_info *si = NULL; + int root_item_valid = 0; + unsigned long off = 0; + int i; + char *path; + + s->mnt_fd = mnt_fd; + + s->root_id_subvols = RB_ROOT; + s->local_subvols = RB_ROOT; + s->received_subvols = RB_ROOT; + s->path_subvols = RB_ROOT; + + ret = is_uuid_tree_supported(mnt_fd); + if (ret < 0) { + fprintf(stderr, + "ERROR: check if we support uuid tree fails - %m\n"); + return ret; + } else if (ret) { + /* uuid tree is supported */ + s->uuid_tree_existed = 1; + return 0; + } + memset(&args, 0, sizeof(args)); + + sk->tree_id = BTRFS_ROOT_TREE_OBJECTID; + + sk->max_objectid = (u64)-1; + sk->max_offset = (u64)-1; + sk->max_transid = (u64)-1; + sk->min_type = BTRFS_ROOT_ITEM_KEY; + sk->max_type = BTRFS_ROOT_BACKREF_KEY; + sk->nr_items = 4096; + + while (1) { + ret = ioctl(mnt_fd, BTRFS_IOC_TREE_SEARCH, &args); + if (ret < 0) { + fprintf(stderr, "ERROR: can't perform the search - %m\n"); + return ret; + } + if (sk->nr_items == 0) + break; + + off = 0; + + for (i = 0; i < sk->nr_items; i++) { + sh = (struct btrfs_ioctl_search_header *)(args.buf + + off); + off += sizeof(*sh); + + if ((btrfs_search_header_objectid(sh) != 5 && + btrfs_search_header_objectid(sh) + < BTRFS_FIRST_FREE_OBJECTID) || + btrfs_search_header_objectid(sh) + > BTRFS_LAST_FREE_OBJECTID) { + goto skip; + } + + if (btrfs_search_header_type(sh) + == BTRFS_ROOT_ITEM_KEY) { + /* older kernels don't have uuids+times */ + if (btrfs_search_header_len(sh) + < sizeof(root_item)) { + root_item_valid = 0; + goto skip; + } + root_item_ptr = (struct btrfs_root_item *) + (args.buf + off); + memcpy(&root_item, root_item_ptr, + sizeof(root_item)); + root_item_valid = 1; + } else if (btrfs_search_header_type(sh) + == BTRFS_ROOT_BACKREF_KEY || + root_item_valid) { + if (!root_item_valid) + goto skip; + + path = btrfs_list_path_for_root(mnt_fd, + btrfs_search_header_objectid(sh)); + if (!path) + path = strdup(""); + if (IS_ERR(path)) { + ret = PTR_ERR(path); + fprintf(stderr, "ERROR: unable to " + "resolve path " + "for root %llu\n", + btrfs_search_header_objectid(sh)); + goto out; + } + + si = calloc(1, sizeof(*si)); + si->root_id = btrfs_search_header_objectid(sh); + memcpy(si->uuid, root_item.uuid, + BTRFS_UUID_SIZE); + memcpy(si->parent_uuid, root_item.parent_uuid, + BTRFS_UUID_SIZE); + memcpy(si->received_uuid, + root_item.received_uuid, + BTRFS_UUID_SIZE); + si->ctransid = btrfs_root_ctransid(&root_item); + si->otransid = btrfs_root_otransid(&root_item); + si->stransid = btrfs_root_stransid(&root_item); + si->rtransid = btrfs_root_rtransid(&root_item); + si->path = path; + subvol_uuid_search_add(s, si); + root_item_valid = 0; + } else { + goto skip; + } + +skip: + off += btrfs_search_header_len(sh); + + /* + * record the mins in sk so we can make sure the + * next search doesn't repeat this root + */ + sk->min_objectid = btrfs_search_header_objectid(sh); + sk->min_offset = btrfs_search_header_offset(sh); + sk->min_type = btrfs_search_header_type(sh); + } + sk->nr_items = 4096; + if (sk->min_offset < (u64)-1) + sk->min_offset++; + else if (sk->min_objectid < (u64)-1) { + sk->min_objectid++; + sk->min_offset = 0; + sk->min_type = 0; + } else + break; + } + +out: + return ret; +} + +void subvol_uuid_search_finit(struct subvol_uuid_search *s) +{ + struct rb_root *root = &s->root_id_subvols; + struct rb_node *node; + + if (!s->uuid_tree_existed) + return; + + while ((node = rb_first(root))) { + struct subvol_info *entry = + rb_entry(node, struct subvol_info, rb_root_id_node); + + free(entry->path); + rb_erase(node, root); + free(entry); + } + + s->root_id_subvols = RB_ROOT; + s->local_subvols = RB_ROOT; + s->received_subvols = RB_ROOT; + s->path_subvols = RB_ROOT; +} +#else +int subvol_uuid_search_init(int mnt_fd, struct subvol_uuid_search *s) +{ + s->mnt_fd = mnt_fd; + + return 0; +} + +void subvol_uuid_search_finit(struct subvol_uuid_search *s) +{ +} +#endif diff --git a/libbtrfs/send-utils.h b/libbtrfs/send-utils.h new file mode 100644 index 00000000..dd67b3fe --- /dev/null +++ b/libbtrfs/send-utils.h @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2012 Alexander Block. 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. + */ + +#ifndef __BTRFS_SEND_UTILS_H__ +#define __BTRFS_SEND_UTILS_H__ + +#if BTRFS_FLAT_INCLUDES +#include "kerncompat.h" +#include "kernel-shared/ctree.h" +#include "kernel-lib/rbtree.h" +#else +#include +#include +#include +#endif /* BTRFS_FLAT_INCLUDES */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Compatibility code for kernels < 3.12; the UUID tree is not available there + * and we have to do the slow search. This should be deprecated someday. + */ +#define BTRFS_COMPAT_SEND_NO_UUID_TREE 1 + +enum subvol_search_type { + subvol_search_by_root_id, + subvol_search_by_uuid, + subvol_search_by_received_uuid, + subvol_search_by_path, +}; + +struct subvol_info { +#ifdef BTRFS_COMPAT_SEND_NO_UUID_TREE + struct rb_node rb_root_id_node; + struct rb_node rb_local_node; + struct rb_node rb_received_node; + struct rb_node rb_path_node; +#endif + + u64 root_id; + u8 uuid[BTRFS_UUID_SIZE]; + u8 parent_uuid[BTRFS_UUID_SIZE]; + u8 received_uuid[BTRFS_UUID_SIZE]; + u64 ctransid; + u64 otransid; + u64 stransid; + u64 rtransid; + + char *path; +}; + +struct subvol_uuid_search { + int mnt_fd; +#ifdef BTRFS_COMPAT_SEND_NO_UUID_TREE + int uuid_tree_existed; + + struct rb_root root_id_subvols; + struct rb_root local_subvols; + struct rb_root received_subvols; + struct rb_root path_subvols; +#endif +}; + +int subvol_uuid_search_init(int mnt_fd, struct subvol_uuid_search *s); +void subvol_uuid_search_finit(struct subvol_uuid_search *s); +/* + * Search for a subvolume by given type (received uuid, root id, path), returns + * pointer to newly allocated struct subvol_info or NULL in case it's not found + * or there was another error. This ambiguity of error value is fixed by + * subvol_uuid_search2 that returns a negative errno in case of an error, of a + * valid pointer otherwise. + * + * This function will be deprecated in the future, please consider using v2 in + * new code unless you need to keep backward compatibility with older + * btrfs-progs. + */ +struct subvol_info *subvol_uuid_search(struct subvol_uuid_search *s, + u64 root_id, const u8 *uuid, u64 transid, + const char *path, + enum subvol_search_type type); +struct subvol_info *subvol_uuid_search2(struct subvol_uuid_search *s, + u64 root_id, const u8 *uuid, u64 transid, + const char *path, + enum subvol_search_type type); +void subvol_uuid_search_add(struct subvol_uuid_search *s, + struct subvol_info *si); + +int btrfs_subvolid_resolve(int fd, char *path, size_t path_len, u64 subvol_id); + +int path_cat_out(char *out, const char *p1, const char *p2); +int path_cat3_out(char *out, const char *p1, const char *p2, const char *p3); + +#ifdef __cplusplus +} +#endif + +#endif