2009-12-17 21:24:29 -05:00
# include <linux/fanotify.h>
2009-12-17 21:24:25 -05:00
# include <linux/fdtable.h>
# include <linux/fsnotify_backend.h>
# include <linux/init.h>
2009-12-17 21:24:34 -05:00
# include <linux/jiffies.h>
2009-12-17 21:24:25 -05:00
# include <linux/kernel.h> /* UINT_MAX */
2009-12-17 21:24:28 -05:00
# include <linux/mount.h>
2009-12-17 21:24:34 -05:00
# include <linux/sched.h>
2009-12-17 21:24:25 -05:00
# include <linux/types.h>
2009-12-17 21:24:34 -05:00
# include <linux/wait.h>
2009-12-17 21:24:25 -05:00
2014-01-21 15:48:14 -08:00
# include "fanotify.h"
static bool should_merge ( struct fsnotify_event * old_fsn ,
struct fsnotify_event * new_fsn )
2009-12-17 21:24:25 -05:00
{
2014-01-21 15:48:14 -08:00
struct fanotify_event_info * old , * new ;
2009-12-17 21:24:25 -05:00
2012-03-23 02:42:23 +01:00
# ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
2014-01-21 15:48:14 -08:00
/* dont merge two permission events */
if ( ( old_fsn - > mask & FAN_ALL_PERM_EVENTS ) & &
( new_fsn - > mask & FAN_ALL_PERM_EVENTS ) )
return false ;
2012-03-23 02:42:23 +01:00
# endif
2014-01-21 15:48:14 -08:00
pr_debug ( " %s: old=%p new=%p \n " , __func__ , old_fsn , new_fsn ) ;
old = FANOTIFY_E ( old_fsn ) ;
new = FANOTIFY_E ( new_fsn ) ;
if ( old_fsn - > inode = = new_fsn - > inode & & old - > tgid = = new - > tgid & &
old - > path . mnt = = new - > path . mnt & &
old - > path . dentry = = new - > path . dentry )
return true ;
2009-12-17 21:24:25 -05:00
return false ;
}
2010-07-28 10:18:37 -04:00
/* and the list better be locked by something too! */
static struct fsnotify_event * fanotify_merge ( struct list_head * list ,
struct fsnotify_event * event )
2009-12-17 21:24:25 -05:00
{
2014-01-21 15:48:14 -08:00
struct fsnotify_event * test_event ;
bool do_merge = false ;
2009-12-17 21:24:25 -05:00
pr_debug ( " %s: list=%p event=%p \n " , __func__ , list , event ) ;
2014-01-21 15:48:14 -08:00
list_for_each_entry_reverse ( test_event , list , list ) {
if ( should_merge ( test_event , event ) ) {
do_merge = true ;
2009-12-17 21:24:25 -05:00
break ;
}
}
2010-07-28 10:18:37 -04:00
2014-01-21 15:48:14 -08:00
if ( ! do_merge )
2010-07-28 10:18:37 -04:00
return NULL ;
2014-01-21 15:48:14 -08:00
test_event - > mask | = event - > mask ;
return test_event ;
2009-12-17 21:24:25 -05:00
}
2009-12-17 21:24:34 -05:00
# ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
static int fanotify_get_response_from_access ( struct fsnotify_group * group ,
2014-01-21 15:48:14 -08:00
struct fanotify_event_info * event )
2009-12-17 21:24:34 -05:00
{
int ret ;
pr_debug ( " %s: group=%p event=%p \n " , __func__ , group , event ) ;
2010-11-19 10:58:07 +01:00
wait_event ( group - > fanotify_data . access_waitq , event - > response | |
atomic_read ( & group - > fanotify_data . bypass_perm ) ) ;
if ( ! event - > response ) /* bypass_perm set */
return 0 ;
2009-12-17 21:24:34 -05:00
/* userspace responded, convert to something usable */
switch ( event - > response ) {
case FAN_ALLOW :
ret = 0 ;
break ;
case FAN_DENY :
default :
ret = - EPERM ;
}
event - > response = 0 ;
2009-12-17 21:24:34 -05:00
pr_debug ( " %s: group=%p event=%p about to return ret=%d \n " , __func__ ,
group , event , ret ) ;
2009-12-17 21:24:34 -05:00
return ret ;
}
# endif
2014-01-21 15:48:15 -08:00
static bool fanotify_should_send_event ( struct fsnotify_mark * inode_mark ,
2010-07-28 10:18:39 -04:00
struct fsnotify_mark * vfsmnt_mark ,
2014-01-21 15:48:15 -08:00
u32 event_mask ,
void * data , int data_type )
2009-12-17 21:24:28 -05:00
{
2010-07-28 10:18:39 -04:00
__u32 marks_mask , marks_ignored_mask ;
2010-10-28 17:21:58 -04:00
struct path * path = data ;
2010-07-28 10:18:39 -04:00
2014-01-21 15:48:15 -08:00
pr_debug ( " %s: inode_mark=%p vfsmnt_mark=%p mask=%x data=%p "
" data_type=%d \n " , __func__ , inode_mark , vfsmnt_mark ,
event_mask , data , data_type ) ;
2010-07-28 10:18:39 -04:00
2009-12-17 21:24:28 -05:00
/* if we don't have enough info to send an event to userspace say no */
2010-08-12 14:23:04 -07:00
if ( data_type ! = FSNOTIFY_EVENT_PATH )
2009-12-17 21:24:28 -05:00
return false ;
2010-10-28 17:21:58 -04:00
/* sorry, fanotify only gives a damn about files and dirs */
if ( ! S_ISREG ( path - > dentry - > d_inode - > i_mode ) & &
! S_ISDIR ( path - > dentry - > d_inode - > i_mode ) )
return false ;
2010-07-28 10:18:39 -04:00
if ( inode_mark & & vfsmnt_mark ) {
marks_mask = ( vfsmnt_mark - > mask | inode_mark - > mask ) ;
marks_ignored_mask = ( vfsmnt_mark - > ignored_mask | inode_mark - > ignored_mask ) ;
} else if ( inode_mark ) {
/*
* if the event is for a child and this inode doesn ' t care about
* events on the child , don ' t send it !
*/
if ( ( event_mask & FS_EVENT_ON_CHILD ) & &
! ( inode_mark - > mask & FS_EVENT_ON_CHILD ) )
return false ;
marks_mask = inode_mark - > mask ;
marks_ignored_mask = inode_mark - > ignored_mask ;
} else if ( vfsmnt_mark ) {
marks_mask = vfsmnt_mark - > mask ;
marks_ignored_mask = vfsmnt_mark - > ignored_mask ;
} else {
BUG ( ) ;
}
2010-10-28 17:21:59 -04:00
if ( S_ISDIR ( path - > dentry - > d_inode - > i_mode ) & &
( marks_ignored_mask & FS_ISDIR ) )
return false ;
2010-07-28 10:18:39 -04:00
if ( event_mask & marks_mask & ~ marks_ignored_mask )
return true ;
return false ;
2009-12-17 21:24:28 -05:00
}
2014-01-21 15:48:14 -08:00
static int fanotify_handle_event ( struct fsnotify_group * group ,
struct inode * inode ,
struct fsnotify_mark * inode_mark ,
struct fsnotify_mark * fanotify_mark ,
u32 mask , void * data , int data_type ,
const unsigned char * file_name )
{
int ret = 0 ;
struct fanotify_event_info * event ;
struct fsnotify_event * fsn_event ;
struct fsnotify_event * notify_fsn_event ;
BUILD_BUG_ON ( FAN_ACCESS ! = FS_ACCESS ) ;
BUILD_BUG_ON ( FAN_MODIFY ! = FS_MODIFY ) ;
BUILD_BUG_ON ( FAN_CLOSE_NOWRITE ! = FS_CLOSE_NOWRITE ) ;
BUILD_BUG_ON ( FAN_CLOSE_WRITE ! = FS_CLOSE_WRITE ) ;
BUILD_BUG_ON ( FAN_OPEN ! = FS_OPEN ) ;
BUILD_BUG_ON ( FAN_EVENT_ON_CHILD ! = FS_EVENT_ON_CHILD ) ;
BUILD_BUG_ON ( FAN_Q_OVERFLOW ! = FS_Q_OVERFLOW ) ;
BUILD_BUG_ON ( FAN_OPEN_PERM ! = FS_OPEN_PERM ) ;
BUILD_BUG_ON ( FAN_ACCESS_PERM ! = FS_ACCESS_PERM ) ;
BUILD_BUG_ON ( FAN_ONDIR ! = FS_ISDIR ) ;
2014-01-21 15:48:15 -08:00
if ( ! fanotify_should_send_event ( inode_mark , fanotify_mark , mask , data ,
data_type ) )
return 0 ;
2014-01-21 15:48:14 -08:00
pr_debug ( " %s: group=%p inode=%p mask=%x \n " , __func__ , group , inode ,
mask ) ;
event = kmem_cache_alloc ( fanotify_event_cachep , GFP_KERNEL ) ;
if ( unlikely ( ! event ) )
return - ENOMEM ;
fsn_event = & event - > fse ;
fsnotify_init_event ( fsn_event , inode , mask ) ;
event - > tgid = get_pid ( task_tgid ( current ) ) ;
if ( data_type = = FSNOTIFY_EVENT_PATH ) {
struct path * path = data ;
event - > path = * path ;
path_get ( & event - > path ) ;
} else {
event - > path . mnt = NULL ;
event - > path . dentry = NULL ;
}
# ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
event - > response = 0 ;
# endif
notify_fsn_event = fsnotify_add_notify_event ( group , fsn_event ,
fanotify_merge ) ;
if ( notify_fsn_event ) {
/* Our event wasn't used in the end. Free it. */
fsnotify_destroy_event ( group , fsn_event ) ;
if ( IS_ERR ( notify_fsn_event ) )
return PTR_ERR ( notify_fsn_event ) ;
/* We need to ask about a different events after a merge... */
event = FANOTIFY_E ( notify_fsn_event ) ;
fsn_event = notify_fsn_event ;
}
# ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
if ( fsn_event - > mask & FAN_ALL_PERM_EVENTS )
ret = fanotify_get_response_from_access ( group , event ) ;
# endif
return ret ;
}
2010-10-28 17:21:58 -04:00
static void fanotify_free_group_priv ( struct fsnotify_group * group )
{
struct user_struct * user ;
user = group - > fanotify_data . user ;
atomic_dec ( & user - > fanotify_listeners ) ;
free_uid ( user ) ;
}
2014-01-21 15:48:14 -08:00
static void fanotify_free_event ( struct fsnotify_event * fsn_event )
{
struct fanotify_event_info * event ;
event = FANOTIFY_E ( fsn_event ) ;
path_put ( & event - > path ) ;
put_pid ( event - > tgid ) ;
kmem_cache_free ( fanotify_event_cachep , event ) ;
}
2009-12-17 21:24:25 -05:00
const struct fsnotify_ops fanotify_fsnotify_ops = {
. handle_event = fanotify_handle_event ,
2010-10-28 17:21:58 -04:00
. free_group_priv = fanotify_free_group_priv ,
2014-01-21 15:48:14 -08:00
. free_event = fanotify_free_event ,
2009-12-17 21:24:25 -05:00
} ;