From 543ff192b37feff91fd77c336177cb628bd1e08a Mon Sep 17 00:00:00 2001 From: James Peach Date: Thu, 19 Jan 2006 00:30:16 +0000 Subject: r13027: Support file change notifications from FAM. (This used to be commit 652b511ff24ce41c7745a0e00363e619e5027828) --- source3/smbd/notify.c | 8 +- source3/smbd/notify_fam.c | 446 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 453 insertions(+), 1 deletion(-) create mode 100644 source3/smbd/notify_fam.c (limited to 'source3/smbd') 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,9 +212,15 @@ 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(); 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 + +/* 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(""); +} + +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 */ -- cgit