2006-04-03 10:46:55 +04:00
/*
Unix SMB / CIFS implementation .
Copyright ( C ) Andrew Tridgell 2006
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 of the License , 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 ; if not , write to the Free Software
Foundation , Inc . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*/
/*
notify implementation using inotify
*/
# include "includes.h"
# include "system/filesys.h"
# include "ntvfs/sysdep/sys_notify.h"
# include "lib/events/events.h"
2006-08-30 15:29:34 +04:00
# include "lib/util/dlinklist.h"
# include "libcli/raw/smb.h"
2006-04-03 10:46:55 +04:00
# include <linux/inotify.h>
# include <asm/unistd.h>
# ifndef HAVE_INOTIFY_INIT
/*
glibc doesn ' t define these functions yet ( as of March 2006 )
*/
static int inotify_init ( void )
{
return syscall ( __NR_inotify_init ) ;
}
static int inotify_add_watch ( int fd , const char * path , __u32 mask )
{
return syscall ( __NR_inotify_add_watch , fd , path , mask ) ;
}
static int inotify_rm_watch ( int fd , int wd )
{
return syscall ( __NR_inotify_rm_watch , fd , wd ) ;
}
# endif
/* older glibc headers don't have these defines either */
# ifndef IN_ONLYDIR
# define IN_ONLYDIR 0x01000000
# endif
# ifndef IN_MASK_ADD
# define IN_MASK_ADD 0x20000000
# endif
struct inotify_private {
struct sys_notify_context * ctx ;
int fd ;
struct watch_context * watches ;
} ;
struct watch_context {
struct watch_context * next , * prev ;
struct inotify_private * in ;
int wd ;
sys_notify_callback_t callback ;
void * private ;
2006-04-06 14:07:13 +04:00
uint32_t mask ; /* the inotify mask */
uint32_t filter ; /* the windows completion filter */
const char * path ;
2006-04-03 10:46:55 +04:00
} ;
/*
destroy the inotify private context
*/
2006-05-24 11:35:06 +04:00
static int inotify_destructor ( struct inotify_private * in )
2006-04-03 10:46:55 +04:00
{
close ( in - > fd ) ;
return 0 ;
}
2006-04-06 14:07:13 +04:00
/*
see if a particular event from inotify really does match a requested
notify event in SMB
*/
static BOOL filter_match ( struct watch_context * w , struct inotify_event * e )
{
if ( ( e - > mask & w - > mask ) = = 0 ) {
/* this happens because inotify_add_watch() coalesces watches on the same
path , oring their masks together */
return False ;
}
/* SMB separates the filters for files and directories */
if ( e - > mask & IN_ISDIR ) {
if ( ( w - > filter & FILE_NOTIFY_CHANGE_DIR_NAME ) = = 0 ) {
return False ;
}
} else {
if ( ( e - > mask & IN_ATTRIB ) & &
( w - > filter & ( FILE_NOTIFY_CHANGE_ATTRIBUTES |
FILE_NOTIFY_CHANGE_LAST_WRITE |
FILE_NOTIFY_CHANGE_LAST_ACCESS |
FILE_NOTIFY_CHANGE_EA |
FILE_NOTIFY_CHANGE_SECURITY ) ) ) {
return True ;
}
2006-04-06 15:09:24 +04:00
if ( ( e - > mask & IN_MODIFY ) & &
( w - > filter & FILE_NOTIFY_CHANGE_ATTRIBUTES ) ) {
return True ;
}
2006-04-06 14:07:13 +04:00
if ( ( w - > filter & FILE_NOTIFY_CHANGE_FILE_NAME ) = = 0 ) {
return False ;
}
}
return True ;
}
2006-04-03 10:46:55 +04:00
/*
dispatch one inotify event
2006-04-05 12:52:55 +04:00
the cookies are used to correctly handle renames
2006-04-03 10:46:55 +04:00
*/
2006-04-05 12:52:55 +04:00
static void inotify_dispatch ( struct inotify_private * in ,
struct inotify_event * e ,
uint32_t prev_cookie ,
2006-04-06 14:07:13 +04:00
struct inotify_event * e2 )
2006-04-03 10:46:55 +04:00
{
2006-04-06 14:07:13 +04:00
struct watch_context * w , * next ;
2006-04-03 10:46:55 +04:00
struct notify_event ne ;
/* ignore extraneous events, such as unmount and IN_IGNORED events */
2006-04-06 14:07:13 +04:00
if ( ( e - > mask & ( IN_ATTRIB | IN_MODIFY | IN_CREATE | IN_DELETE |
IN_MOVED_FROM | IN_MOVED_TO ) ) = = 0 ) {
2006-04-03 10:46:55 +04:00
return ;
}
2006-04-05 12:52:55 +04:00
/* map the inotify mask to a action. This gets complicated for
renames */
2006-04-03 10:46:55 +04:00
if ( e - > mask & IN_CREATE ) {
ne . action = NOTIFY_ACTION_ADDED ;
} else if ( e - > mask & IN_DELETE ) {
ne . action = NOTIFY_ACTION_REMOVED ;
2006-04-05 12:52:55 +04:00
} else if ( e - > mask & IN_MOVED_FROM ) {
2006-04-06 14:07:13 +04:00
if ( e2 ! = NULL & & e2 - > cookie = = e - > cookie ) {
2006-04-05 12:52:55 +04:00
ne . action = NOTIFY_ACTION_OLD_NAME ;
} else {
ne . action = NOTIFY_ACTION_REMOVED ;
}
} else if ( e - > mask & IN_MOVED_TO ) {
if ( e - > cookie = = prev_cookie ) {
ne . action = NOTIFY_ACTION_NEW_NAME ;
} else {
ne . action = NOTIFY_ACTION_ADDED ;
}
2006-04-03 10:46:55 +04:00
} else {
ne . action = NOTIFY_ACTION_MODIFIED ;
}
ne . path = e - > name ;
/* find any watches that have this watch descriptor */
2006-04-06 14:07:13 +04:00
for ( w = in - > watches ; w ; w = next ) {
next = w - > next ;
if ( w - > wd = = e - > wd & & filter_match ( w , e ) ) {
w - > callback ( in - > ctx , w - > private , & ne ) ;
}
}
/* SMB expects a file rename to generate three events, two for
the rename and the other for a modify of the
destination . Strange ! */
if ( ne . action ! = NOTIFY_ACTION_NEW_NAME | |
( e - > mask & IN_ISDIR ) ! = 0 ) {
return ;
}
ne . action = NOTIFY_ACTION_MODIFIED ;
e - > mask = IN_ATTRIB ;
for ( w = in - > watches ; w ; w = next ) {
next = w - > next ;
if ( w - > wd = = e - > wd & & filter_match ( w , e ) & &
! ( w - > filter & FILE_NOTIFY_CHANGE_CREATION ) ) {
2006-04-03 10:46:55 +04:00
w - > callback ( in - > ctx , w - > private , & ne ) ;
}
}
}
/*
called when the kernel has some events for us
*/
static void inotify_handler ( struct event_context * ev , struct fd_event * fde ,
uint16_t flags , void * private )
{
struct inotify_private * in = talloc_get_type ( private , struct inotify_private ) ;
int bufsize = 0 ;
struct inotify_event * e0 , * e ;
2006-04-05 12:52:55 +04:00
uint32_t prev_cookie = 0 ;
2006-04-03 10:46:55 +04:00
/*
we must use FIONREAD as we cannot predict the length of the
filenames , and thus can ' t know how much to allocate
otherwise
*/
if ( ioctl ( in - > fd , FIONREAD , & bufsize ) ! = 0 | |
bufsize = = 0 ) {
DEBUG ( 0 , ( " No data on inotify fd?! \n " ) ) ;
return ;
}
e0 = e = talloc_size ( in , bufsize ) ;
if ( e = = NULL ) return ;
if ( read ( in - > fd , e0 , bufsize ) ! = bufsize ) {
DEBUG ( 0 , ( " Failed to read all inotify data \n " ) ) ;
talloc_free ( e0 ) ;
return ;
}
/* we can get more than one event in the buffer */
while ( bufsize > = sizeof ( * e ) ) {
2006-04-05 12:52:55 +04:00
struct inotify_event * e2 = NULL ;
2006-04-03 10:46:55 +04:00
bufsize - = e - > len + sizeof ( * e ) ;
2006-04-05 12:52:55 +04:00
if ( bufsize > = sizeof ( * e ) ) {
e2 = ( struct inotify_event * ) ( e - > len + sizeof ( * e ) + ( char * ) e ) ;
}
2006-04-06 14:07:13 +04:00
inotify_dispatch ( in , e , prev_cookie , e2 ) ;
2006-04-05 12:52:55 +04:00
prev_cookie = e - > cookie ;
e = e2 ;
2006-04-03 10:46:55 +04:00
}
talloc_free ( e0 ) ;
}
/*
setup the inotify handle - called the first time a watch is added on
this context
*/
static NTSTATUS inotify_setup ( struct sys_notify_context * ctx )
{
struct inotify_private * in ;
in = talloc ( ctx , struct inotify_private ) ;
NT_STATUS_HAVE_NO_MEMORY ( in ) ;
in - > fd = inotify_init ( ) ;
if ( in - > fd = = - 1 ) {
DEBUG ( 0 , ( " Failed to init inotify - %s \n " , strerror ( errno ) ) ) ;
talloc_free ( in ) ;
return map_nt_error_from_unix ( errno ) ;
}
in - > ctx = ctx ;
in - > watches = NULL ;
ctx - > private = in ;
talloc_set_destructor ( in , inotify_destructor ) ;
/* add a event waiting for the inotify fd to be readable */
event_add_fd ( ctx - > ev , in , in - > fd , EVENT_FD_READ , inotify_handler , in ) ;
return NT_STATUS_OK ;
}
/*
2006-04-05 09:54:10 +04:00
map from a change notify mask to a inotify mask . Remove any bits
which we can handle
2006-04-03 10:46:55 +04:00
*/
static const struct {
uint32_t notify_mask ;
uint32_t inotify_mask ;
} inotify_mapping [ ] = {
2006-04-06 14:07:13 +04:00
{ FILE_NOTIFY_CHANGE_FILE_NAME , IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO } ,
{ FILE_NOTIFY_CHANGE_DIR_NAME , IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO } ,
2006-04-06 15:09:24 +04:00
{ FILE_NOTIFY_CHANGE_ATTRIBUTES , IN_ATTRIB | IN_MOVED_TO | IN_MOVED_FROM | IN_MODIFY } ,
2006-04-06 14:07:13 +04:00
{ FILE_NOTIFY_CHANGE_LAST_WRITE , IN_ATTRIB } ,
{ FILE_NOTIFY_CHANGE_LAST_ACCESS , IN_ATTRIB } ,
{ FILE_NOTIFY_CHANGE_EA , IN_ATTRIB } ,
{ FILE_NOTIFY_CHANGE_SECURITY , IN_ATTRIB }
2006-04-03 10:46:55 +04:00
} ;
2006-04-05 09:54:10 +04:00
static uint32_t inotify_map ( struct notify_entry * e )
2006-04-03 10:46:55 +04:00
{
int i ;
uint32_t out = 0 ;
for ( i = 0 ; i < ARRAY_SIZE ( inotify_mapping ) ; i + + ) {
2006-04-05 09:54:10 +04:00
if ( inotify_mapping [ i ] . notify_mask & e - > filter ) {
2006-04-03 10:46:55 +04:00
out | = inotify_mapping [ i ] . inotify_mask ;
2006-04-05 09:54:10 +04:00
e - > filter & = ~ inotify_mapping [ i ] . notify_mask ;
2006-04-03 10:46:55 +04:00
}
}
return out ;
}
/*
destroy a watch
*/
2006-05-24 11:35:06 +04:00
static int watch_destructor ( struct watch_context * w )
2006-04-03 10:46:55 +04:00
{
struct inotify_private * in = w - > in ;
int wd = w - > wd ;
DLIST_REMOVE ( w - > in - > watches , w ) ;
/* only rm the watch if its the last one with this wd */
for ( w = in - > watches ; w ; w = w - > next ) {
if ( w - > wd = = wd ) break ;
}
if ( w = = NULL ) {
inotify_rm_watch ( in - > fd , wd ) ;
}
return 0 ;
}
/*
add a watch . The watch is removed when the caller calls
2006-04-05 08:50:08 +04:00
talloc_free ( ) on * handle
2006-04-03 10:46:55 +04:00
*/
2006-04-05 09:54:10 +04:00
static NTSTATUS inotify_watch ( struct sys_notify_context * ctx , struct notify_entry * e ,
sys_notify_callback_t callback , void * private ,
void * * handle )
2006-04-03 10:46:55 +04:00
{
struct inotify_private * in ;
int wd ;
uint32_t mask ;
struct watch_context * w ;
2006-04-05 09:54:10 +04:00
uint32_t filter = e - > filter ;
2006-04-03 10:46:55 +04:00
/* maybe setup the inotify fd */
if ( ctx - > private = = NULL ) {
NTSTATUS status ;
status = inotify_setup ( ctx ) ;
NT_STATUS_NOT_OK_RETURN ( status ) ;
}
in = talloc_get_type ( ctx - > private , struct inotify_private ) ;
2006-04-05 09:54:10 +04:00
mask = inotify_map ( e ) ;
2006-04-03 10:46:55 +04:00
if ( mask = = 0 ) {
/* this filter can't be handled by inotify */
return NT_STATUS_INVALID_PARAMETER ;
}
/* using IN_MASK_ADD allows us to cope with inotify() returning the same
watch descriptor for muliple watches on the same path */
mask | = ( IN_MASK_ADD | IN_ONLYDIR ) ;
/* get a new watch descriptor for this path */
2006-04-05 09:54:10 +04:00
wd = inotify_add_watch ( in - > fd , e - > path , mask ) ;
2006-04-03 10:46:55 +04:00
if ( wd = = - 1 ) {
2006-04-05 09:54:10 +04:00
e - > filter = filter ;
2006-04-03 10:46:55 +04:00
return map_nt_error_from_unix ( errno ) ;
}
w = talloc ( in , struct watch_context ) ;
if ( w = = NULL ) {
inotify_rm_watch ( in - > fd , wd ) ;
2006-04-05 09:54:10 +04:00
e - > filter = filter ;
2006-04-03 10:46:55 +04:00
return NT_STATUS_NO_MEMORY ;
}
w - > in = in ;
w - > wd = wd ;
w - > callback = callback ;
w - > private = private ;
w - > mask = mask ;
2006-04-06 14:07:13 +04:00
w - > filter = filter ;
w - > path = talloc_strdup ( w , e - > path ) ;
if ( w - > path = = NULL ) {
inotify_rm_watch ( in - > fd , wd ) ;
e - > filter = filter ;
return NT_STATUS_NO_MEMORY ;
}
2006-04-03 10:46:55 +04:00
( * handle ) = w ;
DLIST_ADD ( in - > watches , w ) ;
/* the caller frees the handle to stop watching */
talloc_set_destructor ( w , watch_destructor ) ;
return NT_STATUS_OK ;
}
static struct sys_notify_backend inotify = {
. name = " inotify " ,
. notify_watch = inotify_watch
} ;
/*
initialialise the inotify module
*/
2006-04-07 17:15:46 +04:00
NTSTATUS sys_notify_inotify_init ( void )
2006-04-03 10:46:55 +04:00
{
/* register ourselves as a system inotify module */
return sys_notify_register ( & inotify ) ;
}