Esempio n. 1
0
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;

}
Esempio n. 2
0
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;
}
Esempio n. 4
0
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;
}
Esempio n. 5
0
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;
}
Esempio n. 6
0
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;
}
Esempio n. 7
0
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;
}