diff options
Diffstat (limited to 'source3/smbd/notify_kernel.c')
-rw-r--r-- | source3/smbd/notify_kernel.c | 216 |
1 files changed, 128 insertions, 88 deletions
diff --git a/source3/smbd/notify_kernel.c b/source3/smbd/notify_kernel.c index 0c20effc3d..0b5784f3d0 100644 --- a/source3/smbd/notify_kernel.c +++ b/source3/smbd/notify_kernel.c @@ -3,6 +3,7 @@ Version 3.0 change notify handling - linux kernel based implementation Copyright (C) Andrew Tridgell 2000 + 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 @@ -23,10 +24,6 @@ #if HAVE_KERNEL_CHANGE_NOTIFY -#define FD_PENDING_SIZE 20 -static SIG_ATOMIC_T fd_pending_array[FD_PENDING_SIZE]; -static SIG_ATOMIC_T signals_received; - #ifndef DN_ACCESS #define DN_ACCESS 0x00000001 /* File accessed in directory */ #define DN_MODIFY 0x00000002 /* File modified in directory */ @@ -55,10 +52,16 @@ static SIG_ATOMIC_T signals_received; determine if a directory has changed. *****************************************************************************/ -struct change_data { - int directory_handle; +struct dnotify_ctx { + struct dnotify_ctx *prev, *next; + + int fd; + files_struct *fsp; }; +static struct dnotify_ctx *dnotify_list; +static int dnotify_signal_pipe[2]; + /**************************************************************************** The signal handler for change notify. The Linux kernel has a bug in that we should be able to block any @@ -68,107 +71,91 @@ struct change_data { test case for the kernel hackers. JRA. *****************************************************************************/ -static void signal_handler(int sig, siginfo_t *info, void *unused) +static void dnotify_signal_handler(int sig, siginfo_t *info, void *unused) { - if (signals_received < FD_PENDING_SIZE - 1) { - fd_pending_array[signals_received] = (SIG_ATOMIC_T)info->si_fd; - signals_received++; - } /* Else signal is lost. */ + int saved_errno; + + /* + * According to http://www.opengroup.org/onlinepubs/009695399/ write + * to a pipe either writes all or nothing, so we can safely write a + * full sizeof(int) and not risk the pipe to become out of sync with + * the receiving end. + * + * We don't care about the result of the write() call. If the pipe is + * full, then this signal is lost, we can't do anything about it. + */ + + saved_errno = errno; + write(dnotify_signal_pipe[1], (const void *)&info->si_fd, sizeof(int)); + errno = saved_errno; + sys_select_signal(RT_SIGNAL_NOTIFY); } /**************************************************************************** - Check if a change notify should be issued. - time non-zero means timeout check (used for hash). Ignore this (async method - where time is zero will be used instead). + The upper level handler informed when the pipe is ready for reading *****************************************************************************/ -static BOOL kernel_check_notify(connection_struct *conn, uint16 vuid, char *path, uint32 flags, void *datap, time_t t) +static void dnotify_pipe_handler(struct event_context *event_ctx, + struct fd_event *event, + uint16 flags, + void *private_data) { - struct change_data *data = (struct change_data *)datap; - int i; - BOOL ret = False; - - if (t) - return False; - - BlockSignals(True, RT_SIGNAL_NOTIFY); - for (i = 0; i < signals_received; i++) { - if (data->directory_handle == (int)fd_pending_array[i]) { - DEBUG(3,("kernel_check_notify: kernel change notify on %s fd[%d]=%d (signals_received=%d)\n", - path, i, (int)fd_pending_array[i], (int)signals_received )); - - close((int)fd_pending_array[i]); - fd_pending_array[i] = (SIG_ATOMIC_T)-1; - if (signals_received - i - 1) { - memmove((void *)&fd_pending_array[i], (void *)&fd_pending_array[i+1], - sizeof(SIG_ATOMIC_T)*(signals_received-i-1)); - } - data->directory_handle = -1; - signals_received--; - ret = True; - break; - } + int res, fd; + struct dnotify_ctx *ctx; + + res = read(dnotify_signal_pipe[0], (void *)&fd, sizeof(int)); + + if (res == -1) { + DEBUG(0, ("Read from the dnotify pipe failed: %s\n", + strerror(errno))); + TALLOC_FREE(event); /* Don't try again */ + return; } - BlockSignals(False, RT_SIGNAL_NOTIFY); - return ret; -} -/**************************************************************************** - Remove a change notify data structure. -*****************************************************************************/ + if (res != sizeof(int)) { + smb_panic("read from dnotify pipe gave wrong number of " + "bytes\n"); + } -static void kernel_remove_notify(void *datap) -{ - struct change_data *data = (struct change_data *)datap; - int fd = data->directory_handle; - if (fd != -1) { - int i; - BlockSignals(True, RT_SIGNAL_NOTIFY); - for (i = 0; i < signals_received; i++) { - if (fd == (int)fd_pending_array[i]) { - fd_pending_array[i] = (SIG_ATOMIC_T)-1; - if (signals_received - i - 1) { - memmove((void *)&fd_pending_array[i], (void *)&fd_pending_array[i+1], - sizeof(SIG_ATOMIC_T)*(signals_received-i-1)); - } - data->directory_handle = -1; - signals_received--; - break; - } + for (ctx = dnotify_list; ctx; ctx = ctx->next) { + if (ctx->fd == fd) { + notify_fsp(ctx->fsp, 0, NULL); } - close(fd); - BlockSignals(False, RT_SIGNAL_NOTIFY); } - SAFE_FREE(data); - DEBUG(3,("kernel_remove_notify: fd=%d\n", fd)); } /**************************************************************************** Register a change notify request. *****************************************************************************/ -static void *kernel_register_notify(connection_struct *conn, char *path, uint32 flags) +static int kernel_register_notify(connection_struct *conn, char *path, + uint32 flags) { - struct change_data data; int fd; unsigned long kernel_flags; fd = sys_open(path,O_RDONLY, 0); if (fd == -1) { - DEBUG(3,("Failed to open directory %s for change notify\n", path)); - return NULL; + DEBUG(3,("Failed to open directory %s for change notify\n", + path)); + return -1; } if (sys_fcntl_long(fd, F_SETSIG, RT_SIGNAL_NOTIFY) == -1) { DEBUG(3,("Failed to set signal handler for change notify\n")); - return NULL; + close(fd); + return -1; } - kernel_flags = DN_CREATE|DN_DELETE|DN_RENAME; /* creation/deletion changes everything! */ + kernel_flags = DN_CREATE|DN_DELETE|DN_RENAME; /* creation/deletion + * changes + * everything! */ if (flags & FILE_NOTIFY_CHANGE_FILE) kernel_flags |= DN_MODIFY; - if (flags & FILE_NOTIFY_CHANGE_DIR_NAME) kernel_flags |= DN_RENAME|DN_DELETE; + if (flags & FILE_NOTIFY_CHANGE_DIR_NAME) kernel_flags + |= DN_RENAME + |DN_DELETE; if (flags & FILE_NOTIFY_CHANGE_ATTRIBUTES) kernel_flags |= DN_ATTRIB; if (flags & FILE_NOTIFY_CHANGE_SIZE) kernel_flags |= DN_MODIFY; if (flags & FILE_NOTIFY_CHANGE_LAST_WRITE) kernel_flags |= DN_MODIFY; @@ -176,19 +163,20 @@ static void *kernel_register_notify(connection_struct *conn, char *path, uint32 if (flags & FILE_NOTIFY_CHANGE_CREATION) kernel_flags |= DN_CREATE; if (flags & FILE_NOTIFY_CHANGE_SECURITY) kernel_flags |= DN_ATTRIB; if (flags & FILE_NOTIFY_CHANGE_EA) kernel_flags |= DN_ATTRIB; - if (flags & FILE_NOTIFY_CHANGE_FILE_NAME) kernel_flags |= DN_RENAME|DN_DELETE; + if (flags & FILE_NOTIFY_CHANGE_FILE_NAME) kernel_flags + |= DN_RENAME + |DN_DELETE; if (sys_fcntl_long(fd, F_NOTIFY, kernel_flags) == -1) { DEBUG(3,("Failed to set async flag for change notify\n")); - return NULL; + close(fd); + return -1; } - data.directory_handle = fd; - - DEBUG(3,("kernel change notify on %s (ntflags=0x%x flags=0x%x) fd=%d\n", - path, (int)flags, (int)kernel_flags, fd)); + DEBUG(3,("kernel change notify on %s (ntflags=0x%x flags=0x%x) " + "fd=%d\n", path, (int)flags, (int)kernel_flags, fd)); - return (void *)memdup(&data, sizeof(data)); + return fd; } /**************************************************************************** @@ -206,18 +194,74 @@ static BOOL kernel_notify_available(void) return ret == 0; } +static int dnotify_ctx_destructor(struct dnotify_ctx *ctx) +{ + close(ctx->fd); + DLIST_REMOVE(dnotify_list, ctx); + return 0; +} + +static void *kernel_notify_add(TALLOC_CTX *mem_ctx, + struct event_context *event_ctx, + files_struct *fsp, + uint32 *filter) +{ + struct dnotify_ctx *ctx; + + if (!(ctx = TALLOC_P(mem_ctx, struct dnotify_ctx))) { + DEBUG(0, ("talloc failed\n")); + return NULL; + } + + ctx->fsp = fsp; + ctx->fd = kernel_register_notify(fsp->conn, fsp->fsp_name, *filter); + + if (ctx->fd == -1) { + TALLOC_FREE(ctx); + return NULL; + } + + DLIST_ADD(dnotify_list, ctx); + talloc_set_destructor(ctx, dnotify_ctx_destructor); + + return ctx; +} + /**************************************************************************** Setup kernel based change notify. ****************************************************************************/ -struct cnotify_fns *kernel_notify_init(void) +struct cnotify_fns *kernel_notify_init(struct event_context *event_ctx) { static struct cnotify_fns cnotify; struct sigaction act; + if (pipe(dnotify_signal_pipe) == -1) { + DEBUG(0, ("Failed to create signal pipe: %s\n", + strerror(errno))); + return NULL; + } + + if ((set_blocking(dnotify_signal_pipe[0], False) == -1) + || (set_blocking(dnotify_signal_pipe[1], False) == -1)) { + DEBUG(0, ("Failed to set signal pipe to non-blocking: %s\n", + strerror(errno))); + close(dnotify_signal_pipe[0]); + close(dnotify_signal_pipe[1]); + return NULL; + } + + if (event_add_fd(event_ctx, NULL, dnotify_signal_pipe[0], + EVENT_FD_READ, dnotify_pipe_handler, NULL) == NULL) { + DEBUG(0, ("Failed to set signal event handler\n")); + close(dnotify_signal_pipe[0]); + close(dnotify_signal_pipe[1]); + return NULL; + } + ZERO_STRUCT(act); - act.sa_sigaction = signal_handler; + act.sa_sigaction = dnotify_signal_handler; act.sa_flags = SA_SIGINFO; sigemptyset( &act.sa_mask ); if (sigaction(RT_SIGNAL_NOTIFY, &act, NULL) != 0) { @@ -228,11 +272,7 @@ struct cnotify_fns *kernel_notify_init(void) if (!kernel_notify_available()) return NULL; - cnotify.register_notify = kernel_register_notify; - cnotify.check_notify = kernel_check_notify; - cnotify.remove_notify = kernel_remove_notify; - cnotify.select_time = -1; - cnotify.notification_fd = -1; + cnotify.notify_add = kernel_notify_add; /* the signal can start off blocked due to a bug in bash */ BlockSignals(False, RT_SIGNAL_NOTIFY); |