summaryrefslogtreecommitdiff
path: root/source3/smbd/notify_fam.c
diff options
context:
space:
mode:
Diffstat (limited to 'source3/smbd/notify_fam.c')
-rw-r--r--source3/smbd/notify_fam.c446
1 files changed, 446 insertions, 0 deletions
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 */