2006-01-19 03:30:16 +03:00
/*
* FAM file notification support .
*
* Copyright ( c ) James Peach 2005
*
* 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 . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
# include "includes.h"
# ifdef HAVE_FAM_CHANGE_NOTIFY
# include <fam.h>
/* NOTE: There are multiple versions of FAM floating around the net, each with
* slight differences from the original SGI FAM implementation . In this file ,
* we rely only on the SGI features and do not assume any extensions . For
* example , we do not look at FAMErrno , because it is not set by the original
* implementation .
*
* Random FAM links :
* http : //oss.sgi.com/projects/fam/
* http : //savannah.nongnu.org/projects/fam/
* http : //sourceforge.net/projects/bsdfam/
*/
struct fam_req_info
{
FAMRequest req ;
int generation ;
enum FAMCodes code ;
enum
{
/* We are waiting for an event. */
FAM_REQ_MONITORING ,
/* An event has been receive, but we haven't been able to send it back
* to the client yet . It is stashed in the code member .
*/
FAM_REQ_FIRED
} state ;
} ;
/* Don't initialise this until the first register request. We want a single
* FAM connection for each worker smbd . If we allow the master ( parent ) smbd to
* open a FAM connection , multiple processes talking on the same socket will
* undoubtedly create havoc .
*/
static FAMConnection global_fc ;
static int global_fc_generation ;
# define FAM_TRACE 8
# define FAM_TRACE_LOW 10
# define FAM_EVENT_DRAIN ((uint32_t)(-1))
2006-02-13 07:07:15 +03:00
static void * fam_register_notify ( connection_struct * conn ,
char * path ,
uint32 flags ) ;
static BOOL fam_check_notify ( connection_struct * conn ,
uint16_t vuid ,
char * path ,
uint32_t flags ,
void * data ,
time_t when ) ;
2006-02-13 07:29:42 +03:00
static void fam_remove_notify ( void * data ) ;
2006-02-13 07:07:15 +03:00
static struct cnotify_fns global_fam_notify =
{
fam_register_notify ,
fam_check_notify ,
fam_remove_notify ,
- 1 ,
- 1
} ;
2006-01-19 03:30:16 +03:00
/* Turn a FAM event code into a string. Don't rely on specific code values,
* because that might not work across all flavours of FAM .
*/
static const char *
fam_event_str ( enum FAMCodes code )
{
static struct { enum FAMCodes code ; const char * name ; } evstr [ ] =
{
{ FAMChanged , " FAMChanged " } ,
{ FAMDeleted , " FAMDeleted " } ,
{ FAMStartExecuting , " FAMStartExecuting " } ,
{ FAMStopExecuting , " FAMStopExecuting " } ,
{ FAMCreated , " FAMCreated " } ,
{ FAMMoved , " FAMMoved " } ,
{ FAMAcknowledge , " FAMAcknowledge " } ,
{ FAMExists , " FAMExists " } ,
{ FAMEndExist , " FAMEndExist " }
} ;
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( evstr ) ; + + i ) {
if ( code = = evstr [ i ] . code )
return ( evstr [ i ] . name ) ;
}
return ( " <unknown> " ) ;
}
static BOOL
fam_check_reconnect ( void )
{
if ( FAMCONNECTION_GETFD ( & global_fc ) < 0 ) {
fstring name ;
global_fc_generation + + ;
snprintf ( name , sizeof ( name ) , " smbd (%lu) " , ( unsigned long ) sys_getpid ( ) ) ;
if ( FAMOpen2 ( & global_fc , name ) < 0 ) {
DEBUG ( 0 , ( " failed to connect to FAM service \n " ) ) ;
return ( False ) ;
}
}
2006-02-13 07:07:15 +03:00
global_fam_notify . notification_fd = FAMCONNECTION_GETFD ( & global_fc ) ;
2006-01-19 03:30:16 +03:00
return ( True ) ;
}
static BOOL
fam_monitor_path ( connection_struct * conn ,
struct fam_req_info * info ,
const char * path ,
uint32 flags )
{
SMB_STRUCT_STAT st ;
pstring fullpath ;
DEBUG ( FAM_TRACE , ( " requesting FAM notifications for '%s' \n " , path ) ) ;
/* FAM needs an absolute pathname. */
/* It would be better to use reduce_name() here, but reduce_name does not
* actually return the reduced result . How utterly un - useful .
*/
pstrcpy ( fullpath , path ) ;
if ( ! canonicalize_path ( conn , fullpath ) ) {
DEBUG ( 0 , ( " failed to canonicalize path '%s' \n " , path ) ) ;
return ( False ) ;
}
if ( * fullpath ! = ' / ' ) {
DEBUG ( 0 , ( " canonicalized path '%s' into `%s` \n " , path , fullpath ) ) ;
DEBUGADD ( 0 , ( " but expected an absolute path \n " ) ) ;
return ( False ) ;
}
if ( SMB_VFS_STAT ( conn , path , & st ) < 0 ) {
DEBUG ( 0 , ( " stat of '%s' failed: %s \n " , path , strerror ( errno ) ) ) ;
return ( False ) ;
}
/* Start monitoring this file or directory. We hand the state structure to
* both the caller and the FAM library so we can match up the caller ' s
* status requests with FAM notifications .
*/
if ( S_ISDIR ( st . st_mode ) ) {
FAMMonitorDirectory ( & global_fc , fullpath , & ( info - > req ) , info ) ;
} else {
FAMMonitorFile ( & global_fc , fullpath , & ( info - > req ) , info ) ;
}
/* Grr. On IRIX, neither of the monitor functions return a status. */
/* We will stay in initialising state until we see the FAMendExist message
* for this file .
*/
info - > state = FAM_REQ_MONITORING ;
info - > generation = global_fc_generation ;
return ( True ) ;
}
static BOOL
fam_handle_event ( enum FAMCodes code , uint32 flags )
{
# define F_CHANGE_MASK (FILE_NOTIFY_CHANGE_FILE | \
FILE_NOTIFY_CHANGE_ATTRIBUTES | \
FILE_NOTIFY_CHANGE_SIZE | \
FILE_NOTIFY_CHANGE_LAST_WRITE | \
FILE_NOTIFY_CHANGE_LAST_ACCESS | \
FILE_NOTIFY_CHANGE_CREATION | \
FILE_NOTIFY_CHANGE_EA | \
FILE_NOTIFY_CHANGE_SECURITY )
# define F_DELETE_MASK (FILE_NOTIFY_CHANGE_FILE_NAME | \
FILE_NOTIFY_CHANGE_DIR_NAME )
# define F_CREATE_MASK (FILE_NOTIFY_CHANGE_FILE_NAME | \
FILE_NOTIFY_CHANGE_DIR_NAME )
switch ( code ) {
case FAMChanged :
if ( flags & F_CHANGE_MASK )
return ( True ) ;
break ;
case FAMDeleted :
if ( flags & F_DELETE_MASK )
return ( True ) ;
break ;
case FAMCreated :
if ( flags & F_CREATE_MASK )
return ( True ) ;
break ;
default :
/* Ignore anything else. */
break ;
}
return ( False ) ;
# undef F_CHANGE_MASK
# undef F_DELETE_MASK
# undef F_CREATE_MASK
}
static BOOL
fam_pump_events ( struct fam_req_info * info , uint32_t flags )
{
FAMEvent ev ;
for ( ; ; ) {
/* If we are draining the event queue we must keep going until we find
* the correct FAMAcknowledge event or the connection drops . Otherwise
* we should stop when there are no more events pending .
*/
if ( flags ! = FAM_EVENT_DRAIN & & ! FAMPending ( & global_fc ) ) {
break ;
}
if ( FAMNextEvent ( & global_fc , & ev ) < 0 ) {
DEBUG ( 0 , ( " failed to fetch pending FAM event \n " ) ) ;
DEBUGADD ( 0 , ( " resetting FAM connection \n " ) ) ;
FAMClose ( & global_fc ) ;
FAMCONNECTION_GETFD ( & global_fc ) = - 1 ;
return ( False ) ;
}
DEBUG ( FAM_TRACE_LOW , ( " FAM event %s on '%s' for request %d \n " ,
fam_event_str ( ev . code ) , ev . filename , ev . fr . reqnum ) ) ;
switch ( ev . code ) {
case FAMAcknowledge :
/* FAM generates an ACK event when we cancel a monitor. We need
* this to know when it is safe to free out request state
* structure .
*/
if ( info - > generation = = global_fc_generation & &
info - > req . reqnum = = ev . fr . reqnum & &
flags = = FAM_EVENT_DRAIN ) {
return ( True ) ;
}
case FAMEndExist :
case FAMExists :
/* Ignore these. FAM sends these enumeration events when we
* start monitoring . If we are monitoring a directory , we will
* get a FAMExists event for each directory entry .
*/
/* TODO: we might be able to use these to implement recursive
* monitoring of entire subtrees .
*/
case FAMMoved :
/* These events never happen. A move or rename shows up as a
* create / delete pair .
*/
case FAMStartExecuting :
case FAMStopExecuting :
/* We might get these, but we just don't care. */
break ;
case FAMChanged :
case FAMDeleted :
case FAMCreated :
if ( info - > generation ! = global_fc_generation ) {
/* Ignore this; the req number can't be matched. */
break ;
}
if ( info - > req . reqnum = = ev . fr . reqnum ) {
/* This is the event the caller was interested in. */
DEBUG ( FAM_TRACE , ( " handling FAM %s event on '%s' \n " ,
fam_event_str ( ev . code ) , ev . filename ) ) ;
/* Ignore events if we are draining this request. */
if ( flags ! = FAM_EVENT_DRAIN ) {
return ( fam_handle_event ( ev . code , flags ) ) ;
}
break ;
} else {
/* Caller doesn't want this event. Stash the result so we
* can come back to it . Unfortunately , FAM doesn ' t
* guarantee to give us back evinfo .
*/
struct fam_req_info * evinfo =
( struct fam_req_info * ) ev . userdata ;
if ( evinfo ) {
DEBUG ( FAM_TRACE , ( " storing FAM %s event for winter \n " ,
fam_event_str ( ev . code ) ) ) ;
evinfo - > state = FAM_REQ_FIRED ;
evinfo - > code = ev . code ;
} else {
DEBUG ( 2 , ( " received FAM %s notification for %s, "
" but userdata was unexpectedly NULL \n " ,
fam_event_str ( ev . code ) , ev . filename ) ) ;
}
break ;
}
default :
DEBUG ( 0 , ( " ignoring unknown FAM event code %d for `%s` \n " ,
ev . code , ev . filename ) ) ;
}
}
/* No more notifications pending. */
return ( False ) ;
}
static BOOL
fam_test_connection ( void )
{
FAMConnection fc ;
/* On IRIX FAMOpen2 leaks 960 bytes in 48 blocks. It's a deliberate leak
* in the library and there ' s nothing we can do about it here .
*/
if ( FAMOpen2 ( & fc , " smbd probe " ) < 0 )
return ( False ) ;
FAMClose ( & fc ) ;
return ( True ) ;
}
/* ------------------------------------------------------------------------- */
static void *
fam_register_notify ( connection_struct * conn ,
char * path ,
uint32 flags )
{
struct fam_req_info * info ;
if ( ! fam_check_reconnect ( ) ) {
return ( False ) ;
}
if ( ( info = SMB_MALLOC_P ( struct fam_req_info ) ) = = NULL ) {
DEBUG ( 0 , ( " malloc of %d bytes failed \n " , sizeof ( struct fam_req_info ) ) ) ;
return ( NULL ) ;
}
if ( fam_monitor_path ( conn , info , path , flags ) ) {
return ( info ) ;
} else {
SAFE_FREE ( info ) ;
return ( NULL ) ;
}
}
static BOOL
fam_check_notify ( connection_struct * conn ,
uint16_t vuid ,
char * path ,
uint32_t flags ,
void * data ,
time_t when )
{
struct fam_req_info * info ;
info = ( struct fam_req_info * ) data ;
SMB_ASSERT ( info ! = NULL ) ;
DEBUG ( 10 , ( " checking FAM events for `%s` \n " , path ) ) ;
if ( info - > state = = FAM_REQ_FIRED ) {
DEBUG ( FAM_TRACE , ( " handling previously fired FAM %s event \n " ,
fam_event_str ( info - > code ) ) ) ;
info - > state = FAM_REQ_MONITORING ;
return ( fam_handle_event ( info - > code , flags ) ) ;
}
if ( ! fam_check_reconnect ( ) ) {
return ( False ) ;
}
if ( info - > generation ! = global_fc_generation ) {
DEBUG ( FAM_TRACE , ( " reapplying stale FAM monitor to %s \n " , path ) ) ;
fam_monitor_path ( conn , info , path , flags ) ;
return ( False ) ;
}
return ( fam_pump_events ( info , flags ) ) ;
}
static void
fam_remove_notify ( void * data )
{
struct fam_req_info * info ;
if ( ( info = ( struct fam_req_info * ) data ) = = NULL )
return ;
/* No need to reconnect. If the FAM connection is gone, there's no need to
* cancel and we can safely let FAMCancelMonitor fail . If it we
* reconnected , then the generation check will stop us cancelling the wrong
* request .
*/
if ( info - > generation = = global_fc_generation ) {
DEBUG ( FAM_TRACE , ( " removing FAM notification for request %d \n " ,
info - > req . reqnum ) ) ;
FAMCancelMonitor ( & global_fc , & ( info - > req ) ) ;
/* Soak up all events until the FAMAcknowledge. We can't free
* our request state until we are sure there are no more events in
* flight .
*/
fam_pump_events ( info , FAM_EVENT_DRAIN ) ;
}
SAFE_FREE ( info ) ;
}
struct cnotify_fns * fam_notify_init ( void )
{
FAMCONNECTION_GETFD ( & global_fc ) = - 1 ;
if ( ! fam_test_connection ( ) ) {
DEBUG ( 0 , ( " FAM file change notifications not available \n " ) ) ;
return ( NULL ) ;
}
DEBUG ( FAM_TRACE , ( " enabling FAM change notifications \n " ) ) ;
return & global_fam_notify ;
}
# endif /* HAVE_FAM_CHANGE_NOTIFY */