| /* |
| * 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; |
| } |