1 /* IOHandler.c - TransparentIRC 0.1
2 * Copyright (C) 2011-2012 Philipp Kreil (pk910)
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 struct IODescriptor *first_descriptor = NULL;
21 extern struct IOEngine engine_select; /* select system call (should always be useable) */
23 struct IOEngine *engine = NULL;
25 static void iohandler_init_engine() {
29 //found an useable IO engine
30 else if (engine_select.init())
31 engine = &engine_select;
33 //STDERR: found no useable IO engine
37 struct IODescriptor *iohandler_add(int sockfd, IOType type, iohandler_callback *callback) {
38 //just add a new IODescriptor
39 struct IODescriptor *descriptor = calloc(1, sizeof(*descriptor));
40 if(!descriptor) return NULL;
41 descriptor->fd = sockfd;
42 descriptor->type = type;
43 descriptor->state = IO_CLOSED;
44 descriptor->callback = callback;
45 if(type != IOTYPE_TIMER) {
46 descriptor->readbuf.buffer = malloc(IOREADBUFLEN + 2);
47 descriptor->readbuf.bufpos = 0;
48 descriptor->readbuf.buflen = IOREADBUFLEN;
49 descriptor->writebuf.buffer = malloc(IOREADBUFLEN + 2);
50 descriptor->writebuf.bufpos = 0;
51 descriptor->writebuf.buflen = IOREADBUFLEN;
55 iohandler_init_engine();
60 engine->add(descriptor);
62 //add IODescriptor to the list
63 descriptor->prev = NULL;
64 descriptor->next = first_descriptor;
65 first_descriptor->prev = descriptor;
66 first_descriptor = descriptor;
71 static void iohandler_remove(struct IODescriptor *descriptor) {
72 //remove IODescriptor from the list
74 descriptor->prev->next = descriptor->next;
76 first_descriptor = descriptor->next;
78 descriptor->next->prev = descriptor->prev;
79 engine->remove(descriptor);
80 if(descriptor->readbuf.buffer)
81 free(descriptor->readbuf.buffer);
82 if(descriptor->writebuf.buffer)
83 free(descriptor->writebuf.buffer);
87 static void iohandler_increase_iobuf(struct IOBuffer *iobuf, size_t required) {
88 if(iobuf->buflen >= required) return;
89 char *new_buf = realloc(iobuf->buffer, required + 2);
91 iobuf->buffer = new_buf;
92 iobuf->buflen = required;
96 struct IODescriptor *iohandler_timer(timeval timeout, iohandler_callback *callback) {
97 struct IODescriptor *descriptor;
98 descriptor = iohandler_add(-1, IOTYPE_TIMER, callback);
99 if(!descriptor) return NULL;
100 descriptor->timeout = timeout;
101 engine->update(descriptor);
105 struct IODescriptor *iohandler_connect(const char *hostname, unsigned int port, const char *bind, iohandler_callback *callback) {
106 //non-blocking connect
108 struct addrinfo hints, *res, *freeres;
109 struct sockaddr_in *ip4 = NULL;
110 struct sockaddr_in6 *ip6 = NULL;
112 struct sockaddr *dstaddr = NULL;
113 struct IODescriptor *descriptor;
115 memset (&hints, 0, sizeof (hints));
116 hints.ai_family = PF_UNSPEC;
117 hints.ai_socktype = SOCK_STREAM;
118 hints.ai_flags |= AI_CANONNAME;
119 if (getaddrinfo (hostname, NULL, &hints, &res)) {
123 switch (res->ai_family) {
125 ip4 = (struct sockaddr_in *) res->ai_addr;
128 ip6 = (struct sockaddr_in6 *) res->ai_addr;
137 sockfd = socket(AF_INET6, SOCK_STREAM, 0);
138 if(sockfd == -1) return NULL;
140 ip6->sin6_family = AF_INET6;
141 ip6->sin6_port = htons(port);
143 struct sockaddr_in6 *ip6vhost = NULL;
144 if (bind && !getaddrinfo(bind, NULL, &hints, &res)) {
146 switch (res->ai_family) {
148 ip6vhost = (struct sockaddr_in6 *) res->ai_addr;
157 ip6vhost->sin6_family = AF_INET6;
158 ip6vhost->sin6_port = htons(0);
159 bind(sockfd, (struct sockaddr*)ip6vhost, sizeof(*ip6vhost));
161 dstaddr = (struct sockaddr*)ip6;
162 dstaddrlen = sizeof(*ip6);
164 sockfd = socket(AF_INET, SOCK_STREAM, 0);
165 if(sockfd == -1) return NULL;
167 ip4->sin_family = AF_INET;
168 ip4->sin_port = htons(port);
170 struct sockaddr_in *ip4vhost = NULL;
171 if (bind && !getaddrinfo(bind, NULL, &hints, &res)) {
173 switch (res->ai_family) {
175 ip4vhost = (struct sockaddr_in *) res->ai_addr;
184 ip4vhost->sin_family = AF_INET;
185 ip4vhost->sin_port = htons(0);
186 bind(sockfd, (struct sockaddr*)ip4vhost, sizeof(*ip4vhost));
188 dstaddr = (struct sockaddr*)ip4;
189 dstaddrlen = sizeof(*ip4);
192 //make sockfd unblocking
194 flags = fcntl(sockfd, F_GETFL);
195 fcntl(sockfd, F_SETFL, flags|O_NONBLOCK);
196 flags = fcntl(sockfd, F_GETFD);
197 fcntl(sockfd, F_SETFD, flags|FD_CLOEXEC);
199 /* I hope you're using the Win32 backend or something else that
200 * automatically marks the file descriptor non-blocking...
204 descriptor = iohandler_add(sockfd, IOTYPE_CLIENT, callback);
209 connect(sockfd, dstaddr, dstaddrlen); //returns EINPROGRESS here (nonblocking)
210 descriptor->state = IO_CONNECTING;
211 descriptor->read_lines = 1;
212 engine->update(descriptor);
215 struct IODescriptor *iohandler_listen(const char *hostname, unsigned int port, iohandler_callback *callback) {
217 struct addrinfo hints, *res, *freeres;
218 struct sockaddr_in *ip4 = NULL;
219 struct sockaddr_in6 *ip6 = NULL;
220 struct IODescriptor *descriptor;
223 memset (&hints, 0, sizeof (hints));
224 hints.ai_family = PF_UNSPEC;
225 hints.ai_socktype = SOCK_STREAM;
226 hints.ai_flags |= AI_CANONNAME;
227 if (getaddrinfo (hostname, NULL, &hints, &res)) {
231 switch (res->ai_family) {
233 ip4 = (struct sockaddr_in *) res->ai_addr;
236 ip6 = (struct sockaddr_in6 *) res->ai_addr;
245 sockfd = socket(AF_INET6, SOCK_STREAM, 0);
246 if(sockfd == -1) return NULL;
249 setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt));
251 ip6->sin6_family = AF_INET6;
252 ip6->sin6_port = htons(port);
254 bind(sockfd, (struct sockaddr*)ip6, sizeof(*ip6));
256 sockfd = socket(AF_INET, SOCK_STREAM, 0);
257 if(sockfd == -1) return NULL;
260 setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt));
262 ip4->sin_family = AF_INET;
263 ip4->sin_port = htons(port);
265 bind(sockfd, (struct sockaddr*)ip4, sizeof(*ip4));
268 //make sockfd unblocking
270 flags = fcntl(sockfd, F_GETFL);
271 fcntl(sockfd, F_SETFL, flags|O_NONBLOCK);
272 flags = fcntl(sockfd, F_GETFD);
273 fcntl(sockfd, F_SETFD, flags|FD_CLOEXEC);
275 /* I hope you're using the Win32 backend or something else that
276 * automatically marks the file descriptor non-blocking...
280 descriptor = iohandler_add(sockfd, IOTYPE_SERVER, callback);
286 descriptor->state = IO_LISTENING;
287 engine->update(descriptor);
290 void iohandler_write(struct IODescriptor *iofd, const char *line) {
291 size_t linelen = strlen(line);
292 iohandler_send(iofd, line, linelen);
295 void iohandler_send(struct IODescriptor *iofd, const char *data, size_t datalen) {
296 if(iofd->type == IOTYPE_TIMER) return; //can not write to timer? :D
297 if(iofd->writebuf.buflen < iofd->writebuf.bufpos + datalen) {
298 iohandler_increase_iobuf(&iofd->writebuf, iofd->writebuf.bufpos + datalen);
299 if(iofd->writebuf.buflen < iofd->writebuf.bufpos + datalen)
302 memcpy(iofd->writebuf.buffer + iofd->writebuf.bufpos, data, datalen);
303 engine->update(iofd);
306 void iohandler_printf(struct IODescriptor *iofd, const char *text, ...) {
308 char sendBuf[LINELEN];
311 va_start(arg_list, text);
312 pos = vsnprintf(sendBuf, LINELEN - 2, text, arg_list);
314 if (pos < 0 || pos > (LINELEN - 2)) pos = LINELEN - 2;
316 sendBuf[pos+1] = '\0';
317 iohandler_send(iofd, sendBuf, pos+1);
320 void iohandler_try_write(struct IODescriptor *iofd) {
321 if(!iofd->writebuf.bufpos) return;
322 int res = send(iofd->fd, iofd->writebuf.buffer, iofd->writebuf.bufpos, 0);
324 if (errno != EAGAIN) {
325 //error: could not write
328 iofd->writebuf.bufpos -= res;
329 engine->update(iofd);
333 void iohandler_close(struct IODescriptor *iofd) {
335 if(iofd->type == IOTYPE_SERVER || iofd->type == IOTYPE_CLIENT || iofd->type == IOTYPE_STDIN)
337 iohandler_remove(iofd);
340 void iohandler_update(struct IODescriptor *iofd) {
341 engine->update(iofd);
344 static void iohandler_trigger_event(struct IOEvent *event) {
345 if(!event->iofd->callback) return;
346 event->iofd->callback(event);
349 void iohandler_events(struct IODescriptor *iofd, int readable, int writeable) {
350 struct IOEvent callback_event;
351 callback_event.type = IOEVENT_IGNORE;
352 callback_event.iofd = iofd;
353 switch(iofd->state) {
355 if(iofd->type == IOTYPE_TIMER)
356 callback_event.type = IOEVENT_TIMEOUT;
359 callback_event.data.accept_fd = accept(iofd->fd, NULL, 0);
360 if(callback_event.data.accept_fd < 0) {
361 //error: could not accept
363 callback_event.type = IOEVENT_ACCEPT;
366 if(readable) { //could not connect
367 callback_event.type = IOEVENT_NOTCONNECTED;
369 arglen = sizeof(callback_event.errno);
370 if (getsockopt(fd->fd, SOL_SOCKET, SO_ERROR, &callback_event.errno, &arglen) < 0)
371 callback_event.data.errno = errno;
372 iofd->state = IO_CLOSED;
373 } else if(writeable) {
374 callback_event.type = IOEVENT_CONNECTED;
375 iofd->state = IO_CONNECTED;
376 engine->update(iofd);
381 if(iofd->read_lines) {
382 int bytes = recv(iofd->fd, iofd->readbuf.buffer + iofd->readbuf.bufpos, iofd->readbuf.buflen - iofd->readbuf.bufpos, 0);
384 if (errno != EAGAIN) {
385 callback_event.type = IOEVENT_CLOSED;
386 callback_event.data.errno = errno;
389 int i, used_bytes = 0, buffer_offset = 0;
390 iofd->readbuf.bufpos += bytes;
391 callback_event.type = IOEVENT_RECV;
392 for(i = 0; i < iofd->readbuf.bufpos; i++) {
393 if(iofd->readbuf.buffer[i] == '\r' && iofd->readbuf.buffer[i+1] == '\n')
394 iofd->readbuf.buffer[i] = 0;
395 else if(iofd->readbuf.buffer[i] == '\n' || iofd->readbuf.buffer[i] == '\r') {
396 iofd->readbuf.buffer[i] = 0;
397 callback_event.data.recv_str = iofd->readbuf.buffer + used_bytes;
399 iohandler_trigger_event(&callback_event);
400 } else if(i + 1 - used_bytes >= LINELEN) { //512 max
401 iofd->readbuf.buffer[i] = 0;
402 callback_event.data.recv_str = iofd->readbuf.buffer + used_bytes;
403 for(; i < iofd->readbuf.bufpos; i++) { //skip the rest of the line
404 if(iofd->readbuf.buffer[i] == '\n' || (iofd->readbuf.buffer[i] == '\r' && iofd->readbuf.buffer[i+1] != '\n')) {
409 iohandler_trigger_event(&callback_event);
413 if(used_bytes == iofd->readbuf.bufpos)
414 iofd->readbuf.bufpos = 0;
416 memmove(iofd->readbuf.buffer, iofd->readbuf.buffer + used_bytes, iofd->readbuf.bufpos - used_bytes);
418 callback_event.type = IOEVENT_IGNORE;
421 callback_event.type = IOEVENT_READABLE;
424 iohandler_try_write(iofd);
428 if(callback_event.type != IOEVENT_IGNORE)
429 iohandler_trigger_event(&callback_event);
432 void iohandler_poll() {
434 struct timeval timeout;
435 timeout.tv_sec = IO_MAX_TIMEOUT;
437 engine->loop(&timeout);