summaryrefslogtreecommitdiff
path: root/cmds-receive.c
diff options
context:
space:
mode:
Diffstat (limited to 'cmds-receive.c')
-rw-r--r--cmds-receive.c912
1 files changed, 912 insertions, 0 deletions
diff --git a/cmds-receive.c b/cmds-receive.c
new file mode 100644
index 00000000..a8be6fa4
--- /dev/null
+++ b/cmds-receive.c
@@ -0,0 +1,912 @@
+/*
+ * 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.
+ */
+
+#define _GNU_SOURCE
+#define _POSIX_C_SOURCE 200809
+#define _XOPEN_SOURCE 700
+#define _BSD_SOURCE
+
+#include <unistd.h>
+#include <stdint.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <math.h>
+#include <ftw.h>
+#include <wait.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/xattr.h>
+#include <uuid/uuid.h>
+
+#include "ctree.h"
+#include "ioctl.h"
+#include "commands.h"
+#include "list.h"
+
+#include "send.h"
+#include "send-stream.h"
+#include "send-utils.h"
+
+static int g_verbose = 0;
+
+struct btrfs_receive
+{
+ int mnt_fd;
+
+ int write_fd;
+ char *write_path;
+
+ char *root_path;
+ char *full_subvol_path;
+
+ struct subvol_info *cur_subvol;
+ struct subvol_info *parent_subvol;
+
+ struct subvol_uuid_search sus;
+};
+
+static int finish_subvol(struct btrfs_receive *r)
+{
+ int ret;
+ int subvol_fd = -1;
+ int info_fd = -1;
+ struct btrfs_ioctl_received_subvol_args rs_args;
+ char uuid_str[128];
+ u64 flags;
+
+ if (r->cur_subvol == NULL)
+ return 0;
+
+ subvol_fd = openat(r->mnt_fd, r->cur_subvol->path,
+ O_RDONLY | O_NOATIME);
+ if (subvol_fd < 0) {
+ ret = -errno;
+ fprintf(stderr, "ERROR: open %s failed. %s\n",
+ r->cur_subvol->path, strerror(-ret));
+ goto out;
+ }
+
+ memset(&rs_args, 0, sizeof(rs_args));
+ memcpy(rs_args.uuid, r->cur_subvol->received_uuid, BTRFS_UUID_SIZE);
+ rs_args.stransid = r->cur_subvol->stransid;
+
+ if (g_verbose >= 1) {
+ uuid_unparse((u8*)rs_args.uuid, uuid_str);
+ fprintf(stderr, "BTRFS_IOC_SET_RECEIVED_SUBVOL uuid=%s, "
+ "stransid=%llu\n", uuid_str, 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;
+ }
+ r->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;
+ }
+
+ subvol_uuid_search_add(&r->sus, r->cur_subvol);
+ r->cur_subvol = NULL;
+ ret = 0;
+
+out:
+ if (subvol_fd != -1)
+ close(subvol_fd);
+ if (info_fd != -1)
+ close(info_fd);
+ return ret;
+}
+
+static int process_subvol(const char *path, const u8 *uuid, u64 ctransid,
+ void *user)
+{
+ int ret;
+ struct btrfs_receive *r = user;
+ struct btrfs_ioctl_vol_args args_v1;
+ char uuid_str[128];
+
+ ret = finish_subvol(r);
+ if (ret < 0)
+ goto out;
+
+ r->cur_subvol = calloc(1, sizeof(*r->cur_subvol));
+ r->parent_subvol = NULL;
+
+ r->cur_subvol->path = strdup(path);
+ r->full_subvol_path = path_cat(r->root_path, path);
+
+ fprintf(stderr, "At subvol %s\n", path);
+
+ memcpy(r->cur_subvol->received_uuid, uuid, BTRFS_UUID_SIZE);
+ r->cur_subvol->stransid = ctransid;
+
+ if (g_verbose) {
+ uuid_unparse((u8*)r->cur_subvol->received_uuid, uuid_str);
+ fprintf(stderr, "receiving subvol %s uuid=%s, stransid=%llu\n",
+ path, uuid_str,
+ r->cur_subvol->stransid);
+ }
+
+ memset(&args_v1, 0, sizeof(args_v1));
+ strcpy(args_v1.name, path);
+ ret = ioctl(r->mnt_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 process_snapshot(const char *path, const u8 *uuid, u64 ctransid,
+ const u8 *parent_uuid, u64 parent_ctransid,
+ void *user)
+{
+ int ret;
+ struct btrfs_receive *r = user;
+ char uuid_str[128];
+ struct btrfs_ioctl_vol_args_v2 args_v2;
+
+ ret = finish_subvol(r);
+ if (ret < 0)
+ goto out;
+
+ r->cur_subvol = calloc(1, sizeof(*r->cur_subvol));
+ r->parent_subvol = NULL;
+
+ r->cur_subvol->path = strdup(path);
+ r->full_subvol_path = path_cat(r->root_path, path);
+
+ fprintf(stderr, "At snapshot %s\n", path);
+
+ memcpy(r->cur_subvol->received_uuid, uuid, BTRFS_UUID_SIZE);
+ r->cur_subvol->stransid = ctransid;
+
+ if (g_verbose) {
+ uuid_unparse((u8*)r->cur_subvol->received_uuid, uuid_str);
+ fprintf(stderr, "receiving snapshot %s uuid=%s, "
+ "ctransid=%llu ", path, uuid_str,
+ r->cur_subvol->stransid);
+ uuid_unparse(parent_uuid, uuid_str);
+ fprintf(stderr, "parent_uuid=%s, parent_ctransid=%llu\n",
+ uuid_str, parent_ctransid);
+ }
+
+ memset(&args_v2, 0, sizeof(args_v2));
+ strcpy(args_v2.name, path);
+
+ r->parent_subvol = subvol_uuid_search(&r->sus, 0, parent_uuid,
+ parent_ctransid, NULL, subvol_search_by_received_uuid);
+ if (!r->parent_subvol) {
+ ret = -ENOENT;
+ fprintf(stderr, "ERROR: could not find parent subvolume\n");
+ goto out;
+ }
+
+ /*if (rs_args.ctransid > rs_args.rtransid) {
+ if (!r->force) {
+ ret = -EINVAL;
+ fprintf(stderr, "ERROR: subvolume %s was modified after it was received.\n", r->subvol_parent_name);
+ goto out;
+ } else {
+ fprintf(stderr, "WARNING: subvolume %s was modified after it was received.\n", r->subvol_parent_name);
+ }
+ }*/
+
+ args_v2.fd = openat(r->mnt_fd, r->parent_subvol->path,
+ O_RDONLY | O_NOATIME);
+ if (args_v2.fd < 0) {
+ ret = -errno;
+ fprintf(stderr, "ERROR: open %s failed. %s\n",
+ r->parent_subvol->path, strerror(-ret));
+ goto out;
+ }
+
+ ret = ioctl(r->mnt_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", r->parent_subvol->path,
+ path, strerror(-ret));
+ goto out;
+ }
+
+out:
+ return ret;
+}
+
+static int process_mkfile(const char *path, void *user)
+{
+ int ret;
+ struct btrfs_receive *r = user;
+ char *full_path = path_cat(r->full_subvol_path, path);
+
+ if (g_verbose >= 1)
+ fprintf(stderr, "mkfile %s\n", path);
+
+ ret = creat(full_path, 0600);
+ if (ret < 0) {
+ ret = -errno;
+ fprintf(stderr, "ERROR: mkfile %s failed. %s\n", path,
+ strerror(-ret));
+ goto out;
+ }
+ close(ret);
+ ret = 0;
+
+out:
+ free(full_path);
+ return ret;
+}
+
+static int process_mkdir(const char *path, void *user)
+{
+ int ret;
+ struct btrfs_receive *r = user;
+ char *full_path = path_cat(r->full_subvol_path, path);
+
+ if (g_verbose >= 1)
+ fprintf(stderr, "mkdir %s\n", path);
+
+ ret = mkdir(full_path, 0700);
+ if (ret < 0) {
+ ret = -errno;
+ fprintf(stderr, "ERROR: mkdir %s failed. %s\n", path,
+ strerror(-ret));
+ }
+
+ free(full_path);
+ return ret;
+}
+
+static int process_mknod(const char *path, u64 mode, u64 dev, void *user)
+{
+ int ret;
+ struct btrfs_receive *r = user;
+ char *full_path = path_cat(r->full_subvol_path, path);
+
+ if (g_verbose >= 1)
+ fprintf(stderr, "mknod %s mode=%llu, dev=%llu\n",
+ path, mode, dev);
+
+ ret = mknod(full_path, mode & S_IFMT, dev);
+ if (ret < 0) {
+ ret = -errno;
+ fprintf(stderr, "ERROR: mknod %s failed. %s\n", path,
+ strerror(-ret));
+ }
+
+ free(full_path);
+ return ret;
+}
+
+static int process_mkfifo(const char *path, void *user)
+{
+ int ret;
+ struct btrfs_receive *r = user;
+ char *full_path = path_cat(r->full_subvol_path, path);
+
+ if (g_verbose >= 1)
+ fprintf(stderr, "mkfifo %s\n", path);
+
+ ret = mkfifo(full_path, 0600);
+ if (ret < 0) {
+ ret = -errno;
+ fprintf(stderr, "ERROR: mkfifo %s failed. %s\n", path,
+ strerror(-ret));
+ }
+
+ free(full_path);
+ return ret;
+}
+
+static int process_mksock(const char *path, void *user)
+{
+ int ret;
+ struct btrfs_receive *r = user;
+ char *full_path = path_cat(r->full_subvol_path, path);
+
+ if (g_verbose >= 1)
+ fprintf(stderr, "mksock %s\n", path);
+
+ ret = mknod(full_path, 0600 | S_IFSOCK, 0);
+ if (ret < 0) {
+ ret = -errno;
+ fprintf(stderr, "ERROR: mknod %s failed. %s\n", path,
+ strerror(-ret));
+ }
+
+ free(full_path);
+ return ret;
+}
+
+static int process_symlink(const char *path, const char *lnk, void *user)
+{
+ int ret;
+ struct btrfs_receive *r = user;
+ char *full_path = path_cat(r->full_subvol_path, path);
+
+ if (g_verbose >= 1)
+ fprintf(stderr, "symlink %s -> %s\n", path, lnk);
+
+ ret = symlink(lnk, full_path);
+ if (ret < 0) {
+ ret = -errno;
+ fprintf(stderr, "ERROR: symlink %s -> %s failed. %s\n", path,
+ lnk, strerror(-ret));
+ }
+
+ free(full_path);
+ return ret;
+}
+
+static int process_rename(const char *from, const char *to, void *user)
+{
+ int ret;
+ struct btrfs_receive *r = user;
+ char *full_from = path_cat(r->full_subvol_path, from);
+ char *full_to = path_cat(r->full_subvol_path, to);
+
+ if (g_verbose >= 1)
+ fprintf(stderr, "rename %s -> %s\n", from, to);
+
+ ret = rename(full_from, full_to);
+ if (ret < 0) {
+ ret = -errno;
+ fprintf(stderr, "ERROR: rename %s -> %s failed. %s\n", from,
+ to, strerror(-ret));
+ }
+
+ free(full_from);
+ free(full_to);
+ return ret;
+}
+
+static int process_link(const char *path, const char *lnk, void *user)
+{
+ int ret;
+ struct btrfs_receive *r = user;
+ char *full_path = path_cat(r->full_subvol_path, path);
+ char *full_link_path = path_cat(r->full_subvol_path, lnk);
+
+ if (g_verbose >= 1)
+ fprintf(stderr, "link %s -> %s\n", path, lnk);
+
+ ret = link(full_link_path, full_path);
+ if (ret < 0) {
+ ret = -errno;
+ fprintf(stderr, "ERROR: link %s -> %s failed. %s\n", path,
+ lnk, strerror(-ret));
+ }
+
+ free(full_path);
+ free(full_link_path);
+ return ret;
+}
+
+
+static int process_unlink(const char *path, void *user)
+{
+ int ret;
+ struct btrfs_receive *r = user;
+ char *full_path = path_cat(r->full_subvol_path, path);
+
+ if (g_verbose >= 1)
+ fprintf(stderr, "unlink %s\n", path);
+
+ ret = unlink(full_path);
+ if (ret < 0) {
+ ret = -errno;
+ fprintf(stderr, "ERROR: unlink %s failed. %s\n", path,
+ strerror(-ret));
+ }
+
+ free(full_path);
+ return ret;
+}
+
+static int process_rmdir(const char *path, void *user)
+{
+ int ret;
+ struct btrfs_receive *r = user;
+ char *full_path = path_cat(r->full_subvol_path, path);
+
+ if (g_verbose >= 1)
+ fprintf(stderr, "rmdir %s\n", path);
+
+ ret = rmdir(full_path);
+ if (ret < 0) {
+ ret = -errno;
+ fprintf(stderr, "ERROR: rmdir %s failed. %s\n", path,
+ strerror(-ret));
+ }
+
+ free(full_path);
+ return ret;
+}
+
+
+static int open_inode_for_write(struct btrfs_receive *r, const char *path)
+{
+ int ret = 0;
+
+ if (r->write_fd != -1) {
+ if (strcmp(r->write_path, path) == 0)
+ goto out;
+ close(r->write_fd);
+ r->write_fd = -1;
+ }
+
+ r->write_fd = open(path, O_RDWR);
+ if (r->write_fd < 0) {
+ ret = -errno;
+ fprintf(stderr, "ERROR: open %s failed. %s\n", path,
+ strerror(-ret));
+ goto out;
+ }
+ free(r->write_path);
+ r->write_path = strdup(path);
+
+out:
+ return ret;
+}
+
+static int close_inode_for_write(struct btrfs_receive *r)
+{
+ int ret = 0;
+
+ if(r->write_fd == -1)
+ goto out;
+
+ close(r->write_fd);
+ r->write_fd = -1;
+ r->write_path[0] = 0;
+
+out:
+ return ret;
+}
+
+static int process_write(const char *path, const void *data, u64 offset,
+ u64 len, void *user)
+{
+ int ret = 0;
+ struct btrfs_receive *r = user;
+ char *full_path = path_cat(r->full_subvol_path, path);
+ u64 pos = 0;
+ int w;
+
+ ret = open_inode_for_write(r, full_path);
+ if (ret < 0)
+ goto out;
+
+ while (pos < len) {
+ w = pwrite(r->write_fd, (char*)data + pos, len - pos,
+ offset + pos);
+ if (w < 0) {
+ ret = -errno;
+ fprintf(stderr, "ERROR: writing to %s failed. %s\n",
+ path, strerror(-ret));
+ goto out;
+ }
+ pos += w;
+ }
+
+out:
+ free(full_path);
+ return ret;
+}
+
+static int process_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 ret = 0;
+ struct btrfs_receive *r = user;
+ struct btrfs_ioctl_clone_range_args clone_args;
+ struct subvol_info *si = NULL;
+ char *full_path = path_cat(r->full_subvol_path, path);
+ char *subvol_path = NULL;
+ char *full_clone_path = NULL;
+ int clone_fd = -1;
+
+ ret = open_inode_for_write(r, full_path);
+ if (ret < 0)
+ goto out;
+
+ si = subvol_uuid_search(&r->sus, 0, clone_uuid, clone_ctransid, NULL,
+ subvol_search_by_received_uuid);
+ if (!si) {
+ if (memcmp(clone_uuid, r->cur_subvol->received_uuid,
+ BTRFS_FSID_SIZE) == 0) {
+ /* TODO check generation of extent */
+ subvol_path = strdup(r->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 (!r->force) {
+ ret = -EINVAL;
+ fprintf(stderr, "ERROR: subvolume %s was "
+ "modified after it was "
+ "received.\n",
+ r->subvol_parent_name);
+ goto out;
+ } else {
+ fprintf(stderr, "WARNING: subvolume %s was "
+ "modified after it was "
+ "received.\n",
+ r->subvol_parent_name);
+ }
+ }*/
+ subvol_path = strdup(si->path);
+ }
+
+ full_clone_path = path_cat3(r->root_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 %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(r->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:
+ free(full_path);
+ free(full_clone_path);
+ free(subvol_path);
+ if (clone_fd != -1)
+ close(clone_fd);
+ return ret;
+}
+
+
+static int process_set_xattr(const char *path, const char *name,
+ const void *data, int len, void *user)
+{
+ int ret = 0;
+ struct btrfs_receive *r = user;
+ char *full_path = path_cat(r->full_subvol_path, path);
+
+ if (g_verbose >= 1) {
+ fprintf(stderr, "set_xattr %s - name=%s data_len=%d "
+ "data=%.*s\n", path, name, len,
+ len, (char*)data);
+ }
+
+ ret = lsetxattr(full_path, name, data, len, 0);
+ if (ret < 0) {
+ ret = -errno;
+ fprintf(stderr, "ERROR: lsetxattr %s %s=%.*s failed. %s\n",
+ path, name, len, (char*)data, strerror(-ret));
+ goto out;
+ }
+
+out:
+ free(full_path);
+ return ret;
+}
+
+static int process_remove_xattr(const char *path, const char *name, void *user)
+{
+ int ret = 0;
+ struct btrfs_receive *r = user;
+ char *full_path = path_cat(r->full_subvol_path, path);
+
+ if (g_verbose >= 1) {
+ fprintf(stderr, "remove_xattr %s - name=%s\n",
+ path, name);
+ }
+
+ ret = lremovexattr(full_path, name);
+ if (ret < 0) {
+ ret = -errno;
+ fprintf(stderr, "ERROR: lremovexattr %s %s failed. %s\n",
+ path, name, strerror(-ret));
+ goto out;
+ }
+
+out:
+ free(full_path);
+ return ret;
+}
+
+static int process_truncate(const char *path, u64 size, void *user)
+{
+ int ret = 0;
+ struct btrfs_receive *r = user;
+ char *full_path = path_cat(r->full_subvol_path, path);
+
+ if (g_verbose >= 1)
+ fprintf(stderr, "truncate %s size=%llu\n", path, size);
+
+ ret = truncate(full_path, size);
+ if (ret < 0) {
+ ret = -errno;
+ fprintf(stderr, "ERROR: truncate %s failed. %s\n",
+ path, strerror(-ret));
+ goto out;
+ }
+
+out:
+ free(full_path);
+ return ret;
+}
+
+static int process_chmod(const char *path, u64 mode, void *user)
+{
+ int ret = 0;
+ struct btrfs_receive *r = user;
+ char *full_path = path_cat(r->full_subvol_path, path);
+
+ if (g_verbose >= 1)
+ fprintf(stderr, "chmod %s - mode=0%o\n", path, (int)mode);
+
+ ret = chmod(full_path, mode);
+ if (ret < 0) {
+ ret = -errno;
+ fprintf(stderr, "ERROR: chmod %s failed. %s\n",
+ path, strerror(-ret));
+ goto out;
+ }
+
+out:
+ free(full_path);
+ return ret;
+}
+
+static int process_chown(const char *path, u64 uid, u64 gid, void *user)
+{
+ int ret = 0;
+ struct btrfs_receive *r = user;
+ char *full_path = path_cat(r->full_subvol_path, path);
+
+ if (g_verbose >= 1)
+ fprintf(stderr, "chown %s - uid=%llu, gid=%llu\n", path,
+ uid, gid);
+
+ ret = chown(full_path, uid, gid);
+ if (ret < 0) {
+ ret = -errno;
+ fprintf(stderr, "ERROR: chown %s failed. %s\n",
+ path, strerror(-ret));
+ goto out;
+ }
+
+out:
+ free(full_path);
+ return ret;
+}
+
+static int process_utimes(const char *path, struct timespec *at,
+ struct timespec *mt, struct timespec *ct,
+ void *user)
+{
+ int ret = 0;
+ struct btrfs_receive *r = user;
+ char *full_path = path_cat(r->full_subvol_path, path);
+ struct timespec tv[2];
+
+ if (g_verbose >= 1)
+ fprintf(stderr, "utimes %s\n", path);
+
+ tv[0] = *at;
+ tv[1] = *mt;
+ ret = utimensat(-1, full_path, tv, AT_SYMLINK_NOFOLLOW);
+ if (ret < 0) {
+ ret = -errno;
+ fprintf(stderr, "ERROR: utimes %s failed. %s\n",
+ path, strerror(-ret));
+ goto out;
+ }
+
+out:
+ free(full_path);
+ return ret;
+}
+
+
+struct btrfs_send_ops send_ops = {
+ .subvol = process_subvol,
+ .snapshot = process_snapshot,
+ .mkfile = process_mkfile,
+ .mkdir = process_mkdir,
+ .mknod = process_mknod,
+ .mkfifo = process_mkfifo,
+ .mksock = process_mksock,
+ .symlink = process_symlink,
+ .rename = process_rename,
+ .link = process_link,
+ .unlink = process_unlink,
+ .rmdir = process_rmdir,
+ .write = process_write,
+ .clone = process_clone,
+ .set_xattr = process_set_xattr,
+ .remove_xattr = process_remove_xattr,
+ .truncate = process_truncate,
+ .chmod = process_chmod,
+ .chown = process_chown,
+ .utimes = process_utimes,
+};
+
+int do_receive(struct btrfs_receive *r, const char *tomnt, int r_fd)
+{
+ int ret;
+ int end = 0;
+
+ r->root_path = strdup(tomnt);
+ r->mnt_fd = open(tomnt, O_RDONLY | O_NOATIME);
+ if (r->mnt_fd < 0) {
+ ret = -errno;
+ fprintf(stderr, "ERROR: failed to open %s. %s\n", tomnt,
+ strerror(-ret));
+ goto out;
+ }
+
+ ret = subvol_uuid_search_init(r->mnt_fd, &r->sus);
+ if (ret < 0)
+ return ret;
+
+ r->write_fd = -1;
+
+ while (!end) {
+ ret = btrfs_read_and_process_send_stream(r_fd, &send_ops, r);
+ if (ret < 0)
+ goto out;
+ if (ret)
+ end = 1;
+
+ ret = close_inode_for_write(r);
+ if (ret < 0)
+ goto out;
+ ret = finish_subvol(r);
+ if (ret < 0)
+ goto out;
+ }
+ ret = 0;
+
+out:
+ return ret;
+}
+
+static int do_cmd_receive(int argc, char **argv)
+{
+ int c;
+ char *tomnt = NULL;
+ char *fromfile = NULL;
+ struct btrfs_receive r;
+ int receive_fd = fileno(stdin);
+
+ int ret;
+
+ memset(&r, 0, sizeof(r));
+
+ while ((c = getopt(argc, argv, "vf:")) != -1) {
+ switch (c) {
+ case 'v':
+ g_verbose++;
+ break;
+ case 'f':
+ fromfile = optarg;
+ break;
+ case '?':
+ default:
+ fprintf(stderr, "ERROR: receive args invalid.\n");
+ return 1;
+ }
+ }
+
+ if (optind + 1 != argc) {
+ fprintf(stderr, "ERROR: receive needs path to subvolume\n");
+ return 1;
+ }
+
+ tomnt = argv[optind];
+
+ if (fromfile) {
+ receive_fd = open(fromfile, O_RDONLY | O_NOATIME);
+ if (receive_fd < 0) {
+ fprintf(stderr, "ERROR: failed to open %s\n", fromfile);
+ return -errno;
+ }
+ }
+
+ ret = do_receive(&r, tomnt, receive_fd);
+
+ return ret;
+}
+
+static const char * const receive_cmd_group_usage[] = {
+ "btrfs receive <command> <args>",
+ NULL
+};
+
+static const char * const cmd_receive_usage[] = {
+ "btrfs receive [-v] [-i <infile>] <mount>",
+ "Receive subvolumes from stdin.",
+ "Receives one or more subvolumes that were previously ",
+ "sent with btrfs send. The received subvolumes are stored",
+ "into <mount>.",
+ "btrfs receive will fail in case a receiving 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.\n",
+ "-v Enable verbose debug output. Each",
+ " occurrency of this option increases the",
+ " verbose level more.",
+ "-f <infile> By default, btrfs receive uses stdin",
+ " to receive the subvolumes. Use this",
+ " option to specify a file to use instead.",
+ NULL
+};
+
+const struct cmd_group receive_cmd_group = {
+ receive_cmd_group_usage, NULL, {
+ { "receive", do_cmd_receive, cmd_receive_usage, NULL, 0 },
+ { 0, 0, 0, 0, 0 },
+ },
+};
+
+int cmd_receive(int argc, char **argv)
+{
+ return do_cmd_receive(argc, argv);
+}