diff options
author | Stefan Behrens <sbehrens@giantdisaster.de> | 2013-03-26 17:33:10 +0100 |
---|---|---|
committer | Stefan Behrens <sbehrens@giantdisaster.de> | 2013-04-25 11:17:48 +0200 |
commit | d3fde0a7c8496cd0c8c386871f6481c93bae3609 (patch) | |
tree | 217198effb85c547d62c5909e06a41a164715882 | |
parent | cb0e413a80c9a9347159a4ecae31acfd2ea9f87c (diff) | |
download | far-progs-d3fde0a7c8496cd0c8c386871f6481c93bae3609.tar.gz |
far-progs: add applications that use the far-lib
Signed-off-by: Stefan Behrens <sbehrens@giantdisaster.de>
-rw-r--r-- | Makefile | 70 | ||||
-rw-r--r-- | btrfs-receive.c | 778 | ||||
-rw-r--r-- | commonfs-receive.c | 476 | ||||
-rw-r--r-- | far-rcv/far-rcv.c | 3 | ||||
-rw-r--r-- | far-rcv/far-rcv.h | 1 | ||||
-rw-r--r-- | zfs-receive.c | 439 |
6 files changed, 1759 insertions, 8 deletions
@@ -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; +} |