On Tue, 2018-07-03 at 12:39 +1000, Benjamin Herrenschmidt wrote: > On Mon, 2018-07-02 at 19:26 -0700, Linus Torvalds wrote: > > On Mon, Jul 2, 2018 at 7:15 PM Linus Torvalds > > <torva...@linux-foundation.org> wrote: > > > > > > It's whitespace-damaged on purpose. It's probably broken shit. DO NOT > > > USE UNDER ANY CIRCUMSTANCES. Think of it more as a "something like > > > this might work, but probably doesn't". Maybe it gives you an idea, > > > although that idea might be "Linus has finally lost it". > > > > Even if it were to work, it should probably just be done inside kernfs > > and exposed as some kind of "kernfs_remove_if_empty()" function. > > That would be harder, we'd have to expose it via a > kobject_del_if_empty() then. > > Since we control completely when things are added/removed from the > gluedir and have a big fat mutex for it, I don't think locking is much > of an issue. At least in that specific case.
Ok, kernfs was a tad tougher to crack than I hoped but here's a prototype lightly tested. Tejun, Greg, does it look reasonable to you ? Cheers, Ben. diff --git a/drivers/base/core.c b/drivers/base/core.c index b610816eb887..9166f68276c6 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -1570,6 +1570,8 @@ static void cleanup_glue_dir(struct device *dev, struct kobject *glue_dir) return; mutex_lock(&gdp_mutex); + if (!kobject_has_children(glue_dir)) + kobject_del(glue_dir); kobject_put(glue_dir); mutex_unlock(&gdp_mutex); } diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c index 89d1dc19340b..e4bec09bc602 100644 --- a/fs/kernfs/dir.c +++ b/fs/kernfs/dir.c @@ -1251,6 +1251,29 @@ void kernfs_activate(struct kernfs_node *kn) mutex_unlock(&kernfs_mutex); } +bool kernfs_has_children(struct kernfs_node *kn) +{ + bool has_children = false; + struct kernfs_node *pos; + + /* Lockless shortcut */ + if (RB_EMPTY_NODE(&kn->rb)) + return false; + + /* Now check for active children */ + mutex_lock(&kernfs_mutex); + pos = NULL; + while ((pos = kernfs_next_descendant_post(pos, kn)) && pos != kn) { + if (kernfs_active(pos)) { + has_children = true; + break; + } + } + mutex_unlock(&kernfs_mutex); + + return has_children; +} + static void __kernfs_remove(struct kernfs_node *kn) { struct kernfs_node *pos; diff --git a/include/linux/kernfs.h b/include/linux/kernfs.h index ab25c8b6d9e3..776350ac1cbc 100644 --- a/include/linux/kernfs.h +++ b/include/linux/kernfs.h @@ -300,6 +300,12 @@ static inline bool kernfs_ns_enabled(struct kernfs_node *kn) return kn->flags & KERNFS_NS; } +/** + * kernfs_has_children - Returns whether a kernfs node has children. + * @kn: the node to test + */ +bool kernfs_has_children(struct kernfs_node *kn); + int kernfs_name(struct kernfs_node *kn, char *buf, size_t buflen); int kernfs_path_from_node(struct kernfs_node *root_kn, struct kernfs_node *kn, char *buf, size_t buflen); diff --git a/include/linux/kobject.h b/include/linux/kobject.h index 7f6f93c3df9c..1e3294ab95f0 100644 --- a/include/linux/kobject.h +++ b/include/linux/kobject.h @@ -116,6 +116,17 @@ extern void kobject_put(struct kobject *kobj); extern const void *kobject_namespace(struct kobject *kobj); extern char *kobject_get_path(struct kobject *kobj, gfp_t flag); +/** + * kobject_has_children - Returns whether a kobject has children. + * @kobj: the object to test + */ +static inline bool kobject_has_children(struct kobject *kobj) +{ + WARN_ON_ONCE(kref_read(&kobj->kref) == 0); + + return kobj->sd && kernfs_has_children(kobj->sd); +} + struct kobj_type { void (*release)(struct kobject *kobj); const struct sysfs_ops *sysfs_ops;