summaryrefslogtreecommitdiffstats
path: root/fs/sysfs
diff options
context:
space:
mode:
Diffstat (limited to 'fs/sysfs')
-rw-r--r--fs/sysfs/dir.c250
-rw-r--r--fs/sysfs/file.c17
-rw-r--r--fs/sysfs/inode.c46
-rw-r--r--fs/sysfs/symlink.c20
-rw-r--r--fs/sysfs/sysfs.h20
5 files changed, 229 insertions, 124 deletions
diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c
index 9fc8558fd86c..edb30621b82f 100644
--- a/fs/sysfs/dir.c
+++ b/fs/sysfs/dir.c
@@ -30,7 +30,7 @@ static DEFINE_IDA(sysfs_ino_ida);
* Locking:
* mutex_lock(sysfs_mutex)
*/
-static void sysfs_link_sibling(struct sysfs_dirent *sd)
+void sysfs_link_sibling(struct sysfs_dirent *sd)
{
struct sysfs_dirent *parent_sd = sd->s_parent;
@@ -49,7 +49,7 @@ static void sysfs_link_sibling(struct sysfs_dirent *sd)
* Locking:
* mutex_lock(sysfs_mutex)
*/
-static void sysfs_unlink_sibling(struct sysfs_dirent *sd)
+void sysfs_unlink_sibling(struct sysfs_dirent *sd)
{
struct sysfs_dirent **pos;
@@ -165,7 +165,7 @@ void sysfs_put_active_two(struct sysfs_dirent *sd)
*
* Deny new active references and drain existing ones.
*/
-void sysfs_deactivate(struct sysfs_dirent *sd)
+static void sysfs_deactivate(struct sysfs_dirent *sd)
{
DECLARE_COMPLETION_ONSTACK(wait);
int v;
@@ -318,27 +318,164 @@ static void sysfs_attach_dentry(struct sysfs_dirent *sd, struct dentry *dentry)
d_rehash(dentry);
}
+static int sysfs_ilookup_test(struct inode *inode, void *arg)
+{
+ struct sysfs_dirent *sd = arg;
+ return inode->i_ino == sd->s_ino;
+}
+
/**
- * sysfs_attach_dirent - attach sysfs_dirent to its parent and dentry
- * @sd: sysfs_dirent to attach
- * @parent_sd: parent to attach to (optional)
- * @dentry: dentry to be associated to @sd (optional)
+ * sysfs_addrm_start - prepare for sysfs_dirent add/remove
+ * @acxt: pointer to sysfs_addrm_cxt to be used
+ * @parent_sd: parent sysfs_dirent
*
- * Attach @sd to @parent_sd and/or @dentry. Both are optional.
+ * This function is called when the caller is about to add or
+ * remove sysfs_dirent under @parent_sd. This function acquires
+ * sysfs_mutex, grabs inode for @parent_sd if available and lock
+ * i_mutex of it. @acxt is used to keep and pass context to
+ * other addrm functions.
*
* LOCKING:
- * mutex_lock(sysfs_mutex)
+ * Kernel thread context (may sleep). sysfs_mutex is locked on
+ * return. i_mutex of parent inode is locked on return if
+ * available.
*/
-void sysfs_attach_dirent(struct sysfs_dirent *sd,
- struct sysfs_dirent *parent_sd, struct dentry *dentry)
+void sysfs_addrm_start(struct sysfs_addrm_cxt *acxt,
+ struct sysfs_dirent *parent_sd)
{
- if (dentry)
- sysfs_attach_dentry(sd, dentry);
+ struct inode *inode;
- if (parent_sd) {
- sd->s_parent = sysfs_get(parent_sd);
- sysfs_link_sibling(sd);
+ memset(acxt, 0, sizeof(*acxt));
+ acxt->parent_sd = parent_sd;
+
+ /* Lookup parent inode. inode initialization and I_NEW
+ * clearing are protected by sysfs_mutex. By grabbing it and
+ * looking up with _nowait variant, inode state can be
+ * determined reliably.
+ */
+ mutex_lock(&sysfs_mutex);
+
+ inode = ilookup5_nowait(sysfs_sb, parent_sd->s_ino, sysfs_ilookup_test,
+ parent_sd);
+
+ if (inode && !(inode->i_state & I_NEW)) {
+ /* parent inode available */
+ acxt->parent_inode = inode;
+
+ /* sysfs_mutex is below i_mutex in lock hierarchy.
+ * First, trylock i_mutex. If fails, unlock
+ * sysfs_mutex and lock them in order.
+ */
+ if (!mutex_trylock(&inode->i_mutex)) {
+ mutex_unlock(&sysfs_mutex);
+ mutex_lock(&inode->i_mutex);
+ mutex_lock(&sysfs_mutex);
+ }
+ } else
+ iput(inode);
+}
+
+/**
+ * sysfs_add_one - add sysfs_dirent to parent
+ * @acxt: addrm context to use
+ * @sd: sysfs_dirent to be added
+ *
+ * Get @acxt->parent_sd and set sd->s_parent to it and increment
+ * nlink of parent inode if @sd is a directory. @sd is NOT
+ * linked into the children list of the parent. The caller
+ * should invoke sysfs_link_sibling() after this function
+ * completes if @sd needs to be on the children list.
+ *
+ * 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().
+ */
+void sysfs_add_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd)
+{
+ sd->s_parent = sysfs_get(acxt->parent_sd);
+
+ if (sysfs_type(sd) == SYSFS_DIR && acxt->parent_inode)
+ inc_nlink(acxt->parent_inode);
+
+ acxt->cnt++;
+}
+
+/**
+ * sysfs_remove_one - remove sysfs_dirent from parent
+ * @acxt: addrm context to use
+ * @sd: sysfs_dirent to be added
+ *
+ * Mark @sd removed and drop nlink of parent inode if @sd is a
+ * directory. @sd is NOT unlinked from the children list of the
+ * parent. The caller is repsonsible for removing @sd from the
+ * children list before calling this function.
+ *
+ * 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().
+ */
+void sysfs_remove_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd)
+{
+ BUG_ON(sd->s_sibling || (sd->s_flags & SYSFS_FLAG_REMOVED));
+
+ sd->s_flags |= SYSFS_FLAG_REMOVED;
+ sd->s_sibling = acxt->removed;
+ acxt->removed = sd;
+
+ if (sysfs_type(sd) == SYSFS_DIR && acxt->parent_inode)
+ drop_nlink(acxt->parent_inode);
+
+ acxt->cnt++;
+}
+
+/**
+ * sysfs_addrm_finish - finish up sysfs_dirent add/remove
+ * @acxt: addrm context to finish up
+ *
+ * Finish up sysfs_dirent add/remove. Resources acquired by
+ * sysfs_addrm_start() are released and removed sysfs_dirents are
+ * cleaned up. Timestamps on the parent inode are updated.
+ *
+ * LOCKING:
+ * All mutexes acquired by sysfs_addrm_start() are released.
+ *
+ * RETURNS:
+ * Number of added/removed sysfs_dirents since sysfs_addrm_start().
+ */
+int sysfs_addrm_finish(struct sysfs_addrm_cxt *acxt)
+{
+ /* release resources acquired by sysfs_addrm_start() */
+ mutex_unlock(&sysfs_mutex);
+ if (acxt->parent_inode) {
+ struct inode *inode = acxt->parent_inode;
+
+ /* if added/removed, update timestamps on the parent */
+ if (acxt->cnt)
+ inode->i_ctime = inode->i_mtime = CURRENT_TIME;
+
+ mutex_unlock(&inode->i_mutex);
+ iput(inode);
+ }
+
+ /* kill removed sysfs_dirents */
+ while (acxt->removed) {
+ struct sysfs_dirent *sd = acxt->removed;
+
+ acxt->removed = sd->s_sibling;
+ sd->s_sibling = NULL;
+
+ sysfs_drop_dentry(sd);
+ sysfs_deactivate(sd);
+ sysfs_put(sd);
}
+
+ return acxt->cnt;
}
/**
@@ -396,19 +533,20 @@ static int create_dir(struct kobject *kobj, struct sysfs_dirent *parent_sd,
const char *name, struct sysfs_dirent **p_sd)
{
struct dentry *parent = parent_sd->s_dentry;
+ struct sysfs_addrm_cxt acxt;
int error;
umode_t mode = S_IFDIR| S_IRWXU | S_IRUGO | S_IXUGO;
struct dentry *dentry;
struct inode *inode;
struct sysfs_dirent *sd;
- mutex_lock(&parent->d_inode->i_mutex);
+ sysfs_addrm_start(&acxt, parent_sd);
/* allocate */
dentry = lookup_one_len(name, parent, strlen(name));
if (IS_ERR(dentry)) {
error = PTR_ERR(dentry);
- goto out_unlock;
+ goto out_finish;
}
error = -EEXIST;
@@ -433,23 +571,18 @@ static int create_dir(struct kobject *kobj, struct sysfs_dirent *parent_sd,
}
/* link in */
- mutex_lock(&sysfs_mutex);
-
error = -EEXIST;
- if (sysfs_find_dirent(parent_sd, name)) {
- mutex_unlock(&sysfs_mutex);
+ if (sysfs_find_dirent(parent_sd, name))
goto out_iput;
- }
+ sysfs_add_one(&acxt, sd);
+ sysfs_link_sibling(sd);
sysfs_instantiate(dentry, inode);
- inc_nlink(parent->d_inode);
- sysfs_attach_dirent(sd, parent_sd, dentry);
-
- mutex_unlock(&sysfs_mutex);
+ sysfs_attach_dentry(sd, dentry);
*p_sd = sd;
error = 0;
- goto out_unlock; /* pin directory dentry in core */
+ goto out_finish; /* pin directory dentry in core */
out_iput:
iput(inode);
@@ -459,8 +592,8 @@ static int create_dir(struct kobject *kobj, struct sysfs_dirent *parent_sd,
d_drop(dentry);
out_dput:
dput(dentry);
- out_unlock:
- mutex_unlock(&parent->d_inode->i_mutex);
+ out_finish:
+ sysfs_addrm_finish(&acxt);
return error;
}
@@ -561,16 +694,12 @@ const struct inode_operations sysfs_dir_inode_operations = {
static void remove_dir(struct sysfs_dirent *sd)
{
- mutex_lock(&sysfs_mutex);
- sysfs_unlink_sibling(sd);
- sd->s_flags |= SYSFS_FLAG_REMOVED;
- mutex_unlock(&sysfs_mutex);
+ struct sysfs_addrm_cxt acxt;
- pr_debug(" o %s removing done\n", sd->s_name);
-
- sysfs_drop_dentry(sd);
- sysfs_deactivate(sd);
- sysfs_put(sd);
+ sysfs_addrm_start(&acxt, sd->s_parent);
+ sysfs_unlink_sibling(sd);
+ sysfs_remove_one(&acxt, sd);
+ sysfs_addrm_finish(&acxt);
}
void sysfs_remove_subdir(struct sysfs_dirent *sd)
@@ -581,38 +710,26 @@ void sysfs_remove_subdir(struct sysfs_dirent *sd)
static void __sysfs_remove_dir(struct sysfs_dirent *dir_sd)
{
- struct sysfs_dirent *removed = NULL;
+ struct sysfs_addrm_cxt acxt;
struct sysfs_dirent **pos;
if (!dir_sd)
return;
pr_debug("sysfs %s: removing dir\n", dir_sd->s_name);
- mutex_lock(&sysfs_mutex);
+ sysfs_addrm_start(&acxt, dir_sd);
pos = &dir_sd->s_children;
while (*pos) {
struct sysfs_dirent *sd = *pos;
if (sysfs_type(sd) && (sysfs_type(sd) & SYSFS_NOT_PINNED)) {
- sd->s_flags |= SYSFS_FLAG_REMOVED;
*pos = sd->s_sibling;
- sd->s_sibling = removed;
- removed = sd;
+ sd->s_sibling = NULL;
+ sysfs_remove_one(&acxt, sd);
} else
pos = &(*pos)->s_sibling;
}
- mutex_unlock(&sysfs_mutex);
-
- while (removed) {
- struct sysfs_dirent *sd = removed;
-
- removed = sd->s_sibling;
- sd->s_sibling = NULL;
-
- sysfs_drop_dentry(sd);
- sysfs_deactivate(sd);
- sysfs_put(sd);
- }
+ sysfs_addrm_finish(&acxt);
remove_dir(dir_sd);
}
@@ -772,7 +889,8 @@ static int sysfs_dir_open(struct inode *inode, struct file *file)
sd = sysfs_new_dirent("_DIR_", 0, 0);
if (sd) {
mutex_lock(&sysfs_mutex);
- sysfs_attach_dirent(sd, parent_sd, NULL);
+ sd->s_parent = sysfs_get(parent_sd);
+ sysfs_link_sibling(sd);
mutex_unlock(&sysfs_mutex);
}
@@ -957,6 +1075,7 @@ struct sysfs_dirent *sysfs_create_shadow_dir(struct kobject *kobj)
struct sysfs_dirent *parent_sd = parent->d_fsdata;
struct dentry *shadow;
struct sysfs_dirent *sd;
+ struct sysfs_addrm_cxt acxt;
sd = ERR_PTR(-EINVAL);
if (!sysfs_is_shadowed_inode(inode))
@@ -970,15 +1089,18 @@ struct sysfs_dirent *sysfs_create_shadow_dir(struct kobject *kobj)
if (!sd)
goto nomem;
sd->s_elem.dir.kobj = kobj;
- /* point to parent_sd but don't attach to it */
- sd->s_parent = sysfs_get(parent_sd);
- mutex_lock(&sysfs_mutex);
- sysfs_attach_dirent(sd, NULL, shadow);
- mutex_unlock(&sysfs_mutex);
+ sysfs_addrm_start(&acxt, parent_sd);
+
+ /* add but don't link into children list */
+ sysfs_add_one(&acxt, sd);
+
+ /* attach and instantiate dentry */
+ sysfs_attach_dentry(sd, shadow);
d_instantiate(shadow, igrab(inode));
- inc_nlink(inode);
- inc_nlink(parent->d_inode);
+ inc_nlink(inode); /* tj: synchronization? */
+
+ sysfs_addrm_finish(&acxt);
dget(shadow); /* Extra count - pin the dentry in core */
diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c
index d0deed3e60b5..69bacf1db596 100644
--- a/fs/sysfs/file.c
+++ b/fs/sysfs/file.c
@@ -416,6 +416,7 @@ int sysfs_add_file(struct sysfs_dirent *dir_sd, const struct attribute *attr,
int type)
{
umode_t mode = (attr->mode & S_IALLUGO) | S_IFREG;
+ struct sysfs_addrm_cxt acxt;
struct sysfs_dirent *sd;
sd = sysfs_new_dirent(attr->name, mode, type);
@@ -423,20 +424,18 @@ int sysfs_add_file(struct sysfs_dirent *dir_sd, const struct attribute *attr,
return -ENOMEM;
sd->s_elem.attr.attr = (void *)attr;
- mutex_lock(&sysfs_mutex);
+ sysfs_addrm_start(&acxt, dir_sd);
if (!sysfs_find_dirent(dir_sd, attr->name)) {
- sysfs_attach_dirent(sd, dir_sd, NULL);
- sd = NULL;
+ sysfs_add_one(&acxt, sd);
+ sysfs_link_sibling(sd);
}
- mutex_unlock(&sysfs_mutex);
+ if (sysfs_addrm_finish(&acxt))
+ return 0;
- if (sd) {
- sysfs_put(sd);
- return -EEXIST;
- }
- return 0;
+ sysfs_put(sd);
+ return -EEXIST;
}
diff --git a/fs/sysfs/inode.c b/fs/sysfs/inode.c
index d439c0b4bfce..f95966847a81 100644
--- a/fs/sysfs/inode.c
+++ b/fs/sysfs/inode.c
@@ -191,15 +191,9 @@ void sysfs_instantiate(struct dentry *dentry, struct inode *inode)
{
BUG_ON(!dentry || dentry->d_inode);
- if (inode->i_state & I_NEW) {
+ if (inode->i_state & I_NEW)
unlock_new_inode(inode);
- 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;
- }
- }
-
d_instantiate(dentry, inode);
}
@@ -220,7 +214,6 @@ void sysfs_instantiate(struct dentry *dentry, struct inode *inode)
void sysfs_drop_dentry(struct sysfs_dirent *sd)
{
struct dentry *dentry = NULL;
- struct timespec curtime;
struct inode *inode;
/* We're not holding a reference to ->s_dentry dentry but the
@@ -246,13 +239,11 @@ void sysfs_drop_dentry(struct sysfs_dirent *sd)
dput(dentry);
/* adjust nlink and update timestamp */
- curtime = CURRENT_TIME;
-
inode = ilookup(sysfs_sb, sd->s_ino);
if (inode) {
mutex_lock(&inode->i_mutex);
- inode->i_ctime = curtime;
+ inode->i_ctime = CURRENT_TIME;
drop_nlink(inode);
if (sysfs_type(sd) == SYSFS_DIR)
drop_nlink(inode);
@@ -260,30 +251,17 @@ void sysfs_drop_dentry(struct sysfs_dirent *sd)
mutex_unlock(&inode->i_mutex);
iput(inode);
}
-
- /* adjust nlink and udpate timestamp of the parent */
- inode = ilookup(sysfs_sb, sd->s_parent->s_ino);
- if (inode) {
- mutex_lock(&inode->i_mutex);
-
- inode->i_ctime = inode->i_mtime = curtime;
- if (sysfs_type(sd) == SYSFS_DIR)
- drop_nlink(inode);
-
- mutex_unlock(&inode->i_mutex);
- iput(inode);
- }
}
int sysfs_hash_and_remove(struct sysfs_dirent *dir_sd, const char *name)
{
+ struct sysfs_addrm_cxt acxt;
struct sysfs_dirent **pos, *sd;
- int found = 0;
if (!dir_sd)
return -ENOENT;
- mutex_lock(&sysfs_mutex);
+ sysfs_addrm_start(&acxt, dir_sd);
for (pos = &dir_sd->s_children; *pos; pos = &(*pos)->s_sibling) {
sd = *pos;
@@ -291,22 +269,14 @@ int sysfs_hash_and_remove(struct sysfs_dirent *dir_sd, const char *name)
if (!sysfs_type(sd))
continue;
if (!strcmp(sd->s_name, name)) {
- sd->s_flags |= SYSFS_FLAG_REMOVED;
*pos = sd->s_sibling;
sd->s_sibling = NULL;
- found = 1;
+ sysfs_remove_one(&acxt, sd);
break;
}
}
- mutex_unlock(&sysfs_mutex);
-
- if (!found)
- return -ENOENT;
-
- sysfs_drop_dentry(sd);
- sysfs_deactivate(sd);
- sysfs_put(sd);
-
- return 0;
+ if (sysfs_addrm_finish(&acxt))
+ return 0;
+ return -ENOENT;
}
diff --git a/fs/sysfs/symlink.c b/fs/sysfs/symlink.c
index 683316f0aa96..2f86e0422290 100644
--- a/fs/sysfs/symlink.c
+++ b/fs/sysfs/symlink.c
@@ -55,6 +55,7 @@ int sysfs_create_link(struct kobject * kobj, struct kobject * target, const char
struct sysfs_dirent *parent_sd = NULL;
struct sysfs_dirent *target_sd = NULL;
struct sysfs_dirent *sd = NULL;
+ struct sysfs_addrm_cxt acxt;
int error;
BUG_ON(!name);
@@ -87,17 +88,18 @@ int sysfs_create_link(struct kobject * kobj, struct kobject * target, const char
goto out_put;
sd->s_elem.symlink.target_sd = target_sd;
- mutex_lock(&sysfs_mutex);
- error = -EEXIST;
- if (sysfs_find_dirent(parent_sd, name))
- goto out_unlock;
- sysfs_attach_dirent(sd, parent_sd, NULL);
- mutex_unlock(&sysfs_mutex);
+ sysfs_addrm_start(&acxt, parent_sd);
- return 0;
+ if (!sysfs_find_dirent(parent_sd, name)) {
+ sysfs_add_one(&acxt, sd);
+ sysfs_link_sibling(sd);
+ }
- out_unlock:
- mutex_unlock(&sysfs_mutex);
+ if (sysfs_addrm_finish(&acxt))
+ return 0;
+
+ error = -EEXIST;
+ /* fall through */
out_put:
sysfs_put(target_sd);
sysfs_put(sd);
diff --git a/fs/sysfs/sysfs.h b/fs/sysfs/sysfs.h
index 26051616ed11..3e9a5ee38233 100644
--- a/fs/sysfs/sysfs.h
+++ b/fs/sysfs/sysfs.h
@@ -44,14 +44,29 @@ struct sysfs_dirent {
#define SD_DEACTIVATED_BIAS INT_MIN
+struct sysfs_addrm_cxt {
+ struct sysfs_dirent *parent_sd;
+ struct inode *parent_inode;
+ struct sysfs_dirent *removed;
+ int cnt;
+};
+
extern struct vfsmount * sysfs_mount;
extern struct kmem_cache *sysfs_dir_cachep;
+extern void sysfs_link_sibling(struct sysfs_dirent *sd);
+extern void sysfs_unlink_sibling(struct sysfs_dirent *sd);
extern struct sysfs_dirent *sysfs_get_active(struct sysfs_dirent *sd);
extern void sysfs_put_active(struct sysfs_dirent *sd);
extern struct sysfs_dirent *sysfs_get_active_two(struct sysfs_dirent *sd);
extern void sysfs_put_active_two(struct sysfs_dirent *sd);
-extern void sysfs_deactivate(struct sysfs_dirent *sd);
+extern void sysfs_addrm_start(struct sysfs_addrm_cxt *acxt,
+ struct sysfs_dirent *parent_sd);
+extern void sysfs_add_one(struct sysfs_addrm_cxt *acxt,
+ struct sysfs_dirent *sd);
+extern void sysfs_remove_one(struct sysfs_addrm_cxt *acxt,
+ struct sysfs_dirent *sd);
+extern int sysfs_addrm_finish(struct sysfs_addrm_cxt *acxt);
extern void sysfs_delete_inode(struct inode *inode);
extern void sysfs_init_inode(struct sysfs_dirent *sd, struct inode *inode);
@@ -65,9 +80,6 @@ extern struct sysfs_dirent *sysfs_get_dirent(struct sysfs_dirent *parent_sd,
const unsigned char *name);
extern struct sysfs_dirent *sysfs_new_dirent(const char *name, umode_t mode,
int type);
-extern void sysfs_attach_dirent(struct sysfs_dirent *sd,
- struct sysfs_dirent *parent_sd,
- struct dentry *dentry);
extern int sysfs_add_file(struct sysfs_dirent *dir_sd,
const struct attribute *attr, int type);