btrfs-progs: wait until all subvolumes are cleaned
authorDavid Sterba <dsterba@suse.cz>
Wed, 23 Jul 2014 20:56:13 +0000 (22:56 +0200)
committerDavid Sterba <dsterba@suse.cz>
Fri, 10 Oct 2014 07:32:05 +0000 (09:32 +0200)
Enhance the 'subvolume' subcommand to wait until a given list of
subvolumes or all currently scheduled for deletion are cleaned
completely from the filesystem.

Signed-off-by: David Sterba <dsterba@suse.cz>
Documentation/btrfs-subvolume.txt
cmds-subvolume.c

index 231f487..1360aba 100644 (file)
@@ -153,6 +153,17 @@ List the recently modified files in a subvolume, after <last_gen> ID.
 *show* <path>::
 Show information of a given subvolume in the <path>.
 
+*sync* <path> [subvolid...]::
+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.
++
+`Options`
++
+-s <N>::::
+sleep N seconds between checks (default: 1)
+
 EXIT STATUS
 -----------
 *btrfs subvolume* returns a zero exit status if it succeeds. Non zero is
index 45712ac..fa58a24 100644 (file)
@@ -1024,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 },
@@ -1036,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
        }
 };