diff options
-rw-r--r-- | fs/bcachefs/chardev.c | 14 | ||||
-rw-r--r-- | fs/bcachefs/fs.c | 16 | ||||
-rw-r--r-- | fs/bcachefs/opts.c | 49 | ||||
-rw-r--r-- | fs/bcachefs/opts.h | 22 | ||||
-rw-r--r-- | fs/bcachefs/super-io.c | 138 | ||||
-rw-r--r-- | fs/bcachefs/super-io.h | 5 | ||||
-rw-r--r-- | fs/bcachefs/super.c | 89 | ||||
-rw-r--r-- | fs/bcachefs/super.h | 2 | ||||
-rw-r--r-- | fs/bcachefs/sysfs.c | 34 | ||||
-rw-r--r-- | fs/bcachefs/xattr.c | 10 |
10 files changed, 331 insertions, 48 deletions
diff --git a/fs/bcachefs/chardev.c b/fs/bcachefs/chardev.c index 5ff90cc0015f..ab6dc665186e 100644 --- a/fs/bcachefs/chardev.c +++ b/fs/bcachefs/chardev.c @@ -40,27 +40,15 @@ static struct bch_dev *bch2_device_lookup(struct bch_fs *c, u64 dev, if (!ca) return ERR_PTR(-EINVAL); } else { - struct block_device *bdev; char *path; - unsigned i; path = strndup_user((const char __user *) (unsigned long) dev, PATH_MAX); if (IS_ERR(path)) return ERR_CAST(path); - bdev = lookup_bdev(path); + ca = bch2_dev_lookup(c, path); kfree(path); - if (IS_ERR(bdev)) - return ERR_CAST(bdev); - - for_each_member_device(ca, c, i) - if (ca->disk_sb.bdev == bdev) - goto found; - - ca = ERR_PTR(-ENOENT); -found: - bdput(bdev); } return ca; diff --git a/fs/bcachefs/fs.c b/fs/bcachefs/fs.c index 80962b5d16a1..c7e842ee8437 100644 --- a/fs/bcachefs/fs.c +++ b/fs/bcachefs/fs.c @@ -1266,6 +1266,7 @@ static int bch2_show_options(struct seq_file *seq, struct dentry *root) { struct bch_fs *c = root->d_sb->s_fs_info; enum bch_opt_id i; + char buf[512]; for (i = 0; i < bch2_opts_nr; i++) { const struct bch_option *opt = &bch2_opt_table[i]; @@ -1277,17 +1278,10 @@ static int bch2_show_options(struct seq_file *seq, struct dentry *root) if (v == bch2_opt_get_by_id(&bch2_opts_default, i)) continue; - switch (opt->type) { - case BCH_OPT_BOOL: - seq_printf(seq, ",%s%s", v ? "" : "no", opt->attr.name); - break; - case BCH_OPT_UINT: - seq_printf(seq, ",%s=%llu", opt->attr.name, v); - break; - case BCH_OPT_STR: - seq_printf(seq, ",%s=%s", opt->attr.name, opt->choices[v]); - break; - } + bch2_opt_to_text(c, buf, sizeof(buf), opt, v, + OPT_SHOW_MOUNT_STYLE); + seq_putc(seq, ','); + seq_puts(seq, buf); } return 0; diff --git a/fs/bcachefs/opts.c b/fs/bcachefs/opts.c index ec50345fda62..326b8ad9caf2 100644 --- a/fs/bcachefs/opts.c +++ b/fs/bcachefs/opts.c @@ -1,7 +1,9 @@ #include <linux/kernel.h> +#include "bcachefs.h" #include "opts.h" +#include "super-io.h" #include "util.h" const char * const bch2_error_actions[] = { @@ -139,6 +141,9 @@ const struct bch_option bch2_opt_table[] = { #define OPT_BOOL() .type = BCH_OPT_BOOL #define OPT_UINT(_min, _max) .type = BCH_OPT_UINT, .min = _min, .max = _max #define OPT_STR(_choices) .type = BCH_OPT_STR, .choices = _choices +#define OPT_FN(_fn) .type = BCH_OPT_FN, \ + .parse = _fn##_parse, \ + .print = _fn##_print #define BCH_OPT(_name, _bits, _mode, _type, _sb_opt, _default) \ [Opt_##_name] = { \ @@ -189,7 +194,8 @@ static int bch2_mount_opt_lookup(const char *name) return bch2_opt_lookup(name); } -int bch2_opt_parse(const struct bch_option *opt, const char *val, u64 *res) +int bch2_opt_parse(struct bch_fs *c, const struct bch_option *opt, + const char *val, u64 *res) { ssize_t ret; @@ -217,11 +223,50 @@ int bch2_opt_parse(const struct bch_option *opt, const char *val, u64 *res) *res = ret; break; + case BCH_OPT_FN: + if (!c) + return -EINVAL; + + return opt->parse(c, val, res); } return 0; } +int bch2_opt_to_text(struct bch_fs *c, char *buf, size_t len, + const struct bch_option *opt, u64 v, + unsigned flags) +{ + char *out = buf, *end = buf + len; + + if (flags & OPT_SHOW_MOUNT_STYLE) { + if (opt->type == BCH_OPT_BOOL) + return scnprintf(out, end - out, "%s%s", + v ? "" : "no", + opt->attr.name); + + out += scnprintf(out, end - out, "%s=", opt->attr.name); + } + + switch (opt->type) { + case BCH_OPT_BOOL: + case BCH_OPT_UINT: + out += scnprintf(out, end - out, "%lli", v); + break; + case BCH_OPT_STR: + out += (flags & OPT_SHOW_FULL_LIST) + ? bch2_scnprint_string_list(out, end - out, opt->choices, v) + : scnprintf(out, end - out, opt->choices[v]); + break; + case BCH_OPT_FN: + return opt->print(c, out, end - out, v); + default: + BUG(); + } + + return out - buf; +} + int bch2_parse_mount_opts(struct bch_opts *opts, char *options) { char *opt, *name, *val; @@ -237,7 +282,7 @@ int bch2_parse_mount_opts(struct bch_opts *opts, char *options) if (id < 0) goto bad_opt; - ret = bch2_opt_parse(&bch2_opt_table[id], val, &v); + ret = bch2_opt_parse(NULL, &bch2_opt_table[id], val, &v); if (ret < 0) goto bad_val; } else { diff --git a/fs/bcachefs/opts.h b/fs/bcachefs/opts.h index ec4e36ab8dd8..e7ab8870d3ac 100644 --- a/fs/bcachefs/opts.h +++ b/fs/bcachefs/opts.h @@ -42,6 +42,7 @@ enum opt_type { BCH_OPT_BOOL, BCH_OPT_UINT, BCH_OPT_STR, + BCH_OPT_FN, }; /** @@ -101,13 +102,13 @@ enum opt_type { OPT_STR(bch2_str_hash_types), \ BCH_SB_STR_HASH_TYPE, BCH_STR_HASH_SIPHASH) \ BCH_OPT(foreground_target, u16, OPT_RUNTIME, \ - OPT_UINT(0, U16_MAX), \ + OPT_FN(bch2_opt_target), \ BCH_SB_FOREGROUND_TARGET, 0) \ BCH_OPT(background_target, u16, OPT_RUNTIME, \ - OPT_UINT(0, U16_MAX), \ + OPT_FN(bch2_opt_target), \ BCH_SB_BACKGROUND_TARGET, 0) \ BCH_OPT(promote_target, u16, OPT_RUNTIME, \ - OPT_UINT(0, U16_MAX), \ + OPT_FN(bch2_opt_target), \ BCH_SB_PROMOTE_TARGET, 0) \ BCH_OPT(inodes_32bit, u8, OPT_RUNTIME, \ OPT_BOOL(), \ @@ -217,6 +218,8 @@ enum bch_opt_id { bch2_opts_nr }; +struct bch_fs; + struct bch_option { struct attribute attr; void (*set_sb)(struct bch_sb *, u64); @@ -230,6 +233,10 @@ struct bch_option { struct { const char * const *choices; }; + struct { + int (*parse)(struct bch_fs *, const char *, u64 *); + int (*print)(struct bch_fs *, char *, size_t, u64); + }; }; }; @@ -243,7 +250,14 @@ void bch2_opt_set_by_id(struct bch_opts *, enum bch_opt_id, u64); struct bch_opts bch2_opts_from_sb(struct bch_sb *); int bch2_opt_lookup(const char *); -int bch2_opt_parse(const struct bch_option *, const char *, u64 *); +int bch2_opt_parse(struct bch_fs *, const struct bch_option *, const char *, u64 *); + +#define OPT_SHOW_FULL_LIST (1 << 0) +#define OPT_SHOW_MOUNT_STYLE (1 << 1) + +int bch2_opt_to_text(struct bch_fs *, char *, size_t, + const struct bch_option *, u64, unsigned); + int bch2_parse_mount_opts(struct bch_opts *, char *); /* inode opts: */ diff --git a/fs/bcachefs/super-io.c b/fs/bcachefs/super-io.c index ba32e69d2a98..69101f3a68a5 100644 --- a/fs/bcachefs/super-io.c +++ b/fs/bcachefs/super-io.c @@ -1736,18 +1736,140 @@ const struct bch_devs_mask *bch2_target_to_mask(struct bch_fs *c, unsigned targe struct target t = target_decode(target); switch (t.type) { - case TARGET_DEV: - BUG_ON(t.dev >= c->sb.nr_devices && !c->devs[t.dev]); - return &c->devs[t.dev]->self; + case TARGET_DEV: { + struct bch_dev *ca = t.dev < c->sb.nr_devices + ? rcu_dereference(c->devs[t.dev]) + : NULL; + return ca ? &ca->self : NULL; + } + case TARGET_GROUP: { + struct bch_disk_groups_cpu *g = rcu_dereference(c->disk_groups); + + return t.group < g->nr && !g->entries[t.group].deleted + ? &g->entries[t.group].devs + : NULL; + } + default: + BUG(); + } +} + +int __bch2_disk_group_find(struct bch_sb_field_disk_groups *groups, + const char *name) +{ + unsigned i, nr_groups = disk_groups_nr(groups); + unsigned len = strlen(name); + + for (i = 0; i < nr_groups; i++) { + struct bch_disk_group *g = groups->entries + i; + + if (BCH_GROUP_DELETED(g)) + continue; + + if (strnlen(g->label, sizeof(g->label)) == len && + !memcmp(name, g->label, len)) + return i; + } + + return -1; +} + +static int bch2_disk_group_find(struct bch_fs *c, const char *name) +{ + int ret; + + mutex_lock(&c->sb_lock); + ret = __bch2_disk_group_find(bch2_sb_get_disk_groups(c->disk_sb), name); + mutex_unlock(&c->sb_lock); + + return ret; +} + +int bch2_opt_target_parse(struct bch_fs *c, const char *buf, u64 *v) +{ + struct bch_dev *ca; + int g; + + if (!strlen(buf) || !strcmp(buf, "none")) { + *v = 0; + return 0; + } + + /* Is it a device? */ + ca = bch2_dev_lookup(c, buf); + if (!IS_ERR(ca)) { + *v = dev_to_target(ca->dev_idx); + percpu_ref_put(&ca->ref); + return 0; + } + + g = bch2_disk_group_find(c, buf); + if (g >= 0) { + *v = group_to_target(g); + return 0; + } + + return -EINVAL; +} + +int bch2_opt_target_print(struct bch_fs *c, char *buf, size_t len, u64 v) +{ + struct target t = target_decode(v); + int ret; + + switch (t.type) { + case TARGET_NULL: + return scnprintf(buf, len, "none"); + case TARGET_DEV: { + struct bch_dev *ca; + + rcu_read_lock(); + ca = t.dev < c->sb.nr_devices + ? rcu_dereference(c->devs[t.dev]) + : NULL; + + if (ca && percpu_ref_tryget(&ca->io_ref)) { + char b[BDEVNAME_SIZE]; + + ret = scnprintf(buf, len, "/dev/%s", + bdevname(ca->disk_sb.bdev, b)); + percpu_ref_put(&ca->io_ref); + } else if (ca) { + ret = scnprintf(buf, len, "offline device %u", t.dev); + } else { + ret = scnprintf(buf, len, "invalid device %u", t.dev); + } + + rcu_read_unlock(); + break; + } case TARGET_GROUP: { - struct bch_disk_groups_cpu *g = - rcu_dereference(c->disk_groups); + struct bch_sb_field_disk_groups *groups; + struct bch_disk_group *g; + + mutex_lock(&c->sb_lock); + groups = bch2_sb_get_disk_groups(c->disk_sb); - /* XXX: what to do here? */ - BUG_ON(t.group >= g->nr || g->entries[t.group].deleted); - return &g->entries[t.group].devs; + g = t.group < disk_groups_nr(groups) + ? groups->entries + t.group + : NULL; + + if (g && !BCH_GROUP_DELETED(g)) { + ret = len ? min(len - 1, strnlen(g->label, sizeof(g->label))) : 0; + + memcpy(buf, g->label, ret); + if (len) + buf[ret] = '\0'; + } else { + ret = scnprintf(buf, len, "invalid group %u", t.group); + } + + mutex_unlock(&c->sb_lock); + break; } default: BUG(); } + + return ret; } diff --git a/fs/bcachefs/super-io.h b/fs/bcachefs/super-io.h index cd8bfe6cfea8..3811de72c7a9 100644 --- a/fs/bcachefs/super-io.h +++ b/fs/bcachefs/super-io.h @@ -251,4 +251,9 @@ static inline bool dev_in_target(struct bch_dev *ca, unsigned target) const struct bch_devs_mask *bch2_target_to_mask(struct bch_fs *, unsigned); +int __bch2_disk_group_find(struct bch_sb_field_disk_groups *, const char *); + +int bch2_opt_target_parse(struct bch_fs *, const char *, u64 *); +int bch2_opt_target_print(struct bch_fs *, char *, size_t, u64); + #endif /* _BCACHEFS_SUPER_IO_H */ diff --git a/fs/bcachefs/super.c b/fs/bcachefs/super.c index 287535e9c4f7..abb971286cdd 100644 --- a/fs/bcachefs/super.c +++ b/fs/bcachefs/super.c @@ -1702,6 +1702,95 @@ err: return ret; } +/* return with ref on ca->ref: */ +struct bch_dev *bch2_dev_lookup(struct bch_fs *c, const char *path) +{ + + struct block_device *bdev = lookup_bdev(path); + struct bch_dev *ca; + unsigned i; + + if (IS_ERR(bdev)) + return ERR_CAST(bdev); + + for_each_member_device(ca, c, i) + if (ca->disk_sb.bdev == bdev) + goto found; + + ca = ERR_PTR(-ENOENT); +found: + bdput(bdev); + return ca; +} + +int bch2_dev_group_set(struct bch_fs *c, struct bch_dev *ca, const char *label) +{ + struct bch_sb_field_disk_groups *groups; + struct bch_disk_group *g; + struct bch_member *mi; + unsigned i, v, nr_groups; + int ret; + + if (strlen(label) > BCH_SB_LABEL_SIZE) + return -EINVAL; + + mutex_lock(&c->sb_lock); + groups = bch2_sb_get_disk_groups(c->disk_sb); + nr_groups = disk_groups_nr(groups); + + if (!strcmp(label, "none")) { + v = 0; + goto write_sb; + } + + ret = __bch2_disk_group_find(groups, label); + if (ret >= 0) { + v = ret + 1; + goto write_sb; + } + + /* not found - create a new disk group: */ + + for (i = 0; + i < nr_groups && !BCH_GROUP_DELETED(&groups->entries[i]); + i++) + ; + + if (i == nr_groups) { + unsigned u64s = + (sizeof(struct bch_sb_field_disk_groups) + + sizeof(struct bch_disk_group) * (nr_groups + 1)) / + sizeof(u64); + + groups = bch2_fs_sb_resize_disk_groups(c, u64s); + if (!groups) { + mutex_unlock(&c->sb_lock); + return -ENOSPC; + } + + nr_groups = disk_groups_nr(groups); + } + + BUG_ON(i >= nr_groups); + + g = &groups->entries[i]; + v = i + 1; + + memcpy(g->label, label, strlen(label)); + if (strlen(label) < sizeof(g->label)) + g->label[strlen(label)] = '\0'; + SET_BCH_GROUP_DELETED(g, 0); + SET_BCH_GROUP_DATA_ALLOWED(g, ~0); +write_sb: + mi = &bch2_sb_get_members(c->disk_sb)->members[ca->dev_idx]; + SET_BCH_MEMBER_GROUP(mi, v); + + bch2_write_super(c); + mutex_unlock(&c->sb_lock); + + return 0; +} + /* Filesystem open: */ struct bch_fs *bch2_fs_open(char * const *devices, unsigned nr_devices, diff --git a/fs/bcachefs/super.h b/fs/bcachefs/super.h index 1718f5c10303..652a572ff329 100644 --- a/fs/bcachefs/super.h +++ b/fs/bcachefs/super.h @@ -194,6 +194,8 @@ int bch2_dev_add(struct bch_fs *, const char *); int bch2_dev_online(struct bch_fs *, const char *); int bch2_dev_offline(struct bch_fs *, struct bch_dev *, int); int bch2_dev_resize(struct bch_fs *, struct bch_dev *, u64); +struct bch_dev *bch2_dev_lookup(struct bch_fs *, const char *); +int bch2_dev_group_set(struct bch_fs *, struct bch_dev *, const char *); bool bch2_fs_emergency_read_only(struct bch_fs *); void bch2_fs_read_only(struct bch_fs *); diff --git a/fs/bcachefs/sysfs.c b/fs/bcachefs/sysfs.c index 34c5f9029bd6..e42bc1dae336 100644 --- a/fs/bcachefs/sysfs.c +++ b/fs/bcachefs/sysfs.c @@ -168,6 +168,7 @@ rw_attribute(writeback_pages_max); rw_attribute(discard); rw_attribute(cache_replacement_policy); +rw_attribute(group); rw_attribute(copy_gc_enabled); sysfs_pd_controller_attribute(copy_gc); @@ -525,9 +526,7 @@ SHOW(bch2_fs_opts_dir) int id = opt - bch2_opt_table; u64 v = bch2_opt_get_by_id(&c->opts, id); - out += opt->type == BCH_OPT_STR - ? bch2_scnprint_string_list(out, end - out, opt->choices, v) - : scnprintf(out, end - out, "%lli", v); + out += bch2_opt_to_text(c, out, end - out, opt, v, OPT_SHOW_FULL_LIST); out += scnprintf(out, end - out, "\n"); return out - buf; @@ -540,7 +539,7 @@ STORE(bch2_fs_opts_dir) int ret, id = opt - bch2_opt_table; u64 v; - ret = bch2_opt_parse(opt, buf, &v); + ret = bch2_opt_parse(c, opt, buf, &v); if (ret < 0) return ret; @@ -812,6 +811,26 @@ SHOW(bch2_dev) sysfs_print(nbuckets, ca->mi.nbuckets); sysfs_print(discard, ca->mi.discard); + if (attr == &sysfs_group) { + struct bch_sb_field_disk_groups *groups; + struct bch_disk_group *g; + unsigned len; + + if (!ca->mi.group) + return scnprintf(out, end - out, "none\n"); + + mutex_lock(&c->sb_lock); + groups = bch2_sb_get_disk_groups(c->disk_sb); + + g = &groups->entries[ca->mi.group - 1]; + len = strnlen(g->label, sizeof(g->label)); + memcpy(buf, g->label, len); + mutex_unlock(&c->sb_lock); + + buf[len++] = '\n'; + return len; + } + if (attr == &sysfs_has_data) { out += bch2_scnprint_flag_list(out, end - out, bch2_data_types, @@ -893,6 +912,12 @@ STORE(bch2_dev) mutex_unlock(&c->sb_lock); } + if (attr == &sysfs_group) { + int ret = bch2_dev_group_set(c, ca, buf); + if (ret) + return ret; + } + if (attr == &sysfs_wake_allocator) bch2_wake_allocator(ca); @@ -911,6 +936,7 @@ struct attribute *bch2_dev_files[] = { &sysfs_discard, &sysfs_cache_replacement_policy, &sysfs_state_rw, + &sysfs_group, &sysfs_has_data, &sysfs_iostats, diff --git a/fs/bcachefs/xattr.c b/fs/bcachefs/xattr.c index 70742fc8a5fc..81e942e5039c 100644 --- a/fs/bcachefs/xattr.c +++ b/fs/bcachefs/xattr.c @@ -367,6 +367,7 @@ static int bch2_xattr_bcachefs_get(const struct xattr_handler *handler, const char *name, void *buffer, size_t size) { struct bch_inode_info *inode = to_bch_ei(vinode); + struct bch_fs *c = inode->v.i_sb->s_fs_info; struct bch_opts opts = bch2_inode_opts_to_opts(bch2_inode_opts_get(&inode->ei_inode)); const struct bch_option *opt; @@ -384,12 +385,9 @@ static int bch2_xattr_bcachefs_get(const struct xattr_handler *handler, v = bch2_opt_get_by_id(&opts, id); - if (opt->type == BCH_OPT_STR) - ret = snprintf(buffer, size, "%s", opt->choices[v]); - else - ret = snprintf(buffer, size, "%llu", v); + ret = bch2_opt_to_text(c, buffer, size, opt, v, 0); - return ret <= size || !buffer ? ret : -ERANGE; + return ret < size || !buffer ? ret : -ERANGE; } struct inode_opt_set { @@ -436,7 +434,7 @@ static int bch2_xattr_bcachefs_set(const struct xattr_handler *handler, memcpy(buf, value, size); buf[size] = '\0'; - ret = bch2_opt_parse(opt, buf, &s.v); + ret = bch2_opt_parse(c, opt, buf, &s.v); kfree(buf); if (ret < 0) |