summaryrefslogtreecommitdiffstats
path: root/src/lib/ioloop-poll.c
blob: 02cdce67120835b41756124b0ba84e15eff096bf (plain)
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
/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */

/* @UNSAFE: whole file */

#include "lib.h"
#include "ioloop-private.h"

#ifdef IOLOOP_POLL

#include <fcntl.h>
#include <sys/poll.h>

struct ioloop_handler_context {
	unsigned int fds_count, fds_pos;
	struct pollfd *fds;

	unsigned int idx_count;
	int *fd_index;
};

void io_loop_handler_init(struct ioloop *ioloop, unsigned int initial_fd_count)
{
	struct ioloop_handler_context *ctx;

	ioloop->handler_context = ctx = i_new(struct ioloop_handler_context, 1);
	ctx->fds_count = initial_fd_count;
	ctx->fds = i_new(struct pollfd, ctx->fds_count);

	ctx->idx_count = initial_fd_count;
	ctx->fd_index = i_new(int, ctx->idx_count);
        memset(ctx->fd_index, 0xff, sizeof(int) * ctx->idx_count);
}

void io_loop_handler_deinit(struct ioloop *ioloop)
{
        i_free(ioloop->handler_context->fds);
        i_free(ioloop->handler_context->fd_index);
        i_free(ioloop->handler_context);
}

#define IO_POLL_ERROR (POLLERR | POLLHUP | POLLNVAL)
#define IO_POLL_INPUT (POLLIN | POLLPRI | IO_POLL_ERROR)
#define IO_POLL_OUTPUT (POLLOUT | IO_POLL_ERROR)

void io_loop_handle_add(struct io_file *io)
{
	struct ioloop_handler_context *ctx = io->io.ioloop->handler_context;
	enum io_condition condition = io->io.condition;
	unsigned int old_count;
	int index, old_events, fd = io->fd;

	if ((unsigned int)fd >= ctx->idx_count) {
                /* grow the fd -> index array */
		old_count = ctx->idx_count;

		ctx->idx_count = nearest_power((unsigned int) fd+1);

		ctx->fd_index = i_realloc_type(ctx->fd_index, int,
					       old_count, ctx->idx_count);
		memset(ctx->fd_index + old_count, 0xff,
		       sizeof(int) * (ctx->idx_count-old_count));
	}

	if (ctx->fds_pos >= ctx->fds_count) {
		/* grow the fd array */
		old_count = ctx->fds_count;

		ctx->fds_count = nearest_power(ctx->fds_count+1);

		ctx->fds = i_realloc_type(ctx->fds, struct pollfd,
					  old_count, ctx->fds_count);
	}

	if (ctx->fd_index[fd] != -1) {
		/* update existing pollfd */
                index = ctx->fd_index[fd];
	} else {
                /* add new pollfd */
                index = ctx->fds_pos++;

		ctx->fd_index[fd] = index;
		ctx->fds[index].fd = fd;
		ctx->fds[index].events = 0;
		ctx->fds[index].revents = 0;
	}

	old_events = ctx->fds[index].events;
	if ((condition & IO_READ) != 0)
		ctx->fds[index].events |= IO_POLL_INPUT;
        if ((condition & IO_WRITE) != 0)
		ctx->fds[index].events |= IO_POLL_OUTPUT;
	if ((condition & IO_ERROR) != 0)
		ctx->fds[index].events |= IO_POLL_ERROR;
	i_assert(ctx->fds[index].events != old_events);
}

void io_loop_handle_remove(struct io_file *io, bool closed ATTR_UNUSED)
{
	struct ioloop_handler_context *ctx = io->io.ioloop->handler_context;
	enum io_condition condition = io->io.condition;
	int index, fd = io->fd;

	index = ctx->fd_index[fd];
	i_assert(index >= 0 && (unsigned int) index < ctx->fds_count);

#ifdef DEBUG
	if (!closed) {
		/* io_remove() is required to be called before fd is closed.
		   This is required by epoll/kqueue, but since poll is more
		   commonly used while developing, this check here should catch
		   the error early enough not to cause problems for kqueue
		   users. */
		if (fcntl(io->fd, F_GETFD, 0) < 0) {
			if (errno == EBADF)
				i_panic("io_remove(%d) called too late", io->fd);
			else
				i_error("fcntl(%d, F_GETFD) failed: %m", io->fd);
		}
	}
#endif
	i_free(io);

	if ((condition & IO_READ) != 0) {
		ctx->fds[index].events &= ENUM_NEGATE(POLLIN | POLLPRI);
		ctx->fds[index].revents &= ENUM_NEGATE(POLLIN | POLLPRI);
	}
	if ((condition & IO_WRITE) != 0) {
		ctx->fds[index].events &= ENUM_NEGATE(POLLOUT);
		ctx->fds[index].revents &= ENUM_NEGATE(POLLOUT);
	}

	if ((ctx->fds[index].events & (POLLIN|POLLOUT)) == 0) {
		/* remove the whole pollfd */
		ctx->fd_index[ctx->fds[index].fd] = -1;
		if (--ctx->fds_pos == (unsigned int) index)
                        return; /* removing last one */

                /* move the last pollfd over the removed one */
		ctx->fds[index] = ctx->fds[ctx->fds_pos];
		ctx->fd_index[ctx->fds[index].fd] = index;
	}
}

void io_loop_handler_run_internal(struct ioloop *ioloop)
{
	struct ioloop_handler_context *ctx = ioloop->handler_context;
        struct pollfd *pollfd;
        struct timeval tv;
	struct io_file *io;
	int msecs, ret;
	bool call;

        /* get the time left for next timeout task */
	msecs = io_loop_run_get_wait_time(ioloop, &tv);
#ifdef _AIX
	if (msecs > 1000) {
		/* AIX seems to check IO_POLL_ERRORs only at the beginning of
		   the poll() call, not during it. keep timeouts short enough
		   so that we'll notice them pretty quickly. */
		msecs = 1000;
	}
#endif

	ret = poll(ctx->fds, ctx->fds_pos, msecs);
	if (ret < 0 && errno != EINTR)
		i_fatal("poll(): %m");

	/* execute timeout handlers */
        io_loop_handle_timeouts(ioloop);

	if (ret <= 0 || !ioloop->running) {
                /* no I/O events */
		return;
	}

	io = ioloop->io_files;
	for (; io != NULL && ret > 0; io = ioloop->next_io_file) {
		ioloop->next_io_file = io->next;

		if (io->fd == -1) {
			/* io_add_istream() without fd */
			continue;
		}
		pollfd = &ctx->fds[ctx->fd_index[io->fd]];
		if (pollfd->revents != 0) {
			if (pollfd->revents & POLLNVAL) {
				i_error("invalid I/O fd %d, callback %p",
					io->fd, (void *) io->io.callback);
				pollfd->events = 0;
				pollfd->revents = 0;
				call = TRUE;
			} else if ((io->io.condition &
				    (IO_READ|IO_WRITE)) == (IO_READ|IO_WRITE)) {
				call = TRUE;
				pollfd->revents = 0;
			} else if ((io->io.condition & IO_READ) != 0) {
				call = (pollfd->revents & IO_POLL_INPUT) != 0;
				pollfd->revents &= ENUM_NEGATE(IO_POLL_INPUT);
			} else if ((io->io.condition & IO_WRITE) != 0) {
				call = (pollfd->revents & IO_POLL_OUTPUT) != 0;
				pollfd->revents &= ENUM_NEGATE(IO_POLL_OUTPUT);
			} else if ((io->io.condition & IO_ERROR) != 0) {
				call = (pollfd->revents & IO_POLL_ERROR) != 0;
				pollfd->revents &= ENUM_NEGATE(IO_POLL_ERROR);
			} else {
				call = FALSE;
			}

			if (pollfd->revents == 0)
				ret--;

			if (call) {
				io_loop_call_io(&io->io);
				if (!ioloop->running)
					return;
			}
		}
	}
}

#endif