Fix memory management of aio event handlers
[jimtcl.git] / jim-aio.c
blob9f3dcc88ea94b80b7160738755883d7c6194dac4
2 /* Jim - A small embeddable Tcl interpreter
4 * Copyright 2005 Salvatore Sanfilippo <antirez@invece.org>
5 * Copyright 2005 Clemens Hintze <c.hintze@gmx.net>
6 * Copyright 2005 patthoyts - Pat Thoyts <patthoyts@users.sf.net>
7 * Copyright 2008 oharboe - Øyvind Harboe - oyvind.harboe@zylin.com
8 * Copyright 2008 Andrew Lunn <andrew@lunn.ch>
9 * Copyright 2008 Duane Ellis <openocd@duaneellis.com>
10 * Copyright 2008 Uwe Klein <uklein@klein-messgeraete.de>
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
16 * 1. Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
18 * 2. Redistributions in binary form must reproduce the above
19 * copyright notice, this list of conditions and the following
20 * disclaimer in the documentation and/or other materials
21 * provided with the distribution.
23 * THIS SOFTWARE IS PROVIDED BY THE JIM TCL PROJECT ``AS IS'' AND ANY
24 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
25 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
26 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
27 * JIM TCL PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
28 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
29 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
32 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
34 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 * The views and conclusions contained in the software and documentation
37 * are those of the authors and should not be interpreted as representing
38 * official policies, either expressed or implied, of the Jim Tcl Project.
39 **/
41 #include "jimautoconf.h"
43 #include <stdio.h>
44 #include <string.h>
45 #include <errno.h>
46 #include <fcntl.h>
48 #include "jim.h"
50 #if defined(HAVE_SYS_SOCKET_H) && defined(HAVE_SELECT) && defined(HAVE_NETINET_IN_H) && defined(HAVE_NETDB_H) && defined(HAVE_ARPA_INET_H)
51 #include <sys/socket.h>
52 #include <netinet/in.h>
53 #include <arpa/inet.h>
54 #include <netdb.h>
55 #include <unistd.h>
56 #ifdef HAVE_SYS_UN_H
57 #include <sys/un.h>
58 #endif
59 #else
60 #define JIM_ANSIC
61 #endif
63 #include "jim-eventloop.h"
64 #include "jim-subcmd.h"
66 #define AIO_CMD_LEN 32 /* e.g. aio.handleXXXXXX */
67 #define AIO_BUF_LEN 256 /* Can keep this small and rely on stdio buffering */
69 #ifndef HAVE_FTELLO
70 #define ftello ftell
71 #endif
72 #ifndef HAVE_FSEEKO
73 #define fseeko fseek
74 #endif
76 #define AIO_KEEPOPEN 1
78 #if defined(JIM_IPV6)
79 #define IPV6 1
80 #else
81 #define IPV6 0
82 #ifndef PF_INET6
83 #define PF_INET6 0
84 #endif
85 #endif
87 #if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)
88 union sockaddr_any {
89 struct sockaddr sa;
90 struct sockaddr_in sin;
91 #if IPV6
92 struct sockaddr_in6 sin6;
93 #endif
96 #ifndef HAVE_INET_NTOP
97 const char *inet_ntop(int af, const void *src, char *dst, int size)
99 if (af != PF_INET) {
100 return NULL;
102 snprintf(dst, size, "%s", inet_ntoa(((struct sockaddr_in *)src)->sin_addr));
103 return dst;
105 #endif
106 #endif /* JIM_BOOTSTRAP */
108 typedef struct AioFile
110 FILE *fp;
111 Jim_Obj *filename;
112 int type;
113 int OpenFlags; /* AIO_KEEPOPEN? keep FILE* */
114 int fd;
115 #ifdef O_NDELAY
116 int flags;
117 #endif
118 Jim_Obj *rEvent;
119 Jim_Obj *wEvent;
120 Jim_Obj *eEvent;
121 int addr_family;
122 } AioFile;
124 static int JimAioSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv);
125 static int JimMakeChannel(Jim_Interp *interp, FILE *fh, int fd, Jim_Obj *filename,
126 const char *hdlfmt, int family, const char *mode);
128 #if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)
129 static int JimParseIPv6Address(Jim_Interp *interp, const char *hostport, union sockaddr_any *sa, int *salen)
131 #if IPV6
133 * An IPv6 addr/port looks like:
134 * [::1]
135 * [::1]:2000
136 * [fe80::223:6cff:fe95:bdc0%en1]:2000
137 * [::]:2000
138 * 2000
140 * Note that the "any" address is ::, which is the same as when no address is specified.
142 char *sthost = NULL;
143 const char *stport;
144 int ret = JIM_OK;
145 struct addrinfo req;
146 struct addrinfo *ai;
148 stport = strrchr(hostport, ':');
149 if (!stport) {
150 /* No : so, the whole thing is the port */
151 stport = hostport;
152 hostport = "::";
153 sthost = Jim_StrDup(hostport);
155 else {
156 stport++;
159 if (*hostport == '[') {
160 /* This is a numeric ipv6 address */
161 char *pt = strchr(++hostport, ']');
162 if (pt) {
163 sthost = Jim_StrDupLen(hostport, pt - hostport);
167 if (!sthost) {
168 sthost = Jim_StrDupLen(hostport, stport - hostport - 1);
171 memset(&req, '\0', sizeof(req));
172 req.ai_family = PF_INET6;
174 if (getaddrinfo(sthost, NULL, &req, &ai)) {
175 Jim_SetResultFormatted(interp, "Not a valid address: %s", hostport);
176 ret = JIM_ERR;
178 else {
179 memcpy(&sa->sin, ai->ai_addr, ai->ai_addrlen);
180 *salen = ai->ai_addrlen;
182 sa->sin.sin_port = htons(atoi(stport));
184 freeaddrinfo(ai);
186 Jim_Free(sthost);
188 return ret;
189 #else
190 Jim_SetResultString(interp, "ipv6 not supported", -1);
191 return JIM_ERR;
192 #endif
195 static int JimParseIpAddress(Jim_Interp *interp, const char *hostport, union sockaddr_any *sa, int *salen)
197 /* An IPv4 addr/port looks like:
198 * 192.168.1.5
199 * 192.168.1.5:2000
200 * 2000
202 * If the address is missing, INADDR_ANY is used.
203 * If the port is missing, 0 is used (only useful for server sockets).
205 char *sthost = NULL;
206 const char *stport;
207 int ret = JIM_OK;
209 stport = strrchr(hostport, ':');
210 if (!stport) {
211 /* No : so, the whole thing is the port */
212 stport = hostport;
213 sthost = Jim_StrDup("0.0.0.0");
215 else {
216 sthost = Jim_StrDupLen(hostport, stport - hostport);
217 stport++;
221 #ifdef HAVE_GETADDRINFO
222 struct addrinfo req;
223 struct addrinfo *ai;
224 memset(&req, '\0', sizeof(req));
225 req.ai_family = PF_INET;
227 if (getaddrinfo(sthost, NULL, &req, &ai)) {
228 ret = JIM_ERR;
230 else {
231 memcpy(&sa->sin, ai->ai_addr, ai->ai_addrlen);
232 *salen = ai->ai_addrlen;
233 freeaddrinfo(ai);
235 #else
236 struct hostent *he;
238 ret = JIM_ERR;
240 if ((he = gethostbyname(sthost)) != NULL) {
241 if (he->h_length == sizeof(sa->sin.sin_addr)) {
242 *salen = sizeof(sa->sin);
243 sa->sin.sin_family= he->h_addrtype;
244 memcpy(&sa->sin.sin_addr, he->h_addr, he->h_length); /* set address */
245 ret = JIM_OK;
248 #endif
250 sa->sin.sin_port = htons(atoi(stport));
252 Jim_Free(sthost);
254 if (ret != JIM_OK) {
255 Jim_SetResultFormatted(interp, "Not a valid address: %s", hostport);
258 return ret;
261 #ifdef HAVE_SYS_UN_H
262 static int JimParseDomainAddress(Jim_Interp *interp, const char *path, struct sockaddr_un *sa)
264 sa->sun_family = PF_UNIX;
265 snprintf(sa->sun_path, sizeof(sa->sun_path), "%s", path);
267 return JIM_OK;
269 #endif
270 #endif /* JIM_BOOTSTRAP */
272 static void JimAioSetError(Jim_Interp *interp, Jim_Obj *name)
274 if (name) {
275 Jim_SetResultFormatted(interp, "%#s: %s", name, strerror(errno));
277 else {
278 Jim_SetResultString(interp, strerror(errno), -1);
282 static void JimAioDelProc(Jim_Interp *interp, void *privData)
284 AioFile *af = privData;
286 JIM_NOTUSED(interp);
288 if (!(af->OpenFlags & AIO_KEEPOPEN)) {
289 fclose(af->fp);
292 Jim_DecrRefCount(interp, af->filename);
294 #ifdef jim_ext_eventloop
295 /* remove all existing EventHandlers */
296 Jim_DeleteFileHandler(interp, af->fp, JIM_EVENT_READABLE | JIM_EVENT_WRITABLE | JIM_EVENT_EXCEPTION);
297 #endif
298 Jim_Free(af);
301 static int JimCheckStreamError(Jim_Interp *interp, AioFile *af)
303 if (!ferror(af->fp)) {
304 return JIM_OK;
306 clearerr(af->fp);
307 /* EAGAIN and similar are not error conditions. Just treat them like eof */
308 if (feof(af->fp) || errno == EAGAIN || errno == EINTR) {
309 return JIM_OK;
311 #ifdef ECONNRESET
312 if (errno == ECONNRESET) {
313 return JIM_OK;
315 #endif
316 #ifdef ECONNABORTED
317 if (errno != ECONNABORTED) {
318 return JIM_OK;
320 #endif
321 JimAioSetError(interp, af->filename);
322 return JIM_ERR;
325 static int aio_cmd_read(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
327 AioFile *af = Jim_CmdPrivData(interp);
328 char buf[AIO_BUF_LEN];
329 Jim_Obj *objPtr;
330 int nonewline = 0;
331 jim_wide neededLen = -1; /* -1 is "read as much as possible" */
333 if (argc && Jim_CompareStringImmediate(interp, argv[0], "-nonewline")) {
334 nonewline = 1;
335 argv++;
336 argc--;
338 if (argc == 1) {
339 if (Jim_GetWide(interp, argv[0], &neededLen) != JIM_OK)
340 return JIM_ERR;
341 if (neededLen < 0) {
342 Jim_SetResultString(interp, "invalid parameter: negative len", -1);
343 return JIM_ERR;
346 else if (argc) {
347 return -1;
349 objPtr = Jim_NewStringObj(interp, NULL, 0);
350 while (neededLen != 0) {
351 int retval;
352 int readlen;
354 if (neededLen == -1) {
355 readlen = AIO_BUF_LEN;
357 else {
358 readlen = (neededLen > AIO_BUF_LEN ? AIO_BUF_LEN : neededLen);
360 retval = fread(buf, 1, readlen, af->fp);
361 if (retval > 0) {
362 Jim_AppendString(interp, objPtr, buf, retval);
363 if (neededLen != -1) {
364 neededLen -= retval;
367 if (retval != readlen)
368 break;
370 /* Check for error conditions */
371 if (JimCheckStreamError(interp, af)) {
372 Jim_FreeNewObj(interp, objPtr);
373 return JIM_ERR;
375 if (nonewline) {
376 int len;
377 const char *s = Jim_GetString(objPtr, &len);
379 if (len > 0 && s[len - 1] == '\n') {
380 objPtr->length--;
381 objPtr->bytes[objPtr->length] = '\0';
384 Jim_SetResult(interp, objPtr);
385 return JIM_OK;
388 static int aio_cmd_copy(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
390 AioFile *af = Jim_CmdPrivData(interp);
391 jim_wide count = 0;
392 jim_wide maxlen = JIM_WIDE_MAX;
393 FILE *outfh = Jim_AioFilehandle(interp, argv[0]);
395 if (outfh == NULL) {
396 return JIM_ERR;
399 if (argc == 2) {
400 if (Jim_GetWide(interp, argv[1], &maxlen) != JIM_OK) {
401 return JIM_ERR;
405 while (count < maxlen) {
406 int ch = fgetc(af->fp);
408 if (ch == EOF || fputc(ch, outfh) == EOF) {
409 break;
411 count++;
414 if (ferror(af->fp)) {
415 Jim_SetResultFormatted(interp, "error while reading: %s", strerror(errno));
416 clearerr(af->fp);
417 return JIM_ERR;
420 if (ferror(outfh)) {
421 Jim_SetResultFormatted(interp, "error while writing: %s", strerror(errno));
422 clearerr(outfh);
423 return JIM_ERR;
426 Jim_SetResultInt(interp, count);
428 return JIM_OK;
431 static int aio_cmd_gets(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
433 AioFile *af = Jim_CmdPrivData(interp);
434 char buf[AIO_BUF_LEN];
435 Jim_Obj *objPtr;
436 int len;
438 errno = 0;
440 objPtr = Jim_NewStringObj(interp, NULL, 0);
441 while (1) {
442 buf[AIO_BUF_LEN - 1] = '_';
443 if (fgets(buf, AIO_BUF_LEN, af->fp) == NULL)
444 break;
446 if (buf[AIO_BUF_LEN - 1] == '\0' && buf[AIO_BUF_LEN - 2] != '\n') {
447 Jim_AppendString(interp, objPtr, buf, AIO_BUF_LEN - 1);
449 else {
450 len = strlen(buf);
452 if (len && (buf[len - 1] == '\n')) {
453 /* strip "\n" */
454 len--;
457 Jim_AppendString(interp, objPtr, buf, len);
458 break;
461 if (JimCheckStreamError(interp, af)) {
462 /* I/O error */
463 Jim_FreeNewObj(interp, objPtr);
464 return JIM_ERR;
467 if (argc) {
468 if (Jim_SetVariable(interp, argv[0], objPtr) != JIM_OK) {
469 Jim_FreeNewObj(interp, objPtr);
470 return JIM_ERR;
473 len = Jim_Length(objPtr);
475 if (len == 0 && feof(af->fp)) {
476 /* On EOF returns -1 if varName was specified */
477 len = -1;
479 Jim_SetResultInt(interp, len);
481 else {
482 Jim_SetResult(interp, objPtr);
484 return JIM_OK;
487 static int aio_cmd_puts(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
489 AioFile *af = Jim_CmdPrivData(interp);
490 int wlen;
491 const char *wdata;
492 Jim_Obj *strObj;
494 if (argc == 2) {
495 if (!Jim_CompareStringImmediate(interp, argv[0], "-nonewline")) {
496 return -1;
498 strObj = argv[1];
500 else {
501 strObj = argv[0];
504 wdata = Jim_GetString(strObj, &wlen);
505 if (fwrite(wdata, 1, wlen, af->fp) == (unsigned)wlen) {
506 if (argc == 2 || putc('\n', af->fp) != EOF) {
507 return JIM_OK;
510 JimAioSetError(interp, af->filename);
511 return JIM_ERR;
514 static int aio_cmd_isatty(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
516 #ifdef HAVE_ISATTY
517 AioFile *af = Jim_CmdPrivData(interp);
518 Jim_SetResultInt(interp, isatty(fileno(af->fp)));
519 #else
520 Jim_SetResultInt(interp, 0);
521 #endif
523 return JIM_OK;
526 #if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)
527 static int aio_cmd_recvfrom(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
529 AioFile *af = Jim_CmdPrivData(interp);
530 char *buf;
531 union sockaddr_any sa;
532 long len;
533 socklen_t salen = sizeof(sa);
534 int rlen;
536 if (Jim_GetLong(interp, argv[0], &len) != JIM_OK) {
537 return JIM_ERR;
540 buf = Jim_Alloc(len + 1);
542 rlen = recvfrom(fileno(af->fp), buf, len, 0, &sa.sa, &salen);
543 if (rlen < 0) {
544 Jim_Free(buf);
545 JimAioSetError(interp, NULL);
546 return JIM_ERR;
548 buf[rlen] = 0;
549 Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, buf, rlen));
551 if (argc > 1) {
552 /* INET6_ADDRSTRLEN is 46. Add some for [] and port */
553 char addrbuf[60];
555 #if IPV6
556 if (sa.sa.sa_family == PF_INET6) {
557 addrbuf[0] = '[';
558 /* Allow 9 for []:65535\0 */
559 inet_ntop(sa.sa.sa_family, &sa.sin6.sin6_addr, addrbuf + 1, sizeof(addrbuf) - 9);
560 snprintf(addrbuf + strlen(addrbuf), 8, "]:%d", ntohs(sa.sin.sin_port));
562 else
563 #endif
565 /* Allow 7 for :65535\0 */
566 inet_ntop(sa.sa.sa_family, &sa.sin.sin_addr, addrbuf, sizeof(addrbuf) - 7);
567 snprintf(addrbuf + strlen(addrbuf), 7, ":%d", ntohs(sa.sin.sin_port));
570 if (Jim_SetVariable(interp, argv[1], Jim_NewStringObj(interp, addrbuf, -1)) != JIM_OK) {
571 return JIM_ERR;
575 return JIM_OK;
579 static int aio_cmd_sendto(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
581 AioFile *af = Jim_CmdPrivData(interp);
582 int wlen;
583 int len;
584 const char *wdata;
585 union sockaddr_any sa;
586 const char *addr = Jim_String(argv[1]);
587 int salen;
589 if (IPV6 && af->addr_family == PF_INET6) {
590 if (JimParseIPv6Address(interp, addr, &sa, &salen) != JIM_OK) {
591 return JIM_ERR;
594 else if (JimParseIpAddress(interp, addr, &sa, &salen) != JIM_OK) {
595 return JIM_ERR;
597 wdata = Jim_GetString(argv[0], &wlen);
599 /* Note that we don't validate the socket type. Rely on sendto() failing if appropriate */
600 len = sendto(fileno(af->fp), wdata, wlen, 0, &sa.sa, salen);
601 if (len < 0) {
602 JimAioSetError(interp, NULL);
603 return JIM_ERR;
605 Jim_SetResultInt(interp, len);
606 return JIM_OK;
609 static int aio_cmd_accept(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
611 AioFile *af = Jim_CmdPrivData(interp);
612 int sock;
613 union sockaddr_any sa;
614 socklen_t addrlen = sizeof(sa);
616 sock = accept(af->fd, &sa.sa, &addrlen);
617 if (sock < 0) {
618 JimAioSetError(interp, NULL);
619 return JIM_ERR;
622 /* Create the file command */
623 return JimMakeChannel(interp, NULL, sock, Jim_NewStringObj(interp, "accept", -1),
624 "aio.sockstream%ld", af->addr_family, "r+");
627 static int aio_cmd_listen(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
629 AioFile *af = Jim_CmdPrivData(interp);
630 long backlog;
632 if (Jim_GetLong(interp, argv[0], &backlog) != JIM_OK) {
633 return JIM_ERR;
636 if (listen(af->fd, backlog)) {
637 JimAioSetError(interp, NULL);
638 return JIM_ERR;
641 return JIM_OK;
643 #endif /* JIM_BOOTSTRAP */
645 static int aio_cmd_flush(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
647 AioFile *af = Jim_CmdPrivData(interp);
649 if (fflush(af->fp) == EOF) {
650 JimAioSetError(interp, af->filename);
651 return JIM_ERR;
653 return JIM_OK;
656 static int aio_cmd_eof(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
658 AioFile *af = Jim_CmdPrivData(interp);
660 Jim_SetResultInt(interp, feof(af->fp));
661 return JIM_OK;
664 static int aio_cmd_close(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
666 Jim_DeleteCommand(interp, Jim_String(argv[0]));
667 return JIM_OK;
670 static int aio_cmd_seek(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
672 AioFile *af = Jim_CmdPrivData(interp);
673 int orig = SEEK_SET;
674 jim_wide offset;
676 if (argc == 2) {
677 if (Jim_CompareStringImmediate(interp, argv[1], "start"))
678 orig = SEEK_SET;
679 else if (Jim_CompareStringImmediate(interp, argv[1], "current"))
680 orig = SEEK_CUR;
681 else if (Jim_CompareStringImmediate(interp, argv[1], "end"))
682 orig = SEEK_END;
683 else {
684 return -1;
687 if (Jim_GetWide(interp, argv[0], &offset) != JIM_OK) {
688 return JIM_ERR;
690 if (fseeko(af->fp, offset, orig) == -1) {
691 JimAioSetError(interp, af->filename);
692 return JIM_ERR;
694 return JIM_OK;
697 static int aio_cmd_tell(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
699 AioFile *af = Jim_CmdPrivData(interp);
701 Jim_SetResultInt(interp, ftello(af->fp));
702 return JIM_OK;
705 static int aio_cmd_filename(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
707 AioFile *af = Jim_CmdPrivData(interp);
709 Jim_SetResult(interp, af->filename);
710 return JIM_OK;
713 #ifdef O_NDELAY
714 static int aio_cmd_ndelay(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
716 AioFile *af = Jim_CmdPrivData(interp);
718 int fmode = af->flags;
720 if (argc) {
721 long nb;
723 if (Jim_GetLong(interp, argv[0], &nb) != JIM_OK) {
724 return JIM_ERR;
726 if (nb) {
727 fmode |= O_NDELAY;
729 else {
730 fmode &= ~O_NDELAY;
732 fcntl(af->fd, F_SETFL, fmode);
733 af->flags = fmode;
735 Jim_SetResultInt(interp, (fmode & O_NONBLOCK) ? 1 : 0);
736 return JIM_OK;
738 #endif
740 static int aio_cmd_buffering(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
742 AioFile *af = Jim_CmdPrivData(interp);
744 static const char * const options[] = {
745 "none",
746 "line",
747 "full",
748 NULL
750 enum
752 OPT_NONE,
753 OPT_LINE,
754 OPT_FULL,
756 int option;
758 if (Jim_GetEnum(interp, argv[0], options, &option, NULL, JIM_ERRMSG) != JIM_OK) {
759 return JIM_ERR;
761 switch (option) {
762 case OPT_NONE:
763 setvbuf(af->fp, NULL, _IONBF, 0);
764 break;
765 case OPT_LINE:
766 setvbuf(af->fp, NULL, _IOLBF, BUFSIZ);
767 break;
768 case OPT_FULL:
769 setvbuf(af->fp, NULL, _IOFBF, BUFSIZ);
770 break;
772 return JIM_OK;
775 #ifdef jim_ext_eventloop
776 static void JimAioFileEventFinalizer(Jim_Interp *interp, void *clientData)
778 Jim_Obj **objPtrPtr = clientData;
780 Jim_DecrRefCount(interp, *objPtrPtr);
781 *objPtrPtr = NULL;
784 static int JimAioFileEventHandler(Jim_Interp *interp, void *clientData, int mask)
786 Jim_Obj **objPtrPtr = clientData;
788 return Jim_EvalObjBackground(interp, *objPtrPtr);
791 static int aio_eventinfo(Jim_Interp *interp, AioFile * af, unsigned mask, Jim_Obj **scriptHandlerObj,
792 int argc, Jim_Obj * const *argv)
794 if (argc == 0) {
795 /* Return current script */
796 if (*scriptHandlerObj) {
797 Jim_SetResult(interp, *scriptHandlerObj);
799 return JIM_OK;
802 if (*scriptHandlerObj) {
803 /* Delete old handler */
804 Jim_DeleteFileHandler(interp, af->fp, mask);
807 /* Now possibly add the new script(s) */
808 if (Jim_Length(argv[0]) == 0) {
809 /* Empty script, so done */
810 return JIM_OK;
813 /* A new script to add */
814 Jim_IncrRefCount(argv[0]);
815 *scriptHandlerObj = argv[0];
817 Jim_CreateFileHandler(interp, af->fp, mask,
818 JimAioFileEventHandler, scriptHandlerObj, JimAioFileEventFinalizer);
820 return JIM_OK;
823 static int aio_cmd_readable(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
825 AioFile *af = Jim_CmdPrivData(interp);
827 return aio_eventinfo(interp, af, JIM_EVENT_READABLE, &af->rEvent, argc, argv);
830 static int aio_cmd_writable(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
832 AioFile *af = Jim_CmdPrivData(interp);
834 return aio_eventinfo(interp, af, JIM_EVENT_WRITABLE, &af->wEvent, argc, argv);
837 static int aio_cmd_onexception(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
839 AioFile *af = Jim_CmdPrivData(interp);
841 return aio_eventinfo(interp, af, JIM_EVENT_EXCEPTION, &af->wEvent, argc, argv);
843 #endif
845 static const jim_subcmd_type aio_command_table[] = {
846 { "read",
847 "?-nonewline? ?len?",
848 aio_cmd_read,
851 /* Description: Read and return bytes from the stream. To eof if no len. */
853 { "copyto",
854 "handle ?size?",
855 aio_cmd_copy,
858 /* Description: Copy up to 'size' bytes to the given filehandle, or to eof if no size. */
860 { "gets",
861 "?var?",
862 aio_cmd_gets,
865 /* Description: Read one line and return it or store it in the var */
867 { "puts",
868 "?-nonewline? str",
869 aio_cmd_puts,
872 /* Description: Write the string, with newline unless -nonewline */
874 { "isatty",
875 NULL,
876 aio_cmd_isatty,
879 /* Description: Is the file descriptor a tty? */
881 #if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)
882 { "recvfrom",
883 "len ?addrvar?",
884 aio_cmd_recvfrom,
887 /* Description: Receive up to 'len' bytes on the socket. Sets 'addrvar' with receive address, if set */
889 { "sendto",
890 "str address",
891 aio_cmd_sendto,
894 /* Description: Send 'str' to the given address (dgram only) */
896 { "accept",
897 NULL,
898 aio_cmd_accept,
901 /* Description: Server socket only: Accept a connection and return stream */
903 { "listen",
904 "backlog",
905 aio_cmd_listen,
908 /* Description: Set the listen backlog for server socket */
910 #endif /* JIM_BOOTSTRAP */
911 { "flush",
912 NULL,
913 aio_cmd_flush,
916 /* Description: Flush the stream */
918 { "eof",
919 NULL,
920 aio_cmd_eof,
923 /* Description: Returns 1 if stream is at eof */
925 { "close",
926 NULL,
927 aio_cmd_close,
930 JIM_MODFLAG_FULLARGV,
931 /* Description: Closes the stream */
933 { "seek",
934 "offset ?start|current|end",
935 aio_cmd_seek,
938 /* Description: Seeks in the stream (default 'current') */
940 { "tell",
941 NULL,
942 aio_cmd_tell,
945 /* Description: Returns the current seek position */
947 { "filename",
948 NULL,
949 aio_cmd_filename,
952 /* Description: Returns the original filename */
954 #ifdef O_NDELAY
955 { "ndelay",
956 "?0|1?",
957 aio_cmd_ndelay,
960 /* Description: Set O_NDELAY (if arg). Returns current/new setting. */
962 #endif
963 { "buffering",
964 "none|line|full",
965 aio_cmd_buffering,
968 /* Description: Sets buffering */
970 #ifdef jim_ext_eventloop
971 { "readable",
972 "?readable-script?",
973 aio_cmd_readable,
976 /* Description: Returns script, or invoke readable-script when readable, {} to remove */
978 { "writable",
979 "?writable-script?",
980 aio_cmd_writable,
983 /* Description: Returns script, or invoke writable-script when writable, {} to remove */
985 { "onexception",
986 "?exception-script?",
987 aio_cmd_onexception,
990 /* Description: Returns script, or invoke exception-script when oob data, {} to remove */
992 #endif
993 { NULL }
996 static int JimAioSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
998 return Jim_CallSubCmd(interp, Jim_ParseSubCmd(interp, aio_command_table, argc, argv), argc, argv);
1001 static int JimAioOpenCommand(Jim_Interp *interp, int argc,
1002 Jim_Obj *const *argv)
1004 const char *mode;
1005 const char *filename;
1007 if (argc != 2 && argc != 3) {
1008 Jim_WrongNumArgs(interp, 1, argv, "filename ?mode?");
1009 return JIM_ERR;
1012 mode = (argc == 3) ? Jim_String(argv[2]) : "r";
1013 filename = Jim_String(argv[1]);
1015 #ifdef jim_ext_tclcompat
1016 /* If the filename starts with '|', use popen instead */
1017 if (*filename == '|') {
1018 Jim_Obj *evalObj[3];
1020 evalObj[0] = Jim_NewStringObj(interp, "popen", -1);
1021 evalObj[1] = Jim_NewStringObj(interp, filename + 1, -1);
1022 evalObj[2] = Jim_NewStringObj(interp, mode, -1);
1024 return Jim_EvalObjVector(interp, 3, evalObj);
1026 #endif
1027 return JimMakeChannel(interp, NULL, -1, argv[1], "aio.handle%ld", 0, mode);
1031 * Creates a channel for fh/fd/filename.
1033 * If fh is not NULL, uses that as the channel (and set AIO_KEEPOPEN).
1034 * Otherwise, if fd is >= 0, uses that as the chanel.
1035 * Otherwise opens 'filename' with mode 'mode'.
1037 * hdlfmt is a sprintf format for the filehandle. Anything with %ld at the end will do.
1038 * mode is used for open or fdopen.
1040 * Creates the command and sets the name as the current result.
1042 static int JimMakeChannel(Jim_Interp *interp, FILE *fh, int fd, Jim_Obj *filename,
1043 const char *hdlfmt, int family, const char *mode)
1045 AioFile *af;
1046 char buf[AIO_CMD_LEN];
1047 int OpenFlags = 0;
1049 if (filename == NULL) {
1050 filename = Jim_NewStringObj(interp, hdlfmt, -1);
1053 Jim_IncrRefCount(filename);
1055 if (fh == NULL) {
1056 if (fd < 0) {
1057 fh = fopen(Jim_String(filename), mode);
1059 else {
1060 fh = fdopen(fd, mode);
1063 else {
1064 OpenFlags = AIO_KEEPOPEN;
1067 if (fh == NULL) {
1068 JimAioSetError(interp, filename);
1069 #if !defined(JIM_ANSIC)
1070 if (fd >= 0) {
1071 close(fd);
1073 #endif
1074 Jim_DecrRefCount(interp, filename);
1075 return JIM_ERR;
1078 /* Create the file command */
1079 af = Jim_Alloc(sizeof(*af));
1080 memset(af, 0, sizeof(*af));
1081 af->fp = fh;
1082 af->fd = fileno(fh);
1083 af->filename = filename;
1084 #ifdef FD_CLOEXEC
1085 if ((OpenFlags & AIO_KEEPOPEN) == 0) {
1086 fcntl(af->fd, F_SETFD, FD_CLOEXEC);
1088 #endif
1089 af->OpenFlags = OpenFlags;
1090 #ifdef O_NDELAY
1091 af->flags = fcntl(af->fd, F_GETFL);
1092 #endif
1093 af->addr_family = family;
1094 snprintf(buf, sizeof(buf), hdlfmt, Jim_GetId(interp));
1095 Jim_CreateCommand(interp, buf, JimAioSubCmdProc, af, JimAioDelProc);
1097 Jim_SetResultString(interp, buf, -1);
1099 return JIM_OK;
1102 #if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)
1104 static int JimAioSockCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1106 const char *hdlfmt = "aio.unknown%ld";
1107 const char *socktypes[] = {
1108 "unix",
1109 "unix.server",
1110 "dgram",
1111 "dgram.server",
1112 "stream",
1113 "stream.server",
1114 "pipe",
1115 NULL
1117 enum
1119 SOCK_UNIX,
1120 SOCK_UNIX_SERVER,
1121 SOCK_DGRAM_CLIENT,
1122 SOCK_DGRAM_SERVER,
1123 SOCK_STREAM_CLIENT,
1124 SOCK_STREAM_SERVER,
1125 SOCK_STREAM_PIPE,
1126 SOCK_DGRAM6_CLIENT,
1127 SOCK_DGRAM6_SERVER,
1128 SOCK_STREAM6_CLIENT,
1129 SOCK_STREAM6_SERVER,
1131 int socktype;
1132 int sock;
1133 const char *hostportarg = NULL;
1134 int res;
1135 int on = 1;
1136 const char *mode = "r+";
1137 int family = PF_INET;
1138 Jim_Obj *argv0 = argv[0];
1139 int ipv6 = 0;
1141 if (argc > 1 && Jim_CompareStringImmediate(interp, argv[1], "-ipv6")) {
1142 if (!IPV6) {
1143 Jim_SetResultString(interp, "ipv6 not supported", -1);
1144 return JIM_ERR;
1146 ipv6 = 1;
1147 family = PF_INET6;
1149 argc -= ipv6;
1150 argv += ipv6;
1152 if (argc < 2) {
1153 wrongargs:
1154 Jim_WrongNumArgs(interp, 1, &argv0, "?-ipv6? type ?address?");
1155 return JIM_ERR;
1158 if (Jim_GetEnum(interp, argv[1], socktypes, &socktype, "socket type", JIM_ERRMSG) != JIM_OK)
1159 return JIM_ERR;
1161 Jim_SetEmptyResult(interp);
1163 hdlfmt = "aio.sock%ld";
1165 if (argc > 2) {
1166 hostportarg = Jim_String(argv[2]);
1169 switch (socktype) {
1170 case SOCK_DGRAM_CLIENT:
1171 if (argc == 2) {
1172 /* No address, so an unconnected dgram socket */
1173 sock = socket(family, SOCK_DGRAM, 0);
1174 if (sock < 0) {
1175 JimAioSetError(interp, NULL);
1176 return JIM_ERR;
1178 break;
1180 /* fall through */
1181 case SOCK_STREAM_CLIENT:
1183 union sockaddr_any sa;
1184 int salen;
1186 if (argc != 3) {
1187 goto wrongargs;
1190 if (ipv6) {
1191 if (JimParseIPv6Address(interp, hostportarg, &sa, &salen) != JIM_OK) {
1192 return JIM_ERR;
1195 else if (JimParseIpAddress(interp, hostportarg, &sa, &salen) != JIM_OK) {
1196 return JIM_ERR;
1198 sock = socket(family, (socktype == SOCK_DGRAM_CLIENT) ? SOCK_DGRAM : SOCK_STREAM, 0);
1199 if (sock < 0) {
1200 JimAioSetError(interp, NULL);
1201 return JIM_ERR;
1203 res = connect(sock, &sa.sa, salen);
1204 if (res) {
1205 JimAioSetError(interp, argv[2]);
1206 close(sock);
1207 return JIM_ERR;
1210 break;
1212 case SOCK_STREAM_SERVER:
1213 case SOCK_DGRAM_SERVER:
1215 union sockaddr_any sa;
1216 int salen;
1218 if (argc != 3) {
1219 goto wrongargs;
1222 if (ipv6) {
1223 if (JimParseIPv6Address(interp, hostportarg, &sa, &salen) != JIM_OK) {
1224 return JIM_ERR;
1227 else if (JimParseIpAddress(interp, hostportarg, &sa, &salen) != JIM_OK) {
1228 return JIM_ERR;
1230 sock = socket(family, (socktype == SOCK_DGRAM_SERVER) ? SOCK_DGRAM : SOCK_STREAM, 0);
1231 if (sock < 0) {
1232 JimAioSetError(interp, NULL);
1233 return JIM_ERR;
1236 /* Enable address reuse */
1237 setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on));
1239 res = bind(sock, &sa.sa, salen);
1240 if (res) {
1241 JimAioSetError(interp, argv[2]);
1242 close(sock);
1243 return JIM_ERR;
1245 if (socktype == SOCK_STREAM_SERVER) {
1246 res = listen(sock, 5);
1247 if (res) {
1248 JimAioSetError(interp, NULL);
1249 close(sock);
1250 return JIM_ERR;
1253 hdlfmt = "aio.socksrv%ld";
1255 break;
1257 #ifdef HAVE_SYS_UN_H
1258 case SOCK_UNIX:
1260 struct sockaddr_un sa;
1261 socklen_t len;
1263 if (argc != 3 || ipv6) {
1264 goto wrongargs;
1267 if (JimParseDomainAddress(interp, hostportarg, &sa) != JIM_OK) {
1268 JimAioSetError(interp, argv[2]);
1269 return JIM_ERR;
1271 family = PF_UNIX;
1272 sock = socket(PF_UNIX, SOCK_STREAM, 0);
1273 if (sock < 0) {
1274 JimAioSetError(interp, NULL);
1275 return JIM_ERR;
1277 len = strlen(sa.sun_path) + 1 + sizeof(sa.sun_family);
1278 res = connect(sock, (struct sockaddr *)&sa, len);
1279 if (res) {
1280 JimAioSetError(interp, argv[2]);
1281 close(sock);
1282 return JIM_ERR;
1284 hdlfmt = "aio.sockunix%ld";
1285 break;
1288 case SOCK_UNIX_SERVER:
1290 struct sockaddr_un sa;
1291 socklen_t len;
1293 if (argc != 3 || ipv6) {
1294 goto wrongargs;
1297 if (JimParseDomainAddress(interp, hostportarg, &sa) != JIM_OK) {
1298 JimAioSetError(interp, argv[2]);
1299 return JIM_ERR;
1301 family = PF_UNIX;
1302 sock = socket(PF_UNIX, SOCK_STREAM, 0);
1303 if (sock < 0) {
1304 JimAioSetError(interp, NULL);
1305 return JIM_ERR;
1307 len = strlen(sa.sun_path) + 1 + sizeof(sa.sun_family);
1308 res = bind(sock, (struct sockaddr *)&sa, len);
1309 if (res) {
1310 JimAioSetError(interp, argv[2]);
1311 close(sock);
1312 return JIM_ERR;
1314 res = listen(sock, 5);
1315 if (res) {
1316 JimAioSetError(interp, NULL);
1317 close(sock);
1318 return JIM_ERR;
1320 hdlfmt = "aio.sockunixsrv%ld";
1321 break;
1323 #endif
1325 #ifdef HAVE_PIPE
1326 case SOCK_STREAM_PIPE:
1328 int p[2];
1330 if (argc != 2 || ipv6) {
1331 goto wrongargs;
1334 if (pipe(p) < 0) {
1335 JimAioSetError(interp, NULL);
1336 return JIM_ERR;
1339 if (JimMakeChannel(interp, NULL, p[0], argv[1], "aio.pipe%ld", 0, "r") == JIM_OK) {
1340 Jim_Obj *objPtr = Jim_NewListObj(interp, NULL, 0);
1341 Jim_ListAppendElement(interp, objPtr, Jim_GetResult(interp));
1343 if (JimMakeChannel(interp, NULL, p[1], argv[1], "aio.pipe%ld", 0, "w") == JIM_OK) {
1344 Jim_ListAppendElement(interp, objPtr, Jim_GetResult(interp));
1345 Jim_SetResult(interp, objPtr);
1346 return JIM_OK;
1349 /* Can only be here if fdopen() failed */
1350 close(p[0]);
1351 close(p[1]);
1352 JimAioSetError(interp, NULL);
1353 return JIM_ERR;
1355 break;
1356 #endif
1357 default:
1358 Jim_SetResultString(interp, "Unsupported socket type", -1);
1359 return JIM_ERR;
1362 return JimMakeChannel(interp, NULL, sock, argv[1], hdlfmt, family, mode);
1364 #endif /* JIM_BOOTSTRAP */
1366 FILE *Jim_AioFilehandle(Jim_Interp *interp, Jim_Obj *command)
1368 Jim_Cmd *cmdPtr = Jim_GetCommand(interp, command, JIM_ERRMSG);
1370 if (cmdPtr && !cmdPtr->isproc && cmdPtr->u.native.cmdProc == JimAioSubCmdProc) {
1371 return ((AioFile *) cmdPtr->u.native.privData)->fp;
1373 Jim_SetResultFormatted(interp, "Not a filehandle: \"%#s\"", command);
1374 return NULL;
1377 int Jim_aioInit(Jim_Interp *interp)
1379 if (Jim_PackageProvide(interp, "aio", "1.0", JIM_ERRMSG))
1380 return JIM_ERR;
1382 Jim_CreateCommand(interp, "open", JimAioOpenCommand, NULL, NULL);
1383 #ifndef JIM_ANSIC
1384 Jim_CreateCommand(interp, "socket", JimAioSockCommand, NULL, NULL);
1385 #endif
1387 /* Create filehandles for stdin, stdout and stderr */
1388 JimMakeChannel(interp, stdin, -1, NULL, "stdin", 0, "r");
1389 JimMakeChannel(interp, stdout, -1, NULL, "stdout", 0, "w");
1390 JimMakeChannel(interp, stderr, -1, NULL, "stderr", 0, "w");
1392 return JIM_OK;