// SPDX-License-Identifier: GPL-2.0-only
/*
* event_inode.c - part of tracefs, a pseudo file system for activating tracing
*
* Copyright (C) 2020-23 VMware Inc, author: Steven Rostedt <rostedt@goodmis.org>
* Copyright (C) 2020-23 VMware Inc, author: Ajay Kaher <akaher@vmware.com>
* Copyright (C) 2023 Google, author: Steven Rostedt <rostedt@goodmis.org>
*
* eventfs is used to dynamically create inodes and dentries based on the
* meta data provided by the tracing system.
*
* eventfs stores the meta-data of files/dirs and holds off on creating
* inodes/dentries of the files. When accessed, the eventfs will create the
* inodes/dentries in a just-in-time (JIT) manner. The eventfs will clean up
* and delete the inodes/dentries when they are no longer referenced.
*/
#include <linux/fsnotify.h>
#include <linux/fs.h>
#include <linux/namei.h>
#include <linux/workqueue.h>
#include <linux/security.h>
#include <linux/tracefs.h>
#include <linux/kref.h>
#include <linux/delay.h>
#include "internal.h"
/*
* eventfs_mutex protects the eventfs_inode (ei) dentry. Any access
* to the ei->dentry must be done under this mutex and after checking
* if ei->is_freed is not set. The ei->dentry is released under the
* mutex at the same time ei->is_freed is set. If ei->is_freed is set
* then the ei->dentry is invalid.
*/
static DEFINE_MUTEX(eventfs_mutex);
/*
* The eventfs_inode (ei) itself is protected by SRCU. It is released from
* its parent's list and will have is_freed set (under eventfs_mutex).
* After the SRCU grace period is over, the ei may be freed.
*/
DEFINE_STATIC_SRCU(eventfs_srcu);
/* Mode is unsigned short, use the upper bits for flags */
enum {
EVENTFS_SAVE_MODE = BIT(16),
EVENTFS_SAVE_UID = BIT(17),
EVENTFS_SAVE_GID = BIT(18),
};
#define EVENTFS_MODE_MASK (EVENTFS_SAVE_MODE - 1)
static struct dentry *eventfs_root_lookup(struct inode *dir,
struct dentry *dentry,
unsigned int flags);
static int dcache_dir_open_wrapper(struct inode *inode, struct file *file);
static int dcache_readdir_wrapper(struct file *file, struct dir_context *ctx);
static int eventfs_release(struct inode *inode, struct file *file);
static void update_attr(struct eventfs_attr *attr, struct iattr *iattr)
{
unsigned int ia_valid = iattr->ia_valid;
if (ia_valid & ATTR_MODE) {
attr->mode = (attr->mode & ~EVENTFS_MODE_MASK) |
(iattr->ia_mode & EVENTFS_MODE_MASK) |
EVENTFS_SAVE_MODE;
}
if (ia_valid & ATTR_UID) {
attr->mode |= EVENTFS_SAVE_UID;
attr->uid = iattr->ia_uid;
}
if (ia_valid & ATTR_GID) {
attr->mode |= EVENTFS_SAVE_GID;
attr->gid = iattr->ia_gid;
}
}
static int eventfs_set_attr(struct mnt_idmap *idmap, struct dentry *dentry,
struct iattr *iattr)
{
const struct eventfs_entry *entry;
struct eventfs_inode *ei;
const char *name;
int ret;
mutex_lock(&eventfs_mutex);
ei = dentry->d_fsdata;
if (ei->is_freed) {
/* Do not allow changes if the event is about to be removed. */
mutex_unlock(&eventfs_mutex);
return -ENODEV;
}
/* Preallocate the children mode array if necessary */
if (!(dentry->d_inode->i_mode & S_IFDIR)) {
if (!ei->entry_attrs) {
ei->entry_attrs = kzalloc(sizeof(*ei->entry_attrs) * ei->nr_entries,
GFP_KERNEL);
if (!ei->entry_attrs) {
ret = -ENOMEM;
goto out;
}
}
}
ret = simple_setattr(idmap, dentry, iattr);
if (ret < 0)
goto out;
/*
* If this is a dir, then update the ei cache, only the file
* mode is saved in the ei->m_children, and the ownership is
* determined by the parent directory.
*/
if (dentry->d_inode->i_mode & S_IFDIR) {
update_attr(&ei->attr, iattr);
} else {
name = dentry->d_name.name;
for (int i = 0; i < ei->nr_entries; i++) {
entry = &ei->entries[i];
if (strcmp(name, entry->name) == 0) {
update_attr(&ei->entry_attrs[i], iattr);
break;
}
}
}
out:
mutex_unlock(&eventfs_mutex);
return ret;
}
static const struct inode_operations eventfs_root_dir_inode_operations = {
.lookup = eventfs_root_lookup,
.setattr = eventfs_set_attr,
};
static const struct inode_operations eventfs_file_inode_operations = {
.setattr = eventfs_set_attr,
};
static const struct file_operations eventfs_file_operations = {
.open = dcache_dir_open_wrapper,
.read = generic_read_dir,
.iterate_shared = dcache_readdir_wrapper,
.llseek = generic_file_llseek,
.release = eventfs_release,
};
static void update_inode_attr(struct inode *inode, struct eventfs_attr *attr, umode_t mode)
{
if (!attr) {
inode->i_mode = mode;
return;
}
if (attr->mode & EVENTFS_SAVE_MODE)
inode->i_mode = attr->mode & EVENTFS_MODE_MASK;
else
inode->i_mode = mode;
if (attr->mode & EVENTFS_SAVE_UID)
inode->i_uid = attr->uid;
if (attr->