mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-26 02:39:48 +00:00
![Arjan van de Ven](/assets/img/avatar_default.png)
Many struct inode_operations in the kernel can be "const". Marking them const moves these to the .rodata section, which avoids false sharing with potential dirty data. In addition it'll catch accidental writes at compile time to these shared resources. Signed-off-by: Arjan van de Ven <arjan@linux.intel.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
300 lines
7.3 KiB
C
300 lines
7.3 KiB
C
/*
|
|
* 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 <linux/capability.h>
|
|
#include <linux/errno.h>
|
|
#include <asm/semaphore.h>
|
|
#include "sysfs.h"
|
|
|
|
extern struct super_block * sysfs_sb;
|
|
|
|
static const 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 const struct inode_operations sysfs_inode_operations ={
|
|
.setattr = sysfs_setattr,
|
|
};
|
|
|
|
void sysfs_delete_inode(struct inode *inode)
|
|
{
|
|
/* Free the shadowed directory inode operations */
|
|
if (sysfs_is_shadowed_inode(inode)) {
|
|
kfree(inode->i_op);
|
|
inode->i_op = NULL;
|
|
}
|
|
return generic_delete_inode(inode);
|
|
}
|
|
|
|
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 = kzalloc(sizeof(struct iattr), GFP_KERNEL);
|
|
if (!sd_iattr)
|
|
return -ENOMEM;
|
|
/* assign default attributes */
|
|
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;
|
|
}
|
|
|
|
|
|
/*
|
|
* sysfs has a different i_mutex lock order behavior for i_mutex than other
|
|
* filesystems; sysfs i_mutex is called in many places with subsystem locks
|
|
* held. At the same time, many of the VFS locking rules do not apply to
|
|
* sysfs at all (cross directory rename for example). To untangle this mess
|
|
* (which gives false positives in lockdep), we're giving sysfs inodes their
|
|
* own class for i_mutex.
|
|
*/
|
|
static struct lock_class_key sysfs_inode_imutex_key;
|
|
|
|
struct inode * sysfs_new_inode(mode_t mode, struct sysfs_dirent * sd)
|
|
{
|
|
struct inode * inode = new_inode(sysfs_sb);
|
|
if (inode) {
|
|
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;
|
|
lockdep_set_class(&inode->i_mutex, &sysfs_inode_imutex_key);
|
|
|
|
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;
|
|
|
|
BUG_ON(!sd || !sd->s_element);
|
|
|
|
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;
|
|
}
|
|
|
|
static inline void orphan_all_buffers(struct inode *node)
|
|
{
|
|
struct sysfs_buffer_collection *set = node->i_private;
|
|
struct sysfs_buffer *buf;
|
|
|
|
mutex_lock_nested(&node->i_mutex, I_MUTEX_CHILD);
|
|
if (node->i_private) {
|
|
list_for_each_entry(buf, &set->associates, associates) {
|
|
down(&buf->sem);
|
|
buf->orphaned = 1;
|
|
up(&buf->sem);
|
|
}
|
|
}
|
|
mutex_unlock(&node->i_mutex);
|
|
}
|
|
|
|
|
|
/*
|
|
* Unhashes the dentry corresponding to given sysfs_dirent
|
|
* Called with parent inode's i_mutex held.
|
|
*/
|
|
void sysfs_drop_dentry(struct sysfs_dirent * sd, struct dentry * parent)
|
|
{
|
|
struct dentry * dentry = sd->s_dentry;
|
|
struct inode *inode;
|
|
|
|
if (dentry) {
|
|
spin_lock(&dcache_lock);
|
|
spin_lock(&dentry->d_lock);
|
|
if (!(d_unhashed(dentry) && dentry->d_inode)) {
|
|
inode = dentry->d_inode;
|
|
spin_lock(&inode->i_lock);
|
|
__iget(inode);
|
|
spin_unlock(&inode->i_lock);
|
|
dget_locked(dentry);
|
|
__d_drop(dentry);
|
|
spin_unlock(&dentry->d_lock);
|
|
spin_unlock(&dcache_lock);
|
|
simple_unlink(parent->d_inode, dentry);
|
|
orphan_all_buffers(inode);
|
|
iput(inode);
|
|
} else {
|
|
spin_unlock(&dentry->d_lock);
|
|
spin_unlock(&dcache_lock);
|
|
}
|
|
}
|
|
}
|
|
|
|
int sysfs_hash_and_remove(struct dentry * dir, const char * name)
|
|
{
|
|
struct sysfs_dirent * sd;
|
|
struct sysfs_dirent * parent_sd;
|
|
int found = 0;
|
|
|
|
if (!dir)
|
|
return -ENOENT;
|
|
|
|
if (dir->d_inode == NULL)
|
|
/* no inode means this hasn't been made visible yet */
|
|
return -ENOENT;
|
|
|
|
parent_sd = dir->d_fsdata;
|
|
mutex_lock_nested(&dir->d_inode->i_mutex, I_MUTEX_PARENT);
|
|
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);
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
mutex_unlock(&dir->d_inode->i_mutex);
|
|
|
|
return found ? 0 : -ENOENT;
|
|
}
|