@@ -57,7 +57,9 @@ enum {
static struct dentry *eventfs_root_lookup(struct inode *dir,
struct dentry *dentry,
unsigned int flags);
+static int eventfs_dir_open(struct inode *inode, struct file *file);
static int eventfs_iterate(struct file *file, struct dir_context *ctx);
+static int eventfs_dir_release(struct inode *inode, struct file *file);
static void update_attr(struct eventfs_attr *attr, struct iattr *iattr)
{
@@ -216,7 +218,9 @@ static const struct inode_operations eventfs_file_inode_operations = {
static const struct file_operations eventfs_file_operations = {
.read = generic_read_dir,
+ .open = eventfs_dir_open,
.iterate_shared = eventfs_iterate,
+ .release = eventfs_dir_release,
.llseek = generic_file_llseek,
};
@@ -716,117 +720,198 @@ static struct dentry *eventfs_root_lookup(struct inode *dir,
return ret;
}
+struct eventfs_dents {
+ const char *name;
+ int ino;
+ int type;
+};
+
+struct eventfs_list {
+ struct eventfs_dents *dents;
+ int count;
+};
+
+static int update_entry(struct eventfs_dents *dents, const char *name,
+ int type, int cnt)
+{
+ dents[cnt].name = kstrdup_const(name, GFP_KERNEL);
+ if (!dents[cnt].name)
+ return -ENOMEM;
+ if (type == DT_DIR)
+ dents[cnt].ino = EVENTFS_DIR_INODE_INO;
+ else
+ dents[cnt].ino = EVENTFS_FILE_INODE_INO;
+ dents[cnt].type = type;
+ return 0;
+}
+
+static int add_entry(struct eventfs_dents **edents, const char *name,
+ int type, int cnt)
+{
+ struct eventfs_dents *tmp;
+
+ tmp = krealloc(*edents, sizeof(**edents) * (cnt + 1), GFP_NOFS);
+ if (!tmp)
+ return -ENOMEM;
+ *edents = tmp;
+
+ return update_entry(tmp, name, type, cnt);
+}
+
/*
* Walk the children of a eventfs_inode to fill in getdents().
*/
-static int eventfs_iterate(struct file *file, struct dir_context *ctx)
+static int eventfs_dir_open(struct inode *inode, struct file *file)
{
const struct file_operations *fops;
struct inode *f_inode = file_inode(file);
const struct eventfs_entry *entry;
+ struct eventfs_list *edents;
struct eventfs_inode *ei_child;
struct tracefs_inode *ti;
struct eventfs_inode *ei;
- struct dentry *ei_dentry = NULL;
- struct dentry *dentry;
+ struct eventfs_dents *dents;
const char *name;
umode_t mode;
+ void *data;
+ int cnt = 0;
int idx;
- int ret = -EINVAL;
- int ino;
- int i, r, c;
-
- if (!dir_emit_dots(file, ctx))
- return 0;
+ int ret;
+ int i;
+ int r;
ti = get_tracefs(f_inode);
if (!(ti->flags & TRACEFS_EVENT_INODE))
return -EINVAL;
- c = ctx->pos - 2;
+ if (WARN_ON_ONCE(file->private_data))
+ return -EINVAL;
idx = srcu_read_lock(&eventfs_srcu);
mutex_lock(&eventfs_mutex);
ei = READ_ONCE(ti->private);
- if (ei && !ei->is_freed)
- ei_dentry = READ_ONCE(ei->dentry);
+ if (ei && ei->is_freed)
+ ei = NULL;
mutex_unlock(&eventfs_mutex);
- if (!ei || !ei_dentry)
- goto out;
+ if (!ei) {
+ srcu_read_unlock(&eventfs_srcu, idx);
+ return -ENOENT;
+ }
+
+ data = ei->data;
+
+ edents = kmalloc(sizeof(*edents), GFP_KERNEL);
+ if (!edents) {
+ srcu_read_unlock(&eventfs_srcu, idx);
+ return -ENOMEM;
+ }
/*
- * Need to create the dentries and inodes to have a consistent
- * inode number.
+ * Need to make a struct eventfs_dent array, start by
+ * allocating enough for just the files, which is a fixed
+ * array. Then use realloc via add_entry() for the directories
+ * which is stored in a linked list.
*/
- ret = 0;
-
- /* Start at 'c' to jump over already read entries */
- for (i = c; i < ei->nr_entries; i++, ctx->pos++) {
- void *cdata = ei->data;
+ dents = kcalloc(ei->nr_entries, sizeof(*dents), GFP_KERNEL);
+ if (!dents) {
+ srcu_read_unlock(&eventfs_srcu, idx);
+ kfree(edents);
+ return -ENOMEM;
+ }
+ for (i = 0; i < ei->nr_entries; i++) {
+ void *cdata = data;
entry = &ei->entries[i];
name = entry->name;
-
mutex_lock(&eventfs_mutex);
- /* If ei->is_freed then just bail here, nothing more to do */
- if (ei->is_freed) {
- mutex_unlock(&eventfs_mutex);
- goto out;
- }
- r = entry->callback(name, &mode, &cdata, &fops);
+ /* If ei->is_freed, then the event itself may be too */
+ if (!ei->is_freed)
+ r = entry->callback(name, &mode, &cdata, &fops);
+ else
+ r = -1;
mutex_unlock(&eventfs_mutex);
- if (r <= 0)
+ /* If the ei is being freed, no need to continue */
+ if (r < 0) {
+ ret = -ENOENT;
+ goto fail;
+ }
+ /* callbacks returning zero just means skip this file */
+ if (r == 0)
continue;
+ ret = update_entry(dents, name, DT_REG, cnt);
- dentry = create_file_dentry(ei, i, ei_dentry, name, mode, cdata, fops);
- if (!dentry)
- goto out;
- ino = dentry->d_inode->i_ino;
- dput(dentry);
+ if (ret < 0)
+ goto fail;
- if (!dir_emit(ctx, name, strlen(name), ino, DT_REG))
- goto out;
+ cnt++;
}
- /* Subtract the skipped entries above */
- c -= min((unsigned int)c, (unsigned int)ei->nr_entries);
-
list_for_each_entry_srcu(ei_child, &ei->children, list,
srcu_read_lock_held(&eventfs_srcu)) {
+ ret = add_entry(&dents, ei_child->name, DT_DIR, cnt);
+ if (ret < 0)
+ goto fail;
+ cnt++;
+ }
- if (c > 0) {
- c--;
- continue;
- }
+ edents->count = cnt;
+ edents->dents = dents;
- ctx->pos++;
+ srcu_read_unlock(&eventfs_srcu, idx);
+ file->private_data = edents;
+ return 0;
+ fail:
+ srcu_read_unlock(&eventfs_srcu, idx);
+ for (; cnt >= 0; cnt--)
+ kfree_const(dents[cnt].name);
+ kfree(dents);
+ kfree(edents);
+ return ret;
+}
- if (ei_child->is_freed)
- continue;
+static int eventfs_dir_release(struct inode *inode, struct file *file)
+{
+ struct eventfs_list *edents = file->private_data;
+ struct tracefs_inode *ti;
+ int i;
- name = ei_child->name;
+ ti = get_tracefs(inode);
+ if (!(ti->flags & TRACEFS_EVENT_INODE))
+ return -EINVAL;
- dentry = create_dir_dentry(ei, ei_child, ei_dentry);
- if (!dentry)
- goto out_dec;
- ino = dentry->d_inode->i_ino;
- dput(dentry);
+ if (WARN_ON_ONCE(!edents))
+ return -EINVAL;
- if (!dir_emit(ctx, name, strlen(name), ino, DT_DIR))
- goto out_dec;
+ for (i = 0; i < edents->count; i++) {
+ kfree_const(edents->dents[i].name);
}
- ret = 1;
- out:
- srcu_read_unlock(&eventfs_srcu, idx);
- return ret;
+ kfree(edents->dents);
+ kfree(edents);
+ return 0;
+}
+
+static int eventfs_iterate(struct file *file, struct dir_context *ctx)
+{
+ struct eventfs_list *edents = file->private_data;
+ int i, c;
- out_dec:
- /* Incremented ctx->pos without adding something, reset it */
- ctx->pos--;
- goto out;
+ if (!dir_emit_dots(file, ctx))
+ return 0;
+
+ c = ctx->pos - 2;
+
+ /* Start at 'c' to jump over already read entries */
+ for (i = c; i < edents->count; i++, ctx->pos++) {
+
+ if (!dir_emit(ctx, edents->dents[i].name,
+ strlen(edents->dents[i].name),
+ edents->dents[i].ino, edents->dents[i].type))
+ break;
+ }
+ return 0;
}
/**