| // 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_obj(struct ubifs_stats_info); | |
| 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); | |
| } |