lkml.org 
[lkml]   [2007]   [Sep]   [20]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    Subject[PATCH 19/22] sysfs: implement sysfs_dirent based rename - sysfs_rename()
    Date
    From
    sysfs_rename() takes target @sd, @new_parent and @new_name and rename
    @sd to @new_name and move it under @new_parent. @new_parent and/or
    @new_name can be NULL if the specific operation is not needed.

    To handle both move and rename && prepare for multiple renames in one
    shot for easier symlink handling and shadow dirents, sysfs_rename() is
    implemented to be able to handle arbitrary number of rename/move
    operations.

    During sysfs_prep_rename(), it acquires all the resources it will need
    during the operations including dentries and copied names. After all
    are acquired, all needed i_mutexes are locked. Deadlock is avoided by
    using trylock. If any lock acquisition fails, it releases all
    i_mutexes and retries after 1ms. Because i_mutexes are used very
    lightly in sysfs, almost like spinlocks just to satisfy VFS locking
    rules, I don't think there will be any starvation issues.

    This makes rename a heavy operation but sysfs_rename() may fail and
    it's shady-side-of-the-moon cold path where programming convenience
    dominates performance by all measures.

    sysfs_rename() can be called on any type of sysfs_dirent and always
    copies @new_name.

    Kobject based sysfs_rename_dir() and sysfs_move_dir() are
    reimplemented using sysfs_remove().

    This patch doesn't introduce any behavior change to the original API.

    Signed-off-by: Tejun Heo <htejun@gmail.com>
    ---
    fs/sysfs/dir.c | 433 +++++++++++++++++++++++++++++++++----------------
    fs/sysfs/kobject.c | 31 ++++
    include/linux/sysfs.h | 9 +
    3 files changed, 337 insertions(+), 136 deletions(-)

    diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c
    index 986718c..d0eb9bf 100644
    --- a/fs/sysfs/dir.c
    +++ b/fs/sysfs/dir.c
    @@ -11,6 +11,7 @@
    #include <linux/idr.h>
    #include <linux/completion.h>
    #include <linux/mutex.h>
    +#include <linux/delay.h>
    #include "sysfs.h"

    /* verify all mode flags are inside S_IFMT */
    @@ -948,142 +949,6 @@ void sysfs_remove(struct sysfs_dirent *sd)
    }
    EXPORT_SYMBOL_GPL(sysfs_remove);

    -int sysfs_rename_dir(struct kobject * kobj, const char *new_name)
    -{
    - struct sysfs_dirent *sd = kobj->sd;
    - struct dentry *parent = NULL;
    - struct dentry *old_dentry = NULL, *new_dentry = NULL;
    - const char *dup_name = NULL;
    - int error;
    -
    - mutex_lock(&sysfs_op_mutex);
    -
    - error = 0;
    - if (strcmp(sd->s_name, new_name) == 0)
    - goto out; /* nothing to rename */
    -
    - /* get the original dentry */
    - old_dentry = sysfs_get_dentry(sd);
    - if (IS_ERR(old_dentry)) {
    - error = PTR_ERR(old_dentry);
    - goto out;
    - }
    -
    - parent = old_dentry->d_parent;
    -
    - /* lock parent and get dentry for new name */
    - mutex_lock(&parent->d_inode->i_mutex);
    - mutex_lock(&sysfs_mutex);
    -
    - error = -EEXIST;
    - if (sysfs_find_dirent(sd->s_parent, new_name))
    - goto out_unlock;
    -
    - error = -ENOMEM;
    - new_dentry = d_alloc_name(parent, new_name);
    - if (!new_dentry)
    - goto out_unlock;
    -
    - /* rename kobject and sysfs_dirent */
    - error = -ENOMEM;
    - new_name = dup_name = kstrdup(new_name, GFP_KERNEL);
    - if (!new_name)
    - goto out_unlock;
    -
    - error = kobject_set_name(kobj, "%s", new_name);
    - if (error)
    - goto out_unlock;
    -
    - dup_name = sd->s_name;
    - sd->s_name = new_name;
    -
    - /* rename */
    - d_add(new_dentry, NULL);
    - d_move(old_dentry, new_dentry);
    -
    - error = 0;
    - out_unlock:
    - mutex_unlock(&sysfs_mutex);
    - mutex_unlock(&parent->d_inode->i_mutex);
    - kfree(dup_name);
    - dput(old_dentry);
    - dput(new_dentry);
    - out:
    - mutex_unlock(&sysfs_op_mutex);
    - return error;
    -}
    -
    -int sysfs_move_dir(struct kobject *kobj, struct kobject *new_parent_kobj)
    -{
    - struct sysfs_dirent *sd = kobj->sd;
    - struct sysfs_dirent *new_parent_sd;
    - struct dentry *old_parent, *new_parent = NULL;
    - struct dentry *old_dentry = NULL, *new_dentry = NULL;
    - int error;
    -
    - mutex_lock(&sysfs_op_mutex);
    - BUG_ON(!sd->s_parent);
    - new_parent_sd = new_parent_kobj->sd ? new_parent_kobj->sd : sysfs_root;
    -
    - error = 0;
    - if (sd->s_parent == new_parent_sd)
    - goto out; /* nothing to move */
    -
    - /* get dentries */
    - old_dentry = sysfs_get_dentry(sd);
    - if (IS_ERR(old_dentry)) {
    - error = PTR_ERR(old_dentry);
    - goto out;
    - }
    - old_parent = old_dentry->d_parent;
    -
    - new_parent = sysfs_get_dentry(new_parent_sd);
    - if (IS_ERR(new_parent)) {
    - error = PTR_ERR(new_parent);
    - goto out;
    - }
    -
    -again:
    - mutex_lock(&old_parent->d_inode->i_mutex);
    - if (!mutex_trylock(&new_parent->d_inode->i_mutex)) {
    - mutex_unlock(&old_parent->d_inode->i_mutex);
    - goto again;
    - }
    - mutex_lock(&sysfs_mutex);
    -
    - error = -EEXIST;
    - if (sysfs_find_dirent(new_parent_sd, sd->s_name))
    - goto out_unlock;
    -
    - error = -ENOMEM;
    - new_dentry = d_alloc_name(new_parent, sd->s_name);
    - if (!new_dentry)
    - goto out_unlock;
    -
    - error = 0;
    - d_add(new_dentry, NULL);
    - d_move(old_dentry, new_dentry);
    - dput(new_dentry);
    -
    - /* Remove from old parent's list and insert into new parent's list. */
    - sysfs_unlink_sibling(sd);
    - sysfs_get(new_parent_sd);
    - sysfs_put(sd->s_parent);
    - sd->s_parent = new_parent_sd;
    - sysfs_link_sibling(sd);
    -
    - out_unlock:
    - mutex_unlock(&sysfs_mutex);
    - mutex_unlock(&new_parent->d_inode->i_mutex);
    - mutex_unlock(&old_parent->d_inode->i_mutex);
    - out:
    - dput(new_parent);
    - dput(old_dentry);
    - dput(new_dentry);
    - mutex_unlock(&sysfs_op_mutex);
    - return error;
    -}
    -
    /* Relationship between s_mode and the DT_xxx types */
    static inline unsigned char dt_type(struct sysfs_dirent *sd)
    {
    @@ -1143,6 +1008,302 @@ const struct file_operations sysfs_dir_operations = {
    .readdir = sysfs_readdir,
    };

    +/*
    + * Renaming a single node can result in renames of multiple nodes as
    + * symlinks pointing to the node are renamed together. To rename
    + * multiple nodes atogether atomically, all resources including
    + * i_mutexes and dentries are grabbed before committing the operation.
    + *
    + * i_mutexes are recorded using sysfs_rcxt_mutex_ent as they might not
    + * correspond one to one to renames (e.g. two symlinks to the same
    + * target in the same directory). All other resources are recorded in
    + * sysfs_rcxt_rename_ent. Both are chained to sysfs_rename_context to
    + * be used later.
    + */
    +struct sysfs_rcxt_rename_ent {
    + struct list_head list;
    + struct sysfs_dirent *sd;
    + struct sysfs_dirent *new_parent;
    + const char *old_name;
    + const char *new_name;
    + int old_name_copied;
    + int new_name_copied;
    + struct dentry *old_dentry;
    + struct dentry *new_dentry;
    +};
    +
    +struct sysfs_rcxt_mutex_ent {
    + struct list_head list;
    + struct mutex *mutex;
    + int locked;
    +};
    +
    +struct sysfs_rename_context {
    + struct list_head mutexes;
    + struct list_head renames;
    +};
    +
    +static struct sysfs_rcxt_rename_ent *
    +sysfs_rcxt_add(struct sysfs_rename_context *rcxt, struct sysfs_dirent *sd,
    + struct sysfs_dirent *new_parent)
    +{
    + struct sysfs_rcxt_rename_ent *rent;
    +
    + rent = kzalloc(sizeof(*rent), GFP_KERNEL);
    + if (!rent)
    + return NULL;
    +
    + rent->sd = sysfs_get(sd);
    + rent->new_parent = sysfs_get(new_parent);
    + rent->old_name = sd->s_name;
    + rent->old_name_copied = !!(sd->s_flags & SYSFS_FLAG_NAME_COPIED);
    +
    + list_add_tail(&rent->list, &rcxt->renames);
    +
    + return rent;
    +}
    +
    +static int sysfs_rcxt_add_mutex(struct sysfs_rename_context *rcxt,
    + struct mutex *mutex)
    +{
    + struct sysfs_rcxt_mutex_ent *ment;
    +
    + list_for_each_entry(ment, &rcxt->mutexes, list)
    + if (ment->mutex == mutex)
    + return 0;
    +
    + ment = kzalloc(sizeof(*ment), GFP_KERNEL);
    + if (!ment)
    + return -ENOMEM;
    +
    + ment->mutex = mutex;
    +
    + list_add_tail(&ment->list, &rcxt->mutexes);
    +
    + return 0;
    +}
    +
    +static int sysfs_rcxt_get_dentries(struct sysfs_rename_context *rcxt,
    + struct sysfs_rcxt_rename_ent *rent)
    +{
    + struct dentry *old_dentry, *new_parent_dentry, *new_dentry;
    + int rc;
    +
    + /* get old dentry */
    + old_dentry = sysfs_get_dentry(rent->sd);
    + if (IS_ERR(old_dentry))
    + return PTR_ERR(old_dentry);
    + rent->old_dentry = old_dentry;
    +
    + /* allocate new dentry */
    + new_parent_dentry = sysfs_get_dentry(rent->new_parent);
    + if (IS_ERR(new_parent_dentry))
    + return PTR_ERR(new_parent_dentry);
    +
    + new_dentry = d_alloc_name(new_parent_dentry, rent->new_name);
    + dput(new_parent_dentry);
    + if (!new_dentry)
    + return -ENOMEM;
    + rent->new_dentry = new_dentry;
    +
    + /* add i_mutexes to mutex list */
    + rc = sysfs_rcxt_add_mutex(rcxt, &old_dentry->d_parent->d_inode->i_mutex);
    + if (rc)
    + return rc;
    +
    + rc = sysfs_rcxt_add_mutex(rcxt, &new_dentry->d_parent->d_inode->i_mutex);
    + if (rc)
    + return rc;
    +
    + return 0;
    +}
    +
    +static void sysfs_post_rename(struct sysfs_rename_context *rcxt, int error)
    +{
    + struct sysfs_rcxt_mutex_ent *ment, *next_ment;
    + struct sysfs_rcxt_rename_ent *rent, *next_rent;
    +
    + /* release all mutexes */
    + list_for_each_entry_safe(ment, next_ment, &rcxt->mutexes, list) {
    + if (ment->locked)
    + mutex_unlock(ment->mutex);
    +
    + list_del(&ment->list);
    + kfree(ment);
    + }
    +
    + /* release all renames */
    + list_for_each_entry_safe(rent, next_rent, &rcxt->renames, list) {
    + /* If rename succeeded, old name is unused; otherwise,
    + * new name is unused. Free accordingly.
    + */
    + if (!error) {
    + if (rent->old_name_copied)
    + kfree(rent->old_name);
    + } else {
    + if (rent->new_name_copied)
    + kfree(rent->new_name);
    + }
    +
    + dput(rent->old_dentry);
    + dput(rent->new_dentry);
    + sysfs_put(rent->sd);
    + sysfs_put(rent->new_parent);
    +
    + list_del(&rent->list);
    + kfree(rent);
    + }
    +}
    +
    +static int sysfs_prep_rename(struct sysfs_rename_context *rcxt,
    + struct sysfs_dirent *sd,
    + struct sysfs_dirent *new_parent,
    + const char *new_name)
    +{
    + struct sysfs_rcxt_rename_ent *rent;
    + struct sysfs_rcxt_mutex_ent *ment;
    + int rc;
    +
    + INIT_LIST_HEAD(&rcxt->mutexes);
    + INIT_LIST_HEAD(&rcxt->renames);
    +
    + /*
    + * prep @sd
    + */
    + rc = -ENOMEM;
    + rent = sysfs_rcxt_add(rcxt, sd, new_parent);
    + if (!rent)
    + goto err;
    +
    + rc = -ENOMEM;
    + rent->new_name = kstrdup(new_name, GFP_KERNEL);
    + if (!rent->new_name)
    + goto err;
    + rent->new_name_copied = 1;
    +
    + rc = sysfs_rcxt_get_dentries(rcxt, rent);
    + if (rc)
    + goto err;
    +
    + /*
    + * lock all i_mutexes
    + */
    + try_lock:
    + list_for_each_entry(ment, &rcxt->mutexes, list) {
    + if (!mutex_trylock(ment->mutex)) {
    + /* unlock all and retry */
    + list_for_each_entry(ment, &rcxt->mutexes, list) {
    + if (ment->locked) {
    + mutex_unlock(ment->mutex);
    + ment->locked = 0;
    + }
    + }
    +
    + /* No need to be over-anxious, let's take it
    + * slow. Sysfs i_mutexes are lightly loaded
    + * and starvation is highly unlikely.
    + */
    + msleep(1);
    + goto try_lock;
    + }
    +
    + ment->locked = 1;
    + }
    +
    + return 0;
    +
    + err:
    + sysfs_post_rename(rcxt, rc);
    + return rc;
    +}
    +
    +/**
    + * sysfs_rename - rename and/or move sysfs node
    + * @sd: sysfs_dirent to rename
    + * @new_parent: new parent to move @sd under (NULL if only renaming)
    + * @new_name: new name to rename @sd to (NULL if only moving)
    + *
    + * Rename and/or move @sd. If both @new_parent and @new_name are
    + * specified, @sd is renamed to @new_name and moved under
    + * @new_parent atomically. If only one of the two is specified,
    + * only the specified operation is performed.
    + *
    + * Renaming and/or moving a sysfs node which is pointed to by
    + * symlinks causes the symlinks to be renamed according to their
    + * name formats.
    + *
    + * LOCKING:
    + * Kernel thread context (may sleep).
    + *
    + * RETURNS:
    + * 0 on success -errno on failure.
    + */
    +int sysfs_rename(struct sysfs_dirent *sd, struct sysfs_dirent *new_parent,
    + const char *new_name)
    +{
    + struct sysfs_rename_context rcxt;
    + struct sysfs_rcxt_rename_ent *rent;
    + int error;
    +
    + mutex_lock(&sysfs_op_mutex);
    +
    + if (!new_parent)
    + new_parent = sd->s_parent;
    + if (!new_name)
    + new_name = sd->s_name;
    +
    + error = 0;
    + if (sd->s_parent == new_parent && !strcmp(sd->s_name, new_name))
    + goto out; /* nothing to rename */
    +
    + error = sysfs_prep_rename(&rcxt, sd, new_parent, new_name);
    + if (error)
    + goto out;
    +
    + /* check whether there are duplicate names */
    + error = -EEXIST;
    + list_for_each_entry(rent, &rcxt.renames, list)
    + if (sysfs_find_dirent(rent->new_parent, rent->new_name))
    + goto out_post;
    +
    + mutex_lock(&sysfs_mutex);
    +
    + /* rename sysfs_dirents and dentries */
    + list_for_each_entry(rent, &rcxt.renames, list) {
    + /* rename sd */
    + rent->sd->s_name = rent->new_name;
    + rent->sd->s_flags &= ~SYSFS_FLAG_NAME_COPIED;
    + if (rent->new_name_copied)
    + rent->sd->s_flags |= SYSFS_FLAG_NAME_COPIED;
    +
    + /* move sd */
    + if (rent->sd->s_parent != rent->new_parent) {
    + sysfs_unlink_sibling(rent->sd);
    + sysfs_put(rent->sd->s_parent);
    + rent->sd->s_parent = sysfs_get(rent->new_parent);
    + sysfs_link_sibling(rent->sd);
    + }
    +
    + /* update dcache and inode accordingly */
    + if (sysfs_type(rent->sd) == SYSFS_DIR) {
    + drop_nlink(rent->old_dentry->d_parent->d_inode);
    + inc_nlink(rent->new_dentry->d_parent->d_inode);
    + }
    + d_add(rent->new_dentry, NULL);
    + d_move(rent->old_dentry, rent->new_dentry);
    + }
    +
    + mutex_unlock(&sysfs_mutex);
    + error = 0;
    + /* fall through */
    + out_post:
    + sysfs_post_rename(&rcxt, error);
    + out:
    + mutex_unlock(&sysfs_op_mutex);
    + return error;
    +}
    +EXPORT_SYMBOL_GPL(sysfs_rename);
    +
    /**
    * sysfs_chmod - chmod a sysfs_dirent
    * @sd: sysfs_dirent to chmod
    diff --git a/fs/sysfs/kobject.c b/fs/sysfs/kobject.c
    index 0a0d583..55b884b 100644
    --- a/fs/sysfs/kobject.c
    +++ b/fs/sysfs/kobject.c
    @@ -75,6 +75,37 @@ void sysfs_remove_dir(struct kobject * kobj)
    __sysfs_remove(sd, 0);
    }

    +int sysfs_rename_dir(struct kobject *kobj, const char *new_name)
    +{
    + const char *dup_name;
    + int rc;
    +
    + dup_name = kstrdup(new_name, GFP_KERNEL);
    + if (!dup_name)
    + return -ENOMEM;
    +
    + rc = sysfs_rename(kobj->sd, NULL, new_name);
    + if (rc) {
    + kfree(dup_name);
    + return rc;
    + }
    +
    + __kobject_set_name(kobj, dup_name);
    +
    + return 0;
    +}
    +
    +int sysfs_move_dir(struct kobject *kobj, struct kobject *new_parent_kobj)
    +{
    + struct sysfs_dirent *sd = kobj->sd;
    + struct sysfs_dirent *new_parent_sd = new_parent_kobj->sd;
    +
    + if (!new_parent_sd)
    + new_parent_sd = sysfs_root;
    +
    + return sysfs_rename(sd, new_parent_sd, NULL);
    +}
    +
    /*
    * Subsystem file operations. These operations allow subsystems to
    * have files that can be read/written.
    diff --git a/include/linux/sysfs.h b/include/linux/sysfs.h
    index 08ed1b0..f0279a7 100644
    --- a/include/linux/sysfs.h
    +++ b/include/linux/sysfs.h
    @@ -67,6 +67,8 @@ struct sysfs_dirent *sysfs_add_link(struct sysfs_dirent *parent,
    struct sysfs_dirent *sysfs_find_child(struct sysfs_dirent *parent,
    const char *name);
    void sysfs_remove(struct sysfs_dirent *sd);
    +int sysfs_rename(struct sysfs_dirent *sd, struct sysfs_dirent *new_parent,
    + const char *new_name);

    void sysfs_notify_file(struct sysfs_dirent *sd);
    int sysfs_chmod(struct sysfs_dirent *sd, mode_t mode);
    @@ -114,6 +116,13 @@ static inline void sysfs_remove(struct sysfs_dirent *sd)
    {
    }

    +static inline int sysfs_rename(struct sysfs_dirent *sd,
    + struct sysfs_dirent *new_parent,
    + const char *new_name)
    +{
    + return 0;
    +}
    +
    static inline void sysfs_notify_file(struct sysfs_dirent *sd)
    {
    }
    --
    1.5.0.3

    -
    To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
    the body of a message to majordomo@vger.kernel.org
    More majordomo info at http://vger.kernel.org/majordomo-info.html
    Please read the FAQ at http://www.tux.org/lkml/

    \
     
     \ /
      Last update: 2007-09-20 10:17    [W:2.750 / U:0.020 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site