| // SPDX-License-Identifier: GPL-2.0-only |
| /* inode.c: /proc/openprom handling routines |
| * |
| * Copyright (C) 1996-1999 Jakub Jelinek (jakub@redhat.com) |
| * Copyright (C) 1998 Eddie C. Dost (ecd@skynet.be) |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/types.h> |
| #include <linux/string.h> |
| #include <linux/fs.h> |
| #include <linux/init.h> |
| #include <linux/slab.h> |
| #include <linux/seq_file.h> |
| #include <linux/magic.h> |
| |
| #include <asm/openprom.h> |
| #include <asm/oplib.h> |
| #include <asm/prom.h> |
| #include <linux/uaccess.h> |
| |
| static DEFINE_MUTEX(op_mutex); |
| |
| #define OPENPROM_ROOT_INO 0 |
| |
| enum op_inode_type { |
| op_inode_node, |
| op_inode_prop, |
| }; |
| |
| union op_inode_data { |
| struct device_node *node; |
| struct property *prop; |
| }; |
| |
| struct op_inode_info { |
| struct inode vfs_inode; |
| enum op_inode_type type; |
| union op_inode_data u; |
| }; |
| |
| static struct inode *openprom_iget(struct super_block *sb, ino_t ino); |
| |
| static inline struct op_inode_info *OP_I(struct inode *inode) |
| { |
| return container_of(inode, struct op_inode_info, vfs_inode); |
| } |
| |
| static int is_string(unsigned char *p, int len) |
| { |
| int i; |
| |
| for (i = 0; i < len; i++) { |
| unsigned char val = p[i]; |
| |
| if ((i && !val) || |
| (val >= ' ' && val <= '~')) |
| continue; |
| |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| static int property_show(struct seq_file *f, void *v) |
| { |
| struct property *prop = f->private; |
| void *pval; |
| int len; |
| |
| len = prop->length; |
| pval = prop->value; |
| |
| if (is_string(pval, len)) { |
| while (len > 0) { |
| int n = strlen(pval); |
| |
| seq_printf(f, "%s", (char *) pval); |
| |
| /* Skip over the NULL byte too. */ |
| pval += n + 1; |
| len -= n + 1; |
| |
| if (len > 0) |
| seq_printf(f, " + "); |
| } |
| } else { |
| if (len & 3) { |
| while (len) { |
| len--; |
| if (len) |
| seq_printf(f, "%02x.", |
| *(unsigned char *) pval); |
| else |
| seq_printf(f, "%02x", |
| *(unsigned char *) pval); |
| pval++; |
| } |
| } else { |
| while (len >= 4) { |
| len -= 4; |
| |
| if (len) |
| seq_printf(f, "%08x.", |
| *(unsigned int *) pval); |
| else |
| seq_printf(f, "%08x", |
| *(unsigned int *) pval); |
| pval += 4; |
| } |
| } |
| } |
| seq_printf(f, "\n"); |
| |
| return 0; |
| } |
| |
| static void *property_start(struct seq_file *f, loff_t *pos) |
| { |
| if (*pos == 0) |
| return pos; |
| return NULL; |
| } |
| |
| static void *property_next(struct seq_file *f, void *v, loff_t *pos) |
| { |
| (*pos)++; |
| return NULL; |
| } |
| |
| static void property_stop(struct seq_file *f, void *v) |
| { |
| /* Nothing to do */ |
| } |
| |
| static const struct seq_operations property_op = { |
| .start = property_start, |
| .next = property_next, |
| .stop = property_stop, |
| .show = property_show |
| }; |
| |
| static int property_open(struct inode *inode, struct file *file) |
| { |
| struct op_inode_info *oi = OP_I(inode); |
| int ret; |
| |
| BUG_ON(oi->type != op_inode_prop); |
| |
| ret = seq_open(file, &property_op); |
| if (!ret) { |
| struct seq_file *m = file->private_data; |
| m->private = oi->u.prop; |
| } |
| return ret; |
| } |
| |
| static const struct file_operations openpromfs_prop_ops = { |
| .open = property_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = seq_release, |
| }; |
| |
| static int openpromfs_readdir(struct file *, struct dir_context *); |
| |
| static const struct file_operations openprom_operations = { |
| .read = generic_read_dir, |
| .iterate_shared = openpromfs_readdir, |
| .llseek = generic_file_llseek, |
| }; |
| |
| static struct dentry *openpromfs_lookup(struct inode *, struct dentry *, unsigned int); |
| |
| static const struct inode_operations openprom_inode_operations = { |
| .lookup = openpromfs_lookup, |
| }; |
| |
| static struct dentry *openpromfs_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags) |
| { |
| struct op_inode_info *ent_oi, *oi = OP_I(dir); |
| struct device_node *dp, *child; |
| struct property *prop; |
| enum op_inode_type ent_type; |
| union op_inode_data ent_data; |
| const char *name; |
| struct inode *inode; |
| unsigned int ino; |
| int len; |
| |
| BUG_ON(oi->type != op_inode_node); |
| |
| dp = oi->u.node; |
| |
| name = dentry->d_name.name; |
| len = dentry->d_name.len; |
| |
| mutex_lock(&op_mutex); |
| |
| child = dp->child; |
| while (child) { |
| const char *node_name = kbasename(child->full_name); |
| int n = strlen(node_name); |
| |
| if (len == n && |
| !strncmp(node_name, name, len)) { |
| ent_type = op_inode_node; |
| ent_data.node = child; |
| ino = child->unique_id; |
| goto found; |
| } |
| child = child->sibling; |
| } |
| |
| prop = dp->properties; |
| while (prop) { |
| int n = strlen(prop->name); |
| |
| if (len == n && !strncmp(prop->name, name, len)) { |
| ent_type = op_inode_prop; |
| ent_data.prop = prop; |
| ino = prop->unique_id; |
| goto found; |
| } |
| |
| prop = prop->next; |
| } |
| |
| mutex_unlock(&op_mutex); |
| return ERR_PTR(-ENOENT); |
| |
| found: |
| inode = openprom_iget(dir->i_sb, ino); |
| mutex_unlock(&op_mutex); |
| if (IS_ERR(inode)) |
| return ERR_CAST(inode); |
| ent_oi = OP_I(inode); |
| ent_oi->type = ent_type; |
| ent_oi->u = ent_data; |
| |
| switch (ent_type) { |
| case op_inode_node: |
| inode->i_mode = S_IFDIR | S_IRUGO | S_IXUGO; |
| inode->i_op = &openprom_inode_operations; |
| inode->i_fop = &openprom_operations; |
| set_nlink(inode, 2); |
| break; |
| case op_inode_prop: |
| if (of_node_name_eq(dp, "options") && (len == 17) && |
| !strncmp (name, "security-password", 17)) |
| inode->i_mode = S_IFREG | S_IRUSR | S_IWUSR; |
| else |
| inode->i_mode = S_IFREG | S_IRUGO; |
| inode->i_fop = &openpromfs_prop_ops; |
| set_nlink(inode, 1); |
| inode->i_size = ent_oi->u.prop->length; |
| break; |
| } |
| |
| return d_splice_alias(inode, dentry); |
| } |
| |
| static int openpromfs_readdir(struct file *file, struct dir_context *ctx) |
| { |
| struct inode *inode = file_inode(file); |
| struct op_inode_info *oi = OP_I(inode); |
| struct device_node *dp = oi->u.node; |
| struct device_node *child; |
| struct property *prop; |
| int i; |
| |
| mutex_lock(&op_mutex); |
| |
| if (ctx->pos == 0) { |
| if (!dir_emit(ctx, ".", 1, inode->i_ino, DT_DIR)) |
| goto out; |
| ctx->pos = 1; |
| } |
| if (ctx->pos == 1) { |
| if (!dir_emit(ctx, "..", 2, |
| (dp->parent == NULL ? |
| OPENPROM_ROOT_INO : |
| dp->parent->unique_id), DT_DIR)) |
| goto out; |
| ctx->pos = 2; |
| } |
| i = ctx->pos - 2; |
| |
| /* First, the children nodes as directories. */ |
| child = dp->child; |
| while (i && child) { |
| child = child->sibling; |
| i--; |
| } |
| while (child) { |
| if (!dir_emit(ctx, |
| kbasename(child->full_name), |
| strlen(kbasename(child->full_name)), |
| child->unique_id, DT_DIR)) |
| goto out; |
| |
| ctx->pos++; |
| child = child->sibling; |
| } |
| |
| /* Next, the properties as files. */ |
| prop = dp->properties; |
| while (i && prop) { |
| prop = prop->next; |
| i--; |
| } |
| while (prop) { |
| if (!dir_emit(ctx, prop->name, strlen(prop->name), |
| prop->unique_id, DT_REG)) |
| goto out; |
| |
| ctx->pos++; |
| prop = prop->next; |
| } |
| |
| out: |
| mutex_unlock(&op_mutex); |
| return 0; |
| } |
| |
| static struct kmem_cache *op_inode_cachep; |
| |
| static struct inode *openprom_alloc_inode(struct super_block *sb) |
| { |
| struct op_inode_info *oi; |
| |
| oi = kmem_cache_alloc(op_inode_cachep, GFP_KERNEL); |
| if (!oi) |
| return NULL; |
| |
| return &oi->vfs_inode; |
| } |
| |
| static void openprom_free_inode(struct inode *inode) |
| { |
| kmem_cache_free(op_inode_cachep, OP_I(inode)); |
| } |
| |
| static struct inode *openprom_iget(struct super_block *sb, ino_t ino) |
| { |
| struct inode *inode; |
| |
| inode = iget_locked(sb, ino); |
| if (!inode) |
| return ERR_PTR(-ENOMEM); |
| if (inode->i_state & I_NEW) { |
| inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode); |
| if (inode->i_ino == OPENPROM_ROOT_INO) { |
| inode->i_op = &openprom_inode_operations; |
| inode->i_fop = &openprom_operations; |
| inode->i_mode = S_IFDIR | S_IRUGO | S_IXUGO; |
| } |
| unlock_new_inode(inode); |
| } |
| return inode; |
| } |
| |
| static int openprom_remount(struct super_block *sb, int *flags, char *data) |
| { |
| sync_filesystem(sb); |
| *flags |= SB_NOATIME; |
| return 0; |
| } |
| |
| static const struct super_operations openprom_sops = { |
| .alloc_inode = openprom_alloc_inode, |
| .free_inode = openprom_free_inode, |
| .statfs = simple_statfs, |
| .remount_fs = openprom_remount, |
| }; |
| |
| static int openprom_fill_super(struct super_block *s, void *data, int silent) |
| { |
| struct inode *root_inode; |
| struct op_inode_info *oi; |
| int ret; |
| |
| s->s_flags |= SB_NOATIME; |
| s->s_blocksize = 1024; |
| s->s_blocksize_bits = 10; |
| s->s_magic = OPENPROM_SUPER_MAGIC; |
| s->s_op = &openprom_sops; |
| s->s_time_gran = 1; |
| root_inode = openprom_iget(s, OPENPROM_ROOT_INO); |
| if (IS_ERR(root_inode)) { |
| ret = PTR_ERR(root_inode); |
| goto out_no_root; |
| } |
| |
| oi = OP_I(root_inode); |
| oi->type = op_inode_node; |
| oi->u.node = of_find_node_by_path("/"); |
| |
| s->s_root = d_make_root(root_inode); |
| if (!s->s_root) |
| goto out_no_root_dentry; |
| return 0; |
| |
| out_no_root_dentry: |
| ret = -ENOMEM; |
| out_no_root: |
| printk("openprom_fill_super: get root inode failed\n"); |
| return ret; |
| } |
| |
| static struct dentry *openprom_mount(struct file_system_type *fs_type, |
| int flags, const char *dev_name, void *data) |
| { |
| return mount_single(fs_type, flags, data, openprom_fill_super); |
| } |
| |
| static struct file_system_type openprom_fs_type = { |
| .owner = THIS_MODULE, |
| .name = "openpromfs", |
| .mount = openprom_mount, |
| .kill_sb = kill_anon_super, |
| }; |
| MODULE_ALIAS_FS("openpromfs"); |
| |
| static void op_inode_init_once(void *data) |
| { |
| struct op_inode_info *oi = (struct op_inode_info *) data; |
| |
| inode_init_once(&oi->vfs_inode); |
| } |
| |
| static int __init init_openprom_fs(void) |
| { |
| int err; |
| |
| op_inode_cachep = kmem_cache_create("op_inode_cache", |
| sizeof(struct op_inode_info), |
| 0, |
| (SLAB_RECLAIM_ACCOUNT | |
| SLAB_MEM_SPREAD | SLAB_ACCOUNT), |
| op_inode_init_once); |
| if (!op_inode_cachep) |
| return -ENOMEM; |
| |
| err = register_filesystem(&openprom_fs_type); |
| if (err) |
| kmem_cache_destroy(op_inode_cachep); |
| |
| return err; |
| } |
| |
| static void __exit exit_openprom_fs(void) |
| { |
| unregister_filesystem(&openprom_fs_type); |
| /* |
| * Make sure all delayed rcu free inodes are flushed before we |
| * destroy cache. |
| */ |
| rcu_barrier(); |
| kmem_cache_destroy(op_inode_cachep); |
| } |
| |
| module_init(init_openprom_fs) |
| module_exit(exit_openprom_fs) |
| MODULE_LICENSE("GPL"); |