* [PATCH -v2 01/16] filesystem notification: create fs/notify to contain all fs notification
2008-10-14 20:51 [PATCH -v2 00/16] fanotify: novel all file access notification and permissions system Eric Paris
@ 2008-10-14 20:51 ` Eric Paris
2008-10-14 20:51 ` [PATCH -v2 02/16] fsnotify: pass a file instead of an inode to open, read, and write Eric Paris
` (14 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Eric Paris @ 2008-10-14 20:51 UTC (permalink / raw)
To: linux-kernel, malware-list; +Cc: viro, alan, arjan, greg, tytso
Adding yet another filesystem notification system it seemed like a good
idea to clean up fs/ but creating an fs/notify and putting everything
there.
Signed-off-by: Eric Paris <eparis@redhat.com>
---
fs/Kconfig | 39 --
fs/Makefile | 5
fs/dnotify.c | 194 -----------
fs/inotify.c | 773 ----------------------------------------------
fs/inotify_user.c | 781 ----------------------------------------------
fs/notify/Kconfig | 38 ++
fs/notify/Makefile | 4
fs/notify/dnotify.c | 194 +++++++++++
fs/notify/inotify.c | 773 ++++++++++++++++++++++++++++++++++++++++++++++
fs/notify/inotify_user.c | 781 ++++++++++++++++++++++++++++++++++++++++++++++
10 files changed, 1792 insertions(+), 1790 deletions(-)
delete mode 100644 fs/dnotify.c
delete mode 100644 fs/inotify.c
delete mode 100644 fs/inotify_user.c
create mode 100644 fs/notify/Kconfig
create mode 100644 fs/notify/Makefile
create mode 100644 fs/notify/dnotify.c
create mode 100644 fs/notify/inotify.c
create mode 100644 fs/notify/inotify_user.c
diff --git a/fs/Kconfig b/fs/Kconfig
index f54a157..e5e0959 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -513,44 +513,7 @@ config OCFS2_DEBUG_FS
endif # BLOCK
-config DNOTIFY
- bool "Dnotify support"
- default y
- help
- Dnotify is a directory-based per-fd file change notification system
- that uses signals to communicate events to user-space. There exist
- superior alternatives, but some applications may still rely on
- dnotify.
-
- If unsure, say Y.
-
-config INOTIFY
- bool "Inotify file change notification support"
- default y
- ---help---
- Say Y here to enable inotify support. Inotify is a file change
- notification system and a replacement for dnotify. Inotify fixes
- numerous shortcomings in dnotify and introduces several new features
- including multiple file events, one-shot support, and unmount
- notification.
-
- For more information, see <file:Documentation/filesystems/inotify.txt>
-
- If unsure, say Y.
-
-config INOTIFY_USER
- bool "Inotify support for userspace"
- depends on INOTIFY
- default y
- ---help---
- Say Y here to enable inotify support for userspace, including the
- associated system calls. Inotify allows monitoring of both files and
- directories via a single open fd. Events are read from the file
- descriptor, which is also select()- and poll()-able.
-
- For more information, see <file:Documentation/filesystems/inotify.txt>
-
- If unsure, say Y.
+source "fs/notify/Kconfig"
config QUOTA
bool "Quota support"
diff --git a/fs/Makefile b/fs/Makefile
index de404b0..b101855 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -20,8 +20,7 @@ obj-y += no-block.o
endif
obj-$(CONFIG_BLK_DEV_INTEGRITY) += bio-integrity.o
-obj-$(CONFIG_INOTIFY) += inotify.o
-obj-$(CONFIG_INOTIFY_USER) += inotify_user.o
+obj-y += notify/
obj-$(CONFIG_EPOLL) += eventpoll.o
obj-$(CONFIG_ANON_INODES) += anon_inodes.o
obj-$(CONFIG_SIGNALFD) += signalfd.o
@@ -55,8 +54,6 @@ obj-$(CONFIG_QFMT_V1) += quota_v1.o
obj-$(CONFIG_QFMT_V2) += quota_v2.o
obj-$(CONFIG_QUOTACTL) += quota.o
-obj-$(CONFIG_DNOTIFY) += dnotify.o
-
obj-$(CONFIG_PROC_FS) += proc/
obj-y += partitions/
obj-$(CONFIG_SYSFS) += sysfs/
diff --git a/fs/dnotify.c b/fs/dnotify.c
deleted file mode 100644
index 676073b..0000000
--- a/fs/dnotify.c
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Directory notifications for Linux.
- *
- * Copyright (C) 2000,2001,2002 Stephen Rothwell
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the
- * Free Software Foundation; either version 2, or (at your option) any
- * later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- */
-#include <linux/fs.h>
-#include <linux/module.h>
-#include <linux/sched.h>
-#include <linux/dnotify.h>
-#include <linux/init.h>
-#include <linux/spinlock.h>
-#include <linux/slab.h>
-#include <linux/fdtable.h>
-
-int dir_notify_enable __read_mostly = 1;
-
-static struct kmem_cache *dn_cache __read_mostly;
-
-static void redo_inode_mask(struct inode *inode)
-{
- unsigned long new_mask;
- struct dnotify_struct *dn;
-
- new_mask = 0;
- for (dn = inode->i_dnotify; dn != NULL; dn = dn->dn_next)
- new_mask |= dn->dn_mask & ~DN_MULTISHOT;
- inode->i_dnotify_mask = new_mask;
-}
-
-void dnotify_flush(struct file *filp, fl_owner_t id)
-{
- struct dnotify_struct *dn;
- struct dnotify_struct **prev;
- struct inode *inode;
-
- inode = filp->f_path.dentry->d_inode;
- if (!S_ISDIR(inode->i_mode))
- return;
- spin_lock(&inode->i_lock);
- prev = &inode->i_dnotify;
- while ((dn = *prev) != NULL) {
- if ((dn->dn_owner == id) && (dn->dn_filp == filp)) {
- *prev = dn->dn_next;
- redo_inode_mask(inode);
- kmem_cache_free(dn_cache, dn);
- break;
- }
- prev = &dn->dn_next;
- }
- spin_unlock(&inode->i_lock);
-}
-
-int fcntl_dirnotify(int fd, struct file *filp, unsigned long arg)
-{
- struct dnotify_struct *dn;
- struct dnotify_struct *odn;
- struct dnotify_struct **prev;
- struct inode *inode;
- fl_owner_t id = current->files;
- struct file *f;
- int error = 0;
-
- if ((arg & ~DN_MULTISHOT) == 0) {
- dnotify_flush(filp, id);
- return 0;
- }
- if (!dir_notify_enable)
- return -EINVAL;
- inode = filp->f_path.dentry->d_inode;
- if (!S_ISDIR(inode->i_mode))
- return -ENOTDIR;
- dn = kmem_cache_alloc(dn_cache, GFP_KERNEL);
- if (dn == NULL)
- return -ENOMEM;
- spin_lock(&inode->i_lock);
- prev = &inode->i_dnotify;
- while ((odn = *prev) != NULL) {
- if ((odn->dn_owner == id) && (odn->dn_filp == filp)) {
- odn->dn_fd = fd;
- odn->dn_mask |= arg;
- inode->i_dnotify_mask |= arg & ~DN_MULTISHOT;
- goto out_free;
- }
- prev = &odn->dn_next;
- }
-
- rcu_read_lock();
- f = fcheck(fd);
- rcu_read_unlock();
- /* we'd lost the race with close(), sod off silently */
- /* note that inode->i_lock prevents reordering problems
- * between accesses to descriptor table and ->i_dnotify */
- if (f != filp)
- goto out_free;
-
- error = __f_setown(filp, task_pid(current), PIDTYPE_PID, 0);
- if (error)
- goto out_free;
-
- dn->dn_mask = arg;
- dn->dn_fd = fd;
- dn->dn_filp = filp;
- dn->dn_owner = id;
- inode->i_dnotify_mask |= arg & ~DN_MULTISHOT;
- dn->dn_next = inode->i_dnotify;
- inode->i_dnotify = dn;
- spin_unlock(&inode->i_lock);
-
- if (filp->f_op && filp->f_op->dir_notify)
- return filp->f_op->dir_notify(filp, arg);
- return 0;
-
-out_free:
- spin_unlock(&inode->i_lock);
- kmem_cache_free(dn_cache, dn);
- return error;
-}
-
-void __inode_dir_notify(struct inode *inode, unsigned long event)
-{
- struct dnotify_struct * dn;
- struct dnotify_struct **prev;
- struct fown_struct * fown;
- int changed = 0;
-
- spin_lock(&inode->i_lock);
- prev = &inode->i_dnotify;
- while ((dn = *prev) != NULL) {
- if ((dn->dn_mask & event) == 0) {
- prev = &dn->dn_next;
- continue;
- }
- fown = &dn->dn_filp->f_owner;
- send_sigio(fown, dn->dn_fd, POLL_MSG);
- if (dn->dn_mask & DN_MULTISHOT)
- prev = &dn->dn_next;
- else {
- *prev = dn->dn_next;
- changed = 1;
- kmem_cache_free(dn_cache, dn);
- }
- }
- if (changed)
- redo_inode_mask(inode);
- spin_unlock(&inode->i_lock);
-}
-
-EXPORT_SYMBOL(__inode_dir_notify);
-
-/*
- * This is hopelessly wrong, but unfixable without API changes. At
- * least it doesn't oops the kernel...
- *
- * To safely access ->d_parent we need to keep d_move away from it. Use the
- * dentry's d_lock for this.
- */
-void dnotify_parent(struct dentry *dentry, unsigned long event)
-{
- struct dentry *parent;
-
- if (!dir_notify_enable)
- return;
-
- spin_lock(&dentry->d_lock);
- parent = dentry->d_parent;
- if (parent->d_inode->i_dnotify_mask & event) {
- dget(parent);
- spin_unlock(&dentry->d_lock);
- __inode_dir_notify(parent->d_inode, event);
- dput(parent);
- } else {
- spin_unlock(&dentry->d_lock);
- }
-}
-EXPORT_SYMBOL_GPL(dnotify_parent);
-
-static int __init dnotify_init(void)
-{
- dn_cache = kmem_cache_create("dnotify_cache",
- sizeof(struct dnotify_struct), 0, SLAB_PANIC, NULL);
- return 0;
-}
-
-module_init(dnotify_init)
diff --git a/fs/inotify.c b/fs/inotify.c
deleted file mode 100644
index 690e725..0000000
--- a/fs/inotify.c
+++ /dev/null
@@ -1,773 +0,0 @@
-/*
- * fs/inotify.c - inode-based file event notifications
- *
- * Authors:
- * John McCutchan <ttb@tentacle.dhs.org>
- * Robert Love <rml@novell.com>
- *
- * Kernel API added by: Amy Griffis <amy.griffis@hp.com>
- *
- * Copyright (C) 2005 John McCutchan
- * Copyright 2006 Hewlett-Packard Development Company, L.P.
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the
- * Free Software Foundation; either version 2, or (at your option) any
- * later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- */
-
-#include <linux/module.h>
-#include <linux/kernel.h>
-#include <linux/spinlock.h>
-#include <linux/idr.h>
-#include <linux/slab.h>
-#include <linux/fs.h>
-#include <linux/sched.h>
-#include <linux/init.h>
-#include <linux/list.h>
-#include <linux/writeback.h>
-#include <linux/inotify.h>
-
-static atomic_t inotify_cookie;
-
-/*
- * Lock ordering:
- *
- * dentry->d_lock (used to keep d_move() away from dentry->d_parent)
- * iprune_mutex (synchronize shrink_icache_memory())
- * inode_lock (protects the super_block->s_inodes list)
- * inode->inotify_mutex (protects inode->inotify_watches and watches->i_list)
- * inotify_handle->mutex (protects inotify_handle and watches->h_list)
- *
- * The inode->inotify_mutex and inotify_handle->mutex and held during execution
- * of a caller's event handler. Thus, the caller must not hold any locks
- * taken in their event handler while calling any of the published inotify
- * interfaces.
- */
-
-/*
- * Lifetimes of the three main data structures--inotify_handle, inode, and
- * inotify_watch--are managed by reference count.
- *
- * inotify_handle: Lifetime is from inotify_init() to inotify_destroy().
- * Additional references can bump the count via get_inotify_handle() and drop
- * the count via put_inotify_handle().
- *
- * inotify_watch: for inotify's purposes, lifetime is from inotify_add_watch()
- * to remove_watch_no_event(). Additional references can bump the count via
- * get_inotify_watch() and drop the count via put_inotify_watch(). The caller
- * is reponsible for the final put after receiving IN_IGNORED, or when using
- * IN_ONESHOT after receiving the first event. Inotify does the final put if
- * inotify_destroy() is called.
- *
- * inode: Pinned so long as the inode is associated with a watch, from
- * inotify_add_watch() to the final put_inotify_watch().
- */
-
-/*
- * struct inotify_handle - represents an inotify instance
- *
- * This structure is protected by the mutex 'mutex'.
- */
-struct inotify_handle {
- struct idr idr; /* idr mapping wd -> watch */
- struct mutex mutex; /* protects this bad boy */
- struct list_head watches; /* list of watches */
- atomic_t count; /* reference count */
- u32 last_wd; /* the last wd allocated */
- const struct inotify_operations *in_ops; /* inotify caller operations */
-};
-
-static inline void get_inotify_handle(struct inotify_handle *ih)
-{
- atomic_inc(&ih->count);
-}
-
-static inline void put_inotify_handle(struct inotify_handle *ih)
-{
- if (atomic_dec_and_test(&ih->count)) {
- idr_destroy(&ih->idr);
- kfree(ih);
- }
-}
-
-/**
- * get_inotify_watch - grab a reference to an inotify_watch
- * @watch: watch to grab
- */
-void get_inotify_watch(struct inotify_watch *watch)
-{
- atomic_inc(&watch->count);
-}
-EXPORT_SYMBOL_GPL(get_inotify_watch);
-
-/**
- * put_inotify_watch - decrements the ref count on a given watch. cleans up
- * watch references if the count reaches zero. inotify_watch is freed by
- * inotify callers via the destroy_watch() op.
- * @watch: watch to release
- */
-void put_inotify_watch(struct inotify_watch *watch)
-{
- if (atomic_dec_and_test(&watch->count)) {
- struct inotify_handle *ih = watch->ih;
-
- iput(watch->inode);
- ih->in_ops->destroy_watch(watch);
- put_inotify_handle(ih);
- }
-}
-EXPORT_SYMBOL_GPL(put_inotify_watch);
-
-/*
- * inotify_handle_get_wd - returns the next WD for use by the given handle
- *
- * Callers must hold ih->mutex. This function can sleep.
- */
-static int inotify_handle_get_wd(struct inotify_handle *ih,
- struct inotify_watch *watch)
-{
- int ret;
-
- do {
- if (unlikely(!idr_pre_get(&ih->idr, GFP_KERNEL)))
- return -ENOSPC;
- ret = idr_get_new_above(&ih->idr, watch, ih->last_wd+1, &watch->wd);
- } while (ret == -EAGAIN);
-
- if (likely(!ret))
- ih->last_wd = watch->wd;
-
- return ret;
-}
-
-/*
- * inotify_inode_watched - returns nonzero if there are watches on this inode
- * and zero otherwise. We call this lockless, we do not care if we race.
- */
-static inline int inotify_inode_watched(struct inode *inode)
-{
- return !list_empty(&inode->inotify_watches);
-}
-
-/*
- * Get child dentry flag into synch with parent inode.
- * Flag should always be clear for negative dentrys.
- */
-static void set_dentry_child_flags(struct inode *inode, int watched)
-{
- struct dentry *alias;
-
- spin_lock(&dcache_lock);
- list_for_each_entry(alias, &inode->i_dentry, d_alias) {
- struct dentry *child;
-
- list_for_each_entry(child, &alias->d_subdirs, d_u.d_child) {
- if (!child->d_inode)
- continue;
-
- spin_lock(&child->d_lock);
- if (watched)
- child->d_flags |= DCACHE_INOTIFY_PARENT_WATCHED;
- else
- child->d_flags &=~DCACHE_INOTIFY_PARENT_WATCHED;
- spin_unlock(&child->d_lock);
- }
- }
- spin_unlock(&dcache_lock);
-}
-
-/*
- * inotify_find_handle - find the watch associated with the given inode and
- * handle
- *
- * Callers must hold inode->inotify_mutex.
- */
-static struct inotify_watch *inode_find_handle(struct inode *inode,
- struct inotify_handle *ih)
-{
- struct inotify_watch *watch;
-
- list_for_each_entry(watch, &inode->inotify_watches, i_list) {
- if (watch->ih == ih)
- return watch;
- }
-
- return NULL;
-}
-
-/*
- * remove_watch_no_event - remove watch without the IN_IGNORED event.
- *
- * Callers must hold both inode->inotify_mutex and ih->mutex.
- */
-static void remove_watch_no_event(struct inotify_watch *watch,
- struct inotify_handle *ih)
-{
- list_del(&watch->i_list);
- list_del(&watch->h_list);
-
- if (!inotify_inode_watched(watch->inode))
- set_dentry_child_flags(watch->inode, 0);
-
- idr_remove(&ih->idr, watch->wd);
-}
-
-/**
- * inotify_remove_watch_locked - Remove a watch from both the handle and the
- * inode. Sends the IN_IGNORED event signifying that the inode is no longer
- * watched. May be invoked from a caller's event handler.
- * @ih: inotify handle associated with watch
- * @watch: watch to remove
- *
- * Callers must hold both inode->inotify_mutex and ih->mutex.
- */
-void inotify_remove_watch_locked(struct inotify_handle *ih,
- struct inotify_watch *watch)
-{
- remove_watch_no_event(watch, ih);
- ih->in_ops->handle_event(watch, watch->wd, IN_IGNORED, 0, NULL, NULL);
-}
-EXPORT_SYMBOL_GPL(inotify_remove_watch_locked);
-
-/* Kernel API for producing events */
-
-/*
- * inotify_d_instantiate - instantiate dcache entry for inode
- */
-void inotify_d_instantiate(struct dentry *entry, struct inode *inode)
-{
- struct dentry *parent;
-
- if (!inode)
- return;
-
- spin_lock(&entry->d_lock);
- parent = entry->d_parent;
- if (parent->d_inode && inotify_inode_watched(parent->d_inode))
- entry->d_flags |= DCACHE_INOTIFY_PARENT_WATCHED;
- spin_unlock(&entry->d_lock);
-}
-
-/*
- * inotify_d_move - dcache entry has been moved
- */
-void inotify_d_move(struct dentry *entry)
-{
- struct dentry *parent;
-
- parent = entry->d_parent;
- if (inotify_inode_watched(parent->d_inode))
- entry->d_flags |= DCACHE_INOTIFY_PARENT_WATCHED;
- else
- entry->d_flags &= ~DCACHE_INOTIFY_PARENT_WATCHED;
-}
-
-/**
- * inotify_inode_queue_event - queue an event to all watches on this inode
- * @inode: inode event is originating from
- * @mask: event mask describing this event
- * @cookie: cookie for synchronization, or zero
- * @name: filename, if any
- * @n_inode: inode associated with name
- */
-void inotify_inode_queue_event(struct inode *inode, u32 mask, u32 cookie,
- const char *name, struct inode *n_inode)
-{
- struct inotify_watch *watch, *next;
-
- if (!inotify_inode_watched(inode))
- return;
-
- mutex_lock(&inode->inotify_mutex);
- list_for_each_entry_safe(watch, next, &inode->inotify_watches, i_list) {
- u32 watch_mask = watch->mask;
- if (watch_mask & mask) {
- struct inotify_handle *ih= watch->ih;
- mutex_lock(&ih->mutex);
- if (watch_mask & IN_ONESHOT)
- remove_watch_no_event(watch, ih);
- ih->in_ops->handle_event(watch, watch->wd, mask, cookie,
- name, n_inode);
- mutex_unlock(&ih->mutex);
- }
- }
- mutex_unlock(&inode->inotify_mutex);
-}
-EXPORT_SYMBOL_GPL(inotify_inode_queue_event);
-
-/**
- * inotify_dentry_parent_queue_event - queue an event to a dentry's parent
- * @dentry: the dentry in question, we queue against this dentry's parent
- * @mask: event mask describing this event
- * @cookie: cookie for synchronization, or zero
- * @name: filename, if any
- */
-void inotify_dentry_parent_queue_event(struct dentry *dentry, u32 mask,
- u32 cookie, const char *name)
-{
- struct dentry *parent;
- struct inode *inode;
-
- if (!(dentry->d_flags & DCACHE_INOTIFY_PARENT_WATCHED))
- return;
-
- spin_lock(&dentry->d_lock);
- parent = dentry->d_parent;
- inode = parent->d_inode;
-
- if (inotify_inode_watched(inode)) {
- dget(parent);
- spin_unlock(&dentry->d_lock);
- inotify_inode_queue_event(inode, mask, cookie, name,
- dentry->d_inode);
- dput(parent);
- } else
- spin_unlock(&dentry->d_lock);
-}
-EXPORT_SYMBOL_GPL(inotify_dentry_parent_queue_event);
-
-/**
- * inotify_get_cookie - return a unique cookie for use in synchronizing events.
- */
-u32 inotify_get_cookie(void)
-{
- return atomic_inc_return(&inotify_cookie);
-}
-EXPORT_SYMBOL_GPL(inotify_get_cookie);
-
-/**
- * inotify_unmount_inodes - an sb is unmounting. handle any watched inodes.
- * @list: list of inodes being unmounted (sb->s_inodes)
- *
- * Called with inode_lock held, protecting the unmounting super block's list
- * of inodes, and with iprune_mutex held, keeping shrink_icache_memory() at bay.
- * We temporarily drop inode_lock, however, and CAN block.
- */
-void inotify_unmount_inodes(struct list_head *list)
-{
- struct inode *inode, *next_i, *need_iput = NULL;
-
- list_for_each_entry_safe(inode, next_i, list, i_sb_list) {
- struct inotify_watch *watch, *next_w;
- struct inode *need_iput_tmp;
- struct list_head *watches;
-
- /*
- * If i_count is zero, the inode cannot have any watches and
- * doing an __iget/iput with MS_ACTIVE clear would actually
- * evict all inodes with zero i_count from icache which is
- * unnecessarily violent and may in fact be illegal to do.
- */
- if (!atomic_read(&inode->i_count))
- continue;
-
- /*
- * We cannot __iget() an inode in state I_CLEAR, I_FREEING, or
- * I_WILL_FREE which is fine because by that point the inode
- * cannot have any associated watches.
- */
- if (inode->i_state & (I_CLEAR | I_FREEING | I_WILL_FREE))
- continue;
-
- need_iput_tmp = need_iput;
- need_iput = NULL;
- /* In case inotify_remove_watch_locked() drops a reference. */
- if (inode != need_iput_tmp)
- __iget(inode);
- else
- need_iput_tmp = NULL;
- /* In case the dropping of a reference would nuke next_i. */
- if ((&next_i->i_sb_list != list) &&
- atomic_read(&next_i->i_count) &&
- !(next_i->i_state & (I_CLEAR | I_FREEING |
- I_WILL_FREE))) {
- __iget(next_i);
- need_iput = next_i;
- }
-
- /*
- * We can safely drop inode_lock here because we hold
- * references on both inode and next_i. Also no new inodes
- * will be added since the umount has begun. Finally,
- * iprune_mutex keeps shrink_icache_memory() away.
- */
- spin_unlock(&inode_lock);
-
- if (need_iput_tmp)
- iput(need_iput_tmp);
-
- /* for each watch, send IN_UNMOUNT and then remove it */
- mutex_lock(&inode->inotify_mutex);
- watches = &inode->inotify_watches;
- list_for_each_entry_safe(watch, next_w, watches, i_list) {
- struct inotify_handle *ih= watch->ih;
- mutex_lock(&ih->mutex);
- ih->in_ops->handle_event(watch, watch->wd, IN_UNMOUNT, 0,
- NULL, NULL);
- inotify_remove_watch_locked(ih, watch);
- mutex_unlock(&ih->mutex);
- }
- mutex_unlock(&inode->inotify_mutex);
- iput(inode);
-
- spin_lock(&inode_lock);
- }
-}
-EXPORT_SYMBOL_GPL(inotify_unmount_inodes);
-
-/**
- * inotify_inode_is_dead - an inode has been deleted, cleanup any watches
- * @inode: inode that is about to be removed
- */
-void inotify_inode_is_dead(struct inode *inode)
-{
- struct inotify_watch *watch, *next;
-
- mutex_lock(&inode->inotify_mutex);
- list_for_each_entry_safe(watch, next, &inode->inotify_watches, i_list) {
- struct inotify_handle *ih = watch->ih;
- mutex_lock(&ih->mutex);
- inotify_remove_watch_locked(ih, watch);
- mutex_unlock(&ih->mutex);
- }
- mutex_unlock(&inode->inotify_mutex);
-}
-EXPORT_SYMBOL_GPL(inotify_inode_is_dead);
-
-/* Kernel Consumer API */
-
-/**
- * inotify_init - allocate and initialize an inotify instance
- * @ops: caller's inotify operations
- */
-struct inotify_handle *inotify_init(const struct inotify_operations *ops)
-{
- struct inotify_handle *ih;
-
- ih = kmalloc(sizeof(struct inotify_handle), GFP_KERNEL);
- if (unlikely(!ih))
- return ERR_PTR(-ENOMEM);
-
- idr_init(&ih->idr);
- INIT_LIST_HEAD(&ih->watches);
- mutex_init(&ih->mutex);
- ih->last_wd = 0;
- ih->in_ops = ops;
- atomic_set(&ih->count, 0);
- get_inotify_handle(ih);
-
- return ih;
-}
-EXPORT_SYMBOL_GPL(inotify_init);
-
-/**
- * inotify_init_watch - initialize an inotify watch
- * @watch: watch to initialize
- */
-void inotify_init_watch(struct inotify_watch *watch)
-{
- INIT_LIST_HEAD(&watch->h_list);
- INIT_LIST_HEAD(&watch->i_list);
- atomic_set(&watch->count, 0);
- get_inotify_watch(watch); /* initial get */
-}
-EXPORT_SYMBOL_GPL(inotify_init_watch);
-
-/**
- * inotify_destroy - clean up and destroy an inotify instance
- * @ih: inotify handle
- */
-void inotify_destroy(struct inotify_handle *ih)
-{
- /*
- * Destroy all of the watches for this handle. Unfortunately, not very
- * pretty. We cannot do a simple iteration over the list, because we
- * do not know the inode until we iterate to the watch. But we need to
- * hold inode->inotify_mutex before ih->mutex. The following works.
- */
- while (1) {
- struct inotify_watch *watch;
- struct list_head *watches;
- struct inode *inode;
-
- mutex_lock(&ih->mutex);
- watches = &ih->watches;
- if (list_empty(watches)) {
- mutex_unlock(&ih->mutex);
- break;
- }
- watch = list_first_entry(watches, struct inotify_watch, h_list);
- get_inotify_watch(watch);
- mutex_unlock(&ih->mutex);
-
- inode = watch->inode;
- mutex_lock(&inode->inotify_mutex);
- mutex_lock(&ih->mutex);
-
- /* make sure we didn't race with another list removal */
- if (likely(idr_find(&ih->idr, watch->wd))) {
- remove_watch_no_event(watch, ih);
- put_inotify_watch(watch);
- }
-
- mutex_unlock(&ih->mutex);
- mutex_unlock(&inode->inotify_mutex);
- put_inotify_watch(watch);
- }
-
- /* free this handle: the put matching the get in inotify_init() */
- put_inotify_handle(ih);
-}
-EXPORT_SYMBOL_GPL(inotify_destroy);
-
-/**
- * inotify_find_watch - find an existing watch for an (ih,inode) pair
- * @ih: inotify handle
- * @inode: inode to watch
- * @watchp: pointer to existing inotify_watch
- *
- * Caller must pin given inode (via nameidata).
- */
-s32 inotify_find_watch(struct inotify_handle *ih, struct inode *inode,
- struct inotify_watch **watchp)
-{
- struct inotify_watch *old;
- int ret = -ENOENT;
-
- mutex_lock(&inode->inotify_mutex);
- mutex_lock(&ih->mutex);
-
- old = inode_find_handle(inode, ih);
- if (unlikely(old)) {
- get_inotify_watch(old); /* caller must put watch */
- *watchp = old;
- ret = old->wd;
- }
-
- mutex_unlock(&ih->mutex);
- mutex_unlock(&inode->inotify_mutex);
-
- return ret;
-}
-EXPORT_SYMBOL_GPL(inotify_find_watch);
-
-/**
- * inotify_find_update_watch - find and update the mask of an existing watch
- * @ih: inotify handle
- * @inode: inode's watch to update
- * @mask: mask of events to watch
- *
- * Caller must pin given inode (via nameidata).
- */
-s32 inotify_find_update_watch(struct inotify_handle *ih, struct inode *inode,
- u32 mask)
-{
- struct inotify_watch *old;
- int mask_add = 0;
- int ret;
-
- if (mask & IN_MASK_ADD)
- mask_add = 1;
-
- /* don't allow invalid bits: we don't want flags set */
- mask &= IN_ALL_EVENTS | IN_ONESHOT;
- if (unlikely(!mask))
- return -EINVAL;
-
- mutex_lock(&inode->inotify_mutex);
- mutex_lock(&ih->mutex);
-
- /*
- * Handle the case of re-adding a watch on an (inode,ih) pair that we
- * are already watching. We just update the mask and return its wd.
- */
- old = inode_find_handle(inode, ih);
- if (unlikely(!old)) {
- ret = -ENOENT;
- goto out;
- }
-
- if (mask_add)
- old->mask |= mask;
- else
- old->mask = mask;
- ret = old->wd;
-out:
- mutex_unlock(&ih->mutex);
- mutex_unlock(&inode->inotify_mutex);
- return ret;
-}
-EXPORT_SYMBOL_GPL(inotify_find_update_watch);
-
-/**
- * inotify_add_watch - add a watch to an inotify instance
- * @ih: inotify handle
- * @watch: caller allocated watch structure
- * @inode: inode to watch
- * @mask: mask of events to watch
- *
- * Caller must pin given inode (via nameidata).
- * Caller must ensure it only calls inotify_add_watch() once per watch.
- * Calls inotify_handle_get_wd() so may sleep.
- */
-s32 inotify_add_watch(struct inotify_handle *ih, struct inotify_watch *watch,
- struct inode *inode, u32 mask)
-{
- int ret = 0;
- int newly_watched;
-
- /* don't allow invalid bits: we don't want flags set */
- mask &= IN_ALL_EVENTS | IN_ONESHOT;
- if (unlikely(!mask))
- return -EINVAL;
- watch->mask = mask;
-
- mutex_lock(&inode->inotify_mutex);
- mutex_lock(&ih->mutex);
-
- /* Initialize a new watch */
- ret = inotify_handle_get_wd(ih, watch);
- if (unlikely(ret))
- goto out;
- ret = watch->wd;
-
- /* save a reference to handle and bump the count to make it official */
- get_inotify_handle(ih);
- watch->ih = ih;
-
- /*
- * Save a reference to the inode and bump the ref count to make it
- * official. We hold a reference to nameidata, which makes this safe.
- */
- watch->inode = igrab(inode);
-
- /* Add the watch to the handle's and the inode's list */
- newly_watched = !inotify_inode_watched(inode);
- list_add(&watch->h_list, &ih->watches);
- list_add(&watch->i_list, &inode->inotify_watches);
- /*
- * Set child flags _after_ adding the watch, so there is no race
- * windows where newly instantiated children could miss their parent's
- * watched flag.
- */
- if (newly_watched)
- set_dentry_child_flags(inode, 1);
-
-out:
- mutex_unlock(&ih->mutex);
- mutex_unlock(&inode->inotify_mutex);
- return ret;
-}
-EXPORT_SYMBOL_GPL(inotify_add_watch);
-
-/**
- * inotify_clone_watch - put the watch next to existing one
- * @old: already installed watch
- * @new: new watch
- *
- * Caller must hold the inotify_mutex of inode we are dealing with;
- * it is expected to remove the old watch before unlocking the inode.
- */
-s32 inotify_clone_watch(struct inotify_watch *old, struct inotify_watch *new)
-{
- struct inotify_handle *ih = old->ih;
- int ret = 0;
-
- new->mask = old->mask;
- new->ih = ih;
-
- mutex_lock(&ih->mutex);
-
- /* Initialize a new watch */
- ret = inotify_handle_get_wd(ih, new);
- if (unlikely(ret))
- goto out;
- ret = new->wd;
-
- get_inotify_handle(ih);
-
- new->inode = igrab(old->inode);
-
- list_add(&new->h_list, &ih->watches);
- list_add(&new->i_list, &old->inode->inotify_watches);
-out:
- mutex_unlock(&ih->mutex);
- return ret;
-}
-
-void inotify_evict_watch(struct inotify_watch *watch)
-{
- get_inotify_watch(watch);
- mutex_lock(&watch->ih->mutex);
- inotify_remove_watch_locked(watch->ih, watch);
- mutex_unlock(&watch->ih->mutex);
-}
-
-/**
- * inotify_rm_wd - remove a watch from an inotify instance
- * @ih: inotify handle
- * @wd: watch descriptor to remove
- *
- * Can sleep.
- */
-int inotify_rm_wd(struct inotify_handle *ih, u32 wd)
-{
- struct inotify_watch *watch;
- struct inode *inode;
-
- mutex_lock(&ih->mutex);
- watch = idr_find(&ih->idr, wd);
- if (unlikely(!watch)) {
- mutex_unlock(&ih->mutex);
- return -EINVAL;
- }
- get_inotify_watch(watch);
- inode = watch->inode;
- mutex_unlock(&ih->mutex);
-
- mutex_lock(&inode->inotify_mutex);
- mutex_lock(&ih->mutex);
-
- /* make sure that we did not race */
- if (likely(idr_find(&ih->idr, wd) == watch))
- inotify_remove_watch_locked(ih, watch);
-
- mutex_unlock(&ih->mutex);
- mutex_unlock(&inode->inotify_mutex);
- put_inotify_watch(watch);
-
- return 0;
-}
-EXPORT_SYMBOL_GPL(inotify_rm_wd);
-
-/**
- * inotify_rm_watch - remove a watch from an inotify instance
- * @ih: inotify handle
- * @watch: watch to remove
- *
- * Can sleep.
- */
-int inotify_rm_watch(struct inotify_handle *ih,
- struct inotify_watch *watch)
-{
- return inotify_rm_wd(ih, watch->wd);
-}
-EXPORT_SYMBOL_GPL(inotify_rm_watch);
-
-/*
- * inotify_setup - core initialization function
- */
-static int __init inotify_setup(void)
-{
- atomic_set(&inotify_cookie, 0);
-
- return 0;
-}
-
-module_init(inotify_setup);
diff --git a/fs/inotify_user.c b/fs/inotify_user.c
deleted file mode 100644
index d85c7d9..0000000
--- a/fs/inotify_user.c
+++ /dev/null
@@ -1,781 +0,0 @@
-/*
- * fs/inotify_user.c - inotify support for userspace
- *
- * Authors:
- * John McCutchan <ttb@tentacle.dhs.org>
- * Robert Love <rml@novell.com>
- *
- * Copyright (C) 2005 John McCutchan
- * Copyright 2006 Hewlett-Packard Development Company, L.P.
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the
- * Free Software Foundation; either version 2, or (at your option) any
- * later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- */
-
-#include <linux/kernel.h>
-#include <linux/sched.h>
-#include <linux/slab.h>
-#include <linux/fs.h>
-#include <linux/file.h>
-#include <linux/mount.h>
-#include <linux/namei.h>
-#include <linux/poll.h>
-#include <linux/init.h>
-#include <linux/list.h>
-#include <linux/inotify.h>
-#include <linux/syscalls.h>
-#include <linux/magic.h>
-
-#include <asm/ioctls.h>
-
-static struct kmem_cache *watch_cachep __read_mostly;
-static struct kmem_cache *event_cachep __read_mostly;
-
-static struct vfsmount *inotify_mnt __read_mostly;
-
-/* these are configurable via /proc/sys/fs/inotify/ */
-static int inotify_max_user_instances __read_mostly;
-static int inotify_max_user_watches __read_mostly;
-static int inotify_max_queued_events __read_mostly;
-
-/*
- * Lock ordering:
- *
- * inotify_dev->up_mutex (ensures we don't re-add the same watch)
- * inode->inotify_mutex (protects inode's watch list)
- * inotify_handle->mutex (protects inotify_handle's watch list)
- * inotify_dev->ev_mutex (protects device's event queue)
- */
-
-/*
- * Lifetimes of the main data structures:
- *
- * inotify_device: Lifetime is managed by reference count, from
- * sys_inotify_init() until release. Additional references can bump the count
- * via get_inotify_dev() and drop the count via put_inotify_dev().
- *
- * inotify_user_watch: Lifetime is from create_watch() to the receipt of an
- * IN_IGNORED event from inotify, or when using IN_ONESHOT, to receipt of the
- * first event, or to inotify_destroy().
- */
-
-/*
- * struct inotify_device - represents an inotify instance
- *
- * This structure is protected by the mutex 'mutex'.
- */
-struct inotify_device {
- wait_queue_head_t wq; /* wait queue for i/o */
- struct mutex ev_mutex; /* protects event queue */
- struct mutex up_mutex; /* synchronizes watch updates */
- struct list_head events; /* list of queued events */
- atomic_t count; /* reference count */
- struct user_struct *user; /* user who opened this dev */
- struct inotify_handle *ih; /* inotify handle */
- struct fasync_struct *fa; /* async notification */
- unsigned int queue_size; /* size of the queue (bytes) */
- unsigned int event_count; /* number of pending events */
- unsigned int max_events; /* maximum number of events */
-};
-
-/*
- * struct inotify_kernel_event - An inotify event, originating from a watch and
- * queued for user-space. A list of these is attached to each instance of the
- * device. In read(), this list is walked and all events that can fit in the
- * buffer are returned.
- *
- * Protected by dev->ev_mutex of the device in which we are queued.
- */
-struct inotify_kernel_event {
- struct inotify_event event; /* the user-space event */
- struct list_head list; /* entry in inotify_device's list */
- char *name; /* filename, if any */
-};
-
-/*
- * struct inotify_user_watch - our version of an inotify_watch, we add
- * a reference to the associated inotify_device.
- */
-struct inotify_user_watch {
- struct inotify_device *dev; /* associated device */
- struct inotify_watch wdata; /* inotify watch data */
-};
-
-#ifdef CONFIG_SYSCTL
-
-#include <linux/sysctl.h>
-
-static int zero;
-
-ctl_table inotify_table[] = {
- {
- .ctl_name = INOTIFY_MAX_USER_INSTANCES,
- .procname = "max_user_instances",
- .data = &inotify_max_user_instances,
- .maxlen = sizeof(int),
- .mode = 0644,
- .proc_handler = &proc_dointvec_minmax,
- .strategy = &sysctl_intvec,
- .extra1 = &zero,
- },
- {
- .ctl_name = INOTIFY_MAX_USER_WATCHES,
- .procname = "max_user_watches",
- .data = &inotify_max_user_watches,
- .maxlen = sizeof(int),
- .mode = 0644,
- .proc_handler = &proc_dointvec_minmax,
- .strategy = &sysctl_intvec,
- .extra1 = &zero,
- },
- {
- .ctl_name = INOTIFY_MAX_QUEUED_EVENTS,
- .procname = "max_queued_events",
- .data = &inotify_max_queued_events,
- .maxlen = sizeof(int),
- .mode = 0644,
- .proc_handler = &proc_dointvec_minmax,
- .strategy = &sysctl_intvec,
- .extra1 = &zero
- },
- { .ctl_name = 0 }
-};
-#endif /* CONFIG_SYSCTL */
-
-static inline void get_inotify_dev(struct inotify_device *dev)
-{
- atomic_inc(&dev->count);
-}
-
-static inline void put_inotify_dev(struct inotify_device *dev)
-{
- if (atomic_dec_and_test(&dev->count)) {
- atomic_dec(&dev->user->inotify_devs);
- free_uid(dev->user);
- kfree(dev);
- }
-}
-
-/*
- * free_inotify_user_watch - cleans up the watch and its references
- */
-static void free_inotify_user_watch(struct inotify_watch *w)
-{
- struct inotify_user_watch *watch;
- struct inotify_device *dev;
-
- watch = container_of(w, struct inotify_user_watch, wdata);
- dev = watch->dev;
-
- atomic_dec(&dev->user->inotify_watches);
- put_inotify_dev(dev);
- kmem_cache_free(watch_cachep, watch);
-}
-
-/*
- * kernel_event - create a new kernel event with the given parameters
- *
- * This function can sleep.
- */
-static struct inotify_kernel_event * kernel_event(s32 wd, u32 mask, u32 cookie,
- const char *name)
-{
- struct inotify_kernel_event *kevent;
-
- kevent = kmem_cache_alloc(event_cachep, GFP_NOFS);
- if (unlikely(!kevent))
- return NULL;
-
- /* we hand this out to user-space, so zero it just in case */
- memset(&kevent->event, 0, sizeof(struct inotify_event));
-
- kevent->event.wd = wd;
- kevent->event.mask = mask;
- kevent->event.cookie = cookie;
-
- INIT_LIST_HEAD(&kevent->list);
-
- if (name) {
- size_t len, rem, event_size = sizeof(struct inotify_event);
-
- /*
- * We need to pad the filename so as to properly align an
- * array of inotify_event structures. Because the structure is
- * small and the common case is a small filename, we just round
- * up to the next multiple of the structure's sizeof. This is
- * simple and safe for all architectures.
- */
- len = strlen(name) + 1;
- rem = event_size - len;
- if (len > event_size) {
- rem = event_size - (len % event_size);
- if (len % event_size == 0)
- rem = 0;
- }
-
- kevent->name = kmalloc(len + rem, GFP_KERNEL);
- if (unlikely(!kevent->name)) {
- kmem_cache_free(event_cachep, kevent);
- return NULL;
- }
- memcpy(kevent->name, name, len);
- if (rem)
- memset(kevent->name + len, 0, rem);
- kevent->event.len = len + rem;
- } else {
- kevent->event.len = 0;
- kevent->name = NULL;
- }
-
- return kevent;
-}
-
-/*
- * inotify_dev_get_event - return the next event in the given dev's queue
- *
- * Caller must hold dev->ev_mutex.
- */
-static inline struct inotify_kernel_event *
-inotify_dev_get_event(struct inotify_device *dev)
-{
- return list_entry(dev->events.next, struct inotify_kernel_event, list);
-}
-
-/*
- * inotify_dev_get_last_event - return the last event in the given dev's queue
- *
- * Caller must hold dev->ev_mutex.
- */
-static inline struct inotify_kernel_event *
-inotify_dev_get_last_event(struct inotify_device *dev)
-{
- if (list_empty(&dev->events))
- return NULL;
- return list_entry(dev->events.prev, struct inotify_kernel_event, list);
-}
-
-/*
- * inotify_dev_queue_event - event handler registered with core inotify, adds
- * a new event to the given device
- *
- * Can sleep (calls kernel_event()).
- */
-static void inotify_dev_queue_event(struct inotify_watch *w, u32 wd, u32 mask,
- u32 cookie, const char *name,
- struct inode *ignored)
-{
- struct inotify_user_watch *watch;
- struct inotify_device *dev;
- struct inotify_kernel_event *kevent, *last;
-
- watch = container_of(w, struct inotify_user_watch, wdata);
- dev = watch->dev;
-
- mutex_lock(&dev->ev_mutex);
-
- /* we can safely put the watch as we don't reference it while
- * generating the event
- */
- if (mask & IN_IGNORED || w->mask & IN_ONESHOT)
- put_inotify_watch(w); /* final put */
-
- /* coalescing: drop this event if it is a dupe of the previous */
- last = inotify_dev_get_last_event(dev);
- if (last && last->event.mask == mask && last->event.wd == wd &&
- last->event.cookie == cookie) {
- const char *lastname = last->name;
-
- if (!name && !lastname)
- goto out;
- if (name && lastname && !strcmp(lastname, name))
- goto out;
- }
-
- /* the queue overflowed and we already sent the Q_OVERFLOW event */
- if (unlikely(dev->event_count > dev->max_events))
- goto out;
-
- /* if the queue overflows, we need to notify user space */
- if (unlikely(dev->event_count == dev->max_events))
- kevent = kernel_event(-1, IN_Q_OVERFLOW, cookie, NULL);
- else
- kevent = kernel_event(wd, mask, cookie, name);
-
- if (unlikely(!kevent))
- goto out;
-
- /* queue the event and wake up anyone waiting */
- dev->event_count++;
- dev->queue_size += sizeof(struct inotify_event) + kevent->event.len;
- list_add_tail(&kevent->list, &dev->events);
- wake_up_interruptible(&dev->wq);
- kill_fasync(&dev->fa, SIGIO, POLL_IN);
-
-out:
- mutex_unlock(&dev->ev_mutex);
-}
-
-/*
- * remove_kevent - cleans up the given kevent
- *
- * Caller must hold dev->ev_mutex.
- */
-static void remove_kevent(struct inotify_device *dev,
- struct inotify_kernel_event *kevent)
-{
- list_del(&kevent->list);
-
- dev->event_count--;
- dev->queue_size -= sizeof(struct inotify_event) + kevent->event.len;
-}
-
-/*
- * free_kevent - frees the given kevent.
- */
-static void free_kevent(struct inotify_kernel_event *kevent)
-{
- kfree(kevent->name);
- kmem_cache_free(event_cachep, kevent);
-}
-
-/*
- * inotify_dev_event_dequeue - destroy an event on the given device
- *
- * Caller must hold dev->ev_mutex.
- */
-static void inotify_dev_event_dequeue(struct inotify_device *dev)
-{
- if (!list_empty(&dev->events)) {
- struct inotify_kernel_event *kevent;
- kevent = inotify_dev_get_event(dev);
- remove_kevent(dev, kevent);
- free_kevent(kevent);
- }
-}
-
-/*
- * find_inode - resolve a user-given path to a specific inode
- */
-static int find_inode(const char __user *dirname, struct path *path,
- unsigned flags)
-{
- int error;
-
- error = user_path_at(AT_FDCWD, dirname, flags, path);
- if (error)
- return error;
- /* you can only watch an inode if you have read permissions on it */
- error = inode_permission(path->dentry->d_inode, MAY_READ);
- if (error)
- path_put(path);
- return error;
-}
-
-/*
- * create_watch - creates a watch on the given device.
- *
- * Callers must hold dev->up_mutex.
- */
-static int create_watch(struct inotify_device *dev, struct inode *inode,
- u32 mask)
-{
- struct inotify_user_watch *watch;
- int ret;
-
- if (atomic_read(&dev->user->inotify_watches) >=
- inotify_max_user_watches)
- return -ENOSPC;
-
- watch = kmem_cache_alloc(watch_cachep, GFP_KERNEL);
- if (unlikely(!watch))
- return -ENOMEM;
-
- /* save a reference to device and bump the count to make it official */
- get_inotify_dev(dev);
- watch->dev = dev;
-
- atomic_inc(&dev->user->inotify_watches);
-
- inotify_init_watch(&watch->wdata);
- ret = inotify_add_watch(dev->ih, &watch->wdata, inode, mask);
- if (ret < 0)
- free_inotify_user_watch(&watch->wdata);
-
- return ret;
-}
-
-/* Device Interface */
-
-static unsigned int inotify_poll(struct file *file, poll_table *wait)
-{
- struct inotify_device *dev = file->private_data;
- int ret = 0;
-
- poll_wait(file, &dev->wq, wait);
- mutex_lock(&dev->ev_mutex);
- if (!list_empty(&dev->events))
- ret = POLLIN | POLLRDNORM;
- mutex_unlock(&dev->ev_mutex);
-
- return ret;
-}
-
-static ssize_t inotify_read(struct file *file, char __user *buf,
- size_t count, loff_t *pos)
-{
- size_t event_size = sizeof (struct inotify_event);
- struct inotify_device *dev;
- char __user *start;
- int ret;
- DEFINE_WAIT(wait);
-
- start = buf;
- dev = file->private_data;
-
- while (1) {
-
- prepare_to_wait(&dev->wq, &wait, TASK_INTERRUPTIBLE);
-
- mutex_lock(&dev->ev_mutex);
- if (!list_empty(&dev->events)) {
- ret = 0;
- break;
- }
- mutex_unlock(&dev->ev_mutex);
-
- if (file->f_flags & O_NONBLOCK) {
- ret = -EAGAIN;
- break;
- }
-
- if (signal_pending(current)) {
- ret = -EINTR;
- break;
- }
-
- schedule();
- }
-
- finish_wait(&dev->wq, &wait);
- if (ret)
- return ret;
-
- while (1) {
- struct inotify_kernel_event *kevent;
-
- ret = buf - start;
- if (list_empty(&dev->events))
- break;
-
- kevent = inotify_dev_get_event(dev);
- if (event_size + kevent->event.len > count) {
- if (ret == 0 && count > 0) {
- /*
- * could not get a single event because we
- * didn't have enough buffer space.
- */
- ret = -EINVAL;
- }
- break;
- }
- remove_kevent(dev, kevent);
-
- /*
- * Must perform the copy_to_user outside the mutex in order
- * to avoid a lock order reversal with mmap_sem.
- */
- mutex_unlock(&dev->ev_mutex);
-
- if (copy_to_user(buf, &kevent->event, event_size)) {
- ret = -EFAULT;
- break;
- }
- buf += event_size;
- count -= event_size;
-
- if (kevent->name) {
- if (copy_to_user(buf, kevent->name, kevent->event.len)){
- ret = -EFAULT;
- break;
- }
- buf += kevent->event.len;
- count -= kevent->event.len;
- }
-
- free_kevent(kevent);
-
- mutex_lock(&dev->ev_mutex);
- }
- mutex_unlock(&dev->ev_mutex);
-
- return ret;
-}
-
-static int inotify_fasync(int fd, struct file *file, int on)
-{
- struct inotify_device *dev = file->private_data;
-
- return fasync_helper(fd, file, on, &dev->fa) >= 0 ? 0 : -EIO;
-}
-
-static int inotify_release(struct inode *ignored, struct file *file)
-{
- struct inotify_device *dev = file->private_data;
-
- inotify_destroy(dev->ih);
-
- /* destroy all of the events on this device */
- mutex_lock(&dev->ev_mutex);
- while (!list_empty(&dev->events))
- inotify_dev_event_dequeue(dev);
- mutex_unlock(&dev->ev_mutex);
-
- if (file->f_flags & FASYNC)
- inotify_fasync(-1, file, 0);
-
- /* free this device: the put matching the get in inotify_init() */
- put_inotify_dev(dev);
-
- return 0;
-}
-
-static long inotify_ioctl(struct file *file, unsigned int cmd,
- unsigned long arg)
-{
- struct inotify_device *dev;
- void __user *p;
- int ret = -ENOTTY;
-
- dev = file->private_data;
- p = (void __user *) arg;
-
- switch (cmd) {
- case FIONREAD:
- ret = put_user(dev->queue_size, (int __user *) p);
- break;
- }
-
- return ret;
-}
-
-static const struct file_operations inotify_fops = {
- .poll = inotify_poll,
- .read = inotify_read,
- .fasync = inotify_fasync,
- .release = inotify_release,
- .unlocked_ioctl = inotify_ioctl,
- .compat_ioctl = inotify_ioctl,
-};
-
-static const struct inotify_operations inotify_user_ops = {
- .handle_event = inotify_dev_queue_event,
- .destroy_watch = free_inotify_user_watch,
-};
-
-asmlinkage long sys_inotify_init1(int flags)
-{
- struct inotify_device *dev;
- struct inotify_handle *ih;
- struct user_struct *user;
- struct file *filp;
- int fd, ret;
-
- /* Check the IN_* constants for consistency. */
- BUILD_BUG_ON(IN_CLOEXEC != O_CLOEXEC);
- BUILD_BUG_ON(IN_NONBLOCK != O_NONBLOCK);
-
- if (flags & ~(IN_CLOEXEC | IN_NONBLOCK))
- return -EINVAL;
-
- fd = get_unused_fd_flags(flags & O_CLOEXEC);
- if (fd < 0)
- return fd;
-
- filp = get_empty_filp();
- if (!filp) {
- ret = -ENFILE;
- goto out_put_fd;
- }
-
- user = get_uid(current->user);
- if (unlikely(atomic_read(&user->inotify_devs) >=
- inotify_max_user_instances)) {
- ret = -EMFILE;
- goto out_free_uid;
- }
-
- dev = kmalloc(sizeof(struct inotify_device), GFP_KERNEL);
- if (unlikely(!dev)) {
- ret = -ENOMEM;
- goto out_free_uid;
- }
-
- ih = inotify_init(&inotify_user_ops);
- if (IS_ERR(ih)) {
- ret = PTR_ERR(ih);
- goto out_free_dev;
- }
- dev->ih = ih;
- dev->fa = NULL;
-
- filp->f_op = &inotify_fops;
- filp->f_path.mnt = mntget(inotify_mnt);
- filp->f_path.dentry = dget(inotify_mnt->mnt_root);
- filp->f_mapping = filp->f_path.dentry->d_inode->i_mapping;
- filp->f_mode = FMODE_READ;
- filp->f_flags = O_RDONLY | (flags & O_NONBLOCK);
- filp->private_data = dev;
-
- INIT_LIST_HEAD(&dev->events);
- init_waitqueue_head(&dev->wq);
- mutex_init(&dev->ev_mutex);
- mutex_init(&dev->up_mutex);
- dev->event_count = 0;
- dev->queue_size = 0;
- dev->max_events = inotify_max_queued_events;
- dev->user = user;
- atomic_set(&dev->count, 0);
-
- get_inotify_dev(dev);
- atomic_inc(&user->inotify_devs);
- fd_install(fd, filp);
-
- return fd;
-out_free_dev:
- kfree(dev);
-out_free_uid:
- free_uid(user);
- put_filp(filp);
-out_put_fd:
- put_unused_fd(fd);
- return ret;
-}
-
-asmlinkage long sys_inotify_init(void)
-{
- return sys_inotify_init1(0);
-}
-
-asmlinkage long sys_inotify_add_watch(int fd, const char __user *pathname, u32 mask)
-{
- struct inode *inode;
- struct inotify_device *dev;
- struct path path;
- struct file *filp;
- int ret, fput_needed;
- unsigned flags = 0;
-
- filp = fget_light(fd, &fput_needed);
- if (unlikely(!filp))
- return -EBADF;
-
- /* verify that this is indeed an inotify instance */
- if (unlikely(filp->f_op != &inotify_fops)) {
- ret = -EINVAL;
- goto fput_and_out;
- }
-
- if (!(mask & IN_DONT_FOLLOW))
- flags |= LOOKUP_FOLLOW;
- if (mask & IN_ONLYDIR)
- flags |= LOOKUP_DIRECTORY;
-
- ret = find_inode(pathname, &path, flags);
- if (unlikely(ret))
- goto fput_and_out;
-
- /* inode held in place by reference to path; dev by fget on fd */
- inode = path.dentry->d_inode;
- dev = filp->private_data;
-
- mutex_lock(&dev->up_mutex);
- ret = inotify_find_update_watch(dev->ih, inode, mask);
- if (ret == -ENOENT)
- ret = create_watch(dev, inode, mask);
- mutex_unlock(&dev->up_mutex);
-
- path_put(&path);
-fput_and_out:
- fput_light(filp, fput_needed);
- return ret;
-}
-
-asmlinkage long sys_inotify_rm_watch(int fd, u32 wd)
-{
- struct file *filp;
- struct inotify_device *dev;
- int ret, fput_needed;
-
- filp = fget_light(fd, &fput_needed);
- if (unlikely(!filp))
- return -EBADF;
-
- /* verify that this is indeed an inotify instance */
- if (unlikely(filp->f_op != &inotify_fops)) {
- ret = -EINVAL;
- goto out;
- }
-
- dev = filp->private_data;
-
- /* we free our watch data when we get IN_IGNORED */
- ret = inotify_rm_wd(dev->ih, wd);
-
-out:
- fput_light(filp, fput_needed);
- return ret;
-}
-
-static int
-inotify_get_sb(struct file_system_type *fs_type, int flags,
- const char *dev_name, void *data, struct vfsmount *mnt)
-{
- return get_sb_pseudo(fs_type, "inotify", NULL,
- INOTIFYFS_SUPER_MAGIC, mnt);
-}
-
-static struct file_system_type inotify_fs_type = {
- .name = "inotifyfs",
- .get_sb = inotify_get_sb,
- .kill_sb = kill_anon_super,
-};
-
-/*
- * inotify_user_setup - Our initialization function. Note that we cannnot return
- * error because we have compiled-in VFS hooks. So an (unlikely) failure here
- * must result in panic().
- */
-static int __init inotify_user_setup(void)
-{
- int ret;
-
- ret = register_filesystem(&inotify_fs_type);
- if (unlikely(ret))
- panic("inotify: register_filesystem returned %d!\n", ret);
-
- inotify_mnt = kern_mount(&inotify_fs_type);
- if (IS_ERR(inotify_mnt))
- panic("inotify: kern_mount ret %ld!\n", PTR_ERR(inotify_mnt));
-
- inotify_max_queued_events = 16384;
- inotify_max_user_instances = 128;
- inotify_max_user_watches = 8192;
-
- watch_cachep = kmem_cache_create("inotify_watch_cache",
- sizeof(struct inotify_user_watch),
- 0, SLAB_PANIC, NULL);
- event_cachep = kmem_cache_create("inotify_event_cache",
- sizeof(struct inotify_kernel_event),
- 0, SLAB_PANIC, NULL);
-
- return 0;
-}
-
-module_init(inotify_user_setup);
diff --git a/fs/notify/Kconfig b/fs/notify/Kconfig
new file mode 100644
index 0000000..23415de
--- /dev/null
+++ b/fs/notify/Kconfig
@@ -0,0 +1,38 @@
+config DNOTIFY
+ bool "Dnotify support"
+ default y
+ help
+ Dnotify is a directory-based per-fd file change notification system
+ that uses signals to communicate events to user-space. There exist
+ superior alternatives, but some applications may still rely on
+ dnotify.
+
+ If unsure, say Y.
+
+config INOTIFY
+ bool "Inotify file change notification support"
+ default y
+ ---help---
+ Say Y here to enable inotify support. Inotify is a file change
+ notification system and a replacement for dnotify. Inotify fixes
+ numerous shortcomings in dnotify and introduces several new features
+ including multiple file events, one-shot support, and unmount
+ notification.
+
+ For more information, see <file:Documentation/filesystems/inotify.txt>
+
+ If unsure, say Y.
+
+config INOTIFY_USER
+ bool "Inotify support for userspace"
+ depends on INOTIFY
+ default y
+ ---help---
+ Say Y here to enable inotify support for userspace, including the
+ associated system calls. Inotify allows monitoring of both files and
+ directories via a single open fd. Events are read from the file
+ descriptor, which is also select()- and poll()-able.
+
+ For more information, see <file:Documentation/filesystems/inotify.txt>
+
+ If unsure, say Y.
diff --git a/fs/notify/Makefile b/fs/notify/Makefile
new file mode 100644
index 0000000..882ecf9
--- /dev/null
+++ b/fs/notify/Makefile
@@ -0,0 +1,4 @@
+obj-$(CONFIG_INOTIFY) += inotify.o
+obj-$(CONFIG_INOTIFY_USER) += inotify_user.o
+
+obj-$(CONFIG_DNOTIFY) += dnotify.o
diff --git a/fs/notify/dnotify.c b/fs/notify/dnotify.c
new file mode 100644
index 0000000..676073b
--- /dev/null
+++ b/fs/notify/dnotify.c
@@ -0,0 +1,194 @@
+/*
+ * Directory notifications for Linux.
+ *
+ * Copyright (C) 2000,2001,2002 Stephen Rothwell
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+#include <linux/fs.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/dnotify.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+#include <linux/fdtable.h>
+
+int dir_notify_enable __read_mostly = 1;
+
+static struct kmem_cache *dn_cache __read_mostly;
+
+static void redo_inode_mask(struct inode *inode)
+{
+ unsigned long new_mask;
+ struct dnotify_struct *dn;
+
+ new_mask = 0;
+ for (dn = inode->i_dnotify; dn != NULL; dn = dn->dn_next)
+ new_mask |= dn->dn_mask & ~DN_MULTISHOT;
+ inode->i_dnotify_mask = new_mask;
+}
+
+void dnotify_flush(struct file *filp, fl_owner_t id)
+{
+ struct dnotify_struct *dn;
+ struct dnotify_struct **prev;
+ struct inode *inode;
+
+ inode = filp->f_path.dentry->d_inode;
+ if (!S_ISDIR(inode->i_mode))
+ return;
+ spin_lock(&inode->i_lock);
+ prev = &inode->i_dnotify;
+ while ((dn = *prev) != NULL) {
+ if ((dn->dn_owner == id) && (dn->dn_filp == filp)) {
+ *prev = dn->dn_next;
+ redo_inode_mask(inode);
+ kmem_cache_free(dn_cache, dn);
+ break;
+ }
+ prev = &dn->dn_next;
+ }
+ spin_unlock(&inode->i_lock);
+}
+
+int fcntl_dirnotify(int fd, struct file *filp, unsigned long arg)
+{
+ struct dnotify_struct *dn;
+ struct dnotify_struct *odn;
+ struct dnotify_struct **prev;
+ struct inode *inode;
+ fl_owner_t id = current->files;
+ struct file *f;
+ int error = 0;
+
+ if ((arg & ~DN_MULTISHOT) == 0) {
+ dnotify_flush(filp, id);
+ return 0;
+ }
+ if (!dir_notify_enable)
+ return -EINVAL;
+ inode = filp->f_path.dentry->d_inode;
+ if (!S_ISDIR(inode->i_mode))
+ return -ENOTDIR;
+ dn = kmem_cache_alloc(dn_cache, GFP_KERNEL);
+ if (dn == NULL)
+ return -ENOMEM;
+ spin_lock(&inode->i_lock);
+ prev = &inode->i_dnotify;
+ while ((odn = *prev) != NULL) {
+ if ((odn->dn_owner == id) && (odn->dn_filp == filp)) {
+ odn->dn_fd = fd;
+ odn->dn_mask |= arg;
+ inode->i_dnotify_mask |= arg & ~DN_MULTISHOT;
+ goto out_free;
+ }
+ prev = &odn->dn_next;
+ }
+
+ rcu_read_lock();
+ f = fcheck(fd);
+ rcu_read_unlock();
+ /* we'd lost the race with close(), sod off silently */
+ /* note that inode->i_lock prevents reordering problems
+ * between accesses to descriptor table and ->i_dnotify */
+ if (f != filp)
+ goto out_free;
+
+ error = __f_setown(filp, task_pid(current), PIDTYPE_PID, 0);
+ if (error)
+ goto out_free;
+
+ dn->dn_mask = arg;
+ dn->dn_fd = fd;
+ dn->dn_filp = filp;
+ dn->dn_owner = id;
+ inode->i_dnotify_mask |= arg & ~DN_MULTISHOT;
+ dn->dn_next = inode->i_dnotify;
+ inode->i_dnotify = dn;
+ spin_unlock(&inode->i_lock);
+
+ if (filp->f_op && filp->f_op->dir_notify)
+ return filp->f_op->dir_notify(filp, arg);
+ return 0;
+
+out_free:
+ spin_unlock(&inode->i_lock);
+ kmem_cache_free(dn_cache, dn);
+ return error;
+}
+
+void __inode_dir_notify(struct inode *inode, unsigned long event)
+{
+ struct dnotify_struct * dn;
+ struct dnotify_struct **prev;
+ struct fown_struct * fown;
+ int changed = 0;
+
+ spin_lock(&inode->i_lock);
+ prev = &inode->i_dnotify;
+ while ((dn = *prev) != NULL) {
+ if ((dn->dn_mask & event) == 0) {
+ prev = &dn->dn_next;
+ continue;
+ }
+ fown = &dn->dn_filp->f_owner;
+ send_sigio(fown, dn->dn_fd, POLL_MSG);
+ if (dn->dn_mask & DN_MULTISHOT)
+ prev = &dn->dn_next;
+ else {
+ *prev = dn->dn_next;
+ changed = 1;
+ kmem_cache_free(dn_cache, dn);
+ }
+ }
+ if (changed)
+ redo_inode_mask(inode);
+ spin_unlock(&inode->i_lock);
+}
+
+EXPORT_SYMBOL(__inode_dir_notify);
+
+/*
+ * This is hopelessly wrong, but unfixable without API changes. At
+ * least it doesn't oops the kernel...
+ *
+ * To safely access ->d_parent we need to keep d_move away from it. Use the
+ * dentry's d_lock for this.
+ */
+void dnotify_parent(struct dentry *dentry, unsigned long event)
+{
+ struct dentry *parent;
+
+ if (!dir_notify_enable)
+ return;
+
+ spin_lock(&dentry->d_lock);
+ parent = dentry->d_parent;
+ if (parent->d_inode->i_dnotify_mask & event) {
+ dget(parent);
+ spin_unlock(&dentry->d_lock);
+ __inode_dir_notify(parent->d_inode, event);
+ dput(parent);
+ } else {
+ spin_unlock(&dentry->d_lock);
+ }
+}
+EXPORT_SYMBOL_GPL(dnotify_parent);
+
+static int __init dnotify_init(void)
+{
+ dn_cache = kmem_cache_create("dnotify_cache",
+ sizeof(struct dnotify_struct), 0, SLAB_PANIC, NULL);
+ return 0;
+}
+
+module_init(dnotify_init)
diff --git a/fs/notify/inotify.c b/fs/notify/inotify.c
new file mode 100644
index 0000000..690e725
--- /dev/null
+++ b/fs/notify/inotify.c
@@ -0,0 +1,773 @@
+/*
+ * fs/inotify.c - inode-based file event notifications
+ *
+ * Authors:
+ * John McCutchan <ttb@tentacle.dhs.org>
+ * Robert Love <rml@novell.com>
+ *
+ * Kernel API added by: Amy Griffis <amy.griffis@hp.com>
+ *
+ * Copyright (C) 2005 John McCutchan
+ * Copyright 2006 Hewlett-Packard Development Company, L.P.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/spinlock.h>
+#include <linux/idr.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/sched.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/writeback.h>
+#include <linux/inotify.h>
+
+static atomic_t inotify_cookie;
+
+/*
+ * Lock ordering:
+ *
+ * dentry->d_lock (used to keep d_move() away from dentry->d_parent)
+ * iprune_mutex (synchronize shrink_icache_memory())
+ * inode_lock (protects the super_block->s_inodes list)
+ * inode->inotify_mutex (protects inode->inotify_watches and watches->i_list)
+ * inotify_handle->mutex (protects inotify_handle and watches->h_list)
+ *
+ * The inode->inotify_mutex and inotify_handle->mutex and held during execution
+ * of a caller's event handler. Thus, the caller must not hold any locks
+ * taken in their event handler while calling any of the published inotify
+ * interfaces.
+ */
+
+/*
+ * Lifetimes of the three main data structures--inotify_handle, inode, and
+ * inotify_watch--are managed by reference count.
+ *
+ * inotify_handle: Lifetime is from inotify_init() to inotify_destroy().
+ * Additional references can bump the count via get_inotify_handle() and drop
+ * the count via put_inotify_handle().
+ *
+ * inotify_watch: for inotify's purposes, lifetime is from inotify_add_watch()
+ * to remove_watch_no_event(). Additional references can bump the count via
+ * get_inotify_watch() and drop the count via put_inotify_watch(). The caller
+ * is reponsible for the final put after receiving IN_IGNORED, or when using
+ * IN_ONESHOT after receiving the first event. Inotify does the final put if
+ * inotify_destroy() is called.
+ *
+ * inode: Pinned so long as the inode is associated with a watch, from
+ * inotify_add_watch() to the final put_inotify_watch().
+ */
+
+/*
+ * struct inotify_handle - represents an inotify instance
+ *
+ * This structure is protected by the mutex 'mutex'.
+ */
+struct inotify_handle {
+ struct idr idr; /* idr mapping wd -> watch */
+ struct mutex mutex; /* protects this bad boy */
+ struct list_head watches; /* list of watches */
+ atomic_t count; /* reference count */
+ u32 last_wd; /* the last wd allocated */
+ const struct inotify_operations *in_ops; /* inotify caller operations */
+};
+
+static inline void get_inotify_handle(struct inotify_handle *ih)
+{
+ atomic_inc(&ih->count);
+}
+
+static inline void put_inotify_handle(struct inotify_handle *ih)
+{
+ if (atomic_dec_and_test(&ih->count)) {
+ idr_destroy(&ih->idr);
+ kfree(ih);
+ }
+}
+
+/**
+ * get_inotify_watch - grab a reference to an inotify_watch
+ * @watch: watch to grab
+ */
+void get_inotify_watch(struct inotify_watch *watch)
+{
+ atomic_inc(&watch->count);
+}
+EXPORT_SYMBOL_GPL(get_inotify_watch);
+
+/**
+ * put_inotify_watch - decrements the ref count on a given watch. cleans up
+ * watch references if the count reaches zero. inotify_watch is freed by
+ * inotify callers via the destroy_watch() op.
+ * @watch: watch to release
+ */
+void put_inotify_watch(struct inotify_watch *watch)
+{
+ if (atomic_dec_and_test(&watch->count)) {
+ struct inotify_handle *ih = watch->ih;
+
+ iput(watch->inode);
+ ih->in_ops->destroy_watch(watch);
+ put_inotify_handle(ih);
+ }
+}
+EXPORT_SYMBOL_GPL(put_inotify_watch);
+
+/*
+ * inotify_handle_get_wd - returns the next WD for use by the given handle
+ *
+ * Callers must hold ih->mutex. This function can sleep.
+ */
+static int inotify_handle_get_wd(struct inotify_handle *ih,
+ struct inotify_watch *watch)
+{
+ int ret;
+
+ do {
+ if (unlikely(!idr_pre_get(&ih->idr, GFP_KERNEL)))
+ return -ENOSPC;
+ ret = idr_get_new_above(&ih->idr, watch, ih->last_wd+1, &watch->wd);
+ } while (ret == -EAGAIN);
+
+ if (likely(!ret))
+ ih->last_wd = watch->wd;
+
+ return ret;
+}
+
+/*
+ * inotify_inode_watched - returns nonzero if there are watches on this inode
+ * and zero otherwise. We call this lockless, we do not care if we race.
+ */
+static inline int inotify_inode_watched(struct inode *inode)
+{
+ return !list_empty(&inode->inotify_watches);
+}
+
+/*
+ * Get child dentry flag into synch with parent inode.
+ * Flag should always be clear for negative dentrys.
+ */
+static void set_dentry_child_flags(struct inode *inode, int watched)
+{
+ struct dentry *alias;
+
+ spin_lock(&dcache_lock);
+ list_for_each_entry(alias, &inode->i_dentry, d_alias) {
+ struct dentry *child;
+
+ list_for_each_entry(child, &alias->d_subdirs, d_u.d_child) {
+ if (!child->d_inode)
+ continue;
+
+ spin_lock(&child->d_lock);
+ if (watched)
+ child->d_flags |= DCACHE_INOTIFY_PARENT_WATCHED;
+ else
+ child->d_flags &=~DCACHE_INOTIFY_PARENT_WATCHED;
+ spin_unlock(&child->d_lock);
+ }
+ }
+ spin_unlock(&dcache_lock);
+}
+
+/*
+ * inotify_find_handle - find the watch associated with the given inode and
+ * handle
+ *
+ * Callers must hold inode->inotify_mutex.
+ */
+static struct inotify_watch *inode_find_handle(struct inode *inode,
+ struct inotify_handle *ih)
+{
+ struct inotify_watch *watch;
+
+ list_for_each_entry(watch, &inode->inotify_watches, i_list) {
+ if (watch->ih == ih)
+ return watch;
+ }
+
+ return NULL;
+}
+
+/*
+ * remove_watch_no_event - remove watch without the IN_IGNORED event.
+ *
+ * Callers must hold both inode->inotify_mutex and ih->mutex.
+ */
+static void remove_watch_no_event(struct inotify_watch *watch,
+ struct inotify_handle *ih)
+{
+ list_del(&watch->i_list);
+ list_del(&watch->h_list);
+
+ if (!inotify_inode_watched(watch->inode))
+ set_dentry_child_flags(watch->inode, 0);
+
+ idr_remove(&ih->idr, watch->wd);
+}
+
+/**
+ * inotify_remove_watch_locked - Remove a watch from both the handle and the
+ * inode. Sends the IN_IGNORED event signifying that the inode is no longer
+ * watched. May be invoked from a caller's event handler.
+ * @ih: inotify handle associated with watch
+ * @watch: watch to remove
+ *
+ * Callers must hold both inode->inotify_mutex and ih->mutex.
+ */
+void inotify_remove_watch_locked(struct inotify_handle *ih,
+ struct inotify_watch *watch)
+{
+ remove_watch_no_event(watch, ih);
+ ih->in_ops->handle_event(watch, watch->wd, IN_IGNORED, 0, NULL, NULL);
+}
+EXPORT_SYMBOL_GPL(inotify_remove_watch_locked);
+
+/* Kernel API for producing events */
+
+/*
+ * inotify_d_instantiate - instantiate dcache entry for inode
+ */
+void inotify_d_instantiate(struct dentry *entry, struct inode *inode)
+{
+ struct dentry *parent;
+
+ if (!inode)
+ return;
+
+ spin_lock(&entry->d_lock);
+ parent = entry->d_parent;
+ if (parent->d_inode && inotify_inode_watched(parent->d_inode))
+ entry->d_flags |= DCACHE_INOTIFY_PARENT_WATCHED;
+ spin_unlock(&entry->d_lock);
+}
+
+/*
+ * inotify_d_move - dcache entry has been moved
+ */
+void inotify_d_move(struct dentry *entry)
+{
+ struct dentry *parent;
+
+ parent = entry->d_parent;
+ if (inotify_inode_watched(parent->d_inode))
+ entry->d_flags |= DCACHE_INOTIFY_PARENT_WATCHED;
+ else
+ entry->d_flags &= ~DCACHE_INOTIFY_PARENT_WATCHED;
+}
+
+/**
+ * inotify_inode_queue_event - queue an event to all watches on this inode
+ * @inode: inode event is originating from
+ * @mask: event mask describing this event
+ * @cookie: cookie for synchronization, or zero
+ * @name: filename, if any
+ * @n_inode: inode associated with name
+ */
+void inotify_inode_queue_event(struct inode *inode, u32 mask, u32 cookie,
+ const char *name, struct inode *n_inode)
+{
+ struct inotify_watch *watch, *next;
+
+ if (!inotify_inode_watched(inode))
+ return;
+
+ mutex_lock(&inode->inotify_mutex);
+ list_for_each_entry_safe(watch, next, &inode->inotify_watches, i_list) {
+ u32 watch_mask = watch->mask;
+ if (watch_mask & mask) {
+ struct inotify_handle *ih= watch->ih;
+ mutex_lock(&ih->mutex);
+ if (watch_mask & IN_ONESHOT)
+ remove_watch_no_event(watch, ih);
+ ih->in_ops->handle_event(watch, watch->wd, mask, cookie,
+ name, n_inode);
+ mutex_unlock(&ih->mutex);
+ }
+ }
+ mutex_unlock(&inode->inotify_mutex);
+}
+EXPORT_SYMBOL_GPL(inotify_inode_queue_event);
+
+/**
+ * inotify_dentry_parent_queue_event - queue an event to a dentry's parent
+ * @dentry: the dentry in question, we queue against this dentry's parent
+ * @mask: event mask describing this event
+ * @cookie: cookie for synchronization, or zero
+ * @name: filename, if any
+ */
+void inotify_dentry_parent_queue_event(struct dentry *dentry, u32 mask,
+ u32 cookie, const char *name)
+{
+ struct dentry *parent;
+ struct inode *inode;
+
+ if (!(dentry->d_flags & DCACHE_INOTIFY_PARENT_WATCHED))
+ return;
+
+ spin_lock(&dentry->d_lock);
+ parent = dentry->d_parent;
+ inode = parent->d_inode;
+
+ if (inotify_inode_watched(inode)) {
+ dget(parent);
+ spin_unlock(&dentry->d_lock);
+ inotify_inode_queue_event(inode, mask, cookie, name,
+ dentry->d_inode);
+ dput(parent);
+ } else
+ spin_unlock(&dentry->d_lock);
+}
+EXPORT_SYMBOL_GPL(inotify_dentry_parent_queue_event);
+
+/**
+ * inotify_get_cookie - return a unique cookie for use in synchronizing events.
+ */
+u32 inotify_get_cookie(void)
+{
+ return atomic_inc_return(&inotify_cookie);
+}
+EXPORT_SYMBOL_GPL(inotify_get_cookie);
+
+/**
+ * inotify_unmount_inodes - an sb is unmounting. handle any watched inodes.
+ * @list: list of inodes being unmounted (sb->s_inodes)
+ *
+ * Called with inode_lock held, protecting the unmounting super block's list
+ * of inodes, and with iprune_mutex held, keeping shrink_icache_memory() at bay.
+ * We temporarily drop inode_lock, however, and CAN block.
+ */
+void inotify_unmount_inodes(struct list_head *list)
+{
+ struct inode *inode, *next_i, *need_iput = NULL;
+
+ list_for_each_entry_safe(inode, next_i, list, i_sb_list) {
+ struct inotify_watch *watch, *next_w;
+ struct inode *need_iput_tmp;
+ struct list_head *watches;
+
+ /*
+ * If i_count is zero, the inode cannot have any watches and
+ * doing an __iget/iput with MS_ACTIVE clear would actually
+ * evict all inodes with zero i_count from icache which is
+ * unnecessarily violent and may in fact be illegal to do.
+ */
+ if (!atomic_read(&inode->i_count))
+ continue;
+
+ /*
+ * We cannot __iget() an inode in state I_CLEAR, I_FREEING, or
+ * I_WILL_FREE which is fine because by that point the inode
+ * cannot have any associated watches.
+ */
+ if (inode->i_state & (I_CLEAR | I_FREEING | I_WILL_FREE))
+ continue;
+
+ need_iput_tmp = need_iput;
+ need_iput = NULL;
+ /* In case inotify_remove_watch_locked() drops a reference. */
+ if (inode != need_iput_tmp)
+ __iget(inode);
+ else
+ need_iput_tmp = NULL;
+ /* In case the dropping of a reference would nuke next_i. */
+ if ((&next_i->i_sb_list != list) &&
+ atomic_read(&next_i->i_count) &&
+ !(next_i->i_state & (I_CLEAR | I_FREEING |
+ I_WILL_FREE))) {
+ __iget(next_i);
+ need_iput = next_i;
+ }
+
+ /*
+ * We can safely drop inode_lock here because we hold
+ * references on both inode and next_i. Also no new inodes
+ * will be added since the umount has begun. Finally,
+ * iprune_mutex keeps shrink_icache_memory() away.
+ */
+ spin_unlock(&inode_lock);
+
+ if (need_iput_tmp)
+ iput(need_iput_tmp);
+
+ /* for each watch, send IN_UNMOUNT and then remove it */
+ mutex_lock(&inode->inotify_mutex);
+ watches = &inode->inotify_watches;
+ list_for_each_entry_safe(watch, next_w, watches, i_list) {
+ struct inotify_handle *ih= watch->ih;
+ mutex_lock(&ih->mutex);
+ ih->in_ops->handle_event(watch, watch->wd, IN_UNMOUNT, 0,
+ NULL, NULL);
+ inotify_remove_watch_locked(ih, watch);
+ mutex_unlock(&ih->mutex);
+ }
+ mutex_unlock(&inode->inotify_mutex);
+ iput(inode);
+
+ spin_lock(&inode_lock);
+ }
+}
+EXPORT_SYMBOL_GPL(inotify_unmount_inodes);
+
+/**
+ * inotify_inode_is_dead - an inode has been deleted, cleanup any watches
+ * @inode: inode that is about to be removed
+ */
+void inotify_inode_is_dead(struct inode *inode)
+{
+ struct inotify_watch *watch, *next;
+
+ mutex_lock(&inode->inotify_mutex);
+ list_for_each_entry_safe(watch, next, &inode->inotify_watches, i_list) {
+ struct inotify_handle *ih = watch->ih;
+ mutex_lock(&ih->mutex);
+ inotify_remove_watch_locked(ih, watch);
+ mutex_unlock(&ih->mutex);
+ }
+ mutex_unlock(&inode->inotify_mutex);
+}
+EXPORT_SYMBOL_GPL(inotify_inode_is_dead);
+
+/* Kernel Consumer API */
+
+/**
+ * inotify_init - allocate and initialize an inotify instance
+ * @ops: caller's inotify operations
+ */
+struct inotify_handle *inotify_init(const struct inotify_operations *ops)
+{
+ struct inotify_handle *ih;
+
+ ih = kmalloc(sizeof(struct inotify_handle), GFP_KERNEL);
+ if (unlikely(!ih))
+ return ERR_PTR(-ENOMEM);
+
+ idr_init(&ih->idr);
+ INIT_LIST_HEAD(&ih->watches);
+ mutex_init(&ih->mutex);
+ ih->last_wd = 0;
+ ih->in_ops = ops;
+ atomic_set(&ih->count, 0);
+ get_inotify_handle(ih);
+
+ return ih;
+}
+EXPORT_SYMBOL_GPL(inotify_init);
+
+/**
+ * inotify_init_watch - initialize an inotify watch
+ * @watch: watch to initialize
+ */
+void inotify_init_watch(struct inotify_watch *watch)
+{
+ INIT_LIST_HEAD(&watch->h_list);
+ INIT_LIST_HEAD(&watch->i_list);
+ atomic_set(&watch->count, 0);
+ get_inotify_watch(watch); /* initial get */
+}
+EXPORT_SYMBOL_GPL(inotify_init_watch);
+
+/**
+ * inotify_destroy - clean up and destroy an inotify instance
+ * @ih: inotify handle
+ */
+void inotify_destroy(struct inotify_handle *ih)
+{
+ /*
+ * Destroy all of the watches for this handle. Unfortunately, not very
+ * pretty. We cannot do a simple iteration over the list, because we
+ * do not know the inode until we iterate to the watch. But we need to
+ * hold inode->inotify_mutex before ih->mutex. The following works.
+ */
+ while (1) {
+ struct inotify_watch *watch;
+ struct list_head *watches;
+ struct inode *inode;
+
+ mutex_lock(&ih->mutex);
+ watches = &ih->watches;
+ if (list_empty(watches)) {
+ mutex_unlock(&ih->mutex);
+ break;
+ }
+ watch = list_first_entry(watches, struct inotify_watch, h_list);
+ get_inotify_watch(watch);
+ mutex_unlock(&ih->mutex);
+
+ inode = watch->inode;
+ mutex_lock(&inode->inotify_mutex);
+ mutex_lock(&ih->mutex);
+
+ /* make sure we didn't race with another list removal */
+ if (likely(idr_find(&ih->idr, watch->wd))) {
+ remove_watch_no_event(watch, ih);
+ put_inotify_watch(watch);
+ }
+
+ mutex_unlock(&ih->mutex);
+ mutex_unlock(&inode->inotify_mutex);
+ put_inotify_watch(watch);
+ }
+
+ /* free this handle: the put matching the get in inotify_init() */
+ put_inotify_handle(ih);
+}
+EXPORT_SYMBOL_GPL(inotify_destroy);
+
+/**
+ * inotify_find_watch - find an existing watch for an (ih,inode) pair
+ * @ih: inotify handle
+ * @inode: inode to watch
+ * @watchp: pointer to existing inotify_watch
+ *
+ * Caller must pin given inode (via nameidata).
+ */
+s32 inotify_find_watch(struct inotify_handle *ih, struct inode *inode,
+ struct inotify_watch **watchp)
+{
+ struct inotify_watch *old;
+ int ret = -ENOENT;
+
+ mutex_lock(&inode->inotify_mutex);
+ mutex_lock(&ih->mutex);
+
+ old = inode_find_handle(inode, ih);
+ if (unlikely(old)) {
+ get_inotify_watch(old); /* caller must put watch */
+ *watchp = old;
+ ret = old->wd;
+ }
+
+ mutex_unlock(&ih->mutex);
+ mutex_unlock(&inode->inotify_mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(inotify_find_watch);
+
+/**
+ * inotify_find_update_watch - find and update the mask of an existing watch
+ * @ih: inotify handle
+ * @inode: inode's watch to update
+ * @mask: mask of events to watch
+ *
+ * Caller must pin given inode (via nameidata).
+ */
+s32 inotify_find_update_watch(struct inotify_handle *ih, struct inode *inode,
+ u32 mask)
+{
+ struct inotify_watch *old;
+ int mask_add = 0;
+ int ret;
+
+ if (mask & IN_MASK_ADD)
+ mask_add = 1;
+
+ /* don't allow invalid bits: we don't want flags set */
+ mask &= IN_ALL_EVENTS | IN_ONESHOT;
+ if (unlikely(!mask))
+ return -EINVAL;
+
+ mutex_lock(&inode->inotify_mutex);
+ mutex_lock(&ih->mutex);
+
+ /*
+ * Handle the case of re-adding a watch on an (inode,ih) pair that we
+ * are already watching. We just update the mask and return its wd.
+ */
+ old = inode_find_handle(inode, ih);
+ if (unlikely(!old)) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ if (mask_add)
+ old->mask |= mask;
+ else
+ old->mask = mask;
+ ret = old->wd;
+out:
+ mutex_unlock(&ih->mutex);
+ mutex_unlock(&inode->inotify_mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(inotify_find_update_watch);
+
+/**
+ * inotify_add_watch - add a watch to an inotify instance
+ * @ih: inotify handle
+ * @watch: caller allocated watch structure
+ * @inode: inode to watch
+ * @mask: mask of events to watch
+ *
+ * Caller must pin given inode (via nameidata).
+ * Caller must ensure it only calls inotify_add_watch() once per watch.
+ * Calls inotify_handle_get_wd() so may sleep.
+ */
+s32 inotify_add_watch(struct inotify_handle *ih, struct inotify_watch *watch,
+ struct inode *inode, u32 mask)
+{
+ int ret = 0;
+ int newly_watched;
+
+ /* don't allow invalid bits: we don't want flags set */
+ mask &= IN_ALL_EVENTS | IN_ONESHOT;
+ if (unlikely(!mask))
+ return -EINVAL;
+ watch->mask = mask;
+
+ mutex_lock(&inode->inotify_mutex);
+ mutex_lock(&ih->mutex);
+
+ /* Initialize a new watch */
+ ret = inotify_handle_get_wd(ih, watch);
+ if (unlikely(ret))
+ goto out;
+ ret = watch->wd;
+
+ /* save a reference to handle and bump the count to make it official */
+ get_inotify_handle(ih);
+ watch->ih = ih;
+
+ /*
+ * Save a reference to the inode and bump the ref count to make it
+ * official. We hold a reference to nameidata, which makes this safe.
+ */
+ watch->inode = igrab(inode);
+
+ /* Add the watch to the handle's and the inode's list */
+ newly_watched = !inotify_inode_watched(inode);
+ list_add(&watch->h_list, &ih->watches);
+ list_add(&watch->i_list, &inode->inotify_watches);
+ /*
+ * Set child flags _after_ adding the watch, so there is no race
+ * windows where newly instantiated children could miss their parent's
+ * watched flag.
+ */
+ if (newly_watched)
+ set_dentry_child_flags(inode, 1);
+
+out:
+ mutex_unlock(&ih->mutex);
+ mutex_unlock(&inode->inotify_mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(inotify_add_watch);
+
+/**
+ * inotify_clone_watch - put the watch next to existing one
+ * @old: already installed watch
+ * @new: new watch
+ *
+ * Caller must hold the inotify_mutex of inode we are dealing with;
+ * it is expected to remove the old watch before unlocking the inode.
+ */
+s32 inotify_clone_watch(struct inotify_watch *old, struct inotify_watch *new)
+{
+ struct inotify_handle *ih = old->ih;
+ int ret = 0;
+
+ new->mask = old->mask;
+ new->ih = ih;
+
+ mutex_lock(&ih->mutex);
+
+ /* Initialize a new watch */
+ ret = inotify_handle_get_wd(ih, new);
+ if (unlikely(ret))
+ goto out;
+ ret = new->wd;
+
+ get_inotify_handle(ih);
+
+ new->inode = igrab(old->inode);
+
+ list_add(&new->h_list, &ih->watches);
+ list_add(&new->i_list, &old->inode->inotify_watches);
+out:
+ mutex_unlock(&ih->mutex);
+ return ret;
+}
+
+void inotify_evict_watch(struct inotify_watch *watch)
+{
+ get_inotify_watch(watch);
+ mutex_lock(&watch->ih->mutex);
+ inotify_remove_watch_locked(watch->ih, watch);
+ mutex_unlock(&watch->ih->mutex);
+}
+
+/**
+ * inotify_rm_wd - remove a watch from an inotify instance
+ * @ih: inotify handle
+ * @wd: watch descriptor to remove
+ *
+ * Can sleep.
+ */
+int inotify_rm_wd(struct inotify_handle *ih, u32 wd)
+{
+ struct inotify_watch *watch;
+ struct inode *inode;
+
+ mutex_lock(&ih->mutex);
+ watch = idr_find(&ih->idr, wd);
+ if (unlikely(!watch)) {
+ mutex_unlock(&ih->mutex);
+ return -EINVAL;
+ }
+ get_inotify_watch(watch);
+ inode = watch->inode;
+ mutex_unlock(&ih->mutex);
+
+ mutex_lock(&inode->inotify_mutex);
+ mutex_lock(&ih->mutex);
+
+ /* make sure that we did not race */
+ if (likely(idr_find(&ih->idr, wd) == watch))
+ inotify_remove_watch_locked(ih, watch);
+
+ mutex_unlock(&ih->mutex);
+ mutex_unlock(&inode->inotify_mutex);
+ put_inotify_watch(watch);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(inotify_rm_wd);
+
+/**
+ * inotify_rm_watch - remove a watch from an inotify instance
+ * @ih: inotify handle
+ * @watch: watch to remove
+ *
+ * Can sleep.
+ */
+int inotify_rm_watch(struct inotify_handle *ih,
+ struct inotify_watch *watch)
+{
+ return inotify_rm_wd(ih, watch->wd);
+}
+EXPORT_SYMBOL_GPL(inotify_rm_watch);
+
+/*
+ * inotify_setup - core initialization function
+ */
+static int __init inotify_setup(void)
+{
+ atomic_set(&inotify_cookie, 0);
+
+ return 0;
+}
+
+module_init(inotify_setup);
diff --git a/fs/notify/inotify_user.c b/fs/notify/inotify_user.c
new file mode 100644
index 0000000..d85c7d9
--- /dev/null
+++ b/fs/notify/inotify_user.c
@@ -0,0 +1,781 @@
+/*
+ * fs/inotify_user.c - inotify support for userspace
+ *
+ * Authors:
+ * John McCutchan <ttb@tentacle.dhs.org>
+ * Robert Love <rml@novell.com>
+ *
+ * Copyright (C) 2005 John McCutchan
+ * Copyright 2006 Hewlett-Packard Development Company, L.P.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/file.h>
+#include <linux/mount.h>
+#include <linux/namei.h>
+#include <linux/poll.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/inotify.h>
+#include <linux/syscalls.h>
+#include <linux/magic.h>
+
+#include <asm/ioctls.h>
+
+static struct kmem_cache *watch_cachep __read_mostly;
+static struct kmem_cache *event_cachep __read_mostly;
+
+static struct vfsmount *inotify_mnt __read_mostly;
+
+/* these are configurable via /proc/sys/fs/inotify/ */
+static int inotify_max_user_instances __read_mostly;
+static int inotify_max_user_watches __read_mostly;
+static int inotify_max_queued_events __read_mostly;
+
+/*
+ * Lock ordering:
+ *
+ * inotify_dev->up_mutex (ensures we don't re-add the same watch)
+ * inode->inotify_mutex (protects inode's watch list)
+ * inotify_handle->mutex (protects inotify_handle's watch list)
+ * inotify_dev->ev_mutex (protects device's event queue)
+ */
+
+/*
+ * Lifetimes of the main data structures:
+ *
+ * inotify_device: Lifetime is managed by reference count, from
+ * sys_inotify_init() until release. Additional references can bump the count
+ * via get_inotify_dev() and drop the count via put_inotify_dev().
+ *
+ * inotify_user_watch: Lifetime is from create_watch() to the receipt of an
+ * IN_IGNORED event from inotify, or when using IN_ONESHOT, to receipt of the
+ * first event, or to inotify_destroy().
+ */
+
+/*
+ * struct inotify_device - represents an inotify instance
+ *
+ * This structure is protected by the mutex 'mutex'.
+ */
+struct inotify_device {
+ wait_queue_head_t wq; /* wait queue for i/o */
+ struct mutex ev_mutex; /* protects event queue */
+ struct mutex up_mutex; /* synchronizes watch updates */
+ struct list_head events; /* list of queued events */
+ atomic_t count; /* reference count */
+ struct user_struct *user; /* user who opened this dev */
+ struct inotify_handle *ih; /* inotify handle */
+ struct fasync_struct *fa; /* async notification */
+ unsigned int queue_size; /* size of the queue (bytes) */
+ unsigned int event_count; /* number of pending events */
+ unsigned int max_events; /* maximum number of events */
+};
+
+/*
+ * struct inotify_kernel_event - An inotify event, originating from a watch and
+ * queued for user-space. A list of these is attached to each instance of the
+ * device. In read(), this list is walked and all events that can fit in the
+ * buffer are returned.
+ *
+ * Protected by dev->ev_mutex of the device in which we are queued.
+ */
+struct inotify_kernel_event {
+ struct inotify_event event; /* the user-space event */
+ struct list_head list; /* entry in inotify_device's list */
+ char *name; /* filename, if any */
+};
+
+/*
+ * struct inotify_user_watch - our version of an inotify_watch, we add
+ * a reference to the associated inotify_device.
+ */
+struct inotify_user_watch {
+ struct inotify_device *dev; /* associated device */
+ struct inotify_watch wdata; /* inotify watch data */
+};
+
+#ifdef CONFIG_SYSCTL
+
+#include <linux/sysctl.h>
+
+static int zero;
+
+ctl_table inotify_table[] = {
+ {
+ .ctl_name = INOTIFY_MAX_USER_INSTANCES,
+ .procname = "max_user_instances",
+ .data = &inotify_max_user_instances,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec_minmax,
+ .strategy = &sysctl_intvec,
+ .extra1 = &zero,
+ },
+ {
+ .ctl_name = INOTIFY_MAX_USER_WATCHES,
+ .procname = "max_user_watches",
+ .data = &inotify_max_user_watches,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec_minmax,
+ .strategy = &sysctl_intvec,
+ .extra1 = &zero,
+ },
+ {
+ .ctl_name = INOTIFY_MAX_QUEUED_EVENTS,
+ .procname = "max_queued_events",
+ .data = &inotify_max_queued_events,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec_minmax,
+ .strategy = &sysctl_intvec,
+ .extra1 = &zero
+ },
+ { .ctl_name = 0 }
+};
+#endif /* CONFIG_SYSCTL */
+
+static inline void get_inotify_dev(struct inotify_device *dev)
+{
+ atomic_inc(&dev->count);
+}
+
+static inline void put_inotify_dev(struct inotify_device *dev)
+{
+ if (atomic_dec_and_test(&dev->count)) {
+ atomic_dec(&dev->user->inotify_devs);
+ free_uid(dev->user);
+ kfree(dev);
+ }
+}
+
+/*
+ * free_inotify_user_watch - cleans up the watch and its references
+ */
+static void free_inotify_user_watch(struct inotify_watch *w)
+{
+ struct inotify_user_watch *watch;
+ struct inotify_device *dev;
+
+ watch = container_of(w, struct inotify_user_watch, wdata);
+ dev = watch->dev;
+
+ atomic_dec(&dev->user->inotify_watches);
+ put_inotify_dev(dev);
+ kmem_cache_free(watch_cachep, watch);
+}
+
+/*
+ * kernel_event - create a new kernel event with the given parameters
+ *
+ * This function can sleep.
+ */
+static struct inotify_kernel_event * kernel_event(s32 wd, u32 mask, u32 cookie,
+ const char *name)
+{
+ struct inotify_kernel_event *kevent;
+
+ kevent = kmem_cache_alloc(event_cachep, GFP_NOFS);
+ if (unlikely(!kevent))
+ return NULL;
+
+ /* we hand this out to user-space, so zero it just in case */
+ memset(&kevent->event, 0, sizeof(struct inotify_event));
+
+ kevent->event.wd = wd;
+ kevent->event.mask = mask;
+ kevent->event.cookie = cookie;
+
+ INIT_LIST_HEAD(&kevent->list);
+
+ if (name) {
+ size_t len, rem, event_size = sizeof(struct inotify_event);
+
+ /*
+ * We need to pad the filename so as to properly align an
+ * array of inotify_event structures. Because the structure is
+ * small and the common case is a small filename, we just round
+ * up to the next multiple of the structure's sizeof. This is
+ * simple and safe for all architectures.
+ */
+ len = strlen(name) + 1;
+ rem = event_size - len;
+ if (len > event_size) {
+ rem = event_size - (len % event_size);
+ if (len % event_size == 0)
+ rem = 0;
+ }
+
+ kevent->name = kmalloc(len + rem, GFP_KERNEL);
+ if (unlikely(!kevent->name)) {
+ kmem_cache_free(event_cachep, kevent);
+ return NULL;
+ }
+ memcpy(kevent->name, name, len);
+ if (rem)
+ memset(kevent->name + len, 0, rem);
+ kevent->event.len = len + rem;
+ } else {
+ kevent->event.len = 0;
+ kevent->name = NULL;
+ }
+
+ return kevent;
+}
+
+/*
+ * inotify_dev_get_event - return the next event in the given dev's queue
+ *
+ * Caller must hold dev->ev_mutex.
+ */
+static inline struct inotify_kernel_event *
+inotify_dev_get_event(struct inotify_device *dev)
+{
+ return list_entry(dev->events.next, struct inotify_kernel_event, list);
+}
+
+/*
+ * inotify_dev_get_last_event - return the last event in the given dev's queue
+ *
+ * Caller must hold dev->ev_mutex.
+ */
+static inline struct inotify_kernel_event *
+inotify_dev_get_last_event(struct inotify_device *dev)
+{
+ if (list_empty(&dev->events))
+ return NULL;
+ return list_entry(dev->events.prev, struct inotify_kernel_event, list);
+}
+
+/*
+ * inotify_dev_queue_event - event handler registered with core inotify, adds
+ * a new event to the given device
+ *
+ * Can sleep (calls kernel_event()).
+ */
+static void inotify_dev_queue_event(struct inotify_watch *w, u32 wd, u32 mask,
+ u32 cookie, const char *name,
+ struct inode *ignored)
+{
+ struct inotify_user_watch *watch;
+ struct inotify_device *dev;
+ struct inotify_kernel_event *kevent, *last;
+
+ watch = container_of(w, struct inotify_user_watch, wdata);
+ dev = watch->dev;
+
+ mutex_lock(&dev->ev_mutex);
+
+ /* we can safely put the watch as we don't reference it while
+ * generating the event
+ */
+ if (mask & IN_IGNORED || w->mask & IN_ONESHOT)
+ put_inotify_watch(w); /* final put */
+
+ /* coalescing: drop this event if it is a dupe of the previous */
+ last = inotify_dev_get_last_event(dev);
+ if (last && last->event.mask == mask && last->event.wd == wd &&
+ last->event.cookie == cookie) {
+ const char *lastname = last->name;
+
+ if (!name && !lastname)
+ goto out;
+ if (name && lastname && !strcmp(lastname, name))
+ goto out;
+ }
+
+ /* the queue overflowed and we already sent the Q_OVERFLOW event */
+ if (unlikely(dev->event_count > dev->max_events))
+ goto out;
+
+ /* if the queue overflows, we need to notify user space */
+ if (unlikely(dev->event_count == dev->max_events))
+ kevent = kernel_event(-1, IN_Q_OVERFLOW, cookie, NULL);
+ else
+ kevent = kernel_event(wd, mask, cookie, name);
+
+ if (unlikely(!kevent))
+ goto out;
+
+ /* queue the event and wake up anyone waiting */
+ dev->event_count++;
+ dev->queue_size += sizeof(struct inotify_event) + kevent->event.len;
+ list_add_tail(&kevent->list, &dev->events);
+ wake_up_interruptible(&dev->wq);
+ kill_fasync(&dev->fa, SIGIO, POLL_IN);
+
+out:
+ mutex_unlock(&dev->ev_mutex);
+}
+
+/*
+ * remove_kevent - cleans up the given kevent
+ *
+ * Caller must hold dev->ev_mutex.
+ */
+static void remove_kevent(struct inotify_device *dev,
+ struct inotify_kernel_event *kevent)
+{
+ list_del(&kevent->list);
+
+ dev->event_count--;
+ dev->queue_size -= sizeof(struct inotify_event) + kevent->event.len;
+}
+
+/*
+ * free_kevent - frees the given kevent.
+ */
+static void free_kevent(struct inotify_kernel_event *kevent)
+{
+ kfree(kevent->name);
+ kmem_cache_free(event_cachep, kevent);
+}
+
+/*
+ * inotify_dev_event_dequeue - destroy an event on the given device
+ *
+ * Caller must hold dev->ev_mutex.
+ */
+static void inotify_dev_event_dequeue(struct inotify_device *dev)
+{
+ if (!list_empty(&dev->events)) {
+ struct inotify_kernel_event *kevent;
+ kevent = inotify_dev_get_event(dev);
+ remove_kevent(dev, kevent);
+ free_kevent(kevent);
+ }
+}
+
+/*
+ * find_inode - resolve a user-given path to a specific inode
+ */
+static int find_inode(const char __user *dirname, struct path *path,
+ unsigned flags)
+{
+ int error;
+
+ error = user_path_at(AT_FDCWD, dirname, flags, path);
+ if (error)
+ return error;
+ /* you can only watch an inode if you have read permissions on it */
+ error = inode_permission(path->dentry->d_inode, MAY_READ);
+ if (error)
+ path_put(path);
+ return error;
+}
+
+/*
+ * create_watch - creates a watch on the given device.
+ *
+ * Callers must hold dev->up_mutex.
+ */
+static int create_watch(struct inotify_device *dev, struct inode *inode,
+ u32 mask)
+{
+ struct inotify_user_watch *watch;
+ int ret;
+
+ if (atomic_read(&dev->user->inotify_watches) >=
+ inotify_max_user_watches)
+ return -ENOSPC;
+
+ watch = kmem_cache_alloc(watch_cachep, GFP_KERNEL);
+ if (unlikely(!watch))
+ return -ENOMEM;
+
+ /* save a reference to device and bump the count to make it official */
+ get_inotify_dev(dev);
+ watch->dev = dev;
+
+ atomic_inc(&dev->user->inotify_watches);
+
+ inotify_init_watch(&watch->wdata);
+ ret = inotify_add_watch(dev->ih, &watch->wdata, inode, mask);
+ if (ret < 0)
+ free_inotify_user_watch(&watch->wdata);
+
+ return ret;
+}
+
+/* Device Interface */
+
+static unsigned int inotify_poll(struct file *file, poll_table *wait)
+{
+ struct inotify_device *dev = file->private_data;
+ int ret = 0;
+
+ poll_wait(file, &dev->wq, wait);
+ mutex_lock(&dev->ev_mutex);
+ if (!list_empty(&dev->events))
+ ret = POLLIN | POLLRDNORM;
+ mutex_unlock(&dev->ev_mutex);
+
+ return ret;
+}
+
+static ssize_t inotify_read(struct file *file, char __user *buf,
+ size_t count, loff_t *pos)
+{
+ size_t event_size = sizeof (struct inotify_event);
+ struct inotify_device *dev;
+ char __user *start;
+ int ret;
+ DEFINE_WAIT(wait);
+
+ start = buf;
+ dev = file->private_data;
+
+ while (1) {
+
+ prepare_to_wait(&dev->wq, &wait, TASK_INTERRUPTIBLE);
+
+ mutex_lock(&dev->ev_mutex);
+ if (!list_empty(&dev->events)) {
+ ret = 0;
+ break;
+ }
+ mutex_unlock(&dev->ev_mutex);
+
+ if (file->f_flags & O_NONBLOCK) {
+ ret = -EAGAIN;
+ break;
+ }
+
+ if (signal_pending(current)) {
+ ret = -EINTR;
+ break;
+ }
+
+ schedule();
+ }
+
+ finish_wait(&dev->wq, &wait);
+ if (ret)
+ return ret;
+
+ while (1) {
+ struct inotify_kernel_event *kevent;
+
+ ret = buf - start;
+ if (list_empty(&dev->events))
+ break;
+
+ kevent = inotify_dev_get_event(dev);
+ if (event_size + kevent->event.len > count) {
+ if (ret == 0 && count > 0) {
+ /*
+ * could not get a single event because we
+ * didn't have enough buffer space.
+ */
+ ret = -EINVAL;
+ }
+ break;
+ }
+ remove_kevent(dev, kevent);
+
+ /*
+ * Must perform the copy_to_user outside the mutex in order
+ * to avoid a lock order reversal with mmap_sem.
+ */
+ mutex_unlock(&dev->ev_mutex);
+
+ if (copy_to_user(buf, &kevent->event, event_size)) {
+ ret = -EFAULT;
+ break;
+ }
+ buf += event_size;
+ count -= event_size;
+
+ if (kevent->name) {
+ if (copy_to_user(buf, kevent->name, kevent->event.len)){
+ ret = -EFAULT;
+ break;
+ }
+ buf += kevent->event.len;
+ count -= kevent->event.len;
+ }
+
+ free_kevent(kevent);
+
+ mutex_lock(&dev->ev_mutex);
+ }
+ mutex_unlock(&dev->ev_mutex);
+
+ return ret;
+}
+
+static int inotify_fasync(int fd, struct file *file, int on)
+{
+ struct inotify_device *dev = file->private_data;
+
+ return fasync_helper(fd, file, on, &dev->fa) >= 0 ? 0 : -EIO;
+}
+
+static int inotify_release(struct inode *ignored, struct file *file)
+{
+ struct inotify_device *dev = file->private_data;
+
+ inotify_destroy(dev->ih);
+
+ /* destroy all of the events on this device */
+ mutex_lock(&dev->ev_mutex);
+ while (!list_empty(&dev->events))
+ inotify_dev_event_dequeue(dev);
+ mutex_unlock(&dev->ev_mutex);
+
+ if (file->f_flags & FASYNC)
+ inotify_fasync(-1, file, 0);
+
+ /* free this device: the put matching the get in inotify_init() */
+ put_inotify_dev(dev);
+
+ return 0;
+}
+
+static long inotify_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct inotify_device *dev;
+ void __user *p;
+ int ret = -ENOTTY;
+
+ dev = file->private_data;
+ p = (void __user *) arg;
+
+ switch (cmd) {
+ case FIONREAD:
+ ret = put_user(dev->queue_size, (int __user *) p);
+ break;
+ }
+
+ return ret;
+}
+
+static const struct file_operations inotify_fops = {
+ .poll = inotify_poll,
+ .read = inotify_read,
+ .fasync = inotify_fasync,
+ .release = inotify_release,
+ .unlocked_ioctl = inotify_ioctl,
+ .compat_ioctl = inotify_ioctl,
+};
+
+static const struct inotify_operations inotify_user_ops = {
+ .handle_event = inotify_dev_queue_event,
+ .destroy_watch = free_inotify_user_watch,
+};
+
+asmlinkage long sys_inotify_init1(int flags)
+{
+ struct inotify_device *dev;
+ struct inotify_handle *ih;
+ struct user_struct *user;
+ struct file *filp;
+ int fd, ret;
+
+ /* Check the IN_* constants for consistency. */
+ BUILD_BUG_ON(IN_CLOEXEC != O_CLOEXEC);
+ BUILD_BUG_ON(IN_NONBLOCK != O_NONBLOCK);
+
+ if (flags & ~(IN_CLOEXEC | IN_NONBLOCK))
+ return -EINVAL;
+
+ fd = get_unused_fd_flags(flags & O_CLOEXEC);
+ if (fd < 0)
+ return fd;
+
+ filp = get_empty_filp();
+ if (!filp) {
+ ret = -ENFILE;
+ goto out_put_fd;
+ }
+
+ user = get_uid(current->user);
+ if (unlikely(atomic_read(&user->inotify_devs) >=
+ inotify_max_user_instances)) {
+ ret = -EMFILE;
+ goto out_free_uid;
+ }
+
+ dev = kmalloc(sizeof(struct inotify_device), GFP_KERNEL);
+ if (unlikely(!dev)) {
+ ret = -ENOMEM;
+ goto out_free_uid;
+ }
+
+ ih = inotify_init(&inotify_user_ops);
+ if (IS_ERR(ih)) {
+ ret = PTR_ERR(ih);
+ goto out_free_dev;
+ }
+ dev->ih = ih;
+ dev->fa = NULL;
+
+ filp->f_op = &inotify_fops;
+ filp->f_path.mnt = mntget(inotify_mnt);
+ filp->f_path.dentry = dget(inotify_mnt->mnt_root);
+ filp->f_mapping = filp->f_path.dentry->d_inode->i_mapping;
+ filp->f_mode = FMODE_READ;
+ filp->f_flags = O_RDONLY | (flags & O_NONBLOCK);
+ filp->private_data = dev;
+
+ INIT_LIST_HEAD(&dev->events);
+ init_waitqueue_head(&dev->wq);
+ mutex_init(&dev->ev_mutex);
+ mutex_init(&dev->up_mutex);
+ dev->event_count = 0;
+ dev->queue_size = 0;
+ dev->max_events = inotify_max_queued_events;
+ dev->user = user;
+ atomic_set(&dev->count, 0);
+
+ get_inotify_dev(dev);
+ atomic_inc(&user->inotify_devs);
+ fd_install(fd, filp);
+
+ return fd;
+out_free_dev:
+ kfree(dev);
+out_free_uid:
+ free_uid(user);
+ put_filp(filp);
+out_put_fd:
+ put_unused_fd(fd);
+ return ret;
+}
+
+asmlinkage long sys_inotify_init(void)
+{
+ return sys_inotify_init1(0);
+}
+
+asmlinkage long sys_inotify_add_watch(int fd, const char __user *pathname, u32 mask)
+{
+ struct inode *inode;
+ struct inotify_device *dev;
+ struct path path;
+ struct file *filp;
+ int ret, fput_needed;
+ unsigned flags = 0;
+
+ filp = fget_light(fd, &fput_needed);
+ if (unlikely(!filp))
+ return -EBADF;
+
+ /* verify that this is indeed an inotify instance */
+ if (unlikely(filp->f_op != &inotify_fops)) {
+ ret = -EINVAL;
+ goto fput_and_out;
+ }
+
+ if (!(mask & IN_DONT_FOLLOW))
+ flags |= LOOKUP_FOLLOW;
+ if (mask & IN_ONLYDIR)
+ flags |= LOOKUP_DIRECTORY;
+
+ ret = find_inode(pathname, &path, flags);
+ if (unlikely(ret))
+ goto fput_and_out;
+
+ /* inode held in place by reference to path; dev by fget on fd */
+ inode = path.dentry->d_inode;
+ dev = filp->private_data;
+
+ mutex_lock(&dev->up_mutex);
+ ret = inotify_find_update_watch(dev->ih, inode, mask);
+ if (ret == -ENOENT)
+ ret = create_watch(dev, inode, mask);
+ mutex_unlock(&dev->up_mutex);
+
+ path_put(&path);
+fput_and_out:
+ fput_light(filp, fput_needed);
+ return ret;
+}
+
+asmlinkage long sys_inotify_rm_watch(int fd, u32 wd)
+{
+ struct file *filp;
+ struct inotify_device *dev;
+ int ret, fput_needed;
+
+ filp = fget_light(fd, &fput_needed);
+ if (unlikely(!filp))
+ return -EBADF;
+
+ /* verify that this is indeed an inotify instance */
+ if (unlikely(filp->f_op != &inotify_fops)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ dev = filp->private_data;
+
+ /* we free our watch data when we get IN_IGNORED */
+ ret = inotify_rm_wd(dev->ih, wd);
+
+out:
+ fput_light(filp, fput_needed);
+ return ret;
+}
+
+static int
+inotify_get_sb(struct file_system_type *fs_type, int flags,
+ const char *dev_name, void *data, struct vfsmount *mnt)
+{
+ return get_sb_pseudo(fs_type, "inotify", NULL,
+ INOTIFYFS_SUPER_MAGIC, mnt);
+}
+
+static struct file_system_type inotify_fs_type = {
+ .name = "inotifyfs",
+ .get_sb = inotify_get_sb,
+ .kill_sb = kill_anon_super,
+};
+
+/*
+ * inotify_user_setup - Our initialization function. Note that we cannnot return
+ * error because we have compiled-in VFS hooks. So an (unlikely) failure here
+ * must result in panic().
+ */
+static int __init inotify_user_setup(void)
+{
+ int ret;
+
+ ret = register_filesystem(&inotify_fs_type);
+ if (unlikely(ret))
+ panic("inotify: register_filesystem returned %d!\n", ret);
+
+ inotify_mnt = kern_mount(&inotify_fs_type);
+ if (IS_ERR(inotify_mnt))
+ panic("inotify: kern_mount ret %ld!\n", PTR_ERR(inotify_mnt));
+
+ inotify_max_queued_events = 16384;
+ inotify_max_user_instances = 128;
+ inotify_max_user_watches = 8192;
+
+ watch_cachep = kmem_cache_create("inotify_watch_cache",
+ sizeof(struct inotify_user_watch),
+ 0, SLAB_PANIC, NULL);
+ event_cachep = kmem_cache_create("inotify_event_cache",
+ sizeof(struct inotify_kernel_event),
+ 0, SLAB_PANIC, NULL);
+
+ return 0;
+}
+
+module_init(inotify_user_setup);
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH -v2 02/16] fsnotify: pass a file instead of an inode to open, read, and write
2008-10-14 20:51 [PATCH -v2 00/16] fanotify: novel all file access notification and permissions system Eric Paris
2008-10-14 20:51 ` [PATCH -v2 01/16] filesystem notification: create fs/notify to contain all fs notification Eric Paris
@ 2008-10-14 20:51 ` Eric Paris
2008-10-14 20:51 ` [PATCH -v2 03/16] fanotify: fscking all notify, system wide file access notification Eric Paris
` (13 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Eric Paris @ 2008-10-14 20:51 UTC (permalink / raw)
To: linux-kernel, malware-list; +Cc: viro, alan, arjan, greg, tytso
fanotify, the upcoming notification system actually needs a f_path so it can
do opens in the context of listeners, and it needs a file so it can get f_flags
from the original process. Close already was passing a file.
Signed-off-by: Eric Paris <eparis@redhat.com>
---
fs/compat.c | 5 ++---
fs/nfsd/vfs.c | 4 ++--
fs/open.c | 2 +-
fs/read_write.c | 8 ++++----
include/linux/fsnotify.h | 9 ++++++---
5 files changed, 15 insertions(+), 13 deletions(-)
diff --git a/fs/compat.c b/fs/compat.c
index 075d050..f59c796 100644
--- a/fs/compat.c
+++ b/fs/compat.c
@@ -1125,11 +1125,10 @@ out:
if (iov != iovstack)
kfree(iov);
if ((ret + (type == READ)) > 0) {
- struct dentry *dentry = file->f_path.dentry;
if (type == READ)
- fsnotify_access(dentry);
+ fsnotify_access(file);
else
- fsnotify_modify(dentry);
+ fsnotify_modify(file);
}
return ret;
}
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index 18060be..b576e38 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -937,7 +937,7 @@ nfsd_vfs_read(struct svc_rqst *rqstp, struct svc_fh *fhp, struct file *file,
nfsdstats.io_read += host_err;
*count = host_err;
err = 0;
- fsnotify_access(file->f_path.dentry);
+ fsnotify_access(file);
} else
err = nfserrno(host_err);
out:
@@ -1004,7 +1004,7 @@ nfsd_vfs_write(struct svc_rqst *rqstp, struct svc_fh *fhp, struct file *file,
set_fs(oldfs);
if (host_err >= 0) {
nfsdstats.io_write += cnt;
- fsnotify_modify(file->f_path.dentry);
+ fsnotify_modify(file);
}
/* clear setuid/setgid flag after write */
diff --git a/fs/open.c b/fs/open.c
index 07da935..1057c34 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -1020,7 +1020,7 @@ long do_sys_open(int dfd, const char __user *filename, int flags, int mode)
put_unused_fd(fd);
fd = PTR_ERR(f);
} else {
- fsnotify_open(f->f_path.dentry);
+ fsnotify_open(f);
fd_install(fd, f);
}
}
diff --git a/fs/read_write.c b/fs/read_write.c
index 9ba495d..39f1fce 100644
--- a/fs/read_write.c
+++ b/fs/read_write.c
@@ -258,7 +258,7 @@ ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
else
ret = do_sync_read(file, buf, count, pos);
if (ret > 0) {
- fsnotify_access(file->f_path.dentry);
+ fsnotify_access(file);
add_rchar(current, ret);
}
inc_syscr(current);
@@ -313,7 +313,7 @@ ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_
else
ret = do_sync_write(file, buf, count, pos);
if (ret > 0) {
- fsnotify_modify(file->f_path.dentry);
+ fsnotify_modify(file);
add_wchar(current, ret);
}
inc_syscw(current);
@@ -604,9 +604,9 @@ out:
kfree(iov);
if ((ret + (type == READ)) > 0) {
if (type == READ)
- fsnotify_access(file->f_path.dentry);
+ fsnotify_access(file);
else
- fsnotify_modify(file->f_path.dentry);
+ fsnotify_modify(file);
}
return ret;
}
diff --git a/include/linux/fsnotify.h b/include/linux/fsnotify.h
index a895131..dd76948 100644
--- a/include/linux/fsnotify.h
+++ b/include/linux/fsnotify.h
@@ -136,8 +136,9 @@ static inline void fsnotify_mkdir(struct inode *inode, struct dentry *dentry)
/*
* fsnotify_access - file was read
*/
-static inline void fsnotify_access(struct dentry *dentry)
+static inline void fsnotify_access(struct file *file)
{
+ struct dentry *dentry = file->f_path.dentry;
struct inode *inode = dentry->d_inode;
u32 mask = IN_ACCESS;
@@ -152,8 +153,9 @@ static inline void fsnotify_access(struct dentry *dentry)
/*
* fsnotify_modify - file was modified
*/
-static inline void fsnotify_modify(struct dentry *dentry)
+static inline void fsnotify_modify(struct file *file)
{
+ struct dentry *dentry = file->f_path.dentry;
struct inode *inode = dentry->d_inode;
u32 mask = IN_MODIFY;
@@ -168,8 +170,9 @@ static inline void fsnotify_modify(struct dentry *dentry)
/*
* fsnotify_open - file was opened
*/
-static inline void fsnotify_open(struct dentry *dentry)
+static inline void fsnotify_open(struct file *file)
{
+ struct dentry *dentry = file->f_path.dentry;
struct inode *inode = dentry->d_inode;
u32 mask = IN_OPEN;
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH -v2 03/16] fanotify: fscking all notify, system wide file access notification
2008-10-14 20:51 [PATCH -v2 00/16] fanotify: novel all file access notification and permissions system Eric Paris
2008-10-14 20:51 ` [PATCH -v2 01/16] filesystem notification: create fs/notify to contain all fs notification Eric Paris
2008-10-14 20:51 ` [PATCH -v2 02/16] fsnotify: pass a file instead of an inode to open, read, and write Eric Paris
@ 2008-10-14 20:51 ` Eric Paris
2008-10-14 20:51 ` [PATCH -v2 04/16] fsnotify: sys_execve and sys_uselib do not call into fsnotify Eric Paris
` (12 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Eric Paris @ 2008-10-14 20:51 UTC (permalink / raw)
To: linux-kernel, malware-list; +Cc: viro, alan, arjan, greg, tytso
A new system wide file access notification system.
Signed-off-by: Eric Paris <eparis@redhat.com>
---
fs/notify/Kconfig | 14 ++
fs/notify/Makefile | 2
fs/notify/fanotify.c | 99 +++++++++++++++++
fs/notify/fanotify.h | 64 +++++++++++
fs/notify/group.c | 110 +++++++++++++++++++
fs/notify/notification.c | 266 ++++++++++++++++++++++++++++++++++++++++++++++
include/linux/Kbuild | 1
include/linux/fanotify.h | 95 ++++++++++++++++
include/linux/fsnotify.h | 14 ++
include/linux/sched.h | 1
10 files changed, 662 insertions(+), 4 deletions(-)
create mode 100644 fs/notify/fanotify.c
create mode 100644 fs/notify/fanotify.h
create mode 100644 fs/notify/group.c
create mode 100644 fs/notify/notification.c
create mode 100644 include/linux/fanotify.h
diff --git a/fs/notify/Kconfig b/fs/notify/Kconfig
index 23415de..97cc832 100644
--- a/fs/notify/Kconfig
+++ b/fs/notify/Kconfig
@@ -36,3 +36,17 @@ config INOTIFY_USER
For more information, see <file:Documentation/filesystems/inotify.txt>
If unsure, say Y.
+
+config FANOTIFY
+ bool "Filesystem wide access notification"
+ select SECURITY
+ default y
+ ---help---
+ Say Y here to enable fanotify suport. fanotify is a system wide
+ file access notification interface. Events are read from from a
+ single open fd and in doing so a fd is created in the reading process
+ which points to the same data as the one on which the event occured.
+
+ For more information, see <file:Documentation/filesystems/fanotify.txt>
+
+ If unsure, say Y.
diff --git a/fs/notify/Makefile b/fs/notify/Makefile
index 882ecf9..c467c97 100644
--- a/fs/notify/Makefile
+++ b/fs/notify/Makefile
@@ -2,3 +2,5 @@ obj-$(CONFIG_INOTIFY) += inotify.o
obj-$(CONFIG_INOTIFY_USER) += inotify_user.o
obj-$(CONFIG_DNOTIFY) += dnotify.o
+
+obj-$(CONFIG_FANOTIFY) += fanotify.o notification.o group.o
diff --git a/fs/notify/fanotify.c b/fs/notify/fanotify.c
new file mode 100644
index 0000000..e2a338e
--- /dev/null
+++ b/fs/notify/fanotify.c
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2008 Red Hat, Inc., Eric Paris <eparis@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/namei.h>
+#include <linux/poll.h>
+#include <linux/rculist.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/security.h>
+#include <linux/uaccess.h>
+
+#include <linux/fanotify.h>
+#include "fanotify.h"
+
+struct dentry *fanotify_fs_root;
+
+void fanotify(struct file *file, unsigned int mask)
+{
+ struct fanotify_group *group;
+ struct fanotify_event *event = NULL;
+ struct task_struct *tsk = current;
+ struct inode *inode = file->f_path.dentry->d_inode;
+ int idx;
+
+ if (likely(list_empty(&fanotify_groups)))
+ return;
+
+ if (tsk->flags & PF_NOFACCESS)
+ return;
+
+ if (!S_ISREG(inode->i_mode))
+ return;
+
+ /*
+ * SRCU!! the groups list is very very much read only and the path is
+ * very hot (assuming something is using fanotify) Not blocking while
+ * walking this list is ugly. We could preallocate an event and an
+ * event holder for every group that event might need to be put on, but
+ * all that possibly wasted allocation is nuts. For all we know there
+ * are already fastpath entries, groups don't need this event, or all
+ * sorts of reasons to believe not every kernel action is going to get
+ * sent to userspace. Hopefully this won't get shit on too much,
+ * because going to a mutex here is really going to needlessly serialize
+ * read/write/open/close across the whole system....
+ */
+ idx = srcu_read_lock(&fanotify_grp_srcu_struct);
+ list_for_each_entry_rcu(group, &fanotify_groups, group_list) {
+ if (mask & group->mask) {
+ if (!event) {
+ event = create_event(file, mask);
+ /* shit, we OOM'd and now we can't tell, lets hope something else blows up */
+ if (!event)
+ break;
+ }
+ fanotify_add_event_to_notif(group, event);
+ }
+ }
+ srcu_read_unlock(&fanotify_grp_srcu_struct, idx);
+ /*
+ * create_event() take a reference so the event can't be cleaned up while
+ * we are still trying to add it to lists
+ */
+ if (event)
+ fanotify_put_event(event);
+}
+EXPORT_SYMBOL_GPL(fanotify);
+
+static __init int fanotify_init(void)
+{
+ int ret;
+
+ ret = fanotify_notification_init();
+ if (ret)
+ return ret;
+
+ return init_srcu_struct(&fanotify_grp_srcu_struct);
+}
+__initcall(fanotify_init);
diff --git a/fs/notify/fanotify.h b/fs/notify/fanotify.h
new file mode 100644
index 0000000..5b0253e
--- /dev/null
+++ b/fs/notify/fanotify.h
@@ -0,0 +1,64 @@
+#ifndef _LINUX_FANOTIFY_PRIVATE_H
+#define _LINUX_FANOTIFY_PRIVATE_H
+
+#include <linux/fanotify.h>
+
+
+#include <asm/atomic.h>
+#include <linux/dcache.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/path.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/wait.h>
+
+/*
+ * A single event can be queued in multiple group->notification_lists.
+ *
+ * each group->notification_list will point to an event_holer which in turns points
+ * to the actual event that needs to be sent to userspace.
+ *
+ * Seemed cheaper to create a refcnt'd event and a small holder for every group
+ * than create a different event for every group
+ *
+ */
+struct fanotify_event_holder {
+ struct fanotify_event *event;
+ struct list_head event_list;
+};
+
+/*
+ * all of the information about the original object we want to now send to
+ * a scanner. If you want to carry more info from the accessing task to the
+ * listener this structure is where you need to be adding fields.
+ */
+struct fanotify_event {
+ /*
+ * If we create an event we are also going to need to create a holder
+ * to link to a group. So embed one holder in the event. Means only
+ * one allocation for the common case where we only have one group
+ */
+ struct fanotify_event_holder holder;
+ spinlock_t holder_spinlock; /* protection for the associated event_holder */
+ struct path path; /* path from the original access */
+ unsigned int mask; /* the type of access */
+ atomic_t refcnt; /* how many groups still are using/need to send this event */
+};
+
+extern struct mutex fanotify_grp_mutex;
+extern struct srcu_struct fanotify_grp_srcu_struct;
+extern struct list_head fanotify_groups;
+
+extern int fanotify_check_notif_queue(struct fanotify_group *group);
+extern void fanotify_get_event(struct fanotify_event *event);
+extern void fanotify_put_event(struct fanotify_event *event);
+extern int fanotify_add_event_to_notif(struct fanotify_group *group, struct fanotify_event *event);
+extern struct fanotify_event *remove_event_from_group_notification(struct fanotify_group *group);
+extern void fanotify_notification_clear_group(struct fanotify_group *group);
+extern struct fanotify_event *create_event(struct file *file, unsigned int mask);
+extern struct fanotify_event_holder *alloc_event_holder(void);
+extern void fanotify_destroy_event_holder(struct fanotify_event_holder *holder);
+extern __init int fanotify_notification_init(void);
+extern __init int fanotify_notification_uninit(void);
+#endif /* _LINUX_FANOTIFY_PRIVATE_H */
diff --git a/fs/notify/group.c b/fs/notify/group.c
new file mode 100644
index 0000000..fc3d736
--- /dev/null
+++ b/fs/notify/group.c
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2008 Red Hat, Inc., Eric Paris <eparis@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/namei.h>
+#include <linux/poll.h>
+#include <linux/rculist.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/security.h>
+#include <linux/uaccess.h>
+
+#include <linux/fanotify.h>
+#include "fanotify.h"
+
+DEFINE_MUTEX(fanotify_grp_mutex);
+struct srcu_struct fanotify_grp_srcu_struct;
+LIST_HEAD(fanotify_groups);
+
+void fanotify_get_group(struct fanotify_group *group)
+{
+ atomic_inc(&group->refcnt);
+}
+
+struct fanotify_group *fanotify_find_group(unsigned int group_num, unsigned int mask)
+{
+ struct fanotify_group *group_iter;
+ struct fanotify_group *group = NULL;
+
+ mutex_lock(&fanotify_grp_mutex);
+ list_for_each_entry_rcu(group_iter, &fanotify_groups, group_list) {
+ if (group_iter->group_num == group_num) {
+ if (group_iter->mask == mask) {
+ fanotify_get_group(group_iter);
+ group = group_iter;
+ } else
+ group = ERR_PTR(-EEXIST);
+ goto out;
+ }
+ }
+
+ group = kmalloc(sizeof(struct fanotify_group), GFP_KERNEL);
+ if (!group) {
+ group = ERR_PTR(-ENOMEM);
+ goto out;
+ }
+
+ atomic_set(&group->refcnt, 1);
+
+ group->group_num = group_num;
+ group->mask = mask;
+
+ mutex_init(&group->notification_mutex);
+ INIT_LIST_HEAD(&group->notification_list);
+ init_waitqueue_head(&group->notification_waitq);
+
+ /* add it */
+ list_add_rcu(&group->group_list, &fanotify_groups);
+
+out:
+ mutex_unlock(&fanotify_grp_mutex);
+ return group;
+}
+
+void fanotify_kill_group(struct fanotify_group *group)
+{
+ /* clear the notification queue of all events */
+ fanotify_notification_clear_group(group);
+
+ kfree(group);
+}
+
+void fanotify_put_group(struct fanotify_group *group)
+{
+ mutex_lock(&fanotify_grp_mutex);
+ if (atomic_dec_and_test(&group->refcnt)) {
+
+ list_del_rcu(&group->group_list);
+ mutex_unlock(&fanotify_grp_mutex);
+
+ synchronize_srcu(&fanotify_grp_srcu_struct);
+
+ fanotify_kill_group(group);
+
+ return;
+ }
+ mutex_unlock(&fanotify_grp_mutex);
+
+ return;
+}
diff --git a/fs/notify/notification.c b/fs/notify/notification.c
new file mode 100644
index 0000000..3dd69ef
--- /dev/null
+++ b/fs/notify/notification.c
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2008 Red Hat, Inc., Eric Paris <eparis@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <asm/atomic.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/mount.h>
+#include <linux/mutex.h>
+#include <linux/path.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+
+#include <linux/fanotify.h>
+#include "fanotify.h"
+
+static struct kmem_cache *event_kmem_cache;
+static struct kmem_cache *event_holder_kmem_cache;
+
+int fanotify_check_notif_queue(struct fanotify_group *group)
+{
+ mutex_lock(&group->notification_mutex);
+ if (!list_empty(&group->notification_list))
+ return 1;
+ mutex_unlock(&group->notification_mutex);
+ return 0;
+}
+
+void fanotify_get_event(struct fanotify_event *event)
+{
+ atomic_inc(&event->refcnt);
+}
+
+void fanotify_put_event(struct fanotify_event *event)
+{
+ if (!event)
+ return;
+
+ if (atomic_dec_and_test(&event->refcnt)) {
+ path_put(&event->path);
+ event->path.dentry = NULL;
+ event->path.mnt = NULL;
+
+ event->mask = 0;
+ kmem_cache_free(event_kmem_cache, event);
+ }
+}
+
+struct fanotify_event_holder *alloc_event_holder(void)
+{
+ return kmem_cache_alloc(event_holder_kmem_cache, GFP_KERNEL);
+}
+
+void fanotify_destroy_event_holder(struct fanotify_event_holder *holder)
+{
+ kmem_cache_free(event_holder_kmem_cache, holder);
+}
+
+int fanotify_add_event_to_notif(struct fanotify_group *group, struct fanotify_event *event)
+{
+ struct fanotify_event_holder *holder;
+
+ /*
+ * holder locking!!!
+ *
+ * only this task is going to be adding events to lists. fanotify
+ * listeners may be removing events from groups and freeing holders
+ * but only we will be adding. So locking is pretty easy. We don't
+ * need to lock when we check the list. We don't really care what
+ * order we see things from the freeing tasks. If we see the holder
+ * list is empty we know after we take the spinlock that holder will
+ * be safe to use.
+ *
+ * When only looking at the notification list this is all useless since
+ * everything meaningful is already being protected by the
+ * notification_mutex but since access lists also use event_holders we
+ * need this.
+ */
+ if (list_empty(&event->holder.event_list))
+ holder = (struct fanotify_event_holder *)event;
+ else
+ holder = alloc_event_holder();
+ if (!holder)
+ return -ENOMEM;
+
+ fanotify_get_event(event);
+
+
+ mutex_lock(&group->notification_mutex);
+ spin_lock(&event->holder_spinlock);
+ holder->event = event;
+ list_add_tail(&holder->event_list, &group->notification_list);
+ spin_unlock(&event->holder_spinlock);
+ mutex_unlock(&group->notification_mutex);
+
+
+ wake_up(&group->notification_waitq);
+
+ return 0;
+}
+
+/*
+ * must be called with group->notification_mutex held and must know event is present.
+ * it is the responsibility of the caller to call put_event() on the returned
+ * structure
+ */
+struct fanotify_event *remove_event_from_group_notification(struct fanotify_group *group)
+{
+ struct fanotify_event *event;
+ struct fanotify_event_holder *holder;
+
+ holder = list_first_entry(&group->notification_list, struct fanotify_event_holder, event_list);
+
+ event = holder->event;
+
+ spin_lock(&event->holder_spinlock);
+ holder->event = NULL;
+ /*
+ * be sure you are done with holder, since after this point it
+ * could be reused in another group
+ */
+ list_del_init(&holder->event_list);
+ spin_unlock(&event->holder_spinlock);
+
+ /* event == holder means we are referenced through the in event holder */
+ if (event != (struct fanotify_event *)holder)
+ fanotify_destroy_event_holder(holder);
+
+ return event;
+}
+
+void fanotify_notification_clear_group(struct fanotify_group *group)
+{
+ struct fanotify_event *event;
+
+ while (fanotify_check_notif_queue(group)) {
+ event = remove_event_from_group_notification(group);
+ fanotify_put_event(event);
+ /* fanotify_check_notif_queue() took this lock */
+ mutex_unlock(&group->notification_mutex);
+ }
+}
+
+struct fanotify_event *create_event(struct file *file, unsigned int mask)
+{
+ struct fanotify_event *event;
+
+ event = kmem_cache_alloc(event_kmem_cache, GFP_KERNEL);
+ if (!event)
+ return NULL;
+
+ event->holder.event = NULL;
+ INIT_LIST_HEAD(&event->holder.event_list);
+ atomic_set(&event->refcnt, 1);
+
+ spin_lock_init(&event->holder_spinlock);
+
+ event->path.dentry = file->f_path.dentry;
+ event->path.mnt = file->f_path.mnt;
+ path_get(&event->path);
+
+ event->mask = mask;
+
+ WARN_ON(!event->path.dentry);
+ WARN_ON(!event->path.mnt);
+
+ return event;
+}
+
+int fanotify_create_event_fd(struct fanotify_group *group, struct fanotify_event_metadata *data, int nonblock)
+{
+ int rc;
+ int client_fd;
+ struct dentry *dentry;
+ struct vfsmount *mnt;
+ struct file *new_file;
+
+ struct fanotify_event *event;
+
+ memset(data, 0, sizeof(struct fanotify_event_metadata));
+
+ /* get a notification off the queue blocking if we can/should */
+ if (fanotify_check_notif_queue(group)) {
+ event = remove_event_from_group_notification(group);
+ mutex_unlock(&group->notification_mutex);
+ } else {
+ if (nonblock)
+ return -EAGAIN;
+ rc = wait_event_interruptible(group->notification_waitq, fanotify_check_notif_queue(group));
+ if (!rc) {
+ event = remove_event_from_group_notification(group);
+ mutex_unlock(&group->notification_mutex);
+ } else {
+ return rc;
+ }
+ }
+
+ BUG_ON(!event);
+
+ client_fd = get_unused_fd();
+ if (client_fd < 0)
+ return client_fd;
+
+ /*
+ * 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);
+ new_file = dentry_open(dentry, mnt, O_RDONLY);
+ if (IS_ERR(new_file)) {
+ /*
+ * we still send an event even if we can't open the file. this
+ * can happen for say tasks are gone and we try to open their
+ * /proc entries 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);
+ }
+
+ data->fd = client_fd;
+ data->mask = event->mask;
+
+ fanotify_put_event(event);
+
+ return 0;
+}
+
+__init int fanotify_notification_uninit(void)
+{
+ kmem_cache_destroy(event_kmem_cache);
+ event_kmem_cache = NULL;
+ kmem_cache_destroy(event_holder_kmem_cache);
+ event_holder_kmem_cache = NULL;
+
+ return 0;
+}
+
+__init int fanotify_notification_init(void)
+{
+ event_kmem_cache = kmem_cache_create("fanotify_event", sizeof(struct fanotify_event), 0, SLAB_PANIC, NULL);
+ event_holder_kmem_cache = kmem_cache_create("fanotify_event_holder", sizeof(struct fanotify_event_holder), 0, SLAB_PANIC, NULL);
+
+ return 0;
+}
diff --git a/include/linux/Kbuild b/include/linux/Kbuild
index 282a504..b6372b4 100644
--- a/include/linux/Kbuild
+++ b/include/linux/Kbuild
@@ -244,6 +244,7 @@ unifdef-y += inet_diag.h
unifdef-y += in.h
unifdef-y += in6.h
unifdef-y += inotify.h
+unifdef-y += fanotify.h
unifdef-y += input.h
unifdef-y += ip.h
unifdef-y += ipc.h
diff --git a/include/linux/fanotify.h b/include/linux/fanotify.h
new file mode 100644
index 0000000..7f1179e
--- /dev/null
+++ b/include/linux/fanotify.h
@@ -0,0 +1,95 @@
+/*
+ * Filesystem access notification for Linux
+ *
+ * Copyright (C) 2008 Red Hat, Inc., Eric Paris <eparis@redhat.com>
+ *
+ * Basis of file stolen from inotify.h
+ */
+
+#ifndef _LINUX_FANOTIFY_H
+#define _LINUX_FANOTIFY_H
+
+/* the following events that user-space can register for */
+#define FAN_ACCESS 0x00000001 /* File was accessed */
+#define FAN_MODIFY 0x00000002 /* File was modified */
+#define FAN_CLOSE_NOWRITE 0x00000004 /* Unwrittable file closed */
+#define FAN_CLOSE_WRITE 0x00000008 /* Writtable file closed */
+#define FAN_OPEN 0x00000010 /* File was opened */
+
+/* FIXME currently Q's have no limit.... */
+#define FAN_Q_OVERFLOW 0x80000000 /* Event queued overflowed */
+
+/* helper events */
+#define FAN_CLOSE (FAN_CLOSE_WRITE | FAN_CLOSE_NOWRITE) /* close */
+
+/*
+ * All of the events - we build the list by hand so that we can add flags in
+ * the future and not break backward compatibility. Apps will get only the
+ * events that they originally wanted. Be sure to add new events here!
+ */
+#define FAN_ALL_EVENTS (FAN_ACCESS |\
+ FAN_MODIFY |\
+ FAN_CLOSE |\
+ FAN_OPEN)
+
+#ifdef __KERNEL__
+#include <linux/types.h>
+#else
+#include <stdint.h>
+#include <sys/types.h>
+#endif
+
+struct fanotify_event_metadata {
+ int32_t fd;
+ uint32_t mask;
+};
+
+#ifdef __KERNEL__
+
+#include <linux/fs.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/wait.h>
+
+#include <asm/atomic.h>
+
+struct fanotify_group {
+ struct list_head group_list; /* list of all groups on the system */
+ unsigned int group_num; /* the 'name' of the event */
+ unsigned int mask; /* mask of events this group cares about */
+ atomic_t refcnt; /* num of processes with a special file open */
+
+ /* needed to send notification to userspace */
+ struct mutex notification_mutex;/* protect the notification_list */
+ struct list_head notification_list; /* list of event_holder this group needs to send to userspace */
+ wait_queue_head_t notification_waitq; /* read() on the notification file blocks on this waitq */
+};
+
+#ifdef CONFIG_FANOTIFY
+
+extern void fanotify(struct file *file, unsigned int mask);
+
+extern void fanotify_get_group(struct fanotify_group *group);
+extern struct fanotify_group *fanotify_find_group(unsigned int group_num, unsigned int mask);
+extern void fanotify_put_group(struct fanotify_group *group);
+#else
+
+static inline void fanotify(struct file *file, unsigned int mask)
+{}
+
+static inline void fanotify_get_group(struct fanotify_group *group)
+{}
+
+static inline struct fanotify_group *fanotify_find_group(unsigned int group_num, unsigned int mask)
+{
+ return NULL;
+}
+
+static inline extern void fanotify_put_group(struct fanotify_group *group)
+{}
+
+#endif /* CONFIG_FANOTIFY */
+
+#endif /* __KERNEL __ */
+
+#endif /* _LINUX_FANOTIFY_H */
diff --git a/include/linux/fsnotify.h b/include/linux/fsnotify.h
index dd76948..50d8d8b 100644
--- a/include/linux/fsnotify.h
+++ b/include/linux/fsnotify.h
@@ -13,6 +13,7 @@
#include <linux/dnotify.h>
#include <linux/inotify.h>
+#include <linux/fanotify.h>
#include <linux/audit.h>
/*
@@ -148,6 +149,7 @@ static inline void fsnotify_access(struct file *file)
dnotify_parent(dentry, DN_ACCESS);
inotify_dentry_parent_queue_event(dentry, mask, 0, dentry->d_name.name);
inotify_inode_queue_event(inode, mask, 0, NULL, NULL);
+ fanotify(file, FAN_ACCESS);
}
/*
@@ -165,6 +167,7 @@ static inline void fsnotify_modify(struct file *file)
dnotify_parent(dentry, DN_MODIFY);
inotify_dentry_parent_queue_event(dentry, mask, 0, dentry->d_name.name);
inotify_inode_queue_event(inode, mask, 0, NULL, NULL);
+ fanotify(file, FAN_MODIFY);
}
/*
@@ -181,6 +184,7 @@ static inline void fsnotify_open(struct file *file)
inotify_dentry_parent_queue_event(dentry, mask, 0, dentry->d_name.name);
inotify_inode_queue_event(inode, mask, 0, NULL, NULL);
+ fanotify(file, FAN_OPEN);
}
/*
@@ -192,13 +196,15 @@ static inline void fsnotify_close(struct file *file)
struct inode *inode = dentry->d_inode;
const char *name = dentry->d_name.name;
mode_t mode = file->f_mode;
- u32 mask = (mode & FMODE_WRITE) ? IN_CLOSE_WRITE : IN_CLOSE_NOWRITE;
+ u32 in_mask = (mode & FMODE_WRITE) ? IN_CLOSE_WRITE : IN_CLOSE_NOWRITE;
+ u32 fan_mask = (mode & FMODE_WRITE) ? FAN_CLOSE_WRITE : FAN_CLOSE_NOWRITE;
if (S_ISDIR(inode->i_mode))
- mask |= IN_ISDIR;
+ in_mask |= IN_ISDIR;
- inotify_dentry_parent_queue_event(dentry, mask, 0, name);
- inotify_inode_queue_event(inode, mask, 0, NULL, NULL);
+ inotify_dentry_parent_queue_event(dentry, in_mask, 0, name);
+ inotify_inode_queue_event(inode, in_mask, 0, NULL, NULL);
+ fanotify(file, fan_mask);
}
/*
diff --git a/include/linux/sched.h b/include/linux/sched.h
index c226c7b..a20cf99 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -1491,6 +1491,7 @@ extern cputime_t task_gtime(struct task_struct *p);
#define PF_EXITING 0x00000004 /* getting shut down */
#define PF_EXITPIDONE 0x00000008 /* pi exit done on shut down */
#define PF_VCPU 0x00000010 /* I'm a virtual CPU */
+#define PF_NOFACCESS 0x00000020 /* exclude from all faccesses */
#define PF_FORKNOEXEC 0x00000040 /* forked but didn't exec */
#define PF_SUPERPRIV 0x00000100 /* used super-user privileges */
#define PF_DUMPCORE 0x00000200 /* dumped core */
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH -v2 04/16] fsnotify: sys_execve and sys_uselib do not call into fsnotify
2008-10-14 20:51 [PATCH -v2 00/16] fanotify: novel all file access notification and permissions system Eric Paris
` (2 preceding siblings ...)
2008-10-14 20:51 ` [PATCH -v2 03/16] fanotify: fscking all notify, system wide file access notification Eric Paris
@ 2008-10-14 20:51 ` Eric Paris
2008-10-14 20:51 ` [PATCH -v2 05/16] fanotify: make use of the new fsnotify_open_exec calls Eric Paris
` (11 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Eric Paris @ 2008-10-14 20:51 UTC (permalink / raw)
To: linux-kernel, malware-list; +Cc: viro, alan, arjan, greg, tytso
sys_execve and sys_uselib do not call into fsnotify so inotify, dnotify,
and importantly to me fanotify do not see opens on things which are going
to be exectued. Create a generic fsnotify hook for these paths.
Signed-off-by: Eric Paris <eparis@redhat.com>
---
fs/exec.c | 5 +++++
include/linux/fsnotify.h | 7 +++++++
2 files changed, 12 insertions(+), 0 deletions(-)
diff --git a/fs/exec.c b/fs/exec.c
index cecee50..3612805 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -50,6 +50,7 @@
#include <linux/cn_proc.h>
#include <linux/audit.h>
#include <linux/tracehook.h>
+#include <linux/fsnotify.h>
#include <asm/uaccess.h>
#include <asm/mmu_context.h>
@@ -138,6 +139,8 @@ asmlinkage long sys_uselib(const char __user * library)
if (IS_ERR(file))
goto out;
+ fsnotify_open_exec(file);
+
error = -ENOEXEC;
if(file->f_op) {
struct linux_binfmt * fmt;
@@ -690,6 +693,8 @@ struct file *open_exec(const char *name)
if (IS_ERR(file))
return file;
+ fsnotify_open_exec(file);
+
err = deny_write_access(file);
if (err) {
fput(file);
diff --git a/include/linux/fsnotify.h b/include/linux/fsnotify.h
index 50d8d8b..6fc4160 100644
--- a/include/linux/fsnotify.h
+++ b/include/linux/fsnotify.h
@@ -171,6 +171,13 @@ static inline void fsnotify_modify(struct file *file)
}
/*
+ * fsnotify_open_exec - file was opened by execve of uselib
+ */
+static inline void fsnotify_open_exec(struct file *file)
+{
+}
+
+/*
* fsnotify_open - file was opened
*/
static inline void fsnotify_open(struct file *file)
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH -v2 05/16] fanotify: make use of the new fsnotify_open_exec calls
2008-10-14 20:51 [PATCH -v2 00/16] fanotify: novel all file access notification and permissions system Eric Paris
` (3 preceding siblings ...)
2008-10-14 20:51 ` [PATCH -v2 04/16] fsnotify: sys_execve and sys_uselib do not call into fsnotify Eric Paris
@ 2008-10-14 20:51 ` Eric Paris
2008-10-14 20:52 ` [PATCH -v2 06/16] fanotify: add a userspace interface for fanotify notifications Eric Paris
` (10 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Eric Paris @ 2008-10-14 20:51 UTC (permalink / raw)
To: linux-kernel, malware-list; +Cc: viro, alan, arjan, greg, tytso
This function sends fanotify events for opens which we know are being used
for exec. These are basically just systecalls to sys_execve and sys_uselib
Signed-off-by: Eric Paris <eparis@redhat.com>
---
include/linux/fanotify.h | 4 +++-
include/linux/fsnotify.h | 3 ++-
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/include/linux/fanotify.h b/include/linux/fanotify.h
index 7f1179e..c991bd9 100644
--- a/include/linux/fanotify.h
+++ b/include/linux/fanotify.h
@@ -14,13 +14,15 @@
#define FAN_MODIFY 0x00000002 /* File was modified */
#define FAN_CLOSE_NOWRITE 0x00000004 /* Unwrittable file closed */
#define FAN_CLOSE_WRITE 0x00000008 /* Writtable file closed */
-#define FAN_OPEN 0x00000010 /* File was opened */
+#define FAN_OPEN_NOEXEC 0x00000010 /* File was opened */
+#define FAN_OPEN_EXEC 0x00000020 /* File was opened with the intention of being exec'ed */
/* FIXME currently Q's have no limit.... */
#define FAN_Q_OVERFLOW 0x80000000 /* Event queued overflowed */
/* helper events */
#define FAN_CLOSE (FAN_CLOSE_WRITE | FAN_CLOSE_NOWRITE) /* close */
+#define FAN_OPEN (FAN_OPEN_NOEXEC | FAN_OPEN_EXEC) /* open */
/*
* All of the events - we build the list by hand so that we can add flags in
diff --git a/include/linux/fsnotify.h b/include/linux/fsnotify.h
index 6fc4160..ccee5ea 100644
--- a/include/linux/fsnotify.h
+++ b/include/linux/fsnotify.h
@@ -175,6 +175,7 @@ static inline void fsnotify_modify(struct file *file)
*/
static inline void fsnotify_open_exec(struct file *file)
{
+ fanotify(file, FAN_OPEN_EXEC);
}
/*
@@ -191,7 +192,7 @@ static inline void fsnotify_open(struct file *file)
inotify_dentry_parent_queue_event(dentry, mask, 0, dentry->d_name.name);
inotify_inode_queue_event(inode, mask, 0, NULL, NULL);
- fanotify(file, FAN_OPEN);
+ fanotify(file, FAN_OPEN_NOEXEC);
}
/*
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH -v2 06/16] fanotify: add a userspace interface for fanotify notifications
2008-10-14 20:51 [PATCH -v2 00/16] fanotify: novel all file access notification and permissions system Eric Paris
` (4 preceding siblings ...)
2008-10-14 20:51 ` [PATCH -v2 05/16] fanotify: make use of the new fsnotify_open_exec calls Eric Paris
@ 2008-10-14 20:52 ` Eric Paris
2008-10-14 20:52 ` [PATCH -v2 07/16] fanotify: fastpath to ignore certain in core inodes Eric Paris
` (9 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Eric Paris @ 2008-10-14 20:52 UTC (permalink / raw)
To: linux-kernel, malware-list; +Cc: viro, alan, arjan, greg, tytso
notifications from fanotify need a userspace interface. We are going to
try to do it with sockets. Scary....
Signed-off-by: Eric Paris <eparis@redhat.com>
---
include/linux/fanotify.h | 18 +++
include/linux/socket.h | 5 +
net/Makefile | 1
net/core/sock.c | 6 +
net/fanotify/Makefile | 5 +
net/fanotify/af_fanotify.c | 232 ++++++++++++++++++++++++++++++++++++++++++++
net/fanotify/af_fanotify.h | 20 ++++
7 files changed, 283 insertions(+), 4 deletions(-)
create mode 100644 net/fanotify/Makefile
create mode 100644 net/fanotify/af_fanotify.c
create mode 100644 net/fanotify/af_fanotify.h
diff --git a/include/linux/fanotify.h b/include/linux/fanotify.h
index c991bd9..5add54f 100644
--- a/include/linux/fanotify.h
+++ b/include/linux/fanotify.h
@@ -41,11 +41,22 @@
#include <sys/types.h>
#endif
+struct fanotify_addr {
+ uint32_t group_num;
+ uint32_t mask;
+ uint32_t timeout;
+ uint32_t unused[9];
+};
+
+/* struct used for FANOTIFY_GET_EVENT */
struct fanotify_event_metadata {
int32_t fd;
uint32_t mask;
};
+/* fanotify getsockopt optvals */
+#define FANOTIFY_GET_EVENT 1
+
#ifdef __KERNEL__
#include <linux/fs.h>
@@ -74,6 +85,8 @@ extern void fanotify(struct file *file, unsigned int mask);
extern void fanotify_get_group(struct fanotify_group *group);
extern struct fanotify_group *fanotify_find_group(unsigned int group_num, unsigned int mask);
extern void fanotify_put_group(struct fanotify_group *group);
+
+extern int fanotify_create_event_fd(struct fanotify_group *group, struct fanotify_event_metadata *data, int nonblock);
#else
static inline void fanotify(struct file *file, unsigned int mask)
@@ -90,6 +103,11 @@ static inline struct fanotify_group *fanotify_find_group(unsigned int group_num,
static inline extern void fanotify_put_group(struct fanotify_group *group)
{}
+static inline int fanotify_create_event_fd(struct fanotify_group *group, struct fanotify_event_metadata *data, int nonblock)
+{
+ memset(data, 0, sizeof(struct fanotify_event_metadata));
+ return 0;
+}
#endif /* CONFIG_FANOTIFY */
#endif /* __KERNEL __ */
diff --git a/include/linux/socket.h b/include/linux/socket.h
index 20fc4bb..4fe7b83 100644
--- a/include/linux/socket.h
+++ b/include/linux/socket.h
@@ -191,7 +191,8 @@ struct ucred {
#define AF_RXRPC 33 /* RxRPC sockets */
#define AF_ISDN 34 /* mISDN sockets */
#define AF_PHONET 35 /* Phonet sockets */
-#define AF_MAX 36 /* For now.. */
+#define AF_FANOTIFY 36 /* fscking all access sockets */
+#define AF_MAX 37 /* For now.. */
/* Protocol families, same as address families. */
#define PF_UNSPEC AF_UNSPEC
@@ -229,6 +230,7 @@ struct ucred {
#define PF_RXRPC AF_RXRPC
#define PF_ISDN AF_ISDN
#define PF_PHONET AF_PHONET
+#define PF_FANOTIFY AF_FANOTIFY
#define PF_MAX AF_MAX
/* Maximum queue length specifiable by listen. */
@@ -298,6 +300,7 @@ struct ucred {
#define SOL_PPPOL2TP 273
#define SOL_BLUETOOTH 274
#define SOL_PNPIPE 275
+#define SOL_FANOTIFY 276
/* IPX options */
#define IPX_TYPE 1
diff --git a/net/Makefile b/net/Makefile
index 27d1f10..5b98ba4 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -44,6 +44,7 @@ obj-$(CONFIG_ATM) += atm/
obj-$(CONFIG_DECNET) += decnet/
obj-$(CONFIG_ECONET) += econet/
obj-$(CONFIG_PHONET) += phonet/
+obj-$(CONFIG_FANOTIFY) += fanotify/
ifneq ($(CONFIG_VLAN_8021Q),)
obj-y += 8021q/
endif
diff --git a/net/core/sock.c b/net/core/sock.c
index 5e2a313..c7b0cee 100644
--- a/net/core/sock.c
+++ b/net/core/sock.c
@@ -155,7 +155,7 @@ static const char *af_family_key_strings[AF_MAX+1] = {
"sk_lock-27" , "sk_lock-28" , "sk_lock-AF_CAN" ,
"sk_lock-AF_TIPC" , "sk_lock-AF_BLUETOOTH", "sk_lock-IUCV" ,
"sk_lock-AF_RXRPC" , "sk_lock-AF_ISDN" , "sk_lock-AF_PHONET" ,
- "sk_lock-AF_MAX"
+ "sk_lock-AF_FANOTIFY", "sk_lock-AF_MAX"
};
static const char *af_family_slock_key_strings[AF_MAX+1] = {
"slock-AF_UNSPEC", "slock-AF_UNIX" , "slock-AF_INET" ,
@@ -170,7 +170,7 @@ static const char *af_family_slock_key_strings[AF_MAX+1] = {
"slock-27" , "slock-28" , "slock-AF_CAN" ,
"slock-AF_TIPC" , "slock-AF_BLUETOOTH", "slock-AF_IUCV" ,
"slock-AF_RXRPC" , "slock-AF_ISDN" , "slock-AF_PHONET" ,
- "slock-AF_MAX"
+ "slock=AF_FANOTIFY", "slock-AF_MAX"
};
static const char *af_family_clock_key_strings[AF_MAX+1] = {
"clock-AF_UNSPEC", "clock-AF_UNIX" , "clock-AF_INET" ,
@@ -185,7 +185,7 @@ static const char *af_family_clock_key_strings[AF_MAX+1] = {
"clock-27" , "clock-28" , "clock-AF_CAN" ,
"clock-AF_TIPC" , "clock-AF_BLUETOOTH", "clock-AF_IUCV" ,
"clock-AF_RXRPC" , "clock-AF_ISDN" , "clock-AF_PHONET" ,
- "clock-AF_MAX"
+ "clock-AF_FANOTIFY", "clock-AF_MAX"
};
#endif
diff --git a/net/fanotify/Makefile b/net/fanotify/Makefile
new file mode 100644
index 0000000..348cf5e
--- /dev/null
+++ b/net/fanotify/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for the fanotify AF.
+#
+
+obj-$(CONFIG_FANOTIFY) += af_fanotify.o
diff --git a/net/fanotify/af_fanotify.c b/net/fanotify/af_fanotify.c
new file mode 100644
index 0000000..eb2d023
--- /dev/null
+++ b/net/fanotify/af_fanotify.c
@@ -0,0 +1,232 @@
+#include <linux/errno.h>
+#include <linux/fanotify.h>
+#include <linux/fdtable.h>
+#include <linux/file.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/net.h>
+#include <linux/poll.h>
+#include <linux/skbuff.h>
+#include <linux/socket.h>
+#include <linux/types.h>
+
+#include <net/net_namespace.h>
+#include <net/sock.h>
+
+#include "af_fanotify.h"
+
+static const struct proto_ops fanotify_proto_ops;
+
+static struct proto fanotify_proto = {
+ .name = "FANOTIFY",
+ .owner = THIS_MODULE,
+ .obj_size = sizeof(struct fanotify_sock),
+};
+
+static int fan_sock_create(struct net *net, struct socket *sock, int protocol)
+{
+ struct sock *sk;
+ struct fanotify_sock *fan_sock;
+
+ /* FIXME maybe a new LSM hook? */
+ if (!capable(CAP_NET_RAW))
+ return -EPERM;
+
+ if (protocol != 0)
+ return -ESOCKTNOSUPPORT;
+
+ if (sock->type != SOCK_RAW)
+ return -ESOCKTNOSUPPORT;
+
+ sock->state = SS_UNCONNECTED;
+
+ sk = sk_alloc(net, PF_FANOTIFY, GFP_KERNEL, &fanotify_proto);
+ if (sk == NULL)
+ return -ENOBUFS;
+
+ sock->ops = &fanotify_proto_ops;
+
+ sock_init_data(sock, sk);
+
+ sk->sk_family = PF_FANOTIFY;
+ //sk->sk_destruct = packet_sock_destruct;
+ sk_refcnt_debug_inc(sk);
+
+ fan_sock = fan_sk(sk);
+ fan_sock->group = NULL;
+
+ return 0;
+}
+
+static int fan_release(struct socket *sock)
+{
+ struct sock *sk;
+ struct fanotify_sock *fan_sock;
+
+ struct files_struct *files;
+ struct socket *testsock;
+ struct fdtable *fdt;
+ long j = -1;
+ unsigned long set, i;
+ int found = 0, err;
+
+ sk = sock->sk;
+ if (!sk)
+ return 0;
+
+ fan_sock = fan_sk(sk);
+
+ if (sock->state == SS_CONNECTED) {
+ sock->state = SS_UNCONNECTED;
+ fanotify_put_group(fan_sock->group);
+ }
+
+ fan_sock->group = NULL;
+
+ sock_orphan(sk);
+ sock->sk = NULL;
+
+ /* Purge queues */
+
+ sk_refcnt_debug_release(sk);
+
+ sock_put(sk);
+
+ /*
+ * we do all of this shit to figure out if this process is closing the last
+ * fanotify socket. If this is the last one being closed we clear the
+ * exclusion flag. If this is not the last close we should remain excluded.
+ *
+ * the basic idea behind running all of the open fd's was stolen from
+ * fs/exec.c:flush_old_files.
+ */
+ files = current->files;
+ if (files)
+ spin_lock(&files->file_lock);
+ for (; !found && files ;) {
+ j++;
+ i = j * __NFDBITS;
+ fdt = files_fdtable(files);
+ if (i >= fdt->max_fds)
+ break;
+ set = fdt->open_fds->fds_bits[j];
+ if (!set)
+ continue;
+ spin_unlock(&files->file_lock);
+ for ( ; set && !found ; i++, set >>= 1) {
+ if (set & 1) {
+ testsock = sockfd_lookup(i, &err);
+ if (!testsock)
+ continue;
+ if ((testsock != sock) && (testsock->ops == &fanotify_proto_ops))
+ found = 1;
+ sockfd_put(testsock);
+ }
+ }
+ spin_lock(&files->file_lock);
+ }
+ if (files)
+ spin_unlock(&files->file_lock);
+
+ if (!found)
+ current->flags &= ~PF_NOFACCESS;
+
+ return 0;
+}
+
+static int fan_bind(struct socket *sock, struct sockaddr *addr, int addr_len)
+{
+ struct fanotify_addr *fan_addr = (struct fanotify_addr *)addr;
+ struct fanotify_sock *fan_sock;
+
+ if (addr_len != sizeof(struct fanotify_addr))
+ return -EINVAL;
+
+ fan_sock = fan_sk(sock->sk);
+ fan_sock->group = fanotify_find_group(fan_addr->group_num, fan_addr->mask);
+
+ if (IS_ERR(fan_sock->group))
+ return PTR_ERR(fan_sock->group);
+
+ sock->state = SS_CONNECTED;
+ current->flags |= PF_NOFACCESS;
+
+ return 0;
+}
+
+static unsigned int fan_poll(struct file * file, struct socket *sock, poll_table *pt)
+{
+ return 0;
+}
+
+static int fan_getsockopt(struct socket *sock, int level, int optname, char __user *optval, int __user *optlen)
+{
+ struct fanotify_sock *fan_sock;
+ struct fanotify_group *group;
+ struct fanotify_event_metadata metadata;
+ int ret = 0;
+
+ if (sock->state != SS_CONNECTED)
+ return -EBADF;
+
+ fan_sock = fan_sk(sock->sk);
+ group = fan_sock->group;
+
+ switch (optname) {
+ case FANOTIFY_GET_EVENT:
+ if (*optlen < sizeof(struct fanotify_event_metadata))
+ return -ENOMEM;
+ else
+ *optlen = sizeof(struct fanotify_event_metadata);
+ ret = fanotify_create_event_fd(group, &metadata, !!(sock->file->f_flags & O_NONBLOCK));
+ if (ret)
+ break;
+
+ ret = copy_to_user(optval, &metadata, sizeof(struct fanotify_event_metadata));
+ break;
+ default:
+ return -ENOPROTOOPT;
+ };
+
+ return ret;
+}
+
+static const struct net_proto_family fanotify_family_ops = {
+ .family = PF_FANOTIFY,
+ .create = fan_sock_create,
+ .owner = THIS_MODULE,
+};
+
+static const struct proto_ops fanotify_proto_ops = {
+ .family = PF_FANOTIFY,
+ .owner = THIS_MODULE,
+ .release = fan_release,
+ .bind = fan_bind,
+ .connect = sock_no_connect,
+ .socketpair = sock_no_socketpair,
+ .accept = sock_no_accept,
+ .getname = sock_no_getname,
+ .poll = fan_poll,
+ .ioctl = sock_no_ioctl,
+ .listen = sock_no_listen,
+ .shutdown = sock_no_shutdown,
+ .setsockopt = sock_no_setsockopt,
+ .getsockopt = fan_getsockopt,
+ .sendmsg = sock_no_sendmsg,
+ .recvmsg = sock_no_recvmsg,
+ .mmap = sock_no_mmap,
+ .sendpage = sock_no_sendpage,
+};
+
+static int __init fanotify_socket_register(void)
+{
+ int rc = proto_register(&fanotify_proto, 0);
+
+ if (rc != 0)
+ goto out;
+
+ sock_register(&fanotify_family_ops);
+out:
+ return rc;
+}
+__initcall(fanotify_socket_register);
diff --git a/net/fanotify/af_fanotify.h b/net/fanotify/af_fanotify.h
new file mode 100644
index 0000000..a987d7b
--- /dev/null
+++ b/net/fanotify/af_fanotify.h
@@ -0,0 +1,20 @@
+#ifndef _LINUX_AF_FANOTIFY_H
+#define _LINUX_AF_FANOTIFY_H
+
+#ifdef __KERNEL__
+
+#include <linux/fanotify.h>
+#include <net/sock.h>
+
+struct fanotify_sock {
+ struct sock sock;
+ struct fanotify_group *group;
+};
+
+static inline struct fanotify_sock *fan_sk(struct sock *sock)
+{
+ return (struct fanotify_sock *)sock;
+}
+
+#endif /* __KERNEL__ */
+#endif /* _LINUX_AF_NET_H */
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH -v2 07/16] fanotify: fastpath to ignore certain in core inodes
2008-10-14 20:51 [PATCH -v2 00/16] fanotify: novel all file access notification and permissions system Eric Paris
` (5 preceding siblings ...)
2008-10-14 20:52 ` [PATCH -v2 06/16] fanotify: add a userspace interface for fanotify notifications Eric Paris
@ 2008-10-14 20:52 ` Eric Paris
2008-10-14 20:52 ` [PATCH -v2 08/16] fanotify: add a userspace interface for fastpaths Eric Paris
` (8 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Eric Paris @ 2008-10-14 20:52 UTC (permalink / raw)
To: linux-kernel, malware-list; +Cc: viro, alan, arjan, greg, tytso
Allows a process to ignore certain events for the given in core inodes.
This does not preclude the need to keep a userspace or xattr based
cache if a fanotifier listener wants something with a different lifetime
than in core inode.
Signed-off-by: Eric Paris <eparis@redhat.com>
---
fs/inode.c | 6 +
fs/notify/Makefile | 2
fs/notify/fanotify.c | 19 ++++
fs/notify/fanotify.h | 34 +++++++
fs/notify/fastpath.c | 230 ++++++++++++++++++++++++++++++++++++++++++++++
fs/notify/group.c | 3 +
include/linux/fanotify.h | 17 +++
include/linux/fs.h | 5 +
include/linux/fsnotify.h | 8 ++
9 files changed, 323 insertions(+), 1 deletions(-)
create mode 100644 fs/notify/fastpath.c
diff --git a/fs/inode.c b/fs/inode.c
index 0487ddb..cc802ae 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -21,6 +21,7 @@
#include <linux/cdev.h>
#include <linux/bootmem.h>
#include <linux/inotify.h>
+#include <linux/fsnotify.h>
#include <linux/mount.h>
/*
@@ -183,6 +184,10 @@ static struct inode *alloc_inode(struct super_block *sb)
}
inode->i_private = NULL;
inode->i_mapping = mapping;
+#ifdef CONFIG_FANOTIFY
+ INIT_LIST_HEAD(&inode->fastpath_entries);
+ rwlock_init(&inode->fastpath_rwlock);
+#endif
}
return inode;
}
@@ -191,6 +196,7 @@ void destroy_inode(struct inode *inode)
{
BUG_ON(inode_has_buffers(inode));
security_inode_free(inode);
+ fsnotify_inode_delete(inode);
if (inode->i_sb->s_op->destroy_inode)
inode->i_sb->s_op->destroy_inode(inode);
else
diff --git a/fs/notify/Makefile b/fs/notify/Makefile
index c467c97..90cb910 100644
--- a/fs/notify/Makefile
+++ b/fs/notify/Makefile
@@ -3,4 +3,4 @@ obj-$(CONFIG_INOTIFY_USER) += inotify_user.o
obj-$(CONFIG_DNOTIFY) += dnotify.o
-obj-$(CONFIG_FANOTIFY) += fanotify.o notification.o group.o
+obj-$(CONFIG_FANOTIFY) += fanotify.o notification.o group.o fastpath.o
diff --git a/fs/notify/fanotify.c b/fs/notify/fanotify.c
index e2a338e..aa12e91 100644
--- a/fs/notify/fanotify.c
+++ b/fs/notify/fanotify.c
@@ -35,6 +35,15 @@
struct dentry *fanotify_fs_root;
+void fanotify_inode_delete(struct inode *inode)
+{
+ if (likely(list_empty(&fanotify_groups)))
+ return;
+
+ fanotify_fastpath_clear(inode);
+}
+EXPORT_SYMBOL_GPL(fanotify_inode_delete);
+
void fanotify(struct file *file, unsigned int mask)
{
struct fanotify_group *group;
@@ -52,6 +61,8 @@ void fanotify(struct file *file, unsigned int mask)
if (!S_ISREG(inode->i_mode))
return;
+ if (mask & FAN_MODIFY)
+ fanotify_fastpath_clear(inode);
/*
* SRCU!! the groups list is very very much read only and the path is
* very hot (assuming something is using fanotify) Not blocking while
@@ -67,6 +78,8 @@ void fanotify(struct file *file, unsigned int mask)
idx = srcu_read_lock(&fanotify_grp_srcu_struct);
list_for_each_entry_rcu(group, &fanotify_groups, group_list) {
if (mask & group->mask) {
+ if (fanotify_is_fastpath(file, mask, group))
+ continue;
if (!event) {
event = create_event(file, mask);
/* shit, we OOM'd and now we can't tell, lets hope something else blows up */
@@ -94,6 +107,12 @@ static __init int fanotify_init(void)
if (ret)
return ret;
+ ret = fanotify_fastpath_init();
+ if (ret) {
+ fanotify_notification_uninit();
+ return ret;
+ }
+
return init_srcu_struct(&fanotify_grp_srcu_struct);
}
__initcall(fanotify_init);
diff --git a/fs/notify/fanotify.h b/fs/notify/fanotify.h
index 5b0253e..091ea5e 100644
--- a/fs/notify/fanotify.h
+++ b/fs/notify/fanotify.h
@@ -46,6 +46,31 @@ struct fanotify_event {
atomic_t refcnt; /* how many groups still are using/need to send this event */
};
+/*
+ * a fastpath is simply an entry attached to an in core inode which allows an
+ * fanotify listener to indicate they are no longer interested in events of
+ * a type matching mask.
+ *
+ * these are flushed when an inode is evicted from core or when the inode is
+ * modified (as seen by fsnotify_access)
+ */
+struct fanotify_fastpath_entry {
+ struct fanotify_group *group; /* group this fastpath entry is for */
+ unsigned int mask; /* mask this fastpath entry is for */
+ struct inode *inode; /* inode this entry is associated with */
+ /*
+ * killcnt starts at 0 and is incremented when this entry will be killed
+ * since multiple processes my try to kill it at the same time.
+ * One might try to kill it from walking the i_list when an inode is being
+ * kicked out of cache and one might try to kill it when an fanotify group
+ * is being unregistered. The last one to finish cleaning everything up
+ * and decrement it to 0 will do the actual killing
+ */
+ atomic_t killcnt;
+ struct list_head i_list; /* list of fastpath_entries by inode->fastpath_entries */
+ struct list_head g_list; /* list of fastpath_entries by group->fastpath_entries */
+};
+
extern struct mutex fanotify_grp_mutex;
extern struct srcu_struct fanotify_grp_srcu_struct;
extern struct list_head fanotify_groups;
@@ -61,4 +86,13 @@ extern struct fanotify_event_holder *alloc_event_holder(void);
extern void fanotify_destroy_event_holder(struct fanotify_event_holder *holder);
extern __init int fanotify_notification_init(void);
extern __init int fanotify_notification_uninit(void);
+
+extern void fanotify_fastpath_get(struct fanotify_fastpath_entry *entry);
+extern void fanotify_fastpath_put(struct fanotify_fastpath_entry *entry);
+extern int fanotify_is_fastpath(struct file *file, unsigned int mask, struct fanotify_group *group);
+extern void fanotify_fastpath_clear_group(struct fanotify_group *group);
+extern void fanotify_fastpath_clear(struct inode *inode);
+extern __init int fanotify_fastpath_init(void);
+extern __init int fanotify_fastpath_uninit(void);
+
#endif /* _LINUX_FANOTIFY_PRIVATE_H */
diff --git a/fs/notify/fastpath.c b/fs/notify/fastpath.c
new file mode 100644
index 0000000..c4114af
--- /dev/null
+++ b/fs/notify/fastpath.c
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2008 Red Hat, Inc., Eric Paris <eparis@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/namei.h>
+#include <linux/poll.h>
+#include <linux/rculist.h>
+#include <linux/rcupdate.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/security.h>
+#include <linux/uaccess.h>
+#include <linux/writeback.h>
+
+#include <linux/fanotify.h>
+#include "fanotify.h"
+
+static struct kmem_cache *fastpath_kmem_cache;
+
+static void fanotify_fastpath_kill(struct fanotify_fastpath_entry *entry)
+{
+ entry->group = NULL;
+ entry->inode = NULL;
+ entry->mask = 0;
+ entry->i_list.next = NULL;
+ entry->g_list.next = NULL;
+ kmem_cache_free(fastpath_kmem_cache, entry);
+}
+
+static struct fanotify_fastpath_entry *fanotify_fastpath_alloc(void)
+{
+ struct fanotify_fastpath_entry *entry;
+
+ entry = kmem_cache_alloc(fastpath_kmem_cache, GFP_KERNEL);
+
+ return entry;
+}
+
+void fanotify_fastpath_get(struct fanotify_fastpath_entry *entry)
+{
+ atomic_inc(&entry->killcnt);
+}
+
+void fanotify_fastpath_put(struct fanotify_fastpath_entry *entry)
+{
+ if (atomic_dec_and_test(&entry->killcnt))
+ fanotify_fastpath_kill(entry);
+}
+
+int fanotify_is_fastpath(struct file *file, unsigned int mask, struct fanotify_group *group)
+{
+ struct inode *inode = file->f_path.dentry->d_inode;
+ struct fanotify_fastpath_entry *entry;
+ int found = 0;
+
+ read_lock(&inode->fastpath_rwlock);
+ list_for_each_entry(entry, &inode->fastpath_entries, i_list) {
+ /* is the entry for this group and is the entry->mask a superset of this event mask? */
+ if ((entry->group == group) && ((entry->mask & mask) == mask)) {
+ found = 1;
+ break;
+ }
+ }
+ read_unlock(&inode->fastpath_rwlock);
+
+ return found;
+}
+
+void fanotify_fastpath_clear_group(struct fanotify_group *group)
+{
+ struct fanotify_fastpath_entry *entry;
+ struct inode *inode;
+
+ mutex_lock(&group->fastpath_mutex);
+ while (!list_empty(&group->fastpath_entries)) {
+ entry = list_first_entry(&group->fastpath_entries, struct fanotify_fastpath_entry, g_list);
+
+ /* make sure the entry survives until it is off both lists */
+ fanotify_fastpath_get(entry);
+
+ /* remove from g_list */
+ list_del_init(&entry->g_list);
+ mutex_unlock(&group->fastpath_mutex);
+
+ inode = entry->inode;
+
+ /* remove from i_list */
+ write_lock(&inode->fastpath_rwlock);
+ if (!list_empty(&entry->i_list))
+ list_del_init(&entry->i_list);
+ write_unlock(&inode->fastpath_rwlock);
+
+ /* off both lists, may free now */
+ fanotify_fastpath_put(entry);
+
+ mutex_lock(&group->fastpath_mutex);
+ }
+ mutex_unlock(&group->fastpath_mutex);
+}
+
+void fanotify_fastpath_clear(struct inode *inode)
+{
+ struct fanotify_fastpath_entry *entry;
+ struct fanotify_group *group;
+
+ write_lock(&inode->fastpath_rwlock);
+ while (!list_empty(&inode->fastpath_entries)) {
+ entry = list_first_entry(&inode->fastpath_entries, struct fanotify_fastpath_entry, i_list);
+
+ /* make sure the entry survives until off both lists */
+ fanotify_fastpath_get(entry);
+ list_del_init(&entry->i_list);
+
+ group = entry->group;
+
+ write_unlock(&inode->fastpath_rwlock);
+
+ mutex_lock(&group->fastpath_mutex);
+ if (!list_empty(&entry->g_list))
+ list_del_init(&entry->g_list);
+ mutex_unlock(&group->fastpath_mutex);
+
+ /* off both lists, may kill now */
+ fanotify_fastpath_put(entry);
+
+ write_lock(&inode->fastpath_rwlock);
+ }
+ write_unlock(&inode->fastpath_rwlock);
+}
+
+/*
+ * add the fastpath entry to the in core inode referenced by fd
+ */
+int fanotify_fastpath_add(struct fanotify_group *group, int fd, unsigned int mask)
+{
+ struct file *file;
+ int fput_needed;
+ int ret = 0;
+ /* we initialize entry to shut up the compiler in case we just to out... */
+ struct fanotify_fastpath_entry *entry = NULL, *lentry;
+ struct inode *inode;
+ bool used = true;
+
+ file = fget_light(fd, &fput_needed);
+ if (!file)
+ return -EBADF;
+
+ inode = file->f_path.dentry->d_inode;
+
+ if (!S_ISREG(inode->i_mode)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* pre allocate an entry so we can hold the writelock */
+ entry = fanotify_fastpath_alloc();
+ if (!entry) {
+ ret = -ENOMEM;
+ /* be sure that used==TRUE here so it doens't try to free */
+ goto out;
+ }
+
+ /*
+ * this is the only place we hold both the group and the inode lock
+ * we could take them in either order, but we take the mutex first
+ * so we don't sleep holding the rwlock
+ */
+ mutex_lock(&group->fastpath_mutex);
+ write_lock(&inode->fastpath_rwlock);
+ list_for_each_entry(lentry, &inode->fastpath_entries, i_list) {
+ if (lentry->group == group) {
+ lentry->mask |= mask;
+ used = false;
+ goto out_unlock;
+ }
+ }
+
+ atomic_set(&entry->killcnt, 0);
+ entry->group = group;
+ entry->mask = mask;
+ entry->inode = inode;
+
+ list_add(&entry->i_list, &inode->fastpath_entries);
+ list_add(&entry->g_list, &group->fastpath_entries);
+
+out_unlock:
+ write_unlock(&inode->fastpath_rwlock);
+ mutex_unlock(&group->fastpath_mutex);
+out:
+ fput_light(file, fput_needed);
+ if (!used)
+ fanotify_fastpath_kill(entry);
+ return ret;
+}
+
+
+__init int fanotify_fastpath_uninit(void)
+{
+ kmem_cache_destroy(fastpath_kmem_cache);
+ fastpath_kmem_cache = NULL;
+
+ return 0;
+}
+
+__init int fanotify_fastpath_init(void)
+{
+ fastpath_kmem_cache = kmem_cache_create("fanotify_fastpath_entry", sizeof(struct fanotify_fastpath_entry), 0, SLAB_PANIC, NULL);
+
+ return 0;
+}
diff --git a/fs/notify/group.c b/fs/notify/group.c
index fc3d736..ff9b5ef 100644
--- a/fs/notify/group.c
+++ b/fs/notify/group.c
@@ -87,6 +87,9 @@ void fanotify_kill_group(struct fanotify_group *group)
/* clear the notification queue of all events */
fanotify_notification_clear_group(group);
+ /* clear all fastpath entries for this group */
+ fanotify_fastpath_clear_group(group);
+
kfree(group);
}
diff --git a/include/linux/fanotify.h b/include/linux/fanotify.h
index 5add54f..015cfea 100644
--- a/include/linux/fanotify.h
+++ b/include/linux/fanotify.h
@@ -76,17 +76,25 @@ struct fanotify_group {
struct mutex notification_mutex;/* protect the notification_list */
struct list_head notification_list; /* list of event_holder this group needs to send to userspace */
wait_queue_head_t notification_waitq; /* read() on the notification file blocks on this waitq */
+
+ /* stores all fastapth entries assoc with this group so they can be cleaned on unregister */
+ struct mutex fastpath_mutex; /* protect fastpath_entries list */
+ struct list_head fastpath_entries; /* all fastpath entries for this group */
};
#ifdef CONFIG_FANOTIFY
extern void fanotify(struct file *file, unsigned int mask);
+extern void fanotify_inode_delete(struct inode *inode);
extern void fanotify_get_group(struct fanotify_group *group);
extern struct fanotify_group *fanotify_find_group(unsigned int group_num, unsigned int mask);
extern void fanotify_put_group(struct fanotify_group *group);
+/* things called from the socket */
extern int fanotify_create_event_fd(struct fanotify_group *group, struct fanotify_event_metadata *data, int nonblock);
+extern int fanotify_fastpath_add(struct fanotify_group *group, int fd, unsigned int mask);
+
#else
static inline void fanotify(struct file *file, unsigned int mask)
@@ -108,6 +116,15 @@ static inline int fanotify_create_event_fd(struct fanotify_group *group, struct
memset(data, 0, sizeof(struct fanotify_event_metadata));
return 0;
}
+
+static inline int fanotify_fastpath_add(struct fanotify_group *group, int fd, unsigned int mask)
+{
+ return 0;
+}
+
+static inline void fanotify_inode_delete(struct inode *inode)
+{}
+
#endif /* CONFIG_FANOTIFY */
#endif /* __KERNEL __ */
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 44e3cb2..3d877e3 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -676,6 +676,11 @@ struct inode {
struct mutex inotify_mutex; /* protects the watches list */
#endif
+#ifdef CONFIG_FANOTIFY
+ struct list_head fastpath_entries; /* fanotify fastpath entries protected by group */
+ rwlock_t fastpath_rwlock; /* protect the fastpath entries list */
+#endif
+
unsigned long i_state;
unsigned long dirtied_when; /* jiffies of first dirtying */
diff --git a/include/linux/fsnotify.h b/include/linux/fsnotify.h
index ccee5ea..5af4225 100644
--- a/include/linux/fsnotify.h
+++ b/include/linux/fsnotify.h
@@ -82,6 +82,14 @@ static inline void fsnotify_nameremove(struct dentry *dentry, int isdir)
}
/*
+ * fsnotify_inode_delete - and inode is being evicted from cache, clean up is needed
+ */
+static inline void fsnotify_inode_delete(struct inode *inode)
+{
+ fanotify_inode_delete(inode);
+}
+
+/*
* fsnotify_inoderemove - an inode is going away
*/
static inline void fsnotify_inoderemove(struct inode *inode)
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH -v2 08/16] fanotify: add a userspace interface for fastpaths
2008-10-14 20:51 [PATCH -v2 00/16] fanotify: novel all file access notification and permissions system Eric Paris
` (6 preceding siblings ...)
2008-10-14 20:52 ` [PATCH -v2 07/16] fanotify: fastpath to ignore certain in core inodes Eric Paris
@ 2008-10-14 20:52 ` Eric Paris
2008-10-14 20:52 ` [PATCH -v2 09/16] fanotify: add group priorities Eric Paris
` (7 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Eric Paris @ 2008-10-14 20:52 UTC (permalink / raw)
To: linux-kernel, malware-list; +Cc: viro, alan, arjan, greg, tytso
turns out the fastpath code is useless without an interface. This one
works.
Signed-off-by: Eric Paris <eparis@redhat.com>
---
fs/notify/group.c | 3 +++
include/linux/fanotify.h | 9 +++++++++
net/fanotify/af_fanotify.c | 31 ++++++++++++++++++++++++++++++-
3 files changed, 42 insertions(+), 1 deletions(-)
diff --git a/fs/notify/group.c b/fs/notify/group.c
index ff9b5ef..21037cc 100644
--- a/fs/notify/group.c
+++ b/fs/notify/group.c
@@ -74,6 +74,9 @@ struct fanotify_group *fanotify_find_group(unsigned int group_num, unsigned int
INIT_LIST_HEAD(&group->notification_list);
init_waitqueue_head(&group->notification_waitq);
+ mutex_init(&group->fastpath_mutex);
+ INIT_LIST_HEAD(&group->fastpath_entries);
+
/* add it */
list_add_rcu(&group->group_list, &fanotify_groups);
diff --git a/include/linux/fanotify.h b/include/linux/fanotify.h
index 015cfea..a1ea7e0 100644
--- a/include/linux/fanotify.h
+++ b/include/linux/fanotify.h
@@ -57,6 +57,15 @@ struct fanotify_event_metadata {
/* fanotify getsockopt optvals */
#define FANOTIFY_GET_EVENT 1
+/* struct used for FANOTIFY_SET_FASTPATH */
+struct fanotify_so_fastpath {
+ int32_t fd;
+ uint32_t mask;
+};
+
+/* fanotify setsockopt optvals */
+#define FANOTIFY_SET_FASTPATH 1
+
#ifdef __KERNEL__
#include <linux/fs.h>
diff --git a/net/fanotify/af_fanotify.c b/net/fanotify/af_fanotify.c
index eb2d023..f63ee7a 100644
--- a/net/fanotify/af_fanotify.c
+++ b/net/fanotify/af_fanotify.c
@@ -159,6 +159,35 @@ static unsigned int fan_poll(struct file * file, struct socket *sock, poll_table
return 0;
}
+static int fan_setsockopt(struct socket *sock, int level, int optname, char __user *optval, int optlen)
+{
+ struct fanotify_sock *fan_sock;
+ struct fanotify_group *group;
+ struct fanotify_so_fastpath fp_data;
+ int ret = 0;
+
+ if (sock->state != SS_CONNECTED)
+ return -EBADF;
+
+ fan_sock = fan_sk(sock->sk);
+ group = fan_sock->group;
+
+ switch(optname) {
+ case FANOTIFY_SET_FASTPATH:
+ if (optlen < sizeof(struct fanotify_so_fastpath))
+ return -ENOMEM;
+ ret = copy_from_user(&fp_data, optval, sizeof(struct fanotify_so_fastpath));
+ if (ret)
+ return ret;
+ fanotify_fastpath_add(group, fp_data.fd, fp_data.mask);
+ break;
+ default:
+ return -ENOPROTOOPT;
+ }
+
+ return ret;
+}
+
static int fan_getsockopt(struct socket *sock, int level, int optname, char __user *optval, int __user *optlen)
{
struct fanotify_sock *fan_sock;
@@ -210,7 +239,7 @@ static const struct proto_ops fanotify_proto_ops = {
.ioctl = sock_no_ioctl,
.listen = sock_no_listen,
.shutdown = sock_no_shutdown,
- .setsockopt = sock_no_setsockopt,
+ .setsockopt = fan_setsockopt,
.getsockopt = fan_getsockopt,
.sendmsg = sock_no_sendmsg,
.recvmsg = sock_no_recvmsg,
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH -v2 09/16] fanotify: add group priorities
2008-10-14 20:51 [PATCH -v2 00/16] fanotify: novel all file access notification and permissions system Eric Paris
` (7 preceding siblings ...)
2008-10-14 20:52 ` [PATCH -v2 08/16] fanotify: add a userspace interface for fastpaths Eric Paris
@ 2008-10-14 20:52 ` Eric Paris
2008-10-14 20:52 ` [PATCH -v2 10/16] fanotify: blocking and access granting Eric Paris
` (6 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Eric Paris @ 2008-10-14 20:52 UTC (permalink / raw)
To: linux-kernel, malware-list; +Cc: viro, alan, arjan, greg, tytso
In preperation for blocking fanotify calls group priorities must be added.
async events will be sent as quickly as possible without waiting for a
response.
Signed-off-by: Eric Paris <eparis@redhat.com>
---
fs/notify/group.c | 30 ++++++++++++++++++++++++------
include/linux/fanotify.h | 5 ++++-
net/fanotify/af_fanotify.c | 2 +-
3 files changed, 29 insertions(+), 8 deletions(-)
diff --git a/fs/notify/group.c b/fs/notify/group.c
index 21037cc..37b8630 100644
--- a/fs/notify/group.c
+++ b/fs/notify/group.c
@@ -42,15 +42,16 @@ void fanotify_get_group(struct fanotify_group *group)
atomic_inc(&group->refcnt);
}
-struct fanotify_group *fanotify_find_group(unsigned int group_num, unsigned int mask)
+struct fanotify_group *fanotify_find_group(unsigned int priority, unsigned int group_num, unsigned int mask)
{
struct fanotify_group *group_iter;
struct fanotify_group *group = NULL;
mutex_lock(&fanotify_grp_mutex);
list_for_each_entry_rcu(group_iter, &fanotify_groups, group_list) {
- if (group_iter->group_num == group_num) {
- if (group_iter->mask == mask) {
+ if (group_iter->priority == priority) {
+ if ((group_iter->mask == mask) &&
+ (group_iter->group_num == group_num)) {
fanotify_get_group(group_iter);
group = group_iter;
} else
@@ -69,6 +70,7 @@ struct fanotify_group *fanotify_find_group(unsigned int group_num, unsigned int
group->group_num = group_num;
group->mask = mask;
+ group->priority = priority;
mutex_init(&group->notification_mutex);
INIT_LIST_HEAD(&group->notification_list);
@@ -77,9 +79,26 @@ struct fanotify_group *fanotify_find_group(unsigned int group_num, unsigned int
mutex_init(&group->fastpath_mutex);
INIT_LIST_HEAD(&group->fastpath_entries);
- /* add it */
- list_add_rcu(&group->group_list, &fanotify_groups);
+ /* Do we need to be the first entry? */
+ if (list_empty(&fanotify_groups)) {
+ list_add_rcu(&group->group_list, &fanotify_groups);
+ goto out;
+ }
+
+ list_for_each_entry(group_iter, &fanotify_groups, group_list) {
+ /* insert in front of this one? */
+ if (priority < group_iter->priority) {
+ /* I used list_add_tail() to insert in front of group_iter... */
+ list_add_tail_rcu(&group->group_list, &group_iter->group_list);
+ break;
+ }
+ /* are we at the end? if so insert at end */
+ if (list_is_last(&group_iter->group_list, &fanotify_groups)) {
+ list_add_tail_rcu(&group->group_list, &fanotify_groups);
+ break;
+ }
+ }
out:
mutex_unlock(&fanotify_grp_mutex);
return group;
@@ -100,7 +119,6 @@ void fanotify_put_group(struct fanotify_group *group)
{
mutex_lock(&fanotify_grp_mutex);
if (atomic_dec_and_test(&group->refcnt)) {
-
list_del_rcu(&group->group_list);
mutex_unlock(&fanotify_grp_mutex);
diff --git a/include/linux/fanotify.h b/include/linux/fanotify.h
index a1ea7e0..22496b2 100644
--- a/include/linux/fanotify.h
+++ b/include/linux/fanotify.h
@@ -42,6 +42,7 @@
#endif
struct fanotify_addr {
+ uint32_t priority;
uint32_t group_num;
uint32_t mask;
uint32_t timeout;
@@ -89,6 +90,8 @@ struct fanotify_group {
/* stores all fastapth entries assoc with this group so they can be cleaned on unregister */
struct mutex fastpath_mutex; /* protect fastpath_entries list */
struct list_head fastpath_entries; /* all fastpath entries for this group */
+
+ unsigned int priority; /* order this group should receive msgs. low first */
};
#ifdef CONFIG_FANOTIFY
@@ -97,7 +100,7 @@ extern void fanotify(struct file *file, unsigned int mask);
extern void fanotify_inode_delete(struct inode *inode);
extern void fanotify_get_group(struct fanotify_group *group);
-extern struct fanotify_group *fanotify_find_group(unsigned int group_num, unsigned int mask);
+extern struct fanotify_group *fanotify_find_group(unsigned int priority, unsigned int group_num, unsigned int mask);
extern void fanotify_put_group(struct fanotify_group *group);
/* things called from the socket */
diff --git a/net/fanotify/af_fanotify.c b/net/fanotify/af_fanotify.c
index f63ee7a..a7d3b96 100644
--- a/net/fanotify/af_fanotify.c
+++ b/net/fanotify/af_fanotify.c
@@ -143,7 +143,7 @@ static int fan_bind(struct socket *sock, struct sockaddr *addr, int addr_len)
return -EINVAL;
fan_sock = fan_sk(sock->sk);
- fan_sock->group = fanotify_find_group(fan_addr->group_num, fan_addr->mask);
+ fan_sock->group = fanotify_find_group(fan_addr->priority, fan_addr->group_num, fan_addr->mask);
if (IS_ERR(fan_sock->group))
return PTR_ERR(fan_sock->group);
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH -v2 10/16] fanotify: blocking and access granting
2008-10-14 20:51 [PATCH -v2 00/16] fanotify: novel all file access notification and permissions system Eric Paris
` (8 preceding siblings ...)
2008-10-14 20:52 ` [PATCH -v2 09/16] fanotify: add group priorities Eric Paris
@ 2008-10-14 20:52 ` Eric Paris
2008-10-14 20:52 ` [PATCH -v2 11/16] fanotify: give a special access permission check Eric Paris
` (5 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Eric Paris @ 2008-10-14 20:52 UTC (permalink / raw)
To: linux-kernel, malware-list; +Cc: viro, alan, arjan, greg, tytso
This patch introduces blocking an access granting for fanotify. Events are
sent to userspace and the original process is blocked (assuming not
O_NONBLOCK) for at most the amount of time determined by the group. Total
maximum blocking time is the sum of all groups possible blocking time.
O_NONBLOCK processes trying to read and a group registered to require
blocking/access control on read MUST have a fastpath entry in order to ever
make progress.
Signed-off-by: Eric Paris <eparis@redhat.com>
---
fs/aio.c | 7 ++
fs/notify/Makefile | 2 -
fs/notify/access.c | 164 ++++++++++++++++++++++++++++++++++++++++++++++
fs/notify/fanotify.c | 46 +++++++++++--
fs/notify/fanotify.h | 17 ++++-
fs/notify/group.c | 8 ++
fs/notify/notification.c | 2 +
fs/open.c | 5 +
fs/read_write.c | 6 ++
include/linux/fanotify.h | 35 +++++++++-
10 files changed, 278 insertions(+), 14 deletions(-)
create mode 100644 fs/notify/access.c
diff --git a/fs/aio.c b/fs/aio.c
index f658441..008de79 100644
--- a/fs/aio.c
+++ b/fs/aio.c
@@ -31,6 +31,7 @@
#include <linux/workqueue.h>
#include <linux/security.h>
#include <linux/eventfd.h>
+#include <linux/fanotify.h>
#include <asm/kmap_types.h>
#include <asm/uaccess.h>
@@ -1454,6 +1455,9 @@ static ssize_t aio_setup_iocb(struct kiocb *kiocb)
ret = security_file_permission(file, MAY_READ);
if (unlikely(ret))
break;
+ ret = fanotify(file, FAN_ACCESS_PERM);
+ if (unlikely(ret))
+ break;
ret = aio_setup_single_vector(kiocb);
if (ret)
break;
@@ -1486,6 +1490,9 @@ static ssize_t aio_setup_iocb(struct kiocb *kiocb)
ret = security_file_permission(file, MAY_READ);
if (unlikely(ret))
break;
+ ret = fanotify(file, FAN_ACCESS_PERM);
+ if (unlikely(ret))
+ break;
ret = aio_setup_vectored_rw(READ, kiocb);
if (ret)
break;
diff --git a/fs/notify/Makefile b/fs/notify/Makefile
index 90cb910..0af890a 100644
--- a/fs/notify/Makefile
+++ b/fs/notify/Makefile
@@ -3,4 +3,4 @@ obj-$(CONFIG_INOTIFY_USER) += inotify_user.o
obj-$(CONFIG_DNOTIFY) += dnotify.o
-obj-$(CONFIG_FANOTIFY) += fanotify.o notification.o group.o fastpath.o
+obj-$(CONFIG_FANOTIFY) += fanotify.o notification.o group.o fastpath.o access.o
diff --git a/fs/notify/access.c b/fs/notify/access.c
new file mode 100644
index 0000000..6a2606c
--- /dev/null
+++ b/fs/notify/access.c
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2008 Red Hat, Inc., Eric Paris <eparis@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <asm/atomic.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/path.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+
+#include <linux/fanotify.h>
+#include "fanotify.h"
+
+int fanotify_add_event_to_access(struct fanotify_group *group, struct fanotify_event *event)
+{
+ struct fanotify_event_holder *holder;
+
+ if (list_empty(&event->holder.event_list))
+ holder = (struct fanotify_event_holder *)event;
+ else
+ holder = alloc_event_holder();
+ if (!holder)
+ return -ENOMEM;
+
+ fanotify_get_event(event);
+
+ mutex_lock(&group->access_mutex);
+ spin_lock(&event->holder_spinlock);
+ holder->event = event;
+ list_add_tail(&holder->event_list, &group->access_list);
+ spin_unlock(&event->holder_spinlock);
+ mutex_unlock(&group->access_mutex);
+
+ return 0;
+}
+
+int fanotify_get_response_from_access(struct fanotify_group *group, struct fanotify_event *event)
+{
+ int ret;
+ ret = wait_event_interruptible_timeout(group->access_waitq,
+ event->response,
+ msecs_to_jiffies(5000));
+ /* Timeout or signal? */
+ if (ret <= 0) {
+ struct fanotify_event_holder *holder;
+
+ /* pull the event off the access_list */
+ mutex_lock(&group->access_mutex);
+ list_for_each_entry(holder, &group->access_list, event_list) {
+ if (holder->event != event)
+ continue;
+
+ spin_lock(&event->holder_spinlock);
+ holder->event = NULL;
+ /* as soon as we do this the event.holder might get reused */
+ list_del_init(&holder->event_list);
+ spin_unlock(&event->holder_spinlock);
+
+ if (event != (struct fanotify_event *)holder)
+ fanotify_destroy_event_holder(holder);
+ fanotify_put_event(event);
+ break;
+ }
+ mutex_unlock(&group->access_mutex);
+
+ /*
+ * if we took a signal, return ERESTARTSYS
+ * if we timed out userspace is broken so return ALLOW
+ */
+ return ret;
+ }
+
+ /* userspace responded, convert to something usable */
+ switch (event->response) {
+ case FAN_ALLOW:
+ return 0;
+ case FAN_DENY:
+ default:
+ return -EPERM;
+ }
+}
+
+int fanotify_process_access_response(struct fanotify_group *group, unsigned long cookie, unsigned int response)
+{
+ struct fanotify_event *event = NULL;
+ struct fanotify_event_holder *holder;
+
+ /*
+ * make sure the response is valid, if invalid we do nothing and either
+ * userspace can send a valid responce or we will clean it up after the
+ * timeout
+ */
+ if (response & ~(FAN_ALLOW | FAN_DENY))
+ return -EINVAL;
+
+ mutex_lock(&group->access_mutex);
+ list_for_each_entry(holder, &group->access_list, event_list) {
+ if (holder->event->cookie != cookie)
+ continue;
+
+ event = holder->event;
+ spin_lock(&event->holder_spinlock);
+ holder->event = NULL;
+ /* as soon as we do this the event.holder might be reused */
+ list_del_init(&holder->event_list);
+ spin_unlock(&event->holder_spinlock);
+
+ if (event != (struct fanotify_event *)holder)
+ fanotify_destroy_event_holder(holder);
+ break;
+ }
+ mutex_unlock(&group->access_mutex);
+
+ if (!event)
+ return -ENOENT;
+
+ event->response = response;
+ wake_up(&group->access_waitq);
+ fanotify_put_event(event);
+
+ return 0;
+}
+
+void fanotify_access_clear_group(struct fanotify_group *group)
+{
+ struct fanotify_event *event = NULL;
+ struct fanotify_event_holder *holder;
+
+ mutex_lock(&group->access_mutex);
+ list_for_each_entry(holder, &group->access_list, event_list) {
+ event = holder->event;
+ spin_lock(&event->holder_spinlock);
+ holder->event = NULL;
+ /* as soon as we do this the event.holder might be reused */
+ list_del_init(&holder->event_list);
+ spin_unlock(&event->holder_spinlock);
+
+ if (event != (struct fanotify_event *)holder)
+ fanotify_destroy_event_holder(holder);
+
+ event->response = FAN_ALLOW;
+ wake_up(&group->access_waitq);
+ fanotify_put_event(event);
+ }
+ mutex_unlock(&group->access_mutex);
+}
diff --git a/fs/notify/fanotify.c b/fs/notify/fanotify.c
index aa12e91..d995c14 100644
--- a/fs/notify/fanotify.c
+++ b/fs/notify/fanotify.c
@@ -44,22 +44,22 @@ void fanotify_inode_delete(struct inode *inode)
}
EXPORT_SYMBOL_GPL(fanotify_inode_delete);
-void fanotify(struct file *file, unsigned int mask)
+int fanotify(struct file *file, unsigned int mask)
{
struct fanotify_group *group;
struct fanotify_event *event = NULL;
struct task_struct *tsk = current;
struct inode *inode = file->f_path.dentry->d_inode;
- int idx;
+ int idx, ret = 0;
if (likely(list_empty(&fanotify_groups)))
- return;
+ return 0;
if (tsk->flags & PF_NOFACCESS)
- return;
+ return 0;
if (!S_ISREG(inode->i_mode))
- return;
+ return 0;
if (mask & FAN_MODIFY)
fanotify_fastpath_clear(inode);
@@ -86,7 +86,39 @@ void fanotify(struct file *file, unsigned int mask)
if (!event)
break;
}
- fanotify_add_event_to_notif(group, event);
+ if (mask & FAN_ALL_EVENTS_PERM) {
+ /*
+ * if you register for READ_ACCESS you MUST be setting
+ * fastpath events or the client will NEVER make progress.
+ * open is a blocking event so we don't require fastpath
+ * entries and will wait for a decision.
+ *
+ * someday we could do an add_timer here, schedule a workqueue,
+ * and in there we could check to see if this file got a fastpath
+ * groups which don't obey the protocol could be evicted or an
+ * O_NONBLOCK exclusion added. There are some locking and
+ * security implications to doing it that way, so for now we
+ * are just going to assume userspace is adding fastpaths for
+ * O_NONBLOCK files.....
+ */
+ if ((mask & FAN_ACCESS_PERM) && (file->f_flags & O_NONBLOCK)) {
+ fanotify_add_event_to_notif(group, event);
+ ret = -EWOULDBLOCK;
+ break;
+ }
+ event->cookie = atomic_long_inc_return(&group->cookie);
+ event->response = 0;
+ /* put on access_list first so userspace can't be so fast responding it isn't on the list yet */
+ fanotify_add_event_to_access(group, event);
+
+ fanotify_add_event_to_notif(group, event);
+
+ ret = fanotify_get_response_from_access(group, event);
+ if (ret)
+ break;
+ } else {
+ fanotify_add_event_to_notif(group, event);
+ }
}
}
srcu_read_unlock(&fanotify_grp_srcu_struct, idx);
@@ -96,6 +128,8 @@ void fanotify(struct file *file, unsigned int mask)
*/
if (event)
fanotify_put_event(event);
+
+ return ret;
}
EXPORT_SYMBOL_GPL(fanotify);
diff --git a/fs/notify/fanotify.h b/fs/notify/fanotify.h
index 091ea5e..e07703f 100644
--- a/fs/notify/fanotify.h
+++ b/fs/notify/fanotify.h
@@ -14,12 +14,14 @@
#include <linux/wait.h>
/*
- * A single event can be queued in multiple group->notification_lists.
+ * A single event can be queued in multiple group->notification_lists and to at
+ * most one group->access_list at the same time.
*
- * each group->notification_list will point to an event_holer which in turns points
- * to the actual event that needs to be sent to userspace.
+ * each group->notification_list or group->access_list will point to an
+ * event_holer which in turns points to the actual event that needs to be sent
+ * to userspace.
*
- * Seemed cheaper to create a refcnt'd event and a small holder for every group
+ * Seemed cheaper to create a refcnt'd event and a small holder for every list
* than create a different event for every group
*
*/
@@ -44,6 +46,9 @@ struct fanotify_event {
struct path path; /* path from the original access */
unsigned int mask; /* the type of access */
atomic_t refcnt; /* how many groups still are using/need to send this event */
+ /* if waiting for a userspace access answer this is the cookie they will send back */
+ unsigned long cookie;
+ unsigned int response; /* userspace answer to question */
};
/*
@@ -95,4 +100,8 @@ extern void fanotify_fastpath_clear(struct inode *inode);
extern __init int fanotify_fastpath_init(void);
extern __init int fanotify_fastpath_uninit(void);
+extern int fanotify_add_event_to_access(struct fanotify_group *group, struct fanotify_event *event);
+extern int fanotify_get_response_from_access(struct fanotify_group *group, struct fanotify_event *event);
+extern void fanotify_access_clear_group(struct fanotify_group *group);
+
#endif /* _LINUX_FANOTIFY_PRIVATE_H */
diff --git a/fs/notify/group.c b/fs/notify/group.c
index 37b8630..68f895e 100644
--- a/fs/notify/group.c
+++ b/fs/notify/group.c
@@ -79,6 +79,11 @@ struct fanotify_group *fanotify_find_group(unsigned int priority, unsigned int g
mutex_init(&group->fastpath_mutex);
INIT_LIST_HEAD(&group->fastpath_entries);
+ atomic_set(&group->cookie, 0);
+ mutex_init(&group->access_mutex);
+ INIT_LIST_HEAD(&group->access_list);
+ init_waitqueue_head(&group->access_waitq);
+
/* Do we need to be the first entry? */
if (list_empty(&fanotify_groups)) {
list_add_rcu(&group->group_list, &fanotify_groups);
@@ -112,6 +117,9 @@ void fanotify_kill_group(struct fanotify_group *group)
/* clear all fastpath entries for this group */
fanotify_fastpath_clear_group(group);
+ /* set an allow for all outstanding access decisions */
+ fanotify_access_clear_group(group);
+
kfree(group);
}
diff --git a/fs/notify/notification.c b/fs/notify/notification.c
index 3dd69ef..89bb02c 100644
--- a/fs/notify/notification.c
+++ b/fs/notify/notification.c
@@ -176,6 +176,7 @@ struct fanotify_event *create_event(struct file *file, unsigned int mask)
event->path.mnt = file->f_path.mnt;
path_get(&event->path);
+ event->cookie = 0;
event->mask = mask;
WARN_ON(!event->path.dentry);
@@ -241,6 +242,7 @@ int fanotify_create_event_fd(struct fanotify_group *group, struct fanotify_event
data->fd = client_fd;
data->mask = event->mask;
+ data->cookie = event->cookie;
fanotify_put_event(event);
diff --git a/fs/open.c b/fs/open.c
index 1057c34..b9d9335 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -29,6 +29,7 @@
#include <linux/rcupdate.h>
#include <linux/audit.h>
#include <linux/falloc.h>
+#include <linux/fanotify.h>
int vfs_statfs(struct dentry *dentry, struct kstatfs *buf)
{
@@ -820,6 +821,10 @@ static struct file *__dentry_open(struct dentry *dentry, struct vfsmount *mnt,
if (error)
goto cleanup_all;
+ error = fanotify(f, FAN_OPEN_PERM);
+ if (error)
+ goto cleanup_all;
+
if (!open && f->f_op)
open = f->f_op->open;
if (open) {
diff --git a/fs/read_write.c b/fs/read_write.c
index 39f1fce..a74cf76 100644
--- a/fs/read_write.c
+++ b/fs/read_write.c
@@ -16,6 +16,7 @@
#include <linux/syscalls.h>
#include <linux/pagemap.h>
#include <linux/splice.h>
+#include <linux/fanotify.h>
#include "read_write.h"
#include <asm/uaccess.h>
@@ -201,6 +202,11 @@ int rw_verify_area(int read_write, struct file *file, loff_t *ppos, size_t count
read_write == READ ? MAY_READ : MAY_WRITE);
if (retval)
return retval;
+ if (read_write == READ) {
+ retval = fanotify(file, FAN_ACCESS_PERM);
+ if (retval)
+ return retval;
+ }
return count > MAX_RW_COUNT ? MAX_RW_COUNT : count;
}
diff --git a/include/linux/fanotify.h b/include/linux/fanotify.h
index 22496b2..0405e4c 100644
--- a/include/linux/fanotify.h
+++ b/include/linux/fanotify.h
@@ -17,6 +17,10 @@
#define FAN_OPEN_NOEXEC 0x00000010 /* File was opened */
#define FAN_OPEN_EXEC 0x00000020 /* File was opened with the intention of being exec'ed */
+/* userspace may also request blocking for permission checks for open and read */
+#define FAN_ACCESS_PERM 0x00000100
+#define FAN_OPEN_PERM 0x00000200
+
/* FIXME currently Q's have no limit.... */
#define FAN_Q_OVERFLOW 0x80000000 /* Event queued overflowed */
@@ -33,6 +37,16 @@
FAN_MODIFY |\
FAN_CLOSE |\
FAN_OPEN)
+/*
+ * Like the above list of events only these are the event types in which the
+ * kernel will wait for answers.
+ */
+#define FAN_ALL_EVENTS_PERM (FAN_OPEN_PERM |\
+ FAN_ACCESS_PERM)
+
+/* answers will need to be either allow or deny */
+#define FAN_ALLOW 0x00000001
+#define FAN_DENY 0x00000002
#ifdef __KERNEL__
#include <linux/types.h>
@@ -53,6 +67,7 @@ struct fanotify_addr {
struct fanotify_event_metadata {
int32_t fd;
uint32_t mask;
+ uint64_t cookie;
};
/* fanotify getsockopt optvals */
@@ -91,12 +106,18 @@ struct fanotify_group {
struct mutex fastpath_mutex; /* protect fastpath_entries list */
struct list_head fastpath_entries; /* all fastpath entries for this group */
+ /* needed to track outstanding requests we expect to hear from userspace */
+ atomic_long_t cookie; /* next cookie send to userspace for a decision */
+ struct mutex access_mutex; /* protect access_list list */
+ struct list_head access_list; /* event_holder we need an answer from userspace */
+ wait_queue_head_t access_waitq; /* we wait here for userspace access decisions */
+
unsigned int priority; /* order this group should receive msgs. low first */
};
#ifdef CONFIG_FANOTIFY
-extern void fanotify(struct file *file, unsigned int mask);
+extern int fanotify(struct file *file, unsigned int mask);
extern void fanotify_inode_delete(struct inode *inode);
extern void fanotify_get_group(struct fanotify_group *group);
@@ -106,11 +127,14 @@ extern void fanotify_put_group(struct fanotify_group *group);
/* things called from the socket */
extern int fanotify_create_event_fd(struct fanotify_group *group, struct fanotify_event_metadata *data, int nonblock);
extern int fanotify_fastpath_add(struct fanotify_group *group, int fd, unsigned int mask);
+extern int fanotify_process_access_response(struct fanotify_group *group, unsigned long cookie, unsigned int response);
#else
-static inline void fanotify(struct file *file, unsigned int mask)
-{}
+static inline int fanotify(struct file *file, unsigned int mask)
+{
+ return 0;
+}
static inline void fanotify_get_group(struct fanotify_group *group)
{}
@@ -137,6 +161,11 @@ static inline int fanotify_fastpath_add(struct fanotify_group *group, int fd, un
static inline void fanotify_inode_delete(struct inode *inode)
{}
+static inline int fanotify_process_access_response(struct fanotify_group *group, unsigned long cookie, unsigned int response)
+{
+ return 0;
+}
+
#endif /* CONFIG_FANOTIFY */
#endif /* __KERNEL __ */
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH -v2 11/16] fanotify: give a special access permission check
2008-10-14 20:51 [PATCH -v2 00/16] fanotify: novel all file access notification and permissions system Eric Paris
` (9 preceding siblings ...)
2008-10-14 20:52 ` [PATCH -v2 10/16] fanotify: blocking and access granting Eric Paris
@ 2008-10-14 20:52 ` Eric Paris
2008-10-14 20:52 ` [PATCH -v2 12/16] fanotify: user interface for access decisions Eric Paris
` (4 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Eric Paris @ 2008-10-14 20:52 UTC (permalink / raw)
To: linux-kernel, malware-list; +Cc: viro, alan, arjan, greg, tytso
add a special FANOTIFY_ACCESS_EXEC_PERM check. This will be applied any
place a process tries to access a file with execute permissions. Mainly
sys_execve, sys_uselib, and mmap'ing a file for exec.
Signed-off-by: Eric Paris <eparis@redhat.com>
---
fs/aio.c | 4 ++--
fs/exec.c | 10 ++++++++++
fs/read_write.c | 2 +-
include/linux/fanotify.h | 6 ++++--
mm/mmap.c | 7 +++++++
mm/mprotect.c | 6 ++++++
mm/nommu.c | 7 +++++++
7 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/fs/aio.c b/fs/aio.c
index 008de79..6395403 100644
--- a/fs/aio.c
+++ b/fs/aio.c
@@ -1455,7 +1455,7 @@ static ssize_t aio_setup_iocb(struct kiocb *kiocb)
ret = security_file_permission(file, MAY_READ);
if (unlikely(ret))
break;
- ret = fanotify(file, FAN_ACCESS_PERM);
+ ret = fanotify(file, FAN_ACCESS_NOEXEC_PERM);
if (unlikely(ret))
break;
ret = aio_setup_single_vector(kiocb);
@@ -1490,7 +1490,7 @@ static ssize_t aio_setup_iocb(struct kiocb *kiocb)
ret = security_file_permission(file, MAY_READ);
if (unlikely(ret))
break;
- ret = fanotify(file, FAN_ACCESS_PERM);
+ ret = fanotify(file, FAN_ACCESS_NOEXEC_PERM);
if (unlikely(ret))
break;
ret = aio_setup_vectored_rw(READ, kiocb);
diff --git a/fs/exec.c b/fs/exec.c
index 3612805..ab45d65 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -140,6 +140,11 @@ asmlinkage long sys_uselib(const char __user * library)
goto out;
fsnotify_open_exec(file);
+ error = fanotify(file, FAN_ACCESS_EXEC_PERM);
+ if (error) {
+ fput(file);
+ goto out;
+ }
error = -ENOEXEC;
if(file->f_op) {
@@ -694,6 +699,11 @@ struct file *open_exec(const char *name)
return file;
fsnotify_open_exec(file);
+ err = fanotify(file, FAN_ACCESS_EXEC_PERM);
+ if (err) {
+ fput(file);
+ goto out;
+ }
err = deny_write_access(file);
if (err) {
diff --git a/fs/read_write.c b/fs/read_write.c
index a74cf76..8ab4646 100644
--- a/fs/read_write.c
+++ b/fs/read_write.c
@@ -203,7 +203,7 @@ int rw_verify_area(int read_write, struct file *file, loff_t *ppos, size_t count
if (retval)
return retval;
if (read_write == READ) {
- retval = fanotify(file, FAN_ACCESS_PERM);
+ retval = fanotify(file, FAN_ACCESS_NOEXEC_PERM);
if (retval)
return retval;
}
diff --git a/include/linux/fanotify.h b/include/linux/fanotify.h
index 0405e4c..07b8e77 100644
--- a/include/linux/fanotify.h
+++ b/include/linux/fanotify.h
@@ -18,8 +18,9 @@
#define FAN_OPEN_EXEC 0x00000020 /* File was opened with the intention of being exec'ed */
/* userspace may also request blocking for permission checks for open and read */
-#define FAN_ACCESS_PERM 0x00000100
-#define FAN_OPEN_PERM 0x00000200
+#define FAN_ACCESS_NOEXEC_PERM 0x00000100
+#define FAN_ACCESS_EXEC_PERM 0x00000200
+#define FAN_OPEN_PERM 0x00000400
/* FIXME currently Q's have no limit.... */
#define FAN_Q_OVERFLOW 0x80000000 /* Event queued overflowed */
@@ -27,6 +28,7 @@
/* helper events */
#define FAN_CLOSE (FAN_CLOSE_WRITE | FAN_CLOSE_NOWRITE) /* close */
#define FAN_OPEN (FAN_OPEN_NOEXEC | FAN_OPEN_EXEC) /* open */
+#define FAN_ACCESS_PERM (FAN_ACCESS_NOEXEC_PERM | FAN_ACCESS_EXEC_PERM) /* access perm */
/*
* All of the events - we build the list by hand so that we can add flags in
diff --git a/mm/mmap.c b/mm/mmap.c
index e7a5a68..35d051f 100644
--- a/mm/mmap.c
+++ b/mm/mmap.c
@@ -18,6 +18,7 @@
#include <linux/init.h>
#include <linux/file.h>
#include <linux/fs.h>
+#include <linux/fanotify.h>
#include <linux/personality.h>
#include <linux/security.h>
#include <linux/hugetlb.h>
@@ -1027,6 +1028,12 @@ unsigned long do_mmap_pgoff(struct file * file, unsigned long addr,
default:
return -EINVAL;
}
+
+ if (prot & PROT_EXEC) {
+ error = fanotify(file, FAN_ACCESS_EXEC_PERM);
+ if (error)
+ return error;
+ }
} else {
switch (flags & MAP_TYPE) {
case MAP_SHARED:
diff --git a/mm/mprotect.c b/mm/mprotect.c
index fded06f..af1da2d 100644
--- a/mm/mprotect.c
+++ b/mm/mprotect.c
@@ -16,6 +16,7 @@
#include <linux/fs.h>
#include <linux/highmem.h>
#include <linux/security.h>
+#include <linux/fanotify.h>
#include <linux/mempolicy.h>
#include <linux/personality.h>
#include <linux/syscalls.h>
@@ -294,6 +295,11 @@ sys_mprotect(unsigned long start, size_t len, unsigned long prot)
if (error)
goto out;
+ if (vma->vm_file && (prot & PROT_EXEC))
+ error = fanotify(vma->vm_file, FAN_ACCESS_EXEC_PERM);
+ if (error)
+ goto out;
+
tmp = vma->vm_end;
if (tmp > end)
tmp = end;
diff --git a/mm/nommu.c b/mm/nommu.c
index ed75bc9..f541693 100644
--- a/mm/nommu.c
+++ b/mm/nommu.c
@@ -18,6 +18,7 @@
#include <linux/mman.h>
#include <linux/swap.h>
#include <linux/file.h>
+#include <linux/fanotify.h>
#include <linux/highmem.h>
#include <linux/pagemap.h>
#include <linux/slab.h>
@@ -709,6 +710,12 @@ static int validate_mmap_request(struct file *file,
/* backing file is not executable, try to copy */
capabilities &= ~BDI_CAP_MAP_DIRECT;
}
+
+ if (prot & PROT_EXEC) {
+ ret = fanotify(file, FAN_ACCESS_EXEC_PERM);
+ if (ret)
+ return ret;
+ }
}
else {
/* anonymous mappings are always memory backed and can be
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH -v2 12/16] fanotify: user interface for access decisions
2008-10-14 20:51 [PATCH -v2 00/16] fanotify: novel all file access notification and permissions system Eric Paris
` (10 preceding siblings ...)
2008-10-14 20:52 ` [PATCH -v2 11/16] fanotify: give a special access permission check Eric Paris
@ 2008-10-14 20:52 ` Eric Paris
2008-10-14 20:52 ` [PATCH -v2 13/16] fanotify: ability for userspace to delay responses Eric Paris
` (3 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Eric Paris @ 2008-10-14 20:52 UTC (permalink / raw)
To: linux-kernel, malware-list; +Cc: viro, alan, arjan, greg, tytso
turns out that access decisions are useless if there is no way to do
anything about them. So we add an interface. Alan will hate it, but at
least it works...
Signed-off-by: Eric Paris <eparis@redhat.com>
---
include/linux/fanotify.h | 7 +++++++
net/fanotify/af_fanotify.c | 11 ++++++++++-
2 files changed, 17 insertions(+), 1 deletions(-)
diff --git a/include/linux/fanotify.h b/include/linux/fanotify.h
index 07b8e77..b5c9db9 100644
--- a/include/linux/fanotify.h
+++ b/include/linux/fanotify.h
@@ -81,8 +81,15 @@ struct fanotify_so_fastpath {
uint32_t mask;
};
+/* struct used for FANOTIFY_SEND_RESPONSE */
+struct fanotify_so_access {
+ uint64_t cookie;
+ uint32_t response;
+};
+
/* fanotify setsockopt optvals */
#define FANOTIFY_SET_FASTPATH 1
+#define FANOTIFY_SEND_RESPONSE 2
#ifdef __KERNEL__
diff --git a/net/fanotify/af_fanotify.c b/net/fanotify/af_fanotify.c
index a7d3b96..89099ab 100644
--- a/net/fanotify/af_fanotify.c
+++ b/net/fanotify/af_fanotify.c
@@ -164,6 +164,7 @@ static int fan_setsockopt(struct socket *sock, int level, int optname, char __us
struct fanotify_sock *fan_sock;
struct fanotify_group *group;
struct fanotify_so_fastpath fp_data;
+ struct fanotify_so_access access_data;
int ret = 0;
if (sock->state != SS_CONNECTED)
@@ -179,7 +180,15 @@ static int fan_setsockopt(struct socket *sock, int level, int optname, char __us
ret = copy_from_user(&fp_data, optval, sizeof(struct fanotify_so_fastpath));
if (ret)
return ret;
- fanotify_fastpath_add(group, fp_data.fd, fp_data.mask);
+ ret = fanotify_fastpath_add(group, fp_data.fd, fp_data.mask);
+ break;
+ case FANOTIFY_SEND_RESPONSE:
+ if (optlen < sizeof(struct fanotify_so_access))
+ return -ENOMEM;
+ ret = copy_from_user(&access_data, optval, sizeof(struct fanotify_so_access));
+ if (ret)
+ return ret;
+ ret = fanotify_process_access_response(group, access_data.cookie, access_data.response);
break;
default:
return -ENOPROTOOPT;
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH -v2 13/16] fanotify: ability for userspace to delay responses
2008-10-14 20:51 [PATCH -v2 00/16] fanotify: novel all file access notification and permissions system Eric Paris
` (11 preceding siblings ...)
2008-10-14 20:52 ` [PATCH -v2 12/16] fanotify: user interface for access decisions Eric Paris
@ 2008-10-14 20:52 ` Eric Paris
2008-10-14 20:52 ` [PATCH -v2 14/16] fanotify: send pid with fanotify notification events Eric Paris
` (2 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Eric Paris @ 2008-10-14 20:52 UTC (permalink / raw)
To: linux-kernel, malware-list; +Cc: viro, alan, arjan, greg, tytso
Userspace may know that it is behind, may know it is going to take a while
to get a good response, or have any number of other reasons to block an
access request. FAN_RESTTIMER will reset the timeout for an fanotify
access decision response.
Signed-off-by: Eric Paris <eparis@redhat.com>
---
fs/notify/access.c | 31 +++++++++++++++++++++++++++----
fs/notify/fanotify.h | 1 +
fs/notify/notification.c | 2 ++
include/linux/fanotify.h | 1 +
4 files changed, 31 insertions(+), 4 deletions(-)
diff --git a/fs/notify/access.c b/fs/notify/access.c
index 6a2606c..5b3b32e 100644
--- a/fs/notify/access.c
+++ b/fs/notify/access.c
@@ -55,6 +55,8 @@ int fanotify_add_event_to_access(struct fanotify_group *group, struct fanotify_e
int fanotify_get_response_from_access(struct fanotify_group *group, struct fanotify_event *event)
{
int ret;
+
+retry:
ret = wait_event_interruptible_timeout(group->access_waitq,
event->response,
msecs_to_jiffies(5000));
@@ -89,13 +91,25 @@ int fanotify_get_response_from_access(struct fanotify_group *group, struct fanot
}
/* userspace responded, convert to something usable */
+ spin_lock(&event->response_lock);
switch (event->response) {
case FAN_ALLOW:
- return 0;
+ ret = 0;
+ break;
case FAN_DENY:
+ ret = -EPERM;
+ break;
+ case FAN_RESETTIMER:
+ event->response = 0;
+ spin_unlock(&event->response_lock);
+ goto retry;
default:
- return -EPERM;
+ ret = -EPERM;
+ break;
}
+ spin_unlock(&event->response_lock);
+
+ return ret;
}
int fanotify_process_access_response(struct fanotify_group *group, unsigned long cookie, unsigned int response)
@@ -108,7 +122,7 @@ int fanotify_process_access_response(struct fanotify_group *group, unsigned long
* userspace can send a valid responce or we will clean it up after the
* timeout
*/
- if (response & ~(FAN_ALLOW | FAN_DENY))
+ if (response & ~(FAN_ALLOW | FAN_DENY | FAN_RESETTIMER))
return -EINVAL;
mutex_lock(&group->access_mutex);
@@ -117,6 +131,11 @@ int fanotify_process_access_response(struct fanotify_group *group, unsigned long
continue;
event = holder->event;
+
+ /* FAN_RESETTIMER should be left on the list for later.... */
+ if (response == FAN_RESETTIMER)
+ break;
+
spin_lock(&event->holder_spinlock);
holder->event = NULL;
/* as soon as we do this the event.holder might be reused */
@@ -132,9 +151,13 @@ int fanotify_process_access_response(struct fanotify_group *group, unsigned long
if (!event)
return -ENOENT;
+ spin_lock(&event->response_lock);
event->response = response;
+ spin_unlock(&event->response_lock);
wake_up(&group->access_waitq);
- fanotify_put_event(event);
+
+ if (response != FAN_RESETTIMER)
+ fanotify_put_event(event);
return 0;
}
diff --git a/fs/notify/fanotify.h b/fs/notify/fanotify.h
index e07703f..d65db61 100644
--- a/fs/notify/fanotify.h
+++ b/fs/notify/fanotify.h
@@ -48,6 +48,7 @@ struct fanotify_event {
atomic_t refcnt; /* how many groups still are using/need to send this event */
/* if waiting for a userspace access answer this is the cookie they will send back */
unsigned long cookie;
+ spinlock_t response_lock; /* protects response */
unsigned int response; /* userspace answer to question */
};
diff --git a/fs/notify/notification.c b/fs/notify/notification.c
index 89bb02c..dd0d2db 100644
--- a/fs/notify/notification.c
+++ b/fs/notify/notification.c
@@ -27,6 +27,7 @@
#include <linux/path.h>
#include <linux/sched.h>
#include <linux/slab.h>
+#include <linux/spinlock.h>
#include <linux/fanotify.h>
#include "fanotify.h"
@@ -169,6 +170,7 @@ struct fanotify_event *create_event(struct file *file, unsigned int mask)
event->holder.event = NULL;
INIT_LIST_HEAD(&event->holder.event_list);
atomic_set(&event->refcnt, 1);
+ spin_lock_init(&event->response_lock);
spin_lock_init(&event->holder_spinlock);
diff --git a/include/linux/fanotify.h b/include/linux/fanotify.h
index b5c9db9..a36bd6b 100644
--- a/include/linux/fanotify.h
+++ b/include/linux/fanotify.h
@@ -49,6 +49,7 @@
/* answers will need to be either allow or deny */
#define FAN_ALLOW 0x00000001
#define FAN_DENY 0x00000002
+#define FAN_RESETTIMER 0x00000004
#ifdef __KERNEL__
#include <linux/types.h>
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH -v2 14/16] fanotify: send pid with fanotify notification events
2008-10-14 20:51 [PATCH -v2 00/16] fanotify: novel all file access notification and permissions system Eric Paris
` (12 preceding siblings ...)
2008-10-14 20:52 ` [PATCH -v2 13/16] fanotify: ability for userspace to delay responses Eric Paris
@ 2008-10-14 20:52 ` Eric Paris
2008-10-14 20:52 ` [PATCH -v2 15/16] fanotify: send tgid with notification messages Eric Paris
2008-10-14 20:52 ` [PATCH -v2 16/16] fanotify: send file f_flags along with notifications Eric Paris
15 siblings, 0 replies; 17+ messages in thread
From: Eric Paris @ 2008-10-14 20:52 UTC (permalink / raw)
To: linux-kernel, malware-list; +Cc: viro, alan, arjan, greg, tytso
Often things like AV scanners may want to allow access to 'bad' files
based on the process making the access help by sending the pid of the
originally acting process.
Signed-off-by: Eric Paris <eparis@redhat.com>
---
fs/notify/fanotify.h | 1 +
fs/notify/notification.c | 3 +++
include/linux/fanotify.h | 1 +
3 files changed, 5 insertions(+), 0 deletions(-)
diff --git a/fs/notify/fanotify.h b/fs/notify/fanotify.h
index d65db61..74e5d4d 100644
--- a/fs/notify/fanotify.h
+++ b/fs/notify/fanotify.h
@@ -48,6 +48,7 @@ struct fanotify_event {
atomic_t refcnt; /* how many groups still are using/need to send this event */
/* if waiting for a userspace access answer this is the cookie they will send back */
unsigned long cookie;
+ pid_t pid; /* pid of the original process */
spinlock_t response_lock; /* protects response */
unsigned int response; /* userspace answer to question */
};
diff --git a/fs/notify/notification.c b/fs/notify/notification.c
index dd0d2db..da2045a 100644
--- a/fs/notify/notification.c
+++ b/fs/notify/notification.c
@@ -181,6 +181,8 @@ struct fanotify_event *create_event(struct file *file, unsigned int mask)
event->cookie = 0;
event->mask = mask;
+ event->pid = current->pid;
+
WARN_ON(!event->path.dentry);
WARN_ON(!event->path.mnt);
@@ -245,6 +247,7 @@ int fanotify_create_event_fd(struct fanotify_group *group, struct fanotify_event
data->fd = client_fd;
data->mask = event->mask;
data->cookie = event->cookie;
+ data->pid = event->pid;
fanotify_put_event(event);
diff --git a/include/linux/fanotify.h b/include/linux/fanotify.h
index a36bd6b..ab7744a 100644
--- a/include/linux/fanotify.h
+++ b/include/linux/fanotify.h
@@ -71,6 +71,7 @@ struct fanotify_event_metadata {
int32_t fd;
uint32_t mask;
uint64_t cookie;
+ pid_t pid;
};
/* fanotify getsockopt optvals */
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH -v2 15/16] fanotify: send tgid with notification messages
2008-10-14 20:51 [PATCH -v2 00/16] fanotify: novel all file access notification and permissions system Eric Paris
` (13 preceding siblings ...)
2008-10-14 20:52 ` [PATCH -v2 14/16] fanotify: send pid with fanotify notification events Eric Paris
@ 2008-10-14 20:52 ` Eric Paris
2008-10-14 20:52 ` [PATCH -v2 16/16] fanotify: send file f_flags along with notifications Eric Paris
15 siblings, 0 replies; 17+ messages in thread
From: Eric Paris @ 2008-10-14 20:52 UTC (permalink / raw)
To: linux-kernel, malware-list; +Cc: viro, alan, arjan, greg, tytso
Send tgid of original process to fanotify listeners
Signed-off-by: Eric Paris <eparis@redhat.com>
---
fs/notify/fanotify.h | 1 +
fs/notify/notification.c | 2 ++
include/linux/fanotify.h | 1 +
3 files changed, 4 insertions(+), 0 deletions(-)
diff --git a/fs/notify/fanotify.h b/fs/notify/fanotify.h
index 74e5d4d..b45c145 100644
--- a/fs/notify/fanotify.h
+++ b/fs/notify/fanotify.h
@@ -49,6 +49,7 @@ struct fanotify_event {
/* if waiting for a userspace access answer this is the cookie they will send back */
unsigned long cookie;
pid_t pid; /* pid of the original process */
+ pid_t tgid; /* tgid of the original process */
spinlock_t response_lock; /* protects response */
unsigned int response; /* userspace answer to question */
};
diff --git a/fs/notify/notification.c b/fs/notify/notification.c
index da2045a..acd13b0 100644
--- a/fs/notify/notification.c
+++ b/fs/notify/notification.c
@@ -182,6 +182,7 @@ struct fanotify_event *create_event(struct file *file, unsigned int mask)
event->mask = mask;
event->pid = current->pid;
+ event->tgid = current->tgid;
WARN_ON(!event->path.dentry);
WARN_ON(!event->path.mnt);
@@ -248,6 +249,7 @@ int fanotify_create_event_fd(struct fanotify_group *group, struct fanotify_event
data->mask = event->mask;
data->cookie = event->cookie;
data->pid = event->pid;
+ data->tgid = event->tgid;
fanotify_put_event(event);
diff --git a/include/linux/fanotify.h b/include/linux/fanotify.h
index ab7744a..711efa6 100644
--- a/include/linux/fanotify.h
+++ b/include/linux/fanotify.h
@@ -72,6 +72,7 @@ struct fanotify_event_metadata {
uint32_t mask;
uint64_t cookie;
pid_t pid;
+ pid_t tgid;
};
/* fanotify getsockopt optvals */
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH -v2 16/16] fanotify: send file f_flags along with notifications
2008-10-14 20:51 [PATCH -v2 00/16] fanotify: novel all file access notification and permissions system Eric Paris
` (14 preceding siblings ...)
2008-10-14 20:52 ` [PATCH -v2 15/16] fanotify: send tgid with notification messages Eric Paris
@ 2008-10-14 20:52 ` Eric Paris
15 siblings, 0 replies; 17+ messages in thread
From: Eric Paris @ 2008-10-14 20:52 UTC (permalink / raw)
To: linux-kernel, malware-list; +Cc: viro, alan, arjan, greg, tytso
fanotify listeners may be interested in what flags the file associated with
a particular event were set. This sends f_flags along with the event
metadata.
Signed-off-by: Eric Paris <eparis@redhat.com>
---
fs/notify/fanotify.h | 1 +
fs/notify/notification.c | 2 ++
include/linux/fanotify.h | 1 +
3 files changed, 4 insertions(+), 0 deletions(-)
diff --git a/fs/notify/fanotify.h b/fs/notify/fanotify.h
index b45c145..5f774bd 100644
--- a/fs/notify/fanotify.h
+++ b/fs/notify/fanotify.h
@@ -50,6 +50,7 @@ struct fanotify_event {
unsigned long cookie;
pid_t pid; /* pid of the original process */
pid_t tgid; /* tgid of the original process */
+ unsigned int f_flags; /* f_flags from the original file */
spinlock_t response_lock; /* protects response */
unsigned int response; /* userspace answer to question */
};
diff --git a/fs/notify/notification.c b/fs/notify/notification.c
index acd13b0..4d1d046 100644
--- a/fs/notify/notification.c
+++ b/fs/notify/notification.c
@@ -183,6 +183,7 @@ struct fanotify_event *create_event(struct file *file, unsigned int mask)
event->pid = current->pid;
event->tgid = current->tgid;
+ event->f_flags = file->f_flags;
WARN_ON(!event->path.dentry);
WARN_ON(!event->path.mnt);
@@ -250,6 +251,7 @@ int fanotify_create_event_fd(struct fanotify_group *group, struct fanotify_event
data->cookie = event->cookie;
data->pid = event->pid;
data->tgid = event->tgid;
+ data->f_flags = event->f_flags;
fanotify_put_event(event);
diff --git a/include/linux/fanotify.h b/include/linux/fanotify.h
index 711efa6..cecac79 100644
--- a/include/linux/fanotify.h
+++ b/include/linux/fanotify.h
@@ -73,6 +73,7 @@ struct fanotify_event_metadata {
uint64_t cookie;
pid_t pid;
pid_t tgid;
+ uint32_t f_flags;
};
/* fanotify getsockopt optvals */
^ permalink raw reply [flat|nested] 17+ messages in thread