update the sock->poll on the pollfd that we moved
[cnet.git] / cnet.c
blob6259a8e0052d9b547fd598b25cc1cc02ecc8400b
1 /* Copyright (c) 2007 Zachery Hostens <zacheryph@gmail.com>
3 * Permission is hereby granted, free of charge, to any person obtaining a copy
4 * of this software and associated documentation files (the "Software"), to deal
5 * in the Software without restriction, including without limitation the rights
6 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 * copies of the Software, and to permit persons to whom the Software is
8 * furnished to do so, subject to the following conditions:
10 * The above copyright notice and this permission notice shall be included in
11 * all copies or substantial portions of the Software.
13 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 * THE SOFTWARE.
21 /* this define is for gnu/linux retardation! */
22 #define _GNU_SOURCE
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <netdb.h>
26 #include <poll.h>
27 #include <stdarg.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <time.h>
32 #include <unistd.h>
33 #include <arpa/inet.h>
34 #include <netinet/in.h>
35 #include <sys/socket.h>
36 #include <sys/types.h>
37 #include <sys/uio.h>
38 #include "cnet.h"
40 #define CNET_CLIENT 0x01
41 #define CNET_SERVER 0x02
42 #define CNET_AVAIL 0x04
43 #define CNET_DELETED 0x08
44 #define CNET_CONNECT 0x10 /* socket is connecting */
45 #define CNET_BLOCKED 0x20 /* write's will block */
46 #define CNET_LINEMODE 0x40
48 typedef struct {
49 int fd;
50 int poll;
51 int flags;
53 /* connection information */
54 char *lhost;
55 int lport;
56 char *rhost;
57 int rport;
59 /* buffer data */
60 char *in_buf, *out_buf;
61 int in_len, out_len;
63 /* client data */
64 cnet_handler_t *handler;
65 void *data;
66 } cnet_socket_t;
68 static cnet_socket_t *socks = NULL;
69 static struct pollfd *pollfds = NULL;
70 static int *pollsids = NULL;
71 static int nsocks = 0;
72 static int npollfds = 0;
75 /*** private helper methods ***/
76 static int cnet_grow_sockets (void)
78 int i, newsocks;
79 newsocks = (nsocks / 3) + 16;
80 socks = realloc (socks, (nsocks+newsocks) * sizeof(*socks));
81 pollfds = realloc (pollfds, (nsocks+newsocks) * sizeof(*pollfds));
82 pollsids = realloc (pollsids, (nsocks+newsocks) * sizeof(*pollsids));
83 memset(socks+nsocks, '\0', newsocks*sizeof(*socks));
84 memset (pollfds+newsocks, '\0', sizeof(*pollfds));
85 for (i = 0; i < newsocks; i++) {
86 socks[nsocks+i].fd = -1;
87 socks[nsocks+i].flags = CNET_AVAIL;
90 nsocks += newsocks;
91 return newsocks;
94 /* returns fd. NOT sid */
95 static int cnet_bind (const char *host, int port)
97 int salen, fd;
98 char strport[6];
99 struct sockaddr *sa;
100 struct addrinfo hints, *res = NULL;
102 fd = socket (AF_INET, SOCK_STREAM, 0);
103 if (!host || 0 > fd) return fd;
105 memset (&hints, '\0', sizeof(hints));
106 hints.ai_family = PF_UNSPEC;
107 if (cnet_ip_type(host)) hints.ai_flags = AI_NUMERICHOST;
108 if (port) snprintf (strport, 6, "%d", port);
109 if (getaddrinfo (host, (port ? strport : NULL), &hints, &res)) return -1;
110 sa = res->ai_addr;
111 salen = res->ai_addrlen;
113 if (-1 == bind (fd, sa, sizeof(*sa))) {
114 close (fd);
115 fd = -1;
118 freeaddrinfo (res);
119 return fd;
122 /* create a sid / configure fd */
123 static int cnet_register (int fd, int sockflags, int fdflags)
125 int sid, flags;
126 cnet_socket_t *sock;
128 /* get us our sid */
129 if (0 == nsocks) cnet_grow_sockets();
130 for (sid = 0; sid < nsocks; sid++)
131 if (socks[sid].flags & CNET_AVAIL) break;
132 if (sid == nsocks) return -1;
133 sock = &socks[sid];
134 sock->fd = fd;
135 sock->flags = sockflags;
136 sock->poll = npollfds;
138 /* configure the fd */
139 pollfds[npollfds].fd = sock->fd;
140 pollfds[npollfds].events = fdflags;
141 pollsids[npollfds] = sid;
142 npollfds++;
144 /* set nonblocking */
145 if (-1 == (flags = fcntl (sock->fd, F_GETFL, 0))) return -1;
146 flags |= O_NONBLOCK;
147 fcntl (sock->fd, F_SETFL, flags);
149 return sid;
152 /* fetch the cnet_socket_t related to a sid if avail and not deleted */
153 static cnet_socket_t *cnet_fetch (int sid)
155 if (sid >= nsocks) return NULL;
156 if (socks[sid].flags & (CNET_AVAIL | CNET_DELETED)) return NULL;
157 return &socks[sid];
161 /*** connection handlers ***/
162 static int cnet_on_connect (int sid, cnet_socket_t *sock)
164 sock->flags &= ~(CNET_CONNECT);
165 if (sock->handler->on_connect) sock->handler->on_connect (sid, sock->data);
166 return 0;
169 static int cnet_on_newclient (int sid, cnet_socket_t *sock)
171 int fd, newsid, accepted = 0;
172 char host[40], serv[6];
173 socklen_t salen;
174 cnet_socket_t *newsock;
175 struct sockaddr sa;
176 salen = sizeof(sa);
177 memset (&sa, '\0', salen);
179 while (npollfds < nsocks) {
180 fd = accept (sock->fd, &sa, &salen);
181 if (0 > fd) break;
182 accepted++;
183 newsid = cnet_register (fd, CNET_CLIENT, POLLIN|POLLERR|POLLHUP|POLLNVAL);
184 newsock = &socks[newsid];
186 getnameinfo (&sa, salen, host, 40, serv, 6, NI_NUMERICHOST|NI_NUMERICSERV);
187 newsock->rhost = strdup (host);
188 newsock->rport = atoi (serv);
189 sock->handler->on_newclient (sid, sock->data, newsid, newsock->rhost, newsock->rport);
191 return accepted;
194 static int cnet_on_readable (int sid, cnet_socket_t *sock)
196 char buf[1024], *beg, *end;
197 int len, ret = 0;
199 for (;;) {
200 if (-1 == (len = read(sock->fd, buf, 1024))) return cnet_close(sid);
201 sock->in_buf = sock->in_len ? realloc(sock->in_buf, sock->in_len+len+1) : calloc(1, len+1);
202 memcpy (sock->in_buf + sock->in_len, buf, len);
203 sock->in_len += len;
204 if (1024 > len) break;
206 sock->in_buf[sock->in_len] = '\0';
208 if (0 == (sock->flags & CNET_LINEMODE)) {
209 sock->handler->on_read (sid, sock->data, sock->in_buf, sock->in_len);
210 free (sock->in_buf);
211 sock->in_buf = NULL;
212 ret = sock->in_len;
213 sock->in_len = 0;
214 return ret;
217 for (end = sock->in_buf; NULL != (beg = strsep(&end, "\r\n"));) {
218 if (NULL == end) break;
219 if ('\0' == *beg) continue;
220 sock->handler->on_read (sid, sock->data, beg, end-beg);
221 ret += end-beg;
224 if ('\0' == *beg) {
225 free (sock->in_buf);
226 sock->in_len = 0;
228 else {
229 sock->in_len -= (beg - sock->in_buf);
230 memmove (sock->in_buf, beg, sock->in_len);
231 sock->in_buf = realloc(sock->in_buf, sock->in_len);
233 return ret;
236 static int cnet_on_eof (int sid, cnet_socket_t *sock, int err)
238 if (sock->handler->on_eof) sock->handler->on_eof (sid, sock->data, err);
239 return cnet_close(sid);
243 /*** public functions ***/
244 int cnet_listen (const char *host, int port)
246 int fd;
248 if (-1 == (fd = cnet_bind (host, port))) return -1;
249 if (-1 == listen (fd, 2)) {
250 close (fd);
251 return -1;
253 return cnet_register (fd, CNET_SERVER, POLLIN|POLLERR|POLLHUP|POLLNVAL);
256 int cnet_connect (const char *rhost, int rport, const char *lhost, int lport)
258 int salen, fd, ret;
259 char port[6];
260 struct sockaddr *sa;
261 struct addrinfo hints, *res = NULL;
263 memset (&hints, '\0', sizeof(hints));
264 hints.ai_family = PF_UNSPEC;
265 if (-1 == (fd = cnet_bind (lhost, lport))) return -1;
267 /* if we have lhost we need to get hints for connect */
268 if (lhost) {
269 getsockname (fd, sa, NULL);
270 hints.ai_family = sa->sa_family;
273 snprintf (port, 6, "%d", rport);
274 if (getaddrinfo (rhost, port, &hints, &res)) goto cleanup;
275 sa = res->ai_addr;
276 salen = res->ai_addrlen;
278 ret = connect (fd, sa, sizeof(*sa));
279 if (-1 == ret && EINPROGRESS != errno) goto cleanup;
280 return cnet_register (fd, CNET_CLIENT|CNET_CONNECT, POLLIN|POLLOUT|POLLERR|POLLHUP|POLLNVAL);
282 cleanup:
283 close (fd);
284 if (res) freeaddrinfo (res);
285 return -1;
288 int cnet_close (int sid)
290 int i;
291 cnet_socket_t *sock;
292 if (NULL == (sock = cnet_fetch(sid))) return -1;
293 if (0 > sock->fd) return -1;
295 /* remove socket from pollfds is wise.... */
296 npollfds--;
297 if (sock->poll < npollfds) {
298 memcpy (&pollfds[sock->poll], &pollfds[npollfds], sizeof(*pollfds));
299 pollsids[sock->poll] = pollsids[npollfds];
300 socks[pollsids[sock->poll]].poll = sock->poll;
302 memset (&pollfds[npollfds], '\0', sizeof(*pollfds));
304 close (sock->fd);
305 if (sock->handler->on_close) sock->handler->on_close (sid, sock->data);
306 free (sock->lhost);
307 free (sock->rhost);
308 if (sock->out_len) free (sock->out_buf);
309 if (sock->in_len) free (sock->in_buf);
310 memset (sock, '\0', sizeof(*sock));
311 sock->fd = -1;
312 sock->flags = CNET_AVAIL;
313 return 0;
316 int cnet_select (int timeout)
318 static int active = 0;
319 int i, n, ret, sid;
320 struct pollfd *p;
321 cnet_socket_t *sock;
322 if (active) return 0;
323 active++;
325 ret = n = poll (pollfds, npollfds, timeout);
326 if (-1 == ret) return -1;
327 if (0 == ret) n = npollfds;
329 for (i = 0; n && i < npollfds; i++) {
330 p = &pollfds[i];
331 sid = pollsids[i];
332 sock = &socks[sid];
333 if (!sock->handler || !p->revents) continue;
335 if (p->revents & (POLLERR|POLLHUP|POLLNVAL)) {
336 cnet_on_eof (sid, sock, 0);
337 i--;
338 n--;
339 continue;
342 if (p->revents & POLLIN) {
343 if (sock->flags & CNET_SERVER) cnet_on_newclient (sid, sock);
344 else cnet_on_readable (sid, sock);
346 if (p->revents & POLLOUT) {
347 p->events &= ~(POLLOUT);
348 if (sock->flags & CNET_CONNECT) {
349 cnet_on_connect (sid, sock);
350 socks->flags &= ~CNET_CONNECT;
352 if (sock->flags & CNET_BLOCKED) {
353 sock->flags &= ~CNET_BLOCKED;
354 cnet_write (sid, NULL, 0);
358 n--;
359 if (!n) break;
361 active--;
363 /* grow sockets if we must */
364 if (npollfds > nsocks - (nsocks / 3)) cnet_grow_sockets();
366 return ret;
369 /* pass NULL to get the current handler returned */
370 /* you can't 'unset' a handler, defeats the purpose of an open socket */
371 cnet_handler_t *cnet_handler (int sid, cnet_handler_t *handler)
373 cnet_socket_t *sock;
374 if (NULL == (sock = cnet_fetch(sid))) return NULL;
375 if (handler) sock->handler = handler;
376 return sock->handler;
379 void *cnet_conndata (int sid, void *conn_data)
381 cnet_socket_t *sock;
382 if (NULL == (sock = cnet_fetch(sid))) return NULL;
383 if (conn_data) sock->data = conn_data;
384 return sock->data;
387 int cnet_ip_type (const char *ip)
389 struct in_addr in_buf;
390 struct in6_addr in6_buf;
391 if (0 < inet_pton(AF_INET, ip, &in_buf)) return 4;
392 if (0 < inet_pton(AF_INET6, ip, &in6_buf)) return 6;
393 return 0;
396 /* is this a valid & connected sid ? */
397 int cnet_valid (int sid)
399 return cnet_fetch(sid) ? 1 : 0;
402 int cnet_linemode (int sid, int toggle)
404 cnet_socket_t *sock;
405 if (NULL == (sock = cnet_fetch(sid))) return 0;
406 if (toggle) sock->flags |= CNET_LINEMODE;
407 else sock->flags &= ~(CNET_LINEMODE);
408 return 1;
411 int cnet_write (int sid, const void *data, int len)
413 int i, written = 0, ret = 0;
414 cnet_socket_t *sock;
415 if (NULL == (sock = cnet_fetch(sid))) return -1;
416 if (0 >= len && !sock->out_len) return 0;
417 if (sock->flags & (CNET_BLOCKED|CNET_CONNECT)) goto buffer;
419 if (sock->out_len) {
420 if (sock->out_len != (written = write(sock->fd, sock->out_buf, sock->out_len))) goto buffer;
421 free (sock->out_buf);
422 sock->out_len = 0;
424 if (len == (ret = write(sock->fd, data, len))) return ret+written;
426 buffer:
427 pollfds[sock->poll].events |= POLLOUT;
428 sock->flags |= CNET_BLOCKED;
430 if (sock->out_len) memmove(sock->out_buf, sock->out_buf + written, sock->out_len - written);
431 sock->out_buf = realloc(sock->out_buf, (sock->out_len - written) + (len - ret));
432 memcpy(sock->out_buf+(sock->out_len-written), data, len);
433 sock->out_len = (sock->out_len - written) + (len - ret);
434 return written + ret;
437 int cnprintf (int sid, const char *format, ...)
439 va_list args;
440 char *data;
441 int len, ret;
443 va_start (args, format);
444 if (-1 == (len = vasprintf (&data, format, args))) return -1;
445 va_end (args);
446 ret = cnet_write (sid, data, len);
447 free (data);
448 return ret;