btrfs-progs: doc quota, sort subcommands alphabetically
[platform/upstream/btrfs-progs.git] / cmds-subvolume.c
index e1fa81a..15d4b97 100644 (file)
@@ -41,25 +41,6 @@ static const char * const subvolume_cmd_group_usage[] = {
        NULL
 };
 
-/*
- * test if path is a directory
- * this function return
- * 0-> path exists but it is not a directory
- * 1-> path exists and it is  a directory
- * -1 -> path is unaccessible
- */
-static int test_isdir(char *path)
-{
-       struct stat     st;
-       int             res;
-
-       res = stat(path, &st);
-       if(res < 0 )
-               return -1;
-
-       return S_ISDIR(st.st_mode);
-}
-
 static const char * const cmd_subvol_create_usage[] = {
        "btrfs subvolume create [-i <qgroupid>] [<dest>/]<name>",
        "Create a subvolume",
@@ -75,6 +56,8 @@ static int cmd_subvol_create(int argc, char **argv)
 {
        int     retval, res, len;
        int     fddst = -1;
+       char    *dupname = NULL;
+       char    *dupdir = NULL;
        char    *newname;
        char    *dstdir;
        char    *dst;
@@ -83,7 +66,7 @@ static int cmd_subvol_create(int argc, char **argv)
 
        optind = 1;
        while (1) {
-               int c = getopt(argc, argv, "c:i:");
+               int c = getopt(argc, argv, "c:i:v");
                if (c < 0)
                        break;
 
@@ -119,28 +102,27 @@ static int cmd_subvol_create(int argc, char **argv)
                goto out;
        }
 
-       newname = strdup(dst);
-       newname = basename(newname);
-       dstdir = strdup(dst);
-       dstdir = dirname(dstdir);
+       dupname = strdup(dst);
+       newname = basename(dupname);
+       dupdir = strdup(dst);
+       dstdir = dirname(dupdir);
 
-       if (!strcmp(newname, ".") || !strcmp(newname, "..") ||
-            strchr(newname, '/') ){
-               fprintf(stderr, "ERROR: uncorrect subvolume name ('%s')\n",
+       if (!test_issubvolname(newname)) {
+               fprintf(stderr, "ERROR: incorrect subvolume name '%s'\n",
                        newname);
                goto out;
        }
 
        len = strlen(newname);
        if (len == 0 || len >= BTRFS_VOL_NAME_MAX) {
-               fprintf(stderr, "ERROR: subvolume name too long ('%s)\n",
+               fprintf(stderr, "ERROR: subvolume name too long '%s'\n",
                        newname);
                goto out;
        }
 
        fddst = open_file_or_dir(dstdir, &dirstream);
        if (fddst < 0) {
-               fprintf(stderr, "ERROR: can't access to '%s'\n", dstdir);
+               fprintf(stderr, "ERROR: can't access '%s'\n", dstdir);
                goto out;
        }
 
@@ -174,6 +156,8 @@ static int cmd_subvol_create(int argc, char **argv)
 out:
        close_file_or_dir(fddst, dirstream);
        free(inherit);
+       free(dupname);
+       free(dupdir);
 
        return retval;
 }
@@ -197,86 +181,182 @@ int test_issubvolume(char *path)
        return (st.st_ino == 256) && S_ISDIR(st.st_mode);
 }
 
+static int wait_for_commit(int fd)
+{
+       int ret;
+
+       ret = ioctl(fd, BTRFS_IOC_START_SYNC, NULL);
+       if (ret < 0)
+               return ret;
+       return ioctl(fd, BTRFS_IOC_WAIT_SYNC, NULL);
+}
+
 static const char * const cmd_subvol_delete_usage[] = {
-       "btrfs subvolume delete <subvolume> [<subvolume>...]",
+       "btrfs subvolume delete [options] <subvolume> [<subvolume>...]",
        "Delete subvolume(s)",
+       "Delete subvolumes from the filesystem. The corresponding directory",
+       "is removed instantly but the data blocks are removed later.",
+       "The deletion does not involve full commit by default due to",
+       "performance reasons (as a consequence, the subvolume may appear again",
+       "after a crash). Use one of the --commit options to wait until the",
+       "operation is safely stored on the media.",
+       "",
+       "-c|--commit-after      wait for transaction commit at the end of the operation",
+       "-C|--commit-each       wait for transaction commit after deleting each subvolume",
        NULL
 };
 
 static int cmd_subvol_delete(int argc, char **argv)
 {
-       int     res, fd, len, e, cnt = 1, ret = 0;
+       int     res, len, e, ret = 0;
+       int cnt;
+       int fd = -1;
        struct btrfs_ioctl_vol_args     args;
        char    *dname, *vname, *cpath;
+       char    *dupdname = NULL;
+       char    *dupvname = NULL;
        char    *path;
        DIR     *dirstream = NULL;
+       int verbose = 0;
+       int commit_mode = 0;
+       struct option long_options[] = {
+               {"commit-after", no_argument, NULL, 'c'},  /* commit mode 1 */
+               {"commit-each", no_argument, NULL, 'C'},  /* commit mode 2 */
+               {NULL, 0, NULL, 0}
+       };
+
+       optind = 1;
+       while (1) {
+               int c;
+
+               c = getopt_long(argc, argv, "cC", long_options, NULL);
+               if (c < 0)
+                       break;
+
+               switch(c) {
+               case 'c':
+                       commit_mode = 1;
+                       break;
+               case 'C':
+                       commit_mode = 2;
+                       break;
+               case 'v':
+                       verbose++;
+                       break;
+               default:
+                       usage(cmd_subvol_delete_usage);
+               }
+       }
 
-       if (argc < 2)
+       if (check_argc_min(argc - optind, 1))
                usage(cmd_subvol_delete_usage);
 
+       if (verbose > 0) {
+               printf("Transaction commit: %s\n",
+                       !commit_mode ? "none (default)" :
+                       commit_mode == 1 ? "at the end" : "after each");
+       }
+
+       cnt = optind;
+
 again:
        path = argv[cnt];
 
        res = test_issubvolume(path);
-       if(res<0){
+       if (res < 0) {
                fprintf(stderr, "ERROR: error accessing '%s'\n", path);
-               ret = 12;
+               ret = 1;
                goto out;
        }
-       if(!res){
+       if (!res) {
                fprintf(stderr, "ERROR: '%s' is not a subvolume\n", path);
-               ret = 13;
+               ret = 1;
                goto out;
        }
 
        cpath = realpath(path, NULL);
-       dname = strdup(cpath);
-       dname = dirname(dname);
-       vname = strdup(cpath);
-       vname = basename(vname);
+       if (!cpath) {
+               ret = errno;
+               fprintf(stderr, "ERROR: finding real path for '%s': %s\n",
+                       path, strerror(errno));
+               goto out;
+       }
+       dupdname = strdup(cpath);
+       dname = dirname(dupdname);
+       dupvname = strdup(cpath);
+       vname = basename(dupvname);
        free(cpath);
 
-       if( !strcmp(vname,".") || !strcmp(vname,"..") ||
-            strchr(vname, '/') ){
-               fprintf(stderr, "ERROR: incorrect subvolume name ('%s')\n",
+       if (!test_issubvolname(vname)) {
+               fprintf(stderr, "ERROR: incorrect subvolume name '%s'\n",
                        vname);
-               ret = 14;
+               ret = 1;
                goto out;
        }
 
        len = strlen(vname);
        if (len == 0 || len >= BTRFS_VOL_NAME_MAX) {
-               fprintf(stderr, "ERROR: snapshot name too long ('%s)\n",
+               fprintf(stderr, "ERROR: snapshot name too long '%s'\n",
                        vname);
-               ret = 14;
+               ret = 1;
                goto out;
        }
 
        fd = open_file_or_dir(dname, &dirstream);
        if (fd < 0) {
-               fprintf(stderr, "ERROR: can't access to '%s'\n", dname);
-               ret = 12;
+               fprintf(stderr, "ERROR: can't access '%s'\n", dname);
+               ret = 1;
                goto out;
        }
 
-       printf("Delete subvolume '%s/%s'\n", dname, vname);
+       printf("Delete subvolume (%s): '%s/%s'\n",
+               commit_mode == 2 || (commit_mode == 1 && cnt + 1 == argc)
+               ? "commit" : "no-commit", dname, vname);
        strncpy_null(args.name, vname);
        res = ioctl(fd, BTRFS_IOC_SNAP_DESTROY, &args);
        e = errno;
 
-       close_file_or_dir(fd, dirstream);
-
        if(res < 0 ){
                fprintf( stderr, "ERROR: cannot delete '%s/%s' - %s\n",
                        dname, vname, strerror(e));
-               ret = 11;
+               ret = 1;
                goto out;
        }
 
+       if (commit_mode == 1) {
+               res = wait_for_commit(fd);
+               if (res < 0) {
+                       fprintf(stderr,
+                               "ERROR: unable to wait for commit after '%s': %s\n",
+                               path, strerror(errno));
+                       ret = 1;
+               }
+       }
+
 out:
+       free(dupdname);
+       free(dupvname);
+       dupdname = NULL;
+       dupvname = NULL;
        cnt++;
-       if (cnt < argc)
+       if (cnt < argc) {
+               close_file_or_dir(fd, dirstream);
+               /* avoid double free */
+               fd = -1;
+               dirstream = NULL;
                goto again;
+       }
+
+       if (commit_mode == 2 && fd != -1) {
+               res = wait_for_commit(fd);
+               if (res < 0) {
+                       fprintf(stderr,
+                               "ERROR: unable to do final sync: %s\n",
+                               strerror(errno));
+                       ret = 1;
+               }
+       }
+       close_file_or_dir(fd, dirstream);
 
        return ret;
 }
@@ -297,12 +377,14 @@ static const char * const cmd_subvol_list_usage[] = {
        "             to the given <path>",
        "-c           print the ogeneration of the subvolume",
        "-g           print the generation of the subvolume",
-       "-o           print only subvolumes bellow specified path",
+       "-o           print only subvolumes below specified path",
        "-u           print the uuid of subvolumes (and snapshots)",
        "-q           print the parent uuid of the snapshots",
+       "-R           print the uuid of the received snapshots",
        "-t           print the result as a table",
        "-s           list snapshots only in the filesystem",
        "-r           list readonly subvolumes (including snapshots)",
+       "-d           list deleted subvolumes that are not yet cleaned",
        "-G [+|-]value",
        "             filter the subvolumes by generation",
        "             (+value: >= value; -value: <= value; value: = value)",
@@ -341,7 +423,7 @@ static int cmd_subvol_list(int argc, char **argv)
        optind = 1;
        while(1) {
                c = getopt_long(argc, argv,
-                                   "acgopqsurG:C:t", long_options, NULL);
+                                   "acdgopqsurRG:C:t", long_options, NULL);
                if (c < 0)
                        break;
 
@@ -355,6 +437,11 @@ static int cmd_subvol_list(int argc, char **argv)
                case 'c':
                        btrfs_list_setup_print_column(BTRFS_LIST_OGENERATION);
                        break;
+               case 'd':
+                       btrfs_list_setup_filter(&filter_set,
+                                               BTRFS_LIST_FILTER_DELETED,
+                                               0);
+                       break;
                case 'g':
                        btrfs_list_setup_print_column(BTRFS_LIST_GENERATION);
                        break;
@@ -377,6 +464,9 @@ static int cmd_subvol_list(int argc, char **argv)
                case 'q':
                        btrfs_list_setup_print_column(BTRFS_LIST_PUUID);
                        break;
+               case 'R':
+                       btrfs_list_setup_print_column(BTRFS_LIST_RUUID);
+                       break;
                case 'r':
                        flags |= BTRFS_ROOT_SUBVOL_RDONLY;
                        break;
@@ -471,8 +561,7 @@ out:
                btrfs_list_free_comparer_set(comparer_set);
        if (uerr)
                usage(cmd_subvol_list_usage);
-
-       return ret;
+       return !!ret;
 }
 
 static const char * const cmd_snapshot_usage[] = {
@@ -495,6 +584,8 @@ static int cmd_snapshot(int argc, char **argv)
        int     res, retval;
        int     fd = -1, fddst = -1;
        int     len, readonly = 0;
+       char    *dupname = NULL;
+       char    *dupdir = NULL;
        char    *newname;
        char    *dstdir;
        struct btrfs_ioctl_vol_args_v2  args;
@@ -562,39 +653,38 @@ static int cmd_snapshot(int argc, char **argv)
        }
 
        if (res > 0) {
-               newname = strdup(subvol);
-               newname = basename(newname);
+               dupname = strdup(subvol);
+               newname = basename(dupname);
                dstdir = dst;
        } else {
-               newname = strdup(dst);
-               newname = basename(newname);
-               dstdir = strdup(dst);
-               dstdir = dirname(dstdir);
+               dupname = strdup(dst);
+               newname = basename(dupname);
+               dupdir = strdup(dst);
+               dstdir = dirname(dupdir);
        }
 
-       if (!strcmp(newname, ".") || !strcmp(newname, "..") ||
-            strchr(newname, '/') ){
-               fprintf(stderr, "ERROR: incorrect snapshot name ('%s')\n",
+       if (!test_issubvolname(newname)) {
+               fprintf(stderr, "ERROR: incorrect snapshot name '%s'\n",
                        newname);
                goto out;
        }
 
        len = strlen(newname);
        if (len == 0 || len >= BTRFS_VOL_NAME_MAX) {
-               fprintf(stderr, "ERROR: snapshot name too long ('%s)\n",
+               fprintf(stderr, "ERROR: snapshot name too long '%s'\n",
                        newname);
                goto out;
        }
 
        fddst = open_file_or_dir(dstdir, &dirstream1);
        if (fddst < 0) {
-               fprintf(stderr, "ERROR: can't access to '%s'\n", dstdir);
+               fprintf(stderr, "ERROR: can't access '%s'\n", dstdir);
                goto out;
        }
 
        fd = open_file_or_dir(subvol, &dirstream2);
        if (fd < 0) {
-               fprintf(stderr, "ERROR: can't access to '%s'\n", dstdir);
+               fprintf(stderr, "ERROR: can't access '%s'\n", dstdir);
                goto out;
        }
 
@@ -629,6 +719,8 @@ out:
        close_file_or_dir(fddst, dirstream1);
        close_file_or_dir(fd, dirstream2);
        free(inherit);
+       free(dupname);
+       free(dupdir);
 
        return retval;
 }
@@ -674,6 +766,7 @@ static int cmd_subvol_get_default(int argc, char **argv)
        /* no need to resolve roots if FS_TREE is default */
        if (default_id == BTRFS_FS_TREE_OBJECTID) {
                printf("ID 5 (FS_TREE)\n");
+               ret = 0;
                goto out;
        }
 
@@ -694,9 +787,7 @@ static int cmd_subvol_get_default(int argc, char **argv)
                btrfs_list_free_filter_set(filter_set);
 out:
        close_file_or_dir(fd, dirstream);
-       if (ret)
-               return 1;
-       return 0;
+       return !!ret;
 }
 
 static const char * const cmd_subvol_set_default_usage[] = {
@@ -719,15 +810,11 @@ static int cmd_subvol_set_default(int argc, char **argv)
        subvolid = argv[1];
        path = argv[2];
 
-       objectid = (unsigned long long)strtoll(subvolid, NULL, 0);
-       if (errno == ERANGE) {
-               fprintf(stderr, "ERROR: invalid tree id (%s)\n", subvolid);
-               return 1;
-       }
+       objectid = arg_strtou64(subvolid);
 
        fd = open_file_or_dir(path, &dirstream);
        if (fd < 0) {
-               fprintf(stderr, "ERROR: can't access to '%s'\n", path);
+               fprintf(stderr, "ERROR: can't access '%s'\n", path);
                return 1;
        }
 
@@ -760,28 +847,35 @@ static int cmd_find_new(int argc, char **argv)
                usage(cmd_find_new_usage);
 
        subvol = argv[1];
-       last_gen = atoll(argv[2]);
+       last_gen = arg_strtou64(argv[2]);
 
        ret = test_issubvolume(subvol);
        if (ret < 0) {
                fprintf(stderr, "ERROR: error accessing '%s'\n", subvol);
-               return 12;
+               return 1;
        }
        if (!ret) {
                fprintf(stderr, "ERROR: '%s' is not a subvolume\n", subvol);
-               return 13;
+               return 1;
        }
 
        fd = open_file_or_dir(subvol, &dirstream);
        if (fd < 0) {
                fprintf(stderr, "ERROR: can't access '%s'\n", subvol);
-               return 12;
+               return 1;
        }
+
+       ret = ioctl(fd, BTRFS_IOC_SYNC);
+       if (ret < 0) {
+               fprintf(stderr, "ERROR: unable to fs-syncing '%s' - %s\n",
+                       subvol, strerror(errno));
+               close_file_or_dir(fd, dirstream);
+               return 1;
+       }
+
        ret = btrfs_list_find_updated_files(fd, 0, last_gen);
        close_file_or_dir(fd, dirstream);
-       if (ret)
-               return 19;
-       return 0;
+       return !!ret;
 }
 
 static const char * const cmd_subvol_show_usage[] = {
@@ -795,12 +889,12 @@ 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[37];
+       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;
+       int ret = 1;
        DIR *dirstream1 = NULL, *dirstream2 = NULL;
 
        if (check_argc_exact(argc, 2))
@@ -820,17 +914,23 @@ static int cmd_subvol_show(int argc, char **argv)
        }
        if (!ret) {
                fprintf(stderr, "ERROR: '%s' is not a subvolume\n", fullpath);
-               ret = -1;
+               ret = 1;
                goto out;
        }
 
        ret = find_mount_root(fullpath, &mnt);
        if (ret < 0) {
-               fprintf(stderr, "ERROR: find_mount_root failed on %s: "
+               fprintf(stderr, "ERROR: find_mount_root failed on '%s': "
                                "%s\n", fullpath, strerror(-ret));
                goto out;
        }
-       ret = -1;
+       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);
@@ -866,13 +966,13 @@ static int cmd_subvol_show(int argc, char **argv)
        memset(&get_ri, 0, sizeof(get_ri));
        get_ri.root_id = sv_id;
 
-       if (btrfs_get_subvol(mntfd, &get_ri)) {
+       ret = btrfs_get_subvol(mntfd, &get_ri);
+       if (ret) {
                fprintf(stderr, "ERROR: can't find '%s'\n",
                        svpath);
                goto out;
        }
 
-       ret = 0;
        /* print the info */
        printf("%s\n", fullpath);
        printf("\tName: \t\t\t%s\n", get_ri.name);
@@ -919,24 +1019,248 @@ static int cmd_subvol_show(int argc, char **argv)
                        1, raw_prefix);
 
        /* clean up */
-       if (get_ri.path)
-               free(get_ri.path);
-       if (get_ri.name)
-               free(get_ri.name);
-       if (get_ri.full_path)
-               free(get_ri.full_path);
-       if (filter_set)
-               btrfs_list_free_filter_set(filter_set);
+       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);
-       if (mnt)
-               free(mnt);
-       if (fullpath)
-               free(fullpath);
+       free(mnt);
+       free(fullpath);
+       return !!ret;
+}
 
-       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 ongoing deletion requests",
+       "are complete. This may take long if new deleted subvolumes appear during",
+       "the sleep interval.",
+       "",
+       "-s <N>       sleep N seconds between checks (default: 1)",
+       NULL
+};
+
+static int is_subvolume_cleaned(int fd, u64 subvolid)
+{
+       int ret;
+       struct btrfs_ioctl_search_args args;
+       struct btrfs_ioctl_search_key *sk = &args.key;
+
+       sk->tree_id = BTRFS_ROOT_TREE_OBJECTID;
+       sk->min_objectid = subvolid;
+       sk->max_objectid = 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;
+
+       if (sk->nr_items == 0)
+               return 1;
+
+       return 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;
+}
+
+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 remaining;
+       int sleep_interval = 1;
+
+       optind = 1;
+       while (1) {
+               int c = getopt(argc, argv, "s:");
+
+               if (c < 0)
+                       break;
+
+               switch (c) {
+               case 's':
+                       sleep_interval = atoi(argv[optind]);
+                       if (sleep_interval < 1) {
+                               fprintf(stderr,
+                                       "ERROR: invalid sleep interval %s\n",
+                                       argv[optind]);
+                               ret = 1;
+                               goto out;
+                       }
+                       break;
+               default:
+                       usage(cmd_subvol_sync_usage);
+               }
+       }
+
+       if (check_argc_min(argc - optind, 1))
+               usage(cmd_subvol_sync_usage);
+
+       fd = open_file_or_dir(argv[optind], &dirstream);
+       if (fd < 0) {
+               fprintf(stderr, "ERROR: can't access '%s'\n", argv[optind]);
+               ret = 1;
+               goto out;
+       }
+       optind++;
+
+       id_count = argc - optind;
+
+       /*
+        * Wait for all
+        */
+       if (!id_count) {
+               while (1) {
+                       ret = fs_has_dead_subvolumes(fd);
+                       if (ret < 0) {
+                               fprintf(stderr, "ERROR: can't perform the search - %s\n",
+                                               strerror(-ret));
+                               ret = 1;
+                               goto out;
+                       }
+                       if (!ret)
+                               goto out;
+                       sleep(sleep_interval);
+               }
+       }
+
+       /*
+        * Wait only for the requested ones
+        */
+       ids = (u64*)malloc(sizeof(u64) * id_count);
+
+       if (!ids) {
+               fprintf(stderr, "ERROR: not enough memory\n");
+               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) {
+                       fprintf(stderr, "ERROR: unrecognized subvolume id %s\n",
+                               arg);
+                       ret = 1;
+                       goto out;
+               }
+               if (id < BTRFS_FIRST_FREE_OBJECTID || id > BTRFS_LAST_FREE_OBJECTID) {
+                       fprintf(stderr, "ERROR: subvolume id %s out of range\n",
+                               arg);
+                       ret = 1;
+                       goto out;
+               }
+               ids[i] = id;
+       }
+
+       remaining = id_count;
+       while (1) {
+               for (i = 0; i < id_count; i++) {
+                       if (!ids[i])
+                               continue;
+                       ret = is_subvolume_cleaned(fd, ids[i]);
+                       if (ret < 0) {
+                               fprintf(stderr, "ERROR: can't perform the search - %s\n",
+                                               strerror(-ret));
+                               goto out;
+                       }
+                       if (ret) {
+                               printf("Subvolume id %llu is gone\n", ids[i]);
+                               ids[i] = 0;
+                               remaining--;
+                       }
+               }
+               if (!remaining)
+                       break;
+               sleep(sleep_interval);
+       }
+
+out:
+       free(ids);
+       close_file_or_dir(fd, dirstream);
+
+       return !!ret;
 }
 
 const struct cmd_group subvolume_cmd_group = {
@@ -951,6 +1275,7 @@ const struct cmd_group subvolume_cmd_group = {
                        cmd_subvol_set_default_usage, NULL, 0 },
                { "find-new", cmd_find_new, cmd_find_new_usage, NULL, 0 },
                { "show", cmd_subvol_show, cmd_subvol_show_usage, NULL, 0 },
+               { "sync", cmd_subvol_sync, cmd_subvol_sync_usage, NULL, 0 },
                NULL_CMD_STRUCT
        }
 };