diff options
Diffstat (limited to 'fs/sysfs')
| -rw-r--r-- | fs/sysfs/Kconfig | 24 | ||||
| -rw-r--r-- | fs/sysfs/Makefile | 3 | ||||
| -rw-r--r-- | fs/sysfs/bin.c | 204 | ||||
| -rw-r--r-- | fs/sysfs/dir.c | 505 | ||||
| -rw-r--r-- | fs/sysfs/file.c | 733 | ||||
| -rw-r--r-- | fs/sysfs/group.c | 343 | ||||
| -rw-r--r-- | fs/sysfs/inode.c | 249 | ||||
| -rw-r--r-- | fs/sysfs/mount.c | 129 | ||||
| -rw-r--r-- | fs/sysfs/symlink.c | 269 | ||||
| -rw-r--r-- | fs/sysfs/sysfs.h | 139 |
10 files changed, 991 insertions, 1607 deletions
diff --git a/fs/sysfs/Kconfig b/fs/sysfs/Kconfig new file mode 100644 index 00000000000..b2756014508 --- /dev/null +++ b/fs/sysfs/Kconfig @@ -0,0 +1,24 @@ +config SYSFS + bool "sysfs file system support" if EXPERT + default y + select KERNFS + help + The sysfs filesystem is a virtual filesystem that the kernel uses to + export internal kernel objects, their attributes, and their + relationships to one another. + + Users can use sysfs to ascertain useful information about the running + kernel, such as the devices the kernel has discovered on each bus and + which driver each is bound to. sysfs can also be used to tune devices + and other kernel subsystems. + + Some system agents rely on the information in sysfs to operate. + /sbin/hotplug uses device and object attributes in sysfs to assist in + delegating policy decisions, like persistently naming devices. + + sysfs is currently used by the block subsystem to mount the root + partition. If sysfs is disabled you must specify the boot device on + the kernel boot command line via its major and minor numbers. For + example, "root=03:01" for /dev/hda1. + + Designers of embedded systems may wish to say N here to conserve space. diff --git a/fs/sysfs/Makefile b/fs/sysfs/Makefile index 7a1ceb946b8..6eff6e1205a 100644 --- a/fs/sysfs/Makefile +++ b/fs/sysfs/Makefile @@ -2,5 +2,4 @@ # Makefile for the sysfs virtual filesystem # -obj-y := inode.o file.o dir.o symlink.o mount.o bin.o \ - group.o +obj-y := file.o dir.o symlink.o mount.o group.o diff --git a/fs/sysfs/bin.c b/fs/sysfs/bin.c deleted file mode 100644 index 78899eeab97..00000000000 --- a/fs/sysfs/bin.c +++ /dev/null @@ -1,204 +0,0 @@ -/* - * bin.c - binary file operations for sysfs. - * - * Copyright (c) 2003 Patrick Mochel - * Copyright (c) 2003 Matthew Wilcox - * Copyright (c) 2004 Silicon Graphics, Inc. - */ - -#undef DEBUG - -#include <linux/errno.h> -#include <linux/fs.h> -#include <linux/kobject.h> -#include <linux/module.h> -#include <linux/slab.h> - -#include <asm/uaccess.h> - -#include "sysfs.h" - -static int -fill_read(struct dentry *dentry, char *buffer, loff_t off, size_t count) -{ - struct bin_attribute * attr = to_bin_attr(dentry); - struct kobject * kobj = to_kobj(dentry->d_parent); - - if (!attr->read) - return -EIO; - - return attr->read(kobj, buffer, off, count); -} - -static ssize_t -read(struct file * file, char __user * userbuf, size_t count, loff_t * off) -{ - char *buffer = file->private_data; - struct dentry *dentry = file->f_dentry; - int size = dentry->d_inode->i_size; - loff_t offs = *off; - int ret; - - if (count > PAGE_SIZE) - count = PAGE_SIZE; - - if (size) { - if (offs > size) - return 0; - if (offs + count > size) - count = size - offs; - } - - ret = fill_read(dentry, buffer, offs, count); - if (ret < 0) - return ret; - count = ret; - - if (copy_to_user(userbuf, buffer, count)) - return -EFAULT; - - pr_debug("offs = %lld, *off = %lld, count = %zd\n", offs, *off, count); - - *off = offs + count; - - return count; -} - -static int -flush_write(struct dentry *dentry, char *buffer, loff_t offset, size_t count) -{ - struct bin_attribute *attr = to_bin_attr(dentry); - struct kobject *kobj = to_kobj(dentry->d_parent); - - if (!attr->write) - return -EIO; - - return attr->write(kobj, buffer, offset, count); -} - -static ssize_t write(struct file * file, const char __user * userbuf, - size_t count, loff_t * off) -{ - char *buffer = file->private_data; - struct dentry *dentry = file->f_dentry; - int size = dentry->d_inode->i_size; - loff_t offs = *off; - - if (count > PAGE_SIZE) - count = PAGE_SIZE; - if (size) { - if (offs > size) - return 0; - if (offs + count > size) - count = size - offs; - } - - if (copy_from_user(buffer, userbuf, count)) - return -EFAULT; - - count = flush_write(dentry, buffer, offs, count); - if (count > 0) - *off = offs + count; - return count; -} - -static int mmap(struct file *file, struct vm_area_struct *vma) -{ - struct dentry *dentry = file->f_dentry; - struct bin_attribute *attr = to_bin_attr(dentry); - struct kobject *kobj = to_kobj(dentry->d_parent); - - if (!attr->mmap) - return -EINVAL; - - return attr->mmap(kobj, attr, vma); -} - -static int open(struct inode * inode, struct file * file) -{ - struct kobject *kobj = sysfs_get_kobject(file->f_dentry->d_parent); - struct bin_attribute * attr = to_bin_attr(file->f_dentry); - int error = -EINVAL; - - if (!kobj || !attr) - goto Done; - - /* Grab the module reference for this attribute if we have one */ - error = -ENODEV; - if (!try_module_get(attr->attr.owner)) - goto Done; - - error = -EACCES; - if ((file->f_mode & FMODE_WRITE) && !(attr->write || attr->mmap)) - goto Error; - if ((file->f_mode & FMODE_READ) && !(attr->read || attr->mmap)) - goto Error; - - error = -ENOMEM; - file->private_data = kmalloc(PAGE_SIZE, GFP_KERNEL); - if (!file->private_data) - goto Error; - - error = 0; - goto Done; - - Error: - module_put(attr->attr.owner); - Done: - if (error && kobj) - kobject_put(kobj); - return error; -} - -static int release(struct inode * inode, struct file * file) -{ - struct kobject * kobj = to_kobj(file->f_dentry->d_parent); - struct bin_attribute * attr = to_bin_attr(file->f_dentry); - u8 * buffer = file->private_data; - - if (kobj) - kobject_put(kobj); - module_put(attr->attr.owner); - kfree(buffer); - return 0; -} - -struct file_operations bin_fops = { - .read = read, - .write = write, - .mmap = mmap, - .llseek = generic_file_llseek, - .open = open, - .release = release, -}; - -/** - * sysfs_create_bin_file - create binary file for object. - * @kobj: object. - * @attr: attribute descriptor. - * - */ - -int sysfs_create_bin_file(struct kobject * kobj, struct bin_attribute * attr) -{ - BUG_ON(!kobj || !kobj->dentry || !attr); - - return sysfs_add_file(kobj->dentry, &attr->attr, SYSFS_KOBJ_BIN_ATTR); -} - - -/** - * sysfs_remove_bin_file - remove binary file for object. - * @kobj: object. - * @attr: attribute descriptor. - * - */ - -int sysfs_remove_bin_file(struct kobject * kobj, struct bin_attribute * attr) -{ - sysfs_hash_and_remove(kobj->dentry,attr->attr.name); - return 0; -} - -EXPORT_SYMBOL_GPL(sysfs_create_bin_file); -EXPORT_SYMBOL_GPL(sysfs_remove_bin_file); diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index 59734ba1ee6..0b45ff42f37 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -1,482 +1,123 @@ /* - * dir.c - Operations for sysfs directories. + * fs/sysfs/dir.c - sysfs core and dir operation implementation + * + * Copyright (c) 2001-3 Patrick Mochel + * Copyright (c) 2007 SUSE Linux Products GmbH + * Copyright (c) 2007 Tejun Heo <teheo@suse.de> + * + * This file is released under the GPLv2. + * + * Please see Documentation/filesystems/sysfs.txt for more information. */ #undef DEBUG #include <linux/fs.h> -#include <linux/mount.h> -#include <linux/module.h> #include <linux/kobject.h> -#include <linux/namei.h> +#include <linux/slab.h> #include "sysfs.h" -DECLARE_RWSEM(sysfs_rename_sem); +DEFINE_SPINLOCK(sysfs_symlink_target_lock); -static void sysfs_d_iput(struct dentry * dentry, struct inode * inode) +void sysfs_warn_dup(struct kernfs_node *parent, const char *name) { - struct sysfs_dirent * sd = dentry->d_fsdata; + char *buf, *path = NULL; - if (sd) { - BUG_ON(sd->s_dentry != dentry); - sd->s_dentry = NULL; - sysfs_put(sd); - } - iput(inode); -} + buf = kzalloc(PATH_MAX, GFP_KERNEL); + if (buf) + path = kernfs_path(parent, buf, PATH_MAX); -static struct dentry_operations sysfs_dentry_ops = { - .d_iput = sysfs_d_iput, -}; + WARN(1, KERN_WARNING "sysfs: cannot create duplicate filename '%s/%s'\n", + path, name); -/* - * Allocates a new sysfs_dirent and links it to the parent sysfs_dirent - */ -static struct sysfs_dirent * sysfs_new_dirent(struct sysfs_dirent * parent_sd, - void * element) -{ - struct sysfs_dirent * sd; - - sd = kmem_cache_alloc(sysfs_dir_cachep, GFP_KERNEL); - if (!sd) - return NULL; - - memset(sd, 0, sizeof(*sd)); - atomic_set(&sd->s_count, 1); - INIT_LIST_HEAD(&sd->s_children); - list_add(&sd->s_sibling, &parent_sd->s_children); - sd->s_element = element; - - return sd; -} - -int sysfs_make_dirent(struct sysfs_dirent * parent_sd, struct dentry * dentry, - void * element, umode_t mode, int type) -{ - struct sysfs_dirent * sd; - - sd = sysfs_new_dirent(parent_sd, element); - if (!sd) - return -ENOMEM; - - sd->s_mode = mode; - sd->s_type = type; - sd->s_dentry = dentry; - if (dentry) { - dentry->d_fsdata = sysfs_get(sd); - dentry->d_op = &sysfs_dentry_ops; - } - - return 0; -} - -static int init_dir(struct inode * inode) -{ - inode->i_op = &sysfs_dir_inode_operations; - inode->i_fop = &sysfs_dir_operations; - - /* directory inodes start off with i_nlink == 2 (for "." entry) */ - inode->i_nlink++; - return 0; -} - -static int init_file(struct inode * inode) -{ - inode->i_size = PAGE_SIZE; - inode->i_fop = &sysfs_file_operations; - return 0; -} - -static int init_symlink(struct inode * inode) -{ - inode->i_op = &sysfs_symlink_inode_operations; - return 0; -} - -static int create_dir(struct kobject * k, struct dentry * p, - const char * n, struct dentry ** d) -{ - int error; - umode_t mode = S_IFDIR| S_IRWXU | S_IRUGO | S_IXUGO; - - down(&p->d_inode->i_sem); - *d = lookup_one_len(n, p, strlen(n)); - if (!IS_ERR(*d)) { - error = sysfs_make_dirent(p->d_fsdata, *d, k, mode, SYSFS_DIR); - if (!error) { - error = sysfs_create(*d, mode, init_dir); - if (!error) { - p->d_inode->i_nlink++; - (*d)->d_op = &sysfs_dentry_ops; - d_rehash(*d); - } - } - if (error && (error != -EEXIST)) { - sysfs_put((*d)->d_fsdata); - d_drop(*d); - } - dput(*d); - } else - error = PTR_ERR(*d); - up(&p->d_inode->i_sem); - return error; -} - - -int sysfs_create_subdir(struct kobject * k, const char * n, struct dentry ** d) -{ - return create_dir(k,k->dentry,n,d); + kfree(buf); } /** - * sysfs_create_dir - create a directory for an object. - * @parent: parent parent object. - * @kobj: object we're creating directory for. + * sysfs_create_dir_ns - create a directory for an object with a namespace tag + * @kobj: object we're creating directory for + * @ns: the namespace tag to use */ - -int sysfs_create_dir(struct kobject * kobj) +int sysfs_create_dir_ns(struct kobject *kobj, const void *ns) { - struct dentry * dentry = NULL; - struct dentry * parent; - int error = 0; + struct kernfs_node *parent, *kn; BUG_ON(!kobj); if (kobj->parent) - parent = kobj->parent->dentry; - else if (sysfs_mount && sysfs_mount->mnt_sb) - parent = sysfs_mount->mnt_sb->s_root; + parent = kobj->parent->sd; else - return -EFAULT; - - error = create_dir(kobj,parent,kobject_name(kobj),&dentry); - if (!error) - kobj->dentry = dentry; - return error; -} + parent = sysfs_root_kn; -/* attaches attribute's sysfs_dirent to the dentry corresponding to the - * attribute file - */ -static int sysfs_attach_attr(struct sysfs_dirent * sd, struct dentry * dentry) -{ - struct attribute * attr = NULL; - struct bin_attribute * bin_attr = NULL; - int (* init) (struct inode *) = NULL; - int error = 0; + if (!parent) + return -ENOENT; - if (sd->s_type & SYSFS_KOBJ_BIN_ATTR) { - bin_attr = sd->s_element; - attr = &bin_attr->attr; - } else { - attr = sd->s_element; - init = init_file; - } - - dentry->d_fsdata = sysfs_get(sd); - sd->s_dentry = dentry; - error = sysfs_create(dentry, (attr->mode & S_IALLUGO) | S_IFREG, init); - if (error) { - sysfs_put(sd); - return error; + kn = kernfs_create_dir_ns(parent, kobject_name(kobj), + S_IRWXU | S_IRUGO | S_IXUGO, kobj, ns); + if (IS_ERR(kn)) { + if (PTR_ERR(kn) == -EEXIST) + sysfs_warn_dup(parent, kobject_name(kobj)); + return PTR_ERR(kn); } - if (bin_attr) { - dentry->d_inode->i_size = bin_attr->size; - dentry->d_inode->i_fop = &bin_fops; - } - dentry->d_op = &sysfs_dentry_ops; - d_rehash(dentry); - + kobj->sd = kn; return 0; } -static int sysfs_attach_link(struct sysfs_dirent * sd, struct dentry * dentry) -{ - int err = 0; - - dentry->d_fsdata = sysfs_get(sd); - sd->s_dentry = dentry; - err = sysfs_create(dentry, S_IFLNK|S_IRWXUGO, init_symlink); - if (!err) { - dentry->d_op = &sysfs_dentry_ops; - d_rehash(dentry); - } else - sysfs_put(sd); - - return err; -} - -static struct dentry * sysfs_lookup(struct inode *dir, struct dentry *dentry, - struct nameidata *nd) -{ - struct sysfs_dirent * parent_sd = dentry->d_parent->d_fsdata; - struct sysfs_dirent * sd; - int err = 0; - - list_for_each_entry(sd, &parent_sd->s_children, s_sibling) { - if (sd->s_type & SYSFS_NOT_PINNED) { - const unsigned char * name = sysfs_get_name(sd); - - if (strcmp(name, dentry->d_name.name)) - continue; - - if (sd->s_type & SYSFS_KOBJ_LINK) - err = sysfs_attach_link(sd, dentry); - else - err = sysfs_attach_attr(sd, dentry); - break; - } - } - - return ERR_PTR(err); -} - -struct inode_operations sysfs_dir_inode_operations = { - .lookup = sysfs_lookup, - .setattr = sysfs_setattr, -}; - -static void remove_dir(struct dentry * d) -{ - struct dentry * parent = dget(d->d_parent); - struct sysfs_dirent * sd; - - down(&parent->d_inode->i_sem); - d_delete(d); - sd = d->d_fsdata; - list_del_init(&sd->s_sibling); - sysfs_put(sd); - if (d->d_inode) - simple_rmdir(parent->d_inode,d); - - pr_debug(" o %s removing done (%d)\n",d->d_name.name, - atomic_read(&d->d_count)); - - up(&parent->d_inode->i_sem); - dput(parent); -} - -void sysfs_remove_subdir(struct dentry * d) -{ - remove_dir(d); -} - - /** * sysfs_remove_dir - remove an object's directory. - * @kobj: object. + * @kobj: object. * - * The only thing special about this is that we remove any files in + * The only thing special about this is that we remove any files in * the directory before we remove the directory, and we've inlined * what used to be sysfs_rmdir() below, instead of calling separately. */ - -void sysfs_remove_dir(struct kobject * kobj) -{ - struct dentry * dentry = dget(kobj->dentry); - struct sysfs_dirent * parent_sd; - struct sysfs_dirent * sd, * tmp; - - if (!dentry) - return; - - pr_debug("sysfs %s: removing dir\n",dentry->d_name.name); - down(&dentry->d_inode->i_sem); - parent_sd = dentry->d_fsdata; - list_for_each_entry_safe(sd, tmp, &parent_sd->s_children, s_sibling) { - if (!sd->s_element || !(sd->s_type & SYSFS_NOT_PINNED)) - continue; - list_del_init(&sd->s_sibling); - sysfs_drop_dentry(sd, dentry); - sysfs_put(sd); - } - up(&dentry->d_inode->i_sem); - - remove_dir(dentry); - /** - * Drop reference from dget() on entrance. +void sysfs_remove_dir(struct kobject *kobj) +{ + struct kernfs_node *kn = kobj->sd; + + /* + * In general, kboject owner is responsible for ensuring removal + * doesn't race with other operations and sysfs doesn't provide any + * protection; however, when @kobj is used as a symlink target, the + * symlinking entity usually doesn't own @kobj and thus has no + * control over removal. @kobj->sd may be removed anytime + * and symlink code may end up dereferencing an already freed node. + * + * sysfs_symlink_target_lock synchronizes @kobj->sd + * disassociation against symlink operations so that symlink code + * can safely dereference @kobj->sd. */ - dput(dentry); -} - -int sysfs_rename_dir(struct kobject * kobj, const char *new_name) -{ - int error = 0; - struct dentry * new_dentry, * parent; - - if (!strcmp(kobject_name(kobj), new_name)) - return -EINVAL; - - if (!kobj->parent) - return -EINVAL; - - down_write(&sysfs_rename_sem); - parent = kobj->parent->dentry; - - down(&parent->d_inode->i_sem); + spin_lock(&sysfs_symlink_target_lock); + kobj->sd = NULL; + spin_unlock(&sysfs_symlink_target_lock); - new_dentry = lookup_one_len(new_name, parent, strlen(new_name)); - if (!IS_ERR(new_dentry)) { - if (!new_dentry->d_inode) { - error = kobject_set_name(kobj, "%s", new_name); - if (!error) { - d_add(new_dentry, NULL); - d_move(kobj->dentry, new_dentry); - } - else - d_drop(new_dentry); - } else - error = -EEXIST; - dput(new_dentry); + if (kn) { + WARN_ON_ONCE(kernfs_type(kn) != KERNFS_DIR); + kernfs_remove(kn); } - up(&parent->d_inode->i_sem); - up_write(&sysfs_rename_sem); - - return error; -} - -static int sysfs_dir_open(struct inode *inode, struct file *file) -{ - struct dentry * dentry = file->f_dentry; - struct sysfs_dirent * parent_sd = dentry->d_fsdata; - - down(&dentry->d_inode->i_sem); - file->private_data = sysfs_new_dirent(parent_sd, NULL); - up(&dentry->d_inode->i_sem); - - return file->private_data ? 0 : -ENOMEM; - -} - -static int sysfs_dir_close(struct inode *inode, struct file *file) -{ - struct dentry * dentry = file->f_dentry; - struct sysfs_dirent * cursor = file->private_data; - - down(&dentry->d_inode->i_sem); - list_del_init(&cursor->s_sibling); - up(&dentry->d_inode->i_sem); - - release_sysfs_dirent(cursor); - - return 0; } -/* Relationship between s_mode and the DT_xxx types */ -static inline unsigned char dt_type(struct sysfs_dirent *sd) +int sysfs_rename_dir_ns(struct kobject *kobj, const char *new_name, + const void *new_ns) { - return (sd->s_mode >> 12) & 15; -} - -static int sysfs_readdir(struct file * filp, void * dirent, filldir_t filldir) -{ - struct dentry *dentry = filp->f_dentry; - struct sysfs_dirent * parent_sd = dentry->d_fsdata; - struct sysfs_dirent *cursor = filp->private_data; - struct list_head *p, *q = &cursor->s_sibling; - ino_t ino; - int i = filp->f_pos; - - switch (i) { - case 0: - ino = dentry->d_inode->i_ino; - if (filldir(dirent, ".", 1, i, ino, DT_DIR) < 0) - break; - filp->f_pos++; - i++; - /* fallthrough */ - case 1: - ino = parent_ino(dentry); - if (filldir(dirent, "..", 2, i, ino, DT_DIR) < 0) - break; - filp->f_pos++; - i++; - /* fallthrough */ - default: - if (filp->f_pos == 2) { - list_del(q); - list_add(q, &parent_sd->s_children); - } - for (p=q->next; p!= &parent_sd->s_children; p=p->next) { - struct sysfs_dirent *next; - const char * name; - int len; - - next = list_entry(p, struct sysfs_dirent, - s_sibling); - if (!next->s_element) - continue; - - name = sysfs_get_name(next); - len = strlen(name); - if (next->s_dentry) - ino = next->s_dentry->d_inode->i_ino; - else - ino = iunique(sysfs_sb, 2); - - if (filldir(dirent, name, len, filp->f_pos, ino, - dt_type(next)) < 0) - return 0; + struct kernfs_node *parent; + int ret; - list_del(q); - list_add(q, p); - p = q; - filp->f_pos++; - } - } - return 0; + parent = kernfs_get_parent(kobj->sd); + ret = kernfs_rename_ns(kobj->sd, parent, new_name, new_ns); + kernfs_put(parent); + return ret; } -static loff_t sysfs_dir_lseek(struct file * file, loff_t offset, int origin) +int sysfs_move_dir_ns(struct kobject *kobj, struct kobject *new_parent_kobj, + const void *new_ns) { - struct dentry * dentry = file->f_dentry; + struct kernfs_node *kn = kobj->sd; + struct kernfs_node *new_parent; - down(&dentry->d_inode->i_sem); - switch (origin) { - case 1: - offset += file->f_pos; - case 0: - if (offset >= 0) - break; - default: - up(&file->f_dentry->d_inode->i_sem); - return -EINVAL; - } - if (offset != file->f_pos) { - file->f_pos = offset; - if (file->f_pos >= 2) { - struct sysfs_dirent *sd = dentry->d_fsdata; - struct sysfs_dirent *cursor = file->private_data; - struct list_head *p; - loff_t n = file->f_pos - 2; + new_parent = new_parent_kobj && new_parent_kobj->sd ? + new_parent_kobj->sd : sysfs_root_kn; - list_del(&cursor->s_sibling); - p = sd->s_children.next; - while (n && p != &sd->s_children) { - struct sysfs_dirent *next; - next = list_entry(p, struct sysfs_dirent, - s_sibling); - if (next->s_element) - n--; - p = p->next; - } - list_add_tail(&cursor->s_sibling, p); - } - } - up(&dentry->d_inode->i_sem); - return offset; + return kernfs_rename_ns(kn, new_parent, kn->name, new_ns); } - -struct file_operations sysfs_dir_operations = { - .open = sysfs_dir_open, - .release = sysfs_dir_close, - .llseek = sysfs_dir_lseek, - .read = generic_read_dir, - .readdir = sysfs_readdir, -}; - -EXPORT_SYMBOL_GPL(sysfs_create_dir); -EXPORT_SYMBOL_GPL(sysfs_remove_dir); -EXPORT_SYMBOL_GPL(sysfs_rename_dir); - diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c index 4013d7905e8..e9ef59b3abb 100644 --- a/fs/sysfs/file.c +++ b/fs/sysfs/file.c @@ -1,430 +1,332 @@ /* - * file.c - operations for regular (text) files. + * fs/sysfs/file.c - sysfs regular (text) file implementation + * + * Copyright (c) 2001-3 Patrick Mochel + * Copyright (c) 2007 SUSE Linux Products GmbH + * Copyright (c) 2007 Tejun Heo <teheo@suse.de> + * + * This file is released under the GPLv2. + * + * Please see Documentation/filesystems/sysfs.txt for more information. */ #include <linux/module.h> -#include <linux/fsnotify.h> #include <linux/kobject.h> -#include <linux/namei.h> -#include <asm/uaccess.h> -#include <asm/semaphore.h> +#include <linux/kallsyms.h> +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/seq_file.h> #include "sysfs.h" - -#define to_subsys(k) container_of(k,struct subsystem,kset.kobj) -#define to_sattr(a) container_of(a,struct subsys_attribute,attr) +#include "../kernfs/kernfs-internal.h" /* - * Subsystem file operations. - * These operations allow subsystems to have files that can be - * read/written. + * Determine ktype->sysfs_ops for the given kernfs_node. This function + * must be called while holding an active reference. */ -static ssize_t -subsys_attr_show(struct kobject * kobj, struct attribute * attr, char * page) +static const struct sysfs_ops *sysfs_file_ops(struct kernfs_node *kn) { - struct subsystem * s = to_subsys(kobj); - struct subsys_attribute * sattr = to_sattr(attr); - ssize_t ret = -EIO; + struct kobject *kobj = kn->parent->priv; - if (sattr->show) - ret = sattr->show(s,page); - return ret; + if (kn->flags & KERNFS_LOCKDEP) + lockdep_assert_held(kn); + return kobj->ktype ? kobj->ktype->sysfs_ops : NULL; } -static ssize_t -subsys_attr_store(struct kobject * kobj, struct attribute * attr, - const char * page, size_t count) +/* + * Reads on sysfs are handled through seq_file, which takes care of hairy + * details like buffering and seeking. The following function pipes + * sysfs_ops->show() result through seq_file. + */ +static int sysfs_kf_seq_show(struct seq_file *sf, void *v) { - struct subsystem * s = to_subsys(kobj); - struct subsys_attribute * sattr = to_sattr(attr); - ssize_t ret = -EIO; + struct kernfs_open_file *of = sf->private; + struct kobject *kobj = of->kn->parent->priv; + const struct sysfs_ops *ops = sysfs_file_ops(of->kn); + ssize_t count; + char *buf; - if (sattr->store) - ret = sattr->store(s,page,count); - return ret; -} + /* acquire buffer and ensure that it's >= PAGE_SIZE and clear */ + count = seq_get_buf(sf, &buf); + if (count < PAGE_SIZE) { + seq_commit(sf, -1); + return 0; + } + memset(buf, 0, PAGE_SIZE); -static struct sysfs_ops subsys_sysfs_ops = { - .show = subsys_attr_show, - .store = subsys_attr_store, -}; + /* + * Invoke show(). Control may reach here via seq file lseek even + * if @ops->show() isn't implemented. + */ + if (ops->show) { + count = ops->show(kobj, of->kn->priv, buf); + if (count < 0) + return count; + } + /* + * The code works fine with PAGE_SIZE return but it's likely to + * indicate truncated result or overflow in normal use cases. + */ + if (count >= (ssize_t)PAGE_SIZE) { + print_symbol("fill_read_buffer: %s returned bad count\n", + (unsigned long)ops->show); + /* Try to struggle along */ + count = PAGE_SIZE - 1; + } + seq_commit(sf, count); + return 0; +} -struct sysfs_buffer { - size_t count; - loff_t pos; - char * page; - struct sysfs_ops * ops; - struct semaphore sem; - int needs_read_fill; -}; +static ssize_t sysfs_kf_bin_read(struct kernfs_open_file *of, char *buf, + size_t count, loff_t pos) +{ + struct bin_attribute *battr = of->kn->priv; + struct kobject *kobj = of->kn->parent->priv; + loff_t size = file_inode(of->file)->i_size; + if (!count) + return 0; -/** - * fill_read_buffer - allocate and fill buffer from object. - * @dentry: dentry pointer. - * @buffer: data buffer for file. - * - * Allocate @buffer->page, if it hasn't been already, then call the - * kobject's show() method to fill the buffer with this attribute's - * data. - * This is called only once, on the file's first read. - */ -static int fill_read_buffer(struct dentry * dentry, struct sysfs_buffer * buffer) -{ - struct attribute * attr = to_attr(dentry); - struct kobject * kobj = to_kobj(dentry->d_parent); - struct sysfs_ops * ops = buffer->ops; - int ret = 0; - ssize_t count; + if (size) { + if (pos > size) + return 0; + if (pos + count > size) + count = size - pos; + } - if (!buffer->page) - buffer->page = (char *) get_zeroed_page(GFP_KERNEL); - if (!buffer->page) - return -ENOMEM; + if (!battr->read) + return -EIO; - count = ops->show(kobj,attr,buffer->page); - buffer->needs_read_fill = 0; - BUG_ON(count > (ssize_t)PAGE_SIZE); - if (count >= 0) - buffer->count = count; - else - ret = count; - return ret; + return battr->read(of->file, kobj, battr, buf, pos, count); } - -/** - * flush_read_buffer - push buffer to userspace. - * @buffer: data buffer for file. - * @buf: user-passed buffer. - * @count: number of bytes requested. - * @ppos: file position. - * - * Copy the buffer we filled in fill_read_buffer() to userspace. - * This is done at the reader's leisure, copying and advancing - * the amount they specify each time. - * This may be called continuously until the buffer is empty. - */ -static int flush_read_buffer(struct sysfs_buffer * buffer, char __user * buf, - size_t count, loff_t * ppos) +/* kernfs write callback for regular sysfs files */ +static ssize_t sysfs_kf_write(struct kernfs_open_file *of, char *buf, + size_t count, loff_t pos) { - int error; + const struct sysfs_ops *ops = sysfs_file_ops(of->kn); + struct kobject *kobj = of->kn->parent->priv; - if (*ppos > buffer->count) + if (!count) return 0; - if (count > (buffer->count - *ppos)) - count = buffer->count - *ppos; - - error = copy_to_user(buf,buffer->page + *ppos,count); - if (!error) - *ppos += count; - return error ? -EFAULT : count; + return ops->store(kobj, of->kn->priv, buf, count); } -/** - * sysfs_read_file - read an attribute. - * @file: file pointer. - * @buf: buffer to fill. - * @count: number of bytes to read. - * @ppos: starting offset in file. - * - * Userspace wants to read an attribute file. The attribute descriptor - * is in the file's ->d_fsdata. The target object is in the directory's - * ->d_fsdata. - * - * We call fill_read_buffer() to allocate and fill the buffer from the - * object's show() method exactly once (if the read is happening from - * the beginning of the file). That should fill the entire buffer with - * all the data the object has to offer for that attribute. - * We then call flush_read_buffer() to copy the buffer to userspace - * in the increments specified. - */ - -static ssize_t -sysfs_read_file(struct file *file, char __user *buf, size_t count, loff_t *ppos) +/* kernfs write callback for bin sysfs files */ +static ssize_t sysfs_kf_bin_write(struct kernfs_open_file *of, char *buf, + size_t count, loff_t pos) { - struct sysfs_buffer * buffer = file->private_data; - ssize_t retval = 0; - - down(&buffer->sem); - if (buffer->needs_read_fill) { - if ((retval = fill_read_buffer(file->f_dentry,buffer))) - goto out; + struct bin_attribute *battr = of->kn->priv; + struct kobject *kobj = of->kn->parent->priv; + loff_t size = file_inode(of->file)->i_size; + + if (size) { + if (size <= pos) + return 0; + count = min_t(ssize_t, count, size - pos); } - pr_debug("%s: count = %d, ppos = %lld, buf = %s\n", - __FUNCTION__,count,*ppos,buffer->page); - retval = flush_read_buffer(buffer,buf,count,ppos); -out: - up(&buffer->sem); - return retval; -} + if (!count) + return 0; + if (!battr->write) + return -EIO; -/** - * fill_write_buffer - copy buffer from userspace. - * @buffer: data buffer for file. - * @buf: data from user. - * @count: number of bytes in @userbuf. - * - * Allocate @buffer->page if it hasn't been already, then - * copy the user-supplied buffer into it. - */ + return battr->write(of->file, kobj, battr, buf, pos, count); +} -static int -fill_write_buffer(struct sysfs_buffer * buffer, const char __user * buf, size_t count) +static int sysfs_kf_bin_mmap(struct kernfs_open_file *of, + struct vm_area_struct *vma) { - int error; - - if (!buffer->page) - buffer->page = (char *)get_zeroed_page(GFP_KERNEL); - if (!buffer->page) - return -ENOMEM; + struct bin_attribute *battr = of->kn->priv; + struct kobject *kobj = of->kn->parent->priv; - if (count >= PAGE_SIZE) - count = PAGE_SIZE; - error = copy_from_user(buffer->page,buf,count); - buffer->needs_read_fill = 1; - return error ? -EFAULT : count; + return battr->mmap(of->file, kobj, battr, vma); } - -/** - * flush_write_buffer - push buffer to kobject. - * @dentry: dentry to the attribute - * @buffer: data buffer for file. - * @count: number of bytes - * - * Get the correct pointers for the kobject and the attribute we're - * dealing with, then call the store() method for the attribute, - * passing the buffer that we acquired in fill_write_buffer(). - */ - -static int -flush_write_buffer(struct dentry * dentry, struct sysfs_buffer * buffer, size_t count) +void sysfs_notify(struct kobject *kobj, const char *dir, const char *attr) { - struct attribute * attr = to_attr(dentry); - struct kobject * kobj = to_kobj(dentry->d_parent); - struct sysfs_ops * ops = buffer->ops; - - return ops->store(kobj,attr,buffer->page,count); -} + struct kernfs_node *kn = kobj->sd, *tmp; + if (kn && dir) + kn = kernfs_find_and_get(kn, dir); + else + kernfs_get(kn); -/** - * sysfs_write_file - write an attribute. - * @file: file pointer - * @buf: data to write - * @count: number of bytes - * @ppos: starting offset - * - * Similar to sysfs_read_file(), though working in the opposite direction. - * We allocate and fill the data from the user in fill_write_buffer(), - * then push it to the kobject in flush_write_buffer(). - * There is no easy way for us to know if userspace is only doing a partial - * write, so we don't support them. We expect the entire buffer to come - * on the first write. - * Hint: if you're writing a value, first read the file, modify only the - * the value you're changing, then write entire buffer back. - */ + if (kn && attr) { + tmp = kernfs_find_and_get(kn, attr); + kernfs_put(kn); + kn = tmp; + } -static ssize_t -sysfs_write_file(struct file *file, const char __user *buf, size_t count, loff_t *ppos) -{ - struct sysfs_buffer * buffer = file->private_data; - ssize_t len; - - down(&buffer->sem); - len = fill_write_buffer(buffer, buf, count); - if (len > 0) - len = flush_write_buffer(file->f_dentry, buffer, len); - if (len > 0) - *ppos += len; - up(&buffer->sem); - return len; + if (kn) { + kernfs_notify(kn); + kernfs_put(kn); + } } +EXPORT_SYMBOL_GPL(sysfs_notify); -static int check_perm(struct inode * inode, struct file * file) -{ - struct kobject *kobj = sysfs_get_kobject(file->f_dentry->d_parent); - struct attribute * attr = to_attr(file->f_dentry); - struct sysfs_buffer * buffer; - struct sysfs_ops * ops = NULL; - int error = 0; - - if (!kobj || !attr) - goto Einval; - - /* Grab the module reference for this attribute if we have one */ - if (!try_module_get(attr->owner)) { - error = -ENODEV; - goto Done; - } +static const struct kernfs_ops sysfs_file_kfops_empty = { +}; - /* if the kobject has no ktype, then we assume that it is a subsystem - * itself, and use ops for it. - */ - if (kobj->kset && kobj->kset->ktype) - ops = kobj->kset->ktype->sysfs_ops; - else if (kobj->ktype) - ops = kobj->ktype->sysfs_ops; - else - ops = &subsys_sysfs_ops; +static const struct kernfs_ops sysfs_file_kfops_ro = { + .seq_show = sysfs_kf_seq_show, +}; - /* No sysfs operations, either from having no subsystem, - * or the subsystem have no operations. - */ - if (!ops) - goto Eaccess; +static const struct kernfs_ops sysfs_file_kfops_wo = { + .write = sysfs_kf_write, +}; - /* File needs write support. - * The inode's perms must say it's ok, - * and we must have a store method. - */ - if (file->f_mode & FMODE_WRITE) { +static const struct kernfs_ops sysfs_file_kfops_rw = { + .seq_show = sysfs_kf_seq_show, + .write = sysfs_kf_write, +}; - if (!(inode->i_mode & S_IWUGO) || !ops->store) - goto Eaccess; +static const struct kernfs_ops sysfs_bin_kfops_ro = { + .read = sysfs_kf_bin_read, +}; - } +static const struct kernfs_ops sysfs_bin_kfops_wo = { + .write = sysfs_kf_bin_write, +}; - /* File needs read support. - * The inode's perms must say it's ok, and we there - * must be a show method for it. - */ - if (file->f_mode & FMODE_READ) { - if (!(inode->i_mode & S_IRUGO) || !ops->show) - goto Eaccess; - } +static const struct kernfs_ops sysfs_bin_kfops_rw = { + .read = sysfs_kf_bin_read, + .write = sysfs_kf_bin_write, +}; - /* No error? Great, allocate a buffer for the file, and store it - * it in file->private_data for easy access. - */ - buffer = kmalloc(sizeof(struct sysfs_buffer),GFP_KERNEL); - if (buffer) { - memset(buffer,0,sizeof(struct sysfs_buffer)); - init_MUTEX(&buffer->sem); - buffer->needs_read_fill = 1; - buffer->ops = ops; - file->private_data = buffer; - } else - error = -ENOMEM; - goto Done; - - Einval: - error = -EINVAL; - goto Done; - Eaccess: - error = -EACCES; - module_put(attr->owner); - Done: - if (error && kobj) - kobject_put(kobj); - return error; -} +static const struct kernfs_ops sysfs_bin_kfops_mmap = { + .read = sysfs_kf_bin_read, + .write = sysfs_kf_bin_write, + .mmap = sysfs_kf_bin_mmap, +}; -static int sysfs_open_file(struct inode * inode, struct file * filp) +int sysfs_add_file_mode_ns(struct kernfs_node *parent, + const struct attribute *attr, bool is_bin, + umode_t mode, const void *ns) { - return check_perm(inode,filp); -} + struct lock_class_key *key = NULL; + const struct kernfs_ops *ops; + struct kernfs_node *kn; + loff_t size; + + if (!is_bin) { + struct kobject *kobj = parent->priv; + const struct sysfs_ops *sysfs_ops = kobj->ktype->sysfs_ops; + + /* every kobject with an attribute needs a ktype assigned */ + if (WARN(!sysfs_ops, KERN_ERR + "missing sysfs attribute operations for kobject: %s\n", + kobject_name(kobj))) + return -EINVAL; + + if (sysfs_ops->show && sysfs_ops->store) + ops = &sysfs_file_kfops_rw; + else if (sysfs_ops->show) + ops = &sysfs_file_kfops_ro; + else if (sysfs_ops->store) + ops = &sysfs_file_kfops_wo; + else + ops = &sysfs_file_kfops_empty; + + size = PAGE_SIZE; + } else { + struct bin_attribute *battr = (void *)attr; + + if (battr->mmap) + ops = &sysfs_bin_kfops_mmap; + else if (battr->read && battr->write) + ops = &sysfs_bin_kfops_rw; + else if (battr->read) + ops = &sysfs_bin_kfops_ro; + else if (battr->write) + ops = &sysfs_bin_kfops_wo; + else + ops = &sysfs_file_kfops_empty; + + size = battr->size; + } -static int sysfs_release(struct inode * inode, struct file * filp) -{ - struct kobject * kobj = to_kobj(filp->f_dentry->d_parent); - struct attribute * attr = to_attr(filp->f_dentry); - struct module * owner = attr->owner; - struct sysfs_buffer * buffer = filp->private_data; - - if (kobj) - kobject_put(kobj); - /* After this point, attr should not be accessed. */ - module_put(owner); - - if (buffer) { - if (buffer->page) - free_page((unsigned long)buffer->page); - kfree(buffer); +#ifdef CONFIG_DEBUG_LOCK_ALLOC + if (!attr->ignore_lockdep) + key = attr->key ?: (struct lock_class_key *)&attr->skey; +#endif + kn = __kernfs_create_file(parent, attr->name, mode, size, ops, + (void *)attr, ns, true, key); + if (IS_ERR(kn)) { + if (PTR_ERR(kn) == -EEXIST) + sysfs_warn_dup(parent, attr->name); + return PTR_ERR(kn); } return 0; } -struct file_operations sysfs_file_operations = { - .read = sysfs_read_file, - .write = sysfs_write_file, - .llseek = generic_file_llseek, - .open = sysfs_open_file, - .release = sysfs_release, -}; - - -int sysfs_add_file(struct dentry * dir, const struct attribute * attr, int type) +int sysfs_add_file(struct kernfs_node *parent, const struct attribute *attr, + bool is_bin) { - struct sysfs_dirent * parent_sd = dir->d_fsdata; - umode_t mode = (attr->mode & S_IALLUGO) | S_IFREG; - int error = 0; - - down(&dir->d_inode->i_sem); - error = sysfs_make_dirent(parent_sd, NULL, (void *) attr, mode, type); - up(&dir->d_inode->i_sem); - - return error; + return sysfs_add_file_mode_ns(parent, attr, is_bin, attr->mode, NULL); } - /** - * sysfs_create_file - create an attribute file for an object. - * @kobj: object we're creating for. - * @attr: atrribute descriptor. + * sysfs_create_file_ns - create an attribute file for an object with custom ns + * @kobj: object we're creating for + * @attr: attribute descriptor + * @ns: namespace the new file should belong to */ - -int sysfs_create_file(struct kobject * kobj, const struct attribute * attr) +int sysfs_create_file_ns(struct kobject *kobj, const struct attribute *attr, + const void *ns) { - BUG_ON(!kobj || !kobj->dentry || !attr); + BUG_ON(!kobj || !kobj->sd || !attr); - return sysfs_add_file(kobj->dentry, attr, SYSFS_KOBJ_ATTR); + return sysfs_add_file_mode_ns(kobj->sd, attr, false, attr->mode, ns); } +EXPORT_SYMBOL_GPL(sysfs_create_file_ns); +int sysfs_create_files(struct kobject *kobj, const struct attribute **ptr) +{ + int err = 0; + int i; + + for (i = 0; ptr[i] && !err; i++) + err = sysfs_create_file(kobj, ptr[i]); + if (err) + while (--i >= 0) + sysfs_remove_file(kobj, ptr[i]); + return err; +} +EXPORT_SYMBOL_GPL(sysfs_create_files); /** - * sysfs_update_file - update the modified timestamp on an object attribute. + * sysfs_add_file_to_group - add an attribute file to a pre-existing group. * @kobj: object we're acting for. * @attr: attribute descriptor. + * @group: group name. */ -int sysfs_update_file(struct kobject * kobj, const struct attribute * attr) +int sysfs_add_file_to_group(struct kobject *kobj, + const struct attribute *attr, const char *group) { - struct dentry * dir = kobj->dentry; - struct dentry * victim; - int res = -ENOENT; - - down(&dir->d_inode->i_sem); - victim = lookup_one_len(attr->name, dir, strlen(attr->name)); - if (!IS_ERR(victim)) { - /* make sure dentry is really there */ - if (victim->d_inode && - (victim->d_parent->d_inode == dir->d_inode)) { - victim->d_inode->i_mtime = CURRENT_TIME; - fsnotify_modify(victim); - - /** - * Drop reference from initial sysfs_get_dentry(). - */ - dput(victim); - res = 0; - } else - d_drop(victim); - - /** - * Drop the reference acquired from sysfs_get_dentry() above. - */ - dput(victim); + struct kernfs_node *parent; + int error; + + if (group) { + parent = kernfs_find_and_get(kobj->sd, group); + } else { + parent = kobj->sd; + kernfs_get(parent); } - up(&dir->d_inode->i_sem); - return res; -} + if (!parent) + return -ENOENT; + error = sysfs_add_file(parent, attr, false); + kernfs_put(parent); + + return error; +} +EXPORT_SYMBOL_GPL(sysfs_add_file_to_group); /** * sysfs_chmod_file - update the modified mode value on an object attribute. @@ -433,51 +335,122 @@ int sysfs_update_file(struct kobject * kobj, const struct attribute * attr) * @mode: file permissions. * */ -int sysfs_chmod_file(struct kobject *kobj, struct attribute *attr, mode_t mode) +int sysfs_chmod_file(struct kobject *kobj, const struct attribute *attr, + umode_t mode) { - struct dentry *dir = kobj->dentry; - struct dentry *victim; - struct inode * inode; + struct kernfs_node *kn; struct iattr newattrs; - int res = -ENOENT; - - down(&dir->d_inode->i_sem); - victim = lookup_one_len(attr->name, dir, strlen(attr->name)); - if (!IS_ERR(victim)) { - if (victim->d_inode && - (victim->d_parent->d_inode == dir->d_inode)) { - inode = victim->d_inode; - down(&inode->i_sem); - newattrs.ia_mode = (mode & S_IALLUGO) | - (inode->i_mode & ~S_IALLUGO); - newattrs.ia_valid = ATTR_MODE | ATTR_CTIME; - res = notify_change(victim, &newattrs); - up(&inode->i_sem); - } - dput(victim); - } - up(&dir->d_inode->i_sem); + int rc; + + kn = kernfs_find_and_get(kobj->sd, attr->name); + if (!kn) + return -ENOENT; + + newattrs.ia_mode = (mode & S_IALLUGO) | (kn->mode & ~S_IALLUGO); + newattrs.ia_valid = ATTR_MODE; - return res; + rc = kernfs_setattr(kn, &newattrs); + + kernfs_put(kn); + return rc; } EXPORT_SYMBOL_GPL(sysfs_chmod_file); +/** + * sysfs_remove_file_ns - remove an object attribute with a custom ns tag + * @kobj: object we're acting for + * @attr: attribute descriptor + * @ns: namespace tag of the file to remove + * + * Hash the attribute name and namespace tag and kill the victim. + */ +void sysfs_remove_file_ns(struct kobject *kobj, const struct attribute *attr, + const void *ns) +{ + struct kernfs_node *parent = kobj->sd; + + kernfs_remove_by_name_ns(parent, attr->name, ns); +} +EXPORT_SYMBOL_GPL(sysfs_remove_file_ns); /** - * sysfs_remove_file - remove an object attribute. - * @kobj: object we're acting for. - * @attr: attribute descriptor. + * sysfs_remove_file_self - remove an object attribute from its own method + * @kobj: object we're acting for + * @attr: attribute descriptor * - * Hash the attribute name and kill the victim. + * See kernfs_remove_self() for details. */ +bool sysfs_remove_file_self(struct kobject *kobj, const struct attribute *attr) +{ + struct kernfs_node *parent = kobj->sd; + struct kernfs_node *kn; + bool ret; -void sysfs_remove_file(struct kobject * kobj, const struct attribute * attr) + kn = kernfs_find_and_get(parent, attr->name); + if (WARN_ON_ONCE(!kn)) + return false; + + ret = kernfs_remove_self(kn); + + kernfs_put(kn); + return ret; +} + +void sysfs_remove_files(struct kobject *kobj, const struct attribute **ptr) { - sysfs_hash_and_remove(kobj->dentry,attr->name); + int i; + for (i = 0; ptr[i]; i++) + sysfs_remove_file(kobj, ptr[i]); } +EXPORT_SYMBOL_GPL(sysfs_remove_files); +/** + * sysfs_remove_file_from_group - remove an attribute file from a group. + * @kobj: object we're acting for. + * @attr: attribute descriptor. + * @group: group name. + */ +void sysfs_remove_file_from_group(struct kobject *kobj, + const struct attribute *attr, const char *group) +{ + struct kernfs_node *parent; + + if (group) { + parent = kernfs_find_and_get(kobj->sd, group); + } else { + parent = kobj->sd; + kernfs_get(parent); + } -EXPORT_SYMBOL_GPL(sysfs_create_file); -EXPORT_SYMBOL_GPL(sysfs_remove_file); -EXPORT_SYMBOL_GPL(sysfs_update_file); + if (parent) { + kernfs_remove_by_name(parent, attr->name); + kernfs_put(parent); + } +} +EXPORT_SYMBOL_GPL(sysfs_remove_file_from_group); +/** + * sysfs_create_bin_file - create binary file for object. + * @kobj: object. + * @attr: attribute descriptor. + */ +int sysfs_create_bin_file(struct kobject *kobj, + const struct bin_attribute *attr) +{ + BUG_ON(!kobj || !kobj->sd || !attr); + + return sysfs_add_file(kobj->sd, &attr->attr, true); +} +EXPORT_SYMBOL_GPL(sysfs_create_bin_file); + +/** + * sysfs_remove_bin_file - remove binary file for object. + * @kobj: object. + * @attr: attribute descriptor. + */ +void sysfs_remove_bin_file(struct kobject *kobj, + const struct bin_attribute *attr) +{ + kernfs_remove_by_name(kobj->sd, attr->attr.name); +} +EXPORT_SYMBOL_GPL(sysfs_remove_bin_file); diff --git a/fs/sysfs/group.c b/fs/sysfs/group.c index 122145b0895..7d2a860ba78 100644 --- a/fs/sysfs/group.c +++ b/fs/sysfs/group.c @@ -3,8 +3,10 @@ * * Copyright (c) 2003 Patrick Mochel * Copyright (c) 2003 Open Source Development Lab + * Copyright (c) 2013 Greg Kroah-Hartman + * Copyright (c) 2013 The Linux Foundation * - * This file is released undert the GPL v2. + * This file is released undert the GPL v2. * */ @@ -16,71 +18,332 @@ #include "sysfs.h" -static void remove_files(struct dentry * dir, - const struct attribute_group * grp) +static void remove_files(struct kernfs_node *parent, + const struct attribute_group *grp) { - struct attribute *const* attr; + struct attribute *const *attr; + struct bin_attribute *const *bin_attr; - for (attr = grp->attrs; *attr; attr++) - sysfs_hash_and_remove(dir,(*attr)->name); + if (grp->attrs) + for (attr = grp->attrs; *attr; attr++) + kernfs_remove_by_name(parent, (*attr)->name); + if (grp->bin_attrs) + for (bin_attr = grp->bin_attrs; *bin_attr; bin_attr++) + kernfs_remove_by_name(parent, (*bin_attr)->attr.name); } -static int create_files(struct dentry * dir, - const struct attribute_group * grp) +static int create_files(struct kernfs_node *parent, struct kobject *kobj, + const struct attribute_group *grp, int update) { - struct attribute *const* attr; - int error = 0; + struct attribute *const *attr; + struct bin_attribute *const *bin_attr; + int error = 0, i; + + if (grp->attrs) { + for (i = 0, attr = grp->attrs; *attr && !error; i++, attr++) { + umode_t mode = 0; - for (attr = grp->attrs; *attr && !error; attr++) { - error = sysfs_add_file(dir, *attr, SYSFS_KOBJ_ATTR); + /* + * In update mode, we're changing the permissions or + * visibility. Do this by first removing then + * re-adding (if required) the file. + */ + if (update) + kernfs_remove_by_name(parent, (*attr)->name); + if (grp->is_visible) { + mode = grp->is_visible(kobj, *attr, i); + if (!mode) + continue; + } + error = sysfs_add_file_mode_ns(parent, *attr, false, + (*attr)->mode | mode, + NULL); + if (unlikely(error)) + break; + } + if (error) { + remove_files(parent, grp); + goto exit; + } + } + + if (grp->bin_attrs) { + for (bin_attr = grp->bin_attrs; *bin_attr; bin_attr++) { + if (update) + kernfs_remove_by_name(parent, + (*bin_attr)->attr.name); + error = sysfs_add_file_mode_ns(parent, + &(*bin_attr)->attr, true, + (*bin_attr)->attr.mode, NULL); + if (error) + break; + } + if (error) + remove_files(parent, grp); } - if (error) - remove_files(dir,grp); +exit: return error; } -int sysfs_create_group(struct kobject * kobj, - const struct attribute_group * grp) +static int internal_create_group(struct kobject *kobj, int update, + const struct attribute_group *grp) { - struct dentry * dir; + struct kernfs_node *kn; int error; - BUG_ON(!kobj || !kobj->dentry); + BUG_ON(!kobj || (!update && !kobj->sd)); + /* Updates may happen before the object has been instantiated */ + if (unlikely(update && !kobj->sd)) + return -EINVAL; + if (!grp->attrs && !grp->bin_attrs) { + WARN(1, "sysfs: (bin_)attrs not set by subsystem for group: %s/%s\n", + kobj->name, grp->name ? "" : grp->name); + return -EINVAL; + } if (grp->name) { - error = sysfs_create_subdir(kobj,grp->name,&dir); - if (error) - return error; + kn = kernfs_create_dir(kobj->sd, grp->name, + S_IRWXU | S_IRUGO | S_IXUGO, kobj); + if (IS_ERR(kn)) { + if (PTR_ERR(kn) == -EEXIST) + sysfs_warn_dup(kobj->sd, grp->name); + return PTR_ERR(kn); + } } else - dir = kobj->dentry; - dir = dget(dir); - if ((error = create_files(dir,grp))) { + kn = kobj->sd; + kernfs_get(kn); + error = create_files(kn, kobj, grp, update); + if (error) { if (grp->name) - sysfs_remove_subdir(dir); + kernfs_remove(kn); } - dput(dir); + kernfs_put(kn); return error; } -void sysfs_remove_group(struct kobject * kobj, - const struct attribute_group * grp) +/** + * sysfs_create_group - given a directory kobject, create an attribute group + * @kobj: The kobject to create the group on + * @grp: The attribute group to create + * + * This function creates a group for the first time. It will explicitly + * warn and error if any of the attribute files being created already exist. + * + * Returns 0 on success or error. + */ +int sysfs_create_group(struct kobject *kobj, + const struct attribute_group *grp) +{ + return internal_create_group(kobj, 0, grp); +} +EXPORT_SYMBOL_GPL(sysfs_create_group); + +/** + * sysfs_create_groups - given a directory kobject, create a bunch of attribute groups + * @kobj: The kobject to create the group on + * @groups: The attribute groups to create, NULL terminated + * + * This function creates a bunch of attribute groups. If an error occurs when + * creating a group, all previously created groups will be removed, unwinding + * everything back to the original state when this function was called. + * It will explicitly warn and error if any of the attribute files being + * created already exist. + * + * Returns 0 on success or error code from sysfs_create_group on error. + */ +int sysfs_create_groups(struct kobject *kobj, + const struct attribute_group **groups) { - struct dentry * dir; + int error = 0; + int i; - if (grp->name) - dir = lookup_one_len(grp->name, kobj->dentry, - strlen(grp->name)); - else - dir = dget(kobj->dentry); + if (!groups) + return 0; - remove_files(dir,grp); - if (grp->name) - sysfs_remove_subdir(dir); - /* release the ref. taken in this routine */ - dput(dir); + for (i = 0; groups[i]; i++) { + error = sysfs_create_group(kobj, groups[i]); + if (error) { + while (--i >= 0) + sysfs_remove_group(kobj, groups[i]); + break; + } + } + return error; } +EXPORT_SYMBOL_GPL(sysfs_create_groups); +/** + * sysfs_update_group - given a directory kobject, update an attribute group + * @kobj: The kobject to update the group on + * @grp: The attribute group to update + * + * This function updates an attribute group. Unlike + * sysfs_create_group(), it will explicitly not warn or error if any + * of the attribute files being created already exist. Furthermore, + * if the visibility of the files has changed through the is_visible() + * callback, it will update the permissions and add or remove the + * relevant files. + * + * The primary use for this function is to call it after making a change + * that affects group visibility. + * + * Returns 0 on success or error. + */ +int sysfs_update_group(struct kobject *kobj, + const struct attribute_group *grp) +{ + return internal_create_group(kobj, 1, grp); +} +EXPORT_SYMBOL_GPL(sysfs_update_group); -EXPORT_SYMBOL_GPL(sysfs_create_group); +/** + * sysfs_remove_group: remove a group from a kobject + * @kobj: kobject to remove the group from + * @grp: group to remove + * + * This function removes a group of attributes from a kobject. The attributes + * previously have to have been created for this group, otherwise it will fail. + */ +void sysfs_remove_group(struct kobject *kobj, + const struct attribute_group *grp) +{ + struct kernfs_node *parent = kobj->sd; + struct kernfs_node *kn; + + if (grp->name) { + kn = kernfs_find_and_get(parent, grp->name); + if (!kn) { + WARN(!kn, KERN_WARNING + "sysfs group %p not found for kobject '%s'\n", + grp, kobject_name(kobj)); + return; + } + } else { + kn = parent; + kernfs_get(kn); + } + + remove_files(kn, grp); + if (grp->name) + kernfs_remove(kn); + + kernfs_put(kn); +} EXPORT_SYMBOL_GPL(sysfs_remove_group); + +/** + * sysfs_remove_groups - remove a list of groups + * + * @kobj: The kobject for the groups to be removed from + * @groups: NULL terminated list of groups to be removed + * + * If groups is not NULL, remove the specified groups from the kobject. + */ +void sysfs_remove_groups(struct kobject *kobj, + const struct attribute_group **groups) +{ + int i; + + if (!groups) + return; + for (i = 0; groups[i]; i++) + sysfs_remove_group(kobj, groups[i]); +} +EXPORT_SYMBOL_GPL(sysfs_remove_groups); + +/** + * sysfs_merge_group - merge files into a pre-existing attribute group. + * @kobj: The kobject containing the group. + * @grp: The files to create and the attribute group they belong to. + * + * This function returns an error if the group doesn't exist or any of the + * files already exist in that group, in which case none of the new files + * are created. + */ +int sysfs_merge_group(struct kobject *kobj, + const struct attribute_group *grp) +{ + struct kernfs_node *parent; + int error = 0; + struct attribute *const *attr; + int i; + + parent = kernfs_find_and_get(kobj->sd, grp->name); + if (!parent) + return -ENOENT; + + for ((i = 0, attr = grp->attrs); *attr && !error; (++i, ++attr)) + error = sysfs_add_file(parent, *attr, false); + if (error) { + while (--i >= 0) + kernfs_remove_by_name(parent, (*--attr)->name); + } + kernfs_put(parent); + + return error; +} +EXPORT_SYMBOL_GPL(sysfs_merge_group); + +/** + * sysfs_unmerge_group - remove files from a pre-existing attribute group. + * @kobj: The kobject containing the group. + * @grp: The files to remove and the attribute group they belong to. + */ +void sysfs_unmerge_group(struct kobject *kobj, + const struct attribute_group *grp) +{ + struct kernfs_node *parent; + struct attribute *const *attr; + + parent = kernfs_find_and_get(kobj->sd, grp->name); + if (parent) { + for (attr = grp->attrs; *attr; ++attr) + kernfs_remove_by_name(parent, (*attr)->name); + kernfs_put(parent); + } +} +EXPORT_SYMBOL_GPL(sysfs_unmerge_group); + +/** + * sysfs_add_link_to_group - add a symlink to an attribute group. + * @kobj: The kobject containing the group. + * @group_name: The name of the group. + * @target: The target kobject of the symlink to create. + * @link_name: The name of the symlink to create. + */ +int sysfs_add_link_to_group(struct kobject *kobj, const char *group_name, + struct kobject *target, const char *link_name) +{ + struct kernfs_node *parent; + int error = 0; + + parent = kernfs_find_and_get(kobj->sd, group_name); + if (!parent) + return -ENOENT; + + error = sysfs_create_link_sd(parent, target, link_name); + kernfs_put(parent); + + return error; +} +EXPORT_SYMBOL_GPL(sysfs_add_link_to_group); + +/** + * sysfs_remove_link_from_group - remove a symlink from an attribute group. + * @kobj: The kobject containing the group. + * @group_name: The name of the group. + * @link_name: The name of the symlink to remove. + */ +void sysfs_remove_link_from_group(struct kobject *kobj, const char *group_name, + const char *link_name) +{ + struct kernfs_node *parent; + + parent = kernfs_find_and_get(kobj->sd, group_name); + if (parent) { + kernfs_remove_by_name(parent, link_name); + kernfs_put(parent); + } +} +EXPORT_SYMBOL_GPL(sysfs_remove_link_from_group); diff --git a/fs/sysfs/inode.c b/fs/sysfs/inode.c deleted file mode 100644 index 970a33f0329..00000000000 --- a/fs/sysfs/inode.c +++ /dev/null @@ -1,249 +0,0 @@ -/* - * inode.c - basic inode and dentry operations. - * - * sysfs is Copyright (c) 2001-3 Patrick Mochel - * - * Please see Documentation/filesystems/sysfs.txt for more information. - */ - -#undef DEBUG - -#include <linux/pagemap.h> -#include <linux/namei.h> -#include <linux/backing-dev.h> -#include "sysfs.h" - -extern struct super_block * sysfs_sb; - -static struct address_space_operations sysfs_aops = { - .readpage = simple_readpage, - .prepare_write = simple_prepare_write, - .commit_write = simple_commit_write -}; - -static struct backing_dev_info sysfs_backing_dev_info = { - .ra_pages = 0, /* No readahead */ - .capabilities = BDI_CAP_NO_ACCT_DIRTY | BDI_CAP_NO_WRITEBACK, -}; - -static struct inode_operations sysfs_inode_operations ={ - .setattr = sysfs_setattr, -}; - -int sysfs_setattr(struct dentry * dentry, struct iattr * iattr) -{ - struct inode * inode = dentry->d_inode; - struct sysfs_dirent * sd = dentry->d_fsdata; - struct iattr * sd_iattr; - unsigned int ia_valid = iattr->ia_valid; - int error; - - if (!sd) - return -EINVAL; - - sd_iattr = sd->s_iattr; - - error = inode_change_ok(inode, iattr); - if (error) - return error; - - error = inode_setattr(inode, iattr); - if (error) - return error; - - if (!sd_iattr) { - /* setting attributes for the first time, allocate now */ - sd_iattr = kmalloc(sizeof(struct iattr), GFP_KERNEL); - if (!sd_iattr) - return -ENOMEM; - /* assign default attributes */ - memset(sd_iattr, 0, sizeof(struct iattr)); - sd_iattr->ia_mode = sd->s_mode; - sd_iattr->ia_uid = 0; - sd_iattr->ia_gid = 0; - sd_iattr->ia_atime = sd_iattr->ia_mtime = sd_iattr->ia_ctime = CURRENT_TIME; - sd->s_iattr = sd_iattr; - } - - /* attributes were changed atleast once in past */ - - if (ia_valid & ATTR_UID) - sd_iattr->ia_uid = iattr->ia_uid; - if (ia_valid & ATTR_GID) - sd_iattr->ia_gid = iattr->ia_gid; - if (ia_valid & ATTR_ATIME) - sd_iattr->ia_atime = timespec_trunc(iattr->ia_atime, - inode->i_sb->s_time_gran); - if (ia_valid & ATTR_MTIME) - sd_iattr->ia_mtime = timespec_trunc(iattr->ia_mtime, - inode->i_sb->s_time_gran); - if (ia_valid & ATTR_CTIME) - sd_iattr->ia_ctime = timespec_trunc(iattr->ia_ctime, - inode->i_sb->s_time_gran); - if (ia_valid & ATTR_MODE) { - umode_t mode = iattr->ia_mode; - - if (!in_group_p(inode->i_gid) && !capable(CAP_FSETID)) - mode &= ~S_ISGID; - sd_iattr->ia_mode = sd->s_mode = mode; - } - - return error; -} - -static inline void set_default_inode_attr(struct inode * inode, mode_t mode) -{ - inode->i_mode = mode; - inode->i_uid = 0; - inode->i_gid = 0; - inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; -} - -static inline void set_inode_attr(struct inode * inode, struct iattr * iattr) -{ - inode->i_mode = iattr->ia_mode; - inode->i_uid = iattr->ia_uid; - inode->i_gid = iattr->ia_gid; - inode->i_atime = iattr->ia_atime; - inode->i_mtime = iattr->ia_mtime; - inode->i_ctime = iattr->ia_ctime; -} - -struct inode * sysfs_new_inode(mode_t mode, struct sysfs_dirent * sd) -{ - struct inode * inode = new_inode(sysfs_sb); - if (inode) { - inode->i_blksize = PAGE_CACHE_SIZE; - inode->i_blocks = 0; - inode->i_mapping->a_ops = &sysfs_aops; - inode->i_mapping->backing_dev_info = &sysfs_backing_dev_info; - inode->i_op = &sysfs_inode_operations; - - if (sd->s_iattr) { - /* sysfs_dirent has non-default attributes - * get them for the new inode from persistent copy - * in sysfs_dirent - */ - set_inode_attr(inode, sd->s_iattr); - } else - set_default_inode_attr(inode, mode); - } - return inode; -} - -int sysfs_create(struct dentry * dentry, int mode, int (*init)(struct inode *)) -{ - int error = 0; - struct inode * inode = NULL; - if (dentry) { - if (!dentry->d_inode) { - struct sysfs_dirent * sd = dentry->d_fsdata; - if ((inode = sysfs_new_inode(mode, sd))) { - if (dentry->d_parent && dentry->d_parent->d_inode) { - struct inode *p_inode = dentry->d_parent->d_inode; - p_inode->i_mtime = p_inode->i_ctime = CURRENT_TIME; - } - goto Proceed; - } - else - error = -ENOMEM; - } else - error = -EEXIST; - } else - error = -ENOENT; - goto Done; - - Proceed: - if (init) - error = init(inode); - if (!error) { - d_instantiate(dentry, inode); - if (S_ISDIR(mode)) - dget(dentry); /* pin only directory dentry in core */ - } else - iput(inode); - Done: - return error; -} - -/* - * Get the name for corresponding element represented by the given sysfs_dirent - */ -const unsigned char * sysfs_get_name(struct sysfs_dirent *sd) -{ - struct attribute * attr; - struct bin_attribute * bin_attr; - struct sysfs_symlink * sl; - - if (!sd || !sd->s_element) - BUG(); - - switch (sd->s_type) { - case SYSFS_DIR: - /* Always have a dentry so use that */ - return sd->s_dentry->d_name.name; - - case SYSFS_KOBJ_ATTR: - attr = sd->s_element; - return attr->name; - - case SYSFS_KOBJ_BIN_ATTR: - bin_attr = sd->s_element; - return bin_attr->attr.name; - - case SYSFS_KOBJ_LINK: - sl = sd->s_element; - return sl->link_name; - } - return NULL; -} - - -/* - * Unhashes the dentry corresponding to given sysfs_dirent - * Called with parent inode's i_sem held. - */ -void sysfs_drop_dentry(struct sysfs_dirent * sd, struct dentry * parent) -{ - struct dentry * dentry = sd->s_dentry; - - if (dentry) { - spin_lock(&dcache_lock); - spin_lock(&dentry->d_lock); - if (!(d_unhashed(dentry) && dentry->d_inode)) { - dget_locked(dentry); - __d_drop(dentry); - spin_unlock(&dentry->d_lock); - spin_unlock(&dcache_lock); - simple_unlink(parent->d_inode, dentry); - } else { - spin_unlock(&dentry->d_lock); - spin_unlock(&dcache_lock); - } - } -} - -void sysfs_hash_and_remove(struct dentry * dir, const char * name) -{ - struct sysfs_dirent * sd; - struct sysfs_dirent * parent_sd = dir->d_fsdata; - - if (dir->d_inode == NULL) - /* no inode means this hasn't been made visible yet */ - return; - - down(&dir->d_inode->i_sem); - list_for_each_entry(sd, &parent_sd->s_children, s_sibling) { - if (!sd->s_element) - continue; - if (!strcmp(sysfs_get_name(sd), name)) { - list_del_init(&sd->s_sibling); - sysfs_drop_dentry(sd, dir); - sysfs_put(sd); - break; - } - } - up(&dir->d_inode->i_sem); -} - - diff --git a/fs/sysfs/mount.c b/fs/sysfs/mount.c index f1117e885bd..8a49486bf30 100644 --- a/fs/sysfs/mount.c +++ b/fs/sysfs/mount.c @@ -1,109 +1,82 @@ /* - * mount.c - operations for initializing and mounting sysfs. + * fs/sysfs/symlink.c - operations for initializing and mounting sysfs + * + * Copyright (c) 2001-3 Patrick Mochel + * Copyright (c) 2007 SUSE Linux Products GmbH + * Copyright (c) 2007 Tejun Heo <teheo@suse.de> + * + * This file is released under the GPLv2. + * + * Please see Documentation/filesystems/sysfs.txt for more information. */ -#define DEBUG +#define DEBUG #include <linux/fs.h> +#include <linux/magic.h> #include <linux/mount.h> -#include <linux/pagemap.h> #include <linux/init.h> +#include <linux/user_namespace.h> #include "sysfs.h" -/* Random magic number */ -#define SYSFS_MAGIC 0x62656572 +static struct kernfs_root *sysfs_root; +struct kernfs_node *sysfs_root_kn; -struct vfsmount *sysfs_mount; -struct super_block * sysfs_sb = NULL; -kmem_cache_t *sysfs_dir_cachep; - -static struct super_operations sysfs_ops = { - .statfs = simple_statfs, - .drop_inode = generic_delete_inode, -}; - -static struct sysfs_dirent sysfs_root = { - .s_sibling = LIST_HEAD_INIT(sysfs_root.s_sibling), - .s_children = LIST_HEAD_INIT(sysfs_root.s_children), - .s_element = NULL, - .s_type = SYSFS_ROOT, - .s_iattr = NULL, -}; - -static int sysfs_fill_super(struct super_block *sb, void *data, int silent) +static struct dentry *sysfs_mount(struct file_system_type *fs_type, + int flags, const char *dev_name, void *data) { - struct inode *inode; struct dentry *root; + void *ns; + bool new_sb; - sb->s_blocksize = PAGE_CACHE_SIZE; - sb->s_blocksize_bits = PAGE_CACHE_SHIFT; - sb->s_magic = SYSFS_MAGIC; - sb->s_op = &sysfs_ops; - sb->s_time_gran = 1; - sysfs_sb = sb; - - inode = sysfs_new_inode(S_IFDIR | S_IRWXU | S_IRUGO | S_IXUGO, - &sysfs_root); - if (inode) { - inode->i_op = &sysfs_dir_inode_operations; - inode->i_fop = &sysfs_dir_operations; - /* directory inodes start off with i_nlink == 2 (for "." entry) */ - inode->i_nlink++; - } else { - pr_debug("sysfs: could not get root inode\n"); - return -ENOMEM; - } + if (!(flags & MS_KERNMOUNT)) { + if (!capable(CAP_SYS_ADMIN) && !fs_fully_visible(fs_type)) + return ERR_PTR(-EPERM); - root = d_alloc_root(inode); - if (!root) { - pr_debug("%s: could not get root dentry!\n",__FUNCTION__); - iput(inode); - return -ENOMEM; + if (!kobj_ns_current_may_mount(KOBJ_NS_TYPE_NET)) + return ERR_PTR(-EPERM); } - root->d_fsdata = &sysfs_root; - sb->s_root = root; - return 0; + + ns = kobj_ns_grab_current(KOBJ_NS_TYPE_NET); + root = kernfs_mount_ns(fs_type, flags, sysfs_root, + SYSFS_MAGIC, &new_sb, ns); + if (IS_ERR(root) || !new_sb) + kobj_ns_drop(KOBJ_NS_TYPE_NET, ns); + return root; } -static struct super_block *sysfs_get_sb(struct file_system_type *fs_type, - int flags, const char *dev_name, void *data) +static void sysfs_kill_sb(struct super_block *sb) { - return get_sb_single(fs_type, flags, data, sysfs_fill_super); + void *ns = (void *)kernfs_super_ns(sb); + + kernfs_kill_sb(sb); + kobj_ns_drop(KOBJ_NS_TYPE_NET, ns); } static struct file_system_type sysfs_fs_type = { .name = "sysfs", - .get_sb = sysfs_get_sb, - .kill_sb = kill_litter_super, + .mount = sysfs_mount, + .kill_sb = sysfs_kill_sb, + .fs_flags = FS_USERNS_MOUNT, }; int __init sysfs_init(void) { - int err = -ENOMEM; + int err; + + sysfs_root = kernfs_create_root(NULL, KERNFS_ROOT_EXTRA_OPEN_PERM_CHECK, + NULL); + if (IS_ERR(sysfs_root)) + return PTR_ERR(sysfs_root); - sysfs_dir_cachep = kmem_cache_create("sysfs_dir_cache", - sizeof(struct sysfs_dirent), - 0, 0, NULL, NULL); - if (!sysfs_dir_cachep) - goto out; + sysfs_root_kn = sysfs_root->kn; err = register_filesystem(&sysfs_fs_type); - if (!err) { - sysfs_mount = kern_mount(&sysfs_fs_type); - if (IS_ERR(sysfs_mount)) { - printk(KERN_ERR "sysfs: could not mount!\n"); - err = PTR_ERR(sysfs_mount); - sysfs_mount = NULL; - unregister_filesystem(&sysfs_fs_type); - goto out_err; - } - } else - goto out_err; -out: - return err; -out_err: - kmem_cache_destroy(sysfs_dir_cachep); - sysfs_dir_cachep = NULL; - goto out; + if (err) { + kernfs_destroy_root(sysfs_root); + return err; + } + + return 0; } diff --git a/fs/sysfs/symlink.c b/fs/sysfs/symlink.c index de402fa915f..aecb15f8455 100644 --- a/fs/sysfs/symlink.c +++ b/fs/sysfs/symlink.c @@ -1,76 +1,83 @@ /* - * symlink.c - operations for sysfs symlinks. + * fs/sysfs/symlink.c - sysfs symlink implementation + * + * Copyright (c) 2001-3 Patrick Mochel + * Copyright (c) 2007 SUSE Linux Products GmbH + * Copyright (c) 2007 Tejun Heo <teheo@suse.de> + * + * This file is released under the GPLv2. + * + * Please see Documentation/filesystems/sysfs.txt for more information. */ #include <linux/fs.h> #include <linux/module.h> #include <linux/kobject.h> -#include <linux/namei.h> +#include <linux/mutex.h> +#include <linux/security.h> #include "sysfs.h" -static int object_depth(struct kobject * kobj) +static int sysfs_do_create_link_sd(struct kernfs_node *parent, + struct kobject *target_kobj, + const char *name, int warn) { - struct kobject * p = kobj; - int depth = 0; - do { depth++; } while ((p = p->parent)); - return depth; -} + struct kernfs_node *kn, *target = NULL; + + BUG_ON(!name || !parent); + + /* + * We don't own @target_kobj and it may be removed at any time. + * Synchronize using sysfs_symlink_target_lock. See + * sysfs_remove_dir() for details. + */ + spin_lock(&sysfs_symlink_target_lock); + if (target_kobj->sd) { + target = target_kobj->sd; + kernfs_get(target); + } + spin_unlock(&sysfs_symlink_target_lock); -static int object_path_length(struct kobject * kobj) -{ - struct kobject * p = kobj; - int length = 1; - do { - length += strlen(kobject_name(p)) + 1; - p = p->parent; - } while (p); - return length; -} + if (!target) + return -ENOENT; -static void fill_object_path(struct kobject * kobj, char * buffer, int length) -{ - struct kobject * p; + kn = kernfs_create_link(parent, name, target); + kernfs_put(target); - --length; - for (p = kobj; p; p = p->parent) { - int cur = strlen(kobject_name(p)); + if (!IS_ERR(kn)) + return 0; - /* back up enough to print this bus id with '/' */ - length -= cur; - strncpy(buffer + length,kobject_name(p),cur); - *(buffer + --length) = '/'; - } + if (warn && PTR_ERR(kn) == -EEXIST) + sysfs_warn_dup(parent, name); + return PTR_ERR(kn); } -static int sysfs_add_link(struct dentry * parent, const char * name, struct kobject * target) +/** + * sysfs_create_link_sd - create symlink to a given object. + * @kn: directory we're creating the link in. + * @target: object we're pointing to. + * @name: name of the symlink. + */ +int sysfs_create_link_sd(struct kernfs_node *kn, struct kobject *target, + const char *name) { - struct sysfs_dirent * parent_sd = parent->d_fsdata; - struct sysfs_symlink * sl; - int error = 0; - - error = -ENOMEM; - sl = kmalloc(sizeof(*sl), GFP_KERNEL); - if (!sl) - goto exit1; + return sysfs_do_create_link_sd(kn, target, name, 1); +} - sl->link_name = kmalloc(strlen(name) + 1, GFP_KERNEL); - if (!sl->link_name) - goto exit2; +static int sysfs_do_create_link(struct kobject *kobj, struct kobject *target, + const char *name, int warn) +{ + struct kernfs_node *parent = NULL; - strcpy(sl->link_name, name); - sl->target_kobj = kobject_get(target); + if (!kobj) + parent = sysfs_root_kn; + else + parent = kobj->sd; - error = sysfs_make_dirent(parent_sd, NULL, sl, S_IFLNK|S_IRWXUGO, - SYSFS_KOBJ_LINK); - if (!error) - return 0; + if (!parent) + return -EFAULT; - kfree(sl->link_name); -exit2: - kfree(sl); -exit1: - return error; + return sysfs_do_create_link_sd(parent, target, name, warn); } /** @@ -79,102 +86,112 @@ exit1: * @target: object we're pointing to. * @name: name of the symlink. */ -int sysfs_create_link(struct kobject * kobj, struct kobject * target, const char * name) +int sysfs_create_link(struct kobject *kobj, struct kobject *target, + const char *name) { - struct dentry * dentry = kobj->dentry; - int error = 0; - - BUG_ON(!kobj || !kobj->dentry || !name); - - down(&dentry->d_inode->i_sem); - error = sysfs_add_link(dentry, name, target); - up(&dentry->d_inode->i_sem); - return error; + return sysfs_do_create_link(kobj, target, name, 1); } +EXPORT_SYMBOL_GPL(sysfs_create_link); +/** + * sysfs_create_link_nowarn - create symlink between two objects. + * @kobj: object whose directory we're creating the link in. + * @target: object we're pointing to. + * @name: name of the symlink. + * + * This function does the same as sysfs_create_link(), but it + * doesn't warn if the link already exists. + */ +int sysfs_create_link_nowarn(struct kobject *kobj, struct kobject *target, + const char *name) +{ + return sysfs_do_create_link(kobj, target, name, 0); +} /** - * sysfs_remove_link - remove symlink in object's directory. + * sysfs_delete_link - remove symlink in object's directory. * @kobj: object we're acting for. + * @targ: object we're pointing to. * @name: name of the symlink to remove. + * + * Unlike sysfs_remove_link sysfs_delete_link has enough information + * to successfully delete symlinks in tagged directories. */ - -void sysfs_remove_link(struct kobject * kobj, const char * name) +void sysfs_delete_link(struct kobject *kobj, struct kobject *targ, + const char *name) { - sysfs_hash_and_remove(kobj->dentry,name); + const void *ns = NULL; + + /* + * We don't own @target and it may be removed at any time. + * Synchronize using sysfs_symlink_target_lock. See + * sysfs_remove_dir() for details. + */ + spin_lock(&sysfs_symlink_target_lock); + if (targ->sd && kernfs_ns_enabled(kobj->sd)) + ns = targ->sd->ns; + spin_unlock(&sysfs_symlink_target_lock); + kernfs_remove_by_name_ns(kobj->sd, name, ns); } -static int sysfs_get_target_path(struct kobject * kobj, struct kobject * target, - char *path) +/** + * sysfs_remove_link - remove symlink in object's directory. + * @kobj: object we're acting for. + * @name: name of the symlink to remove. + */ +void sysfs_remove_link(struct kobject *kobj, const char *name) { - char * s; - int depth, size; + struct kernfs_node *parent = NULL; - depth = object_depth(kobj); - size = object_path_length(target) + depth * 3 - 1; - if (size > PATH_MAX) - return -ENAMETOOLONG; - - pr_debug("%s: depth = %d, size = %d\n", __FUNCTION__, depth, size); - - for (s = path; depth--; s += 3) - strcpy(s,"../"); - - fill_object_path(target, path, size); - pr_debug("%s: path = '%s'\n", __FUNCTION__, path); + if (!kobj) + parent = sysfs_root_kn; + else + parent = kobj->sd; - return 0; + kernfs_remove_by_name(parent, name); } +EXPORT_SYMBOL_GPL(sysfs_remove_link); -static int sysfs_getlink(struct dentry *dentry, char * path) +/** + * sysfs_rename_link_ns - rename symlink in object's directory. + * @kobj: object we're acting for. + * @targ: object we're pointing to. + * @old: previous name of the symlink. + * @new: new name of the symlink. + * @new_ns: new namespace of the symlink. + * + * A helper function for the common rename symlink idiom. + */ +int sysfs_rename_link_ns(struct kobject *kobj, struct kobject *targ, + const char *old, const char *new, const void *new_ns) { - struct kobject *kobj, *target_kobj; - int error = 0; + struct kernfs_node *parent, *kn = NULL; + const void *old_ns = NULL; + int result; - kobj = sysfs_get_kobject(dentry->d_parent); if (!kobj) - return -EINVAL; + parent = sysfs_root_kn; + else + parent = kobj->sd; - target_kobj = sysfs_get_kobject(dentry); - if (!target_kobj) { - kobject_put(kobj); - return -EINVAL; - } + if (targ->sd) + old_ns = targ->sd->ns; - down_read(&sysfs_rename_sem); - error = sysfs_get_target_path(kobj, target_kobj, path); - up_read(&sysfs_rename_sem); - - kobject_put(kobj); - kobject_put(target_kobj); - return error; + result = -ENOENT; + kn = kernfs_find_and_get_ns(parent, old, old_ns); + if (!kn) + goto out; -} + result = -EINVAL; + if (kernfs_type(kn) != KERNFS_LINK) + goto out; + if (kn->symlink.target_kn->priv != targ) + goto out; -static void *sysfs_follow_link(struct dentry *dentry, struct nameidata *nd) -{ - int error = -ENOMEM; - unsigned long page = get_zeroed_page(GFP_KERNEL); - if (page) - error = sysfs_getlink(dentry, (char *) page); - nd_set_link(nd, error ? ERR_PTR(error) : (char *)page); - return NULL; -} + result = kernfs_rename_ns(kn, parent, new, new_ns); -static void sysfs_put_link(struct dentry *dentry, struct nameidata *nd, void *cookie) -{ - char *page = nd_get_link(nd); - if (!IS_ERR(page)) - free_page((unsigned long)page); +out: + kernfs_put(kn); + return result; } - -struct inode_operations sysfs_symlink_inode_operations = { - .readlink = generic_readlink, - .follow_link = sysfs_follow_link, - .put_link = sysfs_put_link, -}; - - -EXPORT_SYMBOL_GPL(sysfs_create_link); -EXPORT_SYMBOL_GPL(sysfs_remove_link); - +EXPORT_SYMBOL_GPL(sysfs_rename_link_ns); diff --git a/fs/sysfs/sysfs.h b/fs/sysfs/sysfs.h index 3f8953e0e5d..0e2f1cccb81 100644 --- a/fs/sysfs/sysfs.h +++ b/fs/sysfs/sysfs.h @@ -1,96 +1,43 @@ - -extern struct vfsmount * sysfs_mount; -extern kmem_cache_t *sysfs_dir_cachep; - -extern struct inode * sysfs_new_inode(mode_t mode, struct sysfs_dirent *); -extern int sysfs_create(struct dentry *, int mode, int (*init)(struct inode *)); - -extern int sysfs_make_dirent(struct sysfs_dirent *, struct dentry *, void *, - umode_t, int); - -extern int sysfs_add_file(struct dentry *, const struct attribute *, int); -extern void sysfs_hash_and_remove(struct dentry * dir, const char * name); - -extern int sysfs_create_subdir(struct kobject *, const char *, struct dentry **); -extern void sysfs_remove_subdir(struct dentry *); - -extern const unsigned char * sysfs_get_name(struct sysfs_dirent *sd); -extern void sysfs_drop_dentry(struct sysfs_dirent *sd, struct dentry *parent); -extern int sysfs_setattr(struct dentry *dentry, struct iattr *iattr); - -extern struct rw_semaphore sysfs_rename_sem; -extern struct super_block * sysfs_sb; -extern struct file_operations sysfs_dir_operations; -extern struct file_operations sysfs_file_operations; -extern struct file_operations bin_fops; -extern struct inode_operations sysfs_dir_inode_operations; -extern struct inode_operations sysfs_symlink_inode_operations; - -struct sysfs_symlink { - char * link_name; - struct kobject * target_kobj; -}; - -static inline struct kobject * to_kobj(struct dentry * dentry) -{ - struct sysfs_dirent * sd = dentry->d_fsdata; - return ((struct kobject *) sd->s_element); -} - -static inline struct attribute * to_attr(struct dentry * dentry) -{ - struct sysfs_dirent * sd = dentry->d_fsdata; - return ((struct attribute *) sd->s_element); -} - -static inline struct bin_attribute * to_bin_attr(struct dentry * dentry) -{ - struct sysfs_dirent * sd = dentry->d_fsdata; - return ((struct bin_attribute *) sd->s_element); -} - -static inline struct kobject *sysfs_get_kobject(struct dentry *dentry) -{ - struct kobject * kobj = NULL; - - spin_lock(&dcache_lock); - if (!d_unhashed(dentry)) { - struct sysfs_dirent * sd = dentry->d_fsdata; - if (sd->s_type & SYSFS_KOBJ_LINK) { - struct sysfs_symlink * sl = sd->s_element; - kobj = kobject_get(sl->target_kobj); - } else - kobj = kobject_get(sd->s_element); - } - spin_unlock(&dcache_lock); - - return kobj; -} - -static inline void release_sysfs_dirent(struct sysfs_dirent * sd) -{ - if (sd->s_type & SYSFS_KOBJ_LINK) { - struct sysfs_symlink * sl = sd->s_element; - kfree(sl->link_name); - kobject_put(sl->target_kobj); - kfree(sl); - } - kfree(sd->s_iattr); - kmem_cache_free(sysfs_dir_cachep, sd); -} - -static inline struct sysfs_dirent * sysfs_get(struct sysfs_dirent * sd) -{ - if (sd) { - WARN_ON(!atomic_read(&sd->s_count)); - atomic_inc(&sd->s_count); - } - return sd; -} - -static inline void sysfs_put(struct sysfs_dirent * sd) -{ - if (atomic_dec_and_test(&sd->s_count)) - release_sysfs_dirent(sd); -} - +/* + * fs/sysfs/sysfs.h - sysfs internal header file + * + * Copyright (c) 2001-3 Patrick Mochel + * Copyright (c) 2007 SUSE Linux Products GmbH + * Copyright (c) 2007 Tejun Heo <teheo@suse.de> + * + * This file is released under the GPLv2. + */ + +#ifndef __SYSFS_INTERNAL_H +#define __SYSFS_INTERNAL_H + +#include <linux/sysfs.h> + +/* + * mount.c + */ +extern struct kernfs_node *sysfs_root_kn; + +/* + * dir.c + */ +extern spinlock_t sysfs_symlink_target_lock; + +void sysfs_warn_dup(struct kernfs_node *parent, const char *name); + +/* + * file.c + */ +int sysfs_add_file(struct kernfs_node *parent, + const struct attribute *attr, bool is_bin); +int sysfs_add_file_mode_ns(struct kernfs_node *parent, + const struct attribute *attr, bool is_bin, + umode_t amode, const void *ns); + +/* + * symlink.c + */ +int sysfs_create_link_sd(struct kernfs_node *kn, struct kobject *target, + const char *name); + +#endif /* __SYSFS_INTERNAL_H */ |
