#define _GNU_SOURCE
+#include "kerncompat.h"
+
#include <unistd.h>
#include <stdint.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
+#include <libgen.h>
+#include <mntent.h>
#include <uuid/uuid.h>
int find_mount_root(const char *path, char **mount_root)
{
- int ret;
- char cur[BTRFS_PATH_NAME_MAX];
- char fsid[BTRFS_FSID_SIZE];
+ FILE *mnttab;
int fd;
- struct stat st;
- int pos;
- char *tmp;
-
- struct btrfs_ioctl_fs_info_args args;
+ struct mntent *ent;
+ int len;
+ int longest_matchlen = 0;
+ char *longest_match = NULL;
fd = open(path, O_RDONLY | O_NOATIME);
- if (fd < 0) {
- ret = -errno;
- goto out;
- }
-
- ret = fstat(fd, &st);
- if (fd < 0) {
- ret = -errno;
- goto out;
- }
- if (!S_ISDIR(st.st_mode)) {
- ret = -ENOTDIR;
- goto out;
- }
-
- ret = ioctl(fd, BTRFS_IOC_FS_INFO, &args);
- if (fd < 0) {
- ret = -errno;
- goto out;
- }
- memcpy(fsid, args.fsid, BTRFS_FSID_SIZE);
+ if (fd < 0)
+ return -errno;
close(fd);
- fd = -1;
-
- strcpy(cur, path);
- while (1) {
- tmp = strrchr(cur, '/');
- if (!tmp)
- break;
- if (tmp == cur)
- break;
- pos = tmp - cur;
- cur[pos] = 0;
- fd = open(cur, O_RDONLY | O_NOATIME);
- if (fd < 0) {
- ret = -errno;
- goto out;
- }
-
- ret = ioctl(fd, BTRFS_IOC_FS_INFO, &args);
- close(fd);
- fd = -1;
- if (ret < 0) {
- cur[pos] = '/';
- break;
- }
- if (memcmp(fsid, args.fsid, BTRFS_FSID_SIZE) != 0) {
- cur[pos] = '/';
- break;
+ mnttab = fopen("/etc/mtab", "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);
+ }
}
}
- ret = 0;
- *mount_root = realpath(cur, NULL);
+ *mount_root = realpath(longest_match, NULL);
+ free(longest_match);
-out:
- if (fd != -1)
- close(fd);
- return ret;
+ return 0;
}
static int get_root_id(struct btrfs_send *s, const char *path, u64 *root_id)
return ERR_PTR(ret);
}
-static int do_send(struct btrfs_send *send, u64 root_id, u64 parent_root)
+static int do_send(struct btrfs_send *send, u64 root_id, u64 parent_root_id)
{
int ret;
pthread_t t_read;
goto out;
}
+ memset(&io_send, 0, sizeof(io_send));
io_send.send_fd = pipefd[1];
send->send_fd = pipefd[0];
io_send.clone_sources = (__u64*)send->clone_sources;
io_send.clone_sources_count = send->clone_sources_count;
- io_send.parent_root = parent_root;
+ io_send.parent_root = parent_root_id;
ret = ioctl(subvol_fd, BTRFS_IOC_SEND, &io_send);
if (ret) {
ret = -errno;
static const char *get_subvol_name(struct btrfs_send *s, const char *full_path)
{
- return full_path + strlen(s->root_path) + 1;
+ int len = strlen(s->root_path);
+ if (!len)
+ return full_path;
+ if (s->root_path[len - 1] != '/')
+ len += 1;
+
+ return full_path + len;
}
static int init_root_path(struct btrfs_send *s, const char *subvol)
int cmd_send_start(int argc, char **argv)
{
char *subvol = NULL;
- char c;
+ int c;
int ret;
char *outname = NULL;
struct btrfs_send send;
char *snapshot_parent = NULL;
u64 root_id;
u64 parent_root_id = 0;
+ int full_send = 1;
memset(&send, 0, sizeof(send));
send.dump_fd = fileno(stdout);
- while ((c = getopt(argc, argv, "vf:i:p:")) != -1) {
+ if (isatty(send.dump_fd)) {
+ fprintf(stderr, "ERROR: not dumping send stream into a terminal, redirect it into a file\n");
+ return 1;
+ }
+
+ while ((c = getopt(argc, argv, "vc:f:i:p:")) != -1) {
switch (c) {
case 'v':
g_verbose++;
break;
- case 'i': {
+ case 'c':
subvol = realpath(optarg, NULL);
if (!subvol) {
ret = -errno;
}
add_clone_source(&send, root_id);
free(subvol);
+ full_send = 0;
break;
- }
case 'f':
outname = optarg;
break;
case 'p':
+ if (snapshot_parent) {
+ fprintf(stderr, "ERROR: you cannot have more than one parent (-p)\n");
+ return 1;
+ }
snapshot_parent = realpath(optarg, NULL);
if (!snapshot_parent) {
ret = -errno;
"%s\n", optarg, strerror(-ret));
goto out;
}
+ full_send = 0;
break;
+ case 'i':
+ fprintf(stderr,
+ "ERROR: -i was removed, use -c instead\n");
+ return 1;
case '?':
default:
fprintf(stderr, "ERROR: send args invalid.\n");
/* use first send subvol to determine mount_root */
subvol = argv[optind];
+ subvol = realpath(argv[optind], NULL);
+ if (!subvol) {
+ ret = -errno;
+ fprintf(stderr, "ERROR: unable to resolve %s\n", argv[optind]);
+ goto out;
+ }
+
ret = init_root_path(&send, subvol);
if (ret < 0)
goto out;
}
for (i = optind; i < argc; i++) {
- subvol = argv[i];
+ subvol = realpath(argv[i], NULL);
+ if (!subvol) {
+ ret = -errno;
+ fprintf(stderr, "ERROR: unable to resolve %s\n", argv[i]);
+ goto out;
+ }
ret = find_mount_root(subvol, &mount_root);
if (ret < 0) {
subvol);
goto out;
}
+ free(subvol);
}
for (i = optind; i < argc; i++) {
goto out;
}
- if (!parent_root_id) {
+ if (!full_send && !parent_root_id) {
ret = find_good_parent(&send, root_id, &parent_root_id);
- if (ret < 0)
- parent_root_id = 0;
+ if (ret < 0) {
+ fprintf(stderr, "ERROR: parent determination failed for %lld\n",
+ root_id);
+ goto out;
+ }
}
ret = is_subvol_ro(&send, subvol);
add_clone_source(&send, root_id);
parent_root_id = 0;
+ full_send = 0;
free(subvol);
}
};
static const char * const cmd_send_usage[] = {
- "btrfs send [-v] [-i <subvol>] [-p <parent>] <subvol>",
+ "btrfs send [-v] [-p <parent>] [-c <clone-src>] <subvol>",
"Send the subvolume to stdout.",
"Sends the subvolume specified by <subvol> to stdout.",
- "By default, this will send the whole subvolume. To do",
- "an incremental send, one or multiple '-i <clone_source>'",
- "arguments have to be specified. A 'clone source' is",
- "a subvolume that is known to exist on the receiving",
- "side in exactly the same state as on the sending side.\n",
- "Normally, a good snapshot parent is searched automatically",
- "in the list of 'clone sources'. To override this, use",
- "'-p <parent>' to manually specify a snapshot parent.",
- "A manually specified snapshot parent is also regarded",
- "as 'clone source'.\n",
- "-v Enable verbose debug output. Each",
- " occurrency of this option increases the",
- " verbose level more.",
- "-i <subvol> Informs btrfs send that this subvolume,",
- " can be taken as 'clone source'. This can",
- " be used for incremental sends.",
- "-p <subvol> Disable automatic snaphot parent",
- " determination and use <subvol> as parent.",
- " This subvolume is also added to the list",
- " of 'clone sources' (see -i).",
- "-f <outfile> Output is normally written to stdout.",
- " To write to a file, use this option.",
- " An alternative would be to use pipes.",
+ "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",
+ "where applicable). You must not specify clone sources unless you",
+ "guarantee that these snapshots are exactly in the same state on both",
+ "sides, the sender and the receiver. It is allowed to omit the",
+ "'-p <parent>' option when '-c <clone-src>' options are given, in",
+ "which case 'btrfs send' will determine a suitable parent among the",
+ "clone sources itself.",
+ "\n",
+ "-v Enable verbose debug output. Each occurrency of",
+ " this option increases the verbose level more.",
+ "-p <parent> Send an incremental stream from <parent> to",
+ " <subvol>.",
+ "-c <clone-src> Use this snapshot as a clone source for an ",
+ " incremental send (multiple allowed)",
+ "-f <outfile> Output is normally written to stdout. To write to",
+ " a file, use this option. An alternative would be to",
+ " use pipes.",
NULL
};