summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Behrens <sbehrens@giantdisaster.de>2013-03-26 17:33:10 +0100
committerStefan Behrens <sbehrens@giantdisaster.de>2013-04-25 11:17:48 +0200
commitd3fde0a7c8496cd0c8c386871f6481c93bae3609 (patch)
tree217198effb85c547d62c5909e06a41a164715882
parentcb0e413a80c9a9347159a4ecae31acfd2ea9f87c (diff)
downloadfar-progs-d3fde0a7c8496cd0c8c386871f6481c93bae3609.tar.gz
far-progs: add applications that use the far-lib
Signed-off-by: Stefan Behrens <sbehrens@giantdisaster.de>
-rw-r--r--Makefile70
-rw-r--r--btrfs-receive.c778
-rw-r--r--commonfs-receive.c476
-rw-r--r--far-rcv/far-rcv.c3
-rw-r--r--far-rcv/far-rcv.h1
-rw-r--r--zfs-receive.c439
6 files changed, 1759 insertions, 8 deletions
diff --git a/Makefile b/Makefile
index 36b880a..70c42cd 100644
--- a/Makefile
+++ b/Makefile
@@ -1,21 +1,75 @@
OS = $(shell uname -s)
+RELEASE = $(shell uname -r)
+
ifeq "$(OS)" "Linux"
-CFLAGS = -D__LINUX__ -g
+CFLAGS = -D__LINUX__
+CFLAGS += -DHAVE_NTOHLL
+CFLAGS += -DHAVE_UTIMENSAT -I$(LIBBTRFS_INCLUDE_PREFIX)
+OS_SPECIFIC_TARGET = btrfs-receive
else
-CFLAGS = -D__SOLARIS__ -g
+ifeq "$(OS)" "SunOS"
+CFLAGS = -D__SOLARIS__
+ifeq "$(RELEASE)" "5.11"
+CFLAGS += -DHAVE_NTOHLL
+CFLAGS += -DHAVE_UTIMENSAT
+endif
+OS_SPECIFIC_TARGET = zfs-receive
+endif
endif
CFLAGS += -Wall -D_FILE_OFFSET_BITS=64 -D_LARGE_FILES=1
-all: fardump fssum actions
+LIBBTRFS_PREFIX = /usr/local
+LIBBTRFS_INCLUDE_PREFIX = $(LIBBTRFS_PREFIX)/include
+LIBBTRFS_LIB_PREFIX = $(LIBBTRFS_PREFIX)/lib
+
+CC = gcc
+CFLAGS += -O2 -g -Wall -Werror
+DEPFLAGS = -Wp,-MMD,$(@D)/.$(@F).d,-MT,$@
+LDFLAGS = $(COMMON_LIBS)
+COMMON_LIBS =
+FSSUM_LIBS = -lssl -lcrypto
+FAR_RECEIVE_LIBS = far-rcv/far-rcv.a
+BTRFS_RECEIVE_LIBS = -luuid -lpthread -L$(LIBBTRFS_LIB_PREFIX) -lbtrfs
+ZFS_RECEIVE_LIBS = -lzfs
+
+all: fardump actions commonfs-receive $(OS_SPECIFIC_TARGET) fssum
+
+fardump: fardump.o Makefile
+ $(CC) $(LDFLAGS) -o $@ fardump.o
-fardump: fardump.c
- gcc $(CFLAGS) fardump.c -o fardump
+fssum: fssum.o Makefile
+ $(CC) $(LDFLAGS) -o $@ fssum.o $(FSSUM_LIBS)
-fssum: fssum.c
- gcc $(CFLAGS) fssum.c -o fssum -lssl -lcrypto
+commonfs-receive: commonfs-receive.o far-rcv/far-rcv.a Makefile
+ $(CC) $(LDFLAGS) -o $@ commonfs-receive.o $(FAR_RECEIVE_LIBS)
+
+btrfs-receive: btrfs-receive.o far-rcv/far-rcv.a Makefile
+ $(CC) $(LDFLAGS) -o $@ btrfs-receive.o $(FAR_RECEIVE_LIBS) \
+ $(BTRFS_RECEIVE_LIBS)
+
+zfs-receive: zfs-receive.o far-rcv/far-rcv.a Makefile
+ $(CC) $(LDFLAGS) -o $@ zfs-receive.o $(FAR_RECEIVE_LIBS) \
+ $(ZFS_RECEIVE_LIBS)
actions: meta meta/*.mac
perl expand.pl -o actions -q meta/*.mac
+.PHONY: far-rcv/far-rcv.a
+far-rcv/far-rcv.a:
+ (cd far-rcv && $(MAKE) $(MAKEFLAGS) `basename $@`)
+
+.c.o:
+ $(CC) $(DEPFLAGS) $(CFLAGS) -c $<
+
clean:
- rm -rf actions fardump fssum *.o
+ -rm -rf actions fardump fssum zfs-receive btrfs-receive \
+ commonfs-receive *.o .*.d
+ (cd far-rcv && $(MAKE) $(MAKEFLAGS) clean)
+
+-include .*.d
+
+fardump.o: fardump.c
+fssum.o: fssum.c
+zfs-receive.o: zfs-receive.c
+btrfs-receive.o: btrfs-receive.c
+common-receive.o: common-receive.c
diff --git a/btrfs-receive.c b/btrfs-receive.c
new file mode 100644
index 0000000..8ce2b05
--- /dev/null
+++ b/btrfs-receive.c
@@ -0,0 +1,778 @@
+/*
+ * 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, "./");
+ 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;
+}
diff --git a/commonfs-receive.c b/commonfs-receive.c
new file mode 100644
index 0000000..aa2d186
--- /dev/null
+++ b/commonfs-receive.c
@@ -0,0 +1,476 @@
+/*
+ * 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 that should be able
+ * to extract data on all filesystem. Support for snapshots or subvolumes
+ * is not required.
+ */
+
+#define _GNU_SOURCE
+#define _BSD_SOURCE
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <libgen.h>
+#include <string.h>
+#include <strings.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+#include <limits.h>
+
+#include "far-rcv/far-rcv.h"
+
+struct commonfs_rcv_ctx {
+ struct far_rcv_ctx frctx; /* MUST be the first member */
+
+ const char *explicit_parent;
+ int free_explicit_parent;
+ const char *explicit_dest_subvol;
+ int no_S_option_to_tar;
+};
+
+static void usage(const char *progname);
+int main(int argc, char **argv);
+static int commonfs_finish_subvol(struct far_rcv_ctx *frctx);
+static int commonfs_process_subvol(struct far_rcv_ctx *frctx, const char *path,
+ const unsigned char *uuid,
+ uint64_t ctransid);
+static int commonfs_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 void usage(const char *progname)
+{
+ fprintf(stderr, "%s [-vexXsS] [-f <infile>] -d <dest>\n", progname);
+ fprintf(stderr, " or\n");
+ fprintf(stderr, "%s [-vexXsS] [-f <infile>] [-p <parent>] <path>\n",
+ progname);
+ char *more[] = {
+"Receive FAR stream format from stdin.",
+"The received data is stored in the directory <dest> or below the directory",
+"<path> which need to be existent.",
+"If the -d option is used, the name of received subvolumes and snapshots is",
+"discarded and everything is stored in <dest>. Without -d, the name that is",
+"included in the FAR stream is taken to create a subdirectory below <path>",
+"-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.",
+"-x Disable support for extended attributes (this is the default).",
+"-X Enable support for extended attributes.",
+#if defined (__LINUX__)
+"-s Do not give the -S option to tar.",
+"-S Give the -S option to tar (this is the default).",
+#else /* !defined (__LINUX__) */
+"-s Do not give the -S option to tar (this is the default).",
+"-S Give the -S option to tar.",
+#endif /* !defined (__LINUX__) */
+"-f <infile> By default, stdin is used to receive the FAR stream. Use this",
+" option to specify a file to use instead.",
+"-d <dest> Extract everything below <dest> and ignore the names of",
+" subvolumes and snapshots that is specified in the FAR data",
+" stream. The <path> parameter must not be present if the -d",
+" option is used. The -d option is handy for receiving",
+" incremental snapshots if you just want to apply the incremental",
+" data and do not want to store the contents of the snapshots",
+" itself.",
+" The parent data needs to be located in <dest> as well in this",
+" case, and if multiple snapshots are received in one stream,",
+" each must use the previously received one as the parent.",
+"-p <parent> If incremental streams are received, before applying the",
+" incremental data, the contents of the directory <parent> is",
+" copied into the new snapshot directory.",
+" In case of multiple received snapshots, each received snapshot",
+" or subvolume becomes the new parent for the following received",
+" snapshot.",
+NULL
+ };
+ char **pp = more;
+
+ while (*pp) {
+ fprintf(stderr, "%s\n", *pp);
+ pp++;
+ }
+}
+
+int main(int argc, char **argv)
+{
+ int c;
+ char *dest_path = NULL;
+ char *from_file = NULL;
+ int stream_fd = -1;
+ int ret = 0;
+ int verbose = 0;
+ const char *progname;
+ int did_far_rcv_init = 0;
+ struct commonfs_rcv_ctx *crctx = NULL;
+ struct stat st;
+ char *name1 = NULL;
+ char *name2 = NULL;
+ int support_xattrs = 0;
+ int honor_end_cmd = 0;
+
+ progname = basename(argv[0]);
+ crctx = calloc(1, sizeof(*crctx));
+ if (!crctx) {
+ ret = -ENOMEM;
+ fprintf(stderr, "%s: ERROR: malloc() failed.\n", progname);
+ goto out;
+ }
+
+ crctx->explicit_parent = NULL;
+ crctx->free_explicit_parent = 0;
+ crctx->explicit_dest_subvol = NULL;
+#if defined (__LINUX__)
+ crctx->no_S_option_to_tar = 0;
+#else /* !defined (__LINUX__) */
+ crctx->no_S_option_to_tar = 1;
+#endif /* !defined (__LINUX__) */
+
+ 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':
+ crctx->no_S_option_to_tar = 1;
+ break;
+ case 'S':
+ crctx->no_S_option_to_tar = 0;
+ break;
+ case 'f':
+ from_file = optarg;
+ break;
+ case 'd':
+ crctx->explicit_dest_subvol = optarg;
+ break;
+ case 'p':
+ crctx->explicit_parent = optarg;
+ break;
+ case '?':
+ default:
+ fprintf(stderr, "%s: ERROR: args invalid.\n", progname);
+ usage(progname);
+ ret = -EINVAL;
+ goto out;
+ }
+ }
+
+ if (optind == argc && crctx->explicit_dest_subvol) {
+ name1 = strdup(crctx->explicit_dest_subvol);
+ name2 = strdup(crctx->explicit_dest_subvol);
+ dest_path = dirname(name1);
+ crctx->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 target directory.\n",
+ progname);
+ usage(progname);
+ goto out;
+ } else {
+ dest_path = argv[optind];
+ }
+
+ if (crctx->explicit_dest_subvol &&
+ !strncmp(dest_path, crctx->explicit_dest_subvol,
+ strlen(dest_path))) {
+ crctx->explicit_dest_subvol += strlen(dest_path);
+ if (dest_path[0] != '\0')
+ while (*crctx->explicit_dest_subvol == '/')
+ crctx->explicit_dest_subvol++;
+ }
+ if (crctx->explicit_parent &&
+ !strncmp(dest_path, crctx->explicit_parent, strlen(dest_path))) {
+ crctx->explicit_parent += strlen(dest_path);
+ if (dest_path[0] != '\0')
+ while (*crctx->explicit_parent == '/')
+ crctx->explicit_parent++;
+ }
+
+ if (from_file) {
+ stream_fd = open(from_file, O_RDONLY);
+ if (stream_fd < 0) {
+ ret = -errno;
+ fprintf(stderr,
+ "%s: ERROR: failed to open \"%s\". %s.\n",
+ progname, from_file, strerror(errno));
+ goto out;
+ }
+ } else {
+ stream_fd = 0; /* stdin */
+ }
+
+ /* catch some common errors in order to generate good messages */
+ ret = stat(dest_path, &st);
+ if (ret < 0) {
+ ret = -errno;
+ fprintf(stderr, "%s: ERROR: cannot stat path \"%s\". %s.\n",
+ progname, dest_path, strerror(errno));
+ goto out;
+ }
+ if (!S_ISDIR(st.st_mode)) {
+ ret = -EINVAL;
+ fprintf(stderr, "%s: ERROR: path \"%s\" is not a directory.\n",
+ progname, dest_path);
+ goto out;
+ }
+
+ did_far_rcv_init = 1;
+ ret = far_rcv_init(&crctx->frctx);
+ if (ret) {
+ fprintf(stderr, "%s: ERROR: far_rcv_init() failed. %s.\n",
+ progname, strerror(-ret));
+ goto out;
+ }
+
+ crctx->frctx.verbose = verbose;
+ crctx->frctx.support_xattrs = support_xattrs;
+ crctx->frctx.honor_end_cmd = honor_end_cmd;
+ crctx->frctx.ops.finish_subvol = commonfs_finish_subvol;
+ crctx->frctx.ops.subvol = commonfs_process_subvol;
+ crctx->frctx.ops.snapshot = commonfs_process_snapshot;
+
+ ret = far_rcv_mainloop(&crctx->frctx, stream_fd, dest_path);
+ if (ret) {
+ fprintf(stderr,
+ "%s: ERROR, far_rcv_mainloop() failed with %s.\n",
+ progname, strerror(-ret));
+ goto out;
+ }
+
+out:
+ free(name1);
+ free(name2);
+ if (did_far_rcv_init)
+ far_rcv_deinit(&crctx->frctx);
+ if (crctx && crctx->free_explicit_parent)
+ free((void *)crctx->explicit_parent);
+ if (stream_fd != -1)
+ close(stream_fd);
+ free(crctx);
+ exit(-ret);
+}
+
+static int commonfs_finish_subvol(struct far_rcv_ctx *frctx)
+{
+ if (frctx->free_current_base_path)
+ free((void *)frctx->current_base_path);
+ frctx->current_base_path = frctx->root_path;
+ frctx->free_current_base_path = 0;
+
+ return 0;
+}
+
+static int commonfs_process_subvol(struct far_rcv_ctx *frctx, const char *path,
+ const unsigned char *uuid, uint64_t ctransid)
+{
+ struct commonfs_rcv_ctx *crctx = (struct commonfs_rcv_ctx *)frctx;
+ int ret = 0;
+ const char *const orig_path = path;
+
+ if (crctx->explicit_dest_subvol) {
+ if (crctx->frctx.verbose)
+ fprintf(stderr, "Override destination, use \"%s\".\n",
+ crctx->explicit_dest_subvol);
+ path = crctx->explicit_dest_subvol;
+ }
+ if (crctx->frctx.free_current_base_path)
+ free((void *)crctx->frctx.current_base_path);
+ crctx->frctx.current_base_path =
+ far_rcv_path_cat(crctx->frctx.root_path, path);
+ crctx->frctx.free_current_base_path = 1;
+ if (!crctx->explicit_dest_subvol) {
+ /*
+ * The current_base_path will also be the parent of
+ * following snapshots in the mode where seperate
+ * directories are used (where not all subvolumes/
+ * snapshots are merged into a single directory).
+ */
+ if (crctx->free_explicit_parent)
+ free((void *)crctx->explicit_parent);
+ crctx->explicit_parent = strdup(crctx->frctx.current_base_path);
+ crctx->free_explicit_parent = 1;
+ }
+
+ fprintf(stderr, "At subvol %s\n", orig_path);
+ if (mkdir(crctx->frctx.current_base_path, 0777) < 0 &&
+ errno != EEXIST) {
+ ret = -errno;
+ fprintf(stderr, "ERROR: mkdir %s failed. %s\n",
+ crctx->frctx.current_base_path, strerror(-ret));
+ }
+
+ return ret;
+}
+
+static int commonfs_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 commonfs_rcv_ctx *crctx = (struct commonfs_rcv_ctx *)frctx;
+ int ret = 0;
+ const char *const orig_path = path;
+
+ if (crctx->explicit_dest_subvol) {
+ if (crctx->frctx.verbose)
+ fprintf(stderr, "Override destination, use \"%s\".\n",
+ crctx->explicit_dest_subvol);
+ path = crctx->explicit_dest_subvol;
+ }
+ if (crctx->frctx.free_current_base_path)
+ free((void *)crctx->frctx.current_base_path);
+ crctx->frctx.current_base_path =
+ far_rcv_path_cat(crctx->frctx.root_path, path);
+ crctx->frctx.free_current_base_path = 1;
+
+ fprintf(stderr, "At snapshot/subvol %s\n", orig_path);
+ if (mkdir(crctx->frctx.current_base_path, 0777) < 0) {
+ if (errno != EEXIST) {
+ ret = -errno;
+ fprintf(stderr, "ERROR: mkdir %s failed. %s\n",
+ crctx->frctx.current_base_path, strerror(-ret));
+ goto out;
+ }
+ } else if (!crctx->explicit_parent) {
+ /*
+ * if the parent is not specified on command line, expect that
+ * someone has prepared the directory.
+ */
+ fprintf(stderr, "ERROR: received an incremental snapshot but neither does the directory already\n");
+ fprintf(stderr, "exist, nor is a parent explicitely specified on command line!\n");
+ rmdir(crctx->frctx.current_base_path);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (crctx->explicit_parent) {
+ /*
+ * Copy parent directory into target directory.
+ *
+ * "cp -R" (at least the OpenBSD 4.9 version that I
+ * looked at) is able to handle holes and special
+ * files, but cannot detect hard links.
+ * "(cd src_dir && tar cf - .) | \
+ * (cd target_dir && tar xf -)" has the challenge to
+ * report errors.
+ */
+ char buf[PATH_MAX * 2 + 300];
+ const void *mem_to_free;
+ const char *src_dir;
+ const char *target_dir = crctx->frctx.current_base_path;
+
+ src_dir = crctx->explicit_parent;
+ while (isspace(*src_dir))
+ src_dir++;
+ if (*src_dir == '/') {
+ /* Danger: allow removing the leading root_path */
+ mem_to_free = NULL;
+ } else {
+ src_dir = far_rcv_path_cat(crctx->frctx.root_path,
+ src_dir);
+ mem_to_free = src_dir;
+ }
+
+ /*
+ * This should be able to handle sparse files, hard links,
+ * extented attributes (aka. xattrs) and special files.
+ * Note that _some_ versions of GNU tar use --xattrs to
+ * enable to add extended attributes while OI's tar specifies
+ * -@ for this purpose.
+ * The code below just expects that a GNU tar is located
+ * in the path under the name "tar" (for the '-S' option
+ * for efficient handling of sparse files).
+ */
+ ret = snprintf(buf, sizeof(buf),
+ "exit_value=1 && "
+ "name1=`mktemp` && "
+ "name2=`mktemp` && "
+ "echo 1+ > $name1 && "
+ "echo 1 > $name2 && "
+ "(cd %s && "
+ " (tar cf - %s %s . && "
+ " echo 0+ > $name1)) | "
+ "(cd %s && "
+ " (tar xf - && echo 0 > $name2)) && "
+ "exit_value="
+ "`tr -d \\\\\\\\n < $name1 | cat - $name2 |"
+ " bc`; "
+ "rm -f $name1 $name2; "
+ "exit $exit_value",
+ src_dir, frctx->support_xattrs ? "--xattrs" : "",
+ crctx->no_S_option_to_tar ? "" : "-S",
+ target_dir);
+ if (ret == sizeof(buf)) {
+ fprintf(stderr, "ERROR: the string is too long!\n");
+ free((void *)mem_to_free);
+ ret = -ENAMETOOLONG;
+ goto out;
+ }
+ if (crctx->frctx.verbose >= 2)
+ fprintf(stderr, "system(\"%s\")\n", buf);
+ ret = system(buf);
+ if (ret) {
+ fprintf(stderr, "ERROR: failed to copy %s to %s!\n",
+ src_dir, target_dir);
+ free((void *)mem_to_free);
+ ret = -EINVAL;
+ goto out;
+ }
+ free((void *)mem_to_free);
+ if (crctx->free_explicit_parent)
+ free((void *)crctx->explicit_parent);
+ crctx->explicit_parent = strdup(target_dir); /* for next one */
+ crctx->free_explicit_parent = 1;
+ }
+
+out:
+ return ret;
+}
diff --git a/far-rcv/far-rcv.c b/far-rcv/far-rcv.c
index 6eccc9e..926dc4c 100644
--- a/far-rcv/far-rcv.c
+++ b/far-rcv/far-rcv.c
@@ -289,6 +289,7 @@ int far_rcv_init(struct far_rcv_ctx *frctx)
memset(frctx, 0, sizeof(*frctx));
frctx->verbose = 0;
frctx->support_xattrs = 1;
+ frctx->honor_end_cmd = 1;
frctx->free_current_base_path = 0;
frctx->current_base_path = NULL;
frctx->write_fd = -1;
@@ -405,6 +406,8 @@ static int read_and_process_send_stream(struct far_rcv_ctx *frctx, int fd)
goto out;
if (ret) {
/* received end marker */
+ if (!frctx->honor_end_cmd)
+ ret = 0;
goto out;
}
}
diff --git a/far-rcv/far-rcv.h b/far-rcv/far-rcv.h
index 03de4d3..29cfb97 100644
--- a/far-rcv/far-rcv.h
+++ b/far-rcv/far-rcv.h
@@ -88,6 +88,7 @@ struct far_rcv_ctx {
int verbose;
int support_xattrs;
+ int honor_end_cmd;
int free_current_base_path;
const char *current_base_path;
int write_fd;
diff --git a/zfs-receive.c b/zfs-receive.c
new file mode 100644
index 0000000..104b410
--- /dev/null
+++ b/zfs-receive.c
@@ -0,0 +1,439 @@
+/*
+ * 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.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <libgen.h>
+#include <string.h>
+#include <strings.h>
+#include <assert.h>
+#include <unistd.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <uuid/uuid.h>
+#include <sys/ioctl.h>
+#include <sys/mnttab.h>
+#include <libzfs.h>
+
+#include "far-rcv/far-rcv.h"
+
+
+struct zfs_rcv_ctx {
+ struct far_rcv_ctx frctx; /* MUST be the first member */
+
+ libzfs_handle_t *zfs;
+
+ char *base_dataset; /* command line option -b */
+ char *cur_dataset; /* current dataset w/o @... */
+ char *cur_full_dataset; /* base_dataset + cur_dataset */
+ char *cur_snapshot; /* @..., including the @ in the front */
+
+ char *explicit_dest_subvol;
+};
+
+
+static void usage(const char *progname);
+int main(int argc, char **argv);
+static int zfs_rcv_finish_subvol(struct far_rcv_ctx *frctx);
+static int zfs_rcv_process_subvol(struct far_rcv_ctx *frctx, const char *path,
+ const unsigned char *uuid,
+ uint64_t ctransid);
+static int zfs_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 zfs_rcv_process_subvol_snapshot(struct far_rcv_ctx *frctx,
+ const char *path, int is_snapshot);
+
+
+static void usage(const char *progname)
+{
+ fprintf(stderr, "%s [-ve] [-f <infile>] [-d <dest>] <dataset_base>\n",
+ progname);
+ char *more[] = {
+"Receive snapshots from stdin.",
+"Receives one or more snapshots that were previously sent with btrfs send,",
+"zfs send -F or a similar sender for the FAR stream format.",
+"ZFS filesystems are created and mounted as required. The received data is",
+"stored below the ZFS pool or dataset <dataset_base>.",
+"-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.",
+"-d <dest> With <dest> either being filesystem@snapshot or only the name",
+" of a filesystem, this option overrides the name of received",
+" filesystems and optionally also the name of the first received",
+" snapshot.",
+NULL
+ };
+ char **pp = more;
+
+ while (*pp) {
+ fprintf(stderr, "%s\n", *pp);
+ pp++;
+ }
+}
+
+int main(int argc, char **argv)
+{
+ int c;
+ char *fromfile = NULL;
+ int stream_fd = -1;
+ int ret = 0;
+ int verbose = 0;
+ const char *progname;
+ int did_far_rcv_init = 0;
+ struct zfs_rcv_ctx *zrctx = NULL;
+ int support_xattrs = 1;
+ int honor_end_cmd = 0;
+
+ progname = basename(argv[0]);
+ zrctx = calloc(1, sizeof(*zrctx));
+ if (!zrctx) {
+ ret = -ENOMEM;
+ fprintf(stderr, "%s: ERROR: malloc() failed.\n", progname);
+ goto out;
+ }
+
+ zrctx->zfs = NULL;
+ zrctx->base_dataset = NULL;
+ zrctx->cur_dataset = NULL;
+ zrctx->cur_full_dataset = NULL;
+ zrctx->cur_snapshot = NULL;
+ zrctx->explicit_dest_subvol = NULL;
+
+ while ((c = getopt(argc, argv, "vexXsSf:d:")) != -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':
+ zrctx->explicit_dest_subvol = optarg;
+ break;
+ case '?':
+ default:
+ fprintf(stderr, "%s: ERROR: args invalid.\n", progname);
+ usage(progname);
+ ret = -EINVAL;
+ goto out;
+ }
+ }
+
+ 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 <dataset_base>.\n", progname);
+ usage(progname);
+ goto out;
+ }
+ zrctx->base_dataset = argv[optind];
+
+ 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 */
+ }
+
+ zrctx->zfs = libzfs_init();
+
+ did_far_rcv_init = 1;
+ ret = far_rcv_init(&zrctx->frctx);
+ if (ret) {
+ fprintf(stderr, "%s: ERROR: far_rcv_init() failed. %s.\n",
+ progname, strerror(-ret));
+ goto out;
+ }
+
+ zrctx->frctx.verbose = verbose;
+ zrctx->frctx.support_xattrs = support_xattrs;
+ zrctx->frctx.honor_end_cmd = honor_end_cmd;
+ zrctx->frctx.ops.finish_subvol = zfs_rcv_finish_subvol;
+ zrctx->frctx.ops.subvol = zfs_rcv_process_subvol;
+ zrctx->frctx.ops.snapshot = zfs_rcv_process_snapshot;
+
+ ret = far_rcv_mainloop(&zrctx->frctx, stream_fd, "./");
+ 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(&zrctx->frctx);
+ if (stream_fd >= 0)
+ close(stream_fd);
+ if (zrctx) {
+ free(zrctx->cur_dataset);
+ free(zrctx->cur_full_dataset);
+ free(zrctx->cur_snapshot);
+ }
+ if (zrctx->zfs)
+ libzfs_fini(zrctx->zfs);
+ free(zrctx);
+ exit(-ret);
+}
+
+static int zfs_rcv_finish_subvol(struct far_rcv_ctx *frctx)
+{
+ struct zfs_rcv_ctx *zrctx = (struct zfs_rcv_ctx *)frctx;
+ int ret = 0;
+
+ if (zrctx->cur_dataset == NULL)
+ return 0;
+
+ if (zrctx->cur_snapshot) {
+ int l1 = strlen(zrctx->cur_full_dataset);
+ int l2 = strlen(zrctx->cur_snapshot);
+ char *path = malloc(l1 + l2 + 1);
+
+ strcpy(path, zrctx->cur_full_dataset);
+ strcat(path, zrctx->cur_snapshot);
+ ret = zfs_snapshot(zrctx->zfs, path, 1, NULL);
+ free(zrctx->cur_snapshot);
+ zrctx->cur_snapshot = NULL;
+ }
+
+ free(zrctx->cur_dataset);
+ zrctx->cur_dataset = NULL;
+ free(zrctx->cur_full_dataset);
+ zrctx->cur_full_dataset = NULL;
+
+ return ret;
+}
+
+static int zfs_rcv_process_subvol(struct far_rcv_ctx *frctx, const char *path,
+ const unsigned char *uuid, uint64_t ctransid)
+{
+ return zfs_rcv_process_subvol_snapshot(frctx, path, 0);
+}
+
+static int zfs_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)
+{
+ return zfs_rcv_process_subvol_snapshot(frctx, path, 1);
+}
+
+static int zfs_rcv_process_subvol_snapshot(struct far_rcv_ctx *frctx,
+ const char *path, int is_snapshot)
+{
+ struct zfs_rcv_ctx *zrctx = (struct zfs_rcv_ctx *)frctx;
+ int ret;
+ char *p;
+ zfs_handle_t *zfh = NULL;
+ char *mounted_where = NULL;
+
+ ret = zfs_rcv_finish_subvol(frctx);
+ if (ret < 0)
+ goto out;
+
+ assert(zrctx->cur_dataset == NULL);
+ assert(zrctx->cur_full_dataset == NULL);
+ assert(zrctx->cur_snapshot == NULL);
+ if (zrctx->explicit_dest_subvol) {
+ if (zrctx->frctx.verbose)
+ fprintf(stderr, "Override destination, use \"%s\".\n",
+ zrctx->explicit_dest_subvol);
+ zrctx->cur_dataset = strdup(zrctx->explicit_dest_subvol);
+ p = strrchr(zrctx->explicit_dest_subvol, '@');
+ /*
+ * use (optional) snapshot name only for the first one,
+ * all following ones take the snapshot name out of the
+ * transmitted name, and use the explicit name only for
+ * the name of the filesystem
+ */
+ if (p) {
+ *p = '\0';
+ } else {
+ p = strrchr(path, '@');
+ if (p)
+ zrctx->cur_snapshot = strdup(p);
+ }
+ } else {
+ zrctx->cur_dataset = strdup(path);
+ }
+
+ p = strrchr(zrctx->cur_dataset, '@');
+ if (p) {
+ zrctx->cur_snapshot = strdup(p);
+ *p = '\0';
+ }
+
+ if (frctx->free_current_base_path)
+ free((void *)frctx->current_base_path);
+ frctx->free_current_base_path = 0;
+
+ zrctx->cur_full_dataset = far_rcv_path_cat(zrctx->base_dataset,
+ zrctx->cur_dataset);
+ if (is_snapshot) {
+ if (zrctx->cur_snapshot)
+ fprintf(stderr, "At snapshot %s%s.\n",
+ zrctx->cur_dataset, zrctx->cur_snapshot);
+ else /* this will leave with an error message later */
+ fprintf(stderr, "At snapshot %s.\n",
+ zrctx->cur_dataset);
+
+ } else {
+ fprintf(stderr, "At subvol %s.\n", zrctx->cur_dataset);
+ }
+
+ if (is_snapshot) {
+ /*
+ * This means nothing more than that this is an incremental
+ * transfer.
+ * Note that the term "snapshot" in the FAR stream format
+ * stands for incremental transfers while "subvolume" stands
+ * for full transfers. This is not related to ZFS filesystems
+ * and snapshots.
+ *
+ * For incremental transfers, it is enforced that the
+ * target is a snapshot, i.e. that the information is
+ * there which name shall be used for the snapshot.
+ */
+ if (!zrctx->cur_snapshot) {
+ ret = -EINVAL;
+ fprintf(stderr,
+ "ERROR: no snapshot name \"@...\" for %s.\n",
+ zrctx->cur_dataset);
+ goto out;
+ }
+
+ /* check that filesystem is existent */
+ zfh = zfs_open(zrctx->zfs, zrctx->cur_full_dataset,
+ ZFS_TYPE_FILESYSTEM);
+ if (!zfh) {
+ ret = -libzfs_errno(zrctx->zfs);
+ fprintf(stderr,
+ "ERROR: filesystem %s cannot be opened, libzfs_errno = %d, %s.\n",
+ zrctx->cur_full_dataset, -ret,
+ libzfs_error_description(zrctx->zfs));
+ goto out;
+ }
+ } else {
+ ret = zfs_create(zrctx->zfs, zrctx->cur_full_dataset,
+ ZFS_TYPE_FILESYSTEM, NULL);
+ if (ret && libzfs_errno(zrctx->zfs) != EZFS_EXISTS) {
+ ret = -libzfs_errno(zrctx->zfs);
+ fprintf(stderr,
+ "ERROR: create filesystem %s fails with libzfs_errno %d, %s.\n",
+ zrctx->cur_full_dataset, -ret,
+ libzfs_error_description(zrctx->zfs));
+ goto out;
+ } else if (ret && libzfs_errno(zrctx->zfs) == EZFS_EXISTS) {
+ /*
+ * This won't work as expected in most cases unless
+ * the existent filesystem is empty. Enforce that
+ * only incrementally sent data can be applied to
+ * existent filesystems. What should we do otherwise,
+ * remove everything in the filesystem at the
+ * beginning, then apply the received data, than
+ * create a snapshot, afterwards do what?
+ */
+ fprintf(stderr,
+ "ERROR: filesystem %s already exists, only incrementally sent data is supported!\n",
+ zrctx->cur_full_dataset);
+ goto out;
+ }
+
+ zfh = zfs_open(zrctx->zfs, zrctx->cur_full_dataset,
+ ZFS_TYPE_FILESYSTEM);
+ if (!zfh) {
+ ret = -libzfs_errno(zrctx->zfs);
+ fprintf(stderr,
+ "ERROR: filesystem %s cannot be opened, libzfs_errno = %d, %s.\n",
+ zrctx->cur_full_dataset, -ret,
+ libzfs_error_description(zrctx->zfs));
+ goto out;
+ }
+ }
+
+ /* need to mount filesystem if it not there as expected */
+ if (!zfs_is_mounted(zfh, &mounted_where)) {
+ ret = zfs_mount(zfh, NULL, 0);
+ if (ret) {
+ ret = -libzfs_errno(zrctx->zfs);
+ fprintf(stderr,
+ "ERROR: filesystem %s cannot be mounted, libzfs_errno = %d, %s.\n",
+ zrctx->cur_full_dataset, -ret,
+ libzfs_error_description(zrctx->zfs));
+ goto out;
+ }
+ if (!zfs_is_mounted(zfh, &mounted_where)) {
+ fprintf(stderr,
+ "ERROR: failed to mount filesystem %s.\n",
+ zrctx->cur_full_dataset);
+ ret = EZFS_MOUNTFAILED;
+ goto out;
+ }
+ }
+
+ frctx->current_base_path = mounted_where;
+ mounted_where = NULL;
+ frctx->free_current_base_path = 1;
+
+out:
+ free(mounted_where);
+ if (zfh != NULL)
+ zfs_close(zfh);
+ return ret;
+}