| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * This file is part of UBIFS. |
| * |
| * Copyright (C) 2021 Cisco Systems |
| * |
| * Author: Stefan Schaeckeler |
| */ |
| |
| |
| #include <linux/fs.h> |
| #include "ubifs.h" |
| |
| enum attr_id_t { |
| attr_errors_magic, |
| attr_errors_node, |
| attr_errors_crc, |
| }; |
| |
| struct ubifs_attr { |
| struct attribute attr; |
| enum attr_id_t attr_id; |
| }; |
| |
| #define UBIFS_ATTR(_name, _mode, _id) \ |
| static struct ubifs_attr ubifs_attr_##_name = { \ |
| .attr = {.name = __stringify(_name), .mode = _mode }, \ |
| .attr_id = attr_##_id, \ |
| } |
| |
| #define UBIFS_ATTR_FUNC(_name, _mode) UBIFS_ATTR(_name, _mode, _name) |
| |
| UBIFS_ATTR_FUNC(errors_magic, 0444); |
| UBIFS_ATTR_FUNC(errors_crc, 0444); |
| UBIFS_ATTR_FUNC(errors_node, 0444); |
| |
| #define ATTR_LIST(name) (&ubifs_attr_##name.attr) |
| |
| static struct attribute *ubifs_attrs[] = { |
| ATTR_LIST(errors_magic), |
| ATTR_LIST(errors_node), |
| ATTR_LIST(errors_crc), |
| NULL, |
| }; |
| ATTRIBUTE_GROUPS(ubifs); |
| |
| static ssize_t ubifs_attr_show(struct kobject *kobj, |
| struct attribute *attr, char *buf) |
| { |
| struct ubifs_info *sbi = container_of(kobj, struct ubifs_info, |
| kobj); |
| |
| struct ubifs_attr *a = container_of(attr, struct ubifs_attr, attr); |
| |
| switch (a->attr_id) { |
| case attr_errors_magic: |
| return sysfs_emit(buf, "%u\n", sbi->stats->magic_errors); |
| case attr_errors_node: |
| return sysfs_emit(buf, "%u\n", sbi->stats->node_errors); |
| case attr_errors_crc: |
| return sysfs_emit(buf, "%u\n", sbi->stats->crc_errors); |
| } |
| return 0; |
| }; |
| |
| static void ubifs_sb_release(struct kobject *kobj) |
| { |
| struct ubifs_info *c = container_of(kobj, struct ubifs_info, kobj); |
| |
| complete(&c->kobj_unregister); |
| } |
| |
| static const struct sysfs_ops ubifs_attr_ops = { |
| .show = ubifs_attr_show, |
| }; |
| |
| static const struct kobj_type ubifs_sb_ktype = { |
| .default_groups = ubifs_groups, |
| .sysfs_ops = &ubifs_attr_ops, |
| .release = ubifs_sb_release, |
| }; |
| |
| static const struct kobj_type ubifs_ktype = { |
| .sysfs_ops = &ubifs_attr_ops, |
| }; |
| |
| static struct kset ubifs_kset = { |
| .kobj = {.ktype = &ubifs_ktype}, |
| }; |
| |
| int ubifs_sysfs_register(struct ubifs_info *c) |
| { |
| int ret, n; |
| char dfs_dir_name[UBIFS_DFS_DIR_LEN]; |
| |
| c->stats = kzalloc(sizeof(struct ubifs_stats_info), GFP_KERNEL); |
| if (!c->stats) { |
| ret = -ENOMEM; |
| goto out_last; |
| } |
| n = snprintf(dfs_dir_name, UBIFS_DFS_DIR_LEN, UBIFS_DFS_DIR_NAME, |
| c->vi.ubi_num, c->vi.vol_id); |
| |
| if (n >= UBIFS_DFS_DIR_LEN) { |
| /* The array size is too small */ |
| ret = -EINVAL; |
| goto out_free; |
| } |
| |
| c->kobj.kset = &ubifs_kset; |
| init_completion(&c->kobj_unregister); |
| |
| ret = kobject_init_and_add(&c->kobj, &ubifs_sb_ktype, NULL, |
| "%s", dfs_dir_name); |
| if (ret) |
| goto out_put; |
| |
| return 0; |
| |
| out_put: |
| kobject_put(&c->kobj); |
| wait_for_completion(&c->kobj_unregister); |
| out_free: |
| kfree(c->stats); |
| out_last: |
| ubifs_err(c, "cannot create sysfs entry for ubifs%d_%d, error %d\n", |
| c->vi.ubi_num, c->vi.vol_id, ret); |
| return ret; |
| } |
| |
| void ubifs_sysfs_unregister(struct ubifs_info *c) |
| { |
| kobject_del(&c->kobj); |
| kobject_put(&c->kobj); |
| wait_for_completion(&c->kobj_unregister); |
| |
| kfree(c->stats); |
| } |
| |
| int __init ubifs_sysfs_init(void) |
| { |
| int ret; |
| |
| kobject_set_name(&ubifs_kset.kobj, "ubifs"); |
| ubifs_kset.kobj.parent = fs_kobj; |
| ret = kset_register(&ubifs_kset); |
| if (ret) |
| kset_put(&ubifs_kset); |
| |
| return ret; |
| } |
| |
| void ubifs_sysfs_exit(void) |
| { |
| kset_unregister(&ubifs_kset); |
| } |