/src/dovecot/src/lib/ioloop-notify-inotify.c
Line | Count | Source |
1 | | /* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ |
2 | | |
3 | | #define _GNU_SOURCE |
4 | | #include "lib.h" |
5 | | |
6 | | #ifdef IOLOOP_NOTIFY_INOTIFY |
7 | | |
8 | | #include "ioloop-private.h" |
9 | | #include "ioloop-notify-fd.h" |
10 | | #include "buffer.h" |
11 | | #include "net.h" |
12 | | #include "ipwd.h" |
13 | | #include "time-util.h" |
14 | | |
15 | | #include <stdio.h> |
16 | | #include <unistd.h> |
17 | | #include <fcntl.h> |
18 | | #include <sys/ioctl.h> |
19 | | #include <sys/inotify.h> |
20 | | |
21 | | #define INOTIFY_BUFLEN (32*1024) |
22 | | |
23 | | struct ioloop_notify_handler_context { |
24 | | struct ioloop_notify_fd_context fd_ctx; |
25 | | |
26 | | int inotify_fd; |
27 | | struct io *event_io; |
28 | | |
29 | | bool disabled; |
30 | | }; |
31 | | |
32 | | static struct ioloop_notify_handler_context *io_loop_notify_handler_init(void); |
33 | | |
34 | | static bool inotify_input_more(struct ioloop *ioloop) |
35 | 0 | { |
36 | 0 | struct ioloop_notify_handler_context *ctx = |
37 | 0 | ioloop->notify_handler_context; |
38 | 0 | const struct inotify_event *event; |
39 | 0 | unsigned char event_buf[INOTIFY_BUFLEN]; |
40 | 0 | struct io_notify *io; |
41 | 0 | ssize_t ret, pos; |
42 | | |
43 | | /* read as many events as there is available and fit into our buffer. |
44 | | only full events are returned by the kernel. */ |
45 | 0 | ret = read(ctx->inotify_fd, event_buf, sizeof(event_buf)); |
46 | 0 | if (ret <= 0) { |
47 | 0 | if (ret == 0 || errno == EAGAIN) { |
48 | | /* nothing more to read */ |
49 | 0 | return FALSE; |
50 | 0 | } |
51 | 0 | i_fatal("read(inotify) failed: %m"); |
52 | 0 | } |
53 | | |
54 | 0 | i_gettimeofday(&ioloop_timeval); |
55 | 0 | ioloop_time = ioloop_timeval.tv_sec; |
56 | |
|
57 | 0 | for (pos = 0; pos < ret; ) { |
58 | 0 | if ((size_t)(ret - pos) < sizeof(*event)) |
59 | 0 | break; |
60 | | |
61 | 0 | event = (struct inotify_event *)(event_buf + pos); |
62 | 0 | i_assert(event->len < (size_t)ret); |
63 | 0 | pos += sizeof(*event) + event->len; |
64 | |
|
65 | 0 | io = io_notify_fd_find(&ctx->fd_ctx, event->wd); |
66 | 0 | if (io != NULL) { |
67 | 0 | if ((event->mask & IN_IGNORED) != 0) { |
68 | | /* calling inotify_rm_watch() would now give |
69 | | EINVAL */ |
70 | 0 | io->fd = -1; |
71 | 0 | } |
72 | 0 | io_loop_call_io(&io->io); |
73 | 0 | } |
74 | 0 | } |
75 | 0 | if (pos != ret) |
76 | 0 | i_error("read(inotify) returned partial event"); |
77 | 0 | return (size_t)ret >= sizeof(event_buf)-512; |
78 | 0 | } |
79 | | |
80 | | static void inotify_input(struct ioloop *ioloop) |
81 | 0 | { |
82 | 0 | while (inotify_input_more(ioloop)) ; |
83 | 0 | } |
84 | | |
85 | | #undef io_add_notify |
86 | | enum io_notify_result |
87 | | io_add_notify(const char *path, const char *source_filename, |
88 | | unsigned int source_linenum, |
89 | | io_callback_t *callback, void *context, struct io **io_r) |
90 | 0 | { |
91 | 0 | struct ioloop_notify_handler_context *ctx = |
92 | 0 | current_ioloop->notify_handler_context; |
93 | 0 | int wd; |
94 | |
|
95 | 0 | *io_r = NULL; |
96 | |
|
97 | 0 | if (ctx == NULL) |
98 | 0 | ctx = io_loop_notify_handler_init(); |
99 | 0 | if (ctx->disabled) |
100 | 0 | return IO_NOTIFY_NOSUPPORT; |
101 | | |
102 | 0 | wd = inotify_add_watch(ctx->inotify_fd, path, |
103 | 0 | IN_CREATE | IN_DELETE | IN_DELETE_SELF | |
104 | 0 | IN_MOVE | IN_MODIFY); |
105 | 0 | if (wd < 0) { |
106 | | /* ESTALE could happen with NFS. Don't bother giving an error |
107 | | message then. */ |
108 | 0 | if (errno == ENOENT || errno == ESTALE) |
109 | 0 | return IO_NOTIFY_NOTFOUND; |
110 | | |
111 | 0 | if (errno != ENOSPC) |
112 | 0 | i_error("inotify_add_watch(%s) failed: %m", path); |
113 | 0 | else { |
114 | 0 | i_warning("Inotify watch limit for user exceeded, " |
115 | 0 | "disabling. Increase " |
116 | 0 | "/proc/sys/fs/inotify/max_user_watches"); |
117 | 0 | } |
118 | 0 | ctx->disabled = TRUE; |
119 | 0 | return IO_NOTIFY_NOSUPPORT; |
120 | 0 | } |
121 | | |
122 | 0 | if (ctx->event_io == NULL) { |
123 | 0 | ctx->event_io = io_add(ctx->inotify_fd, IO_READ, |
124 | 0 | inotify_input, current_ioloop); |
125 | 0 | } |
126 | |
|
127 | 0 | *io_r = io_notify_fd_add(&ctx->fd_ctx, wd, callback, context); |
128 | 0 | (*io_r)->source_filename = source_filename; |
129 | 0 | (*io_r)->source_linenum = source_linenum; |
130 | 0 | return IO_NOTIFY_ADDED; |
131 | 0 | } |
132 | | |
133 | | void io_loop_notify_remove(struct io *_io) |
134 | 0 | { |
135 | 0 | struct ioloop_notify_handler_context *ctx = |
136 | 0 | _io->ioloop->notify_handler_context; |
137 | 0 | struct io_notify *io = (struct io_notify *)_io; |
138 | |
|
139 | 0 | if (io->fd != -1) { |
140 | | /* ernro=EINVAL happens if the file itself is deleted and |
141 | | kernel has sent IN_IGNORED event which we haven't read. */ |
142 | 0 | if (inotify_rm_watch(ctx->inotify_fd, io->fd) < 0 && |
143 | 0 | errno != EINVAL) |
144 | 0 | i_error("inotify_rm_watch() failed: %m"); |
145 | 0 | } |
146 | |
|
147 | 0 | io_notify_fd_free(&ctx->fd_ctx, io); |
148 | |
|
149 | 0 | if (ctx->fd_ctx.notifies == NULL && ctx->event_io != NULL) |
150 | 0 | io_remove(&ctx->event_io); |
151 | 0 | } |
152 | | |
153 | | static void ioloop_inotify_user_limit_exceeded(void) |
154 | 0 | { |
155 | 0 | struct passwd pw; |
156 | 0 | const char *name; |
157 | 0 | uid_t uid = geteuid(); |
158 | |
|
159 | 0 | if (i_getpwuid(uid, &pw) <= 0) |
160 | 0 | name = t_strdup_printf("UID %s", dec2str(uid)); |
161 | 0 | else { |
162 | 0 | name = t_strdup_printf("%s (UID %s)", |
163 | 0 | dec2str(uid), pw.pw_name); |
164 | 0 | } |
165 | 0 | i_warning("Inotify instance limit for user %s exceeded, disabling. " |
166 | 0 | "Increase /proc/sys/fs/inotify/max_user_instances", name); |
167 | 0 | } |
168 | | |
169 | | static struct ioloop_notify_handler_context *io_loop_notify_handler_init(void) |
170 | 0 | { |
171 | 0 | struct ioloop *ioloop = current_ioloop; |
172 | 0 | struct ioloop_notify_handler_context *ctx; |
173 | |
|
174 | 0 | ctx = ioloop->notify_handler_context = |
175 | 0 | i_new(struct ioloop_notify_handler_context, 1); |
176 | |
|
177 | 0 | ctx->inotify_fd = inotify_init(); |
178 | 0 | if (ctx->inotify_fd == -1) { |
179 | 0 | if (errno != EMFILE) |
180 | 0 | i_error("inotify_init() failed: %m"); |
181 | 0 | else |
182 | 0 | ioloop_inotify_user_limit_exceeded(); |
183 | 0 | ctx->disabled = TRUE; |
184 | 0 | } else { |
185 | 0 | fd_close_on_exec(ctx->inotify_fd, TRUE); |
186 | 0 | fd_set_nonblock(ctx->inotify_fd, TRUE); |
187 | 0 | } |
188 | 0 | return ctx; |
189 | 0 | } |
190 | | |
191 | | void io_loop_notify_handler_deinit(struct ioloop *ioloop) |
192 | 0 | { |
193 | 0 | struct ioloop_notify_handler_context *ctx = |
194 | 0 | ioloop->notify_handler_context; |
195 | |
|
196 | 0 | while (ctx->fd_ctx.notifies != NULL) { |
197 | 0 | struct io_notify *io = ctx->fd_ctx.notifies; |
198 | 0 | struct io *_io = &io->io; |
199 | |
|
200 | 0 | i_warning("I/O notify leak: %p (%s:%u, fd %d)", |
201 | 0 | (void *)_io->callback, |
202 | 0 | _io->source_filename, |
203 | 0 | _io->source_linenum, io->fd); |
204 | 0 | io_remove(&_io); |
205 | 0 | } |
206 | |
|
207 | 0 | i_close_fd(&ctx->inotify_fd); |
208 | 0 | i_free(ctx); |
209 | 0 | } |
210 | | |
211 | | int io_loop_extract_notify_fd(struct ioloop *ioloop) |
212 | 0 | { |
213 | 0 | struct ioloop_notify_handler_context *ctx = |
214 | 0 | ioloop->notify_handler_context; |
215 | 0 | struct io_notify *io; |
216 | 0 | int fd, new_inotify_fd; |
217 | |
|
218 | 0 | if (ctx == NULL || ctx->inotify_fd == -1) |
219 | 0 | return -1; |
220 | | |
221 | 0 | new_inotify_fd = inotify_init(); |
222 | 0 | if (new_inotify_fd == -1) { |
223 | 0 | if (errno != EMFILE) |
224 | 0 | i_error("inotify_init() failed: %m"); |
225 | 0 | else |
226 | 0 | ioloop_inotify_user_limit_exceeded(); |
227 | 0 | return -1; |
228 | 0 | } |
229 | 0 | for (io = ctx->fd_ctx.notifies; io != NULL; io = io->next) |
230 | 0 | io->fd = -1; |
231 | 0 | io_remove(&ctx->event_io); |
232 | 0 | fd = ctx->inotify_fd; |
233 | 0 | ctx->inotify_fd = new_inotify_fd; |
234 | 0 | return fd; |
235 | 0 | } |
236 | | |
237 | | #endif |