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.c548
1 files changed, 184 insertions, 364 deletions
diff --git a/source3/smbd/notify_fam.c b/source3/smbd/notify_fam.c
index bfef4ac871..306316e49f 100644
--- a/source3/smbd/notify_fam.c
+++ b/source3/smbd/notify_fam.c
@@ -2,6 +2,7 @@
* FAM file notification support.
*
* Copyright (c) James Peach 2005
+ * Copyright (c) Volker Lendecke 2007
*
* 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
@@ -43,422 +44,241 @@ typedef enum FAMCodes FAMCodes;
* http://sourceforge.net/projects/bsdfam/
*/
-struct fam_req_info
-{
- FAMRequest req;
- int generation;
- 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
+static void *fam_notify_add(TALLOC_CTX *mem_ctx,
+ struct event_context *event_ctx,
+ files_struct *fsp, uint32 *filter);
-#define FAM_EVENT_DRAIN ((uint32_t)(-1))
-
-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);
+struct fam_notify_ctx {
+ struct fam_notify_ctx *prev, *next;
+ FAMConnection *fam_connection;
+ struct FAMRequest fr;
+ files_struct *fsp;
+ char *path;
+ uint32 filter;
+};
-static void fam_remove_notify(void * data);
+static struct fam_notify_ctx *fam_notify_list;
+static FAMConnection fam_connection;
+static void fam_handler(struct event_context *event_ctx,
+ struct fd_event *fd_event,
+ uint16 flags,
+ void *private_data);
-static struct cnotify_fns global_fam_notify =
+static NTSTATUS fam_open_connection(FAMConnection *fam_conn,
+ struct event_context *event_ctx)
{
- fam_register_notify,
- fam_check_notify,
- fam_remove_notify,
- -1,
- -1
-};
+ int res;
+ char *name;
-/* 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(FAMCodes code)
-{
- static const struct { 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>");
-}
+ ZERO_STRUCTP(fam_conn);
+ FAMCONNECTION_GETFD(fam_conn) = -1;
-static BOOL
-fam_check_reconnect(void)
-{
- if (FAMCONNECTION_GETFD(&global_fc) < 0) {
- fstring name;
+ if (asprintf(&name, "smbd (%lu)", (unsigned long)sys_getpid()) == -1) {
+ DEBUG(0, ("No memory\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
- global_fc_generation++;
- snprintf(name, sizeof(name), "smbd (%lu)", (unsigned long)sys_getpid());
+ res = FAMOpen2(fam_conn, name);
+ SAFE_FREE(name);
- if (FAMOpen2(&global_fc, name) < 0) {
- DEBUG(0, ("failed to connect to FAM service\n"));
- return(False);
+ if (res < 0) {
+ DEBUG(5, ("FAM file change notifications not available\n"));
+ /*
+ * No idea how to get NT_STATUS from a FAM result
+ */
+ FAMCONNECTION_GETFD(fam_conn) = -1;
+ return NT_STATUS_UNEXPECTED_IO_ERROR;
}
- }
- global_fam_notify.notification_fd = FAMCONNECTION_GETFD(&global_fc);
- return(True);
-}
+ if (event_add_fd(event_ctx, event_ctx,
+ FAMCONNECTION_GETFD(fam_conn),
+ EVENT_FD_READ, fam_handler,
+ (void *)fam_conn) == NULL) {
+ DEBUG(0, ("event_add_fd failed\n"));
+ FAMClose(fam_conn);
+ FAMCONNECTION_GETFD(fam_conn) = -1;
+ return NT_STATUS_NO_MEMORY;
+ }
-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);
+ return NT_STATUS_OK;
}
-static BOOL
-fam_handle_event(const FAMCodes code, uint32 flags)
+static void fam_reopen(FAMConnection *fam_conn,
+ struct event_context *event_ctx,
+ struct fam_notify_ctx *notify_list)
{
-#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
-}
+ struct fam_notify_ctx *ctx;
-static BOOL
-fam_pump_events(struct fam_req_info * info, uint32_t flags)
-{
- FAMEvent ev;
+ DEBUG(5, ("Re-opening FAM connection\n"));
- for (;;) {
+ FAMClose(fam_conn);
- /* 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 (!NT_STATUS_IS_OK(fam_open_connection(fam_conn, event_ctx))) {
+ DEBUG(5, ("Re-opening fam connection failed\n"));
+ return;
}
- 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);
+ for (ctx = notify_list; ctx; ctx = ctx->next) {
+ FAMMonitorDirectory(fam_conn, ctx->path, &ctx->fr, NULL);
}
+}
- DEBUG(FAM_TRACE_LOW, ("FAM event %s on '%s' for request %d\n",
- fam_event_str(ev.code), ev.filename, ev.fr.reqnum));
+static void fam_handler(struct event_context *event_ctx,
+ struct fd_event *fd_event,
+ uint16 flags,
+ void *private_data)
+{
+ FAMConnection *fam_conn = (FAMConnection *)private_data;
+ FAMEvent fam_event;
+ struct fam_notify_ctx *ctx;
+ char *name;
+
+ if (FAMPending(fam_conn) == 0) {
+ DEBUG(10, ("fam_handler called but nothing pending\n"));
+ return;
+ }
- 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);
- }
+ if (FAMNextEvent(fam_conn, &fam_event) != 1) {
+ DEBUG(10, ("FAMNextEvent returned an error\n"));
+ TALLOC_FREE(fd_event);
+ fam_reopen(fam_conn, event_ctx, fam_notify_list);
+ return;
+ }
- 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.
- */
+ if ((fam_event.code != FAMCreated) && (fam_event.code != FAMDeleted)) {
+ DEBUG(10, ("Ignoring code FAMCode %d for file %s\n",
+ (int)fam_event.code, fam_event.filename));
+ return;
+ }
- /* 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;
+ for (ctx = fam_notify_list; ctx; ctx = ctx->next) {
+ if (memcmp(&fam_event.fr, &ctx->fr, sizeof(FAMRequest)) == 0) {
+ 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;
- }
+ if (ctx == NULL) {
+ DEBUG(5, ("Discarding event for file %s\n",
+ fam_event.filename));
+ return;
+ }
- default:
- DEBUG(0, ("ignoring unknown FAM event code %d for `%s`\n",
- ev.code, ev.filename));
+ if ((name = strrchr_m(fam_event.filename, '\\')) == NULL) {
+ name = fam_event.filename;
}
- }
- /* No more notifications pending. */
- return(False);
+ notify_fsp(ctx->fsp,
+ fam_event.code == FAMCreated
+ ? NOTIFY_ACTION_ADDED : NOTIFY_ACTION_REMOVED,
+ name);
}
-static BOOL
-fam_test_connection(void)
+static int fam_notify_ctx_destructor(struct fam_notify_ctx *ctx)
{
- FAMConnection fc;
+ if (FAMCONNECTION_GETFD(ctx->fam_connection) != -1) {
+ FAMCancelMonitor(&fam_connection, &ctx->fr);
+ }
+ DLIST_REMOVE(fam_notify_list, ctx);
+ return 0;
+}
- /* 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);
+static void *fam_notify_add(TALLOC_CTX *mem_ctx,
+ struct event_context *event_ctx,
+ files_struct *fsp, uint32 *filter)
+{
+ struct fam_notify_ctx *ctx;
+ pstring fullpath;
- FAMClose(&fc);
- return(True);
-}
+ if ((*filter & FILE_NOTIFY_CHANGE_FILE) == 0) {
+ DEBUG(10, ("filter = %u, no FILE_NOTIFY_CHANGE_FILE\n",
+ *filter));
+ return NULL;
+ }
-/* ------------------------------------------------------------------------- */
+ /* FAM needs an absolute pathname. */
-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 %u bytes failed\n", (unsigned int)sizeof(struct fam_req_info)));
- return(NULL);
- }
-
- if (fam_monitor_path(conn, info, path, flags)) {
- return(info);
- } else {
- SAFE_FREE(info);
- return(NULL);
- }
-}
+ pstrcpy(fullpath, fsp->fsp_name);
+ if (!canonicalize_path(fsp->conn, fullpath)) {
+ DEBUG(0, ("failed to canonicalize path '%s'\n", fullpath));
+ 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;
+ if (*fullpath != '/') {
+ DEBUG(0, ("canonicalized path '%s' into `%s`\n", fsp->fsp_name,
+ fullpath));
+ DEBUGADD(0, ("but expected an absolute path\n"));
+ return NULL;
+ }
+
+ if (!(ctx = TALLOC_P(mem_ctx, struct fam_notify_ctx))) {
+ return NULL;
+ }
- info = (struct fam_req_info *)data;
- SMB_ASSERT(info != NULL);
+ ctx->fsp = fsp;
+ ctx->fam_connection = &fam_connection;
- DEBUG(10, ("checking FAM events for `%s`\n", path));
+ /*
+ * The FAM module in this early state will only take care of
+ * FAMCreated and FAMDeleted events
+ */
- 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));
- }
+ ctx->filter = FILE_NOTIFY_CHANGE_FILE;
- if (!fam_check_reconnect()) {
- return(False);
- }
+ if (!(ctx->path = talloc_strdup(ctx, fullpath))) {
+ DEBUG(0, ("talloc_strdup failed\n"));
+ TALLOC_FREE(ctx);
+ return NULL;
+ }
- 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);
- }
+ /*
+ * Leave the rest to smbd itself
+ */
- return(fam_pump_events(info, flags));
-}
+ *filter &= ~FILE_NOTIFY_CHANGE_FILE;
-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 ((FAMCONNECTION_GETFD(&global_fc) != -1)
- && (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.
+ DLIST_ADD(fam_notify_list, ctx);
+ talloc_set_destructor(ctx, fam_notify_ctx_destructor);
+
+ /*
+ * Only directories monitored so far
*/
- fam_pump_events(info, FAM_EVENT_DRAIN);
- }
- SAFE_FREE(info);
+ if (FAMCONNECTION_GETFD(ctx->fam_connection) != -1) {
+ FAMMonitorDirectory(ctx->fam_connection, ctx->path, &ctx->fr,
+ NULL);
+ }
+ else {
+ /*
+ * If the re-open is successful, this will establish the
+ * FAMMonitor from the list
+ */
+ fam_reopen(ctx->fam_connection, event_ctx, fam_notify_list);
+ }
+
+ return ctx;
}
-struct cnotify_fns * fam_notify_init(void)
+static struct cnotify_fns global_fam_notify =
+{
+ fam_notify_add,
+};
+
+struct cnotify_fns *fam_notify_init(struct event_context *event_ctx)
{
- FAMCONNECTION_GETFD(&global_fc) = -1;
- if (!fam_test_connection()) {
- DEBUG(0, ("FAM file change notifications not available\n"));
- return(NULL);
- }
+ ZERO_STRUCT(fam_connection);
+ FAMCONNECTION_GETFD(&fam_connection) = -1;
+
+ if (!NT_STATUS_IS_OK(fam_open_connection(&fam_connection,
+ event_ctx))) {
+ DEBUG(0, ("FAM file change notifications not available\n"));
+ return NULL;
+ }
- DEBUG(FAM_TRACE, ("enabling FAM change notifications\n"));
- return &global_fam_notify;
+ DEBUG(10, ("enabling FAM change notifications\n"));
+ return &global_fam_notify;
}
#endif /* HAVE_FAM_CHANGE_NOTIFY */