diff options
author | James Peach <jpeach@samba.org> | 2006-01-19 00:30:16 +0000 |
---|---|---|
committer | Gerald (Jerry) Carter <jerry@samba.org> | 2007-10-10 11:06:11 -0500 |
commit | 543ff192b37feff91fd77c336177cb628bd1e08a (patch) | |
tree | 6c7a9826fd6bd0d285d18223e61a255ee6eb0b27 | |
parent | 7b575d7cc513b4da8734f6f06694e7c426cfd41c (diff) | |
download | samba-543ff192b37feff91fd77c336177cb628bd1e08a.tar.gz samba-543ff192b37feff91fd77c336177cb628bd1e08a.tar.bz2 samba-543ff192b37feff91fd77c336177cb628bd1e08a.zip |
r13027: Support file change notifications from FAM.
(This used to be commit 652b511ff24ce41c7745a0e00363e619e5027828)
-rw-r--r-- | source3/Makefile.in | 6 | ||||
-rw-r--r-- | source3/configure.in | 33 | ||||
-rw-r--r-- | source3/param/loadparm.c | 4 | ||||
-rw-r--r-- | source3/smbd/notify.c | 8 | ||||
-rw-r--r-- | source3/smbd/notify_fam.c | 446 |
5 files changed, 491 insertions, 6 deletions
diff --git a/source3/Makefile.in b/source3/Makefile.in index 58d0bb46a1..15d22d8fa5 100644 --- a/source3/Makefile.in +++ b/source3/Makefile.in @@ -347,7 +347,7 @@ PROFILES_OBJ = utils/profiles.o \ OPLOCK_OBJ = smbd/oplock.o smbd/oplock_irix.o smbd/oplock_linux.o -NOTIFY_OBJ = smbd/notify.o smbd/notify_hash.o smbd/notify_kernel.o +NOTIFY_OBJ = smbd/notify.o smbd/notify_hash.o smbd/notify_kernel.o smbd/notify_fam.o VFS_AUDIT_OBJ = modules/vfs_audit.o VFS_EXTD_AUDIT_OBJ = modules/vfs_extd_audit.o @@ -877,7 +877,7 @@ bin/smbd@EXEEXT@: $(SMBD_OBJ) @BUILD_POPT@ bin/.dummy @echo Linking $@ @$(CC) $(FLAGS) @PIE_LDFLAGS@ -o $@ $(SMBD_OBJ) $(LDFLAGS) $(LDAP_LIBS) \ $(KRB5LIBS) $(DYNEXP) $(PRINT_LIBS) $(AUTH_LIBS) \ - $(ACL_LIBS) $(PASSDB_LIBS) $(LIBS) @POPTLIBS@ + $(ACL_LIBS) $(PASSDB_LIBS) $(LIBS) @POPTLIBS@ @SMBD_LIBS@ bin/nmbd@EXEEXT@: $(NMBD_OBJ) @BUILD_POPT@ bin/.dummy @echo Linking $@ @@ -1008,7 +1008,7 @@ bin/nsstest@EXEEXT@: $(NSSTEST_OBJ) bin/.dummy bin/vfstest@EXEEXT@: $(VFSTEST_OBJ) @BUILD_POPT@ bin/.dummy @echo Linking $@ - @$(CC) $(FLAGS) @PIE_LDFLAGS@ -o $@ $(VFSTEST_OBJ) $(LDFLAGS) $(TERMLDFLAGS) $(TERMLIBS) $(DYNEXP) $(PRINT_LIBS) $(AUTH_LIBS) $(ACL_LIBS) $(LIBS) @POPTLIBS@ $(KRB5LIBS) $(LDAP_LIBS) + @$(CC) $(FLAGS) @PIE_LDFLAGS@ -o $@ $(VFSTEST_OBJ) $(LDFLAGS) $(TERMLDFLAGS) $(TERMLIBS) $(DYNEXP) $(PRINT_LIBS) $(AUTH_LIBS) $(ACL_LIBS) $(LIBS) @POPTLIBS@ $(KRB5LIBS) $(LDAP_LIBS) @SMBD_LIBS@ bin/smbiconv@EXEEXT@: $(SMBICONV_OBJ) @BUILD_POPT@ bin/.dummy @echo Linking $@ diff --git a/source3/configure.in b/source3/configure.in index 147038a930..44517ae6ea 100644 --- a/source3/configure.in +++ b/source3/configure.in @@ -2174,6 +2174,33 @@ if test x"$samba_cv_HAVE_KERNEL_CHANGE_NOTIFY" = x"yes"; then AC_DEFINE(HAVE_KERNEL_CHANGE_NOTIFY,1,[Whether kernel notifies changes]) fi +################################################# +# Check if FAM notifications are available. For FAM info, see +# http://oss.sgi.com/projects/fam/ +# http://savannah.nongnu.org/projects/fam/ + +AC_CHECK_HEADERS(fam.h, [samba_cv_HAVE_FAM_H=yes], [samba_cv_HAVE_FAM_H=no]) +if test x"$samba_cv_HAVE_FAM_H" = x"yes"; then + # On IRIX, libfam requires libC, but other FAM implementations might not + # need it. + AC_CHECK_LIB(fam, FAMOpen2, + [samba_cv_HAVE_LIBFAM=yes; samba_fam_libs="-lfam"], + [samba_cv_HAVE_LIBFAM=no]) + + if test x"$samba_cv_HAVE_LIBFAM" = x"no" ; then + samba_fam_xtra=-lC + AC_CHECK_LIB_EXT(fam, samba_fam_xtra, FAMOpen2, + [samba_cv_HAVE_LIBFAM=yes; samba_fam_libs="-lfam -lC"], + [samba_cv_HAVE_LIBFAM=no]) + unset samba_fam_xtra + fi +fi + +if test x"$samba_cv_HAVE_LIBFAM" = x"yes" ; then + AC_DEFINE(HAVE_FAM_CHANGE_NOTIFY, 1, + [Whether FAM is file notifications are available]) +fi + AC_CACHE_CHECK([for kernel share modes],samba_cv_HAVE_KERNEL_SHARE_MODES,[ AC_TRY_RUN([ #include <sys/types.h> @@ -2194,8 +2221,6 @@ if test x"$samba_cv_HAVE_KERNEL_SHARE_MODES" = x"yes"; then fi - - AC_CACHE_CHECK([for IRIX kernel oplock type definitions],samba_cv_HAVE_KERNEL_OPLOCKS_IRIX,[ AC_TRY_COMPILE([#include <sys/types.h> #include <fcntl.h>], @@ -5231,6 +5256,10 @@ AC_TRY_RUN([#include "${srcdir-.}/tests/summary.c"], builddir=`pwd` AC_SUBST(builddir) +# Stuff the FAM libraries at the end of the smbd link path (if we have them). +SMBD_LIBS="$samba_fam_libs" +AC_SUBST(SMBD_LIBS) + dnl Remove -L/usr/lib/? from LDFLAGS and LIBS LIB_REMOVE_USR_LIB(LDFLAGS) LIB_REMOVE_USR_LIB(LIBS) diff --git a/source3/param/loadparm.c b/source3/param/loadparm.c index 526bce9b60..83a5b2fc3c 100644 --- a/source3/param/loadparm.c +++ b/source3/param/loadparm.c @@ -294,6 +294,7 @@ typedef struct BOOL bUnixExtensions; BOOL bDisableNetbios; BOOL bKernelChangeNotify; + BOOL bFamChangeNotify; BOOL bUseKerberosKeytab; BOOL bDeferSharingViolations; BOOL bEnablePrivileges; @@ -992,6 +993,7 @@ static struct parm_struct parm_table[] = { {"getwd cache", P_BOOL, P_GLOBAL, &use_getwd_cache, NULL, NULL, FLAG_ADVANCED}, {"keepalive", P_INTEGER, P_GLOBAL, &keepalive, NULL, NULL, FLAG_ADVANCED}, {"kernel change notify", P_BOOL, P_GLOBAL, &Globals.bKernelChangeNotify, NULL, NULL, FLAG_ADVANCED}, + {"fam change notify", P_BOOL, P_GLOBAL, &Globals.bFamChangeNotify, NULL, NULL, FLAG_ADVANCED}, {"lpq cache time", P_INTEGER, P_GLOBAL, &Globals.lpqcachetime, NULL, NULL, FLAG_ADVANCED}, {"max smbd processes", P_INTEGER, P_GLOBAL, &Globals.iMaxSmbdProcesses, NULL, NULL, FLAG_ADVANCED}, @@ -1486,6 +1488,7 @@ static void init_globals(void) Globals.machine_password_timeout = 60 * 60 * 24 * 7; /* 7 days default. */ Globals.change_notify_timeout = 60; /* 1 minute default. */ Globals.bKernelChangeNotify = True; /* On if we have it. */ + Globals.bFamChangeNotify = True; /* On if we have it. */ Globals.lm_announce = 2; /* = Auto: send only if LM clients found */ Globals.lm_interval = 60; Globals.announce_as = ANNOUNCE_AS_NT_SERVER; @@ -1868,6 +1871,7 @@ FN_GLOBAL_BOOL(lp_use_spnego, &Globals.bUseSpnego) FN_GLOBAL_BOOL(lp_client_use_spnego, &Globals.bClientUseSpnego) FN_GLOBAL_BOOL(lp_hostname_lookups, &Globals.bHostnameLookups) FN_GLOBAL_BOOL(lp_kernel_change_notify, &Globals.bKernelChangeNotify) +FN_GLOBAL_BOOL(lp_fam_change_notify, &Globals.bFamChangeNotify) FN_GLOBAL_BOOL(lp_use_kerberos_keytab, &Globals.bUseKerberosKeytab) FN_GLOBAL_BOOL(lp_defer_sharing_violations, &Globals.bDeferSharingViolations) FN_GLOBAL_BOOL(lp_enable_privileges, &Globals.bEnablePrivileges) diff --git a/source3/smbd/notify.c b/source3/smbd/notify.c index bc76cfb322..df3d45d20b 100644 --- a/source3/smbd/notify.c +++ b/source3/smbd/notify.c @@ -212,10 +212,16 @@ BOOL change_notify_set(char *inbuf, files_struct *fsp, connection_struct *conn, BOOL init_change_notify(void) { + cnotify = NULL; + #if HAVE_KERNEL_CHANGE_NOTIFY - if (lp_kernel_change_notify()) + if (cnotify == NULL && lp_kernel_change_notify()) cnotify = kernel_notify_init(); #endif +#if HAVE_FAM_CHANGE_NOTIFY + if (cnotify == NULL && lp_fam_change_notify()) + cnotify = fam_notify_init(); +#endif if (!cnotify) cnotify = hash_notify_init(); if (!cnotify) { diff --git a/source3/smbd/notify_fam.c b/source3/smbd/notify_fam.c new file mode 100644 index 0000000000..413340266e --- /dev/null +++ b/source3/smbd/notify_fam.c @@ -0,0 +1,446 @@ +/* + * 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_NOTIFY_CHECK_TIMEOUT 1 /* secs */ +#define FAM_EVENT_DRAIN ((uint32_t)(-1)) + +/* 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); + } + } + + 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) +{ + static struct cnotify_fns global_fam_notify = + { + fam_register_notify, + fam_check_notify, + fam_remove_notify, + FAM_NOTIFY_CHECK_TIMEOUT + }; + + /* TODO: rather than relying on FAM_NOTIFY_CHECK_TIMEOUT, we should have an + * API to push the FAM fd into the global server fd set. + */ + + 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 */ |