* Boston, MA 021110-1307, USA.
*/
-#define _GNU_SOURCE
#include "kerncompat.h"
#include <libgen.h>
#include <mntent.h>
#include <assert.h>
-
+#include <getopt.h>
#include <uuid/uuid.h>
+#include <limits.h>
#include "ctree.h"
#include "ioctl.h"
#include "commands.h"
#include "list.h"
+#include "utils.h"
#include "send.h"
#include "send-utils.h"
-static int g_verbose = 0;
+/*
+ * Default is 1 for historical reasons, changing may break scripts that expect
+ * the 'At subvol' message.
+ */
+static int g_verbose = 1;
struct btrfs_send {
int send_fd;
struct subvol_uuid_search sus;
};
-int find_mount_root(const char *path, char **mount_root)
-{
- FILE *mnttab;
- int fd;
- struct mntent *ent;
- int len;
- int ret;
- 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");
- if (!mnttab)
- return -errno;
-
- 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);
-
- if (!longest_match) {
- fprintf(stderr,
- "ERROR: Failed to find mount root for path %s.\n",
- path);
- return -ENOENT;
- }
-
- ret = 0;
- *mount_root = realpath(longest_match, NULL);
- if (!*mount_root)
- ret = -errno;
-
- free(longest_match);
- return ret;
-}
-
static int get_root_id(struct btrfs_send *s, const char *path, u64 *root_id)
{
struct subvol_info *si;
parent2 = subvol_uuid_search(&s->sus, s->clone_sources[i], NULL,
0, NULL, subvol_search_by_root_id);
- assert(parent2);
+ if (!parent2) {
+ ret = -ENOENT;
+ goto out;
+ }
tmp = parent2->ctransid - parent->ctransid;
if (tmp < 0)
tmp *= -1;
return ret;
}
-static void add_clone_source(struct btrfs_send *s, u64 root_id)
+static int add_clone_source(struct btrfs_send *s, u64 root_id)
{
+ void *tmp;
+
+ tmp = s->clone_sources;
s->clone_sources = realloc(s->clone_sources,
sizeof(*s->clone_sources) * (s->clone_sources_count + 1));
+
+ if (!s->clone_sources) {
+ free(tmp);
+ return -ENOMEM;
+ }
s->clone_sources[s->clone_sources_count++] = root_id;
+
+ return 0;
}
static int write_buf(int fd, const void *buf, int size)
ret = write(fd, (char*)buf + pos, size - pos);
if (ret < 0) {
ret = -errno;
- fprintf(stderr, "ERROR: failed to dump stream. %s",
- strerror(-ret));
+ error("failed to dump stream: %s", strerror(-ret));
goto out;
}
if (!ret) {
ret = -EIO;
- fprintf(stderr, "ERROR: failed to dump stream. %s",
- strerror(-ret));
+ error("failed to dump stream: %s", strerror(-ret));
goto out;
}
pos += ret;
readed = read(s->send_fd, buf, sizeof(buf));
if (readed < 0) {
ret = -errno;
- fprintf(stderr, "ERROR: failed to read stream from "
- "kernel. %s\n", strerror(-ret));
+ error("failed to read stream from kernel: %s\n",
+ strerror(-ret));
goto out;
}
if (!readed) {
return ERR_PTR(ret);
}
-static int do_send(struct btrfs_send *send, u64 root_id, u64 parent_root_id,
- int is_first_subvol, int is_last_subvol)
+static int do_send(struct btrfs_send *send, u64 parent_root_id,
+ int is_first_subvol, int is_last_subvol, char *subvol,
+ u64 flags)
{
int ret;
pthread_t t_read;
- pthread_attr_t t_attr;
struct btrfs_ioctl_send_args io_send;
- struct subvol_info *si;
void *t_err = NULL;
int subvol_fd = -1;
int pipefd[2] = {-1, -1};
- si = subvol_uuid_search(&send->sus, root_id, NULL, 0, NULL,
- subvol_search_by_root_id);
- if (!si) {
- ret = -ENOENT;
- fprintf(stderr, "ERROR: could not find subvol info for %llu",
- root_id);
- goto out;
- }
-
- subvol_fd = openat(send->mnt_fd, si->path, O_RDONLY | O_NOATIME);
+ subvol_fd = openat(send->mnt_fd, subvol, O_RDONLY | O_NOATIME);
if (subvol_fd < 0) {
ret = -errno;
- fprintf(stderr, "ERROR: open %s failed. %s\n", si->path,
- strerror(-ret));
+ error("cannot open %s: %s", subvol, strerror(-ret));
goto out;
}
- ret = pthread_attr_init(&t_attr);
-
ret = pipe(pipefd);
if (ret < 0) {
ret = -errno;
- fprintf(stderr, "ERROR: pipe failed. %s\n", strerror(-ret));
+ error("pipe failed: %s", strerror(-ret));
goto out;
}
send->send_fd = pipefd[0];
if (!ret)
- ret = pthread_create(&t_read, &t_attr, dump_thread,
+ ret = pthread_create(&t_read, NULL, dump_thread,
send);
if (ret) {
ret = -ret;
- fprintf(stderr, "ERROR: thread setup failed: %s\n",
- strerror(-ret));
+ error("thread setup failed: %s", strerror(-ret));
goto out;
}
+ io_send.flags = flags;
io_send.clone_sources = (__u64*)send->clone_sources;
io_send.clone_sources_count = send->clone_sources_count;
io_send.parent_root = parent_root_id;
if (!is_last_subvol)
io_send.flags |= BTRFS_SEND_FLAG_OMIT_END_CMD;
ret = ioctl(subvol_fd, BTRFS_IOC_SEND, &io_send);
- if (ret) {
+ if (ret < 0) {
ret = -errno;
- fprintf(stderr, "ERROR: send ioctl failed with %d: %s\n", ret,
- strerror(-ret));
+ error("send ioctl failed with %d: %s", ret, strerror(-ret));
if (ret == -EINVAL && (!is_first_subvol || !is_last_subvol))
fprintf(stderr,
"Try upgrading your kernel or don't use -e.\n");
goto out;
}
- if (g_verbose > 0)
+ if (g_verbose > 1)
fprintf(stderr, "BTRFS_IOC_SEND returned %d\n", ret);
- if (g_verbose > 0)
+ if (g_verbose > 1)
fprintf(stderr, "joining genl thread\n");
close(pipefd[1]);
ret = pthread_join(t_read, &t_err);
if (ret) {
ret = -ret;
- fprintf(stderr, "ERROR: pthread_join failed: %s\n",
- strerror(-ret));
+ error("pthread_join failed: %s", strerror(-ret));
goto out;
}
if (t_err) {
ret = (long int)t_err;
- fprintf(stderr, "ERROR: failed to process send stream, ret=%ld "
- "(%s)\n", (long int)t_err, strerror(-ret));
+ error("failed to process send stream, ret=%ld (%s)",
+ (long int)t_err, strerror(-ret));
goto out;
}
- pthread_attr_destroy(&t_attr);
-
ret = 0;
out:
close(pipefd[0]);
if (pipefd[1] != -1)
close(pipefd[1]);
- if (si) {
- free(si->path);
- free(si);
- }
return ret;
}
-char *get_subvol_name(char *mnt, char *full_path)
-{
- int len = strlen(mnt);
- if (!len)
- return full_path;
- if (mnt[len - 1] != '/')
- len += 1;
-
- return full_path + len;
-}
-
static int init_root_path(struct btrfs_send *s, const char *subvol)
{
int ret = 0;
ret = find_mount_root(subvol, &s->root_path);
if (ret < 0) {
+ error("failed to determine mount point for %s: %s",
+ subvol, strerror(-ret));
+ ret = -EINVAL;
+ goto out;
+ }
+ if (ret > 0) {
+ error("%s doesn't belong to btrfs mount point", subvol);
ret = -EINVAL;
- fprintf(stderr, "ERROR: failed to determine mount point "
- "for %s\n", subvol);
goto out;
}
s->mnt_fd = open(s->root_path, O_RDONLY | O_NOATIME);
if (s->mnt_fd < 0) {
ret = -errno;
- fprintf(stderr, "ERROR: can't open '%s': %s\n", s->root_path,
- strerror(-ret));
+ error("cannot open '%s': %s", s->root_path, strerror(-ret));
goto out;
}
ret = subvol_uuid_search_init(s->mnt_fd, &s->sus);
if (ret < 0) {
- fprintf(stderr, "ERROR: failed to initialize subvol search. "
- "%s\n", strerror(-ret));
+ error("failed to initialize subvol search: %s",
+ strerror(-ret));
goto out;
}
fd = openat(s->mnt_fd, subvol, O_RDONLY | O_NOATIME);
if (fd < 0) {
ret = -errno;
- fprintf(stderr, "ERROR: failed to open %s. %s\n",
- subvol, strerror(-ret));
+ error("cannot open %s: %s", subvol, strerror(-ret));
goto out;
}
ret = ioctl(fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags);
if (ret < 0) {
ret = -errno;
- fprintf(stderr, "ERROR: failed to get flags for subvolume. "
- "%s\n", strerror(-ret));
+ error("failed to get flags for subvolume %s: %s",
+ subvol, strerror(-ret));
goto out;
}
int cmd_send(int argc, char **argv)
{
char *subvol = NULL;
- int c;
int ret;
- char *outname = NULL;
+ char outname[PATH_MAX];
struct btrfs_send send;
u32 i;
char *mount_root = NULL;
char *snapshot_parent = NULL;
- u64 root_id;
+ u64 root_id = 0;
u64 parent_root_id = 0;
int full_send = 1;
int new_end_cmd_semantic = 0;
+ u64 send_flags = 0;
memset(&send, 0, sizeof(send));
send.dump_fd = fileno(stdout);
+ outname[0] = 0;
+
+ while (1) {
+ enum { GETOPT_VAL_SEND_NO_DATA = 256 };
+ static const struct option long_options[] = {
+ { "verbose", no_argument, NULL, 'v' },
+ { "quiet", no_argument, NULL, 'q' },
+ { "no-data", no_argument, NULL, GETOPT_VAL_SEND_NO_DATA }
+ };
+ int c = getopt_long(argc, argv, "vqec:f:i:p:", long_options, NULL);
+
+ if (c < 0)
+ break;
- while ((c = getopt(argc, argv, "vec:f:i:p:")) != -1) {
switch (c) {
case 'v':
g_verbose++;
break;
+ case 'q':
+ g_verbose = 0;
+ break;
case 'e':
new_end_cmd_semantic = 1;
break;
subvol = realpath(optarg, NULL);
if (!subvol) {
ret = -errno;
- fprintf(stderr, "ERROR: realpath %s failed. "
- "%s\n", optarg, strerror(-ret));
+ error("realpath %s failed: %s\n", optarg, strerror(-ret));
goto out;
}
if (ret < 0)
goto out;
- ret = get_root_id(&send, get_subvol_name(send.root_path, subvol),
- &root_id);
+ ret = get_root_id(&send,
+ subvol_strip_mountpoint(send.root_path, subvol),
+ &root_id);
+ if (ret < 0) {
+ error("cannot resolve rootid for %s", subvol);
+ goto out;
+ }
+
+ ret = is_subvol_ro(&send, subvol);
+ if (ret < 0)
+ goto out;
+ if (!ret) {
+ ret = -EINVAL;
+ error("cloned subvolume %s is not read-only", subvol);
+ goto out;
+ }
+
+ ret = add_clone_source(&send, root_id);
if (ret < 0) {
- fprintf(stderr, "ERROR: could not resolve "
- "root_id for %s\n", subvol);
+ error("cannot add clone source: %s", strerror(-ret));
goto out;
}
- add_clone_source(&send, root_id);
subvol_uuid_search_finit(&send.sus);
free(subvol);
subvol = NULL;
full_send = 0;
break;
case 'f':
- outname = optarg;
+ if (arg_copy_path(outname, optarg, sizeof(outname))) {
+ error("output file path too long (%zu)", strlen(optarg));
+ ret = 1;
+ goto out;
+ }
break;
case 'p':
if (snapshot_parent) {
- fprintf(stderr, "ERROR: you cannot have more than one parent (-p)\n");
+ error("you cannot have more than one parent (-p)");
ret = 1;
goto out;
}
snapshot_parent = realpath(optarg, NULL);
if (!snapshot_parent) {
ret = -errno;
- fprintf(stderr, "ERROR: realpath %s failed. "
- "%s\n", optarg, strerror(-ret));
+ error("realpath %s failed: %s", optarg, strerror(-ret));
goto out;
}
+
+ ret = is_subvol_ro(&send, snapshot_parent);
+ if (ret < 0)
+ goto out;
+ if (!ret) {
+ ret = -EINVAL;
+ error("parent subvolume %s is not read-only",
+ snapshot_parent);
+ goto out;
+ }
+
full_send = 0;
break;
case 'i':
- fprintf(stderr,
- "ERROR: -i was removed, use -c instead\n");
+ error("option -i was removed, use -c instead");
ret = 1;
goto out;
+ case GETOPT_VAL_SEND_NO_DATA:
+ send_flags |= BTRFS_SEND_FLAG_NO_FILE_DATA;
+ break;
case '?':
default:
- fprintf(stderr, "ERROR: send args invalid.\n");
+ error("send arguments invalid");
ret = 1;
goto out;
}
}
- if (optind == argc) {
- fprintf(stderr, "ERROR: send needs path to snapshot\n");
- ret = 1;
- goto out;
- }
+ if (check_argc_min(argc - optind, 1))
+ usage(cmd_send_usage);
- if (outname != NULL) {
+ if (outname[0]) {
send.dump_fd = creat(outname, 0600);
if (send.dump_fd == -1) {
ret = -errno;
- fprintf(stderr, "ERROR: can't create '%s': %s\n",
- outname, strerror(-ret));
+ error("cannot create '%s': %s", outname, strerror(-ret));
goto out;
}
}
if (isatty(send.dump_fd)) {
- fprintf(stderr,
- "ERROR: not dumping send stream into a terminal, "
- "redirect it into a file\n");
+ error(
+ "not dumping send stream into a terminal, redirect it into a file");
ret = 1;
goto out;
}
subvol = realpath(argv[optind], NULL);
if (!subvol) {
ret = -errno;
- fprintf(stderr, "ERROR: unable to resolve %s\n", argv[optind]);
+ error("unable to resolve %s", argv[optind]);
goto out;
}
if (snapshot_parent != NULL) {
ret = get_root_id(&send,
- get_subvol_name(send.root_path, snapshot_parent),
- &parent_root_id);
+ subvol_strip_mountpoint(send.root_path, snapshot_parent),
+ &parent_root_id);
if (ret < 0) {
- fprintf(stderr, "ERROR: could not resolve root_id "
- "for %s\n", snapshot_parent);
+ error("could not resolve rootid for %s", snapshot_parent);
goto out;
}
- add_clone_source(&send, parent_root_id);
+ ret = add_clone_source(&send, parent_root_id);
+ if (ret < 0) {
+ error("cannot add clone source: %s", strerror(-ret));
+ goto out;
+ }
}
for (i = optind; i < argc; i++) {
subvol = realpath(argv[i], NULL);
if (!subvol) {
ret = -errno;
- fprintf(stderr, "ERROR: unable to resolve %s\n", argv[i]);
+ error("unable to resolve %s", argv[i]);
goto out;
}
ret = find_mount_root(subvol, &mount_root);
if (ret < 0) {
- fprintf(stderr, "ERROR: find_mount_root failed on %s: "
- "%s\n", subvol,
+ error("find_mount_root failed on %s: %s", subvol,
strerror(-ret));
goto out;
}
+ if (ret > 0) {
+ error("%s does not belong to btrfs mount point",
+ subvol);
+ ret = -EINVAL;
+ goto out;
+ }
if (strcmp(send.root_path, mount_root) != 0) {
ret = -EINVAL;
- fprintf(stderr, "ERROR: all subvols must be from the "
- "same fs.\n");
+ error("all subvolumes must be from the same filesystem");
goto out;
}
free(mount_root);
goto out;
if (!ret) {
ret = -EINVAL;
- fprintf(stderr, "ERROR: %s is not read-only.\n",
- subvol);
+ error("subvolume %s is not read-only", subvol);
goto out;
}
}
+ if ((send_flags & BTRFS_SEND_FLAG_NO_FILE_DATA) && g_verbose > 1)
+ if (g_verbose > 1)
+ fprintf(stderr, "Mode NO_FILE_DATA enabled\n");
+
for (i = optind; i < argc; i++) {
int is_first_subvol;
int is_last_subvol;
free(subvol);
subvol = argv[i];
- fprintf(stderr, "At subvol %s\n", subvol);
+ if (g_verbose > 0)
+ fprintf(stderr, "At subvol %s\n", subvol);
subvol = realpath(subvol, NULL);
if (!subvol) {
ret = -errno;
- fprintf(stderr, "ERROR: realpath %s failed. "
- "%s\n", argv[i], strerror(-ret));
- goto out;
- }
-
- ret = get_root_id(&send, get_subvol_name(send.root_path, subvol),
- &root_id);
- if (ret < 0) {
- fprintf(stderr, "ERROR: could not resolve root_id "
- "for %s\n", subvol);
+ error("realpath %s failed: %s", argv[i], strerror(-ret));
goto out;
}
if (!full_send && !parent_root_id) {
ret = find_good_parent(&send, root_id, &parent_root_id);
if (ret < 0) {
- fprintf(stderr, "ERROR: parent determination failed for %lld\n",
+ error("parent determination failed for %lld",
root_id);
goto out;
}
goto out;
if (!ret) {
ret = -EINVAL;
- fprintf(stderr, "ERROR: %s is not read-only.\n",
- subvol);
+ error("subvolume %s is not read-only", subvol);
goto out;
}
is_first_subvol = 1;
is_last_subvol = 1;
}
- ret = do_send(&send, root_id, parent_root_id,
- is_first_subvol, is_last_subvol);
+ ret = do_send(&send, parent_root_id, is_first_subvol,
+ is_last_subvol, subvol, send_flags);
if (ret < 0)
goto out;
- /* done with this subvol, so add it to the clone sources */
- add_clone_source(&send, root_id);
+ if (!full_send) {
+ /* done with this subvol, so add it to the clone sources */
+ ret = add_clone_source(&send, root_id);
+ if (ret < 0) {
+ error("cannot add clone source: %s", strerror(-ret));
+ goto out;
+ }
+ }
parent_root_id = 0;
- full_send = 0;
}
ret = 0;
}
const char * const cmd_send_usage[] = {
- "btrfs send [-ve] [-p <parent>] [-c <clone-src>] [-f <outfile>] <subvol>",
- "Send the subvolume to stdout.",
- "Sends the subvolume specified by <subvol> to stdout.",
+ "btrfs send [-ve] [-p <parent>] [-c <clone-src>] [-f <outfile>] <subvol> [<subvol>...]",
+ "Send the subvolume(s) to stdout.",
+ "Sends the subvolume(s) specified by <subvol> to stdout.",
+ "<subvol> should be read-only here.",
"By default, this will send the whole subvolume. To do an incremental",
"send, use '-p <parent>'. If you want to allow btrfs to clone from",
"any additional local snapshots, use '-c <clone-src>' (multiple times",
"which case 'btrfs send' will determine a suitable parent among the",
"clone sources itself.",
"\n",
- "-v Enable verbose debug output. Each occurrence of",
- " this option increases the verbose level more.",
"-e If sending multiple subvols at once, use the new",
" format and omit the end-cmd between the subvols.",
"-p <parent> Send an incremental stream from <parent> to",
"-f <outfile> Output is normally written to stdout. To write to",
" a file, use this option. An alternative would be to",
" use pipes.",
+ "--no-data send in NO_FILE_DATA mode, Note: the output stream",
+ " does not contain any file data and thus cannot be used",
+ " to transfer changes. This mode is faster and useful to",
+ " show the differences in metadata.",
+ "-v|--verbose enable verbose output to stderr, each occurrence of",
+ " this option increases verbosity",
+ "-q|--quiet suppress all messages, except errors",
NULL
};