summaryrefslogtreecommitdiffstats
path: root/fs/sysfs/dir.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/sysfs/dir.c')
-rw-r--r--fs/sysfs/dir.c250
1 files changed, 186 insertions, 64 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 */