generalize sid creation / fd configing
[cnet.git] / cnet.c
blob89ef09aab895a0c5f7118f859b60780f155ae4f9
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 */
47 typedef struct {
48 int fd;
49 int flags;
51 /* connection information */
52 char *lhost;
53 int lport;
54 char *rhost;
55 int rport;
57 /* buffer data */
58 char *buf;
59 int len;
61 /* client data */
62 cnet_handler_t *handler;
63 void *data;
64 } cnet_socket_t;
66 static cnet_socket_t *socks = NULL;
67 static int nsocks = 0;
68 static int navailsocks = 0;
69 static struct pollfd *pollfds = NULL;
70 static int *pollsids = NULL;
71 static int npollfds;
74 /*** private helper methods ***/
75 static int cnet_grow_sockets (void)
77 int i, newsocks;
78 newsocks = (nsocks / 3) + 16;
79 printf ("growing socks: %d total: %d\n", newsocks, nsocks+newsocks);
80 socks = realloc (socks, (nsocks+newsocks) * sizeof(*socks));
81 memset(socks+nsocks, '\0', newsocks*sizeof(*socks));
82 for (i = 0; i < newsocks; i++) {
83 socks[nsocks+i].fd = -1;
84 socks[nsocks+i].flags = CNET_AVAIL;
86 nsocks += newsocks;
87 navailsocks += newsocks;
88 return newsocks;
91 /* returns fd. NOT sid */
92 static int cnet_bind (const char *host, int port)
94 int salen, fd;
95 char strport[6];
96 struct sockaddr *sa;
97 struct addrinfo hints, *res = NULL;
99 fd = socket (AF_INET, SOCK_STREAM, 0);
100 if (!host || 0 > fd) return fd;
102 memset (&hints, '\0', sizeof(hints));
103 hints.ai_family = PF_UNSPEC;
104 if (cnet_ip_type(host)) hints.ai_flags = AI_NUMERICHOST;
105 if (port) snprintf (strport, 6, "%d", port);
106 if (getaddrinfo (host, (port ? strport : NULL), &hints, &res)) return -1;
107 sa = res->ai_addr;
108 salen = res->ai_addrlen;
110 if (-1 == bind (fd, sa, sizeof(*sa))) {
111 close (fd);
112 fd = -1;
115 freeaddrinfo (res);
116 return fd;
119 /* create a sid / configure fd */
120 static int cnet_register (int fd, int sockflags, int fdflags)
122 int sid, flags;
123 cnet_socket_t *sock;
125 /* get us our sid */
126 if (0 == nsocks) cnet_grow_sockets();
127 for (sid = 0; sid < nsocks; sid++)
128 if (socks[sid].flags & CNET_AVAIL) break;
129 if (sid == nsocks) return -1;
130 navailsocks--;
131 sock = &socks[sid];
132 sock->fd = fd;
133 sock->flags = sockflags;
135 /* configure the fd */
136 pollfds = realloc (pollfds, (npollfds+1) * sizeof(*pollfds));
137 pollsids = realloc (pollsids, (npollfds+1) * sizeof(*pollsids));
138 memset (pollfds+npollfds, '\0', sizeof(*pollfds));
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 (navailsocks) {
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;
197 int len, ret;
198 buf = calloc (1, 1024);
199 if (-1 == (len = read(sock->fd, buf, 1023))) return cnet_close(sid);
200 ret = sock->handler->on_read (sid, sock->data, buf, len);
201 free (buf);
202 return ret;
205 static int cnet_on_eof (int sid, cnet_socket_t *sock, int err)
207 if (sock->handler->on_eof) sock->handler->on_eof (sid, sock->data, err);
208 return cnet_close(sid);
212 /*** public functions ***/
213 int cnet_listen (const char *host, int port)
215 int fd;
217 if (-1 == (fd = cnet_bind (host, port))) return -1;
218 if (-1 == listen (fd, 2)) {
219 close (fd);
220 return -1;
222 return cnet_register (fd, CNET_SERVER, POLLIN|POLLERR|POLLHUP|POLLNVAL);
225 int cnet_connect (const char *rhost, int rport, const char *lhost, int lport)
227 int salen, fd, ret;
228 char port[6];
229 struct sockaddr *sa;
230 struct addrinfo hints, *res = NULL;
232 memset (&hints, '\0', sizeof(hints));
233 hints.ai_family = PF_UNSPEC;
234 if (-1 == (fd = cnet_bind (lhost, lport))) return -1;
236 /* if we have lhost we need to get hints for connect */
237 if (lhost) {
238 getsockname (fd, sa, NULL);
239 hints.ai_family = sa->sa_family;
242 snprintf (port, 6, "%d", rport);
243 if (getaddrinfo (rhost, port, &hints, &res)) goto cleanup;
244 sa = res->ai_addr;
245 salen = res->ai_addrlen;
247 ret = connect (fd, sa, sizeof(*sa));
248 if (-1 == ret && EINPROGRESS != errno) goto cleanup;
249 return cnet_register (fd, CNET_CLIENT|CNET_CONNECT, POLLIN|POLLOUT|POLLERR|POLLHUP|POLLNVAL);
251 cleanup:
252 close (fd);
253 if (res) freeaddrinfo (res);
254 return -1;
257 int cnet_close (int sid)
259 int i;
260 cnet_socket_t *sock;
261 if (NULL == (sock = cnet_fetch(sid))) return -1;
262 if (0 > sock->fd) return -1;
264 /* remove socket from pollfds is wise.... */
265 for (i = 0; i < npollfds; i++)
266 if (sock->fd == pollfds[i].fd) break;
267 npollfds--;
268 if (i < npollfds) {
269 memcpy (&pollfds[i], &pollfds[npollfds], sizeof(*pollfds));
270 pollsids[i] = pollsids[npollfds];
272 pollfds = realloc (pollfds, npollfds * sizeof(*pollfds));
273 pollsids = realloc (pollsids, npollfds * sizeof(*pollsids));
276 close (sock->fd);
277 if (sock->handler->on_close) sock->handler->on_close (sid, sock->data);
278 free (sock->lhost);
279 free (sock->rhost);
280 if (sock->len) free (sock->buf);
281 memset (sock, '\0', sizeof(*sock));
282 sock->fd = -1;
283 sock->flags = CNET_AVAIL;
284 navailsocks++;
285 return 0;
288 int cnet_select (int timeout)
290 static int active = 0;
291 int i, n, ret, sid;
292 struct pollfd *p;
293 cnet_socket_t *sock;
294 if (active) return 0;
295 active++;
297 ret = n = poll (pollfds, npollfds, timeout);
298 if (-1 == ret) return -1;
299 if (0 == ret) n = npollfds;
301 for (i = 0; n && i < npollfds; i++) {
302 p = &pollfds[i];
303 sid = pollsids[i];
304 sock = &socks[sid];
305 if (!sock->handler || !p->revents) continue;
307 if (p->revents & (POLLERR|POLLHUP|POLLNVAL)) {
308 cnet_on_eof (sid, sock, 0);
309 i--;
310 n--;
311 continue;
314 if (p->revents & POLLIN) {
315 if (sock->flags & CNET_SERVER) cnet_on_newclient (sid, sock);
316 else cnet_on_readable (sid, sock);
318 if (p->revents & POLLOUT) {
319 p->events &= ~(POLLOUT);
320 if (sock->flags & CNET_CONNECT) {
321 cnet_on_connect (sid, sock);
322 socks->flags &= ~CNET_BLOCKED;
324 if (sock->flags & CNET_BLOCKED) cnet_write (sid, NULL, 0);
327 n--;
328 if (!n) break;
330 active--;
332 /* grow sockets if we must */
333 if (navailsocks < (nsocks / 3)) cnet_grow_sockets();
335 return ret;
338 /* pass NULL to get the current handler returned */
339 /* you can't 'unset' a handler, defeats the purpose of an open socket */
340 cnet_handler_t *cnet_handler (int sid, cnet_handler_t *handler)
342 cnet_socket_t *sock;
343 if (NULL == (sock = cnet_fetch(sid))) return NULL;
344 if (handler) sock->handler = handler;
345 return sock->handler;
348 void *cnet_conndata (int sid, void *conn_data)
350 cnet_socket_t *sock;
351 if (NULL == (sock = cnet_fetch(sid))) return NULL;
352 if (conn_data) sock->data = conn_data;
353 return sock->data;
356 int cnet_ip_type (const char *ip)
358 struct in_addr in_buf;
359 struct in6_addr in6_buf;
360 if (0 < inet_pton(AF_INET, ip, &in_buf)) return 4;
361 if (0 < inet_pton(AF_INET6, ip, &in6_buf)) return 6;
362 return 0;
365 /* is this a valid & connected sid ? */
366 int cnet_valid (int sid)
368 return cnet_fetch(sid) ? 1 : 0;
371 int cnet_write (int sid, const char *data, int len)
373 int i;
374 cnet_socket_t *sock;
375 if (NULL == (sock = cnet_fetch(sid))) return -1;
376 if (0 >= len && !sock->len) return 0;
378 /* check if there is data that still needs to be written */
379 if (len > 0) {
380 sock->buf = sock->len ? realloc(sock->buf, sock->len + len) : calloc(1, len);
381 memcpy (sock->buf + sock->len, data, len);
382 sock->len += len;
385 if (sock->flags & (CNET_BLOCKED|CNET_CONNECT)) return 0;
386 len = write (sock->fd, sock->buf, sock->len);
387 if (0 > len && errno != EAGAIN) return cnet_on_eof (sid, sock, errno);
389 for (i = 0; i < npollfds; i++)
390 if (sock->fd == pollfds[i].fd) break;
392 if (len == sock->len) {
393 free (sock->buf);
394 sock->len = 0;
395 pollfds[i].events &= ~POLLOUT;
397 else {
398 memmove (sock->buf, sock->buf+len, sock->len-len);
399 sock->len -= len;
400 sock->buf = realloc (sock->buf, sock->len);
402 /* we need to set block since we still have data */
403 sock->flags |= CNET_BLOCKED;
404 pollfds[i].events |= POLLOUT;
406 return len;
409 int cnprintf (int sid, const char *format, ...)
411 va_list args;
412 char *data;
413 int len, ret;
415 va_start (args, format);
416 if (-1 == (len = vasprintf (&data, format, args))) return -1;
417 va_end (args);
418 ret = cnet_write (sid, data, len);
419 free (data);
420 return ret;