btrfs-progs: libbtrfs: make own copy of files implementing the API

The two files send-stream and send-utils contain the implementations of
the exported API, which was just for send stream. This was the original
idea. That libbtrfs contains another 40 files was a result of
unclean/missing library design and had to be done that way to resolve
the symbols due to dependencies.

That the same files have been used for both internal and public library
has prevented refactoring and cleanups and was always a risk of breaking
something.

Make separate copy for libbtrfs utils and allow any cleanups and
reduction of number of build objects. The API hasn't changed since the
beginning so there's low risk of missing some fixes from the internal
code.

Signed-off-by: David Sterba <dsterba@suse.com>
This commit is contained in:
David Sterba 2021-09-22 18:25:12 +02:00
parent d4dc4793fa
commit 6b5c089469
5 changed files with 1484 additions and 3 deletions

View File

@ -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 \

530
libbtrfs/send-stream.c Normal file
View File

@ -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 <uuid/uuid.h>
#include <unistd.h>
#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;
}

80
libbtrfs/send-stream.h Normal file
View File

@ -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 <btrfs/kerncompat.h>
#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

757
libbtrfs/send-utils.c Normal file
View File

@ -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 <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <uuid/uuid.h>
#include <limits.h>
#include <errno.h>
#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

114
libbtrfs/send-utils.h Normal file
View File

@ -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 <btrfs/kerncompat.h>
#include <btrfs/ctree.h>
#include <btrfs/rbtree.h>
#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