2 * Copyright (C) 2012 Alexander Block. All rights reserved.
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public
6 * License v2 as published by the Free Software Foundation.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
13 * You should have received a copy of the GNU General Public
14 * License along with this program; if not, write to the
15 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
16 * Boston, MA 021110-1307, USA.
21 #include "kerncompat.h"
30 #include <sys/types.h>
31 #include <sys/ioctl.h>
35 #include <uuid/uuid.h>
43 #include "send-utils.h"
45 static int g_verbose = 0;
53 u64 clone_sources_count;
56 struct subvol_uuid_search sus;
59 int find_mount_root(const char *path, char **mount_root)
65 int longest_matchlen = 0;
66 char *longest_match = NULL;
68 fd = open(path, O_RDONLY | O_NOATIME);
73 mnttab = fopen("/etc/mtab", "r");
74 while ((ent = getmntent(mnttab))) {
75 len = strlen(ent->mnt_dir);
76 if (strncmp(ent->mnt_dir, path, len) == 0) {
78 if (longest_matchlen < len) {
80 longest_matchlen = len;
81 longest_match = strdup(ent->mnt_dir);
86 *mount_root = realpath(longest_match, NULL);
92 static int get_root_id(struct btrfs_send *s, const char *path, u64 *root_id)
94 struct subvol_info *si;
96 si = subvol_uuid_search(&s->sus, 0, NULL, 0, path,
97 subvol_search_by_path);
100 *root_id = si->root_id;
104 static struct subvol_info *get_parent(struct btrfs_send *s, u64 root_id)
106 struct subvol_info *si;
108 si = subvol_uuid_search(&s->sus, root_id, NULL, 0, NULL,
109 subvol_search_by_root_id);
113 si = subvol_uuid_search(&s->sus, 0, si->parent_uuid, 0, NULL,
114 subvol_search_by_uuid);
120 static int find_good_parent(struct btrfs_send *s, u64 root_id, u64 *found)
123 struct subvol_info *parent;
124 struct subvol_info *parent2;
125 struct subvol_info *best_parent = NULL;
127 u64 best_diff = (u64)-1;
130 parent = get_parent(s, root_id);
136 for (i = 0; i < s->clone_sources_count; i++) {
137 if (s->clone_sources[i] == parent->root_id) {
138 best_parent = parent;
143 for (i = 0; i < s->clone_sources_count; i++) {
144 parent2 = get_parent(s, s->clone_sources[i]);
145 if (parent2 != parent)
148 parent2 = subvol_uuid_search(&s->sus, s->clone_sources[i], NULL,
149 0, NULL, subvol_search_by_root_id);
151 tmp = parent2->ctransid - parent->ctransid;
154 if (tmp < best_diff) {
155 best_parent = parent2;
166 *found = best_parent->root_id;
173 static void add_clone_source(struct btrfs_send *s, u64 root_id)
175 s->clone_sources = realloc(s->clone_sources,
176 sizeof(*s->clone_sources) * (s->clone_sources_count + 1));
177 s->clone_sources[s->clone_sources_count++] = root_id;
180 static int write_buf(int fd, const void *buf, int size)
186 ret = write(fd, (char*)buf + pos, size - pos);
189 fprintf(stderr, "ERROR: failed to dump stream. %s",
195 fprintf(stderr, "ERROR: failed to dump stream. %s",
207 static void *dump_thread(void *arg_)
210 struct btrfs_send *s = (struct btrfs_send*)arg_;
215 readed = read(s->send_fd, buf, sizeof(buf));
218 fprintf(stderr, "ERROR: failed to read stream from "
219 "kernel. %s\n", strerror(-ret));
226 ret = write_buf(s->dump_fd, buf, readed);
239 static int do_send(struct btrfs_send *send, u64 root_id, u64 parent_root_id)
243 pthread_attr_t t_attr;
244 struct btrfs_ioctl_send_args io_send;
245 struct subvol_info *si;
250 si = subvol_uuid_search(&send->sus, root_id, NULL, 0, NULL,
251 subvol_search_by_root_id);
254 fprintf(stderr, "ERROR: could not find subvol info for %llu",
259 subvol_fd = openat(send->mnt_fd, si->path, O_RDONLY | O_NOATIME);
262 fprintf(stderr, "ERROR: open %s failed. %s\n", si->path,
267 ret = pthread_attr_init(&t_attr);
272 fprintf(stderr, "ERROR: pipe failed. %s\n", strerror(-ret));
276 memset(&io_send, 0, sizeof(io_send));
277 io_send.send_fd = pipefd[1];
278 send->send_fd = pipefd[0];
281 ret = pthread_create(&t_read, &t_attr, dump_thread,
285 fprintf(stderr, "ERROR: thread setup failed: %s\n",
290 io_send.clone_sources = (__u64*)send->clone_sources;
291 io_send.clone_sources_count = send->clone_sources_count;
292 io_send.parent_root = parent_root_id;
293 ret = ioctl(subvol_fd, BTRFS_IOC_SEND, &io_send);
296 fprintf(stderr, "ERROR: send ioctl failed with %d: %s\n", ret,
301 fprintf(stderr, "BTRFS_IOC_SEND returned %d\n", ret);
304 fprintf(stderr, "joining genl thread\n");
309 ret = pthread_join(t_read, &t_err);
312 fprintf(stderr, "ERROR: pthread_join failed: %s\n",
317 ret = (long int)t_err;
318 fprintf(stderr, "ERROR: failed to process send stream, ret=%ld "
319 "(%s)\n", (long int)t_err, strerror(-ret));
323 pthread_attr_destroy(&t_attr);
337 static const char *get_subvol_name(struct btrfs_send *s, const char *full_path)
339 int len = strlen(s->root_path);
342 if (s->root_path[len - 1] != '/')
345 return full_path + len;
348 static int init_root_path(struct btrfs_send *s, const char *subvol)
355 ret = find_mount_root(subvol, &s->root_path);
358 fprintf(stderr, "ERROR: failed to determine mount point "
363 s->mnt_fd = open(s->root_path, O_RDONLY | O_NOATIME);
366 fprintf(stderr, "ERROR: can't open '%s': %s\n", s->root_path,
371 ret = subvol_uuid_search_init(s->mnt_fd, &s->sus);
373 fprintf(stderr, "ERROR: failed to initialize subvol search. "
374 "%s\n", strerror(-ret));
383 static int is_subvol_ro(struct btrfs_send *s, char *subvol)
389 fd = openat(s->mnt_fd, subvol, O_RDONLY | O_NOATIME);
392 fprintf(stderr, "ERROR: failed to open %s. %s\n",
393 subvol, strerror(-ret));
397 ret = ioctl(fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags);
400 fprintf(stderr, "ERROR: failed to get flags for subvolume. "
401 "%s\n", strerror(-ret));
405 if (flags & BTRFS_SUBVOL_RDONLY)
417 int cmd_send_start(int argc, char **argv)
422 char *outname = NULL;
423 struct btrfs_send send;
425 char *mount_root = NULL;
426 char *snapshot_parent = NULL;
428 u64 parent_root_id = 0;
431 memset(&send, 0, sizeof(send));
432 send.dump_fd = fileno(stdout);
434 while ((c = getopt(argc, argv, "vc:f:i:p:")) != -1) {
440 subvol = realpath(optarg, NULL);
443 fprintf(stderr, "ERROR: realpath %s failed. "
444 "%s\n", optarg, strerror(-ret));
448 ret = init_root_path(&send, subvol);
452 ret = get_root_id(&send, get_subvol_name(&send, subvol),
455 fprintf(stderr, "ERROR: could not resolve "
456 "root_id for %s\n", subvol);
459 add_clone_source(&send, root_id);
467 if (snapshot_parent) {
468 fprintf(stderr, "ERROR: you cannot have more than one parent (-p)\n");
471 snapshot_parent = realpath(optarg, NULL);
472 if (!snapshot_parent) {
474 fprintf(stderr, "ERROR: realpath %s failed. "
475 "%s\n", optarg, strerror(-ret));
482 "ERROR: -i was removed, use -c instead\n");
486 fprintf(stderr, "ERROR: send args invalid.\n");
491 if (optind == argc) {
492 fprintf(stderr, "ERROR: send needs path to snapshot\n");
496 if (outname != NULL) {
497 send.dump_fd = creat(outname, 0600);
498 if (send.dump_fd == -1) {
500 fprintf(stderr, "ERROR: can't create '%s': %s\n",
501 outname, strerror(-ret));
506 if (isatty(send.dump_fd)) {
508 "ERROR: not dumping send stream into a terminal, "
509 "redirect it into a file\n");
513 /* use first send subvol to determine mount_root */
514 subvol = argv[optind];
516 subvol = realpath(argv[optind], NULL);
519 fprintf(stderr, "ERROR: unable to resolve %s\n", argv[optind]);
523 ret = init_root_path(&send, subvol);
527 if (snapshot_parent != NULL) {
528 ret = get_root_id(&send,
529 get_subvol_name(&send, snapshot_parent),
532 fprintf(stderr, "ERROR: could not resolve root_id "
533 "for %s\n", snapshot_parent);
537 add_clone_source(&send, parent_root_id);
540 for (i = optind; i < argc; i++) {
541 subvol = realpath(argv[i], NULL);
544 fprintf(stderr, "ERROR: unable to resolve %s\n", argv[i]);
548 ret = find_mount_root(subvol, &mount_root);
550 fprintf(stderr, "ERROR: find_mount_root failed on %s: "
555 if (strcmp(send.root_path, mount_root) != 0) {
557 fprintf(stderr, "ERROR: all subvols must be from the "
563 ret = is_subvol_ro(&send, subvol);
568 fprintf(stderr, "ERROR: %s is not read-only.\n",
575 for (i = optind; i < argc; i++) {
578 fprintf(stderr, "At subvol %s\n", subvol);
580 subvol = realpath(subvol, NULL);
583 fprintf(stderr, "ERROR: realpath %s failed. "
584 "%s\n", argv[i], strerror(-ret));
588 ret = get_root_id(&send, get_subvol_name(&send, subvol),
591 fprintf(stderr, "ERROR: could not resolve root_id "
596 if (!full_send && !parent_root_id) {
597 ret = find_good_parent(&send, root_id, &parent_root_id);
599 fprintf(stderr, "ERROR: parent determination failed for %lld\n",
605 ret = is_subvol_ro(&send, subvol);
610 fprintf(stderr, "ERROR: %s is not read-only.\n",
615 ret = do_send(&send, root_id, parent_root_id);
619 /* done with this subvol, so add it to the clone sources */
620 add_clone_source(&send, root_id);
630 if (send.mnt_fd >= 0)
635 static const char * const send_cmd_group_usage[] = {
636 "btrfs send <command> <args>",
640 const char * const cmd_send_usage[] = {
641 "btrfs send [-v] [-p <parent>] [-c <clone-src>] <subvol>",
642 "Send the subvolume to stdout.",
643 "Sends the subvolume specified by <subvol> to stdout.",
644 "By default, this will send the whole subvolume. To do an incremental",
645 "send, use '-p <parent>'. If you want to allow btrfs to clone from",
646 "any additional local snapshots, use -c <clone-src> (multiple times",
647 "where applicable). You must not specify clone sources unless you",
648 "guarantee that these snapshots are exactly in the same state on both",
649 "sides, the sender and the receiver. It is allowed to omit the",
650 "'-p <parent>' option when '-c <clone-src>' options are given, in",
651 "which case 'btrfs send' will determine a suitable parent among the",
652 "clone sources itself.",
654 "-v Enable verbose debug output. Each occurrence of",
655 " this option increases the verbose level more.",
656 "-p <parent> Send an incremental stream from <parent> to",
658 "-c <clone-src> Use this snapshot as a clone source for an ",
659 " incremental send (multiple allowed)",
660 "-f <outfile> Output is normally written to stdout. To write to",
661 " a file, use this option. An alternative would be to",
666 int cmd_send(int argc, char **argv)
668 return cmd_send_start(argc, argv);