static int init_root_path(struct btrfs_send *s, const char *subvol) { int ret = 0; if (s->root_path) goto out; ret = find_mount_root(subvol, &s->root_path); if (ret < 0) { 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)); 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)); goto out; } out: return ret; }
static int init_root_path(struct btrfs_send *sctx, const char *subvol) { int ret = 0; if (sctx->root_path) goto out; ret = find_mount_root(subvol, &sctx->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; goto out; } sctx->mnt_fd = open(sctx->root_path, O_RDONLY | O_NOATIME); if (sctx->mnt_fd < 0) { ret = -errno; error("cannot open '%s': %s", sctx->root_path, strerror(-ret)); goto out; } ret = subvol_uuid_search_init(sctx->mnt_fd, &sctx->sus); if (ret < 0) { error("failed to initialize subvol search: %s", strerror(-ret)); goto out; } out: return ret; }
static int do_receive(struct btrfs_receive *r, const char *tomnt, char *realmnt, int r_fd, u64 max_errors) { u64 subvol_id; int ret; char *dest_dir_full_path; char root_subvol_path[PATH_MAX]; int end = 0; dest_dir_full_path = realpath(tomnt, NULL); if (!dest_dir_full_path) { ret = -errno; error("realpath(%s) failed: %s", tomnt, strerror(-ret)); goto out; } r->dest_dir_fd = open(dest_dir_full_path, O_RDONLY | O_NOATIME); if (r->dest_dir_fd < 0) { ret = -errno; error("cannot open destination directory %s: %s", dest_dir_full_path, strerror(-ret)); goto out; } if (realmnt[0]) { r->root_path = realmnt; } else { ret = find_mount_root(dest_dir_full_path, &r->root_path); if (ret < 0) { error("failed to determine mount point for %s: %s", dest_dir_full_path, strerror(-ret)); ret = -EINVAL; goto out; } if (ret > 0) { error("%s doesn't belong to btrfs mount point", dest_dir_full_path); ret = -EINVAL; goto out; } } r->mnt_fd = open(r->root_path, O_RDONLY | O_NOATIME); if (r->mnt_fd < 0) { ret = -errno; error("cannot open %s: %s", r->root_path, strerror(-ret)); goto out; } /* * If we use -m or a default subvol we want to resolve the path to the * subvolume we're sitting in so that we can adjust the paths of any * subvols we want to receive in. */ ret = btrfs_list_get_path_rootid(r->mnt_fd, &subvol_id); if (ret) { error("cannot resolve our subvolid: %d", ret); goto out; } root_subvol_path[0] = 0; ret = btrfs_subvolid_resolve(r->mnt_fd, root_subvol_path, PATH_MAX, subvol_id); if (ret) { error("cannot resolve our subvol path"); goto out; } /* * Ok we're inside of a subvol off of the root subvol, we need to * actually set full_root_path. */ if (*root_subvol_path) r->full_root_path = root_subvol_path; if (r->dest_dir_chroot) { if (chroot(dest_dir_full_path)) { ret = -errno; error("failed to chroot to %s: %s", dest_dir_full_path, strerror(-ret)); goto out; } if (chdir("/")) { ret = -errno; error("failed to chdir to / after chroot: %s", strerror(-ret)); goto out; } fprintf(stderr, "Chroot to %s\n", dest_dir_full_path); r->root_path = strdup("/"); r->dest_dir_path = r->root_path; } else { /* * find_mount_root returns a root_path that is a subpath of * dest_dir_full_path. Now get the other part of root_path, * which is the destination dir relative to root_path. */ r->dest_dir_path = dest_dir_full_path + strlen(r->root_path); while (r->dest_dir_path[0] == '/') r->dest_dir_path++; } ret = subvol_uuid_search_init(r->mnt_fd, &r->sus); if (ret < 0) goto out; while (!end) { if (r->cached_capabilities_len) { if (g_verbose >= 3) fprintf(stderr, "clear cached capabilities\n"); memset(r->cached_capabilities, 0, sizeof(r->cached_capabilities)); r->cached_capabilities_len = 0; } ret = btrfs_read_and_process_send_stream(r_fd, &send_ops, r, r->honor_end_cmd, max_errors); if (ret < 0) goto out; if (ret) end = 1; close_inode_for_write(r); ret = finish_subvol(r); if (ret < 0) goto out; } ret = 0; out: if (r->write_fd != -1) { close(r->write_fd); r->write_fd = -1; } if (r->root_path != realmnt) free(r->root_path); r->root_path = NULL; r->dest_dir_path = NULL; free(dest_dir_full_path); subvol_uuid_search_finit(&r->sus); if (r->mnt_fd != -1) { close(r->mnt_fd); r->mnt_fd = -1; } if (r->dest_dir_fd != -1) { close(r->dest_dir_fd); r->dest_dir_fd = -1; } return ret; }
static int cmd_subvol_show(int argc, char **argv) { struct root_info get_ri; struct btrfs_list_filter_set *filter_set; char tstr[256]; char uuidparse[BTRFS_UUID_UNPARSED_SIZE]; char *fullpath = NULL, *svpath = NULL, *mnt = NULL; char raw_prefix[] = "\t\t\t\t"; u64 sv_id, mntid; int fd = -1, mntfd = -1; int ret = 1; DIR *dirstream1 = NULL, *dirstream2 = NULL; if (check_argc_exact(argc, 2)) usage(cmd_subvol_show_usage); fullpath = realpath(argv[1], NULL); if (!fullpath) { fprintf(stderr, "ERROR: finding real path for '%s', %s\n", argv[1], strerror(errno)); goto out; } ret = test_issubvolume(fullpath); if (ret < 0) { fprintf(stderr, "ERROR: error accessing '%s'\n", fullpath); goto out; } if (!ret) { fprintf(stderr, "ERROR: '%s' is not a subvolume\n", fullpath); ret = 1; goto out; } ret = find_mount_root(fullpath, &mnt); if (ret < 0) { fprintf(stderr, "ERROR: find_mount_root failed on '%s': " "%s\n", fullpath, strerror(-ret)); goto out; } if (ret > 0) { fprintf(stderr, "ERROR: %s doesn't belong to btrfs mount point\n", fullpath); goto out; } ret = 1; svpath = get_subvol_name(mnt, fullpath); fd = open_file_or_dir(fullpath, &dirstream1); if (fd < 0) { fprintf(stderr, "ERROR: can't access '%s'\n", fullpath); goto out; } ret = btrfs_list_get_path_rootid(fd, &sv_id); if (ret) { fprintf(stderr, "ERROR: can't get rootid for '%s'\n", fullpath); goto out; } mntfd = open_file_or_dir(mnt, &dirstream2); if (mntfd < 0) { fprintf(stderr, "ERROR: can't access '%s'\n", mnt); goto out; } ret = btrfs_list_get_path_rootid(mntfd, &mntid); if (ret) { fprintf(stderr, "ERROR: can't get rootid for '%s'\n", mnt); goto out; } if (sv_id == BTRFS_FS_TREE_OBJECTID) { printf("%s is btrfs root\n", fullpath); goto out; } memset(&get_ri, 0, sizeof(get_ri)); get_ri.root_id = sv_id; ret = btrfs_get_subvol(mntfd, &get_ri); if (ret) { fprintf(stderr, "ERROR: can't find '%s'\n", svpath); goto out; } /* print the info */ printf("%s\n", fullpath); printf("\tName: \t\t\t%s\n", get_ri.name); if (uuid_is_null(get_ri.uuid)) strcpy(uuidparse, "-"); else uuid_unparse(get_ri.uuid, uuidparse); printf("\tUUID: \t\t\t%s\n", uuidparse); if (uuid_is_null(get_ri.puuid)) strcpy(uuidparse, "-"); else uuid_unparse(get_ri.puuid, uuidparse); printf("\tParent UUID: \t\t%s\n", uuidparse); if (uuid_is_null(get_ri.ruuid)) strcpy(uuidparse, "-"); else uuid_unparse(get_ri.ruuid, uuidparse); printf("\tReceived UUID: \t\t%s\n", uuidparse); if (get_ri.otime) { struct tm tm; localtime_r(&get_ri.otime, &tm); strftime(tstr, 256, "%Y-%m-%d %X %z", &tm); } else strcpy(tstr, "-"); printf("\tCreation time: \t\t%s\n", tstr); printf("\tSubvolume ID: \t\t%llu\n", get_ri.root_id); printf("\tGeneration: \t\t%llu\n", get_ri.gen); printf("\tGen at creation: \t%llu\n", get_ri.ogen); printf("\tParent ID: \t\t%llu\n", get_ri.ref_tree); printf("\tTop level ID: \t\t%llu\n", get_ri.top_id); if (get_ri.flags & BTRFS_ROOT_SUBVOL_RDONLY) printf("\tFlags: \t\t\treadonly\n"); else printf("\tFlags: \t\t\t-\n"); /* print the snapshots of the given subvol if any*/ printf("\tSnapshot(s):\n"); filter_set = btrfs_list_alloc_filter_set(); btrfs_list_setup_filter(&filter_set, BTRFS_LIST_FILTER_BY_PARENT, (u64)(unsigned long)get_ri.uuid); btrfs_list_setup_print_column(BTRFS_LIST_PATH); btrfs_list_subvols_print(fd, filter_set, NULL, BTRFS_LIST_LAYOUT_RAW, 1, raw_prefix); /* clean up */ free(get_ri.path); free(get_ri.name); free(get_ri.full_path); btrfs_list_free_filter_set(filter_set); out: close_file_or_dir(fd, dirstream1); close_file_or_dir(mntfd, dirstream2); free(mnt); free(fullpath); return !!ret; }
static int do_receive(struct btrfs_receive *r, const char *tomnt, int r_fd, u64 max_errors) { int ret; char *dest_dir_full_path; int end = 0; dest_dir_full_path = realpath(tomnt, NULL); if (!dest_dir_full_path) { ret = -errno; fprintf(stderr, "ERROR: realpath(%s) failed. %s\n", tomnt, strerror(-ret)); goto out; } r->dest_dir_fd = open(dest_dir_full_path, O_RDONLY | O_NOATIME); if (r->dest_dir_fd < 0) { ret = -errno; fprintf(stderr, "ERROR: failed to open destination directory %s. %s\n", dest_dir_full_path, strerror(-ret)); goto out; } ret = find_mount_root(dest_dir_full_path, &r->root_path); if (ret < 0) { fprintf(stderr, "ERROR: failed to determine mount point for %s: %s\n", dest_dir_full_path, strerror(-ret)); ret = -EINVAL; goto out; } if (ret > 0) { fprintf(stderr, "ERROR: %s doesn't belong to btrfs mount point\n", dest_dir_full_path); ret = -EINVAL; goto out; } r->mnt_fd = open(r->root_path, O_RDONLY | O_NOATIME); if (r->mnt_fd < 0) { ret = -errno; fprintf(stderr, "ERROR: failed to open %s. %s\n", r->root_path, strerror(-ret)); goto out; } /* * find_mount_root returns a root_path that is a subpath of * dest_dir_full_path. Now get the other part of root_path, * which is the destination dir relative to root_path. */ r->dest_dir_path = dest_dir_full_path + strlen(r->root_path); while (r->dest_dir_path[0] == '/') r->dest_dir_path++; ret = subvol_uuid_search_init(r->mnt_fd, &r->sus); if (ret < 0) goto out; while (!end) { ret = btrfs_read_and_process_send_stream(r_fd, &send_ops, r, r->honor_end_cmd, max_errors); if (ret < 0) goto out; if (ret) end = 1; close_inode_for_write(r); ret = finish_subvol(r); if (ret < 0) goto out; } ret = 0; out: if (r->write_fd != -1) { close(r->write_fd); r->write_fd = -1; } free(r->root_path); r->root_path = NULL; free(r->write_path); r->write_path = NULL; free(r->full_subvol_path); r->full_subvol_path = NULL; r->dest_dir_path = NULL; free(dest_dir_full_path); if (r->cur_subvol) { free(r->cur_subvol->path); free(r->cur_subvol); r->cur_subvol = NULL; } subvol_uuid_search_finit(&r->sus); if (r->mnt_fd != -1) { close(r->mnt_fd); r->mnt_fd = -1; } if (r->dest_dir_fd != -1) { close(r->dest_dir_fd); r->dest_dir_fd = -1; } return ret; }
int cmd_send(int argc, char **argv) { char *subvol = NULL; int ret; char outname[PATH_MAX]; struct btrfs_send send; u32 i; char *mount_root = NULL; char *snapshot_parent = NULL; 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; switch (c) { case 'v': g_verbose++; break; case 'q': g_verbose = 0; break; case 'e': new_end_cmd_semantic = 1; break; case 'c': subvol = realpath(optarg, NULL); if (!subvol) { ret = -errno; error("realpath %s failed: %s\n", optarg, strerror(-ret)); goto out; } ret = set_root_info(&send, subvol, &root_id); if (ret < 0) 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) { error("cannot add clone source: %s", strerror(-ret)); goto out; } free(subvol); subvol = NULL; free_send_info(&send); full_send = 0; break; case 'f': 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) { error("you cannot have more than one parent (-p)"); ret = 1; goto out; } snapshot_parent = realpath(optarg, NULL); if (!snapshot_parent) { ret = -errno; 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': 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: error("send arguments invalid"); ret = 1; goto out; } } if (check_argc_min(argc - optind, 1)) usage(cmd_send_usage); if (outname[0]) { int tmpfd; /* * Try to use an existing file first. Even if send runs as * root, it might not have permissions to create file (eg. on a * NFS) but it should still be able to use a pre-created file. */ tmpfd = open(outname, O_WRONLY | O_TRUNC); if (tmpfd < 0) { if (errno == ENOENT) tmpfd = open(outname, O_CREAT | O_WRONLY | O_TRUNC, 0600); } send.dump_fd = tmpfd; if (send.dump_fd == -1) { ret = -errno; error("cannot create '%s': %s", outname, strerror(-ret)); goto out; } } if (isatty(send.dump_fd)) { error( "not dumping send stream into a terminal, redirect it into a file"); ret = 1; goto out; } /* use first send subvol to determine mount_root */ subvol = realpath(argv[optind], NULL); if (!subvol) { ret = -errno; error("unable to resolve %s", argv[optind]); goto out; } ret = init_root_path(&send, subvol); if (ret < 0) goto out; if (snapshot_parent != NULL) { ret = get_root_id(&send, subvol_strip_mountpoint(send.root_path, snapshot_parent), &parent_root_id); if (ret < 0) { error("could not resolve rootid for %s", snapshot_parent); goto out; } 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++) { free(subvol); subvol = realpath(argv[i], NULL); if (!subvol) { ret = -errno; error("unable to resolve %s", argv[i]); goto out; } ret = find_mount_root(subvol, &mount_root); if (ret < 0) { 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; error("all subvolumes must be from the same filesystem"); goto out; } free(mount_root); ret = is_subvol_ro(&send, subvol); if (ret < 0) goto out; if (!ret) { ret = -EINVAL; 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]; if (g_verbose > 0) fprintf(stderr, "At subvol %s\n", subvol); subvol = realpath(subvol, NULL); if (!subvol) { ret = -errno; error("realpath %s failed: %s", argv[i], strerror(-ret)); goto out; } if (!full_send && !snapshot_parent) { ret = set_root_info(&send, subvol, &root_id); if (ret < 0) goto out; ret = find_good_parent(&send, root_id, &parent_root_id); if (ret < 0) { error("parent determination failed for %lld", root_id); goto out; } } if (new_end_cmd_semantic) { /* require new kernel */ is_first_subvol = (i == optind); is_last_subvol = (i == argc - 1); } else { /* be compatible to old and new kernel */ is_first_subvol = 1; is_last_subvol = 1; } ret = do_send(&send, parent_root_id, is_first_subvol, is_last_subvol, subvol, send_flags); if (ret < 0) goto out; if (!full_send && !snapshot_parent) { /* 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; } free_send_info(&send); } } ret = 0; out: free(subvol); free(snapshot_parent); free(send.clone_sources); free_send_info(&send); return !!ret; }
int cmd_send_start(int argc, char **argv) { char *subvol = NULL; int c; int ret; char *outname = NULL; struct btrfs_send send; u32 i; char *mount_root = NULL; 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, "vc:f:i:p:")) != -1) { switch (c) { case 'v': g_verbose++; break; case 'c': subvol = realpath(optarg, NULL); if (!subvol) { ret = -errno; fprintf(stderr, "ERROR: realpath %s failed. " "%s\n", optarg, strerror(-ret)); goto out; } ret = init_root_path(&send, subvol); if (ret < 0) 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); goto out; } add_clone_source(&send, root_id); subvol_uuid_search_finit(&send.sus); free(subvol); subvol = NULL; if (send.mnt_fd >= 0) { close(send.mnt_fd); send.mnt_fd = -1; } free(send.root_path); send.root_path = NULL; 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"); 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)); goto out; } full_send = 0; break; case 'i': fprintf(stderr, "ERROR: -i was removed, use -c instead\n"); ret = 1; goto out; case '?': default: fprintf(stderr, "ERROR: send args invalid.\n"); ret = 1; goto out; } } if (optind == argc) { fprintf(stderr, "ERROR: send needs path to snapshot\n"); ret = 1; goto out; } if (outname != NULL) { 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)); goto out; } } if (isatty(send.dump_fd)) { fprintf(stderr, "ERROR: not dumping send stream into a terminal, " "redirect it into a file\n"); ret = 1; goto out; } /* 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; if (snapshot_parent != NULL) { ret = get_root_id(&send, get_subvol_name(send.root_path, snapshot_parent), &parent_root_id); if (ret < 0) { fprintf(stderr, "ERROR: could not resolve root_id " "for %s\n", snapshot_parent); goto out; } add_clone_source(&send, parent_root_id); } for (i = optind; i < argc; i++) { free(subvol); 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) { fprintf(stderr, "ERROR: find_mount_root failed on %s: " "%s\n", subvol, strerror(-ret)); goto out; } if (strcmp(send.root_path, mount_root) != 0) { ret = -EINVAL; fprintf(stderr, "ERROR: all subvols must be from the " "same fs.\n"); goto out; } free(mount_root); ret = is_subvol_ro(&send, subvol); if (ret < 0) goto out; if (!ret) { ret = -EINVAL; fprintf(stderr, "ERROR: %s is not read-only.\n", subvol); goto out; } } for (i = optind; i < argc; i++) { free(subvol); subvol = argv[i]; 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); 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", root_id); goto out; } } ret = is_subvol_ro(&send, subvol); if (ret < 0) goto out; if (!ret) { ret = -EINVAL; fprintf(stderr, "ERROR: %s is not read-only.\n", subvol); goto out; } ret = do_send(&send, root_id, parent_root_id); if (ret < 0) goto out; /* done with this subvol, so add it to the clone sources */ add_clone_source(&send, root_id); parent_root_id = 0; full_send = 0; } ret = 0; out: free(subvol); free(snapshot_parent); free(send.clone_sources); if (send.mnt_fd >= 0) close(send.mnt_fd); free(send.root_path); subvol_uuid_search_finit(&send.sus); return ret; }