btrfs-progs: print B for bytes
[platform/upstream/btrfs-progs.git] / cmds-subvolume.c
index 0d4a52e..fa58a24 100644 (file)
@@ -369,9 +369,10 @@ 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)",
@@ -414,7 +415,7 @@ static int cmd_subvol_list(int argc, char **argv)
        optind = 1;
        while(1) {
                c = getopt_long(argc, argv,
-                                   "acdgopqsurG:C:t", long_options, NULL);
+                                   "acdgopqsurRG:C:t", long_options, NULL);
                if (c < 0)
                        break;
 
@@ -455,6 +456,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;
@@ -1020,6 +1024,237 @@ out:
        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 = {
        subvolume_cmd_group_usage, NULL, {
                { "create", cmd_subvol_create, cmd_subvol_create_usage, NULL, 0 },
@@ -1032,6 +1267,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
        }
 };