+ free(get_ri.path);
+ free(get_ri.name);
+ free(get_ri.full_path);
+ free(filter_set);
+
+ close_file_or_dir(fd, dirstream1);
+ free(fullpath);
+ return !!ret;
+}
+
+static const char * const cmd_subvol_sync_usage[] = {
+ "btrfs subvolume sync <path> [<subvol-id>...]",
+ "Wait until given subvolume(s) are completely removed from the filesystem.",
+ "Wait until given subvolume(s) are completely removed from the filesystem",
+ "after deletion.",
+ "If no subvolume id is given, wait until all current deletion requests",
+ "are completed, but do not wait for subvolumes deleted meanwhile.",
+ "The status of subvolume ids is checked periodically.",
+ "",
+ "-s <N> sleep N seconds between checks (default: 1)",
+ NULL
+};
+
+#if 0
+/*
+ * If we're looking for any dead subvolume, take a shortcut and look
+ * for any ORPHAN_ITEMs in the tree root
+ */
+static int fs_has_dead_subvolumes(int fd)
+{
+ int ret;
+ struct btrfs_ioctl_search_args args;
+ struct btrfs_ioctl_search_key *sk = &args.key;
+ struct btrfs_ioctl_search_header sh;
+ u64 min_subvolid = 0;
+
+again:
+ sk->tree_id = BTRFS_ROOT_TREE_OBJECTID;
+ sk->min_objectid = BTRFS_ORPHAN_OBJECTID;
+ sk->max_objectid = BTRFS_ORPHAN_OBJECTID;
+ sk->min_type = BTRFS_ORPHAN_ITEM_KEY;
+ sk->max_type = BTRFS_ORPHAN_ITEM_KEY;
+ sk->min_offset = min_subvolid;
+ sk->max_offset = (u64)-1;
+ sk->min_transid = 0;
+ sk->max_transid = (u64)-1;
+ sk->nr_items = 1;
+
+ ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args);
+ if (ret < 0)
+ return -errno;
+
+ if (!sk->nr_items)
+ return 0;
+
+ memcpy(&sh, args.buf, sizeof(sh));
+ min_subvolid = sh.offset;
+
+ /*
+ * Verify that the root item is really there and we haven't hit
+ * a stale orphan
+ */
+ sk->tree_id = BTRFS_ROOT_TREE_OBJECTID;
+ sk->min_objectid = min_subvolid;
+ sk->max_objectid = min_subvolid;
+ sk->min_type = BTRFS_ROOT_ITEM_KEY;
+ sk->max_type = BTRFS_ROOT_ITEM_KEY;
+ sk->min_offset = 0;
+ sk->max_offset = (u64)-1;
+ sk->min_transid = 0;
+ sk->max_transid = (u64)-1;
+ sk->nr_items = 1;
+
+ ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args);
+ if (ret < 0)
+ return -errno;
+
+ /*
+ * Stale orphan, try the next one
+ */
+ if (!sk->nr_items) {
+ min_subvolid++;
+ goto again;
+ }
+
+ return 1;
+}
+#endif
+
+#define SUBVOL_ID_BATCH 1024
+
+/*
+ * Enumerate all dead subvolumes that exist in the filesystem.
+ * Fill @ids and reallocate to bigger size if needed.
+ */
+static int enumerate_dead_subvols(int fd, u64 **ids)
+{
+ int ret;
+ struct btrfs_ioctl_search_args args;
+ struct btrfs_ioctl_search_key *sk = &args.key;
+ int idx = 0;
+ int count = 0;
+
+ memset(&args, 0, sizeof(args));
+
+ sk->tree_id = BTRFS_ROOT_TREE_OBJECTID;
+ sk->min_objectid = BTRFS_ORPHAN_OBJECTID;
+ sk->max_objectid = BTRFS_ORPHAN_OBJECTID;
+ sk->min_type = BTRFS_ORPHAN_ITEM_KEY;
+ sk->max_type = BTRFS_ORPHAN_ITEM_KEY;
+ sk->min_offset = 0;
+ sk->max_offset = (u64)-1;
+ sk->min_transid = 0;
+ sk->max_transid = (u64)-1;
+ sk->nr_items = 4096;
+
+ *ids = NULL;
+ while (1) {
+ struct btrfs_ioctl_search_header *sh;
+ unsigned long off;
+ int i;
+
+ ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args);
+ if (ret < 0)
+ return -errno;
+
+ if (!sk->nr_items)
+ return idx;
+
+ off = 0;
+ for (i = 0; i < sk->nr_items; i++) {
+ sh = (struct btrfs_ioctl_search_header*)(args.buf + off);
+ off += sizeof(*sh);
+
+ if (btrfs_search_header_type(sh)
+ == BTRFS_ORPHAN_ITEM_KEY) {
+ if (idx >= count) {
+ u64 *newids;
+
+ count += SUBVOL_ID_BATCH;
+ newids = (u64*)realloc(*ids,
+ count * sizeof(u64));
+ if (!newids)
+ return -ENOMEM;
+ *ids = newids;
+ }
+ (*ids)[idx] = btrfs_search_header_offset(sh);
+ idx++;
+ }
+ off += btrfs_search_header_len(sh);
+
+ sk->min_objectid = btrfs_search_header_objectid(sh);
+ sk->min_type = btrfs_search_header_type(sh);
+ sk->min_offset = btrfs_search_header_offset(sh);
+ }
+ if (sk->min_offset < (u64)-1)
+ sk->min_offset++;
+ else
+ break;
+ if (sk->min_type != BTRFS_ORPHAN_ITEM_KEY)
+ break;
+ if (sk->min_objectid != BTRFS_ORPHAN_OBJECTID)
+ break;
+ }
+
+ return idx;
+}
+
+static int cmd_subvol_sync(int argc, char **argv)
+{
+ int fd = -1;
+ int i;
+ int ret = 1;
+ DIR *dirstream = NULL;
+ u64 *ids = NULL;
+ int id_count;
+ int sleep_interval = 1;
+
+ while (1) {
+ int c = getopt(argc, argv, "s:");
+
+ if (c < 0)
+ break;
+
+ switch (c) {
+ case 's':
+ sleep_interval = atoi(optarg);
+ if (sleep_interval < 1) {
+ error("invalid sleep interval %s", optarg);
+ ret = 1;
+ goto out;
+ }
+ break;
+ default:
+ usage(cmd_subvol_sync_usage);
+ }
+ }
+
+ if (check_argc_min(argc - optind, 1))
+ usage(cmd_subvol_sync_usage);
+
+ fd = btrfs_open_dir(argv[optind], &dirstream, 1);
+ if (fd < 0) {
+ ret = 1;
+ goto out;
+ }
+ optind++;
+
+ id_count = argc - optind;
+ if (!id_count) {
+ id_count = enumerate_dead_subvols(fd, &ids);
+ if (id_count < 0) {
+ error("can't enumerate dead subvolumes: %s",
+ strerror(-id_count));
+ ret = 1;
+ goto out;
+ }
+ if (id_count == 0) {
+ ret = 0;
+ goto out;
+ }
+ } else {
+ ids = (u64*)malloc(id_count * sizeof(u64));
+ if (!ids) {
+ error("not enough memory");
+ ret = 1;
+ goto out;
+ }
+
+ for (i = 0; i < id_count; i++) {
+ u64 id;
+ const char *arg;
+
+ arg = argv[optind + i];
+ errno = 0;
+ id = strtoull(arg, NULL, 10);
+ if (errno < 0) {
+ error("unrecognized subvolume id %s", arg);
+ ret = 1;
+ goto out;
+ }
+ if (id < BTRFS_FIRST_FREE_OBJECTID
+ || id > BTRFS_LAST_FREE_OBJECTID) {
+ error("subvolume id %s out of range", arg);
+ ret = 1;
+ goto out;
+ }
+ ids[i] = id;
+ }
+ }
+
+ ret = wait_for_subvolume_cleaning(fd, id_count, ids, sleep_interval);