summaryrefslogtreecommitdiff
path: root/source3
diff options
context:
space:
mode:
Diffstat (limited to 'source3')
-rw-r--r--source3/Makefile.in6
-rw-r--r--source3/configure.in33
-rw-r--r--source3/param/loadparm.c4
-rw-r--r--source3/smbd/notify.c8
-rw-r--r--source3/smbd/notify_fam.c446
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 */