blob: 25c81c6f2ed3723f7b63dd21a23baf8638c0db7c [file] [log] [blame]
/*
* Copyright (C) 2012 Alexander Block.
* Copyright (C) 2012, 2013 STRATO AG.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
/*
* This file contains a receiver for the FAR stream for the target
* filesystem Btrfs on Linux.
*/
#define _GNU_SOURCE
#define _POSIX_C_SOURCE 200809
#define _XOPEN_SOURCE 700
#define _BSD_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <libgen.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <errno.h>
#include <inttypes.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <uuid/uuid.h>
#include <sys/ioctl.h>
#include <mntent.h>
#include <btrfs/ctree.h>
#include <btrfs/ioctl.h>
#include <btrfs/send-utils.h>
#include <btrfs/btrfs-list.h>
#include "far-rcv/far-rcv.h"
#if BTRFS_UUID_SIZE != FAR_UUID_SIZE
# error "This won't work, BTRFS_UUID_SIZE != FAR_UUID_SIZE"
#endif /* if BTRFS_UUID_SIZE != FAR_UUID_SIZE */
struct btrfs_rcv_ctx {
struct far_rcv_ctx frctx; /* MUST be the first member */
int mnt_fd;
int dest_dir_fd;
struct subvol_uuid_search subvol_uuid_search_ctx;
char *mnt_path;
char *dest_dir_path; /* relative to mnt_path */
char *full_subvol_path;
char *explicit_parent;
int free_explicit_parent;
char *explicit_dest_subvol;
struct subvol_info *cur_subvol;
};
static void usage(const char *progname);
int main(int argc, char **argv);
static int btrfs_rcv_finish_subvol(struct far_rcv_ctx *frctx);
static int btrfs_rcv_process_subvol(struct far_rcv_ctx *frctx, const char *path,
const unsigned char *uuid,
uint64_t ctransid);
static int btrfs_rcv_process_snapshot(struct far_rcv_ctx *frctx,
const char *path,
const unsigned char *uuid,
uint64_t ctransid,
const unsigned char *parent_uuid,
uint64_t parent_ctransid);
static int btrfs_rcv_process_clone(struct far_rcv_ctx *frctx, const char *path,
uint64_t offset, uint64_t len,
const unsigned char *clone_uuid,
uint64_t clone_ctransid,
const char *clone_path,
uint64_t clone_offset);
static int btrfs_rcv_find_mount_root(const char *path, char **mount_root);
static void usage(const char *progname)
{
fprintf(stderr,
"%s [-ve] [-f <infile>] [-d <dest>] [-p <parent>] <mount>\n",
progname);
char *more[] = {
"Receive subvolumes from stdin.",
"Receives one or more subvolumes that were previously sent with btrfs send or",
"a similar sender for the FAR stream format.",
"The received subvolumes are stored into <mount>.",
"This tool will fail in case a received subvolume already exists. It will also",
"fail in case a previously received subvolume was changed after it was",
"received.",
"After receiving a subvolume, it is immediately set to read only.",
"-v Enable verbose debug output. Each occurrency of this option",
" increases the verbose level more.",
"-e Terminate after receiving an <end cmd> in the data stream.",
" Without this option, the receiver terminates only if an error",
" is recognized or on EOF.",
"-f <infile> By default, stdin is used to receive the subvolumes. Use this",
" option to specify a file to use instead.",
"-p <parent> Disables the automatic searching for parents if incremental",
" streams are received. In case of multiple received snapshots,",
" each received snapshot or subvolume becomes the new parent for",
" the following received snapshot.",
"-d <dest> Overrides the name of received subvolumes and snapshots to",
" <dest>. The <mount> parameter can be omited if the -d option is",
" used and <dest> is not specified relative to a <mount>",
" parameter.",
" Note that the -d option causes failures if multiple snapshots",
" are received in one stream, since the subvolume is already",
" existent in this case and already set to read-only.",
NULL
};
char **pp = more;
while (*pp) {
fprintf(stderr, "%s\n", *pp);
pp++;
}
}
int main(int argc, char **argv)
{
int c;
char *tomnt = NULL;
char *fromfile = NULL;
int stream_fd = -1;
int ret = 0;
int verbose = 0;
const char *progname;
int did_far_rcv_init = 0;
struct btrfs_rcv_ctx *brctx = NULL;
char *dest_dir_full_path = NULL;
char *name1 = NULL;
char *name2 = NULL;
int support_xattrs = 1;
int honor_end_cmd = 0;
progname = basename(argv[0]);
brctx = calloc(1, sizeof(*brctx));
if (!brctx) {
ret = -ENOMEM;
fprintf(stderr, "%s: ERROR: malloc() failed.\n", progname);
goto out;
}
brctx->mnt_fd = -1;
brctx->dest_dir_fd = -1;
brctx->mnt_path = NULL;
brctx->dest_dir_path = NULL;
brctx->full_subvol_path = NULL;
brctx->explicit_parent = NULL;
brctx->free_explicit_parent = 0;
brctx->explicit_dest_subvol = NULL;
brctx->cur_subvol = NULL;
while ((c = getopt(argc, argv, "vexXsSf:d:p:")) != -1) {
switch (c) {
case 'v':
verbose++;
break;
case 'e':
honor_end_cmd = 1;
break;
case 'x':
support_xattrs = 0;
break;
case 'X':
support_xattrs = 1;
break;
case 's':
case 'S':
/* ignored */
break;
case 'f':
fromfile = optarg;
break;
case 'd':
brctx->explicit_dest_subvol = optarg;
break;
case 'p':
brctx->explicit_parent = optarg;
break;
case '?':
default:
fprintf(stderr, "%s: ERROR: args invalid.\n", progname);
usage(progname);
ret = -EINVAL;
goto out;
}
}
if (optind == argc && brctx->explicit_dest_subvol) {
name1 = strdup(brctx->explicit_dest_subvol);
name2 = strdup(brctx->explicit_dest_subvol);
tomnt = dirname(name1);
brctx->explicit_dest_subvol = basename(name2);
} else if (optind + 1 < argc) {
ret = -EINVAL;
fprintf(stderr, "%s: ERROR: too many arguments.\n", progname);
usage(progname);
goto out;
} else if (optind + 1 != argc) {
ret = -EINVAL;
fprintf(stderr, "%s: ERROR: need path to subvolume.\n",
progname);
usage(progname);
goto out;
} else {
tomnt = argv[optind];
}
if (brctx->explicit_dest_subvol &&
!strncmp(tomnt, brctx->explicit_dest_subvol, strlen(tomnt))) {
brctx->explicit_dest_subvol += strlen(tomnt);
if (tomnt[0] != '\0')
while (*brctx->explicit_dest_subvol == '/')
brctx->explicit_dest_subvol++;
}
if (fromfile) {
stream_fd = open(fromfile, O_RDONLY);
if (stream_fd < 0) {
ret = -errno;
fprintf(stderr,
"%s: ERROR: failed to open \"%s\". %s.\n",
progname, fromfile, strerror(errno));
goto out;
}
} else {
stream_fd = 0; /* stdin */
}
did_far_rcv_init = 1;
ret = far_rcv_init(&brctx->frctx);
if (ret) {
fprintf(stderr, "%s: ERROR: far_rcv_init() failed. %s.\n",
progname, strerror(-ret));
goto out;
}
brctx->frctx.verbose = verbose;
brctx->frctx.support_xattrs = support_xattrs;
brctx->frctx.honor_end_cmd = honor_end_cmd;
brctx->frctx.ops.finish_subvol = btrfs_rcv_finish_subvol;
brctx->frctx.ops.subvol = btrfs_rcv_process_subvol;
brctx->frctx.ops.snapshot = btrfs_rcv_process_snapshot;
brctx->frctx.ops.clone = btrfs_rcv_process_clone;
dest_dir_full_path = realpath(tomnt, NULL);
if (!dest_dir_full_path) {
ret = -errno;
fprintf(stderr, "%s: ERROR: realpath(%s) failed. %s.\n",
progname, tomnt, strerror(errno));
goto out;
}
brctx->dest_dir_fd = open(dest_dir_full_path, O_RDONLY | O_NOATIME);
if (brctx->dest_dir_fd < 0) {
ret = -errno;
fprintf(stderr,
"%s: ERROR: failed to open destination directory %s. %s.\n",
progname, dest_dir_full_path, strerror(errno));
goto out;
}
ret = btrfs_rcv_find_mount_root(dest_dir_full_path, &brctx->mnt_path);
if (ret < 0) {
ret = -EINVAL;
fprintf(stderr,
"%s: ERROR: failed to determine mount point for %s.\n",
progname, dest_dir_full_path);
goto out;
}
brctx->mnt_fd = open(brctx->mnt_path, O_RDONLY | O_NOATIME);
if (brctx->mnt_fd < 0) {
ret = -errno;
fprintf(stderr, "%s: ERROR: failed to open %s. %s.\n",
progname, brctx->mnt_path, strerror(errno));
goto out;
}
/*
* btrfs_rcv_find_mount_root returns a mnt_path that is a subpath
* of dest_dir_full_path. Now get the other part of mnt_path,
* which is the destination dir relative to mnt_path.
*/
brctx->dest_dir_path = dest_dir_full_path + strlen(brctx->mnt_path);
while (*brctx->dest_dir_path == '/')
brctx->dest_dir_path++;
if (brctx->explicit_parent &&
!strncmp(brctx->mnt_path, brctx->explicit_parent,
strlen(brctx->mnt_path))) {
brctx->explicit_parent += strlen(brctx->mnt_path);
while (*brctx->explicit_parent == '/')
brctx->explicit_parent++;
}
ret = subvol_uuid_search_init(brctx->mnt_fd,
&brctx->subvol_uuid_search_ctx);
if (ret < 0) {
fprintf(stderr,
"%s: ERROR, subvol_uuid_search_init() failed with %d.\n",
progname, ret);
goto out;
}
ret = far_rcv_mainloop(&brctx->frctx, stream_fd, "./");
subvol_uuid_search_finit(&brctx->subvol_uuid_search_ctx);
if (ret) {
fprintf(stderr,
"%s: ERROR, far_rcv_mainloop() failed with %s.\n",
progname, strerror(-ret));
goto out;
}
out:
if (did_far_rcv_init)
far_rcv_deinit(&brctx->frctx);
if (stream_fd >= 0)
close(stream_fd);
free(dest_dir_full_path);
free(name1);
free(name2);
if (brctx) {
if (brctx->mnt_fd >= 0)
close(brctx->mnt_fd);
if (brctx->dest_dir_fd >= 0)
close(brctx->dest_dir_fd);
free(brctx->mnt_path);
free(brctx->full_subvol_path);
if (brctx->cur_subvol) {
free(brctx->cur_subvol->path);
free(brctx->cur_subvol);
}
if (brctx->free_explicit_parent)
free(brctx->explicit_parent);
}
free(brctx);
exit(-ret);
}
static int btrfs_rcv_finish_subvol(struct far_rcv_ctx *frctx)
{
struct btrfs_rcv_ctx *brctx = (struct btrfs_rcv_ctx *)frctx;
int ret;
int subvol_fd = -1;
int info_fd = -1;
struct btrfs_ioctl_received_subvol_args rs_args;
char uuid_str[128];
uint64_t flags;
if (brctx->cur_subvol == NULL)
return 0;
subvol_fd = openat(brctx->mnt_fd, brctx->cur_subvol->path,
O_RDONLY | O_NOATIME);
if (subvol_fd < 0) {
ret = -errno;
fprintf(stderr, "ERROR: open %s failed. %s.\n",
brctx->cur_subvol->path, strerror(-ret));
goto out;
}
memset(&rs_args, 0, sizeof(rs_args));
memcpy(rs_args.uuid, brctx->cur_subvol->received_uuid, BTRFS_UUID_SIZE);
rs_args.stransid = brctx->cur_subvol->stransid;
if (brctx->frctx.verbose >= 1) {
uuid_unparse((u8*)rs_args.uuid, uuid_str);
fprintf(stderr,
"BTRFS_IOC_SET_RECEIVED_SUBVOL uuid=%s, stransid=%" PRIu64 ".\n",
uuid_str, (uint64_t)rs_args.stransid);
}
ret = ioctl(subvol_fd, BTRFS_IOC_SET_RECEIVED_SUBVOL, &rs_args);
if (ret < 0) {
ret = -errno;
fprintf(stderr,
"ERROR: BTRFS_IOC_SET_RECEIVED_SUBVOL failed. %s.\n",
strerror(-ret));
goto out;
}
brctx->cur_subvol->rtransid = rs_args.rtransid;
ret = ioctl(subvol_fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags);
if (ret < 0) {
ret = -errno;
fprintf(stderr,
"ERROR: BTRFS_IOC_SUBVOL_GETFLAGS failed. %s.\n",
strerror(-ret));
goto out;
}
flags |= BTRFS_SUBVOL_RDONLY;
ret = ioctl(subvol_fd, BTRFS_IOC_SUBVOL_SETFLAGS, &flags);
if (ret < 0) {
ret = -errno;
fprintf(stderr,
"ERROR: failed to make subvolume read only. %s.\n",
strerror(-ret));
goto out;
}
ret = btrfs_list_get_path_rootid(subvol_fd,
&brctx->cur_subvol->root_id);
if (ret < 0)
goto out;
subvol_uuid_search_add(&brctx->subvol_uuid_search_ctx,
brctx->cur_subvol);
brctx->cur_subvol = NULL;
ret = 0;
out:
if (brctx->cur_subvol) {
free(brctx->cur_subvol->path);
free(brctx->cur_subvol);
brctx->cur_subvol = NULL;
}
if (subvol_fd != -1)
close(subvol_fd);
if (info_fd != -1)
close(info_fd);
return ret;
}
static int btrfs_rcv_process_subvol(struct far_rcv_ctx *frctx, const char *path,
const unsigned char *uuid,
uint64_t ctransid)
{
struct btrfs_rcv_ctx *brctx = (struct btrfs_rcv_ctx *)frctx;
int ret;
struct btrfs_ioctl_vol_args args_v1;
char uuid_str[128];
const char *const orig_path = path;
ret = btrfs_rcv_finish_subvol(frctx);
if (ret < 0)
goto out;
brctx->cur_subvol = calloc(1, sizeof(*brctx->cur_subvol));
if (!brctx->cur_subvol) {
ret = -ENOMEM;
goto out;
}
if (brctx->explicit_dest_subvol) {
if (brctx->frctx.verbose)
fprintf(stderr, "Override destination, use \"%s\".\n",
brctx->explicit_dest_subvol);
path = brctx->explicit_dest_subvol;
}
if (strlen(brctx->dest_dir_path) == 0)
brctx->cur_subvol->path = strdup(path);
else
brctx->cur_subvol->path = path_cat(brctx->dest_dir_path, path);
free(brctx->full_subvol_path);
brctx->full_subvol_path = path_cat3(brctx->mnt_path,
brctx->dest_dir_path, path);
if (frctx->free_current_base_path)
free((void *)frctx->current_base_path);
frctx->current_base_path = brctx->full_subvol_path;
frctx->free_current_base_path = 0;
if (brctx->explicit_dest_subvol)
fprintf(stderr, "At subvol %s (orig name was %s).\n", path,
orig_path);
else
fprintf(stderr, "At subvol %s.\n", path);
memcpy(brctx->cur_subvol->received_uuid, uuid, BTRFS_UUID_SIZE);
brctx->cur_subvol->stransid = ctransid;
if (brctx->frctx.verbose) {
uuid_unparse((u8*)brctx->cur_subvol->received_uuid, uuid_str);
fprintf(stderr,
"receiving subvol %s uuid=%s, stransid=%" PRIu64 ".\n",
path, uuid_str, (uint64_t)brctx->cur_subvol->stransid);
}
memset(&args_v1, 0, sizeof(args_v1));
assert(strlen(path) < sizeof(args_v1.name));
strcpy(args_v1.name, path);
ret = ioctl(brctx->dest_dir_fd, BTRFS_IOC_SUBVOL_CREATE, &args_v1);
if (ret < 0) {
ret = -errno;
fprintf(stderr, "ERROR: creating subvolume %s failed. %s.\n",
path, strerror(-ret));
goto out;
}
out:
return ret;
}
static int btrfs_rcv_process_snapshot(struct far_rcv_ctx *frctx,
const char *path,
const unsigned char *uuid,
uint64_t ctransid,
const unsigned char *parent_uuid,
uint64_t parent_ctransid)
{
struct btrfs_rcv_ctx *brctx = (struct btrfs_rcv_ctx *)frctx;
int ret;
char uuid_str[128];
struct btrfs_ioctl_vol_args_v2 args_v2;
struct subvol_info *parent_subvol = NULL;
const char *const orig_path = path;
ret = btrfs_rcv_finish_subvol(frctx);
if (ret < 0)
goto out;
brctx->cur_subvol = calloc(1, sizeof(*brctx->cur_subvol));
if (!brctx->cur_subvol) {
ret = -ENOMEM;
goto out;
}
if (brctx->explicit_dest_subvol) {
if (brctx->frctx.verbose)
fprintf(stderr, "Override destination, use \"%s\".\n",
brctx->explicit_dest_subvol);
path = brctx->explicit_dest_subvol;
}
if (strlen(brctx->dest_dir_path) == 0)
brctx->cur_subvol->path = strdup(path);
else
brctx->cur_subvol->path = path_cat(brctx->dest_dir_path, path);
free(brctx->full_subvol_path);
brctx->full_subvol_path = path_cat3(brctx->mnt_path,
brctx->dest_dir_path, path);
if (frctx->free_current_base_path)
free((void *)frctx->current_base_path);
frctx->current_base_path = brctx->full_subvol_path;
frctx->free_current_base_path = 0;
if (brctx->explicit_dest_subvol)
fprintf(stderr, "At snapshot %s (orig name was %s).\n", path,
orig_path);
else
fprintf(stderr, "At snapshot %s.\n", path);
memcpy(brctx->cur_subvol->received_uuid, uuid, BTRFS_UUID_SIZE);
brctx->cur_subvol->stransid = ctransid;
if (brctx->frctx.verbose) {
uuid_unparse((unsigned char*)brctx->cur_subvol->received_uuid,
uuid_str);
fprintf(stderr,
"receiving snapshot %s uuid=%s, ctransid=%" PRIu64 ", ",
path, uuid_str, (uint64_t)brctx->cur_subvol->stransid);
uuid_unparse(parent_uuid, uuid_str);
fprintf(stderr,
"parent_uuid=%s, parent_ctransid=%" PRIu64 ".\n",
uuid_str, (uint64_t)parent_ctransid);
}
memset(&args_v2, 0, sizeof(args_v2));
assert(strlen(path) < sizeof(args_v2.name));
strcpy(args_v2.name, path);
if (brctx->explicit_parent) {
if (brctx->frctx.verbose)
fprintf(stderr, "Override parent, use \"%s\".\n",
brctx->explicit_parent);
parent_subvol = subvol_uuid_search(
&brctx->subvol_uuid_search_ctx, 0,
NULL, 0, brctx->explicit_parent,
subvol_search_by_path);
} else {
parent_subvol = subvol_uuid_search(
&brctx->subvol_uuid_search_ctx, 0,
parent_uuid, parent_ctransid, NULL,
subvol_search_by_received_uuid);
}
if (!parent_subvol) {
fprintf(stderr, "ERROR: could not find parent subvolume.\n");
goto out;
}
/*if (rs_args.ctransid > rs_args.rtransid) {
if (!brctx->force) {
ret = -EINVAL;
fprintf(stderr,
"ERROR: subvolume %s was modified after it was received.\n",
brctx->subvol_parent_name);
goto out;
} else {
fprintf(stderr,
"WARNING: subvolume %s was modified after it was received.\n",
brctx->subvol_parent_name);
}
}*/
args_v2.fd = openat(brctx->mnt_fd, parent_subvol->path,
O_RDONLY | O_NOATIME);
if (args_v2.fd < 0) {
ret = -errno;
fprintf(stderr, "ERROR: open %s failed. %s.\n",
parent_subvol->path, strerror(-ret));
goto out;
}
ret = ioctl(brctx->dest_dir_fd, BTRFS_IOC_SNAP_CREATE_V2, &args_v2);
close(args_v2.fd);
if (ret < 0) {
ret = -errno;
fprintf(stderr,
"ERROR: creating snapshot %s -> %s failed. %s.\n",
parent_subvol->path, path, strerror(-ret));
goto out;
}
if (brctx->explicit_parent) {
if (brctx->free_explicit_parent)
free(brctx->explicit_parent);
brctx->explicit_parent = strdup(brctx->cur_subvol->path);
brctx->free_explicit_parent = 1;
}
out:
#if defined (BTRFS_UUID_TREE_OBJECTID) /* hacky way to detect the new
* interface */
if (parent_subvol) {
free(parent_subvol->path);
free(parent_subvol);
}
#endif /* defined (BTRFS_UUID_TREE_OBJECTID) */
return ret;
}
static int btrfs_rcv_process_clone(struct far_rcv_ctx *frctx, const char *path,
uint64_t offset, uint64_t len,
const unsigned char *clone_uuid,
uint64_t clone_ctransid,
const char *clone_path,
uint64_t clone_offset)
{
struct btrfs_rcv_ctx *brctx = (struct btrfs_rcv_ctx *)frctx;
int ret;
struct btrfs_ioctl_clone_range_args clone_args;
struct subvol_info *si = NULL;
char *full_path = path_cat(brctx->full_subvol_path, path);
char *subvol_path = NULL;
char *full_clone_path = NULL;
int clone_fd = -1;
ret = frctx->ops.open_inode_for_write(frctx, full_path);
if (ret < 0)
goto out;
si = subvol_uuid_search(&brctx->subvol_uuid_search_ctx, 0, clone_uuid,
clone_ctransid, NULL,
subvol_search_by_received_uuid);
if (!si) {
if (memcmp(clone_uuid, brctx->cur_subvol->received_uuid,
BTRFS_UUID_SIZE) == 0) {
/* TODO check generation of extent */
subvol_path = strdup(brctx->cur_subvol->path);
} else {
ret = -ENOENT;
fprintf(stderr, "ERROR: did not find source subvol.\n");
goto out;
}
} else {
/*if (rs_args.ctransid > rs_args.rtransid) {
if (!brctx->force) {
ret = -EINVAL;
fprintf(stderr,
"ERROR: subvolume %s was modified after it was received.\n",
brctx->subvol_parent_name);
goto out;
} else {
fprintf(stderr,
"WARNING: subvolume %s was modified after it was received.\n",
brctx->subvol_parent_name);
}
}*/
subvol_path = strdup(si->path);
}
full_clone_path = path_cat3(brctx->mnt_path, subvol_path, clone_path);
clone_fd = open(full_clone_path, O_RDONLY | O_NOATIME);
if (clone_fd < 0) {
ret = -errno;
fprintf(stderr, "ERROR: failed to open clone src %s. %s.\n",
full_clone_path, strerror(-ret));
goto out;
}
clone_args.src_fd = clone_fd;
clone_args.src_offset = clone_offset;
clone_args.src_length = len;
clone_args.dest_offset = offset;
ret = ioctl(brctx->frctx.write_fd, BTRFS_IOC_CLONE_RANGE, &clone_args);
if (ret) {
ret = -errno;
fprintf(stderr, "ERROR: failed to clone extents to %s.\n%s.\n",
path, strerror(-ret));
goto out;
}
out:
#if defined (BTRFS_UUID_TREE_OBJECTID) /* hacky way to detect the new
* interface */
if (si) {
free(si->path);
free(si);
}
#endif /* defined (BTRFS_UUID_TREE_OBJECTID) */
free(full_path);
free(full_clone_path);
free(subvol_path);
if (clone_fd != -1)
close(clone_fd);
return ret;
}
/*
* TODO: this function is a copy of btrfs-progs/cmds-send.c find_mount_root()
* since it is not exported there and since adding it to the list of exported
* functions in libbtrfs either exported too much (util.c) or didn't look
* proper (send-utils.c which is then also used by cmds-subvolume.c).
*/
static int btrfs_rcv_find_mount_root(const char *path, char **mount_root)
{
FILE *mnttab;
int fd;
struct mntent *ent;
int len;
int longest_matchlen = 0;
char *longest_match = NULL;
fd = open(path, O_RDONLY | O_NOATIME);
if (fd < 0)
return -errno;
close(fd);
mnttab = fopen("/proc/mounts", "r");
while ((ent = getmntent(mnttab))) {
len = strlen(ent->mnt_dir);
if (strncmp(ent->mnt_dir, path, len) == 0) {
/* match found */
if (longest_matchlen < len) {
free(longest_match);
longest_matchlen = len;
longest_match = strdup(ent->mnt_dir);
}
}
}
fclose(mnttab);
*mount_root = realpath(longest_match, NULL);
free(longest_match);
return 0;
}