@@ -294,9 +294,13 @@ void release_sysfs_dirent(struct sysfs_dirent * sd)
goto repeat;
}
-static void sysfs_d_iput(struct dentry * dentry, struct inode * inode)
+static void sysfs_d_iput(struct dentry *dentry, struct inode *inode)
{
- struct sysfs_dirent * sd = dentry->d_fsdata;
+ struct sysfs_dirent *sd = dentry->d_fsdata;
+
+ if ((sysfs_type(sd) == SYSFS_DIR) && sd->s_dir.depopulate)
+ sd->s_dir.depopulate(dentry, sd);
+ sd->s_flags &= ~SYSFS_FLAG_POPULATED;
sysfs_put(sd);
iput(inode);
@@ -574,6 +578,22 @@ repeat:
iput(inode);
}
+void sysfs_kill_removed_dirents(struct sysfs_addrm_cxt *acxt)
+{
+ /* 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);
+ unmap_bin_file(sd);
+ sysfs_put(sd);
+ }
+}
+
/**
* sysfs_addrm_finish - finish up sysfs_dirent add/remove
* @acxt: addrm context to finish up
@@ -600,18 +620,7 @@ void sysfs_addrm_finish(struct sysfs_addrm_cxt *acxt)
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);
- unmap_bin_file(sd);
- sysfs_put(sd);
- }
+ sysfs_kill_removed_dirents(acxt);
}
/**
@@ -731,6 +740,17 @@ static struct dentry * sysfs_lookup(struct inode *dir, struct dentry *dentry,
mutex_lock(&sysfs_mutex);
+ if (!(parent_sd->s_flags & SYSFS_FLAG_POPULATED)) {
+ if (parent_sd->s_dir.populate) {
+ int err = parent_sd->s_dir.populate(dentry->d_parent,
+ parent_sd);
+ if (err) {
+ ret = ERR_PTR(err);
+ goto out_unlock;
+ }
+ }
+ parent_sd->s_flags |= SYSFS_FLAG_POPULATED;
+ }
sd = sysfs_find_dirent(parent_sd, dentry->d_name.name);
/* no such entry */
@@ -1012,7 +1032,6 @@ static int sysfs_readdir(struct file * filp, void * dirent, filldir_t filldir)
return 0;
}
-
const struct file_operations sysfs_dir_operations = {
.read = generic_read_dir,
.readdir = sysfs_readdir,
@@ -16,6 +16,102 @@
#include "sysfs.h"
+/*
+ * i_mutex is not held, but this inode is on its way out of the system, so
+ * nobody gets to mess with it. We can't take the sysfs_mutex here as it
+ * leads to a deadlock scenario where it's held in a path that can run
+ * reclaim, and this function can be called from reclaim context. I guess
+ * prayer is the only solution here (other than splitting sysfs_mutex)
+ */
+void sysfs_depopulate_group(struct dentry *dentry,
+ const struct attribute_group *grp)
+
+{
+ struct sysfs_dirent *dir_sd = dentry->d_fsdata;
+ struct sysfs_addrm_cxt acxt;
+ struct attribute **attrp;
+
+ memset(&acxt, 0, sizeof(acxt));
+ acxt.parent_sd = dir_sd;
+ acxt.parent_inode = dentry->d_inode;
+
+ for (attrp = grp->attrs; *attrp; attrp++) {
+ struct attribute *attr = *attrp;
+ struct sysfs_dirent *sd;
+
+ sd = sysfs_find_dirent(dir_sd, attr->name);
+ if (sd)
+ sysfs_remove_one(&acxt, sd);
+ }
+
+ sysfs_kill_removed_dirents(&acxt);
+}
+EXPORT_SYMBOL(sysfs_depopulate_group);
+
+/*
+ * inode->i_mutex is held by the VFS, and sysfs_mutex is held by
+ * sysfs_lookup, so there's no need to call sysfs_addrm_start/finish here.
+ * Nor is there a need to mess around with reference counts.
+ */
+int sysfs_populate_group(struct dentry *dentry,
+ const struct attribute_group *grp)
+
+{
+ struct sysfs_dirent *dir_sd = dentry->d_fsdata;
+ struct kobject *kobj = dir_sd->s_dir.kobj;
+ struct sysfs_addrm_cxt acxt;
+ struct attribute **attrp;
+ int i = 0, error = 0;
+
+ memset(&acxt, 0, sizeof(acxt));
+ acxt.parent_sd = dir_sd;
+ acxt.parent_inode = dentry->d_inode;
+
+ for (attrp = grp->attrs; *attrp; attrp++) {
+ struct attribute *attr = *attrp;
+ mode_t mode = attr->mode;
+ struct sysfs_dirent *sd;
+
+ if (grp->is_visible) {
+ mode_t vis = grp->is_visible(kobj, attr, i++);
+ if (!vis)
+ continue;
+ mode |= vis;
+ }
+
+ sd = sysfs_new_dirent(attr->name, mode, SYSFS_KOBJ_ATTR);
+ if (!sd) {
+ error = -ENOMEM;
+ break;
+ }
+ sd->s_attr.attr = (void *)attr;
+
+ error = sysfs_add_one(&acxt, sd);
+ if (error) {
+ sysfs_put(sd);
+ break;
+ }
+ }
+ if (error)
+ sysfs_depopulate_group(dentry, grp);
+ return error;
+}
+EXPORT_SYMBOL(sysfs_populate_group);
+
+static
+int group_populate(struct dentry *dentry, struct sysfs_dirent *sd)
+{
+ struct attribute_group *grp = sd->s_dir.data;
+ return sysfs_populate_group(dentry, grp);
+}
+
+static
+void group_depopulate(struct dentry *dentry, struct sysfs_dirent *sd)
+{
+ struct attribute_group *grp = sd->s_dir.data;
+ sysfs_depopulate_group(dentry, grp);
+}
+
static void remove_files(struct sysfs_dirent *dir_sd, struct kobject *kobj,
const struct attribute_group *grp)
{
@@ -32,6 +128,10 @@ static int create_files(struct sysfs_dirent *dir_sd, struct kobject *kobj,
struct attribute *const* attr;
int error = 0, i;
+ /* Directory isn't currently instantiated; nothing to do */
+ if (!(dir_sd->s_flags & SYSFS_FLAG_POPULATED))
+ return 0;
+
for (i = 0, attr = grp->attrs; *attr && !error; i++, attr++) {
mode_t mode = 0;
@@ -55,7 +155,6 @@ static int create_files(struct sysfs_dirent *dir_sd, struct kobject *kobj,
return error;
}
-
static int internal_create_group(struct kobject *kobj, int update,
const struct attribute_group *grp)
{
@@ -82,10 +181,18 @@ static int internal_create_group(struct kobject *kobj, int update,
} else {
sd = sysfs_get(kobj->sd);
}
- error = create_files(sd, kobj, grp, update);
- if (error) {
- if (grp->name)
+
+ error = 0;
+ if (update) {
+ error = create_files(sd, kobj, grp, update);
+ if (error && grp->name)
sysfs_remove_subdir(sd);
+ } else if (!sd->s_dir.populate) {
+ sd->s_dir.populate = group_populate;
+ sd->s_dir.depopulate = group_depopulate;
+ sd->s_dir.data = (void *)grp;
+ } else if (sd->s_dir.populate != group_populate) {
+ error = -EEXIST;
}
sysfs_put(sd);
return error;
@@ -17,6 +17,9 @@ struct sysfs_elem_dir {
struct kobject *kobj;
/* children list starts here and goes through sd->s_sibling */
struct sysfs_dirent *children;
+ int (*populate)(struct dentry *, struct sysfs_dirent *);
+ void (*depopulate)(struct dentry *, struct sysfs_dirent *);
+ void *data;
};
struct sysfs_elem_symlink {
@@ -77,6 +80,7 @@ struct sysfs_dirent {
#define SYSFS_COPY_NAME (SYSFS_DIR | SYSFS_KOBJ_LINK)
#define SYSFS_FLAG_MASK ~SYSFS_TYPE_MASK
+#define SYSFS_FLAG_POPULATED 0x0100
#define SYSFS_FLAG_REMOVED 0x0200
static inline unsigned int sysfs_type(struct sysfs_dirent *sd)
@@ -120,6 +124,7 @@ int __sysfs_add_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd);
int sysfs_add_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd);
void sysfs_remove_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd);
void sysfs_addrm_finish(struct sysfs_addrm_cxt *acxt);
+void sysfs_kill_removed_dirents(struct sysfs_addrm_cxt *acxt);
struct sysfs_dirent *sysfs_find_dirent(struct sysfs_dirent *parent_sd,
const unsigned char *name);