1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
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
|