diff options
| -rw-r--r-- | Makefile | 8 | ||||
| -rw-r--r-- | ot_event.c | 332 | ||||
| -rw-r--r-- | ot_event.h | 39 | ||||
| -rw-r--r-- | tests/runner.c | 1 | ||||
| -rw-r--r-- | tests/t_event.c | 222 | ||||
| -rw-r--r-- | tests/test.h | 1 |
6 files changed, 599 insertions, 4 deletions
| @@ -68,8 +68,8 @@ CFLAGS+=-I$(LIBOWFAT_HEADERS) -DGIT_VERSION=$(GIT_VERSION) -Wall -pipe -pthread | |||
| 68 | LDFLAGS+=-L$(LIBOWFAT_LIBRARY) -lowfat -pthread | 68 | LDFLAGS+=-L$(LIBOWFAT_LIBRARY) -lowfat -pthread |
| 69 | 69 | ||
| 70 | BINARY =opentracker | 70 | BINARY =opentracker |
| 71 | HEADERS=trackerlogic.h ot_urlencoded.h ot_batch.h ot_buf.h ot_fdstate.h ot_hex.h ot_ip.h ot_config.h ot_scan.h ot_socket.h ot_mutex.h ot_random.h ot_stats.h ot_vector.h ot_clean.h ot_udp.h ot_iovec.h ot_fullscrape.h ot_accesslist.h ot_http.h ot_livesync.h ot_rijndael.h | 71 | HEADERS=trackerlogic.h ot_urlencoded.h ot_batch.h ot_buf.h ot_event.h ot_fdstate.h ot_hex.h ot_ip.h ot_config.h ot_scan.h ot_socket.h ot_mutex.h ot_random.h ot_stats.h ot_vector.h ot_clean.h ot_udp.h ot_iovec.h ot_fullscrape.h ot_accesslist.h ot_http.h ot_livesync.h ot_rijndael.h |
| 72 | SOURCES=opentracker.c trackerlogic.c ot_urlencoded.c ot_batch.c ot_buf.c ot_fdstate.c ot_hex.c ot_ip.c ot_config.c ot_scan.c ot_socket.c ot_mutex.c ot_random.c ot_stats.c ot_vector.c ot_clean.c ot_udp.c ot_iovec.c ot_fullscrape.c ot_accesslist.c ot_http.c ot_livesync.c ot_rijndael.c | 72 | SOURCES=opentracker.c trackerlogic.c ot_urlencoded.c ot_batch.c ot_buf.c ot_event.c ot_fdstate.c ot_hex.c ot_ip.c ot_config.c ot_scan.c ot_socket.c ot_mutex.c ot_random.c ot_stats.c ot_vector.c ot_clean.c ot_udp.c ot_iovec.c ot_fullscrape.c ot_accesslist.c ot_http.c ot_livesync.c ot_rijndael.c |
| 73 | SOURCES_proxy=proxy.c ot_batch.c ot_fdstate.c ot_vector.c ot_mutex.c ot_random.c ot_scan.c ot_ip.c ot_socket.c | 73 | SOURCES_proxy=proxy.c ot_batch.c ot_fdstate.c ot_vector.c ot_mutex.c ot_random.c ot_scan.c ot_ip.c ot_socket.c |
| 74 | 74 | ||
| 75 | OBJECTS = $(SOURCES:%.c=%.o) | 75 | OBJECTS = $(SOURCES:%.c=%.o) |
| @@ -119,8 +119,8 @@ $(OBJECTS_proxy_debug): $(HEADERS) | |||
| 119 | # has to. tests/stubs.c supplies the few symbols those modules reference but | 119 | # has to. tests/stubs.c supplies the few symbols those modules reference but |
| 120 | # the tests never reach. | 120 | # the tests never reach. |
| 121 | TESTBIN=tests/runner | 121 | TESTBIN=tests/runner |
| 122 | TEST_SOURCES=tests/runner.c tests/test.c tests/stubs.c tests/t_urlencoded.c tests/t_iovec.c tests/t_vector.c tests/t_random.c tests/t_hex.c tests/t_accesslist.c tests/t_scan.c tests/t_buf.c tests/t_ip.c tests/t_socket.c tests/t_mutex.c tests/t_batch.c tests/t_fdstate.c | 122 | TEST_SOURCES=tests/runner.c tests/test.c tests/stubs.c tests/t_urlencoded.c tests/t_iovec.c tests/t_vector.c tests/t_random.c tests/t_hex.c tests/t_accesslist.c tests/t_scan.c tests/t_buf.c tests/t_ip.c tests/t_socket.c tests/t_mutex.c tests/t_batch.c tests/t_fdstate.c tests/t_event.c |
| 123 | TEST_UNITS=ot_urlencoded.c ot_batch.c ot_fdstate.c ot_buf.c ot_ip.c ot_socket.c ot_iovec.c ot_vector.c ot_random.c ot_hex.c ot_scan.c ot_accesslist.c ot_mutex.c | 123 | TEST_UNITS=ot_urlencoded.c ot_event.c ot_batch.c ot_fdstate.c ot_buf.c ot_ip.c ot_socket.c ot_iovec.c ot_vector.c ot_random.c ot_hex.c ot_scan.c ot_accesslist.c ot_mutex.c |
| 124 | TEST_HEADERS=tests/test.h $(HEADERS) | 124 | TEST_HEADERS=tests/test.h $(HEADERS) |
| 125 | # Tests reach code that sits behind feature flags, so the units under test are | 125 | # Tests reach code that sits behind feature flags, so the units under test are |
| 126 | # built with those features enabled. A test binary is not a production binary. | 126 | # built with those features enabled. A test binary is not a production binary. |
diff --git a/ot_event.c b/ot_event.c new file mode 100644 index 0000000..d24227d --- /dev/null +++ b/ot_event.c | |||
| @@ -0,0 +1,332 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. | ||
| 3 | |||
| 4 | $id$ */ | ||
| 5 | |||
| 6 | /* System */ | ||
| 7 | #include <errno.h> | ||
| 8 | #include <stdlib.h> | ||
| 9 | #include <string.h> | ||
| 10 | #include <unistd.h> | ||
| 11 | |||
| 12 | /* Opentracker */ | ||
| 13 | #include "ot_event.h" | ||
| 14 | |||
| 15 | /* One caller waits, so the scratch the mechanism needs is kept here rather | ||
| 16 | than allocated per call. */ | ||
| 17 | |||
| 18 | /* poll works everywhere and is what the BSDs get until kqueue is written. | ||
| 19 | Defining OT_EVENT_FORCE_POLL picks it on a platform that would otherwise | ||
| 20 | use something better, which is how both are exercised by the tests. */ | ||
| 21 | #if defined(OT_EVENT_FORCE_POLL) | ||
| 22 | #define OT_EVENT_POLL | ||
| 23 | #elif defined(OT_EVENT_FORCE_KQUEUE) | ||
| 24 | #define OT_EVENT_KQUEUE | ||
| 25 | #elif defined(__linux__) | ||
| 26 | #define OT_EVENT_EPOLL | ||
| 27 | #elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__APPLE__) | ||
| 28 | #define OT_EVENT_KQUEUE | ||
| 29 | #else | ||
| 30 | #define OT_EVENT_POLL | ||
| 31 | #endif | ||
| 32 | |||
| 33 | #ifdef OT_EVENT_EPOLL | ||
| 34 | /* ------------------------------------------------------------- epoll */ | ||
| 35 | |||
| 36 | #include <sys/epoll.h> | ||
| 37 | |||
| 38 | static int g_epoll = -1; | ||
| 39 | static struct epoll_event *g_ready; | ||
| 40 | static int g_ready_space; | ||
| 41 | |||
| 42 | const char *event_mechanism(void) { return "epoll"; } | ||
| 43 | |||
| 44 | int event_init(void) { | ||
| 45 | g_ready = NULL; | ||
| 46 | g_ready_space = 0; | ||
| 47 | return (g_epoll = epoll_create1(EPOLL_CLOEXEC)) == -1 ? -1 : 0; | ||
| 48 | } | ||
| 49 | |||
| 50 | void event_deinit(void) { | ||
| 51 | if (g_epoll != -1) | ||
| 52 | close(g_epoll); | ||
| 53 | free(g_ready); | ||
| 54 | g_epoll = -1; | ||
| 55 | g_ready = NULL; | ||
| 56 | g_ready_space = 0; | ||
| 57 | } | ||
| 58 | |||
| 59 | static int event_ctl(int op, int fd, int what, uint32_t generation) { | ||
| 60 | struct epoll_event ev; | ||
| 61 | |||
| 62 | memset(&ev, 0, sizeof(ev)); | ||
| 63 | ev.events = (what & EVENT_READ ? (uint32_t)EPOLLIN : 0) | (what & EVENT_WRITE ? (uint32_t)EPOLLOUT : 0); | ||
| 64 | /* The generation rides along inside the kernel, so an event says which | ||
| 65 | connection it belongs to without anybody looking it up */ | ||
| 66 | ev.data.u64 = ((uint64_t)generation << 32) | (uint32_t)fd; | ||
| 67 | return epoll_ctl(g_epoll, op, fd, &ev); | ||
| 68 | } | ||
| 69 | |||
| 70 | int event_add(int fd, int what, uint32_t generation) { return event_ctl(EPOLL_CTL_ADD, fd, what, generation); } | ||
| 71 | int event_modify(int fd, int what, uint32_t generation) { return event_ctl(EPOLL_CTL_MOD, fd, what, generation); } | ||
| 72 | int event_del(int fd) { return epoll_ctl(g_epoll, EPOLL_CTL_DEL, fd, NULL); } | ||
| 73 | |||
| 74 | int event_wait(ot_event *out, int max, int timeout_ms) { | ||
| 75 | int count, i; | ||
| 76 | |||
| 77 | if (max > g_ready_space) { | ||
| 78 | struct epoll_event *grown = realloc(g_ready, (size_t)max * sizeof(struct epoll_event)); | ||
| 79 | |||
| 80 | if (!grown) | ||
| 81 | return -1; | ||
| 82 | g_ready = grown; | ||
| 83 | g_ready_space = max; | ||
| 84 | } | ||
| 85 | |||
| 86 | while ((count = epoll_wait(g_epoll, g_ready, max, timeout_ms)) == -1 && errno == EINTR) | ||
| 87 | ; | ||
| 88 | for (i = 0; i < count; ++i) { | ||
| 89 | uint32_t events = g_ready[i].events; | ||
| 90 | |||
| 91 | out[i].fd = (int)(uint32_t)g_ready[i].data.u64; | ||
| 92 | out[i].generation = (uint32_t)(g_ready[i].data.u64 >> 32); | ||
| 93 | /* A hangup or an error is reported as both, so whichever side the caller | ||
| 94 | was waiting on gets to notice */ | ||
| 95 | out[i].what = (events & (EPOLLIN | EPOLLHUP | EPOLLERR) ? EVENT_READ : 0) | | ||
| 96 | (events & (EPOLLOUT | EPOLLHUP | EPOLLERR) ? EVENT_WRITE : 0); | ||
| 97 | } | ||
| 98 | return count; | ||
| 99 | } | ||
| 100 | |||
| 101 | #endif /* OT_EVENT_EPOLL */ | ||
| 102 | |||
| 103 | #ifdef OT_EVENT_KQUEUE | ||
| 104 | /* ------------------------------------------------------------ kqueue */ | ||
| 105 | |||
| 106 | #include <sys/event.h> | ||
| 107 | #include <sys/time.h> | ||
| 108 | #include <sys/types.h> | ||
| 109 | |||
| 110 | static int g_kqueue = -1; | ||
| 111 | static struct kevent *g_ready; | ||
| 112 | static int g_ready_space; | ||
| 113 | |||
| 114 | const char *event_mechanism(void) { return "kqueue"; } | ||
| 115 | |||
| 116 | int event_init(void) { | ||
| 117 | g_ready = NULL; | ||
| 118 | g_ready_space = 0; | ||
| 119 | return (g_kqueue = kqueue()) == -1 ? -1 : 0; | ||
| 120 | } | ||
| 121 | |||
| 122 | void event_deinit(void) { | ||
| 123 | if (g_kqueue != -1) | ||
| 124 | close(g_kqueue); | ||
| 125 | free(g_ready); | ||
| 126 | g_kqueue = -1; | ||
| 127 | g_ready = NULL; | ||
| 128 | g_ready_space = 0; | ||
| 129 | } | ||
| 130 | |||
| 131 | /* kqueue keeps one registration per filter, so reading and writing are two | ||
| 132 | entries. A change of interest is an add of the one wanted and a delete | ||
| 133 | of the one not. The generation travels in udata, which the kernel passes | ||
| 134 | back untouched. */ | ||
| 135 | static int event_change(int fd, int what, uint32_t generation, int deleting) { | ||
| 136 | struct kevent change[2], result[2]; | ||
| 137 | void *udata = (void *)(uintptr_t)(((uint64_t)generation << 32) | (uint32_t)fd); | ||
| 138 | int succeeded = 0, count, i; | ||
| 139 | |||
| 140 | EV_SET(&change[0], (uintptr_t)fd, EVFILT_READ, | ||
| 141 | EV_RECEIPT | ((deleting || !(what & EVENT_READ)) ? EV_DELETE : EV_ADD), 0, 0, udata); | ||
| 142 | EV_SET(&change[1], (uintptr_t)fd, EVFILT_WRITE, | ||
| 143 | EV_RECEIPT | ((deleting || !(what & EVENT_WRITE)) ? EV_DELETE : EV_ADD), 0, 0, udata); | ||
| 144 | |||
| 145 | if ((count = kevent(g_kqueue, change, 2, result, 2, NULL)) == -1) | ||
| 146 | return -1; | ||
| 147 | |||
| 148 | for (i = 0; i < count && i < 2; ++i) { | ||
| 149 | if (!result[i].data) { | ||
| 150 | ++succeeded; | ||
| 151 | continue; | ||
| 152 | } | ||
| 153 | /* Dropping interest we never registered is not a failure */ | ||
| 154 | if ((change[i].flags & EV_DELETE) && result[i].data == ENOENT) | ||
| 155 | continue; | ||
| 156 | errno = (int)result[i].data; | ||
| 157 | return -1; | ||
| 158 | } | ||
| 159 | |||
| 160 | /* Nothing at all was registered or removed, so the descriptor is unknown */ | ||
| 161 | if (!succeeded) { | ||
| 162 | errno = ENOENT; | ||
| 163 | return -1; | ||
| 164 | } | ||
| 165 | return 0; | ||
| 166 | } | ||
| 167 | |||
| 168 | int event_add(int fd, int what, uint32_t generation) { return event_change(fd, what, generation, 0); } | ||
| 169 | int event_modify(int fd, int what, uint32_t generation) { return event_change(fd, what, generation, 0); } | ||
| 170 | int event_del(int fd) { return event_change(fd, 0, 0, 1); } | ||
| 171 | |||
| 172 | int event_wait(ot_event *out, int max, int timeout_ms) { | ||
| 173 | struct timespec timeout; | ||
| 174 | struct timespec *deadline = NULL; | ||
| 175 | int count, i; | ||
| 176 | |||
| 177 | if (max > g_ready_space) { | ||
| 178 | struct kevent *grown = realloc(g_ready, (size_t)max * sizeof(struct kevent)); | ||
| 179 | |||
| 180 | if (!grown) | ||
| 181 | return -1; | ||
| 182 | g_ready = grown; | ||
| 183 | g_ready_space = max; | ||
| 184 | } | ||
| 185 | |||
| 186 | if (timeout_ms >= 0) { | ||
| 187 | timeout.tv_sec = timeout_ms / 1000; | ||
| 188 | timeout.tv_nsec = (long)(timeout_ms % 1000) * 1000000; | ||
| 189 | deadline = &timeout; | ||
| 190 | } | ||
| 191 | |||
| 192 | while ((count = kevent(g_kqueue, NULL, 0, g_ready, max, deadline)) == -1 && errno == EINTR) | ||
| 193 | ; | ||
| 194 | for (i = 0; i < count; ++i) { | ||
| 195 | uint64_t udata = (uint64_t)(uintptr_t)g_ready[i].udata; | ||
| 196 | int eof = (g_ready[i].flags & (EV_EOF | EV_ERROR)) != 0; | ||
| 197 | |||
| 198 | out[i].fd = (int)(uint32_t)udata; | ||
| 199 | out[i].generation = (uint32_t)(udata >> 32); | ||
| 200 | out[i].what = ((g_ready[i].filter == EVFILT_READ || eof) ? EVENT_READ : 0) | | ||
| 201 | ((g_ready[i].filter == EVFILT_WRITE || eof) ? EVENT_WRITE : 0); | ||
| 202 | } | ||
| 203 | return count; | ||
| 204 | } | ||
| 205 | |||
| 206 | #endif /* OT_EVENT_KQUEUE */ | ||
| 207 | |||
| 208 | #ifdef OT_EVENT_POLL | ||
| 209 | /* -------------------------------------------------------------- poll */ | ||
| 210 | |||
| 211 | #include <poll.h> | ||
| 212 | |||
| 213 | /* poll wants its descriptors packed, so the registered ones live in one | ||
| 214 | array and a second maps a descriptor number to its place in it. */ | ||
| 215 | static struct pollfd *g_pfds; | ||
| 216 | static uint32_t *g_generation; | ||
| 217 | static int *g_where; | ||
| 218 | static int g_count, g_space, g_where_space; | ||
| 219 | |||
| 220 | const char *event_mechanism(void) { return "poll"; } | ||
| 221 | |||
| 222 | int event_init(void) { | ||
| 223 | g_pfds = NULL, g_generation = NULL, g_where = NULL; | ||
| 224 | g_count = g_space = g_where_space = 0; | ||
| 225 | return 0; | ||
| 226 | } | ||
| 227 | |||
| 228 | void event_deinit(void) { | ||
| 229 | free(g_pfds), free(g_generation), free(g_where); | ||
| 230 | g_pfds = NULL, g_generation = NULL, g_where = NULL; | ||
| 231 | g_count = g_space = g_where_space = 0; | ||
| 232 | } | ||
| 233 | |||
| 234 | static short event_to_poll(int what) { | ||
| 235 | return (short)((what & EVENT_READ ? POLLIN : 0) | (what & EVENT_WRITE ? POLLOUT : 0)); | ||
| 236 | } | ||
| 237 | |||
| 238 | static int event_room_for(int fd) { | ||
| 239 | if (fd >= g_where_space) { | ||
| 240 | int space = g_where_space ? g_where_space : 64; | ||
| 241 | int *grown; | ||
| 242 | |||
| 243 | while (space <= fd) | ||
| 244 | space *= 2; | ||
| 245 | if (!(grown = realloc(g_where, (size_t)space * sizeof(int)))) | ||
| 246 | return -1; | ||
| 247 | while (g_where_space < space) | ||
| 248 | grown[g_where_space++] = -1; | ||
| 249 | g_where = grown; | ||
| 250 | } | ||
| 251 | |||
| 252 | if (g_count == g_space) { | ||
| 253 | int space = g_space ? g_space * 2 : 64; | ||
| 254 | struct pollfd *pfds = realloc(g_pfds, (size_t)space * sizeof(struct pollfd)); | ||
| 255 | uint32_t *gens; | ||
| 256 | |||
| 257 | if (!pfds) | ||
| 258 | return -1; | ||
| 259 | g_pfds = pfds; | ||
| 260 | if (!(gens = realloc(g_generation, (size_t)space * sizeof(uint32_t)))) | ||
| 261 | return -1; | ||
| 262 | g_generation = gens; | ||
| 263 | g_space = space; | ||
| 264 | } | ||
| 265 | return 0; | ||
| 266 | } | ||
| 267 | |||
| 268 | int event_add(int fd, int what, uint32_t generation) { | ||
| 269 | if (fd < 0 || event_room_for(fd) == -1) | ||
| 270 | return -1; | ||
| 271 | if (g_where[fd] != -1) { | ||
| 272 | errno = EEXIST; | ||
| 273 | return -1; | ||
| 274 | } | ||
| 275 | |||
| 276 | g_pfds[g_count].fd = fd; | ||
| 277 | g_pfds[g_count].events = event_to_poll(what); | ||
| 278 | g_pfds[g_count].revents = 0; | ||
| 279 | g_generation[g_count] = generation; | ||
| 280 | g_where[fd] = g_count++; | ||
| 281 | return 0; | ||
| 282 | } | ||
| 283 | |||
| 284 | int event_modify(int fd, int what, uint32_t generation) { | ||
| 285 | if (fd < 0 || fd >= g_where_space || g_where[fd] == -1) { | ||
| 286 | errno = ENOENT; | ||
| 287 | return -1; | ||
| 288 | } | ||
| 289 | g_pfds[g_where[fd]].events = event_to_poll(what); | ||
| 290 | g_generation[g_where[fd]] = generation; | ||
| 291 | return 0; | ||
| 292 | } | ||
| 293 | |||
| 294 | int event_del(int fd) { | ||
| 295 | int at; | ||
| 296 | |||
| 297 | if (fd < 0 || fd >= g_where_space || (at = g_where[fd]) == -1) { | ||
| 298 | errno = ENOENT; | ||
| 299 | return -1; | ||
| 300 | } | ||
| 301 | |||
| 302 | /* The last one moves into the gap, so the array stays packed */ | ||
| 303 | g_pfds[at] = g_pfds[--g_count]; | ||
| 304 | g_generation[at] = g_generation[g_count]; | ||
| 305 | g_where[g_pfds[at].fd] = at; | ||
| 306 | g_where[fd] = -1; | ||
| 307 | return 0; | ||
| 308 | } | ||
| 309 | |||
| 310 | int event_wait(ot_event *out, int max, int timeout_ms) { | ||
| 311 | int ready, found = 0, i; | ||
| 312 | |||
| 313 | while ((ready = poll(g_pfds, (nfds_t)g_count, timeout_ms)) == -1 && errno == EINTR) | ||
| 314 | ; | ||
| 315 | if (ready <= 0) | ||
| 316 | return ready; | ||
| 317 | |||
| 318 | for (i = 0; i < g_count && found < max; ++i) { | ||
| 319 | short revents = g_pfds[i].revents; | ||
| 320 | |||
| 321 | if (!revents) | ||
| 322 | continue; | ||
| 323 | out[found].fd = g_pfds[i].fd; | ||
| 324 | out[found].generation = g_generation[i]; | ||
| 325 | out[found].what = (revents & (POLLIN | POLLHUP | POLLERR | POLLNVAL) ? EVENT_READ : 0) | | ||
| 326 | (revents & (POLLOUT | POLLHUP | POLLERR | POLLNVAL) ? EVENT_WRITE : 0); | ||
| 327 | ++found; | ||
| 328 | } | ||
| 329 | return found; | ||
| 330 | } | ||
| 331 | |||
| 332 | #endif /* OT_EVENT_POLL */ | ||
diff --git a/ot_event.h b/ot_event.h new file mode 100644 index 0000000..c1627ab --- /dev/null +++ b/ot_event.h | |||
| @@ -0,0 +1,39 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. | ||
| 3 | |||
| 4 | $id$ */ | ||
| 5 | |||
| 6 | #ifndef OT_EVENT_H__ | ||
| 7 | #define OT_EVENT_H__ | ||
| 8 | |||
| 9 | #include <stdint.h> | ||
| 10 | |||
| 11 | /* Which descriptors have become readable or writable, asked of whichever | ||
| 12 | mechanism the kernel offers. The generation a descriptor was registered | ||
| 13 | with is handed back with every event, so a caller can tell an event for | ||
| 14 | the connection it is holding from one for a connection that has since been | ||
| 15 | closed and had its number reused. */ | ||
| 16 | |||
| 17 | enum { EVENT_READ = 1, EVENT_WRITE = 2 }; | ||
| 18 | |||
| 19 | typedef struct { | ||
| 20 | int fd; | ||
| 21 | uint32_t generation; | ||
| 22 | int what; | ||
| 23 | } ot_event; | ||
| 24 | |||
| 25 | int event_init(void); | ||
| 26 | void event_deinit(void); | ||
| 27 | |||
| 28 | int event_add(int fd, int what, uint32_t generation); | ||
| 29 | int event_modify(int fd, int what, uint32_t generation); | ||
| 30 | int event_del(int fd); | ||
| 31 | |||
| 32 | /* Waits at most timeout_ms, or indefinitely when that is negative, and fills | ||
| 33 | out with up to max ready descriptors. Returns how many, or -1. */ | ||
| 34 | int event_wait(ot_event *out, int max, int timeout_ms); | ||
| 35 | |||
| 36 | /* Which mechanism was compiled in, for the version string */ | ||
| 37 | const char *event_mechanism(void); | ||
| 38 | |||
| 39 | #endif | ||
diff --git a/tests/runner.c b/tests/runner.c index 9e5c99f..c2350c3 100644 --- a/tests/runner.c +++ b/tests/runner.c | |||
| @@ -26,6 +26,7 @@ static const struct module modules[] = {{"ot_urlencoded", test_urlencoded}, | |||
| 26 | {"ot_mutex", test_mutex}, | 26 | {"ot_mutex", test_mutex}, |
| 27 | {"ot_batch", test_batch}, | 27 | {"ot_batch", test_batch}, |
| 28 | {"ot_fdstate", test_fdstate}, | 28 | {"ot_fdstate", test_fdstate}, |
| 29 | {"ot_event", test_event}, | ||
| 29 | {"ot_accesslist", test_accesslist}, | 30 | {"ot_accesslist", test_accesslist}, |
| 30 | {NULL, NULL}}; | 31 | {NULL, NULL}}; |
| 31 | 32 | ||
diff --git a/tests/t_event.c b/tests/t_event.c new file mode 100644 index 0000000..306f8cd --- /dev/null +++ b/tests/t_event.c | |||
| @@ -0,0 +1,222 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. | ||
| 3 | |||
| 4 | $id$ */ | ||
| 5 | |||
| 6 | /* System */ | ||
| 7 | #include <string.h> | ||
| 8 | #include <sys/socket.h> | ||
| 9 | #include <sys/time.h> | ||
| 10 | #include <unistd.h> | ||
| 11 | |||
| 12 | /* Opentracker */ | ||
| 13 | #include "ot_event.h" | ||
| 14 | #include "test.h" | ||
| 15 | |||
| 16 | /* Against real socket pairs: only the kernel can say whether a descriptor is | ||
| 17 | ready, so there is nothing useful to mock here. */ | ||
| 18 | |||
| 19 | #define EVENTS 16 | ||
| 20 | |||
| 21 | static int sv[2]; | ||
| 22 | static ot_event ready[EVENTS]; | ||
| 23 | |||
| 24 | static void pair_open(void) { | ||
| 25 | if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) | ||
| 26 | sv[0] = sv[1] = -1; | ||
| 27 | } | ||
| 28 | |||
| 29 | static void pair_close(void) { | ||
| 30 | if (sv[0] != -1) | ||
| 31 | close(sv[0]); | ||
| 32 | if (sv[1] != -1) | ||
| 33 | close(sv[1]); | ||
| 34 | sv[0] = sv[1] = -1; | ||
| 35 | } | ||
| 36 | |||
| 37 | static int wait_for(int fd, int what, int timeout_ms, uint32_t *generation) { | ||
| 38 | int count = event_wait(ready, EVENTS, timeout_ms), i; | ||
| 39 | |||
| 40 | for (i = 0; i < count; ++i) | ||
| 41 | if (ready[i].fd == fd && (ready[i].what & what)) { | ||
| 42 | if (generation) | ||
| 43 | *generation = ready[i].generation; | ||
| 44 | return 1; | ||
| 45 | } | ||
| 46 | return 0; | ||
| 47 | } | ||
| 48 | |||
| 49 | static unsigned long elapsed_ms(struct timeval *since) { | ||
| 50 | struct timeval now; | ||
| 51 | |||
| 52 | gettimeofday(&now, NULL); | ||
| 53 | return (unsigned long)((now.tv_sec - since->tv_sec) * 1000 + (now.tv_usec - since->tv_usec) / 1000); | ||
| 54 | } | ||
| 55 | |||
| 56 | static void case_readable(void) { | ||
| 57 | uint32_t generation = 0; | ||
| 58 | |||
| 59 | test_case("a descriptor with something to read is reported, with its generation"); | ||
| 60 | pair_open(); | ||
| 61 | CHECK(sv[0] != -1); | ||
| 62 | CHECK_INT(event_add(sv[0], EVENT_READ, 4711), 0); | ||
| 63 | CHECK_INT(write(sv[1], "x", 1), 1); | ||
| 64 | CHECK_INT(wait_for(sv[0], EVENT_READ, 1000, &generation), 1); | ||
| 65 | CHECK_INT(generation, 4711); | ||
| 66 | |||
| 67 | test_case("and keeps being reported until it is read"); | ||
| 68 | CHECK_INT(wait_for(sv[0], EVENT_READ, 1000, NULL), 1); | ||
| 69 | { | ||
| 70 | char scratch[4]; | ||
| 71 | CHECK_INT(read(sv[0], scratch, sizeof(scratch)), 1); | ||
| 72 | } | ||
| 73 | CHECK_INT(wait_for(sv[0], EVENT_READ, 50, NULL), 0); | ||
| 74 | |||
| 75 | CHECK_INT(event_del(sv[0]), 0); | ||
| 76 | pair_close(); | ||
| 77 | } | ||
| 78 | |||
| 79 | static void case_writable(void) { | ||
| 80 | test_case("a descriptor with room to write is reported"); | ||
| 81 | pair_open(); | ||
| 82 | CHECK(sv[0] != -1); | ||
| 83 | CHECK_INT(event_add(sv[0], EVENT_WRITE, 1), 0); | ||
| 84 | CHECK_INT(wait_for(sv[0], EVENT_WRITE, 1000, NULL), 1); | ||
| 85 | |||
| 86 | test_case("and is not, once we say we no longer care"); | ||
| 87 | CHECK_INT(event_modify(sv[0], EVENT_READ, 1), 0); | ||
| 88 | CHECK_INT(wait_for(sv[0], EVENT_WRITE, 50, NULL), 0); | ||
| 89 | |||
| 90 | CHECK_INT(event_del(sv[0]), 0); | ||
| 91 | pair_close(); | ||
| 92 | } | ||
| 93 | |||
| 94 | static void case_interest_is_separate(void) { | ||
| 95 | test_case("asking only about writing does not report reading"); | ||
| 96 | pair_open(); | ||
| 97 | CHECK(sv[0] != -1); | ||
| 98 | CHECK_INT(event_add(sv[0], EVENT_WRITE, 1), 0); | ||
| 99 | CHECK_INT(write(sv[1], "x", 1), 1); | ||
| 100 | CHECK_INT(wait_for(sv[0], EVENT_READ, 50, NULL), 0); | ||
| 101 | |||
| 102 | test_case("until we ask about it"); | ||
| 103 | CHECK_INT(event_modify(sv[0], EVENT_READ | EVENT_WRITE, 1), 0); | ||
| 104 | CHECK_INT(wait_for(sv[0], EVENT_READ, 1000, NULL), 1); | ||
| 105 | |||
| 106 | CHECK_INT(event_del(sv[0]), 0); | ||
| 107 | pair_close(); | ||
| 108 | } | ||
| 109 | |||
| 110 | static void case_removed_is_silent(void) { | ||
| 111 | test_case("a removed descriptor is not reported again"); | ||
| 112 | pair_open(); | ||
| 113 | CHECK(sv[0] != -1); | ||
| 114 | CHECK_INT(event_add(sv[0], EVENT_READ, 1), 0); | ||
| 115 | CHECK_INT(write(sv[1], "x", 1), 1); | ||
| 116 | CHECK_INT(wait_for(sv[0], EVENT_READ, 1000, NULL), 1); | ||
| 117 | CHECK_INT(event_del(sv[0]), 0); | ||
| 118 | CHECK_INT(wait_for(sv[0], EVENT_READ, 50, NULL), 0); | ||
| 119 | pair_close(); | ||
| 120 | } | ||
| 121 | |||
| 122 | static void case_generations_do_not_mix(void) { | ||
| 123 | int other[2]; | ||
| 124 | uint32_t generation = 0; | ||
| 125 | |||
| 126 | test_case("two descriptors carry their own generations"); | ||
| 127 | pair_open(); | ||
| 128 | CHECK(sv[0] != -1); | ||
| 129 | CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, other), 0); | ||
| 130 | CHECK_INT(event_add(sv[0], EVENT_READ, 100), 0); | ||
| 131 | CHECK_INT(event_add(other[0], EVENT_READ, 200), 0); | ||
| 132 | CHECK_INT(write(sv[1], "x", 1), 1); | ||
| 133 | CHECK_INT(write(other[1], "y", 1), 1); | ||
| 134 | |||
| 135 | CHECK_INT(wait_for(sv[0], EVENT_READ, 1000, &generation), 1); | ||
| 136 | CHECK_INT(generation, 100); | ||
| 137 | CHECK_INT(wait_for(other[0], EVENT_READ, 1000, &generation), 1); | ||
| 138 | CHECK_INT(generation, 200); | ||
| 139 | |||
| 140 | test_case("and re-registering one changes only its own"); | ||
| 141 | CHECK_INT(event_modify(sv[0], EVENT_READ, 101), 0); | ||
| 142 | CHECK_INT(wait_for(sv[0], EVENT_READ, 1000, &generation), 1); | ||
| 143 | CHECK_INT(generation, 101); | ||
| 144 | CHECK_INT(wait_for(other[0], EVENT_READ, 1000, &generation), 1); | ||
| 145 | CHECK_INT(generation, 200); | ||
| 146 | |||
| 147 | event_del(sv[0]); | ||
| 148 | event_del(other[0]); | ||
| 149 | close(other[0]); | ||
| 150 | close(other[1]); | ||
| 151 | pair_close(); | ||
| 152 | } | ||
| 153 | |||
| 154 | /* Removing one of several has to leave the rest addressable: a backend that | ||
| 155 | keeps its descriptors packed moves the last one into the gap, and has to | ||
| 156 | remember where it went. */ | ||
| 157 | static void case_delete_from_the_middle(void) { | ||
| 158 | int pair[3][2]; | ||
| 159 | uint32_t generation = 0; | ||
| 160 | int i; | ||
| 161 | |||
| 162 | test_case("deleting one of three leaves the others reported correctly"); | ||
| 163 | for (i = 0; i < 3; ++i) { | ||
| 164 | CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, pair[i]), 0); | ||
| 165 | CHECK_INT(event_add(pair[i][0], EVENT_READ, (uint32_t)(10 + i)), 0); | ||
| 166 | } | ||
| 167 | CHECK_INT(event_del(pair[1][0]), 0); | ||
| 168 | |||
| 169 | for (i = 0; i < 3; i += 2) { | ||
| 170 | CHECK_INT(write(pair[i][1], "x", 1), 1); | ||
| 171 | CHECK_INT(wait_for(pair[i][0], EVENT_READ, 1000, &generation), 1); | ||
| 172 | CHECK_INT(generation, 10 + i); | ||
| 173 | } | ||
| 174 | |||
| 175 | test_case("and the survivors can still be changed and removed"); | ||
| 176 | CHECK_INT(event_modify(pair[2][0], EVENT_READ, 99), 0); | ||
| 177 | CHECK_INT(wait_for(pair[2][0], EVENT_READ, 1000, &generation), 1); | ||
| 178 | CHECK_INT(generation, 99); | ||
| 179 | CHECK_INT(event_del(pair[0][0]), 0); | ||
| 180 | CHECK_INT(event_del(pair[2][0]), 0); | ||
| 181 | CHECK_INT(wait_for(pair[2][0], EVENT_READ, 50, NULL), 0); | ||
| 182 | |||
| 183 | for (i = 0; i < 3; ++i) | ||
| 184 | close(pair[i][0]), close(pair[i][1]); | ||
| 185 | } | ||
| 186 | |||
| 187 | static void case_timeout(void) { | ||
| 188 | struct timeval started; | ||
| 189 | |||
| 190 | test_case("waiting on nothing comes back empty when the time is up"); | ||
| 191 | gettimeofday(&started, NULL); | ||
| 192 | CHECK_INT(event_wait(ready, EVENTS, 200), 0); | ||
| 193 | { | ||
| 194 | unsigned long took = elapsed_ms(&started); | ||
| 195 | /* Scheduling makes the upper bound generous. The point is that it waited | ||
| 196 | rather than returning at once, and that it came back at all */ | ||
| 197 | CHECK(took >= 150); | ||
| 198 | CHECK(took < 5000); | ||
| 199 | } | ||
| 200 | } | ||
| 201 | |||
| 202 | static void case_unknown_descriptor(void) { | ||
| 203 | test_case("changing or removing one that was never added is refused"); | ||
| 204 | CHECK_INT(event_modify(4096, EVENT_READ, 1), -1); | ||
| 205 | CHECK_INT(event_del(4096), -1); | ||
| 206 | } | ||
| 207 | |||
| 208 | void test_event(void) { | ||
| 209 | CHECK_INT(event_init(), 0); | ||
| 210 | CHECK(event_mechanism() != NULL); | ||
| 211 | |||
| 212 | case_readable(); | ||
| 213 | case_writable(); | ||
| 214 | case_interest_is_separate(); | ||
| 215 | case_removed_is_silent(); | ||
| 216 | case_generations_do_not_mix(); | ||
| 217 | case_delete_from_the_middle(); | ||
| 218 | case_timeout(); | ||
| 219 | case_unknown_descriptor(); | ||
| 220 | |||
| 221 | event_deinit(); | ||
| 222 | } | ||
diff --git a/tests/test.h b/tests/test.h index cebd08a..aa4eb00 100644 --- a/tests/test.h +++ b/tests/test.h | |||
| @@ -89,5 +89,6 @@ void test_socket(void); | |||
| 89 | void test_mutex(void); | 89 | void test_mutex(void); |
| 90 | void test_batch(void); | 90 | void test_batch(void); |
| 91 | void test_fdstate(void); | 91 | void test_fdstate(void); |
| 92 | void test_event(void); | ||
| 92 | 93 | ||
| 93 | #endif | 94 | #endif |
