summaryrefslogtreecommitdiff
path: root/source3/smbd/notify_kernel.c
diff options
context:
space:
mode:
Diffstat (limited to 'source3/smbd/notify_kernel.c')
-rw-r--r--source3/smbd/notify_kernel.c216
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);