diff options
Diffstat (limited to 'fs/sysfs/dir.c')
-rw-r--r-- | fs/sysfs/dir.c | 250 |
1 files changed, 186 insertions, 64 deletions
diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index 9fc8558fd86..edb30621b82 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 */ |