/*
* 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_assoc_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
* @ns: Namespace tag to hash
* @name: Null terminated string to hash
*
* Returns 31 bit hash of ns + name (so it fits in an off_t )
*/
static unsigned int sysfs_name_hash(const void *ns, const char *name)
{
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 void *ns,
const char *name, 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_ns, left->s_name,
right);
}
/**
* sysfs_link_subling - 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);
}
#ifdef CONFIG_DEBUG_LOCK_ALLOC
/* Test for attributes that want to ignore lockdep for read-locking */
static bool ignore_lockdep(struct sysfs_dirent *sd)
{
return sysfs_type(sd) == SYSFS_KOBJ_ATTR &&
sd->s_attr