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