summaryrefslogtreecommitdiffstats
path: root/src/lib/ioloop-notify-inotify.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lib/ioloop-notify-inotify.c237
1 files changed, 237 insertions, 0 deletions
diff --git a/src/lib/ioloop-notify-inotify.c b/src/lib/ioloop-notify-inotify.c
new file mode 100644
index 0000000..796ac31
--- /dev/null
+++ b/src/lib/ioloop-notify-inotify.c
@@ -0,0 +1,237 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#define _GNU_SOURCE
+#include "lib.h"
+
+#ifdef IOLOOP_NOTIFY_INOTIFY
+
+#include "ioloop-private.h"
+#include "ioloop-notify-fd.h"
+#include "buffer.h"
+#include "net.h"
+#include "ipwd.h"
+#include "time-util.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/inotify.h>
+
+#define INOTIFY_BUFLEN (32*1024)
+
+struct ioloop_notify_handler_context {
+ struct ioloop_notify_fd_context fd_ctx;
+
+ int inotify_fd;
+ struct io *event_io;
+
+ bool disabled;
+};
+
+static struct ioloop_notify_handler_context *io_loop_notify_handler_init(void);
+
+static bool inotify_input_more(struct ioloop *ioloop)
+{
+ struct ioloop_notify_handler_context *ctx =
+ ioloop->notify_handler_context;
+ const struct inotify_event *event;
+ unsigned char event_buf[INOTIFY_BUFLEN];
+ struct io_notify *io;
+ ssize_t ret, pos;
+
+ /* read as many events as there is available and fit into our buffer.
+ only full events are returned by the kernel. */
+ ret = read(ctx->inotify_fd, event_buf, sizeof(event_buf));
+ if (ret <= 0) {
+ if (ret == 0 || errno == EAGAIN) {
+ /* nothing more to read */
+ return FALSE;
+ }
+ i_fatal("read(inotify) failed: %m");
+ }
+
+ i_gettimeofday(&ioloop_timeval);
+ ioloop_time = ioloop_timeval.tv_sec;
+
+ for (pos = 0; pos < ret; ) {
+ if ((size_t)(ret - pos) < sizeof(*event))
+ break;
+
+ event = (struct inotify_event *)(event_buf + pos);
+ i_assert(event->len < (size_t)ret);
+ pos += sizeof(*event) + event->len;
+
+ io = io_notify_fd_find(&ctx->fd_ctx, event->wd);
+ if (io != NULL) {
+ if ((event->mask & IN_IGNORED) != 0) {
+ /* calling inotify_rm_watch() would now give
+ EINVAL */
+ io->fd = -1;
+ }
+ io_loop_call_io(&io->io);
+ }
+ }
+ if (pos != ret)
+ i_error("read(inotify) returned partial event");
+ return (size_t)ret >= sizeof(event_buf)-512;
+}
+
+static void inotify_input(struct ioloop *ioloop)
+{
+ while (inotify_input_more(ioloop)) ;
+}
+
+#undef io_add_notify
+enum io_notify_result
+io_add_notify(const char *path, const char *source_filename,
+ unsigned int source_linenum,
+ io_callback_t *callback, void *context, struct io **io_r)
+{
+ struct ioloop_notify_handler_context *ctx =
+ current_ioloop->notify_handler_context;
+ int wd;
+
+ *io_r = NULL;
+
+ if (ctx == NULL)
+ ctx = io_loop_notify_handler_init();
+ if (ctx->disabled)
+ return IO_NOTIFY_NOSUPPORT;
+
+ wd = inotify_add_watch(ctx->inotify_fd, path,
+ IN_CREATE | IN_DELETE | IN_DELETE_SELF |
+ IN_MOVE | IN_MODIFY);
+ if (wd < 0) {
+ /* ESTALE could happen with NFS. Don't bother giving an error
+ message then. */
+ if (errno == ENOENT || errno == ESTALE)
+ return IO_NOTIFY_NOTFOUND;
+
+ if (errno != ENOSPC)
+ i_error("inotify_add_watch(%s) failed: %m", path);
+ else {
+ i_warning("Inotify watch limit for user exceeded, "
+ "disabling. Increase "
+ "/proc/sys/fs/inotify/max_user_watches");
+ }
+ ctx->disabled = TRUE;
+ return IO_NOTIFY_NOSUPPORT;
+ }
+
+ if (ctx->event_io == NULL) {
+ ctx->event_io = io_add(ctx->inotify_fd, IO_READ,
+ inotify_input, current_ioloop);
+ }
+
+ *io_r = io_notify_fd_add(&ctx->fd_ctx, wd, callback, context);
+ (*io_r)->source_filename = source_filename;
+ (*io_r)->source_linenum = source_linenum;
+ return IO_NOTIFY_ADDED;
+}
+
+void io_loop_notify_remove(struct io *_io)
+{
+ struct ioloop_notify_handler_context *ctx =
+ _io->ioloop->notify_handler_context;
+ struct io_notify *io = (struct io_notify *)_io;
+
+ if (io->fd != -1) {
+ /* ernro=EINVAL happens if the file itself is deleted and
+ kernel has sent IN_IGNORED event which we haven't read. */
+ if (inotify_rm_watch(ctx->inotify_fd, io->fd) < 0 &&
+ errno != EINVAL)
+ i_error("inotify_rm_watch() failed: %m");
+ }
+
+ io_notify_fd_free(&ctx->fd_ctx, io);
+
+ if (ctx->fd_ctx.notifies == NULL && ctx->event_io != NULL)
+ io_remove(&ctx->event_io);
+}
+
+static void ioloop_inotify_user_limit_exceeded(void)
+{
+ struct passwd pw;
+ const char *name;
+ uid_t uid = geteuid();
+
+ if (i_getpwuid(uid, &pw) <= 0)
+ name = t_strdup_printf("UID %s", dec2str(uid));
+ else {
+ name = t_strdup_printf("%s (UID %s)",
+ dec2str(uid), pw.pw_name);
+ }
+ i_warning("Inotify instance limit for user %s exceeded, disabling. "
+ "Increase /proc/sys/fs/inotify/max_user_instances", name);
+}
+
+static struct ioloop_notify_handler_context *io_loop_notify_handler_init(void)
+{
+ struct ioloop *ioloop = current_ioloop;
+ struct ioloop_notify_handler_context *ctx;
+
+ ctx = ioloop->notify_handler_context =
+ i_new(struct ioloop_notify_handler_context, 1);
+
+ ctx->inotify_fd = inotify_init();
+ if (ctx->inotify_fd == -1) {
+ if (errno != EMFILE)
+ i_error("inotify_init() failed: %m");
+ else
+ ioloop_inotify_user_limit_exceeded();
+ ctx->disabled = TRUE;
+ } else {
+ fd_close_on_exec(ctx->inotify_fd, TRUE);
+ fd_set_nonblock(ctx->inotify_fd, TRUE);
+ }
+ return ctx;
+}
+
+void io_loop_notify_handler_deinit(struct ioloop *ioloop)
+{
+ struct ioloop_notify_handler_context *ctx =
+ ioloop->notify_handler_context;
+
+ while (ctx->fd_ctx.notifies != NULL) {
+ struct io_notify *io = ctx->fd_ctx.notifies;
+ struct io *_io = &io->io;
+
+ i_warning("I/O notify leak: %p (%s:%u, fd %d)",
+ (void *)_io->callback,
+ _io->source_filename,
+ _io->source_linenum, io->fd);
+ io_remove(&_io);
+ }
+
+ i_close_fd(&ctx->inotify_fd);
+ i_free(ctx);
+}
+
+int io_loop_extract_notify_fd(struct ioloop *ioloop)
+{
+ struct ioloop_notify_handler_context *ctx =
+ ioloop->notify_handler_context;
+ struct io_notify *io;
+ int fd, new_inotify_fd;
+
+ if (ctx == NULL || ctx->inotify_fd == -1)
+ return -1;
+
+ new_inotify_fd = inotify_init();
+ if (new_inotify_fd == -1) {
+ if (errno != EMFILE)
+ i_error("inotify_init() failed: %m");
+ else
+ ioloop_inotify_user_limit_exceeded();
+ return -1;
+ }
+ for (io = ctx->fd_ctx.notifies; io != NULL; io = io->next)
+ io->fd = -1;
+ io_remove(&ctx->event_io);
+ fd = ctx->inotify_fd;
+ ctx->inotify_fd = new_inotify_fd;
+ return fd;
+}
+
+#endif