| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright 2021 Google LLC | |
| */ | |
| #include <linux/fs.h> | |
| #include <linux/kobject.h> | |
| #include <uapi/linux/incrementalfs.h> | |
| #include "sysfs.h" | |
| #include "data_mgmt.h" | |
| #include "vfs.h" | |
| /****************************************************************************** | |
| * Define sys/fs/incrementalfs & sys/fs/incrementalfs/features | |
| *****************************************************************************/ | |
| #define INCFS_NODE_FEATURES "features" | |
| #define INCFS_NODE_INSTANCES "instances" | |
| static struct kobject *sysfs_root; | |
| static struct kobject *features_node; | |
| static struct kobject *instances_node; | |
| #define DECLARE_FEATURE_FLAG(name) \ | |
| static ssize_t name##_show(struct kobject *kobj, \ | |
| struct kobj_attribute *attr, char *buff) \ | |
| { \ | |
| return sysfs_emit(buff, "supported\n"); \ | |
| } \ | |
| \ | |
| static struct kobj_attribute name##_attr = __ATTR_RO(name) | |
| DECLARE_FEATURE_FLAG(corefs); | |
| DECLARE_FEATURE_FLAG(zstd); | |
| DECLARE_FEATURE_FLAG(v2); | |
| DECLARE_FEATURE_FLAG(bugfix_throttling); | |
| DECLARE_FEATURE_FLAG(bugfix_inode_eviction); | |
| static struct attribute *attributes[] = { | |
| &corefs_attr.attr, | |
| &zstd_attr.attr, | |
| &v2_attr.attr, | |
| &bugfix_throttling_attr.attr, | |
| &bugfix_inode_eviction_attr.attr, | |
| NULL, | |
| }; | |
| static const struct attribute_group attr_group = { | |
| .attrs = attributes, | |
| }; | |
| int __init incfs_init_sysfs(void) | |
| { | |
| int res = -ENOMEM; | |
| sysfs_root = kobject_create_and_add(INCFS_NAME, fs_kobj); | |
| if (!sysfs_root) | |
| return -ENOMEM; | |
| instances_node = kobject_create_and_add(INCFS_NODE_INSTANCES, | |
| sysfs_root); | |
| if (!instances_node) | |
| goto err_put_root; | |
| features_node = kobject_create_and_add(INCFS_NODE_FEATURES, | |
| sysfs_root); | |
| if (!features_node) | |
| goto err_put_instances; | |
| res = sysfs_create_group(features_node, &attr_group); | |
| if (res) | |
| goto err_put_features; | |
| return 0; | |
| err_put_features: | |
| kobject_put(features_node); | |
| err_put_instances: | |
| kobject_put(instances_node); | |
| err_put_root: | |
| kobject_put(sysfs_root); | |
| return res; | |
| } | |
| void incfs_cleanup_sysfs(void) | |
| { | |
| if (features_node) { | |
| sysfs_remove_group(features_node, &attr_group); | |
| kobject_put(features_node); | |
| } | |
| kobject_put(instances_node); | |
| kobject_put(sysfs_root); | |
| } | |
| /****************************************************************************** | |
| * Define sys/fs/incrementalfs/instances/<name>/ | |
| *****************************************************************************/ | |
| #define __DECLARE_STATUS_FLAG(name) \ | |
| static ssize_t name##_show(struct kobject *kobj, \ | |
| struct kobj_attribute *attr, char *buff) \ | |
| { \ | |
| struct incfs_sysfs_node *node = container_of(kobj, \ | |
| struct incfs_sysfs_node, isn_sysfs_node); \ | |
| \ | |
| return sysfs_emit(buff, "%d\n", node->isn_mi->mi_##name); \ | |
| } \ | |
| \ | |
| static struct kobj_attribute name##_attr = __ATTR_RO(name) | |
| #define __DECLARE_STATUS_FLAG64(name) \ | |
| static ssize_t name##_show(struct kobject *kobj, \ | |
| struct kobj_attribute *attr, char *buff) \ | |
| { \ | |
| struct incfs_sysfs_node *node = container_of(kobj, \ | |
| struct incfs_sysfs_node, isn_sysfs_node); \ | |
| \ | |
| return sysfs_emit(buff, "%lld\n", node->isn_mi->mi_##name); \ | |
| } \ | |
| \ | |
| static struct kobj_attribute name##_attr = __ATTR_RO(name) | |
| __DECLARE_STATUS_FLAG(reads_failed_timed_out); | |
| __DECLARE_STATUS_FLAG(reads_failed_hash_verification); | |
| __DECLARE_STATUS_FLAG(reads_failed_other); | |
| __DECLARE_STATUS_FLAG(reads_delayed_pending); | |
| __DECLARE_STATUS_FLAG64(reads_delayed_pending_us); | |
| __DECLARE_STATUS_FLAG(reads_delayed_min); | |
| __DECLARE_STATUS_FLAG64(reads_delayed_min_us); | |
| static struct attribute *mount_attributes[] = { | |
| &reads_failed_timed_out_attr.attr, | |
| &reads_failed_hash_verification_attr.attr, | |
| &reads_failed_other_attr.attr, | |
| &reads_delayed_pending_attr.attr, | |
| &reads_delayed_pending_us_attr.attr, | |
| &reads_delayed_min_attr.attr, | |
| &reads_delayed_min_us_attr.attr, | |
| NULL, | |
| }; | |
| static void incfs_sysfs_release(struct kobject *kobj) | |
| { | |
| struct incfs_sysfs_node *node = container_of(kobj, | |
| struct incfs_sysfs_node, isn_sysfs_node); | |
| complete(&node->isn_completion); | |
| } | |
| static const struct attribute_group mount_attr_group = { | |
| .attrs = mount_attributes, | |
| }; | |
| static struct kobj_type incfs_kobj_node_ktype = { | |
| .sysfs_ops = &kobj_sysfs_ops, | |
| .release = &incfs_sysfs_release, | |
| }; | |
| struct incfs_sysfs_node *incfs_add_sysfs_node(const char *name, | |
| struct mount_info *mi) | |
| { | |
| struct incfs_sysfs_node *node = NULL; | |
| int error; | |
| if (!name) | |
| return NULL; | |
| node = kzalloc(sizeof(*node), GFP_NOFS); | |
| if (!node) | |
| return ERR_PTR(-ENOMEM); | |
| node->isn_mi = mi; | |
| init_completion(&node->isn_completion); | |
| kobject_init(&node->isn_sysfs_node, &incfs_kobj_node_ktype); | |
| error = kobject_add(&node->isn_sysfs_node, instances_node, "%s", name); | |
| if (error) | |
| goto err; | |
| error = sysfs_create_group(&node->isn_sysfs_node, &mount_attr_group); | |
| if (error) | |
| goto err; | |
| return node; | |
| err: | |
| /* | |
| * Note kobject_put always calls release, so incfs_sysfs_release will | |
| * free node | |
| */ | |
| kobject_put(&node->isn_sysfs_node); | |
| return ERR_PTR(error); | |
| } | |
| void incfs_free_sysfs_node(struct incfs_sysfs_node *node) | |
| { | |
| if (!node) | |
| return; | |
| sysfs_remove_group(&node->isn_sysfs_node, &mount_attr_group); | |
| kobject_put(&node->isn_sysfs_node); | |
| wait_for_completion_interruptible(&node->isn_completion); | |
| kfree(node); | |
| } |