1146 lines · 27.2 KB · C
RawDownload
/* * 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/idr.h>#include <linux/completion.h>#include <linux/mutex.h>#include <linux/slab.h>#include <linux/security.h>#include <linux/hash.h>#include "sysfs.h" DEFINE_MUTEX(sysfs_mutex);DEFINE_SPINLOCK(sysfs_symlink_target_lock); #define to_sysfs_dirent(X) rb_entry((X), struct sysfs_dirent, s_rb) static DEFINE_SPINLOCK(sysfs_ino_lock);static DEFINE_IDA(sysfs_ino_ida); /** *	sysfs_name_hash *	@name: Null terminated string to hash *	@ns:   Namespace tag to hash * *	Returns 31 bit hash of ns + name (so it fits in an off_t ) */static unsigned int sysfs_name_hash(const char *name, const void *ns){	unsigned long hash = init_name_hash();	unsigned int len = strlen(name);	while (len--)		hash = partial_name_hash(*name++, hash);	hash = (end_name_hash(hash) ^ hash_ptr((void *)ns, 31));	hash &= 0x7fffffffU;	/* Reserve hash numbers 0, 1 and INT_MAX for magic directory entries */	if (hash < 1)		hash += 2;	if (hash >= INT_MAX)		hash = INT_MAX - 1;	return hash;} static int sysfs_name_compare(unsigned int hash, const char *name,			      const void *ns, const struct sysfs_dirent *sd){	if (hash != sd->s_hash)		return hash - sd->s_hash;	if (ns != sd->s_ns)		return ns - sd->s_ns;	return strcmp(name, sd->s_name);} static int sysfs_sd_compare(const struct sysfs_dirent *left,			    const struct sysfs_dirent *right){	return sysfs_name_compare(left->s_hash, left->s_name, left->s_ns,				  right);} /** *	sysfs_link_sibling - link sysfs_dirent into sibling rbtree *	@sd: sysfs_dirent of interest * *	Link @sd into its sibling rbtree which starts from *	sd->s_parent->s_dir.children. * *	Locking: *	mutex_lock(sysfs_mutex) * *	RETURNS: *	0 on susccess -EEXIST on failure. */static int sysfs_link_sibling(struct sysfs_dirent *sd){	struct rb_node **node = &sd->s_parent->s_dir.children.rb_node;	struct rb_node *parent = NULL; 	if (sysfs_type(sd) == SYSFS_DIR)		sd->s_parent->s_dir.subdirs++; 	while (*node) {		struct sysfs_dirent *pos;		int result; 		pos = to_sysfs_dirent(*node);		parent = *node;		result = sysfs_sd_compare(sd, pos);		if (result < 0)			node = &pos->s_rb.rb_left;		else if (result > 0)			node = &pos->s_rb.rb_right;		else			return -EEXIST;	}	/* add new node and rebalance the tree */	rb_link_node(&sd->s_rb, parent, node);	rb_insert_color(&sd->s_rb, &sd->s_parent->s_dir.children);	return 0;} /** *	sysfs_unlink_sibling - unlink sysfs_dirent from sibling rbtree *	@sd: sysfs_dirent of interest * *	Unlink @sd from its sibling rbtree which starts from *	sd->s_parent->s_dir.children. * *	Locking: *	mutex_lock(sysfs_mutex) */static void sysfs_unlink_sibling(struct sysfs_dirent *sd){	if (sysfs_type(sd) == SYSFS_DIR)		sd->s_parent->s_dir.subdirs--; 	rb_erase(&sd->s_rb, &sd->s_parent->s_dir.children);} /** *	sysfs_get_active - get an active reference to sysfs_dirent *	@sd: sysfs_dirent to get an active reference to * *	Get an active reference of @sd.  This function is noop if @sd *	is NULL. * *	RETURNS: *	Pointer to @sd on success, NULL on failure. */struct sysfs_dirent *sysfs_get_active(struct sysfs_dirent *sd){	if (unlikely(!sd))		return NULL; 	if (!atomic_inc_unless_negative(&sd->s_active))		return NULL; 	if (likely(!sysfs_ignore_lockdep(sd)))		rwsem_acquire_read(&sd->dep_map, 0, 1, _RET_IP_);	return sd;} /** *	sysfs_put_active - put an active reference to sysfs_dirent *	@sd: sysfs_dirent to put an active reference to * *	Put an active reference to @sd.  This function is noop if @sd *	is NULL. */void sysfs_put_active(struct sysfs_dirent *sd){	int v; 	if (unlikely(!sd))		return; 	if (likely(!sysfs_ignore_lockdep(sd)))		rwsem_release(&sd->dep_map, 1, _RET_IP_);	v = atomic_dec_return(&sd->s_active);	if (likely(v != SD_DEACTIVATED_BIAS))		return; 	/* atomic_dec_return() is a mb(), we'll always see the updated	 * sd->u.completion.	 */	complete(sd->u.completion);} /** *	sysfs_deactivate - deactivate sysfs_dirent *	@sd: sysfs_dirent to deactivate * *	Deny new active references and drain existing ones. */static void sysfs_deactivate(struct sysfs_dirent *sd){	DECLARE_COMPLETION_ONSTACK(wait);	int v; 	BUG_ON(!(sd->s_flags & SYSFS_FLAG_REMOVED)); 	if (!(sysfs_type(sd) & SYSFS_ACTIVE_REF))		return; 	sd->u.completion = (void *)&wait; 	rwsem_acquire(&sd->dep_map, 0, 0, _RET_IP_);	/* atomic_add_return() is a mb(), put_active() will always see	 * the updated sd->u.completion.	 */	v = atomic_add_return(SD_DEACTIVATED_BIAS, &sd->s_active); 	if (v != SD_DEACTIVATED_BIAS) {		lock_contended(&sd->dep_map, _RET_IP_);		wait_for_completion(&wait);	} 	lock_acquired(&sd->dep_map, _RET_IP_);	rwsem_release(&sd->dep_map, 1, _RET_IP_);} static int sysfs_alloc_ino(unsigned int *pino){	int ino, rc;  retry:	spin_lock(&sysfs_ino_lock);	rc = ida_get_new_above(&sysfs_ino_ida, 2, &ino);	spin_unlock(&sysfs_ino_lock); 	if (rc == -EAGAIN) {		if (ida_pre_get(&sysfs_ino_ida, GFP_KERNEL))			goto retry;		rc = -ENOMEM;	} 	*pino = ino;	return rc;} static void sysfs_free_ino(unsigned int ino){	spin_lock(&sysfs_ino_lock);	ida_remove(&sysfs_ino_ida, ino);	spin_unlock(&sysfs_ino_lock);} void release_sysfs_dirent(struct sysfs_dirent *sd){	struct sysfs_dirent *parent_sd;  repeat:	/* Moving/renaming is always done while holding reference.	 * sd->s_parent won't change beneath us.	 */	parent_sd = sd->s_parent; 	WARN(!(sd->s_flags & SYSFS_FLAG_REMOVED),		"sysfs: free using entry: %s/%s\n",		parent_sd ? parent_sd->s_name : "", sd->s_name); 	if (sysfs_type(sd) == SYSFS_KOBJ_LINK)		sysfs_put(sd->s_symlink.target_sd);	if (sysfs_type(sd) & SYSFS_COPY_NAME)		kfree(sd->s_name);	if (sd->s_iattr && sd->s_iattr->ia_secdata)		security_release_secctx(sd->s_iattr->ia_secdata,					sd->s_iattr->ia_secdata_len);	kfree(sd->s_iattr);	sysfs_free_ino(sd->s_ino);	kmem_cache_free(sysfs_dir_cachep, sd); 	sd = parent_sd;	if (sd && atomic_dec_and_test(&sd->s_count))		goto repeat;} static int sysfs_dentry_delete(const struct dentry *dentry){	struct sysfs_dirent *sd = dentry->d_fsdata;	return !(sd && !(sd->s_flags & SYSFS_FLAG_REMOVED));} static int sysfs_dentry_revalidate(struct dentry *dentry, unsigned int flags){	struct sysfs_dirent *sd;	int type; 	if (flags & LOOKUP_RCU)		return -ECHILD; 	sd = dentry->d_fsdata;	mutex_lock(&sysfs_mutex); 	/* The sysfs dirent has been deleted */	if (sd->s_flags & SYSFS_FLAG_REMOVED)		goto out_bad; 	/* The sysfs dirent has been moved? */	if (dentry->d_parent->d_fsdata != sd->s_parent)		goto out_bad; 	/* The sysfs dirent has been renamed */	if (strcmp(dentry->d_name.name, sd->s_name) != 0)		goto out_bad; 	/* The sysfs dirent has been moved to a different namespace */	type = KOBJ_NS_TYPE_NONE;	if (sd->s_parent) {		type = sysfs_ns_type(sd->s_parent);		if (type != KOBJ_NS_TYPE_NONE &&				sysfs_info(dentry->d_sb)->ns[type] != sd->s_ns)			goto out_bad;	} 	mutex_unlock(&sysfs_mutex);out_valid:	return 1;out_bad:	/* Remove the dentry from the dcache hashes.	 * If this is a deleted dentry we use d_drop instead of d_delete	 * so sysfs doesn't need to cope with negative dentries.	 *	 * If this is a dentry that has simply been renamed we	 * use d_drop to remove it from the dcache lookup on its	 * old parent.  If this dentry persists later when a lookup	 * is performed at its new name the dentry will be readded	 * to the dcache hashes.	 */	mutex_unlock(&sysfs_mutex); 	/* If we have submounts we must allow the vfs caches	 * to lie about the state of the filesystem to prevent	 * leaks and other nasty things.	 */	if (check_submounts_and_drop(dentry) != 0)		goto out_valid; 	return 0;} static void sysfs_dentry_release(struct dentry *dentry){	sysfs_put(dentry->d_fsdata);} const struct dentry_operations sysfs_dentry_ops = {	.d_revalidate	= sysfs_dentry_revalidate,	.d_delete	= sysfs_dentry_delete,	.d_release	= sysfs_dentry_release,}; struct sysfs_dirent *sysfs_new_dirent(const char *name, umode_t mode, int type){	char *dup_name = NULL;	struct sysfs_dirent *sd; 	if (type & SYSFS_COPY_NAME) {		name = dup_name = kstrdup(name, GFP_KERNEL);		if (!name)			return NULL;	} 	sd = kmem_cache_zalloc(sysfs_dir_cachep, GFP_KERNEL);	if (!sd)		goto err_out1; 	if (sysfs_alloc_ino(&sd->s_ino))		goto err_out2; 	atomic_set(&sd->s_count, 1);	atomic_set(&sd->s_active, 0); 	sd->s_name = name;	sd->s_mode = mode;	sd->s_flags = type | SYSFS_FLAG_REMOVED; 	return sd;  err_out2:	kmem_cache_free(sysfs_dir_cachep, sd); err_out1:	kfree(dup_name);	return NULL;} /** *	sysfs_addrm_start - prepare for sysfs_dirent add/remove *	@acxt: pointer to sysfs_addrm_cxt to be used * *	This function is called when the caller is about to add or remove *	sysfs_dirent.  This function acquires sysfs_mutex.  @acxt is used *	to keep and pass context to other addrm functions. * *	LOCKING: *	Kernel thread context (may sleep).  sysfs_mutex is locked on *	return. */void sysfs_addrm_start(struct sysfs_addrm_cxt *acxt)	__acquires(sysfs_mutex){	memset(acxt, 0, sizeof(*acxt)); 	mutex_lock(&sysfs_mutex);} /** *	__sysfs_add_one - add sysfs_dirent to parent without warning *	@acxt: addrm context to use *	@sd: sysfs_dirent to be added *	@parent_sd: the parent sysfs_dirent to add @sd to * *	Get @parent_sd and set @sd->s_parent to it and increment nlink of *	the parent inode if @sd is a directory and link into the children *	list of the parent. * *	This function should be called between calls to *	sysfs_addrm_start() and sysfs_addrm_finish() and should be *	passed the same @acxt as passed to sysfs_addrm_start(). * *	LOCKING: *	Determined by sysfs_addrm_start(). * *	RETURNS: *	0 on success, -EEXIST if entry with the given name already *	exists. */int __sysfs_add_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd,		    struct sysfs_dirent *parent_sd){	struct sysfs_inode_attrs *ps_iattr;	int ret; 	if (!!sysfs_ns_type(parent_sd) != !!sd->s_ns) {		WARN(1, KERN_WARNING "sysfs: ns %s in '%s' for '%s'\n",			sysfs_ns_type(parent_sd) ? "required" : "invalid",			parent_sd->s_name, sd->s_name);		return -EINVAL;	} 	sd->s_hash = sysfs_name_hash(sd->s_name, sd->s_ns);	sd->s_parent = sysfs_get(parent_sd); 	ret = sysfs_link_sibling(sd);	if (ret)		return ret; 	/* Update timestamps on the parent */	ps_iattr = parent_sd->s_iattr;	if (ps_iattr) {		struct iattr *ps_iattrs = &ps_iattr->ia_iattr;		ps_iattrs->ia_ctime = ps_iattrs->ia_mtime = CURRENT_TIME;	} 	/* Mark the entry added into directory tree */	sd->s_flags &= ~SYSFS_FLAG_REMOVED; 	return 0;} /** *	sysfs_pathname - return full path to sysfs dirent *	@sd: sysfs_dirent whose path we want *	@path: caller allocated buffer of size PATH_MAX * *	Gives the name "/" to the sysfs_root entry; any path returned *	is relative to wherever sysfs is mounted. */static char *sysfs_pathname(struct sysfs_dirent *sd, char *path){	if (sd->s_parent) {		sysfs_pathname(sd->s_parent, path);		strlcat(path, "/", PATH_MAX);	}	strlcat(path, sd->s_name, PATH_MAX);	return path;} void sysfs_warn_dup(struct sysfs_dirent *parent, const char *name){	char *path; 	path = kzalloc(PATH_MAX, GFP_KERNEL);	if (path) {		sysfs_pathname(parent, path);		strlcat(path, "/", PATH_MAX);		strlcat(path, name, PATH_MAX);	} 	WARN(1, KERN_WARNING "sysfs: cannot create duplicate filename '%s'\n",	     path ? path : name); 	kfree(path);} /** *	sysfs_add_one - add sysfs_dirent to parent *	@acxt: addrm context to use *	@sd: sysfs_dirent to be added *	@parent_sd: the parent sysfs_dirent to add @sd to * *	Get @parent_sd and set @sd->s_parent to it and increment nlink of *	the parent inode if @sd is a directory and link into the children *	list of the parent. *

Lines 1–500 of 1146.