xref: /openbsd/usr.sbin/nsd/netio.c (revision 3d8817e4)
1 /*
2  * netio.c -- network I/O support.
3  *
4  * Copyright (c) 2001-2006, NLnet Labs. All rights reserved.
5  *
6  * See LICENSE for the license.
7  *
8  */
9 #include <config.h>
10 
11 #include <assert.h>
12 #include <errno.h>
13 #include <sys/time.h>
14 #include <string.h>
15 #include <stdlib.h>
16 
17 #include "netio.h"
18 #include "util.h"
19 
20 
21 #ifndef HAVE_PSELECT
22 int pselect(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
23 	    const struct timespec *timeout, const sigset_t *sigmask);
24 #else
25 #include <sys/select.h>
26 #endif
27 
28 
29 struct netio_handler_list
30 {
31 	netio_handler_list_type *next;
32 	netio_handler_type      *handler;
33 };
34 
35 netio_type *
36 netio_create(region_type *region)
37 {
38 	netio_type *result;
39 
40 	assert(region);
41 
42 	result = (netio_type *) region_alloc(region, sizeof(netio_type));
43 	result->region = region;
44 	result->handlers = NULL;
45 	result->deallocated = NULL;
46 	result->dispatch_next = NULL;
47 	return result;
48 }
49 
50 void
51 netio_add_handler(netio_type *netio, netio_handler_type *handler)
52 {
53 	netio_handler_list_type *elt;
54 
55 	assert(netio);
56 	assert(handler);
57 
58 	if (netio->deallocated) {
59 		/*
60 		 * If we have deallocated handler list elements, reuse
61 		 * the first one.
62 		 */
63 		elt = netio->deallocated;
64 		netio->deallocated = elt->next;
65 	} else {
66 		/*
67 		 * Allocate a new one.
68 		 */
69 		elt = (netio_handler_list_type *) region_alloc(
70 			netio->region, sizeof(netio_handler_list_type));
71 	}
72 
73 	elt->next = netio->handlers;
74 	elt->handler = handler;
75 	netio->handlers = elt;
76 }
77 
78 void
79 netio_remove_handler(netio_type *netio, netio_handler_type *handler)
80 {
81 	netio_handler_list_type **elt_ptr;
82 
83 	assert(netio);
84 	assert(handler);
85 
86 	for (elt_ptr = &netio->handlers; *elt_ptr; elt_ptr = &(*elt_ptr)->next) {
87 		if ((*elt_ptr)->handler == handler) {
88 			netio_handler_list_type *next = (*elt_ptr)->next;
89 			if ((*elt_ptr) == netio->dispatch_next)
90 				netio->dispatch_next = next;
91 			(*elt_ptr)->handler = NULL;
92 			(*elt_ptr)->next = netio->deallocated;
93 			netio->deallocated = *elt_ptr;
94 			*elt_ptr = next;
95 			break;
96 		}
97 	}
98 }
99 
100 const struct timespec *
101 netio_current_time(netio_type *netio)
102 {
103 	assert(netio);
104 
105 	if (!netio->have_current_time) {
106 		struct timeval current_timeval;
107 		if (gettimeofday(&current_timeval, NULL) == -1) {
108 			log_msg(LOG_CRIT, "gettimeofday: %s, aborting.", strerror(errno));
109 			abort();
110 		}
111 		timeval_to_timespec(&netio->cached_current_time, &current_timeval);
112 		netio->have_current_time = 1;
113 	}
114 
115 	return &netio->cached_current_time;
116 }
117 
118 int
119 netio_dispatch(netio_type *netio, const struct timespec *timeout, const sigset_t *sigmask)
120 {
121 	fd_set readfds, writefds, exceptfds;
122 	int max_fd;
123 	int have_timeout = 0;
124 	struct timespec minimum_timeout;
125 	netio_handler_type *timeout_handler = NULL;
126 	netio_handler_list_type *elt;
127 	int rc;
128 	int result = 0;
129 
130 	assert(netio);
131 
132 	/*
133 	 * Clear the cached current time.
134 	 */
135 	netio->have_current_time = 0;
136 
137 	/*
138 	 * Initialize the minimum timeout with the timeout parameter.
139 	 */
140 	if (timeout) {
141 		have_timeout = 1;
142 		memcpy(&minimum_timeout, timeout, sizeof(struct timespec));
143 	}
144 
145 	/*
146 	 * Initialize the fd_sets and timeout based on the handler
147 	 * information.
148 	 */
149 	max_fd = -1;
150 	FD_ZERO(&readfds);
151 	FD_ZERO(&writefds);
152 	FD_ZERO(&exceptfds);
153 
154 	for (elt = netio->handlers; elt; elt = elt->next) {
155 		netio_handler_type *handler = elt->handler;
156 		if (handler->fd >= 0 && handler->fd < (int)FD_SETSIZE) {
157 			if (handler->fd > max_fd) {
158 				max_fd = handler->fd;
159 			}
160 			if (handler->event_types & NETIO_EVENT_READ) {
161 				FD_SET(handler->fd, &readfds);
162 			}
163 			if (handler->event_types & NETIO_EVENT_WRITE) {
164 				FD_SET(handler->fd, &writefds);
165 			}
166 			if (handler->event_types & NETIO_EVENT_EXCEPT) {
167 				FD_SET(handler->fd, &exceptfds);
168 			}
169 		}
170 		if (handler->timeout && (handler->event_types & NETIO_EVENT_TIMEOUT)) {
171 			struct timespec relative;
172 
173 			relative.tv_sec = handler->timeout->tv_sec;
174 			relative.tv_nsec = handler->timeout->tv_nsec;
175 			timespec_subtract(&relative, netio_current_time(netio));
176 
177 			if (!have_timeout ||
178 			    timespec_compare(&relative, &minimum_timeout) < 0)
179 			{
180 				have_timeout = 1;
181 				minimum_timeout.tv_sec = relative.tv_sec;
182 				minimum_timeout.tv_nsec = relative.tv_nsec;
183 				timeout_handler = handler;
184 			}
185 		}
186 	}
187 
188 	if (have_timeout && minimum_timeout.tv_sec < 0) {
189 		/*
190 		 * On negative timeout for a handler, immediatly
191 		 * dispatch the timeout event without checking for
192 		 * other events.
193 		 */
194 		if (timeout_handler && (timeout_handler->event_types & NETIO_EVENT_TIMEOUT)) {
195 			timeout_handler->event_handler(netio, timeout_handler, NETIO_EVENT_TIMEOUT);
196 		}
197 		return result;
198 	}
199 
200 	/* Check for events.  */
201 	rc = pselect(max_fd + 1, &readfds, &writefds, &exceptfds,
202 		     have_timeout ? &minimum_timeout : NULL,
203 		     sigmask);
204 	if (rc == -1) {
205 		if(errno == EINVAL || errno == EACCES || errno == EBADF) {
206 			log_msg(LOG_ERR, "fatal error pselect: %s.",
207 				strerror(errno));
208 			exit(1);
209 		}
210 		return -1;
211 	}
212 
213 	/*
214 	 * Clear the cached current_time (pselect(2) may block for
215 	 * some time so the cached value is likely to be old).
216 	 */
217 	netio->have_current_time = 0;
218 
219 	if (rc == 0) {
220 		/*
221 		 * No events before the minimum timeout expired.
222 		 * Dispatch to handler if interested.
223 		 */
224 		if (timeout_handler && (timeout_handler->event_types & NETIO_EVENT_TIMEOUT)) {
225 			timeout_handler->event_handler(netio, timeout_handler, NETIO_EVENT_TIMEOUT);
226 		}
227 	} else {
228 		/*
229 		 * Dispatch all the events to interested handlers
230 		 * based on the fd_sets.  Note that a handler might
231 		 * deinstall itself, so store the next handler before
232 		 * calling the current handler!
233 		 */
234 		assert(netio->dispatch_next == NULL);
235 		for (elt = netio->handlers; elt && rc; ) {
236 			netio_handler_type *handler = elt->handler;
237 			netio->dispatch_next = elt->next;
238 			if (handler->fd >= 0 && handler->fd < (int)FD_SETSIZE) {
239 				netio_event_types_type event_types
240 					= NETIO_EVENT_NONE;
241 				if (FD_ISSET(handler->fd, &readfds)) {
242 					event_types |= NETIO_EVENT_READ;
243 					FD_CLR(handler->fd, &readfds);
244 					rc--;
245 				}
246 				if (FD_ISSET(handler->fd, &writefds)) {
247 					event_types |= NETIO_EVENT_WRITE;
248 					FD_CLR(handler->fd, &writefds);
249 					rc--;
250 				}
251 				if (FD_ISSET(handler->fd, &exceptfds)) {
252 					event_types |= NETIO_EVENT_EXCEPT;
253 					FD_CLR(handler->fd, &exceptfds);
254 					rc--;
255 				}
256 
257 				if (event_types & handler->event_types) {
258 					handler->event_handler(netio, handler, event_types & handler->event_types);
259 					++result;
260 				}
261 			}
262 			elt = netio->dispatch_next;
263 		}
264 		netio->dispatch_next = NULL;
265 	}
266 
267 	return result;
268 }
269