fanotify: send events using read
Send events to userspace by reading the file descriptor from fanotify_init(). One will get blocks of data which look like: struct fanotify_event_metadata { __u32 event_len; __u32 vers; __s32 fd; __u64 mask; __s64 pid; __u64 cookie; } __attribute__ ((packed)); Simple code to retrieve and deal with events is below while ((len = read(fan_fd, buf, sizeof(buf))) > 0) { struct fanotify_event_metadata *metadata; metadata = (void *)buf; while(FAN_EVENT_OK(metadata, len)) { [PROCESS HERE!!] if (metadata->fd >= 0 && close(metadata->fd) != 0) goto fail; metadata = FAN_EVENT_NEXT(metadata, len); } } Signed-off-by: Eric Paris <eparis@redhat.com>
This commit is contained in:
parent
2a3edf8604
commit
a1014f1023
3 changed files with 245 additions and 4 deletions
|
@ -30,3 +30,8 @@ static inline bool fanotify_mask_valid(__u32 mask)
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline __u32 fanotify_outgoing_mask(__u32 mask)
|
||||||
|
{
|
||||||
|
return mask & FAN_ALL_OUTGOING_EVENTS;
|
||||||
|
}
|
||||||
|
|
|
@ -4,15 +4,202 @@
|
||||||
#include <linux/anon_inodes.h>
|
#include <linux/anon_inodes.h>
|
||||||
#include <linux/fsnotify_backend.h>
|
#include <linux/fsnotify_backend.h>
|
||||||
#include <linux/init.h>
|
#include <linux/init.h>
|
||||||
|
#include <linux/mount.h>
|
||||||
#include <linux/namei.h>
|
#include <linux/namei.h>
|
||||||
|
#include <linux/poll.h>
|
||||||
#include <linux/security.h>
|
#include <linux/security.h>
|
||||||
#include <linux/syscalls.h>
|
#include <linux/syscalls.h>
|
||||||
#include <linux/types.h>
|
#include <linux/types.h>
|
||||||
|
#include <linux/uaccess.h>
|
||||||
|
|
||||||
|
#include <asm/ioctls.h>
|
||||||
|
|
||||||
#include "fanotify.h"
|
#include "fanotify.h"
|
||||||
|
|
||||||
static struct kmem_cache *fanotify_mark_cache __read_mostly;
|
static struct kmem_cache *fanotify_mark_cache __read_mostly;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get an fsnotify notification event if one exists and is small
|
||||||
|
* enough to fit in "count". Return an error pointer if the count
|
||||||
|
* is not large enough.
|
||||||
|
*
|
||||||
|
* Called with the group->notification_mutex held.
|
||||||
|
*/
|
||||||
|
static struct fsnotify_event *get_one_event(struct fsnotify_group *group,
|
||||||
|
size_t count)
|
||||||
|
{
|
||||||
|
BUG_ON(!mutex_is_locked(&group->notification_mutex));
|
||||||
|
|
||||||
|
pr_debug("%s: group=%p count=%zd\n", __func__, group, count);
|
||||||
|
|
||||||
|
if (fsnotify_notify_queue_is_empty(group))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (FAN_EVENT_METADATA_LEN > count)
|
||||||
|
return ERR_PTR(-EINVAL);
|
||||||
|
|
||||||
|
/* held the notification_mutex the whole time, so this is the
|
||||||
|
* same event we peeked above */
|
||||||
|
return fsnotify_remove_notify_event(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int create_and_fill_fd(struct fsnotify_group *group,
|
||||||
|
struct fanotify_event_metadata *metadata,
|
||||||
|
struct fsnotify_event *event)
|
||||||
|
{
|
||||||
|
int client_fd;
|
||||||
|
struct dentry *dentry;
|
||||||
|
struct vfsmount *mnt;
|
||||||
|
struct file *new_file;
|
||||||
|
|
||||||
|
pr_debug("%s: group=%p metadata=%p event=%p\n", __func__, group,
|
||||||
|
metadata, event);
|
||||||
|
|
||||||
|
client_fd = get_unused_fd();
|
||||||
|
if (client_fd < 0)
|
||||||
|
return client_fd;
|
||||||
|
|
||||||
|
if (event->data_type != FSNOTIFY_EVENT_PATH) {
|
||||||
|
WARN_ON(1);
|
||||||
|
put_unused_fd(client_fd);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* we need a new file handle for the userspace program so it can read even if it was
|
||||||
|
* originally opened O_WRONLY.
|
||||||
|
*/
|
||||||
|
dentry = dget(event->path.dentry);
|
||||||
|
mnt = mntget(event->path.mnt);
|
||||||
|
/* it's possible this event was an overflow event. in that case dentry and mnt
|
||||||
|
* are NULL; That's fine, just don't call dentry open */
|
||||||
|
if (dentry && mnt)
|
||||||
|
new_file = dentry_open(dentry, mnt,
|
||||||
|
O_RDONLY | O_LARGEFILE | FMODE_NONOTIFY,
|
||||||
|
current_cred());
|
||||||
|
else
|
||||||
|
new_file = ERR_PTR(-EOVERFLOW);
|
||||||
|
if (IS_ERR(new_file)) {
|
||||||
|
/*
|
||||||
|
* we still send an event even if we can't open the file. this
|
||||||
|
* can happen when say tasks are gone and we try to open their
|
||||||
|
* /proc files or we try to open a WRONLY file like in sysfs
|
||||||
|
* we just send the errno to userspace since there isn't much
|
||||||
|
* else we can do.
|
||||||
|
*/
|
||||||
|
put_unused_fd(client_fd);
|
||||||
|
client_fd = PTR_ERR(new_file);
|
||||||
|
} else {
|
||||||
|
fd_install(client_fd, new_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata->fd = client_fd;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t fill_event_metadata(struct fsnotify_group *group,
|
||||||
|
struct fanotify_event_metadata *metadata,
|
||||||
|
struct fsnotify_event *event)
|
||||||
|
{
|
||||||
|
pr_debug("%s: group=%p metadata=%p event=%p\n", __func__,
|
||||||
|
group, metadata, event);
|
||||||
|
|
||||||
|
metadata->event_len = FAN_EVENT_METADATA_LEN;
|
||||||
|
metadata->vers = FANOTIFY_METADATA_VERSION;
|
||||||
|
metadata->mask = fanotify_outgoing_mask(event->mask);
|
||||||
|
|
||||||
|
return create_and_fill_fd(group, metadata, event);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t copy_event_to_user(struct fsnotify_group *group,
|
||||||
|
struct fsnotify_event *event,
|
||||||
|
char __user *buf)
|
||||||
|
{
|
||||||
|
struct fanotify_event_metadata fanotify_event_metadata;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
pr_debug("%s: group=%p event=%p\n", __func__, group, event);
|
||||||
|
|
||||||
|
ret = fill_event_metadata(group, &fanotify_event_metadata, event);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if (copy_to_user(buf, &fanotify_event_metadata, FAN_EVENT_METADATA_LEN))
|
||||||
|
return -EFAULT;
|
||||||
|
|
||||||
|
return FAN_EVENT_METADATA_LEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* intofiy userspace file descriptor functions */
|
||||||
|
static unsigned int fanotify_poll(struct file *file, poll_table *wait)
|
||||||
|
{
|
||||||
|
struct fsnotify_group *group = file->private_data;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
poll_wait(file, &group->notification_waitq, wait);
|
||||||
|
mutex_lock(&group->notification_mutex);
|
||||||
|
if (!fsnotify_notify_queue_is_empty(group))
|
||||||
|
ret = POLLIN | POLLRDNORM;
|
||||||
|
mutex_unlock(&group->notification_mutex);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t fanotify_read(struct file *file, char __user *buf,
|
||||||
|
size_t count, loff_t *pos)
|
||||||
|
{
|
||||||
|
struct fsnotify_group *group;
|
||||||
|
struct fsnotify_event *kevent;
|
||||||
|
char __user *start;
|
||||||
|
int ret;
|
||||||
|
DEFINE_WAIT(wait);
|
||||||
|
|
||||||
|
start = buf;
|
||||||
|
group = file->private_data;
|
||||||
|
|
||||||
|
pr_debug("%s: group=%p\n", __func__, group);
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
prepare_to_wait(&group->notification_waitq, &wait, TASK_INTERRUPTIBLE);
|
||||||
|
|
||||||
|
mutex_lock(&group->notification_mutex);
|
||||||
|
kevent = get_one_event(group, count);
|
||||||
|
mutex_unlock(&group->notification_mutex);
|
||||||
|
|
||||||
|
if (kevent) {
|
||||||
|
ret = PTR_ERR(kevent);
|
||||||
|
if (IS_ERR(kevent))
|
||||||
|
break;
|
||||||
|
ret = copy_event_to_user(group, kevent, buf);
|
||||||
|
fsnotify_put_event(kevent);
|
||||||
|
if (ret < 0)
|
||||||
|
break;
|
||||||
|
buf += ret;
|
||||||
|
count -= ret;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = -EAGAIN;
|
||||||
|
if (file->f_flags & O_NONBLOCK)
|
||||||
|
break;
|
||||||
|
ret = -EINTR;
|
||||||
|
if (signal_pending(current))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (start != buf)
|
||||||
|
break;
|
||||||
|
|
||||||
|
schedule();
|
||||||
|
}
|
||||||
|
|
||||||
|
finish_wait(&group->notification_waitq, &wait);
|
||||||
|
if (start != buf && ret != -EFAULT)
|
||||||
|
ret = buf - start;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static int fanotify_release(struct inode *ignored, struct file *file)
|
static int fanotify_release(struct inode *ignored, struct file *file)
|
||||||
{
|
{
|
||||||
struct fsnotify_group *group = file->private_data;
|
struct fsnotify_group *group = file->private_data;
|
||||||
|
@ -25,13 +212,38 @@ static int fanotify_release(struct inode *ignored, struct file *file)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static long fanotify_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||||
|
{
|
||||||
|
struct fsnotify_group *group;
|
||||||
|
struct fsnotify_event_holder *holder;
|
||||||
|
void __user *p;
|
||||||
|
int ret = -ENOTTY;
|
||||||
|
size_t send_len = 0;
|
||||||
|
|
||||||
|
group = file->private_data;
|
||||||
|
|
||||||
|
p = (void __user *) arg;
|
||||||
|
|
||||||
|
switch (cmd) {
|
||||||
|
case FIONREAD:
|
||||||
|
mutex_lock(&group->notification_mutex);
|
||||||
|
list_for_each_entry(holder, &group->notification_list, event_list)
|
||||||
|
send_len += FAN_EVENT_METADATA_LEN;
|
||||||
|
mutex_unlock(&group->notification_mutex);
|
||||||
|
ret = put_user(send_len, (int __user *) p);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static const struct file_operations fanotify_fops = {
|
static const struct file_operations fanotify_fops = {
|
||||||
.poll = NULL,
|
.poll = fanotify_poll,
|
||||||
.read = NULL,
|
.read = fanotify_read,
|
||||||
.fasync = NULL,
|
.fasync = NULL,
|
||||||
.release = fanotify_release,
|
.release = fanotify_release,
|
||||||
.unlocked_ioctl = NULL,
|
.unlocked_ioctl = fanotify_ioctl,
|
||||||
.compat_ioctl = NULL,
|
.compat_ioctl = fanotify_ioctl,
|
||||||
};
|
};
|
||||||
|
|
||||||
static void fanotify_free_mark(struct fsnotify_mark *fsn_mark)
|
static void fanotify_free_mark(struct fsnotify_mark *fsn_mark)
|
||||||
|
|
|
@ -51,6 +51,30 @@
|
||||||
*/
|
*/
|
||||||
#define FAN_ALL_INCOMING_EVENTS (FAN_ALL_EVENTS |\
|
#define FAN_ALL_INCOMING_EVENTS (FAN_ALL_EVENTS |\
|
||||||
FAN_EVENT_ON_CHILD)
|
FAN_EVENT_ON_CHILD)
|
||||||
|
|
||||||
|
#define FAN_ALL_OUTGOING_EVENTS (FAN_ALL_EVENTS |\
|
||||||
|
FAN_Q_OVERFLOW)
|
||||||
|
|
||||||
|
#define FANOTIFY_METADATA_VERSION 1
|
||||||
|
|
||||||
|
struct fanotify_event_metadata {
|
||||||
|
__u32 event_len;
|
||||||
|
__u32 vers;
|
||||||
|
__s32 fd;
|
||||||
|
__u64 mask;
|
||||||
|
} __attribute__ ((packed));
|
||||||
|
|
||||||
|
/* Helper functions to deal with fanotify_event_metadata buffers */
|
||||||
|
#define FAN_EVENT_METADATA_LEN (sizeof(struct fanotify_event_metadata))
|
||||||
|
|
||||||
|
#define FAN_EVENT_NEXT(meta, len) ((len) -= (meta)->event_len, \
|
||||||
|
(struct fanotify_event_metadata*)(((char *)(meta)) + \
|
||||||
|
(meta)->event_len))
|
||||||
|
|
||||||
|
#define FAN_EVENT_OK(meta, len) ((long)(len) >= (long)FAN_EVENT_METADATA_LEN && \
|
||||||
|
(long)(meta)->event_len >= (long)FAN_EVENT_METADATA_LEN && \
|
||||||
|
(long)(meta)->event_len <= (long)(len))
|
||||||
|
|
||||||
#ifdef __KERNEL__
|
#ifdef __KERNEL__
|
||||||
|
|
||||||
#endif /* __KERNEL__ */
|
#endif /* __KERNEL__ */
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue