btrfs-progs: Remove deprecated btrfs-zero-log standalone tool
[platform/upstream/btrfs-progs.git] / cmds-subvolume.c
index 5e821c7..45363a5 100644 (file)
@@ -14,6 +14,7 @@
  * Boston, MA 021110-1307, USA.
  */
 
+#include <inttypes.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/ioctl.h>
 #include <errno.h>
 #include <sys/stat.h>
+#include <sys/vfs.h>
 #include <libgen.h>
 #include <limits.h>
 #include <getopt.h>
 #include <uuid/uuid.h>
+#include <linux/magic.h>
+
+#include <btrfsutil.h>
 
 #include "kerncompat.h"
 #include "ioctl.h"
 #include "utils.h"
 #include "btrfs-list.h"
 #include "utils.h"
+#include "help.h"
 
-static const char * const subvolume_cmd_group_usage[] = {
-       "btrfs subvolume <command> <args>",
-       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)
+static int wait_for_subvolume_cleaning(int fd, size_t count, uint64_t *ids,
+                                      int sleep_interval)
 {
-       struct stat     st;
-       int             res;
+       size_t i;
+       enum btrfs_util_error err;
 
-       res = stat(path, &st);
-       if(res < 0 )
-               return -1;
+       while (1) {
+               int clean = 1;
+
+               for (i = 0; i < count; i++) {
+                       if (!ids[i])
+                               continue;
+                       err = btrfs_util_subvolume_info_fd(fd, ids[i], NULL);
+                       if (err == BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND) {
+                               printf("Subvolume id %" PRIu64 " is gone\n",
+                                      ids[i]);
+                               ids[i] = 0;
+                       } else if (err) {
+                               error_btrfs_util(err);
+                               return -errno;
+                       } else {
+                               clean = 0;
+                       }
+               }
+               if (clean)
+                       break;
+               sleep(sleep_interval);
+       }
 
-       return S_ISDIR(st.st_mode);
+       return 0;
 }
 
+static const char * const subvolume_cmd_group_usage[] = {
+       "btrfs subvolume <command> <args>",
+       NULL
+};
+
 static const char * const cmd_subvol_create_usage[] = {
        "btrfs subvolume create [-i <qgroupid>] [<dest>/]<name>",
        "Create a subvolume",
@@ -83,7 +102,6 @@ static int cmd_subvol_create(int argc, char **argv)
        struct btrfs_qgroup_inherit *inherit = NULL;
        DIR     *dirstream = NULL;
 
-       optind = 1;
        while (1) {
                int c = getopt(argc, argv, "c:i:");
                if (c < 0)
@@ -116,8 +134,12 @@ static int cmd_subvol_create(int argc, char **argv)
 
        retval = 1;     /* failure */
        res = test_isdir(dst);
+       if (res < 0 && res != -ENOENT) {
+               error("cannot access %s: %s", dst, strerror(-res));
+               goto out;
+       }
        if (res >= 0) {
-               fprintf(stderr, "ERROR: '%s' exists\n", dst);
+               error("target path already exists: %s", dst);
                goto out;
        }
 
@@ -126,25 +148,20 @@ static int cmd_subvol_create(int argc, char **argv)
        dupdir = strdup(dst);
        dstdir = dirname(dupdir);
 
-       if (!strcmp(newname, ".") || !strcmp(newname, "..") ||
-            strchr(newname, '/') ){
-               fprintf(stderr, "ERROR: uncorrect subvolume name ('%s')\n",
-                       newname);
+       if (!test_issubvolname(newname)) {
+               error("invalid subvolume name: %s", newname);
                goto out;
        }
 
        len = strlen(newname);
        if (len == 0 || len >= BTRFS_VOL_NAME_MAX) {
-               fprintf(stderr, "ERROR: subvolume name too long ('%s)\n",
-                       newname);
+               error("subvolume name too long: %s", newname);
                goto out;
        }
 
-       fddst = open_file_or_dir(dstdir, &dirstream);
-       if (fddst < 0) {
-               fprintf(stderr, "ERROR: can't access '%s'\n", dstdir);
+       fddst = btrfs_open_dir(dstdir, &dirstream, 1);
+       if (fddst < 0)
                goto out;
-       }
 
        printf("Create subvolume '%s/%s'\n", dstdir, newname);
        if (inherit) {
@@ -167,8 +184,7 @@ static int cmd_subvol_create(int argc, char **argv)
        }
 
        if (res < 0) {
-               fprintf(stderr, "ERROR: cannot create subvolume - %s\n",
-                       strerror(errno));
+               error("cannot create subvolume: %m");
                goto out;
        }
 
@@ -182,33 +198,20 @@ out:
        return retval;
 }
 
-/*
- * test if path is a subvolume:
- * this function return
- * 0-> path exists but it is not a subvolume
- * 1-> path exists and it is  a subvolume
- * -1 -> path is unaccessible
- */
-int test_issubvolume(char *path)
+static int wait_for_commit(int fd)
 {
-       struct stat     st;
-       int             res;
+       enum btrfs_util_error err;
+       uint64_t transid;
 
-       res = stat(path, &st);
-       if(res < 0 )
+       err = btrfs_util_start_sync_fd(fd, &transid);
+       if (err)
                return -1;
 
-       return (st.st_ino == 256) && S_ISDIR(st.st_mode);
-}
-
-static int wait_for_commit(int fd)
-{
-       int ret;
+       err = btrfs_util_wait_sync_fd(fd, transid);
+       if (err)
+               return -1;
 
-       ret = ioctl(fd, BTRFS_IOC_START_SYNC, NULL);
-       if (ret < 0)
-               return ret;
-       return ioctl(fd, BTRFS_IOC_WAIT_SYNC, NULL);
+       return 0;
 }
 
 static const char * const cmd_subvol_delete_usage[] = {
@@ -223,41 +226,50 @@ static const char * const cmd_subvol_delete_usage[] = {
        "",
        "-c|--commit-after      wait for transaction commit at the end of the operation",
        "-C|--commit-each       wait for transaction commit after deleting each subvolume",
+       "-v|--verbose           verbose output of operations",
        NULL
 };
 
 static int cmd_subvol_delete(int argc, char **argv)
 {
-       int     res, len, e, ret = 0;
+       int res, 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 sync_mode = 0;
-       struct option long_options[] = {
-               {"commit-after", no_argument, NULL, 'c'},  /* sync mode 1 */
-               {"commit-each", no_argument, NULL, 'C'},  /* sync mode 2 */
-               {NULL, 0, NULL, 0}
-       };
-
-       optind = 1;
+       int verbose = 0;
+       int commit_mode = 0;
+       u8 fsid[BTRFS_FSID_SIZE];
+       char uuidbuf[BTRFS_UUID_UNPARSED_SIZE];
+       struct seen_fsid *seen_fsid_hash[SEEN_FSID_HASH_SIZE] = { NULL, };
+       enum { COMMIT_AFTER = 1, COMMIT_EACH = 2 };
+       enum btrfs_util_error err;
+
        while (1) {
                int c;
-
-               c = getopt_long(argc, argv, "cC", long_options, NULL);
+               static const struct option long_options[] = {
+                       {"commit-after", no_argument, NULL, 'c'},
+                       {"commit-each", no_argument, NULL, 'C'},
+                       {"verbose", no_argument, NULL, 'v'},
+                       {NULL, 0, NULL, 0}
+               };
+
+               c = getopt_long(argc, argv, "cCv", long_options, NULL);
                if (c < 0)
                        break;
 
                switch(c) {
                case 'c':
-                       sync_mode = 1;
+                       commit_mode = COMMIT_AFTER;
                        break;
                case 'C':
-                       sync_mode = 2;
+                       commit_mode = COMMIT_EACH;
+                       break;
+               case 'v':
+                       verbose++;
                        break;
                default:
                        usage(cmd_subvol_delete_usage);
@@ -267,23 +279,20 @@ static int cmd_subvol_delete(int argc, char **argv)
        if (check_argc_min(argc - optind, 1))
                usage(cmd_subvol_delete_usage);
 
-       printf("Transaction commit: %s\n",
-               !sync_mode ? "none (default)" :
-               sync_mode == 1 ? "at the end" : "after each");
+       if (verbose > 0) {
+               printf("Transaction commit: %s\n",
+                       !commit_mode ? "none (default)" :
+                       commit_mode == COMMIT_AFTER ? "at the end" : "after each");
+       }
 
        cnt = optind;
 
 again:
        path = argv[cnt];
 
-       res = test_issubvolume(path);
-       if (res < 0) {
-               fprintf(stderr, "ERROR: error accessing '%s'\n", path);
-               ret = 1;
-               goto out;
-       }
-       if (!res) {
-               fprintf(stderr, "ERROR: '%s' is not a subvolume\n", path);
+       err = btrfs_util_is_subvolume(path);
+       if (err) {
+               error_btrfs_util(err);
                ret = 1;
                goto out;
        }
@@ -291,8 +300,7 @@ again:
        cpath = realpath(path, NULL);
        if (!cpath) {
                ret = errno;
-               fprintf(stderr, "ERROR: finding real path for '%s': %s\n",
-                       path, strerror(errno));
+               error("cannot find real path for '%s': %m", path);
                goto out;
        }
        dupdname = strdup(cpath);
@@ -301,75 +309,97 @@ again:
        vname = basename(dupvname);
        free(cpath);
 
-       if (!strcmp(vname, ".") || !strcmp(vname, "..") ||
-            strchr(vname, '/')) {
-               fprintf(stderr, "ERROR: incorrect subvolume name ('%s')\n",
-                       vname);
-               ret = 1;
-               goto out;
-       }
-
-       len = strlen(vname);
-       if (len == 0 || len >= BTRFS_VOL_NAME_MAX) {
-               fprintf(stderr, "ERROR: snapshot name too long ('%s)\n",
-                       vname);
-               ret = 1;
-               goto out;
-       }
-
-       fd = open_file_or_dir(dname, &dirstream);
+       fd = btrfs_open_dir(dname, &dirstream, 1);
        if (fd < 0) {
-               fprintf(stderr, "ERROR: can't access '%s'\n", dname);
                ret = 1;
                goto out;
        }
 
-       printf("Delete subvolume '%s/%s'\n", dname, vname);
-       strncpy_null(args.name, vname);
-       res = ioctl(fd, BTRFS_IOC_SNAP_DESTROY, &args);
-       e = errno;
+       printf("Delete subvolume (%s): '%s/%s'\n",
+               commit_mode == COMMIT_EACH || (commit_mode == COMMIT_AFTER && cnt + 1 == argc)
+               ? "commit" : "no-commit", dname, vname);
 
-       if(res < 0 ){
-               fprintf( stderr, "ERROR: cannot delete '%s/%s' - %s\n",
-                       dname, vname, strerror(e));
+       err = btrfs_util_delete_subvolume_fd(fd, vname, 0);
+       if (err) {
+               error_btrfs_util(err);
                ret = 1;
                goto out;
        }
 
-       if (sync_mode == 1) {
+       if (commit_mode == COMMIT_EACH) {
                res = wait_for_commit(fd);
                if (res < 0) {
-                       fprintf(stderr,
-                               "ERROR: unable to wait for commit after '%s': %s\n",
-                               path, strerror(errno));
+                       error("unable to wait for commit after '%s': %m", path);
+                       ret = 1;
+               }
+       } else if (commit_mode == COMMIT_AFTER) {
+               res = get_fsid(dname, fsid, 0);
+               if (res < 0) {
+                       error("unable to get fsid for '%s': %s",
+                               path, strerror(-res));
+                       error(
+                       "delete succeeded but commit may not be done in the end");
                        ret = 1;
+                       goto out;
+               }
+
+               if (add_seen_fsid(fsid, seen_fsid_hash, fd, dirstream) == 0) {
+                       if (verbose > 0) {
+                               uuid_unparse(fsid, uuidbuf);
+                               printf("  new fs is found for '%s', fsid: %s\n",
+                                               path, uuidbuf);
+                       }
+                       /*
+                        * This is the first time a subvolume on this
+                        * filesystem is deleted, keep fd in order to issue
+                        * SYNC ioctl in the end
+                        */
+                       goto keep_fd;
                }
        }
 
 out:
+       close_file_or_dir(fd, dirstream);
+keep_fd:
+       fd = -1;
+       dirstream = NULL;
        free(dupdname);
        free(dupvname);
        dupdname = NULL;
        dupvname = NULL;
        cnt++;
-       if (cnt < argc) {
-               close_file_or_dir(fd, dirstream);
-               /* avoid double free */
-               fd = -1;
-               dirstream = NULL;
+       if (cnt < argc)
                goto again;
-       }
 
-       if (sync_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;
+       if (commit_mode == COMMIT_AFTER) {
+               int slot;
+
+               /*
+                * Traverse seen_fsid_hash and issue SYNC ioctl on each
+                * filesystem
+                */
+               for (slot = 0; slot < SEEN_FSID_HASH_SIZE; slot++) {
+                       struct seen_fsid *seen = seen_fsid_hash[slot];
+
+                       while (seen) {
+                               res = wait_for_commit(seen->fd);
+                               if (res < 0) {
+                                       uuid_unparse(seen->fsid, uuidbuf);
+                                       error(
+                       "unable to do final sync after deletion: %m, fsid: %s",
+                                               uuidbuf);
+                                       ret = 1;
+                               } else if (verbose > 0) {
+                                       uuid_unparse(seen->fsid, uuidbuf);
+                                       printf("final sync is done for fsid: %s\n",
+                                               uuidbuf);
+                               }
+                               seen = seen->next;
+                       }
                }
+               /* fd will also be closed in free_seen_fsid */
+               free_seen_fsid(seen_fsid_hash);
        }
-       close_file_or_dir(fd, dirstream);
 
        return ret;
 }
@@ -380,23 +410,32 @@ out:
  * - lowercase for enabling specific items in the output
  */
 static const char * const cmd_subvol_list_usage[] = {
-       "btrfs subvolume list [options] [-G [+|-]value] [-C [+|-]value] "
-       "[--sort=gen,ogen,rootid,path] <path>",
-       "List subvolumes (and snapshots)",
+       "btrfs subvolume list [options] <path>",
+       "List subvolumes and snapshots in the filesystem.",
        "",
-       "-p           print parent ID",
+       "Path filtering:",
+       "-o           print only subvolumes below specified path",
        "-a           print all the subvolumes in the filesystem and",
        "             distinguish absolute and relative path with respect",
        "             to the given <path>",
+       "",
+       "Field selection:",
+       "-p           print parent ID",
        "-c           print the ogeneration of the subvolume",
        "-g           print the generation of the subvolume",
-       "-o           print only subvolumes bellow specified path",
        "-u           print the uuid of subvolumes (and snapshots)",
        "-q           print the parent uuid of the snapshots",
-       "-t           print the result as a table",
-       "-s           list snapshots only in the filesystem",
+       "-R           print the uuid of the received snapshots",
+       "",
+       "Type filtering:",
+       "-s           list only snapshots",
        "-r           list readonly subvolumes (including snapshots)",
        "-d           list deleted subvolumes that are not yet cleaned",
+       "",
+       "Other:",
+       "-t           print the result as a table",
+       "",
+       "Sorting:",
        "-G [+|-]value",
        "             filter the subvolumes by generation",
        "             (+value: >= value; -value: <= value; value: = value)",
@@ -418,24 +457,24 @@ static int cmd_subvol_list(int argc, char **argv)
        int fd = -1;
        u64 top_id;
        int ret = -1, uerr = 0;
-       int c;
        char *subvol;
-       int is_tab_result = 0;
        int is_list_all = 0;
        int is_only_in_path = 0;
-       struct option long_options[] = {
-               {"sort", 1, NULL, 'S'},
-               {NULL, 0, NULL, 0}
-       };
        DIR *dirstream = NULL;
+       enum btrfs_list_layout layout = BTRFS_LIST_LAYOUT_DEFAULT;
 
        filter_set = btrfs_list_alloc_filter_set();
        comparer_set = btrfs_list_alloc_comparer_set();
 
-       optind = 1;
        while(1) {
+               int c;
+               static const struct option long_options[] = {
+                       {"sort", required_argument, NULL, 'S'},
+                       {NULL, 0, NULL, 0}
+               };
+
                c = getopt_long(argc, argv,
-                                   "acdgopqsurG:C:t", long_options, NULL);
+                                   "acdgopqsurRG:C:t", long_options, NULL);
                if (c < 0)
                        break;
 
@@ -461,7 +500,7 @@ static int cmd_subvol_list(int argc, char **argv)
                        is_only_in_path = 1;
                        break;
                case 't':
-                       is_tab_result = 1;
+                       layout = BTRFS_LIST_LAYOUT_TABLE;
                        break;
                case 's':
                        btrfs_list_setup_filter(&filter_set,
@@ -476,6 +515,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;
@@ -515,28 +557,26 @@ static int cmd_subvol_list(int argc, char **argv)
                }
        }
 
-       if (flags)
-               btrfs_list_setup_filter(&filter_set, BTRFS_LIST_FILTER_FLAGS,
-                                       flags);
-
        if (check_argc_exact(argc - optind, 1)) {
                uerr = 1;
                goto out;
        }
 
        subvol = argv[optind];
-       fd = open_file_or_dir(subvol, &dirstream);
+       fd = btrfs_open_dir(subvol, &dirstream, 1);
        if (fd < 0) {
                ret = -1;
-               fprintf(stderr, "ERROR: can't access '%s'\n", subvol);
+               error("can't access '%s'", subvol);
                goto out;
        }
 
+       if (flags)
+               btrfs_list_setup_filter(&filter_set, BTRFS_LIST_FILTER_FLAGS,
+                                       flags);
+
        ret = btrfs_list_get_path_rootid(fd, &top_id);
-       if (ret) {
-               fprintf(stderr, "ERROR: can't get rootid for '%s'\n", subvol);
+       if (ret)
                goto out;
-       }
 
        if (is_list_all)
                btrfs_list_setup_filter(&filter_set,
@@ -553,28 +593,21 @@ static int cmd_subvol_list(int argc, char **argv)
        btrfs_list_setup_print_column(BTRFS_LIST_TOP_LEVEL);
        btrfs_list_setup_print_column(BTRFS_LIST_PATH);
 
-       if (is_tab_result)
-               ret = btrfs_list_subvols_print(fd, filter_set, comparer_set,
-                               BTRFS_LIST_LAYOUT_TABLE,
-                               !is_list_all && !is_only_in_path, NULL);
-       else
-               ret = btrfs_list_subvols_print(fd, filter_set, comparer_set,
-                               BTRFS_LIST_LAYOUT_DEFAULT,
-                               !is_list_all && !is_only_in_path, NULL);
+       ret = btrfs_list_subvols_print(fd, filter_set, comparer_set,
+                       layout, !is_list_all && !is_only_in_path, NULL);
 
 out:
        close_file_or_dir(fd, dirstream);
        if (filter_set)
-               btrfs_list_free_filter_set(filter_set);
+               free(filter_set);
        if (comparer_set)
-               btrfs_list_free_comparer_set(comparer_set);
+               free(comparer_set);
        if (uerr)
                usage(cmd_subvol_list_usage);
        return !!ret;
 }
 
-static const char * const cmd_snapshot_usage[] = {
-       "btrfs subvolume snapshot [-r] <source> <dest>|[<dest>/]<name>",
+static const char * const cmd_subvol_snapshot_usage[] = {
        "btrfs subvolume snapshot [-r] [-i <qgroupid>] <source> <dest>|[<dest>/]<name>",
        "Create a snapshot of the subvolume",
        "Create a writable/readonly snapshot of the subvolume <source> with",
@@ -587,7 +620,7 @@ static const char * const cmd_snapshot_usage[] = {
        NULL
 };
 
-static int cmd_snapshot(int argc, char **argv)
+static int cmd_subvol_snapshot(int argc, char **argv)
 {
        char    *subvol, *dst;
        int     res, retval;
@@ -597,11 +630,11 @@ static int cmd_snapshot(int argc, char **argv)
        char    *dupdir = NULL;
        char    *newname;
        char    *dstdir;
+       enum btrfs_util_error err;
        struct btrfs_ioctl_vol_args_v2  args;
        struct btrfs_qgroup_inherit *inherit = NULL;
        DIR *dirstream1 = NULL, *dirstream2 = NULL;
 
-       optind = 1;
        memset(&args, 0, sizeof(args));
        while (1) {
                int c = getopt(argc, argv, "c:i:r");
@@ -634,30 +667,30 @@ static int cmd_snapshot(int argc, char **argv)
                        }
                        break;
                default:
-                       usage(cmd_snapshot_usage);
+                       usage(cmd_subvol_snapshot_usage);
                }
        }
 
        if (check_argc_exact(argc - optind, 2))
-               usage(cmd_snapshot_usage);
+               usage(cmd_subvol_snapshot_usage);
 
        subvol = argv[optind];
        dst = argv[optind + 1];
 
        retval = 1;     /* failure */
-       res = test_issubvolume(subvol);
-       if (res < 0) {
-               fprintf(stderr, "ERROR: error accessing '%s'\n", subvol);
-               goto out;
-       }
-       if (!res) {
-               fprintf(stderr, "ERROR: '%s' is not a subvolume\n", subvol);
+       err = btrfs_util_is_subvolume(subvol);
+       if (err) {
+               error_btrfs_util(err);
                goto out;
        }
 
        res = test_isdir(dst);
+       if (res < 0 && res != -ENOENT) {
+               error("cannot access %s: %s", dst, strerror(-res));
+               goto out;
+       }
        if (res == 0) {
-               fprintf(stderr, "ERROR: '%s' exists and it is not a directory\n", dst);
+               error("'%s' exists and it is not a directory", dst);
                goto out;
        }
 
@@ -672,31 +705,24 @@ static int cmd_snapshot(int argc, char **argv)
                dstdir = dirname(dupdir);
        }
 
-       if (!strcmp(newname, ".") || !strcmp(newname, "..") ||
-            strchr(newname, '/') ){
-               fprintf(stderr, "ERROR: incorrect snapshot name ('%s')\n",
-                       newname);
+       if (!test_issubvolname(newname)) {
+               error("invalid snapshot name '%s'", newname);
                goto out;
        }
 
        len = strlen(newname);
        if (len == 0 || len >= BTRFS_VOL_NAME_MAX) {
-               fprintf(stderr, "ERROR: snapshot name too long ('%s)\n",
-                       newname);
+               error("snapshot name too long '%s'", newname);
                goto out;
        }
 
-       fddst = open_file_or_dir(dstdir, &dirstream1);
-       if (fddst < 0) {
-               fprintf(stderr, "ERROR: can't access '%s'\n", dstdir);
+       fddst = btrfs_open_dir(dstdir, &dirstream1, 1);
+       if (fddst < 0)
                goto out;
-       }
 
-       fd = open_file_or_dir(subvol, &dirstream2);
-       if (fd < 0) {
-               fprintf(stderr, "ERROR: can't access '%s'\n", dstdir);
+       fd = btrfs_open_dir(subvol, &dirstream2, 1);
+       if (fd < 0)
                goto out;
-       }
 
        if (readonly) {
                args.flags |= BTRFS_SUBVOL_RDONLY;
@@ -718,8 +744,7 @@ static int cmd_snapshot(int argc, char **argv)
        res = ioctl(fddst, BTRFS_IOC_SNAP_CREATE_V2, &args);
 
        if (res < 0) {
-               fprintf( stderr, "ERROR: cannot snapshot '%s' - %s\n",
-                       subvol, strerror(errno));
+               error("cannot snapshot '%s': %m", subvol);
                goto out;
        }
 
@@ -744,32 +769,25 @@ static const char * const cmd_subvol_get_default_usage[] = {
 static int cmd_subvol_get_default(int argc, char **argv)
 {
        int fd = -1;
-       int ret;
-       char *subvol;
-       struct btrfs_list_filter_set *filter_set;
-       u64 default_id;
+       int ret = 1;
+       uint64_t default_id;
        DIR *dirstream = NULL;
+       enum btrfs_util_error err;
+       struct btrfs_util_subvolume_info subvol;
+       char *path;
 
-       if (check_argc_exact(argc, 2))
+       clean_args_no_options(argc, argv, cmd_subvol_get_default_usage);
+
+       if (check_argc_exact(argc - optind, 1))
                usage(cmd_subvol_get_default_usage);
 
-       subvol = argv[1];
-       fd = open_file_or_dir(subvol, &dirstream);
-       if (fd < 0) {
-               fprintf(stderr, "ERROR: can't access '%s'\n", subvol);
+       fd = btrfs_open_dir(argv[1], &dirstream, 1);
+       if (fd < 0)
                return 1;
-       }
-
-       ret = btrfs_list_get_default_subvolume(fd, &default_id);
-       if (ret) {
-               fprintf(stderr, "ERROR: can't perform the search - %s\n",
-                       strerror(errno));
-               goto out;
-       }
 
-       ret = 1;
-       if (default_id == 0) {
-               fprintf(stderr, "ERROR: 'default' dir item not found\n");
+       err = btrfs_util_get_default_subvolume_fd(fd, &default_id);
+       if (err) {
+               error_btrfs_util(err);
                goto out;
        }
 
@@ -780,105 +798,104 @@ static int cmd_subvol_get_default(int argc, char **argv)
                goto out;
        }
 
-       filter_set = btrfs_list_alloc_filter_set();
-       btrfs_list_setup_filter(&filter_set, BTRFS_LIST_FILTER_ROOTID,
-                               default_id);
+       err = btrfs_util_subvolume_info_fd(fd, default_id, &subvol);
+       if (err) {
+               error_btrfs_util(err);
+               goto out;
+       }
 
-       /* by default we shall print the following columns*/
-       btrfs_list_setup_print_column(BTRFS_LIST_OBJECTID);
-       btrfs_list_setup_print_column(BTRFS_LIST_GENERATION);
-       btrfs_list_setup_print_column(BTRFS_LIST_TOP_LEVEL);
-       btrfs_list_setup_print_column(BTRFS_LIST_PATH);
+       err = btrfs_util_subvolume_path_fd(fd, default_id, &path);
+       if (err) {
+               error_btrfs_util(err);
+               goto out;
+       }
 
-       ret = btrfs_list_subvols_print(fd, filter_set, NULL,
-               BTRFS_LIST_LAYOUT_DEFAULT, 1, NULL);
+       printf("ID %" PRIu64 " gen %" PRIu64 " top level %" PRIu64 " path %s\n",
+              subvol.id, subvol.generation, subvol.parent_id, path);
 
-       if (filter_set)
-               btrfs_list_free_filter_set(filter_set);
+       free(path);
+
+       ret = 0;
 out:
        close_file_or_dir(fd, dirstream);
-       return !!ret;
+       return ret;
 }
 
 static const char * const cmd_subvol_set_default_usage[] = {
+       "btrfs subvolume set-default <subvolume>\n"
        "btrfs subvolume set-default <subvolid> <path>",
-       "Set the default subvolume of a filesystem",
+       "Set the default subvolume of the filesystem mounted as default.",
+       "The subvolume can be specified by its path,",
+       "or the pair of subvolume id and path to the filesystem.",
        NULL
 };
 
 static int cmd_subvol_set_default(int argc, char **argv)
 {
-       int     ret=0, fd, e;
-       u64     objectid;
-       char    *path;
-       char    *subvolid;
-       DIR     *dirstream = NULL;
-
-       if (check_argc_exact(argc, 3))
-               usage(cmd_subvol_set_default_usage);
+       u64 objectid;
+       char *path;
+       enum btrfs_util_error err;
 
-       subvolid = argv[1];
-       path = argv[2];
+       clean_args_no_options(argc, argv, cmd_subvol_set_default_usage);
 
-       objectid = arg_strtou64(subvolid);
+       if (check_argc_min(argc - optind, 1) ||
+                       check_argc_max(argc - optind, 2))
+               usage(cmd_subvol_set_default_usage);
 
-       fd = open_file_or_dir(path, &dirstream);
-       if (fd < 0) {
-               fprintf(stderr, "ERROR: can't access '%s'\n", path);
-               return 1;
+       if (argc - optind == 1) {
+               /* path to the subvolume is specified */
+               objectid = 0;
+               path = argv[optind];
+       } else {
+               /* subvol id and path to the filesystem are specified */
+               objectid = arg_strtou64(argv[optind]);
+               path = argv[optind + 1];
        }
 
-       ret = ioctl(fd, BTRFS_IOC_DEFAULT_SUBVOL, &objectid);
-       e = errno;
-       close_file_or_dir(fd, dirstream);
-       if (ret < 0) {
-               fprintf(stderr, "ERROR: unable to set a new default subvolume - %s\n",
-                       strerror(e));
+       err = btrfs_util_set_default_subvolume(path, objectid);
+       if (err) {
+               error_btrfs_util(err);
                return 1;
        }
        return 0;
 }
 
-static const char * const cmd_find_new_usage[] = {
+static const char * const cmd_subvol_find_new_usage[] = {
        "btrfs subvolume find-new <path> <lastgen>",
        "List the recently modified files in a filesystem",
        NULL
 };
 
-static int cmd_find_new(int argc, char **argv)
+static int cmd_subvol_find_new(int argc, char **argv)
 {
        int fd;
        int ret;
        char *subvol;
        u64 last_gen;
        DIR *dirstream = NULL;
+       enum btrfs_util_error err;
 
-       if (check_argc_exact(argc, 3))
-               usage(cmd_find_new_usage);
+       clean_args_no_options(argc, argv, cmd_subvol_find_new_usage);
 
-       subvol = argv[1];
-       last_gen = arg_strtou64(argv[2]);
+       if (check_argc_exact(argc - optind, 2))
+               usage(cmd_subvol_find_new_usage);
 
-       ret = test_issubvolume(subvol);
-       if (ret < 0) {
-               fprintf(stderr, "ERROR: error accessing '%s'\n", subvol);
-               return 1;
-       }
-       if (!ret) {
-               fprintf(stderr, "ERROR: '%s' is not a subvolume\n", subvol);
+       subvol = argv[optind];
+       last_gen = arg_strtou64(argv[optind + 1]);
+
+       err = btrfs_util_is_subvolume(subvol);
+       if (err) {
+               error_btrfs_util(err);
                return 1;
        }
 
-       fd = open_file_or_dir(subvol, &dirstream);
-       if (fd < 0) {
-               fprintf(stderr, "ERROR: can't access '%s'\n", subvol);
+       fd = btrfs_open_dir(subvol, &dirstream, 1);
+       if (fd < 0)
                return 1;
-       }
 
-       ret = ioctl(fd, BTRFS_IOC_SYNC);
-       if (ret < 0) {
-               fprintf(stderr, "ERROR: unable to fs-syncing '%s' - %s\n",
-                       subvol, strerror(errno));
+       err = btrfs_util_sync_fd(fd);
+       if (err) {
+               error_btrfs_util(err);
                close_file_or_dir(fd, dirstream);
                return 1;
        }
@@ -889,164 +906,331 @@ static int cmd_find_new(int argc, char **argv)
 }
 
 static const char * const cmd_subvol_show_usage[] = {
-       "btrfs subvolume show <subvol-path>",
-       "Show more information of the subvolume",
+       "btrfs subvolume show [options] <subvol-path>|<mnt>",
+       "Show more information about the subvolume",
+       "-r|--rootid   rootid of the subvolume",
+       "-u|--uuid     uuid of the subvolume",
+       "",
+       "If no option is specified, <subvol-path> will be shown, otherwise",
+       "the rootid or uuid are resolved relative to the <mnt> path.",
        NULL
 };
 
 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;
+       char *fullpath = NULL;
+       int fd = -1;
        int ret = 1;
-       DIR *dirstream1 = NULL, *dirstream2 = NULL;
+       DIR *dirstream1 = NULL;
+       int by_rootid = 0;
+       int by_uuid = 0;
+       u64 rootid_arg = 0;
+       u8 uuid_arg[BTRFS_UUID_SIZE];
+       struct btrfs_util_subvolume_iterator *iter;
+       struct btrfs_util_subvolume_info subvol;
+       char *subvol_path = NULL;
+       enum btrfs_util_error err;
 
-       if (check_argc_exact(argc, 2))
-               usage(cmd_subvol_show_usage);
+       while (1) {
+               int c;
+               static const struct option long_options[] = {
+                       { "rootid", required_argument, NULL, 'r'},
+                       { "uuid", required_argument, NULL, 'u'},
+                       { NULL, 0, NULL, 0 }
+               };
 
-       fullpath = realpath(argv[1], NULL);
-       if (!fullpath) {
-               fprintf(stderr, "ERROR: finding real path for '%s', %s\n",
-                       argv[1], strerror(errno));
-               goto out;
-       }
+               c = getopt_long(argc, argv, "r:u:", long_options, NULL);
+               if (c < 0)
+                       break;
 
-       ret = test_issubvolume(fullpath);
-       if (ret < 0) {
-               fprintf(stderr, "ERROR: error accessing '%s'\n", fullpath);
-               goto out;
+               switch (c) {
+               case 'r':
+                       rootid_arg = arg_strtou64(optarg);
+                       by_rootid = 1;
+                       break;
+               case 'u':
+                       uuid_parse(optarg, uuid_arg);
+                       by_uuid = 1;
+                       break;
+               default:
+                       usage(cmd_subvol_show_usage);
+               }
        }
-       if (!ret) {
-               fprintf(stderr, "ERROR: '%s' is not a subvolume\n", fullpath);
-               goto out;
+
+       if (check_argc_exact(argc - optind, 1))
+               usage(cmd_subvol_show_usage);
+
+       if (by_rootid && by_uuid) {
+               error(
+               "options --rootid and --uuid cannot be used at the same time");
+               usage(cmd_subvol_show_usage);
        }
 
-       ret = find_mount_root(fullpath, &mnt);
-       if (ret < 0) {
-               fprintf(stderr, "ERROR: find_mount_root failed on %s: "
-                               "%s\n", fullpath, strerror(-ret));
+       fullpath = realpath(argv[optind], NULL);
+       if (!fullpath) {
+               error("cannot find real path for '%s': %m", argv[optind]);
                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);
+               error("can't access '%s'", 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;
-       }
+       if (by_uuid) {
+               err = btrfs_util_create_subvolume_iterator_fd(fd,
+                                                             BTRFS_FS_TREE_OBJECTID,
+                                                             0, &iter);
+               if (err) {
+                       error_btrfs_util(err);
+                       goto out;
+               }
 
-       mntfd = open_file_or_dir(mnt, &dirstream2);
-       if (mntfd < 0) {
-               fprintf(stderr, "ERROR: can't access '%s'\n", mnt);
-               goto out;
-       }
+               for (;;) {
+                       err = btrfs_util_subvolume_iterator_next_info(iter,
+                                                                     &subvol_path,
+                                                                     &subvol);
+                       if (err == BTRFS_UTIL_ERROR_STOP_ITERATION) {
+                               uuid_unparse(uuid_arg, uuidparse);
+                               error("can't find uuid '%s' on '%s'", uuidparse,
+                                     fullpath);
+                               btrfs_util_destroy_subvolume_iterator(iter);
+                               goto out;
+                       } else if (err) {
+                               error_btrfs_util(err);
+                               btrfs_util_destroy_subvolume_iterator(iter);
+                               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 (uuid_compare(subvol.uuid, uuid_arg) == 0)
+                               break;
 
-       if (sv_id == BTRFS_FS_TREE_OBJECTID) {
-               printf("%s is btrfs root\n", fullpath);
-               goto out;
-       }
+                       free(subvol_path);
+               }
+               btrfs_util_destroy_subvolume_iterator(iter);
+       } else {
+               /*
+                * If !by_rootid, rootid_arg = 0, which means find the
+                * subvolume ID of the fd and use that.
+                */
+               err = btrfs_util_subvolume_info_fd(fd, rootid_arg, &subvol);
+               if (err) {
+                       error_btrfs_util(err);
+                       goto out;
+               }
 
-       memset(&get_ri, 0, sizeof(get_ri));
-       get_ri.root_id = sv_id;
+               err = btrfs_util_subvolume_path_fd(fd, subvol.id, &subvol_path);
+               if (err) {
+                       error_btrfs_util(err);
+                       goto out;
+               }
 
-       if (btrfs_get_subvol(mntfd, &get_ri)) {
-               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);
+       printf("%s\n", subvol.id == BTRFS_FS_TREE_OBJECTID ? "/" : subvol_path);
+       printf("\tName: \t\t\t%s\n",
+              (subvol.id == BTRFS_FS_TREE_OBJECTID ? "<FS_TREE>" :
+               basename(subvol_path)));
+
+       if (uuid_is_null(subvol.uuid))
+               strcpy(uuidparse, "-");
+       else
+               uuid_unparse(subvol.uuid, uuidparse);
+       printf("\tUUID: \t\t\t%s\n", uuidparse);
 
-       if (uuid_is_null(get_ri.uuid))
+       if (uuid_is_null(subvol.parent_uuid))
                strcpy(uuidparse, "-");
        else
-               uuid_unparse(get_ri.uuid, uuidparse);
-       printf("\tuuid: \t\t\t%s\n", uuidparse);
+               uuid_unparse(subvol.parent_uuid, uuidparse);
+       printf("\tParent UUID: \t\t%s\n", uuidparse);
 
-       if (uuid_is_null(get_ri.puuid))
+       if (uuid_is_null(subvol.received_uuid))
                strcpy(uuidparse, "-");
        else
-               uuid_unparse(get_ri.puuid, uuidparse);
-       printf("\tParent uuid: \t\t%s\n", uuidparse);
+               uuid_unparse(subvol.received_uuid, uuidparse);
+       printf("\tReceived UUID: \t\t%s\n", uuidparse);
 
-       if (get_ri.otime) {
+       if (subvol.otime.tv_sec) {
                struct tm tm;
 
-               localtime_r(&get_ri.otime, &tm);
-               strftime(tstr, 256, "%Y-%m-%d %X", &tm);
+               localtime_r(&subvol.otime.tv_sec, &tm);
+               strftime(tstr, 256, "%Y-%m-%d %X %z", &tm);
        } else
                strcpy(tstr, "-");
        printf("\tCreation time: \t\t%s\n", tstr);
 
-       printf("\tObject ID: \t\t%llu\n", get_ri.root_id);
-       printf("\tGeneration (Gen): \t%llu\n", get_ri.gen);
-       printf("\tGen at creation: \t%llu\n", get_ri.ogen);
-       printf("\tParent: \t\t%llu\n", get_ri.ref_tree);
-       printf("\tTop Level: \t\t%llu\n", get_ri.top_id);
+       printf("\tSubvolume ID: \t\t%" PRIu64 "\n", subvol.id);
+       printf("\tGeneration: \t\t%" PRIu64 "\n", subvol.generation);
+       printf("\tGen at creation: \t%" PRIu64 "\n", subvol.otransid);
+       printf("\tParent ID: \t\t%" PRIu64 "\n", subvol.parent_id);
+       printf("\tTop level ID: \t\t%" PRIu64 "\n", subvol.parent_id);
 
-       if (get_ri.flags & BTRFS_ROOT_SUBVOL_RDONLY)
+       if (subvol.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);
+       err = btrfs_util_create_subvolume_iterator_fd(fd,
+                                                     BTRFS_FS_TREE_OBJECTID, 0,
+                                                     &iter);
 
+       for (;;) {
+               struct btrfs_util_subvolume_info subvol2;
+               char *path;
+
+               err = btrfs_util_subvolume_iterator_next_info(iter, &path, &subvol2);
+               if (err == BTRFS_UTIL_ERROR_STOP_ITERATION) {
+                       break;
+               } else if (err) {
+                       error_btrfs_util(err);
+                       btrfs_util_destroy_subvolume_iterator(iter);
+                       goto out;
+               }
+
+               if (uuid_compare(subvol2.parent_uuid, subvol.uuid) == 0)
+                       printf("\t\t\t\t%s\n", path);
+
+               free(path);
+       }
+       btrfs_util_destroy_subvolume_iterator(iter);
+
+       ret = 0;
 out:
+       free(subvol_path);
        close_file_or_dir(fd, dirstream1);
-       close_file_or_dir(mntfd, dirstream2);
-       free(mnt);
        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
+};
+
+static int cmd_subvol_sync(int argc, char **argv)
+{
+       int fd = -1;
+       int ret = 1;
+       DIR *dirstream = NULL;
+       uint64_t *ids = NULL;
+       size_t id_count, i;
+       int sleep_interval = 1;
+       enum btrfs_util_error err;
+
+       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) {
+               err = btrfs_util_deleted_subvolumes_fd(fd, &ids, &id_count);
+               if (err) {
+                       error_btrfs_util(err);
+                       ret = 1;
+                       goto out;
+               }
+               if (id_count == 0) {
+                       ret = 0;
+                       goto out;
+               }
+       } else {
+               ids = malloc(id_count * sizeof(uint64_t));
+               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) {
+                               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);
+
+out:
+       free(ids);
+       close_file_or_dir(fd, dirstream);
+
+       return !!ret;
+}
+
+static const char subvolume_cmd_group_info[] =
+"manage subvolumes: create, delete, list, etc";
+
 const struct cmd_group subvolume_cmd_group = {
-       subvolume_cmd_group_usage, NULL, {
+       subvolume_cmd_group_usage, subvolume_cmd_group_info, {
                { "create", cmd_subvol_create, cmd_subvol_create_usage, NULL, 0 },
                { "delete", cmd_subvol_delete, cmd_subvol_delete_usage, NULL, 0 },
                { "list", cmd_subvol_list, cmd_subvol_list_usage, NULL, 0 },
-               { "snapshot", cmd_snapshot, cmd_snapshot_usage, NULL, 0 },
+               { "snapshot", cmd_subvol_snapshot, cmd_subvol_snapshot_usage,
+                       NULL, 0 },
                { "get-default", cmd_subvol_get_default,
                        cmd_subvol_get_default_usage, NULL, 0 },
                { "set-default", cmd_subvol_set_default,
                        cmd_subvol_set_default_usage, NULL, 0 },
-               { "find-new", cmd_find_new, cmd_find_new_usage, NULL, 0 },
+               { "find-new", cmd_subvol_find_new, cmd_subvol_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
        }
 };