Coverage Report

Created: 2025-12-11 07:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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