]> git.sesse.net Git - bcachefs-tools-debian/blobdiff - bcacheadm.c
bcacheadm: add command to modify sysfs attributes for
[bcachefs-tools-debian] / bcacheadm.c
index 29a66572dbce0b47b21f2a1c7ab2f2af77ebc428..67f26b17f774294742b2d629138173d467dcdc79 100644 (file)
 #define MAX_DEVS MAX_CACHES_PER_SET
 
 
-/* bcacheadm globals */
-enum exit {
-       EXIT_OK = 0,            /* Ok */
-       EXIT_ERROR = 1,         /* General/OS error */
-       EXIT_SHELL = 2,         /* Start maintenance shell */
-       EXIT_SHELL_REBOOT = 3,  /* Start maintenance shell, reboot when done */
-       EXIT_REBOOT = 4,        /* System must reboot */
-};
-
-
 /* make-bcache globals */
 int bdev = -1;
 int devs = 0;
-const char *cache_devices[MAX_DEVS];
-const char *backing_devices[MAX_DEVS];
-const char *backing_dev_labels[MAX_DEVS];
-size_t i, nr_backing_devices = 0;
+char *cache_devices[MAX_DEVS];
+int tier_mapping[MAX_DEVS];
+char *backing_devices[MAX_DEVS];
+char *backing_dev_labels[MAX_DEVS];
+size_t i, nr_backing_devices = 0, nr_cache_devices = 0;
 unsigned block_size = 0;
 unsigned bucket_sizes[MAX_DEVS];
 int num_bucket_sizes = 0;
 int writeback = 0, discard = 0, wipe_bcache = 0;
-unsigned replication_set = 0, tier = 0, replacement_policy = 0;
+unsigned replication_set = 0, replacement_policy = 0;
 uint64_t data_offset = BDEV_DATA_START_DEFAULT;
 char *label = NULL;
 struct cache_sb *cache_set_sb = NULL;
-enum long_opts {
-       CACHE_SET_UUID = 256,
-       CSUM_TYPE,
-       REPLICATION_SET,
-       META_REPLICAS,
-       DATA_REPLICAS,
-};
+const char *cache_set_uuid = 0;
+const char *csum_type = 0;
+char *metadata_replicas = 0;
+char *data_replicas = 0;
+char *tier = 0;
+
+/* add-dev globals */
+char *add_dev_uuid = NULL;
 
+/* rm-dev globals */
+bool force_remove = false;
 
-/* super-show globals */
+/* Modify globals */
+bool modify_list_attrs = false;
+static const char *modify_set_uuid = NULL;
+static const char *modify_dev_uuid = NULL;
+
+/* query-dev globals */
 bool force_csum = false;
+bool uuid_only = false;
 
 /* probe globals */
 bool udev = false;
 
 /* list globals */
 char *cset_dir = "/sys/fs/bcache";
+bool list_devs = false;
 
 /* status globals */
 bool status_all = false;
@@ -87,6 +88,7 @@ bool status_all = false;
 bool stats_all = false;
 bool stats_list = false;
 static const char *stats_uuid = NULL;
+static const char *stats_dev_uuid = NULL;
 static const char *stats_cache_num = NULL;
 bool stats_five_min = false;
 bool stats_hour = false;
@@ -94,64 +96,47 @@ bool stats_day = false;
 bool stats_total = false;
 
 /* make-bcache option setters */
-static int set_CACHE_SET_UUID(NihOption *option, const char *arg)
+static int set_block_size(NihOption *option, const char *arg)
 {
-       if(uuid_parse(arg, cache_set_sb->set_uuid.b)){
-               fprintf(stderr, "Bad uuid\n");
-               return -1;
-       }
-       return 0;
-}
-static int set_CSUM_TYPE(NihOption *option, const char *arg)
-{
-       SET_CACHE_PREFERRED_CSUM_TYPE(cache_set_sb,
-                       read_string_list_or_die(arg, csum_types,
-                               "csum type"));
-       return 0;
-}
-static int set_REPLICATION_SET(NihOption *option, const char *arg)
-{
-       replication_set = strtoul_or_die(arg,
-                                               CACHE_REPLICATION_SET_MAX,
-                                               "replication set");
-       return 0;
-}
-static int set_META_REPLICAS(NihOption *option, const char *arg)
-{
-       SET_CACHE_SET_META_REPLICAS_WANT(cache_set_sb,
-                       strtoul_or_die(arg,
-                                               CACHE_SET_META_REPLICAS_WANT_MAX,
-                                               "meta replicas"));
-       return 0;
-}
-static int set_DATA_REPLICAS(NihOption *option, const char *arg)
-{
-       SET_CACHE_SET_DATA_REPLICAS_WANT(cache_set_sb,
-                               strtoul_or_die(arg,
-                                               CACHE_SET_DATA_REPLICAS_WANT_MAX,
-                                               "data replicas"));
+       block_size = hatoi_validate(arg, "block size");
        return 0;
 }
+
 static int set_cache(NihOption *option, const char *arg)
 {
-       bdev=0;
-       cache_devices[cache_set_sb->nr_in_set] = arg;
-       next_cache_device(cache_set_sb,
-                         replication_set,
-                         tier,
-                         replacement_policy,
-                         discard);
+       bdev = 0;
+       cache_devices[nr_cache_devices] = strdup(arg);
+       if(!tier)
+               tier_mapping[nr_cache_devices] = 0;
+       else {
+               int ntier = atoi(tier);
+               if(ntier == 0 || ntier == 1)
+                       tier_mapping[nr_cache_devices] = ntier;
+               else
+                       printf("Invalid tier\n");
+       }
+
        devs++;
+       nr_cache_devices++;
+
        return 0;
 }
+
 static int set_bdev(NihOption *option, const char *arg)
 {
-       bdev=1;
-       backing_dev_labels[nr_backing_devices]=label;
-       backing_devices[nr_backing_devices++]=arg;
+       bdev = 1;
+
+       if(label)
+               backing_dev_labels[nr_backing_devices] = strdup(label);
+
+       backing_devices[nr_backing_devices] = strdup(arg);
+
+       nr_backing_devices++;
        devs++;
+
        return 0;
 }
+
 static int set_bucket_sizes(NihOption *option, const char *arg)
 {
        bucket_sizes[num_bucket_sizes]=hatoi_validate(arg, "bucket size");
@@ -174,22 +159,22 @@ static int set_udev(NihOption *option, const char *arg)
 /* options */
 static NihOption make_bcache_options[] = {
 //     {int shortoption, char* longoption, char* help, NihOptionGroup, char* argname, void *value, NihOptionSetter}
-       {'C', "cache",  N_("Format a cache device"), NULL, NULL, NULL, set_cache},
-       {'B', "bdev",   N_("Format a backing device"), NULL, NULL, NULL, set_bdev},
-       {'l', "label",  N_("label"), NULL, NULL, &label, NULL},
+       {'C', "cache",  N_("Format a cache device"), NULL, "dev", NULL, set_cache},
+       {'B', "bdev",   N_("Format a backing device"), NULL, "dev", NULL, set_bdev},
+       {'l', "label",  N_("label"), NULL, "label", &label, NULL},
        //Only one bucket_size supported until a list of bucket sizes is parsed correctly
-       {'b', "bucket", N_("bucket size"), NULL, NULL, NULL, set_bucket_sizes},
+       {'b', "bucket", N_("bucket size"), NULL, "size", NULL, set_bucket_sizes},
        //Does the default setter automatically convert strings to an int?
-       {'w', "block",  N_("block size (hard sector size of SSD, often 2k"), NULL,NULL, &block_size, NULL},
-       {'t', "tier",   N_("tier of subsequent devices"), NULL,NULL, &tier, NULL},
-       {'p', "cache_replacement_policy", N_("one of (lru|fifo|random)"), NULL,NULL, &replacement_policy, NULL},
-       {'o', "data_offset", N_("data offset in sectors"), NULL,NULL, &data_offset, NULL},
+       {'w', "block",  N_("block size (hard sector size of SSD, often 2k"), NULL,"size", NULL, set_block_size},
+       {'t', "tier",   N_("tier of subsequent devices"), NULL,"#", &tier, NULL},
+       {'p', "cache_replacement_policy", N_("one of (lru|fifo|random)"), NULL,"policy", &replacement_policy, NULL},
+       {'o', "data_offset", N_("data offset in sectors"), NULL,"offset", &data_offset, NULL},
 
-       {0, "cset-uuid",        N_("UUID for the cache set"),           NULL,   NULL, NULL, set_CACHE_SET_UUID },
-       {0, "csum-type",        N_("One of (none|crc32c|crc64)"),               NULL,   NULL, NULL, set_CSUM_TYPE },
-       {0, "replication-set",N_("replication set of subsequent devices"),      NULL,   NULL, NULL, set_REPLICATION_SET },
-       {0, "meta-replicas",N_("number of metadata replicas"),          NULL,   NULL, NULL, set_META_REPLICAS},
-       {0, "data-replicas",N_("number of data replicas"),              NULL,   NULL, NULL, set_DATA_REPLICAS },
+       {0, "cset-uuid",        N_("UUID for the cache set"),           NULL,   "uuid", &cache_set_uuid, NULL},
+       {0, "csum-type",        N_("One of (none|crc32c|crc64)"),               NULL,   "type", &csum_type, NULL },
+       {0, "replication-set",N_("replication set of subsequent devices"),      NULL,   NULL, &replication_set, NULL },
+       {0, "meta-replicas",N_("number of metadata replicas"),          NULL,   "#", &metadata_replicas, NULL},
+       {0, "data-replicas",N_("number of data replicas"),              NULL,   "#", &data_replicas, NULL },
 
        {0, "wipe-bcache",      N_("destroy existing bcache data if present"),          NULL, NULL, &wipe_bcache, NULL},
        {0, "discard",          N_("enable discards"),          NULL, NULL, &discard,           NULL},
@@ -207,13 +192,36 @@ static NihOption bcache_register_options[] = {
        NIH_OPTION_LAST
 };
 
+static NihOption bcache_unregister_options[] = {
+       NIH_OPTION_LAST
+};
+
+static NihOption bcache_add_device_options[] = {
+       {'u', "set", N_("cacheset uuid"), NULL, "UUID", &add_dev_uuid, NULL},
+       NIH_OPTION_LAST
+};
+
+static NihOption bcache_rm_device_options[] = {
+       {'f', "force", N_("force cache removal"), NULL, NULL, &force_remove, NULL},
+       NIH_OPTION_LAST
+};
+
+static NihOption bcache_modify_options[] = {
+       {'l', "list", N_("list attributes"), NULL, NULL, &modify_list_attrs, NULL},
+       {'u', "set", N_("cacheset uuid"), NULL, "UUID", &modify_set_uuid, NULL},
+       {'d', "dev", N_("device uuid"), NULL, "UUID", &modify_dev_uuid, NULL},
+       NIH_OPTION_LAST
+};
+
 static NihOption query_devs_options[] = {
        {'f', "force_csum", N_("force_csum"), NULL, NULL, &force_csum, NULL},
+       {'u', "uuid-only", N_("only print out the uuid for the devices, not the whole superblock"), NULL, NULL, &uuid_only, NULL},
        NIH_OPTION_LAST
 };
 
 static NihOption list_cachesets_options[] = {
        {'d', "dir", N_("directory"), NULL, NULL, &cset_dir, NULL},
+       {0, "list-devs", N_("list all devices in the cache sets as well"), NULL, NULL, &list_devs, NULL},
        NIH_OPTION_LAST
 };
 
@@ -225,7 +233,8 @@ static NihOption status_options[] = {
 static NihOption stats_options[] = {
        {'a', "all", N_("all"), NULL, NULL, &stats_all, NULL},
        {'l', "list", N_("list"), NULL, NULL, &stats_list, NULL},
-       {'u', "uuid", N_("cache_set UUID"), NULL, "UUID", &stats_uuid, NULL},
+       {'u', "set", N_("cache_set UUID"), NULL, "UUID", &stats_uuid, NULL},
+       {'d', "dev", N_("dev UUID"), NULL, "UUID", &stats_dev_uuid, NULL},
        {'c', "cache", N_("cache number (starts from 0)"), NULL, "CACHE#", &stats_cache_num, NULL},
        {0, "five-min-stats", N_("stats accumulated in last 5 minutes"), NULL, NULL, &stats_five_min, NULL},
        {0, "hour-stats", N_("stats accumulated in last hour"), NULL, NULL, &stats_hour, NULL},
@@ -240,7 +249,7 @@ static NihOption options[] = {
 
 
 /* commands */
-int make_bcache (NihCommand *command, char *const *args)
+int make_bcache(NihCommand *command, char *const *args)
 {
        int cache_dev_fd[devs];
 
@@ -249,18 +258,58 @@ int make_bcache (NihCommand *command, char *const *args)
        cache_set_sb = calloc(1, sizeof(*cache_set_sb) +
                                     sizeof(struct cache_member) * devs);
 
-       uuid_generate(cache_set_sb->set_uuid.b);
-       SET_CACHE_PREFERRED_CSUM_TYPE(cache_set_sb, BCH_CSUM_CRC32C);
-       SET_CACHE_SET_META_REPLICAS_WANT(cache_set_sb, 1);
-       SET_CACHE_SET_DATA_REPLICAS_WANT(cache_set_sb, 1);
+       if (cache_set_uuid) {
+               if(uuid_parse(cache_set_uuid, cache_set_sb->set_uuid.b)) {
+                       fprintf(stderr, "Bad uuid\n");
+                       return -1;
+               }
+       } else {
+               uuid_generate(cache_set_sb->set_uuid.b);
+       }
 
-       if(!bucket_sizes[0]) bucket_sizes[0] = 1024;
+       if (label) 
+               memcpy(cache_set_sb->label, label, sizeof(cache_set_sb->label));
+
+       if (csum_type) {
+               SET_CACHE_PREFERRED_CSUM_TYPE(cache_set_sb,
+                               read_string_list_or_die(csum_type, csum_types,
+                                       "csum type"));
+       } else {
+               SET_CACHE_PREFERRED_CSUM_TYPE(cache_set_sb, BCH_CSUM_CRC32C);
+       }
+
+       if (metadata_replicas) {
+               SET_CACHE_SET_META_REPLICAS_WANT(cache_set_sb,
+                               strtoul_or_die(metadata_replicas,
+                                       CACHE_SET_META_REPLICAS_WANT_MAX,
+                                       "meta replicas"));
+       } else {
+               SET_CACHE_SET_META_REPLICAS_WANT(cache_set_sb, 1);
+       }
+
+       if (data_replicas) {
+               SET_CACHE_SET_DATA_REPLICAS_WANT(cache_set_sb,
+                       strtoul_or_die(data_replicas,
+                               CACHE_SET_DATA_REPLICAS_WANT_MAX,
+                               "data replicas"));
+       } else {
+               SET_CACHE_SET_DATA_REPLICAS_WANT(cache_set_sb, 1);
+       }
 
        if (bdev == -1) {
                fprintf(stderr, "Please specify -C or -B\n");
                exit(EXIT_FAILURE);
        }
 
+       if(!bucket_sizes[0]) bucket_sizes[0] = 1024;
+
+       for(i = 0; i < nr_cache_devices; i++)
+               next_cache_device(cache_set_sb,
+                                 replication_set,
+                                 tier_mapping[i],
+                                 replacement_policy,
+                                 discard);
+
        if (!cache_set_sb->nr_in_set && !nr_backing_devices) {
                fprintf(stderr, "Please supply a device\n");
                exit(EXIT_FAILURE);
@@ -269,7 +318,8 @@ int make_bcache (NihCommand *command, char *const *args)
        i = 0;
        do {
                if (bucket_sizes[i] < block_size) {
-                       fprintf(stderr, "Bucket size cannot be smaller than block size\n");
+                       fprintf(stderr,
+                       "Bucket size cannot be smaller than block size\n");
                        exit(EXIT_FAILURE);
                }
                i++;
@@ -305,76 +355,231 @@ int make_bcache (NihCommand *command, char *const *args)
        return 0;
 }
 
-int probe_bcache (NihCommand *command, char *const *args)
+int probe_bcache(NihCommand *command, char *const *args)
 {
        int i;
+       char *err = NULL;
 
        for (i = 0; args[i] != NULL; i++) {
-               probe(args[i], udev);
+               err = probe(args[i], udev);
+               if(err) {
+                       printf("probe_bcache error: %s\n", err);
+                       return -1;
+               }
+       }
+
+       return 0;
+}
+
+int bcache_register(NihCommand *command, char *const *args)
+{
+       char *err = NULL;
+
+       err = register_bcache(args);
+       if (err) {
+               printf("bcache_register error: %s\n", err);
+               return -1;
+       }
+
+       return 0;
+}
+
+int bcache_unregister(NihCommand *command, char *const *args)
+{
+       char *err = NULL;
+
+       err = unregister_bcache(args);
+       if (err) {
+               printf("bcache_unregister error: %s\n", err);
+               return -1;
        }
 
        return 0;
 }
 
-int bcache_register (NihCommand *command, char *const *args)
+int bcache_add_devices(NihCommand *command, char *const *args)
 {
-       int ret;
-       char *arg_list = parse_array_to_list(args);
+       char *err;
+
+       if (!add_dev_uuid) {
+               printf("Must specify a cacheset uuid to add the disk to\n");
+               return -1;
+       }
 
-       if(arg_list) {
-               ret = register_bcache(arg_list);
-               free(arg_list);
+       err = add_devices(args, add_dev_uuid);
+       if (err) {
+               printf("bcache_add_devices error: %s\n", err);
+               return -1;
        }
 
-       return ret;
+       return 0;
 }
 
-int bcache_list_cachesets (NihCommand *command, char *const *args)
+int bcache_rm_device(NihCommand *command, char *const *args)
 {
-       return list_cachesets(cset_dir);
+       char *err;
+
+       err = remove_device(args[0], force_remove);
+       if (err) {
+               printf("bcache_rm_devices error: %s\n", err);
+               return -1;
+       }
+
+       return 0;
 }
 
-int bcache_query_devs (NihCommand *command, char *const *args)
+int bcache_modify(NihCommand *command, char *const *args)
+{
+       char *err;
+       char path[MAX_PATH];
+       DIR *path_dir;
+       struct stat cache_stat;
+       char *attr = args[0];
+       char *val = NULL;
+       int fd = -1;
+
+       if (modify_list_attrs) {
+               sysfs_attr_list();
+               return 0;
+       }
+
+       if (!modify_set_uuid) {
+               printf("Must provide a cacheset uuid\n");
+               return -1;
+       }
+
+       snprintf(path, MAX_PATH, "%s/%s", cset_dir, modify_set_uuid);
+
+       if(!attr) {
+               printf("Must provide the name of an attribute to modify\n");
+               goto err;
+       }
+
+       enum sysfs_attr type = sysfs_attr_type(attr);
+
+       if (type == -1)
+               goto err;
+       else if(type == INTERNAL_ATTR)
+               strcat(path, "/internal");
+       else if(type == CACHE_ATTR) {
+               if(modify_dev_uuid) {
+                       /* searches all cache# for a matching uuid,
+                        * path gets modified to the correct cache path */
+                       char subdir[10] = "/cache";
+                       err = find_matching_uuid(path, subdir,
+                                       modify_dev_uuid);
+                       if (err) {
+                               printf("Failed to find "
+                                       "matching dev %s\n", err);
+                               goto err;
+                       } else {
+                               strcat(path, subdir);
+                       }
+               } else {
+                       printf("Must provide a device uuid\n");
+               }
+       }
+       /* SET_ATTRs are just in the current dir */
+
+       strcat(path, "/");
+       strcat(path, attr);
+
+       val = args[1];
+       if (!val) {
+               printf("Must provide a value to change the attribute to\n");
+               goto err;
+       }
+
+       fd = open(path, O_WRONLY);
+       if (fd < 0) {
+               printf("Unable to open modify attr with path %s\n", path);
+               goto err;
+       }
+
+       write(fd, val, strlen(val));
+
+err:
+       if(fd)
+               close(fd);
+       return 0;
+}
+
+int bcache_list_cachesets(NihCommand *command, char *const *args)
+{
+       char *err = NULL;
+       err = list_cachesets(cset_dir, list_devs);
+       if (err) {
+               printf("bcache_list_cachesets error :%s\n", err);
+               return -1;
+       }
+
+       return 0;
+}
+
+int bcache_query_devs(NihCommand *command, char *const *args)
 {
        int i;
 
        for (i = 0; args[i] != NULL; i++) {
-               struct cache_sb *sb = query_dev(args[i], false);
-               print_dev_info(sb, force_csum);
+               char dev_uuid[40];
+               query_dev(args[i], force_csum, true, uuid_only, dev_uuid);
+               if(uuid_only)
+                       printf("%s\n", dev_uuid);
        }
+
+       return 0;
 }
 
-int bcache_status (NihCommand *command, char *const *args)
+int bcache_status(NihCommand *command, char *const *args)
 {
-       int i;
-       struct cache_sb *sb_tier0 = NULL, *sb_tier1 = NULL;
-       char *dev0 = NULL, *dev1 = NULL;
+       int i, seq, nr_in_set = 0;
+       struct cache_sb *seq_sb = NULL;
 
        for (i = 0; args[i] != NULL; i++) {
-               struct cache_sb *sb = query_dev(args[i], false);
-               struct cache_member *m = ((struct cache_member *) sb->d) +
-                       sb->nr_this_dev;
-               long long unsigned cache_tier = CACHE_TIER(m);
-
-               if (!cache_tier)
-                       if (!sb_tier0 || sb->seq > sb_tier0->seq) {
-                               sb_tier0 = sb;
-                               dev0 = args[i];
-                       }
-               else if (cache_tier == 1)
-                       if (!sb_tier1 || sb->seq > sb_tier1->seq) {
-                               sb_tier1 = sb;
-                               dev1 = args[i];
-                       }
+               struct cache_sb *sb = query_dev(args[i], false, false,
+                               false, NULL);
+
+               if(!sb) {
+                       printf("Unable to open superblock, bad path\n");
+                       return -1;
+               }
+
+               if(!seq_sb || sb->seq > seq) {
+                       seq = sb->seq;
+                       seq_sb = sb;
+                       nr_in_set = sb->nr_in_set;
+               }
+       }
+
+       if(!seq_sb)
+               printf("Unable to find a superblock\n");
+       else
+               printf("%-50s%-15s%-4s\n", "uuid", "state", "tier");
+
+       for (i = 0; i < seq_sb->nr_in_set; i++) {
+               char uuid_str[40];
+               struct cache_member *m = ((struct cache_member *) seq_sb->d) + i;
+
+               uuid_unparse(m->uuid.b, uuid_str);
+
+               printf("%-50s%-15s%-4llu\n", uuid_str,
+                               cache_state[CACHE_STATE(m)],
+                               CACHE_TIER(m));
        }
-       if (sb_tier0) sb_state(sb_tier0, dev0);
-       if (sb_tier1) sb_state(sb_tier1, dev1);
+
+       return 0;
 }
 
-static void stats_subdir(char* stats_dir)
+static char *stats_subdir(char* stats_dir)
 {
        char tmp[50] = "/";
-       if(stats_cache_num) {
+       char *err = NULL;
+       if(stats_dev_uuid) {
+               strcat(tmp, "cache");
+               err = find_matching_uuid(stats_dir, tmp, stats_dev_uuid);
+               if(err)
+                       goto err;
+       } else if(stats_cache_num) {
                strcat(tmp, "cache");
                strcat(tmp, stats_cache_num);
        } else if (stats_five_min)
@@ -386,42 +591,60 @@ static void stats_subdir(char* stats_dir)
        else if (stats_total)
                strcat(tmp, "stats_total");
        else
-               return;
+               return err;
 
        strcat(stats_dir, tmp);
+
+err:
+       return err;
 }
 
-int bcache_stats (NihCommand *command, char *const *args)
+int bcache_stats(NihCommand *command, char *const *args)
 {
        int i;
-       char stats_dir[200];
+       char stats_dir[MAX_PATH];
        DIR *dir = NULL;
        struct dirent *ent = NULL;
+       char *err = NULL;
 
        if (stats_uuid) {
-               strcpy(stats_dir, cset_dir);
-               strcat(stats_dir, "/");
-               strcat(stats_dir, stats_uuid);
-               stats_subdir(stats_dir);
+               snprintf(stats_dir, MAX_PATH, "%s/%s", cset_dir, stats_uuid);
+               err = stats_subdir(stats_dir);
+               if(err)
+                       goto err;
+
                dir = opendir(stats_dir);
                if (!dir) {
-                       fprintf(stderr, "Failed to open dir %s\n", cset_dir);
-                       return 1;
+                       err = "Failed to open dir";
+                       goto err;
                }
        } else {
-               printf("Must provide a cacheset uuid\n");
-               exit(EXIT_FAILURE);
+               err = "Must provide a cacheset uuid";
+               goto err;
        }
 
-       if(stats_list || stats_all)
-               while ((ent = readdir(dir)) != NULL)
-                       read_stat_dir(dir, stats_dir, ent->d_name, stats_all);
+       if(stats_list || stats_all) {
+               while ((ent = readdir(dir)) != NULL) {
+                       err = read_stat_dir(dir, stats_dir, ent->d_name, stats_all);
+                       if (err)
+                               goto err;
+               }
+       }
 
 
-       for (i = 0; args[i] != NULL; i++)
-               read_stat_dir(dir, stats_dir, args[i], true);
+       for (i = 0; args[i] != NULL; i++) {
+               err = read_stat_dir(dir, stats_dir, args[i], true);
+               if (err)
+                       goto err;
+       }
 
        closedir(dir);
+       return 0;
+
+err:
+       closedir(dir);
+       printf("bcache_stats error: %s\n", err);
+       return -1;
 }
 
 static NihCommand commands[] = {
@@ -438,6 +661,22 @@ static NihCommand commands[] = {
                     "Registers a list of devices",
                     N_("Registers a list of devices"),
                     NULL, bcache_register_options, bcache_register},
+       {"unregister", N_("unregister <list of devices>"),
+                    "Unregisters a list of devices",
+                    N_("Unregisters a list of devices"),
+                    NULL, bcache_unregister_options, bcache_unregister},
+       {"add-devs", N_("add-devs --set=UUID --tier=# <list of devices>"),
+               "Adds a list of devices to a cacheset",
+               N_("Adds a list of devices to a cacheset"),
+               NULL, bcache_add_device_options, bcache_add_devices},
+       {"rm-dev", N_("rm-dev <dev>"),
+               "Removes a device from its cacheset",
+               N_("Removes a device from its cacheset"),
+               NULL, bcache_rm_device_options, bcache_rm_device},
+       {"modify", N_("modify --set=UUID (dev=UUID) name value"),
+               "Modifies attributes related to the cacheset",
+               N_("Modifies attributes related to the cacheset"),
+               NULL, bcache_modify_options, bcache_modify},
        {"list-cachesets", N_("list-cachesets"),
                           "Lists cachesets in /sys/fs/bcache",
                           N_("Lists cachesets in /sys/fs/bcache"),
@@ -472,4 +711,6 @@ int main(int argc, char *argv[])
                exit (1);
 
        nih_signal_reset();
+
+       return 0;
 }