From fa7f912ad4ae0ed7591add52422e48282389652d Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 14 Jun 2007 03:45:13 +0900 Subject: sysfs: move release_sysfs_dirent() to dir.c There is no reason this function should be inlined and soon to follow sysfs object reference simplification will make it heavier. Move it to dir.c. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'fs/sysfs/dir.c') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index c4342a0..2544aae 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -15,6 +15,18 @@ DECLARE_RWSEM(sysfs_rename_sem); spinlock_t sysfs_lock = SPIN_LOCK_UNLOCKED; +void release_sysfs_dirent(struct sysfs_dirent * sd) +{ + if (sd->s_type & SYSFS_KOBJ_LINK) { + struct sysfs_symlink * sl = sd->s_element; + kfree(sl->link_name); + kobject_put(sl->target_kobj); + kfree(sl); + } + kfree(sd->s_iattr); + kmem_cache_free(sysfs_dir_cachep, sd); +} + static void sysfs_d_iput(struct dentry * dentry, struct inode * inode) { struct sysfs_dirent * sd = dentry->d_fsdata; -- cgit v1.1 From 2b611bb7abdcc08278453fc9f6517401fd69ef95 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 14 Jun 2007 03:45:13 +0900 Subject: sysfs: allocate inode number using ida sysfs used simple incrementing allocator which is not guaranteed to be unique. This patch makes sysfs use ida to give each sd a unique and packed inode number. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 45 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 9 deletions(-) (limited to 'fs/sysfs/dir.c') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index 2544aae..f09626c 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -9,12 +9,42 @@ #include #include #include +#include #include #include "sysfs.h" DECLARE_RWSEM(sysfs_rename_sem); spinlock_t sysfs_lock = SPIN_LOCK_UNLOCKED; +static spinlock_t sysfs_ino_lock = SPIN_LOCK_UNLOCKED; +static DEFINE_IDA(sysfs_ino_ida); + +int sysfs_alloc_ino(ino_t *pino) +{ + int ino, rc; + + retry: + spin_lock(&sysfs_ino_lock); + rc = ida_get_new_above(&sysfs_ino_ida, 2, &ino); + spin_unlock(&sysfs_ino_lock); + + if (rc == -EAGAIN) { + if (ida_pre_get(&sysfs_ino_ida, GFP_KERNEL)) + goto retry; + rc = -ENOMEM; + } + + *pino = ino; + return rc; +} + +static void sysfs_free_ino(ino_t ino) +{ + spin_lock(&sysfs_ino_lock); + ida_remove(&sysfs_ino_ida, ino); + spin_unlock(&sysfs_ino_lock); +} + void release_sysfs_dirent(struct sysfs_dirent * sd) { if (sd->s_type & SYSFS_KOBJ_LINK) { @@ -24,6 +54,7 @@ void release_sysfs_dirent(struct sysfs_dirent * sd) kfree(sl); } kfree(sd->s_iattr); + sysfs_free_ino(sd->s_ino); kmem_cache_free(sysfs_dir_cachep, sd); } @@ -54,14 +85,6 @@ static struct dentry_operations sysfs_dentry_ops = { .d_iput = sysfs_d_iput, }; -static unsigned int sysfs_inode_counter; -ino_t sysfs_get_inum(void) -{ - if (unlikely(sysfs_inode_counter < 3)) - sysfs_inode_counter = 3; - return sysfs_inode_counter++; -} - /* * Allocates a new sysfs_dirent and links it to the parent sysfs_dirent */ @@ -73,7 +96,11 @@ static struct sysfs_dirent * __sysfs_new_dirent(void * element) if (!sd) return NULL; - sd->s_ino = sysfs_get_inum(); + if (sysfs_alloc_ino(&sd->s_ino)) { + kmem_cache_free(sysfs_dir_cachep, sd); + return NULL; + } + atomic_set(&sd->s_count, 1); atomic_set(&sd->s_event, 1); INIT_LIST_HEAD(&sd->s_children); -- cgit v1.1 From dfeb9fb0343363aadc3ee00a9347d120bc2a26b1 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 14 Jun 2007 03:45:14 +0900 Subject: sysfs: flatten cleanup paths in sysfs_add_link() and create_dir() Flatten cleanup paths in sysfs_add_link() and create_dir() to improve readability and ease further changes to these functions. This is in preparation of object reference simplification. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 73 ++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 43 insertions(+), 30 deletions(-) (limited to 'fs/sysfs/dir.c') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index f09626c..b4c4824 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -207,40 +207,53 @@ static int init_symlink(struct inode * inode) return 0; } -static int create_dir(struct kobject * k, struct dentry * p, - const char * n, struct dentry ** d) +static int create_dir(struct kobject *kobj, struct dentry *parent, + const char *name, struct dentry **p_dentry) { int error; umode_t mode = S_IFDIR| S_IRWXU | S_IRUGO | S_IXUGO; + struct dentry *dentry; + struct sysfs_dirent *sd; - mutex_lock(&p->d_inode->i_mutex); - *d = lookup_one_len(n, p, strlen(n)); - if (!IS_ERR(*d)) { - if (sysfs_dirent_exist(p->d_fsdata, n)) - error = -EEXIST; - else - error = sysfs_make_dirent(p->d_fsdata, *d, k, mode, - SYSFS_DIR); - if (!error) { - error = sysfs_create(*d, mode, init_dir); - if (!error) { - inc_nlink(p->d_inode); - (*d)->d_op = &sysfs_dentry_ops; - d_rehash(*d); - } - } - if (error && (error != -EEXIST)) { - struct sysfs_dirent *sd = (*d)->d_fsdata; - if (sd) { - list_del_init(&sd->s_sibling); - sysfs_put(sd); - } - d_drop(*d); - } - dput(*d); - } else - error = PTR_ERR(*d); - mutex_unlock(&p->d_inode->i_mutex); + mutex_lock(&parent->d_inode->i_mutex); + + dentry = lookup_one_len(name, parent, strlen(name)); + if (IS_ERR(dentry)) { + error = PTR_ERR(dentry); + goto out_unlock; + } + + error = -EEXIST; + if (sysfs_dirent_exist(parent->d_fsdata, name)) + goto out_dput; + + error = sysfs_make_dirent(parent->d_fsdata, dentry, kobj, mode, + SYSFS_DIR); + if (error) + goto out_drop; + + error = sysfs_create(dentry, mode, init_dir); + if (error) + goto out_sput; + + inc_nlink(parent->d_inode); + dentry->d_op = &sysfs_dentry_ops; + d_rehash(dentry); + + *p_dentry = dentry; + error = 0; + goto out_dput; + + out_sput: + sd = dentry->d_fsdata; + list_del_init(&sd->s_sibling); + sysfs_put(sd); + out_drop: + d_drop(dentry); + out_dput: + dput(dentry); + out_unlock: + mutex_unlock(&parent->d_inode->i_mutex); return error; } -- cgit v1.1 From 996b73764e9bb9d5e751fd15b130ba38637d66a8 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 14 Jun 2007 03:45:14 +0900 Subject: sysfs: flatten and fix sysfs_rename_dir() error handling Error handling in sysfs_rename_dir() was broken. * When lookup_one_len() fails, 0 is returned. * If parent inode check fails, returns with inode mutex and rename rwsem held. This patch fixes the above bugs and flattens error handling such that it's more readable and easier to modify. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 73 +++++++++++++++++++++++++++++++++------------------------- 1 file changed, 41 insertions(+), 32 deletions(-) (limited to 'fs/sysfs/dir.c') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index b4c4824..90bed5d 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -452,8 +452,9 @@ void sysfs_remove_dir(struct kobject * kobj) int sysfs_rename_dir(struct kobject * kobj, struct dentry *new_parent, const char *new_name) { - int error = 0; + int error; struct dentry * new_dentry; + struct sysfs_dirent *sd, *parent_sd; if (!new_parent) return -EFAULT; @@ -462,40 +463,48 @@ int sysfs_rename_dir(struct kobject * kobj, struct dentry *new_parent, mutex_lock(&new_parent->d_inode->i_mutex); new_dentry = lookup_one_len(new_name, new_parent, strlen(new_name)); - if (!IS_ERR(new_dentry)) { - /* By allowing two different directories with the - * same d_parent we allow this routine to move - * between different shadows of the same directory - */ - if (kobj->dentry->d_parent->d_inode != new_parent->d_inode) - return -EINVAL; - else if (new_dentry->d_parent->d_inode != new_parent->d_inode) - error = -EINVAL; - else if (new_dentry == kobj->dentry) - error = -EINVAL; - else if (!new_dentry->d_inode) { - error = kobject_set_name(kobj, "%s", new_name); - if (!error) { - struct sysfs_dirent *sd, *parent_sd; - - d_add(new_dentry, NULL); - d_move(kobj->dentry, new_dentry); - - sd = kobj->dentry->d_fsdata; - parent_sd = new_parent->d_fsdata; - - list_del_init(&sd->s_sibling); - list_add(&sd->s_sibling, &parent_sd->s_children); - } - else - d_drop(new_dentry); - } else - error = -EEXIST; - dput(new_dentry); + if (IS_ERR(new_dentry)) { + error = PTR_ERR(new_dentry); + goto out_unlock; } + + /* By allowing two different directories with the same + * d_parent we allow this routine to move between different + * shadows of the same directory + */ + error = -EINVAL; + if (kobj->dentry->d_parent->d_inode != new_parent->d_inode || + new_dentry->d_parent->d_inode != new_parent->d_inode || + new_dentry == kobj->dentry) + goto out_dput; + + error = -EEXIST; + if (new_dentry->d_inode) + goto out_dput; + + error = kobject_set_name(kobj, "%s", new_name); + if (error) + goto out_drop; + + d_add(new_dentry, NULL); + d_move(kobj->dentry, new_dentry); + + sd = kobj->dentry->d_fsdata; + parent_sd = new_parent->d_fsdata; + + list_del_init(&sd->s_sibling); + list_add(&sd->s_sibling, &parent_sd->s_children); + + error = 0; + goto out_unlock; + + out_drop: + d_drop(new_dentry); + out_dput: + dput(new_dentry); + out_unlock: mutex_unlock(&new_parent->d_inode->i_mutex); up_write(&sysfs_rename_sem); - return error; } -- cgit v1.1 From a26cd7226c24c3be5dd5f48a74832fe64beb8489 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 14 Jun 2007 03:45:14 +0900 Subject: sysfs: consolidate sysfs_dirent creation functions Currently there are four functions to create sysfs_dirent - __sysfs_new_dirent(), sysfs_new_dirent(), __sysfs_make_dirent() and sysfs_make_dirent(). Other than sysfs_make_dirent(), no function has two users if calls to implement other functions are excluded. This patch consolidates sysfs_dirent creation functions into the following two. * sysfs_new_dirent() : allocate and initialize * sysfs_attach_dirent() : attach to sysfs_dirent hierarchy and/or associate with dentry This simplifies interface and gives callers more flexibility. This is in preparation of object reference simplification. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 82 ++++++++++++++++++---------------------------------------- 1 file changed, 25 insertions(+), 57 deletions(-) (limited to 'fs/sysfs/dir.c') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index 90bed5d..f16aa7e3 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -85,10 +85,7 @@ static struct dentry_operations sysfs_dentry_ops = { .d_iput = sysfs_d_iput, }; -/* - * Allocates a new sysfs_dirent and links it to the parent sysfs_dirent - */ -static struct sysfs_dirent * __sysfs_new_dirent(void * element) +struct sysfs_dirent *sysfs_new_dirent(void *element, umode_t mode, int type) { struct sysfs_dirent * sd; @@ -105,25 +102,25 @@ static struct sysfs_dirent * __sysfs_new_dirent(void * element) atomic_set(&sd->s_event, 1); INIT_LIST_HEAD(&sd->s_children); INIT_LIST_HEAD(&sd->s_sibling); + sd->s_element = element; + sd->s_mode = mode; + sd->s_type = type; return sd; } -static void __sysfs_list_dirent(struct sysfs_dirent *parent_sd, - struct sysfs_dirent *sd) +void sysfs_attach_dirent(struct sysfs_dirent *sd, + struct sysfs_dirent *parent_sd, struct dentry *dentry) { - if (sd) - list_add(&sd->s_sibling, &parent_sd->s_children); -} + if (dentry) { + sd->s_dentry = dentry; + dentry->d_fsdata = sysfs_get(sd); + dentry->d_op = &sysfs_dentry_ops; + } -static struct sysfs_dirent * sysfs_new_dirent(struct sysfs_dirent *parent_sd, - void * element) -{ - struct sysfs_dirent *sd; - sd = __sysfs_new_dirent(element); - __sysfs_list_dirent(parent_sd, sd); - return sd; + if (parent_sd) + list_add(&sd->s_sibling, &parent_sd->s_children); } /* @@ -151,39 +148,6 @@ int sysfs_dirent_exist(struct sysfs_dirent *parent_sd, return 0; } - -static struct sysfs_dirent * -__sysfs_make_dirent(struct dentry *dentry, void *element, mode_t mode, int type) -{ - struct sysfs_dirent * sd; - - sd = __sysfs_new_dirent(element); - if (!sd) - goto out; - - sd->s_mode = mode; - sd->s_type = type; - sd->s_dentry = dentry; - if (dentry) { - dentry->d_fsdata = sysfs_get(sd); - dentry->d_op = &sysfs_dentry_ops; - } - -out: - return sd; -} - -int sysfs_make_dirent(struct sysfs_dirent * parent_sd, struct dentry * dentry, - void * element, umode_t mode, int type) -{ - struct sysfs_dirent *sd; - - sd = __sysfs_make_dirent(dentry, element, mode, type); - __sysfs_list_dirent(parent_sd, sd); - - return sd ? 0 : -ENOMEM; -} - static int init_dir(struct inode * inode) { inode->i_op = &sysfs_dir_inode_operations; @@ -227,10 +191,11 @@ static int create_dir(struct kobject *kobj, struct dentry *parent, if (sysfs_dirent_exist(parent->d_fsdata, name)) goto out_dput; - error = sysfs_make_dirent(parent->d_fsdata, dentry, kobj, mode, - SYSFS_DIR); - if (error) + error = -ENOMEM; + sd = sysfs_new_dirent(kobj, mode, SYSFS_DIR); + if (!sd) goto out_drop; + sysfs_attach_dirent(sd, parent->d_fsdata, dentry); error = sysfs_create(dentry, mode, init_dir); if (error) @@ -245,7 +210,6 @@ static int create_dir(struct kobject *kobj, struct dentry *parent, goto out_dput; out_sput: - sd = dentry->d_fsdata; list_del_init(&sd->s_sibling); sysfs_put(sd); out_drop: @@ -557,13 +521,16 @@ static int sysfs_dir_open(struct inode *inode, struct file *file) { struct dentry * dentry = file->f_path.dentry; struct sysfs_dirent * parent_sd = dentry->d_fsdata; + struct sysfs_dirent * sd; mutex_lock(&dentry->d_inode->i_mutex); - file->private_data = sysfs_new_dirent(parent_sd, NULL); + sd = sysfs_new_dirent(NULL, 0, 0); + if (sd) + sysfs_attach_dirent(sd, parent_sd, NULL); mutex_unlock(&dentry->d_inode->i_mutex); - return file->private_data ? 0 : -ENOMEM; - + file->private_data = sd; + return sd ? 0 : -ENOMEM; } static int sysfs_dir_close(struct inode *inode, struct file *file) @@ -736,9 +703,10 @@ struct dentry *sysfs_create_shadow_dir(struct kobject *kobj) if (!shadow) goto nomem; - sd = __sysfs_make_dirent(shadow, kobj, inode->i_mode, SYSFS_DIR); + sd = sysfs_new_dirent(kobj, inode->i_mode, SYSFS_DIR); if (!sd) goto nomem; + sysfs_attach_dirent(sd, NULL, shadow); d_instantiate(shadow, igrab(inode)); inc_nlink(inode); -- cgit v1.1 From 13b3086d2ea483cbcae5a4236446cecc082a72cf Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 14 Jun 2007 03:45:14 +0900 Subject: sysfs: add sysfs_dirent->s_parent Add sysfs_dirent->s_parent. With this patch, each sd points to and holds a reference to its parent. This allows walking sysfs tree without referencing sd->s_dentry which can go away anytime if the user doesn't control when it's deleted. sd->s_parent is initialized and parent is referenced in sysfs_attach_dirent(). Reference to parent is released when the sd is released, so as long as reference to a sd is held, s_parent can be followed. dentry walk in sysfs_readdir() is convereted to s_parent walk. This will be used to reimplement symlink such that it uses only sysfs_dirent tree. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) (limited to 'fs/sysfs/dir.c') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index f16aa7e3..5d50e1dd 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -47,6 +47,11 @@ static void sysfs_free_ino(ino_t ino) void release_sysfs_dirent(struct sysfs_dirent * sd) { + struct sysfs_dirent *parent_sd; + + repeat: + parent_sd = sd->s_parent; + if (sd->s_type & SYSFS_KOBJ_LINK) { struct sysfs_symlink * sl = sd->s_element; kfree(sl->link_name); @@ -56,6 +61,10 @@ void release_sysfs_dirent(struct sysfs_dirent * sd) kfree(sd->s_iattr); sysfs_free_ino(sd->s_ino); kmem_cache_free(sysfs_dir_cachep, sd); + + sd = parent_sd; + if (sd && atomic_dec_and_test(&sd->s_count)) + goto repeat; } static void sysfs_d_iput(struct dentry * dentry, struct inode * inode) @@ -119,8 +128,10 @@ void sysfs_attach_dirent(struct sysfs_dirent *sd, dentry->d_op = &sysfs_dentry_ops; } - if (parent_sd) + if (parent_sd) { + sd->s_parent = sysfs_get(parent_sd); list_add(&sd->s_sibling, &parent_sd->s_children); + } } /* @@ -571,7 +582,10 @@ static int sysfs_readdir(struct file * filp, void * dirent, filldir_t filldir) i++; /* fallthrough */ case 1: - ino = parent_ino(dentry); + if (parent_sd->s_parent) + ino = parent_sd->s_parent->s_ino; + else + ino = parent_sd->s_ino; if (filldir(dirent, "..", 2, i, ino, DT_DIR) < 0) break; filp->f_pos++; @@ -688,13 +702,13 @@ int sysfs_make_shadowed_dir(struct kobject *kobj, struct dentry *sysfs_create_shadow_dir(struct kobject *kobj) { + struct dentry *dir = kobj->dentry; + struct inode *inode = dir->d_inode; + struct dentry *parent = dir->d_parent; + struct sysfs_dirent *parent_sd = parent->d_fsdata; + struct dentry *shadow; struct sysfs_dirent *sd; - struct dentry *parent, *dir, *shadow; - struct inode *inode; - dir = kobj->dentry; - inode = dir->d_inode; - parent = dir->d_parent; shadow = ERR_PTR(-EINVAL); if (!sysfs_is_shadowed_inode(inode)) goto out; @@ -706,6 +720,8 @@ struct dentry *sysfs_create_shadow_dir(struct kobject *kobj) sd = sysfs_new_dirent(kobj, inode->i_mode, SYSFS_DIR); if (!sd) goto nomem; + /* point to parent_sd but don't attach to it */ + sd->s_parent = sysfs_get(parent_sd); sysfs_attach_dirent(sd, NULL, shadow); d_instantiate(shadow, igrab(inode)); -- cgit v1.1 From 0c096b507f15397da890051ee73de4266d3941fb Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 14 Jun 2007 03:45:15 +0900 Subject: sysfs: add sysfs_dirent->s_name Add s_name to sysfs_dirent. This is to further reduce dependency to the associated dentry. Name is copied for directories and symlinks but not for attributes. Where possible, name dereferences are converted to use sd->s_name. sysfs_symlink->link_name and sysfs_get_name() are unused now and removed. This change allows symlink to be implemented using sysfs_dirent tree proper, which is the last remaining dentry-dependent sysfs walk. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 67 ++++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 23 deletions(-) (limited to 'fs/sysfs/dir.c') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index 5d50e1dd..6e8d6f5 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -54,10 +54,11 @@ void release_sysfs_dirent(struct sysfs_dirent * sd) if (sd->s_type & SYSFS_KOBJ_LINK) { struct sysfs_symlink * sl = sd->s_element; - kfree(sl->link_name); kobject_put(sl->target_kobj); kfree(sl); } + if (sd->s_type & SYSFS_COPY_NAME) + kfree(sd->s_name); kfree(sd->s_iattr); sysfs_free_ino(sd->s_ino); kmem_cache_free(sysfs_dir_cachep, sd); @@ -94,29 +95,41 @@ static struct dentry_operations sysfs_dentry_ops = { .d_iput = sysfs_d_iput, }; -struct sysfs_dirent *sysfs_new_dirent(void *element, umode_t mode, int type) +struct sysfs_dirent *sysfs_new_dirent(const char *name, void *element, + umode_t mode, int type) { - struct sysfs_dirent * sd; + char *dup_name = NULL; + struct sysfs_dirent *sd = NULL; + + if (type & SYSFS_COPY_NAME) { + name = dup_name = kstrdup(name, GFP_KERNEL); + if (!name) + goto err_out; + } sd = kmem_cache_zalloc(sysfs_dir_cachep, GFP_KERNEL); if (!sd) - return NULL; + goto err_out; - if (sysfs_alloc_ino(&sd->s_ino)) { - kmem_cache_free(sysfs_dir_cachep, sd); - return NULL; - } + if (sysfs_alloc_ino(&sd->s_ino)) + goto err_out; atomic_set(&sd->s_count, 1); atomic_set(&sd->s_event, 1); INIT_LIST_HEAD(&sd->s_children); INIT_LIST_HEAD(&sd->s_sibling); + sd->s_name = name; sd->s_element = element; sd->s_mode = mode; sd->s_type = type; return sd; + + err_out: + kfree(dup_name); + kmem_cache_free(sysfs_dir_cachep, sd); + return NULL; } void sysfs_attach_dirent(struct sysfs_dirent *sd, @@ -148,8 +161,7 @@ int sysfs_dirent_exist(struct sysfs_dirent *parent_sd, list_for_each_entry(sd, &parent_sd->s_children, s_sibling) { if (sd->s_element) { - const unsigned char *existing = sysfs_get_name(sd); - if (strcmp(existing, new)) + if (strcmp(sd->s_name, new)) continue; else return -EEXIST; @@ -203,7 +215,7 @@ static int create_dir(struct kobject *kobj, struct dentry *parent, goto out_dput; error = -ENOMEM; - sd = sysfs_new_dirent(kobj, mode, SYSFS_DIR); + sd = sysfs_new_dirent(name, kobj, mode, SYSFS_DIR); if (!sd) goto out_drop; sysfs_attach_dirent(sd, parent->d_fsdata, dentry); @@ -334,9 +346,7 @@ static struct dentry * sysfs_lookup(struct inode *dir, struct dentry *dentry, list_for_each_entry(sd, &parent_sd->s_children, s_sibling) { if (sd->s_type & SYSFS_NOT_PINNED) { - const unsigned char * name = sysfs_get_name(sd); - - if (strcmp(name, dentry->d_name.name)) + if (strcmp(sd->s_name, dentry->d_name.name)) continue; if (sd->s_type & SYSFS_KOBJ_LINK) @@ -427,9 +437,11 @@ void sysfs_remove_dir(struct kobject * kobj) int sysfs_rename_dir(struct kobject * kobj, struct dentry *new_parent, const char *new_name) { + struct sysfs_dirent *sd = kobj->dentry->d_fsdata; + struct sysfs_dirent *parent_sd = new_parent->d_fsdata; + struct dentry *new_dentry; + char *dup_name; int error; - struct dentry * new_dentry; - struct sysfs_dirent *sd, *parent_sd; if (!new_parent) return -EFAULT; @@ -457,22 +469,31 @@ int sysfs_rename_dir(struct kobject * kobj, struct dentry *new_parent, if (new_dentry->d_inode) goto out_dput; + /* rename kobject and sysfs_dirent */ + error = -ENOMEM; + new_name = dup_name = kstrdup(new_name, GFP_KERNEL); + if (!new_name) + goto out_drop; + error = kobject_set_name(kobj, "%s", new_name); if (error) - goto out_drop; + goto out_free; + kfree(sd->s_name); + sd->s_name = new_name; + + /* move under the new parent */ d_add(new_dentry, NULL); d_move(kobj->dentry, new_dentry); - sd = kobj->dentry->d_fsdata; - parent_sd = new_parent->d_fsdata; - list_del_init(&sd->s_sibling); list_add(&sd->s_sibling, &parent_sd->s_children); error = 0; goto out_unlock; + out_free: + kfree(dup_name); out_drop: d_drop(new_dentry); out_dput: @@ -535,7 +556,7 @@ static int sysfs_dir_open(struct inode *inode, struct file *file) struct sysfs_dirent * sd; mutex_lock(&dentry->d_inode->i_mutex); - sd = sysfs_new_dirent(NULL, 0, 0); + sd = sysfs_new_dirent("_DIR_", NULL, 0, 0); if (sd) sysfs_attach_dirent(sd, parent_sd, NULL); mutex_unlock(&dentry->d_inode->i_mutex); @@ -605,7 +626,7 @@ static int sysfs_readdir(struct file * filp, void * dirent, filldir_t filldir) if (!next->s_element) continue; - name = sysfs_get_name(next); + name = next->s_name; len = strlen(name); ino = next->s_ino; @@ -717,7 +738,7 @@ struct dentry *sysfs_create_shadow_dir(struct kobject *kobj) if (!shadow) goto nomem; - sd = sysfs_new_dirent(kobj, inode->i_mode, SYSFS_DIR); + sd = sysfs_new_dirent("_SHADOW_", kobj, inode->i_mode, SYSFS_DIR); if (!sd) goto nomem; /* point to parent_sd but don't attach to it */ -- cgit v1.1 From 3e5190380ebef77f2b015c9e7a4ca225a3d75021 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 14 Jun 2007 03:45:15 +0900 Subject: sysfs: make sysfs_dirent->s_element a union Make sd->s_element a union of sysfs_elem_{dir|symlink|attr|bin_attr} and rename it to s_elem. This is to achieve... * some level of type checking : changing symlink to point to sysfs_dirent instead of kobject is much safer and less painful now. * easier / standardized dereferencing * allow sysfs_elem_* to contain more than one entry Where possible, pointer is obtained by directly deferencing from sd instead of going through other entities. This reduces dependencies to dentry, inode and kobject. to_attr() and to_bin_attr() are unused now and removed. This is in preparation of object reference simplification. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) (limited to 'fs/sysfs/dir.c') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index 6e8d6f5..0791226 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -52,11 +52,8 @@ void release_sysfs_dirent(struct sysfs_dirent * sd) repeat: parent_sd = sd->s_parent; - if (sd->s_type & SYSFS_KOBJ_LINK) { - struct sysfs_symlink * sl = sd->s_element; - kobject_put(sl->target_kobj); - kfree(sl); - } + if (sd->s_type & SYSFS_KOBJ_LINK) + kobject_put(sd->s_elem.symlink.target_kobj); if (sd->s_type & SYSFS_COPY_NAME) kfree(sd->s_name); kfree(sd->s_iattr); @@ -95,8 +92,7 @@ static struct dentry_operations sysfs_dentry_ops = { .d_iput = sysfs_d_iput, }; -struct sysfs_dirent *sysfs_new_dirent(const char *name, void *element, - umode_t mode, int type) +struct sysfs_dirent *sysfs_new_dirent(const char *name, umode_t mode, int type) { char *dup_name = NULL; struct sysfs_dirent *sd = NULL; @@ -120,7 +116,6 @@ struct sysfs_dirent *sysfs_new_dirent(const char *name, void *element, INIT_LIST_HEAD(&sd->s_sibling); sd->s_name = name; - sd->s_element = element; sd->s_mode = mode; sd->s_type = type; @@ -160,7 +155,7 @@ int sysfs_dirent_exist(struct sysfs_dirent *parent_sd, struct sysfs_dirent * sd; list_for_each_entry(sd, &parent_sd->s_children, s_sibling) { - if (sd->s_element) { + if (sd->s_type) { if (strcmp(sd->s_name, new)) continue; else @@ -215,9 +210,10 @@ static int create_dir(struct kobject *kobj, struct dentry *parent, goto out_dput; error = -ENOMEM; - sd = sysfs_new_dirent(name, kobj, mode, SYSFS_DIR); + sd = sysfs_new_dirent(name, mode, SYSFS_DIR); if (!sd) goto out_drop; + sd->s_elem.dir.kobj = kobj; sysfs_attach_dirent(sd, parent->d_fsdata, dentry); error = sysfs_create(dentry, mode, init_dir); @@ -290,10 +286,10 @@ static int sysfs_attach_attr(struct sysfs_dirent * sd, struct dentry * dentry) int error = 0; if (sd->s_type & SYSFS_KOBJ_BIN_ATTR) { - bin_attr = sd->s_element; + bin_attr = sd->s_elem.bin_attr.bin_attr; attr = &bin_attr->attr; } else { - attr = sd->s_element; + attr = sd->s_elem.attr.attr; init = init_file; } @@ -404,7 +400,7 @@ static void __sysfs_remove_dir(struct dentry *dentry) mutex_lock(&dentry->d_inode->i_mutex); parent_sd = dentry->d_fsdata; list_for_each_entry_safe(sd, tmp, &parent_sd->s_children, s_sibling) { - if (!sd->s_element || !(sd->s_type & SYSFS_NOT_PINNED)) + if (!sd->s_type || !(sd->s_type & SYSFS_NOT_PINNED)) continue; list_del_init(&sd->s_sibling); sysfs_drop_dentry(sd, dentry); @@ -556,7 +552,7 @@ static int sysfs_dir_open(struct inode *inode, struct file *file) struct sysfs_dirent * sd; mutex_lock(&dentry->d_inode->i_mutex); - sd = sysfs_new_dirent("_DIR_", NULL, 0, 0); + sd = sysfs_new_dirent("_DIR_", 0, 0); if (sd) sysfs_attach_dirent(sd, parent_sd, NULL); mutex_unlock(&dentry->d_inode->i_mutex); @@ -623,7 +619,7 @@ static int sysfs_readdir(struct file * filp, void * dirent, filldir_t filldir) next = list_entry(p, struct sysfs_dirent, s_sibling); - if (!next->s_element) + if (!next->s_type) continue; name = next->s_name; @@ -671,7 +667,7 @@ static loff_t sysfs_dir_lseek(struct file * file, loff_t offset, int origin) struct sysfs_dirent *next; next = list_entry(p, struct sysfs_dirent, s_sibling); - if (next->s_element) + if (next->s_type) n--; p = p->next; } @@ -738,9 +734,10 @@ struct dentry *sysfs_create_shadow_dir(struct kobject *kobj) if (!shadow) goto nomem; - sd = sysfs_new_dirent("_SHADOW_", kobj, inode->i_mode, SYSFS_DIR); + sd = sysfs_new_dirent("_SHADOW_", inode->i_mode, SYSFS_DIR); 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); sysfs_attach_dirent(sd, NULL, shadow); -- cgit v1.1 From aecdcedaab49ca40620dc7dd70f67ee7269a66c9 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 14 Jun 2007 03:45:15 +0900 Subject: sysfs: implement kobj_sysfs_assoc_lock kobj->dentry can go away anytime unless the user controls when the associated sysfs node is deleted. This patch implements kobj_sysfs_assoc_lock which protects kobj->dentry. This will be used to maintain kobj based API when converting sysfs to use sysfs_dirent tree instead of dentry/kobject. Note that this lock belongs to kobject/driver-model not sysfs. Once sysfs is converted to not use kobject in its interface, this can be removed from sysfs. This is in preparation of object reference simplification. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'fs/sysfs/dir.c') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index 0791226..e9fddcc 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -15,6 +15,7 @@ DECLARE_RWSEM(sysfs_rename_sem); spinlock_t sysfs_lock = SPIN_LOCK_UNLOCKED; +spinlock_t kobj_sysfs_assoc_lock = SPIN_LOCK_UNLOCKED; static spinlock_t sysfs_ino_lock = SPIN_LOCK_UNLOCKED; static DEFINE_IDA(sysfs_ino_ida); @@ -426,8 +427,13 @@ static void __sysfs_remove_dir(struct dentry *dentry) void sysfs_remove_dir(struct kobject * kobj) { - __sysfs_remove_dir(kobj->dentry); + struct dentry *d = kobj->dentry; + + spin_lock(&kobj_sysfs_assoc_lock); kobj->dentry = NULL; + spin_unlock(&kobj_sysfs_assoc_lock); + + __sysfs_remove_dir(d); } int sysfs_rename_dir(struct kobject * kobj, struct dentry *new_parent, -- cgit v1.1 From 2b29ac252afff87b8465b064ca2d9740cf1f6e52 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 14 Jun 2007 03:45:15 +0900 Subject: sysfs: reimplement symlink using sysfs_dirent tree sysfs symlink is implemented by referencing dentry and kobject from sysfs_dirent - symlink entry references kobject, dentry is used to walk the tree. This complicates object lifetimes rules and is dangerous - for example, there is no way to tell to which module the target of a symlink belongs and referencing that kobject can make it linger after the module is gone. This patch reimplements symlink using only sysfs_dirent tree. sd for a symlink points and holds reference to the target sysfs_dirent and all walking is done using sysfs_dirent tree. Simpler and safer. Please read the following message for more info. http://article.gmane.org/gmane.linux.kernel/510293 Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'fs/sysfs/dir.c') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index e9fddcc..2a94dc3 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -54,7 +54,7 @@ void release_sysfs_dirent(struct sysfs_dirent * sd) parent_sd = sd->s_parent; if (sd->s_type & SYSFS_KOBJ_LINK) - kobject_put(sd->s_elem.symlink.target_kobj); + sysfs_put(sd->s_elem.symlink.target_sd); if (sd->s_type & SYSFS_COPY_NAME) kfree(sd->s_name); kfree(sd->s_iattr); -- cgit v1.1 From 0ab66088c855eca68513bdd7442a426c4b374ced Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 14 Jun 2007 03:45:16 +0900 Subject: sysfs: implement sysfs_dirent active reference and immediate disconnect sysfs: implement sysfs_dirent active reference and immediate disconnect Opening a sysfs node references its associated kobject, so userland can arbitrarily prolong lifetime of a kobject which complicates lifetime rules in drivers. This patch implements active reference and makes the association between kobject and sysfs immediately breakable. Now each sysfs_dirent has two reference counts - s_count and s_active. s_count is a regular reference count which guarantees that the containing sysfs_dirent is accessible. As long as s_count reference is held, all sysfs internal fields in sysfs_dirent are accessible including s_parent and s_name. The newly added s_active is active reference count. This is acquired by invoking sysfs_get_active() and it's the caller's responsibility to ensure sysfs_dirent itself is accessible (should be holding s_count one way or the other). Dereferencing sysfs_dirent to access objects out of sysfs proper requires active reference. This includes access to the associated kobjects, attributes and ops. The active references can be drained and denied by calling sysfs_deactivate(). All active sysfs_dirents must be deactivated after deletion but before the default reference is dropped. This enables immediate disconnect of sysfs nodes. Once a sysfs_dirent is deleted, it won't access any entity external to sysfs proper. Because attr/bin_attr ops access both the node itself and its parent for kobject, they need to hold active references to both. sysfs_get/put_active_two() helpers are provided to help grabbing both references. Parent's is acquired first and released last. Unlike other operations, mmapped area lingers on after mmap() is finished and the module implement implementing it and kobj need to stay referenced till all the mapped pages are gone. This is accomplished by holding one set of active references to the bin_attr and its parent if there have been any mmap during lifetime of an openfile. The references are dropped when the openfile is released. This change makes sysfs lifetime rules independent from both kobject's and module's. It not only fixes several race conditions caused by sysfs not holding onto the proper module when referencing kobject, but also helps fixing and simplifying lifetime management in driver model and drivers by taking sysfs out of the equation. Please read the following message for more info. http://article.gmane.org/gmane.linux.kernel/510293 Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) (limited to 'fs/sysfs/dir.c') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index 2a94dc3..e0d377a 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -53,6 +53,19 @@ void release_sysfs_dirent(struct sysfs_dirent * sd) repeat: parent_sd = sd->s_parent; + /* If @sd is being released after deletion, s_active is write + * locked. If @sd is cursor for directory walk or being + * released prematurely, s_active has no reader or writer. + * + * sysfs_deactivate() lies to lockdep that s_active is + * unlocked immediately. Lie one more time to cover the + * previous lie. + */ + if (!down_write_trylock(&sd->s_active)) + rwsem_acquire(&sd->s_active.dep_map, + SYSFS_S_ACTIVE_DEACTIVATE, 0, _RET_IP_); + up_write(&sd->s_active); + if (sd->s_type & SYSFS_KOBJ_LINK) sysfs_put(sd->s_elem.symlink.target_sd); if (sd->s_type & SYSFS_COPY_NAME) @@ -113,6 +126,7 @@ struct sysfs_dirent *sysfs_new_dirent(const char *name, umode_t mode, int type) atomic_set(&sd->s_count, 1); atomic_set(&sd->s_event, 1); + init_rwsem(&sd->s_active); INIT_LIST_HEAD(&sd->s_children); INIT_LIST_HEAD(&sd->s_sibling); @@ -371,7 +385,6 @@ static void remove_dir(struct dentry * d) d_delete(d); sd = d->d_fsdata; list_del_init(&sd->s_sibling); - sysfs_put(sd); if (d->d_inode) simple_rmdir(parent->d_inode,d); @@ -380,6 +393,9 @@ static void remove_dir(struct dentry * d) mutex_unlock(&parent->d_inode->i_mutex); dput(parent); + + sysfs_deactivate(sd); + sysfs_put(sd); } void sysfs_remove_subdir(struct dentry * d) @@ -390,6 +406,7 @@ void sysfs_remove_subdir(struct dentry * d) static void __sysfs_remove_dir(struct dentry *dentry) { + LIST_HEAD(removed); struct sysfs_dirent * parent_sd; struct sysfs_dirent * sd, * tmp; @@ -403,12 +420,17 @@ static void __sysfs_remove_dir(struct dentry *dentry) list_for_each_entry_safe(sd, tmp, &parent_sd->s_children, s_sibling) { if (!sd->s_type || !(sd->s_type & SYSFS_NOT_PINNED)) continue; - list_del_init(&sd->s_sibling); + list_move(&sd->s_sibling, &removed); sysfs_drop_dentry(sd, dentry); - sysfs_put(sd); } mutex_unlock(&dentry->d_inode->i_mutex); + list_for_each_entry_safe(sd, tmp, &removed, s_sibling) { + list_del_init(&sd->s_sibling); + sysfs_deactivate(sd); + sysfs_put(sd); + } + remove_dir(dentry); /** * Drop reference from dget() on entrance. -- cgit v1.1 From 198a2a847015805c6f57d8cc732bdaaccb494007 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 14 Jun 2007 03:45:16 +0900 Subject: sysfs: separate out sysfs_attach_dentry() Consolidate sd <-> dentry association into sysfs_attach_dentry() and call it after dentry and inode are properly set up. This is in preparation of sysfs_drop_dentry() updates. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 59 ++++++++++++++++++++++++++-------------------------------- 1 file changed, 26 insertions(+), 33 deletions(-) (limited to 'fs/sysfs/dir.c') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index e0d377a..01eeb4b 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -142,14 +142,24 @@ struct sysfs_dirent *sysfs_new_dirent(const char *name, umode_t mode, int type) return NULL; } +static void sysfs_attach_dentry(struct sysfs_dirent *sd, struct dentry *dentry) +{ + dentry->d_op = &sysfs_dentry_ops; + dentry->d_fsdata = sysfs_get(sd); + + /* protect sd->s_dentry against sysfs_d_iput */ + spin_lock(&sysfs_lock); + sd->s_dentry = dentry; + spin_unlock(&sysfs_lock); + + d_rehash(dentry); +} + void sysfs_attach_dirent(struct sysfs_dirent *sd, struct sysfs_dirent *parent_sd, struct dentry *dentry) { - if (dentry) { - sd->s_dentry = dentry; - dentry->d_fsdata = sysfs_get(sd); - dentry->d_op = &sysfs_dentry_ops; - } + if (dentry) + sysfs_attach_dentry(sd, dentry); if (parent_sd) { sd->s_parent = sysfs_get(parent_sd); @@ -229,15 +239,13 @@ static int create_dir(struct kobject *kobj, struct dentry *parent, if (!sd) goto out_drop; sd->s_elem.dir.kobj = kobj; - sysfs_attach_dirent(sd, parent->d_fsdata, dentry); - error = sysfs_create(dentry, mode, init_dir); + error = sysfs_create(sd, dentry, mode, init_dir); if (error) goto out_sput; inc_nlink(parent->d_inode); - dentry->d_op = &sysfs_dentry_ops; - d_rehash(dentry); + sysfs_attach_dirent(sd, parent->d_fsdata, dentry); *p_dentry = dentry; error = 0; @@ -308,42 +316,28 @@ static int sysfs_attach_attr(struct sysfs_dirent * sd, struct dentry * dentry) init = init_file; } - dentry->d_fsdata = sysfs_get(sd); - /* protect sd->s_dentry against sysfs_d_iput */ - spin_lock(&sysfs_lock); - sd->s_dentry = dentry; - spin_unlock(&sysfs_lock); - error = sysfs_create(dentry, (attr->mode & S_IALLUGO) | S_IFREG, init); - if (error) { - sysfs_put(sd); + error = sysfs_create(sd, dentry, + (attr->mode & S_IALLUGO) | S_IFREG, init); + if (error) return error; - } if (bin_attr) { dentry->d_inode->i_size = bin_attr->size; dentry->d_inode->i_fop = &bin_fops; } - dentry->d_op = &sysfs_dentry_ops; - d_rehash(dentry); + + sysfs_attach_dentry(sd, dentry); return 0; } static int sysfs_attach_link(struct sysfs_dirent * sd, struct dentry * dentry) { - int err = 0; + int err; - dentry->d_fsdata = sysfs_get(sd); - /* protect sd->s_dentry against sysfs_d_iput */ - spin_lock(&sysfs_lock); - sd->s_dentry = dentry; - spin_unlock(&sysfs_lock); - err = sysfs_create(dentry, S_IFLNK|S_IRWXUGO, init_symlink); - if (!err) { - dentry->d_op = &sysfs_dentry_ops; - d_rehash(dentry); - } else - sysfs_put(sd); + err = sysfs_create(sd, dentry, S_IFLNK|S_IRWXUGO, init_symlink); + if (!err) + sysfs_attach_dentry(sd, dentry); return err; } @@ -773,7 +767,6 @@ struct dentry *sysfs_create_shadow_dir(struct kobject *kobj) d_instantiate(shadow, igrab(inode)); inc_nlink(inode); inc_nlink(parent->d_inode); - shadow->d_op = &sysfs_dentry_ops; dget(shadow); /* Extra count - pin the dentry in core */ -- cgit v1.1 From dbde0fcf9f8f6d477af3c32d9979e789ee680cde Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 14 Jun 2007 03:45:16 +0900 Subject: sysfs: reimplement sysfs_drop_dentry() This patch reimplements sysfs_drop_dentry() such that remove_dir() can use it to drop dentry instead of using a separate mechanism. With this change, making directories reclaimable is much easier. This patch used to contain fixes for two race conditions around sd->s_dentry but that part has been separated out and included into mainline early as commit 6aa054aadfea613a437ad0b15d38eca2b963fc0a and dd14cbc994709a1c5a64ed3621f583c49a27e521. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) (limited to 'fs/sysfs/dir.c') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index 01eeb4b..bc11a26 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -372,22 +372,19 @@ const struct inode_operations sysfs_dir_inode_operations = { static void remove_dir(struct dentry * d) { - struct dentry * parent = dget(d->d_parent); - struct sysfs_dirent * sd; + struct dentry *parent = d->d_parent; + struct sysfs_dirent *sd = d->d_fsdata; mutex_lock(&parent->d_inode->i_mutex); - d_delete(d); - sd = d->d_fsdata; + list_del_init(&sd->s_sibling); - if (d->d_inode) - simple_rmdir(parent->d_inode,d); pr_debug(" o %s removing done (%d)\n",d->d_name.name, atomic_read(&d->d_count)); mutex_unlock(&parent->d_inode->i_mutex); - dput(parent); + sysfs_drop_dentry(sd); sysfs_deactivate(sd); sysfs_put(sd); } @@ -404,7 +401,6 @@ static void __sysfs_remove_dir(struct dentry *dentry) struct sysfs_dirent * parent_sd; struct sysfs_dirent * sd, * tmp; - dget(dentry); if (!dentry) return; @@ -415,21 +411,17 @@ static void __sysfs_remove_dir(struct dentry *dentry) if (!sd->s_type || !(sd->s_type & SYSFS_NOT_PINNED)) continue; list_move(&sd->s_sibling, &removed); - sysfs_drop_dentry(sd, dentry); } mutex_unlock(&dentry->d_inode->i_mutex); list_for_each_entry_safe(sd, tmp, &removed, s_sibling) { list_del_init(&sd->s_sibling); + sysfs_drop_dentry(sd); sysfs_deactivate(sd); sysfs_put(sd); } remove_dir(dentry); - /** - * Drop reference from dget() on entrance. - */ - dput(dentry); } /** -- cgit v1.1 From 42b37df6abb42ae021e15bf865b43f3629c7f3ab Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 14 Jun 2007 03:45:17 +0900 Subject: sysfs: make sysfs_alloc_ino() static sysfs_alloc_ino() isn't used out side of fs/sysfs/dir.c. Make it static. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'fs/sysfs/dir.c') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index bc11a26..a63d12e 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -20,7 +20,7 @@ spinlock_t kobj_sysfs_assoc_lock = SPIN_LOCK_UNLOCKED; static spinlock_t sysfs_ino_lock = SPIN_LOCK_UNLOCKED; static DEFINE_IDA(sysfs_ino_ida); -int sysfs_alloc_ino(ino_t *pino) +static int sysfs_alloc_ino(ino_t *pino) { int ino, rc; -- cgit v1.1 From 7f7cfffe60ed6271c4028ec79ae1c297b44bcb14 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 14 Jun 2007 03:45:17 +0900 Subject: sysfs: fix parent refcounting during rename and move Parent reference wasn't properly transferred during rename and move. Fix it. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'fs/sysfs/dir.c') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index a63d12e..a26e3db 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -497,6 +497,9 @@ int sysfs_rename_dir(struct kobject * kobj, struct dentry *new_parent, d_move(kobj->dentry, new_dentry); list_del_init(&sd->s_sibling); + sysfs_get(parent_sd); + sysfs_put(sd->s_parent); + sd->s_parent = parent_sd; list_add(&sd->s_sibling, &parent_sd->s_children); error = 0; @@ -550,6 +553,9 @@ again: /* Remove from old parent's list and insert into new parent's list. */ list_del_init(&sd->s_sibling); + sysfs_get(new_parent_sd); + sysfs_put(sd->s_parent); + sd->s_parent = new_parent_sd; list_add(&sd->s_sibling, &new_parent_sd->s_children); out: -- cgit v1.1 From fc9f54b9982e14e6dbe023425c87ffbfd6992c45 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 14 Jun 2007 03:45:17 +0900 Subject: sysfs: reorganize sysfs_new_indoe() and sysfs_create() Reorganize/clean up sysfs_new_inode() and sysfs_create(). * sysfs_init_inode() is separated out from sysfs_new_inode() and is responsible for basic initialization. * sysfs_instantiate() replaces the last step of sysfs_create() and is responsible for dentry instantitaion. * type-specific initialization is moved out to the callers. * mode is specified only once when creating a sysfs_dirent. * spurious list_del_init(&sd->s_sibling) dropped from create_dir() This change is to * prepare for inode allocation fix. * separate alloc and init code for synchronization update. * make dentry/inode initialization more flexible for later changes. This patch doesn't introduce visible behavior change. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 130 +++++++++++++++++++++------------------------------------ 1 file changed, 48 insertions(+), 82 deletions(-) (limited to 'fs/sysfs/dir.c') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index a26e3db..bbf3525 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -191,39 +191,18 @@ int sysfs_dirent_exist(struct sysfs_dirent *parent_sd, return 0; } -static int init_dir(struct inode * inode) -{ - inode->i_op = &sysfs_dir_inode_operations; - inode->i_fop = &sysfs_dir_operations; - - /* directory inodes start off with i_nlink == 2 (for "." entry) */ - inc_nlink(inode); - return 0; -} - -static int init_file(struct inode * inode) -{ - inode->i_size = PAGE_SIZE; - inode->i_fop = &sysfs_file_operations; - return 0; -} - -static int init_symlink(struct inode * inode) -{ - inode->i_op = &sysfs_symlink_inode_operations; - return 0; -} - static int create_dir(struct kobject *kobj, struct dentry *parent, const char *name, struct dentry **p_dentry) { 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); + /* allocate */ dentry = lookup_one_len(name, parent, strlen(name)); if (IS_ERR(dentry)) { error = PTR_ERR(dentry); @@ -231,7 +210,7 @@ static int create_dir(struct kobject *kobj, struct dentry *parent, } error = -EEXIST; - if (sysfs_dirent_exist(parent->d_fsdata, name)) + if (dentry->d_inode) goto out_dput; error = -ENOMEM; @@ -240,19 +219,31 @@ static int create_dir(struct kobject *kobj, struct dentry *parent, goto out_drop; sd->s_elem.dir.kobj = kobj; - error = sysfs_create(sd, dentry, mode, init_dir); - if (error) + inode = sysfs_new_inode(sd); + if (!inode) goto out_sput; + inode->i_op = &sysfs_dir_inode_operations; + inode->i_fop = &sysfs_dir_operations; + /* directory inodes start off with i_nlink == 2 (for "." entry) */ + inc_nlink(inode); + + /* link in */ + error = -EEXIST; + if (sysfs_dirent_exist(parent->d_fsdata, name)) + goto out_iput; + + sysfs_instantiate(dentry, inode); inc_nlink(parent->d_inode); sysfs_attach_dirent(sd, parent->d_fsdata, dentry); *p_dentry = dentry; error = 0; - goto out_dput; + goto out_unlock; /* pin directory dentry in core */ + out_iput: + iput(inode); out_sput: - list_del_init(&sd->s_sibling); sysfs_put(sd); out_drop: d_drop(dentry); @@ -298,71 +289,46 @@ int sysfs_create_dir(struct kobject * kobj, struct dentry *shadow_parent) return error; } -/* attaches attribute's sysfs_dirent to the dentry corresponding to the - * attribute file - */ -static int sysfs_attach_attr(struct sysfs_dirent * sd, struct dentry * dentry) -{ - struct attribute * attr = NULL; - struct bin_attribute * bin_attr = NULL; - int (* init) (struct inode *) = NULL; - int error = 0; - - if (sd->s_type & SYSFS_KOBJ_BIN_ATTR) { - bin_attr = sd->s_elem.bin_attr.bin_attr; - attr = &bin_attr->attr; - } else { - attr = sd->s_elem.attr.attr; - init = init_file; - } - - error = sysfs_create(sd, dentry, - (attr->mode & S_IALLUGO) | S_IFREG, init); - if (error) - return error; - - if (bin_attr) { - dentry->d_inode->i_size = bin_attr->size; - dentry->d_inode->i_fop = &bin_fops; - } - - sysfs_attach_dentry(sd, dentry); - - return 0; -} - -static int sysfs_attach_link(struct sysfs_dirent * sd, struct dentry * dentry) -{ - int err; - - err = sysfs_create(sd, dentry, S_IFLNK|S_IRWXUGO, init_symlink); - if (!err) - sysfs_attach_dentry(sd, dentry); - - return err; -} - static struct dentry * sysfs_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd) { struct sysfs_dirent * parent_sd = dentry->d_parent->d_fsdata; struct sysfs_dirent * sd; - int err = 0; + struct inode *inode; + int found = 0; list_for_each_entry(sd, &parent_sd->s_children, s_sibling) { - if (sd->s_type & SYSFS_NOT_PINNED) { - if (strcmp(sd->s_name, dentry->d_name.name)) - continue; - - if (sd->s_type & SYSFS_KOBJ_LINK) - err = sysfs_attach_link(sd, dentry); - else - err = sysfs_attach_attr(sd, dentry); + if ((sd->s_type & SYSFS_NOT_PINNED) && + !strcmp(sd->s_name, dentry->d_name.name)) { + found = 1; break; } } - return ERR_PTR(err); + /* no such entry */ + if (!found) + return NULL; + + /* attach dentry and inode */ + inode = sysfs_new_inode(sd); + if (!inode) + return ERR_PTR(-ENOMEM); + + /* initialize inode according to type */ + if (sd->s_type & SYSFS_KOBJ_ATTR) { + inode->i_size = PAGE_SIZE; + inode->i_fop = &sysfs_file_operations; + } else if (sd->s_type & SYSFS_KOBJ_BIN_ATTR) { + struct bin_attribute *bin_attr = sd->s_elem.bin_attr.bin_attr; + inode->i_size = bin_attr->size; + inode->i_fop = &bin_fops; + } else if (sd->s_type & SYSFS_KOBJ_LINK) + inode->i_op = &sysfs_symlink_inode_operations; + + sysfs_instantiate(dentry, inode); + sysfs_attach_dentry(sd, dentry); + + return NULL; } const struct inode_operations sysfs_dir_inode_operations = { -- cgit v1.1 From 8312a8d7c1d19d31027bd4ca127ce671962c23d4 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 14 Jun 2007 03:45:17 +0900 Subject: sysfs: use iget_locked() instead of new_inode() After dentry is reclaimed, sysfs always used to allocate new dentry and inode if the file is accessed again. This causes problem with operations which only pin the inode. For example, if inotify watch is added to a sysfs file and the dentry for the file is reclaimed, the next update event creates new dentry and new inode making the inotify watch miss all the events from there on. This patch fixes it by using iget_locked() instead of new_inode(). sysfs_new_inode() is renamed to sysfs_get_inode() and inode is initialized iff the inode is newly allocated. sysfs_instantiate() is responsible for unlocking new inodes. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) (limited to 'fs/sysfs/dir.c') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index bbf3525..06dff2c3 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -219,14 +219,16 @@ static int create_dir(struct kobject *kobj, struct dentry *parent, goto out_drop; sd->s_elem.dir.kobj = kobj; - inode = sysfs_new_inode(sd); + inode = sysfs_get_inode(sd); if (!inode) goto out_sput; - inode->i_op = &sysfs_dir_inode_operations; - inode->i_fop = &sysfs_dir_operations; - /* directory inodes start off with i_nlink == 2 (for "." entry) */ - inc_nlink(inode); + if (inode->i_state & I_NEW) { + inode->i_op = &sysfs_dir_inode_operations; + inode->i_fop = &sysfs_dir_operations; + /* directory inodes start off with i_nlink == 2 (for ".") */ + inc_nlink(inode); + } /* link in */ error = -EEXIST; @@ -310,20 +312,23 @@ static struct dentry * sysfs_lookup(struct inode *dir, struct dentry *dentry, return NULL; /* attach dentry and inode */ - inode = sysfs_new_inode(sd); + inode = sysfs_get_inode(sd); if (!inode) return ERR_PTR(-ENOMEM); - /* initialize inode according to type */ - if (sd->s_type & SYSFS_KOBJ_ATTR) { - inode->i_size = PAGE_SIZE; - inode->i_fop = &sysfs_file_operations; - } else if (sd->s_type & SYSFS_KOBJ_BIN_ATTR) { - struct bin_attribute *bin_attr = sd->s_elem.bin_attr.bin_attr; - inode->i_size = bin_attr->size; - inode->i_fop = &bin_fops; - } else if (sd->s_type & SYSFS_KOBJ_LINK) - inode->i_op = &sysfs_symlink_inode_operations; + if (inode->i_state & I_NEW) { + /* initialize inode according to type */ + if (sd->s_type & SYSFS_KOBJ_ATTR) { + inode->i_size = PAGE_SIZE; + inode->i_fop = &sysfs_file_operations; + } else if (sd->s_type & SYSFS_KOBJ_BIN_ATTR) { + struct bin_attribute *bin_attr = + sd->s_elem.bin_attr.bin_attr; + inode->i_size = bin_attr->size; + inode->i_fop = &bin_fops; + } else if (sd->s_type & SYSFS_KOBJ_LINK) + inode->i_op = &sysfs_symlink_inode_operations; + } sysfs_instantiate(dentry, inode); sysfs_attach_dentry(sd, dentry); -- cgit v1.1 From b6b4a4399c2a83d1af77c99dee0d0b5cc15ec268 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 14 Jun 2007 03:45:18 +0900 Subject: sysfs: move s_active functions to fs/sysfs/dir.c These functions are about to receive more complexity and doesn't really need to be inlined in the first place. Move them from fs/sysfs/sysfs.h to fs/sysfs/dir.c. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) (limited to 'fs/sysfs/dir.c') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index 06dff2c3..f5f0b93 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -20,6 +20,94 @@ spinlock_t kobj_sysfs_assoc_lock = SPIN_LOCK_UNLOCKED; static spinlock_t sysfs_ino_lock = SPIN_LOCK_UNLOCKED; static DEFINE_IDA(sysfs_ino_ida); +/** + * sysfs_get_active - get an active reference to sysfs_dirent + * @sd: sysfs_dirent to get an active reference to + * + * Get an active reference of @sd. This function is noop if @sd + * is NULL. + * + * RETURNS: + * Pointer to @sd on success, NULL on failure. + */ +struct sysfs_dirent *sysfs_get_active(struct sysfs_dirent *sd) +{ + if (sd) { + if (unlikely(!down_read_trylock(&sd->s_active))) + sd = NULL; + } + return sd; +} + +/** + * sysfs_put_active - put an active reference to sysfs_dirent + * @sd: sysfs_dirent to put an active reference to + * + * Put an active reference to @sd. This function is noop if @sd + * is NULL. + */ +void sysfs_put_active(struct sysfs_dirent *sd) +{ + if (sd) + up_read(&sd->s_active); +} + +/** + * sysfs_get_active_two - get active references to sysfs_dirent and parent + * @sd: sysfs_dirent of interest + * + * Get active reference to @sd and its parent. Parent's active + * reference is grabbed first. This function is noop if @sd is + * NULL. + * + * RETURNS: + * Pointer to @sd on success, NULL on failure. + */ +struct sysfs_dirent *sysfs_get_active_two(struct sysfs_dirent *sd) +{ + if (sd) { + if (sd->s_parent && unlikely(!sysfs_get_active(sd->s_parent))) + return NULL; + if (unlikely(!sysfs_get_active(sd))) { + sysfs_put_active(sd->s_parent); + return NULL; + } + } + return sd; +} + +/** + * sysfs_put_active_two - put active references to sysfs_dirent and parent + * @sd: sysfs_dirent of interest + * + * Put active references to @sd and its parent. This function is + * noop if @sd is NULL. + */ +void sysfs_put_active_two(struct sysfs_dirent *sd) +{ + if (sd) { + sysfs_put_active(sd); + sysfs_put_active(sd->s_parent); + } +} + +/** + * sysfs_deactivate - deactivate sysfs_dirent + * @sd: sysfs_dirent to deactivate + * + * Deny new active references and drain existing ones. s_active + * will be unlocked when the sysfs_dirent is released. + */ +void sysfs_deactivate(struct sysfs_dirent *sd) +{ + down_write_nested(&sd->s_active, SYSFS_S_ACTIVE_DEACTIVATE); + + /* s_active will be unlocked by the thread doing the final put + * on @sd. Lie to lockdep. + */ + rwsem_release(&sd->s_active.dep_map, 1, _RET_IP_); +} + static int sysfs_alloc_ino(ino_t *pino) { int ino, rc; -- cgit v1.1 From 8619f979898397582e366877fd5feeba7560d70c Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 14 Jun 2007 03:45:18 +0900 Subject: sysfs: slim down sysfs_dirent->s_active Make sysfs_dirent->s_active an atomic_t instead of rwsem. This reduces the size of sysfs_dirent from 136 to 104 on 64bit and from 76 to 60 on 32bit with lock debugging turned off. With lock debugging turned on the reduction is much larger. s_active starts at zero and each active reference increments s_active. Putting a reference decrements s_active. Deactivation subtracts SD_DEACTIVATED_BIAS which is currently INT_MIN and assumed to be small enough to make s_active negative. If s_active is negative, sysfs_get() no longer grants new references. Deactivation succeeds immediately if there is no active user; otherwise, it waits using a completion for the last put. Due to the removal of lockdep tricks, this change makes things less trickier in release_sysfs_dirent(). As all the complexity is contained in three s_active functions, I think it's more readable this way. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 74 +++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 48 insertions(+), 26 deletions(-) (limited to 'fs/sysfs/dir.c') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index f5f0b93..40596a0 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include "sysfs.h" @@ -32,11 +33,24 @@ static DEFINE_IDA(sysfs_ino_ida); */ struct sysfs_dirent *sysfs_get_active(struct sysfs_dirent *sd) { - if (sd) { - if (unlikely(!down_read_trylock(&sd->s_active))) - sd = NULL; + if (unlikely(!sd)) + return NULL; + + while (1) { + int v, t; + + v = atomic_read(&sd->s_active); + if (unlikely(v < 0)) + return NULL; + + t = atomic_cmpxchg(&sd->s_active, v, v + 1); + if (likely(t == v)) + return sd; + if (t < 0) + return NULL; + + cpu_relax(); } - return sd; } /** @@ -48,8 +62,21 @@ struct sysfs_dirent *sysfs_get_active(struct sysfs_dirent *sd) */ void sysfs_put_active(struct sysfs_dirent *sd) { - if (sd) - up_read(&sd->s_active); + struct completion *cmpl; + int v; + + if (unlikely(!sd)) + return; + + v = atomic_dec_return(&sd->s_active); + if (likely(v != SD_DEACTIVATED_BIAS)) + return; + + /* atomic_dec_return() is a mb(), we'll always see the updated + * sd->s_sibling.next. + */ + cmpl = (void *)sd->s_sibling.next; + complete(cmpl); } /** @@ -95,17 +122,25 @@ void sysfs_put_active_two(struct sysfs_dirent *sd) * sysfs_deactivate - deactivate sysfs_dirent * @sd: sysfs_dirent to deactivate * - * Deny new active references and drain existing ones. s_active - * will be unlocked when the sysfs_dirent is released. + * Deny new active references and drain existing ones. */ void sysfs_deactivate(struct sysfs_dirent *sd) { - down_write_nested(&sd->s_active, SYSFS_S_ACTIVE_DEACTIVATE); + DECLARE_COMPLETION_ONSTACK(wait); + int v; + + BUG_ON(!list_empty(&sd->s_sibling)); + sd->s_sibling.next = (void *)&wait; - /* s_active will be unlocked by the thread doing the final put - * on @sd. Lie to lockdep. + /* atomic_add_return() is a mb(), put_active() will always see + * the updated sd->s_sibling.next. */ - rwsem_release(&sd->s_active.dep_map, 1, _RET_IP_); + v = atomic_add_return(SD_DEACTIVATED_BIAS, &sd->s_active); + + if (v != SD_DEACTIVATED_BIAS) + wait_for_completion(&wait); + + INIT_LIST_HEAD(&sd->s_sibling); } static int sysfs_alloc_ino(ino_t *pino) @@ -141,19 +176,6 @@ void release_sysfs_dirent(struct sysfs_dirent * sd) repeat: parent_sd = sd->s_parent; - /* If @sd is being released after deletion, s_active is write - * locked. If @sd is cursor for directory walk or being - * released prematurely, s_active has no reader or writer. - * - * sysfs_deactivate() lies to lockdep that s_active is - * unlocked immediately. Lie one more time to cover the - * previous lie. - */ - if (!down_write_trylock(&sd->s_active)) - rwsem_acquire(&sd->s_active.dep_map, - SYSFS_S_ACTIVE_DEACTIVATE, 0, _RET_IP_); - up_write(&sd->s_active); - if (sd->s_type & SYSFS_KOBJ_LINK) sysfs_put(sd->s_elem.symlink.target_sd); if (sd->s_type & SYSFS_COPY_NAME) @@ -213,8 +235,8 @@ struct sysfs_dirent *sysfs_new_dirent(const char *name, umode_t mode, int type) goto err_out; atomic_set(&sd->s_count, 1); + atomic_set(&sd->s_active, 0); atomic_set(&sd->s_event, 1); - init_rwsem(&sd->s_active); INIT_LIST_HEAD(&sd->s_children); INIT_LIST_HEAD(&sd->s_sibling); -- cgit v1.1 From 0c73f18b7d95de8a007039337063a770b5fc8e7a Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 14 Jun 2007 03:45:18 +0900 Subject: sysfs: use singly-linked list for sysfs_dirent tree Make sysfs_dirent use singly linked list for its tree structure. sysfs_link_sibling() and sysfs_unlink_sibling() functions are added to handle simpler cases. It adds some complexity and cpu cycle overhead but reduced memory footprint is worthwhile on big machines. This change reduces the sizeof sysfs_dirent from 104 to 88 on 64bit and from 60 to 52 on 32bit. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 146 ++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 102 insertions(+), 44 deletions(-) (limited to 'fs/sysfs/dir.c') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index 40596a0..b4074ad 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -22,6 +22,48 @@ static spinlock_t sysfs_ino_lock = SPIN_LOCK_UNLOCKED; static DEFINE_IDA(sysfs_ino_ida); /** + * sysfs_link_sibling - link sysfs_dirent into sibling list + * @sd: sysfs_dirent of interest + * + * Link @sd into its sibling list which starts from + * sd->s_parent->s_children. + * + * Locking: + * mutex_lock(sd->s_parent->dentry->d_inode->i_mutex) + */ +static void sysfs_link_sibling(struct sysfs_dirent *sd) +{ + struct sysfs_dirent *parent_sd = sd->s_parent; + + BUG_ON(sd->s_sibling); + sd->s_sibling = parent_sd->s_children; + parent_sd->s_children = sd; +} + +/** + * sysfs_unlink_sibling - unlink sysfs_dirent from sibling list + * @sd: sysfs_dirent of interest + * + * Unlink @sd from its sibling list which starts from + * sd->s_parent->s_children. + * + * Locking: + * mutex_lock(sd->s_parent->dentry->d_inode->i_mutex) + */ +static void sysfs_unlink_sibling(struct sysfs_dirent *sd) +{ + struct sysfs_dirent **pos; + + for (pos = &sd->s_parent->s_children; *pos; pos = &(*pos)->s_sibling) { + if (*pos == sd) { + *pos = sd->s_sibling; + sd->s_sibling = NULL; + break; + } + } +} + +/** * sysfs_get_active - get an active reference to sysfs_dirent * @sd: sysfs_dirent to get an active reference to * @@ -73,9 +115,9 @@ void sysfs_put_active(struct sysfs_dirent *sd) return; /* atomic_dec_return() is a mb(), we'll always see the updated - * sd->s_sibling.next. + * sd->s_sibling. */ - cmpl = (void *)sd->s_sibling.next; + cmpl = (void *)sd->s_sibling; complete(cmpl); } @@ -129,18 +171,18 @@ void sysfs_deactivate(struct sysfs_dirent *sd) DECLARE_COMPLETION_ONSTACK(wait); int v; - BUG_ON(!list_empty(&sd->s_sibling)); - sd->s_sibling.next = (void *)&wait; + BUG_ON(sd->s_sibling); + sd->s_sibling = (void *)&wait; /* atomic_add_return() is a mb(), put_active() will always see - * the updated sd->s_sibling.next. + * the updated sd->s_sibling. */ v = atomic_add_return(SD_DEACTIVATED_BIAS, &sd->s_active); if (v != SD_DEACTIVATED_BIAS) wait_for_completion(&wait); - INIT_LIST_HEAD(&sd->s_sibling); + sd->s_sibling = NULL; } static int sysfs_alloc_ino(ino_t *pino) @@ -237,8 +279,6 @@ struct sysfs_dirent *sysfs_new_dirent(const char *name, umode_t mode, int type) atomic_set(&sd->s_count, 1); atomic_set(&sd->s_active, 0); atomic_set(&sd->s_event, 1); - INIT_LIST_HEAD(&sd->s_children); - INIT_LIST_HEAD(&sd->s_sibling); sd->s_name = name; sd->s_mode = mode; @@ -273,7 +313,7 @@ void sysfs_attach_dirent(struct sysfs_dirent *sd, if (parent_sd) { sd->s_parent = sysfs_get(parent_sd); - list_add(&sd->s_sibling, &parent_sd->s_children); + sysfs_link_sibling(sd); } } @@ -289,7 +329,7 @@ int sysfs_dirent_exist(struct sysfs_dirent *parent_sd, { struct sysfs_dirent * sd; - list_for_each_entry(sd, &parent_sd->s_children, s_sibling) { + for (sd = parent_sd->s_children; sd; sd = sd->s_sibling) { if (sd->s_type) { if (strcmp(sd->s_name, new)) continue; @@ -409,7 +449,7 @@ static struct dentry * sysfs_lookup(struct inode *dir, struct dentry *dentry, struct inode *inode; int found = 0; - list_for_each_entry(sd, &parent_sd->s_children, s_sibling) { + for (sd = parent_sd->s_children; sd; sd = sd->s_sibling) { if ((sd->s_type & SYSFS_NOT_PINNED) && !strcmp(sd->s_name, dentry->d_name.name)) { found = 1; @@ -458,7 +498,7 @@ static void remove_dir(struct dentry * d) mutex_lock(&parent->d_inode->i_mutex); - list_del_init(&sd->s_sibling); + sysfs_unlink_sibling(sd); pr_debug(" o %s removing done (%d)\n",d->d_name.name, atomic_read(&d->d_count)); @@ -478,9 +518,9 @@ void sysfs_remove_subdir(struct dentry * d) static void __sysfs_remove_dir(struct dentry *dentry) { - LIST_HEAD(removed); - struct sysfs_dirent * parent_sd; - struct sysfs_dirent * sd, * tmp; + struct sysfs_dirent *removed = NULL; + struct sysfs_dirent *parent_sd; + struct sysfs_dirent **pos; if (!dentry) return; @@ -488,15 +528,25 @@ static void __sysfs_remove_dir(struct dentry *dentry) pr_debug("sysfs %s: removing dir\n",dentry->d_name.name); mutex_lock(&dentry->d_inode->i_mutex); parent_sd = dentry->d_fsdata; - list_for_each_entry_safe(sd, tmp, &parent_sd->s_children, s_sibling) { - if (!sd->s_type || !(sd->s_type & SYSFS_NOT_PINNED)) - continue; - list_move(&sd->s_sibling, &removed); + pos = &parent_sd->s_children; + while (*pos) { + struct sysfs_dirent *sd = *pos; + + if (sd->s_type && (sd->s_type & SYSFS_NOT_PINNED)) { + *pos = sd->s_sibling; + sd->s_sibling = removed; + removed = sd; + } else + pos = &(*pos)->s_sibling; } mutex_unlock(&dentry->d_inode->i_mutex); - list_for_each_entry_safe(sd, tmp, &removed, s_sibling) { - list_del_init(&sd->s_sibling); + 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); @@ -577,11 +627,11 @@ int sysfs_rename_dir(struct kobject * kobj, struct dentry *new_parent, d_add(new_dentry, NULL); d_move(kobj->dentry, new_dentry); - list_del_init(&sd->s_sibling); + sysfs_unlink_sibling(sd); sysfs_get(parent_sd); sysfs_put(sd->s_parent); sd->s_parent = parent_sd; - list_add(&sd->s_sibling, &parent_sd->s_children); + sysfs_link_sibling(sd); error = 0; goto out_unlock; @@ -633,11 +683,11 @@ again: dput(new_dentry); /* Remove from old parent's list and insert into new parent's list. */ - list_del_init(&sd->s_sibling); + sysfs_unlink_sibling(sd); sysfs_get(new_parent_sd); sysfs_put(sd->s_parent); sd->s_parent = new_parent_sd; - list_add(&sd->s_sibling, &new_parent_sd->s_children); + sysfs_link_sibling(sd); out: mutex_unlock(&new_parent_dentry->d_inode->i_mutex); @@ -668,7 +718,7 @@ static int sysfs_dir_close(struct inode *inode, struct file *file) struct sysfs_dirent * cursor = file->private_data; mutex_lock(&dentry->d_inode->i_mutex); - list_del_init(&cursor->s_sibling); + sysfs_unlink_sibling(cursor); mutex_unlock(&dentry->d_inode->i_mutex); release_sysfs_dirent(cursor); @@ -687,7 +737,7 @@ static int sysfs_readdir(struct file * filp, void * dirent, filldir_t filldir) struct dentry *dentry = filp->f_path.dentry; struct sysfs_dirent * parent_sd = dentry->d_fsdata; struct sysfs_dirent *cursor = filp->private_data; - struct list_head *p, *q = &cursor->s_sibling; + struct sysfs_dirent **pos; ino_t ino; int i = filp->f_pos; @@ -710,16 +760,21 @@ static int sysfs_readdir(struct file * filp, void * dirent, filldir_t filldir) i++; /* fallthrough */ default: + pos = &parent_sd->s_children; + while (*pos != cursor) + pos = &(*pos)->s_sibling; + + /* unlink cursor */ + *pos = cursor->s_sibling; + if (filp->f_pos == 2) - list_move(q, &parent_sd->s_children); + pos = &parent_sd->s_children; - for (p=q->next; p!= &parent_sd->s_children; p=p->next) { - struct sysfs_dirent *next; + for ( ; *pos; pos = &(*pos)->s_sibling) { + struct sysfs_dirent *next = *pos; const char * name; int len; - next = list_entry(p, struct sysfs_dirent, - s_sibling); if (!next->s_type) continue; @@ -729,12 +784,14 @@ static int sysfs_readdir(struct file * filp, void * dirent, filldir_t filldir) if (filldir(dirent, name, len, filp->f_pos, ino, dt_type(next)) < 0) - return 0; + break; - list_move(q, p); - p = q; filp->f_pos++; } + + /* put cursor back in */ + cursor->s_sibling = *pos; + *pos = cursor; } return 0; } @@ -759,20 +816,21 @@ static loff_t sysfs_dir_lseek(struct file * file, loff_t offset, int origin) if (file->f_pos >= 2) { struct sysfs_dirent *sd = dentry->d_fsdata; struct sysfs_dirent *cursor = file->private_data; - struct list_head *p; + struct sysfs_dirent **pos; loff_t n = file->f_pos - 2; - list_del(&cursor->s_sibling); - p = sd->s_children.next; - while (n && p != &sd->s_children) { - struct sysfs_dirent *next; - next = list_entry(p, struct sysfs_dirent, - s_sibling); + sysfs_unlink_sibling(cursor); + + pos = &sd->s_children; + while (n && *pos) { + struct sysfs_dirent *next = *pos; if (next->s_type) n--; - p = p->next; + pos = &(*pos)->s_sibling; } - list_add_tail(&cursor->s_sibling, p); + + cursor->s_sibling = *pos; + *pos = cursor; } } mutex_unlock(&dentry->d_inode->i_mutex); -- cgit v1.1 From b402d72cf7b338a074e3c12b305ec79284e18845 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 14 Jun 2007 04:27:21 +0900 Subject: sysfs: rename sysfs_dirent->s_type to s_flags and make room for flags Rename sysfs_dirent->s_type to s_flags, pack type into lower eight bits and reserve the rest for flags. sysfs_type() can used to access the type. All existing sd->s_type accesses are converted to use sysfs_type(). While at it, type test is changed to equality test instead of bit-and test where appropriate. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) (limited to 'fs/sysfs/dir.c') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index b4074ad..eb9bc0a 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -218,9 +218,9 @@ void release_sysfs_dirent(struct sysfs_dirent * sd) repeat: parent_sd = sd->s_parent; - if (sd->s_type & SYSFS_KOBJ_LINK) + if (sysfs_type(sd) == SYSFS_KOBJ_LINK) sysfs_put(sd->s_elem.symlink.target_sd); - if (sd->s_type & SYSFS_COPY_NAME) + if (sysfs_type(sd) & SYSFS_COPY_NAME) kfree(sd->s_name); kfree(sd->s_iattr); sysfs_free_ino(sd->s_ino); @@ -282,7 +282,7 @@ struct sysfs_dirent *sysfs_new_dirent(const char *name, umode_t mode, int type) sd->s_name = name; sd->s_mode = mode; - sd->s_type = type; + sd->s_flags = type; return sd; @@ -330,7 +330,7 @@ int sysfs_dirent_exist(struct sysfs_dirent *parent_sd, struct sysfs_dirent * sd; for (sd = parent_sd->s_children; sd; sd = sd->s_sibling) { - if (sd->s_type) { + if (sysfs_type(sd)) { if (strcmp(sd->s_name, new)) continue; else @@ -446,11 +446,12 @@ static struct dentry * sysfs_lookup(struct inode *dir, struct dentry *dentry, { struct sysfs_dirent * parent_sd = dentry->d_parent->d_fsdata; struct sysfs_dirent * sd; + struct bin_attribute *bin_attr; struct inode *inode; int found = 0; for (sd = parent_sd->s_children; sd; sd = sd->s_sibling) { - if ((sd->s_type & SYSFS_NOT_PINNED) && + if ((sysfs_type(sd) & SYSFS_NOT_PINNED) && !strcmp(sd->s_name, dentry->d_name.name)) { found = 1; break; @@ -468,16 +469,22 @@ static struct dentry * sysfs_lookup(struct inode *dir, struct dentry *dentry, if (inode->i_state & I_NEW) { /* initialize inode according to type */ - if (sd->s_type & SYSFS_KOBJ_ATTR) { + switch (sysfs_type(sd)) { + case SYSFS_KOBJ_ATTR: inode->i_size = PAGE_SIZE; inode->i_fop = &sysfs_file_operations; - } else if (sd->s_type & SYSFS_KOBJ_BIN_ATTR) { - struct bin_attribute *bin_attr = - sd->s_elem.bin_attr.bin_attr; + break; + case SYSFS_KOBJ_BIN_ATTR: + bin_attr = sd->s_elem.bin_attr.bin_attr; inode->i_size = bin_attr->size; inode->i_fop = &bin_fops; - } else if (sd->s_type & SYSFS_KOBJ_LINK) + break; + case SYSFS_KOBJ_LINK: inode->i_op = &sysfs_symlink_inode_operations; + break; + default: + BUG(); + } } sysfs_instantiate(dentry, inode); @@ -532,7 +539,7 @@ static void __sysfs_remove_dir(struct dentry *dentry) while (*pos) { struct sysfs_dirent *sd = *pos; - if (sd->s_type && (sd->s_type & SYSFS_NOT_PINNED)) { + if (sysfs_type(sd) && (sysfs_type(sd) & SYSFS_NOT_PINNED)) { *pos = sd->s_sibling; sd->s_sibling = removed; removed = sd; @@ -775,7 +782,7 @@ static int sysfs_readdir(struct file * filp, void * dirent, filldir_t filldir) const char * name; int len; - if (!next->s_type) + if (!sysfs_type(next)) continue; name = next->s_name; @@ -824,7 +831,7 @@ static loff_t sysfs_dir_lseek(struct file * file, loff_t offset, int origin) pos = &sd->s_children; while (n && *pos) { struct sysfs_dirent *next = *pos; - if (next->s_type) + if (sysfs_type(next)) n--; pos = &(*pos)->s_sibling; } -- cgit v1.1 From 380e6fbb729a55b73d5d8409551474884e0d93fc Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 14 Jun 2007 04:27:22 +0900 Subject: sysfs: implement SYSFS_FLAG_REMOVED flag Implement SYSFS_FLAG_REMOVED flag which currently is used only to improve sanity check in sysfs_deactivate(). The flag will be used to make directory entries reclamiable. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'fs/sysfs/dir.c') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index eb9bc0a..f2ea006 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -171,7 +171,7 @@ void sysfs_deactivate(struct sysfs_dirent *sd) DECLARE_COMPLETION_ONSTACK(wait); int v; - BUG_ON(sd->s_sibling); + BUG_ON(sd->s_sibling || !(sd->s_flags & SYSFS_FLAG_REMOVED)); sd->s_sibling = (void *)&wait; /* atomic_add_return() is a mb(), put_active() will always see @@ -506,6 +506,7 @@ static void remove_dir(struct dentry * d) mutex_lock(&parent->d_inode->i_mutex); sysfs_unlink_sibling(sd); + sd->s_flags |= SYSFS_FLAG_REMOVED; pr_debug(" o %s removing done (%d)\n",d->d_name.name, atomic_read(&d->d_count)); @@ -540,6 +541,7 @@ static void __sysfs_remove_dir(struct dentry *dentry) 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; -- cgit v1.1 From f0b0af4792d751106e2003f96af76fa95e10c68d Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 14 Jun 2007 04:27:22 +0900 Subject: sysfs: implement sysfs_find_dirent() and sysfs_get_dirent() Implement sysfs_find_dirent() and sysfs_get_dirent(). sysfs_dirent_exist() is replaced by sysfs_find_dirent(). These will be used to make directory entries reclamiable. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 61 ++++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 17 deletions(-) (limited to 'fs/sysfs/dir.c') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index f2ea006..4762a9a 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -317,28 +317,55 @@ void sysfs_attach_dirent(struct sysfs_dirent *sd, } } -/* +/** + * sysfs_find_dirent - find sysfs_dirent with the given name + * @parent_sd: sysfs_dirent to search under + * @name: name to look for * - * Return -EEXIST if there is already a sysfs element with the same name for - * the same parent. + * Look for sysfs_dirent with name @name under @parent_sd. * - * called with parent inode's i_mutex held + * LOCKING: + * mutex_lock(parent->i_mutex) + * + * RETURNS: + * Pointer to sysfs_dirent if found, NULL if not. */ -int sysfs_dirent_exist(struct sysfs_dirent *parent_sd, - const unsigned char *new) +struct sysfs_dirent *sysfs_find_dirent(struct sysfs_dirent *parent_sd, + const unsigned char *name) { - struct sysfs_dirent * sd; + struct sysfs_dirent *sd; - for (sd = parent_sd->s_children; sd; sd = sd->s_sibling) { - if (sysfs_type(sd)) { - if (strcmp(sd->s_name, new)) - continue; - else - return -EEXIST; - } - } + for (sd = parent_sd->s_children; sd; sd = sd->s_sibling) + if (sysfs_type(sd) && !strcmp(sd->s_name, name)) + return sd; + return NULL; +} - return 0; +/** + * sysfs_get_dirent - find and get sysfs_dirent with the given name + * @parent_sd: sysfs_dirent to search under + * @name: name to look for + * + * Look for sysfs_dirent with name @name under @parent_sd and get + * it if found. + * + * LOCKING: + * Kernel thread context (may sleep) + * + * RETURNS: + * Pointer to sysfs_dirent if found, NULL if not. + */ +struct sysfs_dirent *sysfs_get_dirent(struct sysfs_dirent *parent_sd, + const unsigned char *name) +{ + struct sysfs_dirent *sd; + + mutex_lock(&parent_sd->s_dentry->d_inode->i_mutex); + sd = sysfs_find_dirent(parent_sd, name); + sysfs_get(sd); + mutex_unlock(&parent_sd->s_dentry->d_inode->i_mutex); + + return sd; } static int create_dir(struct kobject *kobj, struct dentry *parent, @@ -382,7 +409,7 @@ static int create_dir(struct kobject *kobj, struct dentry *parent, /* link in */ error = -EEXIST; - if (sysfs_dirent_exist(parent->d_fsdata, name)) + if (sysfs_find_dirent(parent->d_fsdata, name)) goto out_iput; sysfs_instantiate(dentry, inode); -- cgit v1.1 From 608e266a2d4e62c1b98c1c573064b6afe8c06a58 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 14 Jun 2007 04:27:22 +0900 Subject: sysfs: make kobj point to sysfs_dirent instead of dentry As kobj sysfs dentries and inodes are gonna be made reclaimable, dentry can't be used as naming token for sysfs file/directory, replace kobj->dentry with kobj->sd. The only external interface change is shadow directory handling. All other changes are contained in kobj and sysfs. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 119 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 59 insertions(+), 60 deletions(-) (limited to 'fs/sysfs/dir.c') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index 4762a9a..31b6cf3 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -368,9 +368,10 @@ struct sysfs_dirent *sysfs_get_dirent(struct sysfs_dirent *parent_sd, return sd; } -static int create_dir(struct kobject *kobj, struct dentry *parent, - const char *name, struct dentry **p_dentry) +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; int error; umode_t mode = S_IFDIR| S_IRWXU | S_IRUGO | S_IXUGO; struct dentry *dentry; @@ -409,14 +410,14 @@ static int create_dir(struct kobject *kobj, struct dentry *parent, /* link in */ error = -EEXIST; - if (sysfs_find_dirent(parent->d_fsdata, name)) + if (sysfs_find_dirent(parent_sd, name)) goto out_iput; sysfs_instantiate(dentry, inode); inc_nlink(parent->d_inode); - sysfs_attach_dirent(sd, parent->d_fsdata, dentry); + sysfs_attach_dirent(sd, parent_sd, dentry); - *p_dentry = dentry; + *p_sd = sd; error = 0; goto out_unlock; /* pin directory dentry in core */ @@ -433,38 +434,37 @@ static int create_dir(struct kobject *kobj, struct dentry *parent, return error; } - -int sysfs_create_subdir(struct kobject * k, const char * n, struct dentry ** d) +int sysfs_create_subdir(struct kobject *kobj, const char *name, + struct sysfs_dirent **p_sd) { - return create_dir(k,k->dentry,n,d); + return create_dir(kobj, kobj->sd, name, p_sd); } /** * sysfs_create_dir - create a directory for an object. * @kobj: object we're creating directory for. - * @shadow_parent: parent parent object. + * @shadow_parent: parent object. */ - -int sysfs_create_dir(struct kobject * kobj, struct dentry *shadow_parent) +int sysfs_create_dir(struct kobject *kobj, + struct sysfs_dirent *shadow_parent_sd) { - struct dentry * dentry = NULL; - struct dentry * parent; + struct sysfs_dirent *parent_sd, *sd; int error = 0; BUG_ON(!kobj); - if (shadow_parent) - parent = shadow_parent; + if (shadow_parent_sd) + parent_sd = shadow_parent_sd; else if (kobj->parent) - parent = kobj->parent->dentry; + parent_sd = kobj->parent->sd; else if (sysfs_mount && sysfs_mount->mnt_sb) - parent = sysfs_mount->mnt_sb->s_root; + parent_sd = sysfs_mount->mnt_sb->s_root->d_fsdata; else return -EFAULT; - error = create_dir(kobj,parent,kobject_name(kobj),&dentry); + error = create_dir(kobj, parent_sd, kobject_name(kobj), &sd); if (!error) - kobj->dentry = dentry; + kobj->sd = sd; return error; } @@ -525,18 +525,16 @@ const struct inode_operations sysfs_dir_inode_operations = { .setattr = sysfs_setattr, }; -static void remove_dir(struct dentry * d) +static void remove_dir(struct sysfs_dirent *sd) { - struct dentry *parent = d->d_parent; - struct sysfs_dirent *sd = d->d_fsdata; + struct dentry *parent = sd->s_parent->s_dentry; mutex_lock(&parent->d_inode->i_mutex); sysfs_unlink_sibling(sd); sd->s_flags |= SYSFS_FLAG_REMOVED; - pr_debug(" o %s removing done (%d)\n",d->d_name.name, - atomic_read(&d->d_count)); + pr_debug(" o %s removing done\n", sd->s_name); mutex_unlock(&parent->d_inode->i_mutex); @@ -545,25 +543,26 @@ static void remove_dir(struct dentry * d) sysfs_put(sd); } -void sysfs_remove_subdir(struct dentry * d) +void sysfs_remove_subdir(struct sysfs_dirent *sd) { - remove_dir(d); + remove_dir(sd); } -static void __sysfs_remove_dir(struct dentry *dentry) +static void __sysfs_remove_dir(struct sysfs_dirent *dir_sd) { struct sysfs_dirent *removed = NULL; - struct sysfs_dirent *parent_sd; struct sysfs_dirent **pos; + struct dentry *dir; - if (!dentry) + if (!dir_sd) return; - pr_debug("sysfs %s: removing dir\n",dentry->d_name.name); - mutex_lock(&dentry->d_inode->i_mutex); - parent_sd = dentry->d_fsdata; - pos = &parent_sd->s_children; + dir = dir_sd->s_dentry; + + pr_debug("sysfs %s: removing dir\n", dir_sd->s_name); + mutex_lock(&dir->d_inode->i_mutex); + pos = &dir_sd->s_children; while (*pos) { struct sysfs_dirent *sd = *pos; @@ -575,7 +574,7 @@ static void __sysfs_remove_dir(struct dentry *dentry) } else pos = &(*pos)->s_sibling; } - mutex_unlock(&dentry->d_inode->i_mutex); + mutex_unlock(&dir->d_inode->i_mutex); while (removed) { struct sysfs_dirent *sd = removed; @@ -588,7 +587,7 @@ static void __sysfs_remove_dir(struct dentry *dentry) sysfs_put(sd); } - remove_dir(dentry); + remove_dir(dir_sd); } /** @@ -602,25 +601,25 @@ static void __sysfs_remove_dir(struct dentry *dentry) void sysfs_remove_dir(struct kobject * kobj) { - struct dentry *d = kobj->dentry; + struct sysfs_dirent *sd = kobj->sd; spin_lock(&kobj_sysfs_assoc_lock); - kobj->dentry = NULL; + kobj->sd = NULL; spin_unlock(&kobj_sysfs_assoc_lock); - __sysfs_remove_dir(d); + __sysfs_remove_dir(sd); } -int sysfs_rename_dir(struct kobject * kobj, struct dentry *new_parent, +int sysfs_rename_dir(struct kobject *kobj, struct sysfs_dirent *new_parent_sd, const char *new_name) { - struct sysfs_dirent *sd = kobj->dentry->d_fsdata; - struct sysfs_dirent *parent_sd = new_parent->d_fsdata; + struct sysfs_dirent *sd = kobj->sd; + struct dentry *new_parent = new_parent_sd->s_dentry; struct dentry *new_dentry; char *dup_name; int error; - if (!new_parent) + if (!new_parent_sd) return -EFAULT; down_write(&sysfs_rename_sem); @@ -637,9 +636,9 @@ int sysfs_rename_dir(struct kobject * kobj, struct dentry *new_parent, * shadows of the same directory */ error = -EINVAL; - if (kobj->dentry->d_parent->d_inode != new_parent->d_inode || + if (sd->s_parent->s_dentry->d_inode != new_parent->d_inode || new_dentry->d_parent->d_inode != new_parent->d_inode || - new_dentry == kobj->dentry) + new_dentry == sd->s_dentry) goto out_dput; error = -EEXIST; @@ -661,12 +660,12 @@ int sysfs_rename_dir(struct kobject * kobj, struct dentry *new_parent, /* move under the new parent */ d_add(new_dentry, NULL); - d_move(kobj->dentry, new_dentry); + d_move(sd->s_dentry, new_dentry); sysfs_unlink_sibling(sd); - sysfs_get(parent_sd); + sysfs_get(new_parent_sd); sysfs_put(sd->s_parent); - sd->s_parent = parent_sd; + sd->s_parent = new_parent_sd; sysfs_link_sibling(sd); error = 0; @@ -691,9 +690,9 @@ int sysfs_move_dir(struct kobject *kobj, struct kobject *new_parent) int error; old_parent_dentry = kobj->parent ? - kobj->parent->dentry : sysfs_mount->mnt_sb->s_root; + kobj->parent->sd->s_dentry : sysfs_mount->mnt_sb->s_root; new_parent_dentry = new_parent ? - new_parent->dentry : sysfs_mount->mnt_sb->s_root; + new_parent->sd->s_dentry : sysfs_mount->mnt_sb->s_root; if (old_parent_dentry->d_inode == new_parent_dentry->d_inode) return 0; /* nothing to move */ @@ -705,7 +704,7 @@ again: } new_parent_sd = new_parent_dentry->d_fsdata; - sd = kobj->dentry->d_fsdata; + sd = kobj->sd; new_dentry = lookup_one_len(kobj->name, new_parent_dentry, strlen(kobj->name)); @@ -715,7 +714,7 @@ again: } else error = 0; d_add(new_dentry, NULL); - d_move(kobj->dentry, new_dentry); + d_move(sd->s_dentry, new_dentry); dput(new_dentry); /* Remove from old parent's list and insert into new parent's list. */ @@ -885,7 +884,7 @@ int sysfs_make_shadowed_dir(struct kobject *kobj, struct inode *inode; struct inode_operations *i_op; - inode = kobj->dentry->d_inode; + inode = kobj->sd->s_dentry->d_inode; if (inode->i_op != &sysfs_dir_inode_operations) return -EINVAL; @@ -912,16 +911,16 @@ int sysfs_make_shadowed_dir(struct kobject *kobj, * directory. */ -struct dentry *sysfs_create_shadow_dir(struct kobject *kobj) +struct sysfs_dirent *sysfs_create_shadow_dir(struct kobject *kobj) { - struct dentry *dir = kobj->dentry; + struct dentry *dir = kobj->sd->s_dentry; struct inode *inode = dir->d_inode; struct dentry *parent = dir->d_parent; struct sysfs_dirent *parent_sd = parent->d_fsdata; struct dentry *shadow; struct sysfs_dirent *sd; - shadow = ERR_PTR(-EINVAL); + sd = ERR_PTR(-EINVAL); if (!sysfs_is_shadowed_inode(inode)) goto out; @@ -944,25 +943,25 @@ struct dentry *sysfs_create_shadow_dir(struct kobject *kobj) dget(shadow); /* Extra count - pin the dentry in core */ out: - return shadow; + return sd; nomem: dput(shadow); - shadow = ERR_PTR(-ENOMEM); + sd = ERR_PTR(-ENOMEM); goto out; } /** * sysfs_remove_shadow_dir - remove an object's directory. - * @shadow: dentry of shadow directory + * @shadow_sd: sysfs_dirent of shadow directory * * The only thing special about this is that we remove any files in * the directory before we remove the directory, and we've inlined * what used to be sysfs_rmdir() below, instead of calling separately. */ -void sysfs_remove_shadow_dir(struct dentry *shadow) +void sysfs_remove_shadow_dir(struct sysfs_dirent *shadow_sd) { - __sysfs_remove_dir(shadow); + __sysfs_remove_dir(shadow_sd); } const struct file_operations sysfs_dir_operations = { -- cgit v1.1 From 5f9953237f684ea1778adb9d26162da00b282225 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 14 Jun 2007 04:27:23 +0900 Subject: sysfs: consolidate sysfs spinlocks Replace sysfs_lock and kobj_sysfs_assoc_lock with sysfs_assoc_lock. sysfs_lock was originally to be used to protect sysfs_dirent tree but mutex seems better choice, so there is no reason to keep sysfs_lock separate. Merge the two spinlocks into one. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) (limited to 'fs/sysfs/dir.c') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index 31b6cf3..1b56434 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -15,8 +15,7 @@ #include "sysfs.h" DECLARE_RWSEM(sysfs_rename_sem); -spinlock_t sysfs_lock = SPIN_LOCK_UNLOCKED; -spinlock_t kobj_sysfs_assoc_lock = SPIN_LOCK_UNLOCKED; +spinlock_t sysfs_assoc_lock = SPIN_LOCK_UNLOCKED; static spinlock_t sysfs_ino_lock = SPIN_LOCK_UNLOCKED; static DEFINE_IDA(sysfs_ino_ida); @@ -236,10 +235,10 @@ static void sysfs_d_iput(struct dentry * dentry, struct inode * inode) struct sysfs_dirent * sd = dentry->d_fsdata; if (sd) { - /* sd->s_dentry is protected with sysfs_lock. This - * allows sysfs_drop_dentry() to dereference it. + /* sd->s_dentry is protected with sysfs_assoc_lock. + * This allows sysfs_drop_dentry() to dereference it. */ - spin_lock(&sysfs_lock); + spin_lock(&sysfs_assoc_lock); /* The dentry might have been deleted or another * lookup could have happened updating sd->s_dentry to @@ -248,7 +247,7 @@ static void sysfs_d_iput(struct dentry * dentry, struct inode * inode) */ if (sd->s_dentry == dentry) sd->s_dentry = NULL; - spin_unlock(&sysfs_lock); + spin_unlock(&sysfs_assoc_lock); sysfs_put(sd); } iput(inode); @@ -298,9 +297,9 @@ static void sysfs_attach_dentry(struct sysfs_dirent *sd, struct dentry *dentry) dentry->d_fsdata = sysfs_get(sd); /* protect sd->s_dentry against sysfs_d_iput */ - spin_lock(&sysfs_lock); + spin_lock(&sysfs_assoc_lock); sd->s_dentry = dentry; - spin_unlock(&sysfs_lock); + spin_unlock(&sysfs_assoc_lock); d_rehash(dentry); } @@ -603,9 +602,9 @@ void sysfs_remove_dir(struct kobject * kobj) { struct sysfs_dirent *sd = kobj->sd; - spin_lock(&kobj_sysfs_assoc_lock); + spin_lock(&sysfs_assoc_lock); kobj->sd = NULL; - spin_unlock(&kobj_sysfs_assoc_lock); + spin_unlock(&sysfs_assoc_lock); __sysfs_remove_dir(sd); } -- cgit v1.1 From 3007e997de91ec59af39a3f9c91595b31ae6e08b Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 14 Jun 2007 04:27:23 +0900 Subject: sysfs: use sysfs_mutex to protect the sysfs_dirent tree As kobj sysfs dentries and inodes are gonna be made reclaimable, i_mutex can't be used to protect sysfs_dirent tree. Use sysfs_mutex globally instead. As the whole tree is protected with sysfs_mutex, there is no reason to keep sysfs_rename_sem. Drop it. While at it, add docbook comments to functions which require sysfs_mutex locking. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 101 ++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 71 insertions(+), 30 deletions(-) (limited to 'fs/sysfs/dir.c') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index 1b56434..9fc8558 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -14,7 +14,7 @@ #include #include "sysfs.h" -DECLARE_RWSEM(sysfs_rename_sem); +DEFINE_MUTEX(sysfs_mutex); spinlock_t sysfs_assoc_lock = SPIN_LOCK_UNLOCKED; static spinlock_t sysfs_ino_lock = SPIN_LOCK_UNLOCKED; @@ -28,7 +28,7 @@ static DEFINE_IDA(sysfs_ino_ida); * sd->s_parent->s_children. * * Locking: - * mutex_lock(sd->s_parent->dentry->d_inode->i_mutex) + * mutex_lock(sysfs_mutex) */ static void sysfs_link_sibling(struct sysfs_dirent *sd) { @@ -47,7 +47,7 @@ static void sysfs_link_sibling(struct sysfs_dirent *sd) * sd->s_parent->s_children. * * Locking: - * mutex_lock(sd->s_parent->dentry->d_inode->i_mutex) + * mutex_lock(sysfs_mutex) */ static void sysfs_unlink_sibling(struct sysfs_dirent *sd) { @@ -215,6 +215,9 @@ void release_sysfs_dirent(struct sysfs_dirent * sd) struct sysfs_dirent *parent_sd; repeat: + /* Moving/renaming is always done while holding reference. + * sd->s_parent won't change beneath us. + */ parent_sd = sd->s_parent; if (sysfs_type(sd) == SYSFS_KOBJ_LINK) @@ -291,6 +294,17 @@ struct sysfs_dirent *sysfs_new_dirent(const char *name, umode_t mode, int type) return NULL; } +/** + * sysfs_attach_dentry - associate sysfs_dirent with dentry + * @sd: target sysfs_dirent + * @dentry: dentry to associate + * + * Associate @sd with @dentry. This is protected by + * sysfs_assoc_lock to avoid race with sysfs_d_iput(). + * + * LOCKING: + * mutex_lock(sysfs_mutex) + */ static void sysfs_attach_dentry(struct sysfs_dirent *sd, struct dentry *dentry) { dentry->d_op = &sysfs_dentry_ops; @@ -304,6 +318,17 @@ static void sysfs_attach_dentry(struct sysfs_dirent *sd, struct dentry *dentry) d_rehash(dentry); } +/** + * 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) + * + * Attach @sd to @parent_sd and/or @dentry. Both are optional. + * + * LOCKING: + * mutex_lock(sysfs_mutex) + */ void sysfs_attach_dirent(struct sysfs_dirent *sd, struct sysfs_dirent *parent_sd, struct dentry *dentry) { @@ -324,7 +349,7 @@ void sysfs_attach_dirent(struct sysfs_dirent *sd, * Look for sysfs_dirent with name @name under @parent_sd. * * LOCKING: - * mutex_lock(parent->i_mutex) + * mutex_lock(sysfs_mutex) * * RETURNS: * Pointer to sysfs_dirent if found, NULL if not. @@ -349,7 +374,7 @@ struct sysfs_dirent *sysfs_find_dirent(struct sysfs_dirent *parent_sd, * it if found. * * LOCKING: - * Kernel thread context (may sleep) + * Kernel thread context (may sleep). Grabs sysfs_mutex. * * RETURNS: * Pointer to sysfs_dirent if found, NULL if not. @@ -359,10 +384,10 @@ struct sysfs_dirent *sysfs_get_dirent(struct sysfs_dirent *parent_sd, { struct sysfs_dirent *sd; - mutex_lock(&parent_sd->s_dentry->d_inode->i_mutex); + mutex_lock(&sysfs_mutex); sd = sysfs_find_dirent(parent_sd, name); sysfs_get(sd); - mutex_unlock(&parent_sd->s_dentry->d_inode->i_mutex); + mutex_unlock(&sysfs_mutex); return sd; } @@ -408,14 +433,20 @@ 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)) + if (sysfs_find_dirent(parent_sd, name)) { + mutex_unlock(&sysfs_mutex); goto out_iput; + } sysfs_instantiate(dentry, inode); inc_nlink(parent->d_inode); sysfs_attach_dirent(sd, parent_sd, dentry); + mutex_unlock(&sysfs_mutex); + *p_sd = sd; error = 0; goto out_unlock; /* pin directory dentry in core */ @@ -493,6 +524,8 @@ static struct dentry * sysfs_lookup(struct inode *dir, struct dentry *dentry, if (!inode) return ERR_PTR(-ENOMEM); + mutex_lock(&sysfs_mutex); + if (inode->i_state & I_NEW) { /* initialize inode according to type */ switch (sysfs_type(sd)) { @@ -516,6 +549,8 @@ static struct dentry * sysfs_lookup(struct inode *dir, struct dentry *dentry, sysfs_instantiate(dentry, inode); sysfs_attach_dentry(sd, dentry); + mutex_unlock(&sysfs_mutex); + return NULL; } @@ -526,17 +561,13 @@ const struct inode_operations sysfs_dir_inode_operations = { static void remove_dir(struct sysfs_dirent *sd) { - struct dentry *parent = sd->s_parent->s_dentry; - - mutex_lock(&parent->d_inode->i_mutex); - + mutex_lock(&sysfs_mutex); sysfs_unlink_sibling(sd); sd->s_flags |= SYSFS_FLAG_REMOVED; + mutex_unlock(&sysfs_mutex); pr_debug(" o %s removing done\n", sd->s_name); - mutex_unlock(&parent->d_inode->i_mutex); - sysfs_drop_dentry(sd); sysfs_deactivate(sd); sysfs_put(sd); @@ -552,15 +583,12 @@ static void __sysfs_remove_dir(struct sysfs_dirent *dir_sd) { struct sysfs_dirent *removed = NULL; struct sysfs_dirent **pos; - struct dentry *dir; if (!dir_sd) return; - dir = dir_sd->s_dentry; - pr_debug("sysfs %s: removing dir\n", dir_sd->s_name); - mutex_lock(&dir->d_inode->i_mutex); + mutex_lock(&sysfs_mutex); pos = &dir_sd->s_children; while (*pos) { struct sysfs_dirent *sd = *pos; @@ -573,7 +601,7 @@ static void __sysfs_remove_dir(struct sysfs_dirent *dir_sd) } else pos = &(*pos)->s_sibling; } - mutex_unlock(&dir->d_inode->i_mutex); + mutex_unlock(&sysfs_mutex); while (removed) { struct sysfs_dirent *sd = removed; @@ -621,7 +649,6 @@ int sysfs_rename_dir(struct kobject *kobj, struct sysfs_dirent *new_parent_sd, if (!new_parent_sd) return -EFAULT; - down_write(&sysfs_rename_sem); mutex_lock(&new_parent->d_inode->i_mutex); new_dentry = lookup_one_len(new_name, new_parent, strlen(new_name)); @@ -661,12 +688,16 @@ int sysfs_rename_dir(struct kobject *kobj, struct sysfs_dirent *new_parent_sd, d_add(new_dentry, NULL); d_move(sd->s_dentry, new_dentry); + mutex_lock(&sysfs_mutex); + sysfs_unlink_sibling(sd); sysfs_get(new_parent_sd); sysfs_put(sd->s_parent); sd->s_parent = new_parent_sd; sysfs_link_sibling(sd); + mutex_unlock(&sysfs_mutex); + error = 0; goto out_unlock; @@ -678,7 +709,6 @@ int sysfs_rename_dir(struct kobject *kobj, struct sysfs_dirent *new_parent_sd, dput(new_dentry); out_unlock: mutex_unlock(&new_parent->d_inode->i_mutex); - up_write(&sysfs_rename_sem); return error; } @@ -717,12 +747,15 @@ again: dput(new_dentry); /* Remove from old parent's list and insert into new parent's list. */ + mutex_lock(&sysfs_mutex); + sysfs_unlink_sibling(sd); sysfs_get(new_parent_sd); sysfs_put(sd->s_parent); sd->s_parent = new_parent_sd; sysfs_link_sibling(sd); + mutex_unlock(&sysfs_mutex); out: mutex_unlock(&new_parent_dentry->d_inode->i_mutex); mutex_unlock(&old_parent_dentry->d_inode->i_mutex); @@ -736,11 +769,12 @@ static int sysfs_dir_open(struct inode *inode, struct file *file) struct sysfs_dirent * parent_sd = dentry->d_fsdata; struct sysfs_dirent * sd; - mutex_lock(&dentry->d_inode->i_mutex); sd = sysfs_new_dirent("_DIR_", 0, 0); - if (sd) + if (sd) { + mutex_lock(&sysfs_mutex); sysfs_attach_dirent(sd, parent_sd, NULL); - mutex_unlock(&dentry->d_inode->i_mutex); + mutex_unlock(&sysfs_mutex); + } file->private_data = sd; return sd ? 0 : -ENOMEM; @@ -748,12 +782,11 @@ static int sysfs_dir_open(struct inode *inode, struct file *file) static int sysfs_dir_close(struct inode *inode, struct file *file) { - struct dentry * dentry = file->f_path.dentry; struct sysfs_dirent * cursor = file->private_data; - mutex_lock(&dentry->d_inode->i_mutex); + mutex_lock(&sysfs_mutex); sysfs_unlink_sibling(cursor); - mutex_unlock(&dentry->d_inode->i_mutex); + mutex_unlock(&sysfs_mutex); release_sysfs_dirent(cursor); @@ -794,6 +827,8 @@ static int sysfs_readdir(struct file * filp, void * dirent, filldir_t filldir) i++; /* fallthrough */ default: + mutex_lock(&sysfs_mutex); + pos = &parent_sd->s_children; while (*pos != cursor) pos = &(*pos)->s_sibling; @@ -826,6 +861,8 @@ static int sysfs_readdir(struct file * filp, void * dirent, filldir_t filldir) /* put cursor back in */ cursor->s_sibling = *pos; *pos = cursor; + + mutex_unlock(&sysfs_mutex); } return 0; } @@ -834,7 +871,6 @@ static loff_t sysfs_dir_lseek(struct file * file, loff_t offset, int origin) { struct dentry * dentry = file->f_path.dentry; - mutex_lock(&dentry->d_inode->i_mutex); switch (origin) { case 1: offset += file->f_pos; @@ -842,10 +878,11 @@ static loff_t sysfs_dir_lseek(struct file * file, loff_t offset, int origin) if (offset >= 0) break; default: - mutex_unlock(&file->f_path.dentry->d_inode->i_mutex); return -EINVAL; } if (offset != file->f_pos) { + mutex_lock(&sysfs_mutex); + file->f_pos = offset; if (file->f_pos >= 2) { struct sysfs_dirent *sd = dentry->d_fsdata; @@ -866,8 +903,10 @@ static loff_t sysfs_dir_lseek(struct file * file, loff_t offset, int origin) cursor->s_sibling = *pos; *pos = cursor; } + + mutex_unlock(&sysfs_mutex); } - mutex_unlock(&dentry->d_inode->i_mutex); + return offset; } @@ -933,7 +972,9 @@ struct sysfs_dirent *sysfs_create_shadow_dir(struct kobject *kobj) 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); d_instantiate(shadow, igrab(inode)); inc_nlink(inode); -- cgit v1.1 From fb6896da37f19be4b75154c14d1cd79231255b17 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 14 Jun 2007 04:27:24 +0900 Subject: sysfs: restructure add/remove paths and fix inode update The original add/remove code had the following problems. * parent's timestamps are updated on dentry instantiation. this is incorrect with reclaimable files. * updating parent's timestamps isn't synchronized. * parent nlink update assumes the inode is accessible which won't be true once directory dentries are made reclaimable. This patch restructures add/remove paths to resolve the above problems. Add/removal are done in the following steps. 1. sysfs_addrm_start() : acquire locks including sysfs_mutex and other resources. 2-a. sysfs_add_one() : add new sd. linking the new sd into the children list is caller's responsibility. 2-b. sysfs_remove_one() : remove a sd. unlinking the sd from the children list is caller's responsibility. 3. sysfs_addrm_finish() : release all resources and clean up. Steps 2-a and/or 2-b can be repeated multiple times. Parent's inode is looked up during sysfs_addrm_start(). If available (always at the moment), it's pinned and nlink is updated as sd's are added and removed. Timestamps are updated during finish if any sd has been added or removed. If parent's inode is not available during start, sysfs_mutex ensures that parent inode is not created till add/remove is complete. All the complexity is contained inside the helper functions. Especially, dentry/inode handling is properly hidden from the rest of sysfs which now mostly operate on sysfs_dirents. As an added bonus, codes which use these helpers to add and remove sysfs_dirents are now more structured and simpler. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 250 ++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 186 insertions(+), 64 deletions(-) (limited to 'fs/sysfs/dir.c') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index 9fc8558..edb3062 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 */ -- cgit v1.1 From a0edd7c848945a75e2f41673f43bc37d0a5fed15 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 14 Jun 2007 04:27:24 +0900 Subject: sysfs: move sysfs_drop_dentry() to dir.c and make it static After add/remove path restructuring, the only user of sysfs_drop_dentry() is sysfs_addrm_finish(). Move sysfs_drop_dentry() to dir.c and make it static. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) (limited to 'fs/sysfs/dir.c') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index edb3062..c6f3b69 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -435,6 +435,62 @@ void sysfs_remove_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd) } /** + * sysfs_drop_dentry - drop dentry for the specified sysfs_dirent + * @sd: target sysfs_dirent + * + * Drop dentry for @sd. @sd must have been unlinked from its + * parent on entry to this function such that it can't be looked + * up anymore. + * + * @sd->s_dentry which is protected with sysfs_assoc_lock points + * to the currently associated dentry but we're not holding a + * reference to it and racing with dput(). Grab dcache_lock and + * verify dentry before dropping it. If @sd->s_dentry is NULL or + * dput() beats us, no need to bother. + */ +static void sysfs_drop_dentry(struct sysfs_dirent *sd) +{ + struct dentry *dentry = NULL; + struct inode *inode; + + /* We're not holding a reference to ->s_dentry dentry but the + * field will stay valid as long as sysfs_assoc_lock is held. + */ + spin_lock(&sysfs_assoc_lock); + spin_lock(&dcache_lock); + + /* drop dentry if it's there and dput() didn't kill it yet */ + if (sd->s_dentry && sd->s_dentry->d_inode) { + dentry = dget_locked(sd->s_dentry); + spin_lock(&dentry->d_lock); + __d_drop(dentry); + spin_unlock(&dentry->d_lock); + } + + spin_unlock(&dcache_lock); + spin_unlock(&sysfs_assoc_lock); + + dput(dentry); + /* XXX: unpin if directory, this will go away soon */ + if (sysfs_type(sd) == SYSFS_DIR) + dput(dentry); + + /* adjust nlink and update timestamp */ + inode = ilookup(sysfs_sb, sd->s_ino); + if (inode) { + mutex_lock(&inode->i_mutex); + + inode->i_ctime = CURRENT_TIME; + drop_nlink(inode); + if (sysfs_type(sd) == SYSFS_DIR) + drop_nlink(inode); + + mutex_unlock(&inode->i_mutex); + iput(inode); + } +} + +/** * sysfs_addrm_finish - finish up sysfs_dirent add/remove * @acxt: addrm context to finish up * -- cgit v1.1 From 53e0ae92690c52eceb997905d85fbb42de5fff63 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 14 Jun 2007 04:27:25 +0900 Subject: sysfs: implement sysfs_get_dentry() Some sysfs operations require dentry and inode. sysfs_get_dentry() looks up and gets dentry for the specified sysfs_dirent. It finds the first ancestor with dentry attached and starts looking up dentries from there. Looking up from the nearest ancestor is necessary to support shadowed directories because we can't reliably lookup dentry for one of the shadows. Dentries for each shadow will be pinned in memory such that they can serve as the starting point for dentry lookup. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) (limited to 'fs/sysfs/dir.c') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index c6f3b69..9872112 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -63,6 +63,104 @@ void sysfs_unlink_sibling(struct sysfs_dirent *sd) } /** + * sysfs_get_dentry - get dentry for the given sysfs_dirent + * @sd: sysfs_dirent of interest + * + * Get dentry for @sd. Dentry is looked up if currently not + * present. This function climbs sysfs_dirent tree till it + * reaches a sysfs_dirent with valid dentry attached and descends + * down from there looking up dentry for each step. + * + * LOCKING: + * Kernel thread context (may sleep) + * + * RETURNS: + * Pointer to found dentry on success, ERR_PTR() value on error. + */ +struct dentry *sysfs_get_dentry(struct sysfs_dirent *sd) +{ + struct sysfs_dirent *cur; + struct dentry *parent_dentry, *dentry; + int i, depth; + + /* Find the first parent which has valid s_dentry and get the + * dentry. + */ + mutex_lock(&sysfs_mutex); + restart0: + spin_lock(&sysfs_assoc_lock); + restart1: + spin_lock(&dcache_lock); + + dentry = NULL; + depth = 0; + cur = sd; + while (!cur->s_dentry || !cur->s_dentry->d_inode) { + if (cur->s_flags & SYSFS_FLAG_REMOVED) { + dentry = ERR_PTR(-ENOENT); + depth = 0; + break; + } + cur = cur->s_parent; + depth++; + } + if (!IS_ERR(dentry)) + dentry = dget_locked(cur->s_dentry); + + spin_unlock(&dcache_lock); + spin_unlock(&sysfs_assoc_lock); + + /* from the found dentry, look up depth times */ + while (depth--) { + /* find and get depth'th ancestor */ + for (cur = sd, i = 0; cur && i < depth; i++) + cur = cur->s_parent; + + /* This can happen if tree structure was modified due + * to move/rename. Restart. + */ + if (i != depth) { + dput(dentry); + goto restart0; + } + + sysfs_get(cur); + + mutex_unlock(&sysfs_mutex); + + /* look it up */ + parent_dentry = dentry; + dentry = lookup_one_len_kern(cur->s_name, parent_dentry, + strlen(cur->s_name)); + dput(parent_dentry); + + if (IS_ERR(dentry)) { + sysfs_put(cur); + return dentry; + } + + mutex_lock(&sysfs_mutex); + spin_lock(&sysfs_assoc_lock); + + /* This, again, can happen if tree structure has + * changed and we looked up the wrong thing. Restart. + */ + if (cur->s_dentry != dentry) { + dput(dentry); + sysfs_put(cur); + goto restart1; + } + + spin_unlock(&sysfs_assoc_lock); + + sysfs_put(cur); + } + + mutex_unlock(&sysfs_mutex); + return dentry; +} + +/** * sysfs_get_active - get an active reference to sysfs_dirent * @sd: sysfs_dirent to get an active reference to * -- cgit v1.1 From 51225039f3cf9d250596d1344494b293274b9169 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 14 Jun 2007 04:27:25 +0900 Subject: sysfs: make directory dentries and inodes reclaimable This patch makes dentries and inodes for sysfs directories reclaimable. * sysfs_notify() is modified to walk sysfs_dirent tree instead of dentry tree. * sysfs_update_file() and sysfs_chmod_file() use sysfs_get_dentry() to grab the victim dentry. * sysfs_rename_dir() and sysfs_move_dir() grab all dentries using sysfs_get_dentry() on startup. * Dentries for all shadowed directories are pinned in memory to serve as lookup start point. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 231 +++++++++++++++++++++++++++++++-------------------------- 1 file changed, 126 insertions(+), 105 deletions(-) (limited to 'fs/sysfs/dir.c') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index 9872112..aee966c 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -568,10 +568,10 @@ static void sysfs_drop_dentry(struct sysfs_dirent *sd) spin_unlock(&dcache_lock); spin_unlock(&sysfs_assoc_lock); - dput(dentry); - /* XXX: unpin if directory, this will go away soon */ - if (sysfs_type(sd) == SYSFS_DIR) + /* dentries for shadowed inodes are pinned, unpin */ + if (dentry && sysfs_is_shadowed_inode(dentry->d_inode)) dput(dentry); + dput(dentry); /* adjust nlink and update timestamp */ inode = ilookup(sysfs_sb, sd->s_ino); @@ -686,69 +686,29 @@ struct sysfs_dirent *sysfs_get_dirent(struct sysfs_dirent *parent_sd, 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_addrm_cxt acxt; struct sysfs_dirent *sd; - 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_finish; - } - - error = -EEXIST; - if (dentry->d_inode) - goto out_dput; - - error = -ENOMEM; sd = sysfs_new_dirent(name, mode, SYSFS_DIR); if (!sd) - goto out_drop; + return -ENOMEM; sd->s_elem.dir.kobj = kobj; - inode = sysfs_get_inode(sd); - if (!inode) - goto out_sput; - - if (inode->i_state & I_NEW) { - inode->i_op = &sysfs_dir_inode_operations; - inode->i_fop = &sysfs_dir_operations; - /* directory inodes start off with i_nlink == 2 (for ".") */ - inc_nlink(inode); - } - /* link in */ - error = -EEXIST; - if (sysfs_find_dirent(parent_sd, name)) - goto out_iput; - - sysfs_add_one(&acxt, sd); - sysfs_link_sibling(sd); - sysfs_instantiate(dentry, inode); - sysfs_attach_dentry(sd, dentry); - - *p_sd = sd; - error = 0; - goto out_finish; /* pin directory dentry in core */ + sysfs_addrm_start(&acxt, parent_sd); + if (!sysfs_find_dirent(parent_sd, name)) { + sysfs_add_one(&acxt, sd); + sysfs_link_sibling(sd); + } + if (sysfs_addrm_finish(&acxt)) { + *p_sd = sd; + return 0; + } - out_iput: - iput(inode); - out_sput: sysfs_put(sd); - out_drop: - d_drop(dentry); - out_dput: - dput(dentry); - out_finish: - sysfs_addrm_finish(&acxt); - return error; + return -EEXIST; } int sysfs_create_subdir(struct kobject *kobj, const char *name, @@ -785,6 +745,17 @@ int sysfs_create_dir(struct kobject *kobj, return error; } +static int sysfs_count_nlink(struct sysfs_dirent *sd) +{ + struct sysfs_dirent *child; + int nr = 0; + + for (child = sd->s_children; child; child = child->s_sibling) + if (sysfs_type(child) == SYSFS_DIR) + nr++; + return nr + 2; +} + static struct dentry * sysfs_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd) { @@ -795,7 +766,7 @@ static struct dentry * sysfs_lookup(struct inode *dir, struct dentry *dentry, int found = 0; for (sd = parent_sd->s_children; sd; sd = sd->s_sibling) { - if ((sysfs_type(sd) & SYSFS_NOT_PINNED) && + if (sysfs_type(sd) && !strcmp(sd->s_name, dentry->d_name.name)) { found = 1; break; @@ -816,6 +787,11 @@ static struct dentry * sysfs_lookup(struct inode *dir, struct dentry *dentry, if (inode->i_state & I_NEW) { /* initialize inode according to type */ switch (sysfs_type(sd)) { + case SYSFS_DIR: + inode->i_op = &sysfs_dir_inode_operations; + inode->i_fop = &sysfs_dir_operations; + inode->i_nlink = sysfs_count_nlink(sd); + break; case SYSFS_KOBJ_ATTR: inode->i_size = PAGE_SIZE; inode->i_fop = &sysfs_file_operations; @@ -876,7 +852,7 @@ static void __sysfs_remove_dir(struct sysfs_dirent *dir_sd) while (*pos) { struct sysfs_dirent *sd = *pos; - if (sysfs_type(sd) && (sysfs_type(sd) & SYSFS_NOT_PINNED)) { + if (sysfs_type(sd) && sysfs_type(sd) != SYSFS_DIR) { *pos = sd->s_sibling; sd->s_sibling = NULL; sysfs_remove_one(&acxt, sd); @@ -912,14 +888,25 @@ int sysfs_rename_dir(struct kobject *kobj, struct sysfs_dirent *new_parent_sd, const char *new_name) { struct sysfs_dirent *sd = kobj->sd; - struct dentry *new_parent = new_parent_sd->s_dentry; - struct dentry *new_dentry; - char *dup_name; + struct dentry *new_parent = NULL; + struct dentry *old_dentry = NULL, *new_dentry = NULL; + const char *dup_name = NULL; int error; - if (!new_parent_sd) - return -EFAULT; + /* get dentries */ + old_dentry = sysfs_get_dentry(sd); + if (IS_ERR(old_dentry)) { + error = PTR_ERR(old_dentry); + goto out_dput; + } + + new_parent = sysfs_get_dentry(new_parent_sd); + if (IS_ERR(new_parent)) { + error = PTR_ERR(new_parent); + goto out_dput; + } + /* lock new_parent and get dentry for new name */ mutex_lock(&new_parent->d_inode->i_mutex); new_dentry = lookup_one_len(new_name, new_parent, strlen(new_name)); @@ -933,14 +920,14 @@ int sysfs_rename_dir(struct kobject *kobj, struct sysfs_dirent *new_parent_sd, * shadows of the same directory */ error = -EINVAL; - if (sd->s_parent->s_dentry->d_inode != new_parent->d_inode || + if (old_dentry->d_parent->d_inode != new_parent->d_inode || new_dentry->d_parent->d_inode != new_parent->d_inode || - new_dentry == sd->s_dentry) - goto out_dput; + old_dentry == new_dentry) + goto out_unlock; error = -EEXIST; if (new_dentry->d_inode) - goto out_dput; + goto out_unlock; /* rename kobject and sysfs_dirent */ error = -ENOMEM; @@ -950,9 +937,9 @@ int sysfs_rename_dir(struct kobject *kobj, struct sysfs_dirent *new_parent_sd, error = kobject_set_name(kobj, "%s", new_name); if (error) - goto out_free; + goto out_drop; - kfree(sd->s_name); + dup_name = sd->s_name; sd->s_name = new_name; /* move under the new parent */ @@ -972,45 +959,58 @@ int sysfs_rename_dir(struct kobject *kobj, struct sysfs_dirent *new_parent_sd, error = 0; goto out_unlock; - out_free: - kfree(dup_name); out_drop: d_drop(new_dentry); - out_dput: - dput(new_dentry); out_unlock: mutex_unlock(&new_parent->d_inode->i_mutex); + out_dput: + kfree(dup_name); + dput(new_parent); + dput(old_dentry); + dput(new_dentry); return error; } -int sysfs_move_dir(struct kobject *kobj, struct kobject *new_parent) +int sysfs_move_dir(struct kobject *kobj, struct kobject *new_parent_kobj) { - struct dentry *old_parent_dentry, *new_parent_dentry, *new_dentry; - struct sysfs_dirent *new_parent_sd, *sd; + 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; - old_parent_dentry = kobj->parent ? - kobj->parent->sd->s_dentry : sysfs_mount->mnt_sb->s_root; - new_parent_dentry = new_parent ? - new_parent->sd->s_dentry : sysfs_mount->mnt_sb->s_root; + BUG_ON(!sd->s_parent); + new_parent_sd = new_parent_kobj->sd ? new_parent_kobj->sd : &sysfs_root; + + /* get dentries */ + old_dentry = sysfs_get_dentry(sd); + if (IS_ERR(old_dentry)) { + error = PTR_ERR(old_dentry); + goto out_dput; + } + old_parent = sd->s_parent->s_dentry; + + new_parent = sysfs_get_dentry(new_parent_sd); + if (IS_ERR(new_parent)) { + error = PTR_ERR(new_parent); + goto out_dput; + } - if (old_parent_dentry->d_inode == new_parent_dentry->d_inode) - return 0; /* nothing to move */ + if (old_parent->d_inode == new_parent->d_inode) { + error = 0; + goto out_dput; /* nothing to move */ + } again: - mutex_lock(&old_parent_dentry->d_inode->i_mutex); - if (!mutex_trylock(&new_parent_dentry->d_inode->i_mutex)) { - mutex_unlock(&old_parent_dentry->d_inode->i_mutex); + 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; } - new_parent_sd = new_parent_dentry->d_fsdata; - sd = kobj->sd; - - new_dentry = lookup_one_len(kobj->name, new_parent_dentry, - strlen(kobj->name)); + new_dentry = lookup_one_len(kobj->name, new_parent, strlen(kobj->name)); if (IS_ERR(new_dentry)) { error = PTR_ERR(new_dentry); - goto out; + goto out_unlock; } else error = 0; d_add(new_dentry, NULL); @@ -1027,10 +1027,14 @@ again: sysfs_link_sibling(sd); mutex_unlock(&sysfs_mutex); -out: - mutex_unlock(&new_parent_dentry->d_inode->i_mutex); - mutex_unlock(&old_parent_dentry->d_inode->i_mutex); + out_unlock: + mutex_unlock(&new_parent->d_inode->i_mutex); + mutex_unlock(&old_parent->d_inode->i_mutex); + out_dput: + dput(new_parent); + dput(old_dentry); + dput(new_dentry); return error; } @@ -1191,12 +1195,20 @@ static loff_t sysfs_dir_lseek(struct file * file, loff_t offset, int origin) int sysfs_make_shadowed_dir(struct kobject *kobj, void * (*follow_link)(struct dentry *, struct nameidata *)) { + struct dentry *dentry; struct inode *inode; struct inode_operations *i_op; - inode = kobj->sd->s_dentry->d_inode; - if (inode->i_op != &sysfs_dir_inode_operations) + /* get dentry for @kobj->sd, dentry of a shadowed dir is pinned */ + dentry = sysfs_get_dentry(kobj->sd); + if (IS_ERR(dentry)) + return PTR_ERR(dentry); + + inode = dentry->d_inode; + if (inode->i_op != &sysfs_dir_inode_operations) { + dput(dentry); return -EINVAL; + } i_op = kmalloc(sizeof(*i_op), GFP_KERNEL); if (!i_op) @@ -1223,17 +1235,23 @@ int sysfs_make_shadowed_dir(struct kobject *kobj, struct sysfs_dirent *sysfs_create_shadow_dir(struct kobject *kobj) { - struct dentry *dir = kobj->sd->s_dentry; - struct inode *inode = dir->d_inode; - struct dentry *parent = dir->d_parent; - struct sysfs_dirent *parent_sd = parent->d_fsdata; - struct dentry *shadow; + struct sysfs_dirent *parent_sd = kobj->sd->s_parent; + struct dentry *dir, *parent, *shadow; + struct inode *inode; struct sysfs_dirent *sd; struct sysfs_addrm_cxt acxt; + dir = sysfs_get_dentry(kobj->sd); + if (IS_ERR(dir)) { + sd = (void *)dir; + goto out; + } + parent = dir->d_parent; + + inode = dir->d_inode; sd = ERR_PTR(-EINVAL); if (!sysfs_is_shadowed_inode(inode)) - goto out; + goto out_dput; shadow = d_alloc(parent, &dir->d_name); if (!shadow) @@ -1258,12 +1276,15 @@ struct sysfs_dirent *sysfs_create_shadow_dir(struct kobject *kobj) dget(shadow); /* Extra count - pin the dentry in core */ -out: - return sd; -nomem: + goto out_dput; + + nomem: dput(shadow); sd = ERR_PTR(-ENOMEM); - goto out; + out_dput: + dput(dir); + out: + return sd; } /** -- cgit v1.1