diff options
Diffstat (limited to 'source3/smbd/notify_fam.c')
-rw-r--r-- | source3/smbd/notify_fam.c | 548 |
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 */ |