blob: 104b410884a7555c25d355977eb3fee899ea88b1 [file] [log] [blame]
/*
* Copyright (C) 2012 Alexander Block.
* Copyright (C) 2012, 2013 STRATO AG.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#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;
}