changeset 3482:465c465c66be HEAD

Added inotify patch by Johannes Berg and did some restructuring to ioloop notify internals.
author Timo Sirainen <tss@iki.fi>
date Tue, 12 Jul 2005 18:40:33 +0300
parents 2d631ab1d90e
children 84a4f150bd00
files configure.in src/lib/Makefile.am src/lib/ioloop-internal.h src/lib/ioloop-notify-dn.c src/lib/ioloop-notify-inotify.c src/lib/ioloop-notify-none.c src/lib/ioloop.c
diffstat 7 files changed, 321 insertions(+), 52 deletions(-) [+]
line wrap: on
line diff
--- a/configure.in	Tue Jul 12 17:43:44 2005 +0300
+++ b/configure.in	Tue Jul 12 18:40:33 2005 +0300
@@ -1,4 +1,4 @@
-AC_INIT(dovecot, 1.0-test73, [dovecot@dovecot.org])
+AC_INIT(dovecot, 1.0-test77, [dovecot@dovecot.org])
 AC_CONFIG_SRCDIR([src])
 
 AC_CONFIG_HEADERS([config.h])
@@ -57,6 +57,13 @@
 	ioloop=$withval,
 	ioloop=)
 
+AC_ARG_WITH(notify,
+[  --with-notify=IOLOOP    Specify the file system notification method to use
+                          (dnotify, inotify, none;
+                           default dnotify if compilable, otherwise none)],
+	notify=$withval,
+	notify=)
+
 AC_ARG_WITH(passwd,
 [  --with-passwd           Build with /etc/passwd support (default)],
 	if test x$withval = xno; then
@@ -324,20 +331,59 @@
   ioloop="select"
 fi
 
-dnl * dnotify?
-AC_TRY_COMPILE([
-  #define _GNU_SOURCE
-  #include <fcntl.h>
-  #include <signal.h>
-  #include <unistd.h>
-], [
-  fcntl(0, F_SETSIG, SIGRTMIN);
-  fcntl(0, F_NOTIFY, DN_CREATE | DN_DELETE | DN_RENAME | DN_MULTISHOT);
-], [
-  AC_DEFINE(IOLOOP_NOTIFY_DNOTIFY,, Use Linux dnotify)
-], [
+have_notify=no
+
+if test "$notify" = "none"; then
   AC_DEFINE(IOLOOP_NOTIFY_NONE,, No special notify support)
-])
+fi
+
+if test "$notify" = "" || test "$notify" = "dnotify"; then
+  dnl * dnotify?
+  AC_TRY_COMPILE([
+    #define _GNU_SOURCE
+    #include <fcntl.h>
+    #include <signal.h>
+    #include <unistd.h>
+  ], [
+    fcntl(0, F_SETSIG, SIGRTMIN);
+    fcntl(0, F_NOTIFY, DN_CREATE | DN_DELETE | DN_RENAME | DN_MULTISHOT);
+  ], [
+    AC_DEFINE(IOLOOP_NOTIFY_DNOTIFY,, Use Linux dnotify)
+    notify=dnotify
+  ], [
+    if test "$notify" = "dnotify"; then
+      AC_MSG_ERROR([dnotify requested but not available])
+    fi
+    notify=none
+    AC_DEFINE(IOLOOP_NOTIFY_NONE,, No special notify support)
+  ])
+fi
+
+if test "$notify" = "inotify"; then
+  dnl * inotify?
+  AC_TRY_COMPILE([
+    #define _GNU_SOURCE
+    #include <sys/ioctl.h>
+    #include <fcntl.h>
+    #include <linux/inotify.h>
+  ], [
+    struct inotify_watch_request req;
+    int wd, dev_fd;
+    char * fn = "/tmp";
+    dev_fd = open ("/dev/inotify", O_RDONLY);
+    req.fd = open (fn, O_RDONLY);
+    if (req.fd < 0) return -1;
+    req.mask = IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO | IN_MODIFY | IN_ACCESS;
+    wd = ioctl (dev_fd, INOTIFY_WATCH, &req);
+    close(req.fd);
+    ioctl (dev_fd, INOTIFY_IGNORE, &wd);
+  ], [
+    AC_DEFINE(IOLOOP_NOTIFY_INOTIFY,, Use Linux inotify)
+  ], [
+    AC_MSG_ERROR([inotify requested but not available, check for existence of <linux/inotify.h>])
+    notify=none
+  ])
+fi
 
 dnl * GLIBC?
 AC_TRY_COMPILE([
@@ -1409,6 +1455,7 @@
 echo "Install prefix ...................... : $prefix"
 echo "File offsets ........................ : ${offt_bits}bit"
 echo "I/O loop method ..................... : $ioloop"
+echo "File change notification method ..... : $notify"
 echo "Building with SSL support ........... : $have_ssl"
 echo "Building with IPv6 support .......... : $want_ipv6"
 echo "Building with pop3 server ........... : $want_pop3d"
--- a/src/lib/Makefile.am	Tue Jul 12 17:43:44 2005 +0300
+++ b/src/lib/Makefile.am	Tue Jul 12 18:40:33 2005 +0300
@@ -29,6 +29,7 @@
 	ioloop.c \
 	ioloop-notify-none.c \
 	ioloop-notify-dn.c \
+	ioloop-notify-inotify.c \
 	ioloop-poll.c \
 	ioloop-select.c \
 	ioloop-epoll.c \
--- a/src/lib/ioloop-internal.h	Tue Jul 12 17:43:44 2005 +0300
+++ b/src/lib/ioloop-internal.h	Tue Jul 12 18:40:33 2005 +0300
@@ -9,11 +9,12 @@
 	pool_t pool;
 
 	struct io *ios;
-	struct io *notifys, *event_io;
+	struct io *notifys;
 	struct io *next_io;
 	struct timeout *timeouts; /* sorted by next_run */
 
         struct ioloop_handler_data *handler_data;
+        struct ioloop_notify_handler_context *notify_handler_context;
 
 	unsigned int running:1;
 };
@@ -26,6 +27,8 @@
 
 	io_callback_t *callback;
         void *context;
+
+        int notify_context;
 };
 
 struct timeout {
@@ -55,6 +58,9 @@
 void io_loop_handler_init(struct ioloop *ioloop);
 void io_loop_handler_deinit(struct ioloop *ioloop);
 
+void io_loop_notify_handler_init(struct ioloop *ioloop);
+void io_loop_notify_handler_deinit(struct ioloop *ioloop);
+
 struct io *io_loop_notify_add(struct ioloop *ioloop, int fd,
 			      enum io_condition condition,
 			      io_callback_t *callback, void *context);
--- a/src/lib/ioloop-notify-dn.c	Tue Jul 12 17:43:44 2005 +0300
+++ b/src/lib/ioloop-notify-dn.c	Tue Jul 12 18:40:33 2005 +0300
@@ -14,6 +14,10 @@
 #include <unistd.h>
 #include <fcntl.h>
 
+struct ioloop_notify_handler_context {
+	struct io *event_io;
+};
+
 static int event_pipe[2] = { -1, -1 };
 
 static void sigrt_handler(int signo __attr_unused__, siginfo_t *si,
@@ -52,50 +56,19 @@
 	}
 }
 
-static int dn_init(void)
-{
-	struct sigaction act;
-
-	if (pipe(event_pipe) < 0) {
-		i_error("pipe() failed: %m");
-		return FALSE;
-	}
-
-	net_set_nonblock(event_pipe[0], TRUE);
-	net_set_nonblock(event_pipe[1], TRUE);
-
-	/* SIGIO is sent if queue gets full. we'll just ignore it. */
-        signal(SIGIO, SIG_IGN);
-
-	act.sa_sigaction = sigrt_handler;
-	sigemptyset(&act.sa_mask);
-	act.sa_flags = SA_SIGINFO | SA_RESTART | SA_NODEFER;
-
-	if (sigaction(SIGRTMIN, &act, NULL) < 0) {
-		i_error("sigaction(SIGRTMIN) failed: %m");
-		close(event_pipe[0]);
-		close(event_pipe[1]);
-		return FALSE;
-	}
-
-	return TRUE;
-}
-
 struct io *io_loop_notify_add(struct ioloop *ioloop, int fd,
 			      enum io_condition condition,
 			      io_callback_t *callback, void *context)
 {
+	struct ioloop_notify_handler_context *ctx =
+		ioloop->notify_handler_context;
 	struct io *io;
 
 	if ((condition & IO_FILE_NOTIFY) != 0)
 		return NULL;
 
-	if (event_pipe[0] == -1) {
-		if (!dn_init())
-			return NULL;
-	}
-	if (ioloop->event_io == NULL) {
-		ioloop->event_io =
+	if (ctx->event_io == NULL) {
+		ctx->event_io =
 			io_add(event_pipe[0], IO_READ, event_callback, ioloop);
 	}
 
@@ -124,6 +97,8 @@
 
 void io_loop_notify_remove(struct ioloop *ioloop, struct io *io)
 {
+	struct ioloop_notify_handler_context *ctx =
+		ioloop->notify_handler_context;
 	struct io **io_p;
 
 	for (io_p = &ioloop->notifys; *io_p != NULL; io_p = &(*io_p)->next) {
@@ -141,9 +116,53 @@
 	p_free(ioloop->pool, io);
 
 	if (ioloop->notifys == NULL) {
-		io_remove(ioloop->event_io);
-		ioloop->event_io = NULL;
+		io_remove(ctx->event_io);
+		ctx->event_io = NULL;
 	}
 }
 
+void io_loop_notify_handler_init(struct ioloop *ioloop)
+{
+	struct ioloop_notify_handler_context *ctx;
+	struct sigaction act;
+
+	i_assert(event_pipe[0] == -1);
+
+	ctx = ioloop->notify_handler_context =
+		i_new(struct ioloop_notify_handler_context, 1);
+
+	if (pipe(event_pipe) < 0) {
+		i_fatal("pipe() failed: %m");
+		return;
+	}
+
+	net_set_nonblock(event_pipe[0], TRUE);
+	net_set_nonblock(event_pipe[1], TRUE);
+
+	/* SIGIO is sent if queue gets full. we'll just ignore it. */
+        signal(SIGIO, SIG_IGN);
+
+	act.sa_sigaction = sigrt_handler;
+	sigemptyset(&act.sa_mask);
+	act.sa_flags = SA_SIGINFO | SA_RESTART | SA_NODEFER;
+
+	if (sigaction(SIGRTMIN, &act, NULL) < 0)
+		i_fatal("sigaction(SIGRTMIN) failed: %m");
+}
+
+void io_loop_notify_handler_deinit(struct ioloop *ioloop __attr_unused__)
+{
+	struct ioloop_notify_handler_context *ctx =
+		ioloop->notify_handler_context;
+
+	signal(SIGRTMIN, SIG_IGN);
+
+	if (close(event_pipe[0]) < 0)
+		i_error("close(event_pipe[0]) failed: %m");
+	if (close(event_pipe[1]) < 0)
+		i_error("close(event_pipe[1]) failed: %m");
+
+	i_free(ctx);
+}
+
 #endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/ioloop-notify-inotify.c	Tue Jul 12 18:40:33 2005 +0300
@@ -0,0 +1,186 @@
+/* Copyright (C) 2005 Johannes Berg */
+
+#define _GNU_SOURCE
+#include "lib.h"
+
+#ifdef IOLOOP_NOTIFY_INOTIFY
+
+#include "ioloop-internal.h"
+#include "buffer.h"
+#include "network.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <linux/inotify.h>
+
+#define INITIAL_INOTIFY_BUFLEN (FILENAME_MAX + sizeof(struct inotify_event))
+#define MAXIMAL_INOTIFY_BUFLEN (32*1024)
+
+struct ioloop_notify_handler_context {
+	int inotify_fd;
+
+	struct io *event_io;
+
+	buffer_t *buf;
+};
+
+static int event_read_next(struct ioloop *ioloop)
+{
+	struct ioloop_notify_handler_context *ctx =
+		ioloop->notify_handler_context;
+	struct io *io;
+        struct inotify_event *event;
+	ssize_t ret;
+	size_t record_length;
+	int required_bytes;
+
+	if (ioctl(ctx->inotify_fd, FIONREAD, &required_bytes))
+		i_fatal("ioctl(inotify_fd, FIONREAD) failed: %m");
+
+	if (required_bytes <= 0)
+		return FALSE;
+
+	if (required_bytes > MAXIMAL_INOTIFY_BUFLEN)
+		required_bytes = MAXIMAL_INOTIFY_BUFLEN;
+
+	event = buffer_get_space_unsafe(ctx->buf, 0, required_bytes);
+	ret = read(ctx->inotify_fd, (void *)event, required_bytes);
+
+	if (ret == 0)
+		return FALSE;
+
+	if (ret < 0)
+		i_fatal("read(inotify_fd) failed: %m");
+
+	if (gettimeofday(&ioloop_timeval, &ioloop_timezone) < 0)
+		i_fatal("gettimeofday(): %m");
+	ioloop_time = ioloop_timeval.tv_sec;
+
+	while ((size_t)required_bytes > sizeof(*event)) {
+		for (io = ioloop->notifys; io != NULL; io = io->next) {
+			if (io->notify_context == event->wd) {
+				io->callback(io->context);
+				break;
+			}
+		}
+
+		record_length = event->len + sizeof(struct inotify_event);
+		if ((size_t)required_bytes < record_length)
+			break;
+		required_bytes -= record_length;
+
+		/* this might point outside the area if the loop
+		   won't run again */
+		event = PTR_OFFSET(event, record_length);
+	}
+
+	return TRUE;
+}
+
+static void event_callback(void *context)
+{
+	struct ioloop *ioloop = context;
+
+	while (event_read_next(ioloop)) ;
+}
+
+struct io *io_loop_notify_add(struct ioloop *ioloop, int fd,
+			      enum io_condition condition,
+			      io_callback_t *callback, void *context)
+{
+	struct ioloop_notify_handler_context *ctx =
+		ioloop->notify_handler_context;
+	struct io *io;
+	struct inotify_watch_request req;
+	int added = FALSE;
+	int watchdescriptor;
+
+	if ((condition & IO_FILE_NOTIFY) != 0)
+		return NULL;
+
+	if (ctx->event_io == NULL) {
+		added = TRUE;
+		ctx->event_io = io_add(ctx->inotify_fd, IO_READ,
+				       event_callback, ioloop);
+	}
+
+	/* now set up the notification request and shoot it off */
+	req.fd = fd;
+	req.mask = IN_CREATE | IN_DELETE | IN_MOVE | IN_CLOSE | IN_MODIFY;
+	watchdescriptor = ioctl(ctx->inotify_fd, INOTIFY_WATCH, &req);
+	
+	if (watchdescriptor < 0) {
+		i_error("ioctl(INOTIFY_WATCH) failed: %m");
+		if (added) {
+			io_remove(ctx->event_io);
+			ctx->event_io = NULL;
+		}
+		return NULL;
+	}
+
+	io = p_new(ioloop->pool, struct io, 1);
+	io->fd = fd;
+	io->condition = condition;
+
+	io->callback = callback;
+	io->context = context;
+	io->notify_context = watchdescriptor;
+
+	io->next = ioloop->notifys;
+	ioloop->notifys = io;
+	return io;
+}
+
+void io_loop_notify_remove(struct ioloop *ioloop, struct io *io)
+{
+	struct ioloop_notify_handler_context *ctx =
+		ioloop->notify_handler_context;
+	struct io **io_p;
+
+	for (io_p = &ioloop->notifys; *io_p != NULL; io_p = &(*io_p)->next) {
+		if (*io_p == io) {
+			*io_p = io->next;
+			break;
+		}
+	}
+
+	if (ioctl(ctx->inotify_fd, INOTIFY_IGNORE, &io->notify_context) < 0)
+		i_error("ioctl(INOTIFY_IGNORE) failed: %m");
+
+	p_free(ioloop->pool, io);
+
+	if (ioloop->notifys == NULL) {
+		io_remove(ctx->event_io);
+		ctx->event_io = NULL;
+	}
+}
+
+void io_loop_notify_handler_init(struct ioloop *ioloop)
+{
+	struct ioloop_notify_handler_context *ctx;
+
+	ctx = ioloop->notify_handler_context =
+		i_new(struct ioloop_notify_handler_context, 1);
+
+	ctx->inotify_fd = open("/dev/inotify", O_RDONLY);
+	if (ctx->inotify_fd < 0)
+		i_fatal("open(/dev/inotify) failed: %m");
+
+	ctx->buf = buffer_create_dynamic(default_pool, INITIAL_INOTIFY_BUFLEN);
+}
+
+void io_loop_notify_handler_deinit(struct ioloop *ioloop)
+{
+	struct ioloop_notify_handler_context *ctx =
+		ioloop->notify_handler_context;
+
+	if (close(ctx->inotify_fd) < 0)
+		i_error("close(/dev/inotify) failed: %m");
+
+	buffer_free(ctx->buf);
+	i_free(ctx);
+}
+
+#endif
--- a/src/lib/ioloop-notify-none.c	Tue Jul 12 17:43:44 2005 +0300
+++ b/src/lib/ioloop-notify-none.c	Tue Jul 12 18:40:33 2005 +0300
@@ -19,4 +19,12 @@
 {
 }
 
+void io_loop_notify_handler_init(struct ioloop *ioloop __attr_unused__)
+{
+}
+
+void io_loop_notify_handler_deinit(struct ioloop *ioloop __attr_unused__)
+{
+}
+
 #endif
--- a/src/lib/ioloop.c	Tue Jul 12 17:43:44 2005 +0300
+++ b/src/lib/ioloop.c	Tue Jul 12 18:40:33 2005 +0300
@@ -247,6 +247,7 @@
 	ioloop->pool = pool;
 
 	io_loop_handler_init(ioloop);
+	io_loop_notify_handler_init(ioloop);
 
 	ioloop->prev = current_ioloop;
         current_ioloop = ioloop;
@@ -275,6 +276,7 @@
                 timeout_destroy(ioloop, &ioloop->timeouts);
 	}
 
+	io_loop_notify_handler_deinit(ioloop);
         io_loop_handler_deinit(ioloop);
 
         /* ->prev won't work unless loops are destroyed in create order */