Return codes > 7 should be caught by default
[jimtcl.git] / jim-aio.c
blobfc4870051568da0642c9c8c394b9649dbd28f920
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 <unistd.h>
42 #include <stdio.h>
43 #include <string.h>
44 #include <errno.h>
45 #include <fcntl.h>
47 #include "jim.h"
48 #include "jimautoconf.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 #ifdef HAVE_SYS_UN_H
56 #include <sys/un.h>
57 #endif
58 #else
59 #define JIM_ANSIC
60 #endif
62 #include "jim-eventloop.h"
63 #include "jim-subcmd.h"
65 #define AIO_CMD_LEN 32 /* e.g. aio.handleXXXXXX */
66 #define AIO_BUF_LEN 256 /* Can keep this small and rely on stdio buffering */
68 #define AIO_KEEPOPEN 1
70 #if defined(JIM_IPV6)
71 #define IPV6 1
72 #else
73 #define IPV6 0
74 #ifndef PF_INET6
75 #define PF_INET6 0
76 #endif
77 #endif
79 #ifndef JIM_ANSIC
80 union sockaddr_any {
81 struct sockaddr sa;
82 struct sockaddr_in sin;
83 #if IPV6
84 struct sockaddr_in6 sin6;
85 #endif
88 #ifndef HAVE_INET_NTOP
89 const char *inet_ntop(int af, const void *src, char *dst, int size)
91 if (af != PF_INET) {
92 return NULL;
94 snprintf(dst, size, "%s", inet_ntoa(((struct sockaddr_in *)src)->sin_addr));
95 return dst;
97 #endif
98 #endif
100 typedef struct AioFile
102 FILE *fp;
103 Jim_Obj *filename;
104 int type;
105 int OpenFlags; /* AIO_KEEPOPEN? keep FILE* */
106 int fd;
107 #ifdef O_NDELAY
108 int flags;
109 #endif
110 Jim_Obj *rEvent;
111 Jim_Obj *wEvent;
112 Jim_Obj *eEvent;
113 #ifndef JIM_ANSIC
114 int addr_family;
115 #endif
116 } AioFile;
118 static int JimAioSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv);
120 #ifndef JIM_ANSIC
121 static int JimParseIPv6Address(Jim_Interp *interp, const char *hostport, union sockaddr_any *sa, int *salen)
123 #if IPV6
125 * An IPv6 addr/port looks like:
126 * [::1]
127 * [::1]:2000
128 * [fe80::223:6cff:fe95:bdc0%en1]:2000
129 * [::]:2000
130 * 2000
132 * Note that the "any" address is ::, which is the same as when no address is specified.
134 char *sthost = NULL;
135 const char *stport;
136 int ret = JIM_OK;
137 struct addrinfo req;
138 struct addrinfo *ai;
140 stport = strrchr(hostport, ':');
141 if (!stport) {
142 /* No : so, the whole thing is the port */
143 stport = hostport;
144 hostport = "::";
145 sthost = Jim_StrDup(hostport);
147 else {
148 stport++;
151 if (*hostport == '[') {
152 /* This is a numeric ipv6 address */
153 char *pt = strchr(++hostport, ']');
154 if (pt) {
155 sthost = Jim_StrDupLen(hostport, pt - hostport);
159 if (!sthost) {
160 sthost = Jim_StrDupLen(hostport, stport - hostport - 1);
163 memset(&req, '\0', sizeof(req));
164 req.ai_family = PF_INET6;
166 if (getaddrinfo(sthost, NULL, &req, &ai)) {
167 Jim_SetResultFormatted(interp, "Not a valid address: %s", hostport);
168 ret = JIM_ERR;
170 else {
171 memcpy(&sa->sin, ai->ai_addr, ai->ai_addrlen);
172 *salen = ai->ai_addrlen;
174 sa->sin.sin_port = htons(atoi(stport));
176 freeaddrinfo(ai);
178 Jim_Free(sthost);
180 return ret;
181 #else
182 Jim_SetResultString(interp, "ipv6 not supported", -1);
183 return JIM_ERR;
184 #endif
187 static int JimParseIpAddress(Jim_Interp *interp, const char *hostport, union sockaddr_any *sa, int *salen)
189 /* An IPv4 addr/port looks like:
190 * 192.168.1.5
191 * 192.168.1.5:2000
192 * 2000
194 * If the address is missing, INADDR_ANY is used.
195 * If the port is missing, 0 is used (only useful for server sockets).
197 char *sthost = NULL;
198 const char *stport;
199 int ret = JIM_OK;
201 stport = strrchr(hostport, ':');
202 if (!stport) {
203 /* No : so, the whole thing is the port */
204 stport = hostport;
205 sthost = Jim_StrDup("0.0.0.0");
207 else {
208 sthost = Jim_StrDupLen(hostport, stport - hostport);
209 stport++;
213 #ifdef HAVE_GETADDRINFO
214 struct addrinfo req;
215 struct addrinfo *ai;
216 memset(&req, '\0', sizeof(req));
217 req.ai_family = PF_INET;
219 if (getaddrinfo(sthost, NULL, &req, &ai)) {
220 ret = JIM_ERR;
222 else {
223 memcpy(&sa->sin, ai->ai_addr, ai->ai_addrlen);
224 *salen = ai->ai_addrlen;
225 freeaddrinfo(ai);
227 #else
228 struct hostent *he;
230 ret = JIM_ERR;
232 if ((he = gethostbyname(sthost)) != NULL) {
233 if (he->h_length == sizeof(sa->sin.sin_addr)) {
234 *salen = sizeof(sa->sin);
235 sa->sin.sin_family= he->h_addrtype;
236 memcpy(&sa->sin.sin_addr, he->h_addr, he->h_length); /* set address */
237 ret = JIM_OK;
240 #endif
242 sa->sin.sin_port = htons(atoi(stport));
244 Jim_Free(sthost);
246 if (ret != JIM_OK) {
247 Jim_SetResultFormatted(interp, "Not a valid address: %s", hostport);
250 return ret;
253 #ifdef HAVE_SYS_UN_H
254 static int JimParseDomainAddress(Jim_Interp *interp, const char *path, struct sockaddr_un *sa)
256 sa->sun_family = PF_UNIX;
257 snprintf(sa->sun_path, sizeof(sa->sun_path), "%s", path);
259 return JIM_OK;
261 #endif
262 #endif
264 static void JimAioSetError(Jim_Interp *interp, Jim_Obj *name)
266 if (name) {
267 Jim_SetResultFormatted(interp, "%#s: %s", name, strerror(errno));
269 else {
270 Jim_SetResultString(interp, strerror(errno), -1);
274 static void JimAioDelProc(Jim_Interp *interp, void *privData)
276 AioFile *af = privData;
278 JIM_NOTUSED(interp);
280 Jim_DecrRefCount(interp, af->filename);
282 if (!(af->OpenFlags & AIO_KEEPOPEN)) {
283 fclose(af->fp);
285 #ifdef jim_ext_eventloop
286 /* remove existing EventHandlers */
287 if (af->rEvent) {
288 Jim_DeleteFileHandler(interp, af->fp);
290 if (af->wEvent) {
291 Jim_DeleteFileHandler(interp, af->fp);
293 if (af->eEvent) {
294 Jim_DeleteFileHandler(interp, af->fp);
296 #endif
297 Jim_Free(af);
300 static int aio_cmd_read(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
302 AioFile *af = Jim_CmdPrivData(interp);
303 char buf[AIO_BUF_LEN];
304 Jim_Obj *objPtr;
305 int nonewline = 0;
306 int neededLen = -1; /* -1 is "read as much as possible" */
308 if (argc && Jim_CompareStringImmediate(interp, argv[0], "-nonewline")) {
309 nonewline = 1;
310 argv++;
311 argc--;
313 if (argc == 1) {
314 jim_wide wideValue;
316 if (Jim_GetWide(interp, argv[0], &wideValue) != JIM_OK)
317 return JIM_ERR;
318 if (wideValue < 0) {
319 Jim_SetResultString(interp, "invalid parameter: negative len", -1);
320 return JIM_ERR;
322 neededLen = (int)wideValue;
324 else if (argc) {
325 return -1;
327 objPtr = Jim_NewStringObj(interp, NULL, 0);
328 while (neededLen != 0) {
329 int retval;
330 int readlen;
332 if (neededLen == -1) {
333 readlen = AIO_BUF_LEN;
335 else {
336 readlen = (neededLen > AIO_BUF_LEN ? AIO_BUF_LEN : neededLen);
338 retval = fread(buf, 1, readlen, af->fp);
339 if (retval > 0) {
340 Jim_AppendString(interp, objPtr, buf, retval);
341 if (neededLen != -1) {
342 neededLen -= retval;
345 if (retval != readlen)
346 break;
348 /* Check for error conditions */
349 if (ferror(af->fp)) {
350 clearerr(af->fp);
351 /* eof and EAGAIN are not error conditions */
352 if (!feof(af->fp) && errno != EAGAIN) {
353 /* I/O error */
354 Jim_FreeNewObj(interp, objPtr);
355 JimAioSetError(interp, af->filename);
356 return JIM_ERR;
359 if (nonewline) {
360 int len;
361 const char *s = Jim_GetString(objPtr, &len);
363 if (len > 0 && s[len - 1] == '\n') {
364 objPtr->length--;
365 objPtr->bytes[objPtr->length] = '\0';
368 Jim_SetResult(interp, objPtr);
369 return JIM_OK;
372 static int aio_cmd_copy(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
374 AioFile *af = Jim_CmdPrivData(interp);
375 long count = 0;
376 long maxlen = LONG_MAX;
377 FILE *outfh = Jim_AioFilehandle(interp, argv[0]);
379 if (outfh == NULL) {
380 return JIM_ERR;
383 if (argc == 2) {
384 if (Jim_GetLong(interp, argv[1], &maxlen) != JIM_OK) {
385 return JIM_ERR;
389 while (count < maxlen) {
390 int ch = fgetc(af->fp);
392 if (ch == EOF || fputc(ch, outfh) == EOF) {
393 break;
395 count++;
398 if (ferror(af->fp)) {
399 Jim_SetResultFormatted(interp, "error while reading: %s", strerror(errno));
400 clearerr(af->fp);
401 return JIM_ERR;
404 if (ferror(outfh)) {
405 Jim_SetResultFormatted(interp, "error while writing: %s", strerror(errno));
406 clearerr(outfh);
407 return JIM_ERR;
410 Jim_SetResultInt(interp, count);
412 return JIM_OK;
415 static int aio_cmd_gets(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
417 AioFile *af = Jim_CmdPrivData(interp);
418 char buf[AIO_BUF_LEN];
419 Jim_Obj *objPtr;
421 errno = 0;
423 objPtr = Jim_NewStringObj(interp, NULL, 0);
424 while (1) {
425 int more = 0;
427 buf[AIO_BUF_LEN - 1] = '_';
428 if (fgets(buf, AIO_BUF_LEN, af->fp) == NULL)
429 break;
430 if (buf[AIO_BUF_LEN - 1] == '\0' && buf[AIO_BUF_LEN - 2] != '\n')
431 more = 1;
432 if (more) {
433 Jim_AppendString(interp, objPtr, buf, AIO_BUF_LEN - 1);
435 else {
436 int len = strlen(buf);
438 if (len) {
439 int hasnl = (buf[len - 1] == '\n');
441 /* strip "\n" */
442 Jim_AppendString(interp, objPtr, buf, strlen(buf) - hasnl);
445 if (!more)
446 break;
448 if (ferror(af->fp) && errno != EAGAIN && errno != EINTR) {
449 /* I/O error */
450 Jim_FreeNewObj(interp, objPtr);
451 JimAioSetError(interp, af->filename);
452 clearerr(af->fp);
453 return JIM_ERR;
455 /* On EOF returns -1 if varName was specified, or the empty string. */
456 if (feof(af->fp) && Jim_Length(objPtr) == 0) {
457 Jim_FreeNewObj(interp, objPtr);
458 if (argc) {
459 Jim_SetResultInt(interp, -1);
461 return JIM_OK;
463 if (argc) {
464 int totLen;
466 Jim_GetString(objPtr, &totLen);
467 if (Jim_SetVariable(interp, argv[0], objPtr) != JIM_OK) {
468 Jim_FreeNewObj(interp, objPtr);
469 return JIM_ERR;
471 Jim_SetResultInt(interp, totLen);
473 else {
474 Jim_SetResult(interp, objPtr);
476 return JIM_OK;
479 static int aio_cmd_puts(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
481 AioFile *af = Jim_CmdPrivData(interp);
482 int wlen;
483 const char *wdata;
484 Jim_Obj *strObj;
486 if (argc == 2) {
487 if (!Jim_CompareStringImmediate(interp, argv[0], "-nonewline")) {
488 return -1;
490 strObj = argv[1];
492 else {
493 strObj = argv[0];
496 wdata = Jim_GetString(strObj, &wlen);
497 if (fwrite(wdata, 1, wlen, af->fp) == (unsigned)wlen) {
498 if (argc == 2 || putc('\n', af->fp) != EOF) {
499 return JIM_OK;
502 JimAioSetError(interp, af->filename);
503 return JIM_ERR;
506 #ifndef JIM_ANSIC
507 static int aio_cmd_recvfrom(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
509 AioFile *af = Jim_CmdPrivData(interp);
510 char *buf;
511 union sockaddr_any sa;
512 long len;
513 socklen_t salen = sizeof(sa);
514 int rlen;
516 if (Jim_GetLong(interp, argv[0], &len) != JIM_OK) {
517 return JIM_ERR;
520 buf = Jim_Alloc(len + 1);
522 rlen = recvfrom(fileno(af->fp), buf, len, 0, &sa.sa, &salen);
523 if (rlen < 0) {
524 Jim_Free(buf);
525 JimAioSetError(interp, NULL);
526 return JIM_ERR;
528 buf[rlen] = 0;
529 Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, buf, rlen));
531 if (argc > 1) {
532 /* INET6_ADDRSTRLEN is 46. Add some for [] and port */
533 char addrbuf[60];
535 #if IPV6
536 if (sa.sa.sa_family == PF_INET6) {
537 addrbuf[0] = '[';
538 /* Allow 9 for []:65535\0 */
539 inet_ntop(sa.sa.sa_family, &sa.sin6.sin6_addr, addrbuf + 1, sizeof(addrbuf) - 9);
540 snprintf(addrbuf + strlen(addrbuf), 8, "]:%d", ntohs(sa.sin.sin_port));
542 else
543 #endif
545 /* Allow 7 for :65535\0 */
546 inet_ntop(sa.sa.sa_family, &sa.sin.sin_addr, addrbuf, sizeof(addrbuf) - 7);
547 snprintf(addrbuf + strlen(addrbuf), 7, ":%d", ntohs(sa.sin.sin_port));
550 if (Jim_SetVariable(interp, argv[1], Jim_NewStringObj(interp, addrbuf, -1)) != JIM_OK) {
551 return JIM_ERR;
555 return JIM_OK;
559 static int aio_cmd_sendto(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
561 AioFile *af = Jim_CmdPrivData(interp);
562 int wlen;
563 int len;
564 const char *wdata;
565 union sockaddr_any sa;
566 const char *addr = Jim_String(argv[1]);
567 int salen;
569 if (IPV6 && af->addr_family == PF_INET6) {
570 if (JimParseIPv6Address(interp, addr, &sa, &salen) != JIM_OK) {
571 return JIM_ERR;
574 else if (JimParseIpAddress(interp, addr, &sa, &salen) != JIM_OK) {
575 return JIM_ERR;
577 wdata = Jim_GetString(argv[0], &wlen);
579 /* Note that we don't validate the socket type. Rely on sendto() failing if appropriate */
580 len = sendto(fileno(af->fp), wdata, wlen, 0, &sa.sa, salen);
581 if (len < 0) {
582 JimAioSetError(interp, NULL);
583 return JIM_ERR;
585 Jim_SetResultInt(interp, len);
586 return JIM_OK;
589 static int aio_cmd_accept(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
591 AioFile *serv_af = Jim_CmdPrivData(interp);
592 int sock;
593 union sockaddr_any sa;
594 socklen_t addrlen = sizeof(sa);
595 AioFile *af;
596 char buf[AIO_CMD_LEN];
598 sock = accept(serv_af->fd, &sa.sa, &addrlen);
599 if (sock < 0)
600 return JIM_ERR;
602 /* Create the file command */
603 af = Jim_Alloc(sizeof(*af));
604 af->fd = sock;
605 #ifdef FD_CLOEXEC
606 fcntl(af->fd, F_SETFD, FD_CLOEXEC);
607 #endif
608 af->filename = Jim_NewStringObj(interp, "accept", -1);
609 Jim_IncrRefCount(af->filename);
610 af->fp = fdopen(sock, "r+");
612 af->OpenFlags = 0;
613 #ifdef O_NDELAY
614 af->flags = fcntl(af->fd, F_GETFL);
615 #endif
616 af->rEvent = NULL;
617 af->wEvent = NULL;
618 af->eEvent = NULL;
619 af->addr_family = serv_af->addr_family;
620 snprintf(buf, sizeof(buf), "aio.sockstream%ld", Jim_GetId(interp));
621 Jim_CreateCommand(interp, buf, JimAioSubCmdProc, af, JimAioDelProc);
622 Jim_SetResultString(interp, buf, -1);
623 return JIM_OK;
626 #endif
628 static int aio_cmd_flush(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
630 AioFile *af = Jim_CmdPrivData(interp);
632 if (fflush(af->fp) == EOF) {
633 JimAioSetError(interp, af->filename);
634 return JIM_ERR;
636 return JIM_OK;
639 static int aio_cmd_eof(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
641 AioFile *af = Jim_CmdPrivData(interp);
643 Jim_SetResultInt(interp, feof(af->fp));
644 return JIM_OK;
647 static int aio_cmd_close(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
649 Jim_DeleteCommand(interp, Jim_String(argv[0]));
650 return JIM_OK;
653 static int aio_cmd_seek(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
655 AioFile *af = Jim_CmdPrivData(interp);
656 int orig = SEEK_SET;
657 long offset;
659 if (argc == 2) {
660 if (Jim_CompareStringImmediate(interp, argv[1], "start"))
661 orig = SEEK_SET;
662 else if (Jim_CompareStringImmediate(interp, argv[1], "current"))
663 orig = SEEK_CUR;
664 else if (Jim_CompareStringImmediate(interp, argv[1], "end"))
665 orig = SEEK_END;
666 else {
667 return -1;
670 if (Jim_GetLong(interp, argv[0], &offset) != JIM_OK) {
671 return JIM_ERR;
673 if (fseek(af->fp, offset, orig) == -1) {
674 JimAioSetError(interp, af->filename);
675 return JIM_ERR;
677 return JIM_OK;
680 static int aio_cmd_tell(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
682 AioFile *af = Jim_CmdPrivData(interp);
684 Jim_SetResultInt(interp, ftell(af->fp));
685 return JIM_OK;
688 static int aio_cmd_filename(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
690 AioFile *af = Jim_CmdPrivData(interp);
692 Jim_SetResult(interp, af->filename);
693 return JIM_OK;
696 #ifdef O_NDELAY
697 static int aio_cmd_ndelay(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
699 AioFile *af = Jim_CmdPrivData(interp);
701 int fmode = af->flags;
703 if (argc) {
704 long nb;
706 if (Jim_GetLong(interp, argv[0], &nb) != JIM_OK) {
707 return JIM_ERR;
709 if (nb) {
710 fmode |= O_NDELAY;
712 else {
713 fmode &= ~O_NDELAY;
715 fcntl(af->fd, F_SETFL, fmode);
716 af->flags = fmode;
718 Jim_SetResultInt(interp, (fmode & O_NONBLOCK) ? 1 : 0);
719 return JIM_OK;
721 #endif
723 static int aio_cmd_buffering(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
725 AioFile *af = Jim_CmdPrivData(interp);
727 static const char * const options[] = {
728 "none",
729 "line",
730 "full",
731 NULL
733 enum
735 OPT_NONE,
736 OPT_LINE,
737 OPT_FULL,
739 int option;
741 if (Jim_GetEnum(interp, argv[0], options, &option, NULL, JIM_ERRMSG) != JIM_OK) {
742 return JIM_ERR;
744 switch (option) {
745 case OPT_NONE:
746 setvbuf(af->fp, NULL, _IONBF, 0);
747 break;
748 case OPT_LINE:
749 setvbuf(af->fp, NULL, _IOLBF, BUFSIZ);
750 break;
751 case OPT_FULL:
752 setvbuf(af->fp, NULL, _IOFBF, BUFSIZ);
753 break;
755 return JIM_OK;
758 #ifdef jim_ext_eventloop
759 static void JimAioFileEventFinalizer(Jim_Interp *interp, void *clientData)
761 Jim_Obj *objPtr = clientData;
763 Jim_DecrRefCount(interp, objPtr);
766 static int JimAioFileEventHandler(Jim_Interp *interp, void *clientData, int mask)
768 Jim_Obj *objPtr = clientData;
770 return Jim_EvalObjBackground(interp, objPtr);
773 static int aio_eventinfo(Jim_Interp *interp, AioFile * af, unsigned mask, Jim_Obj **scriptHandlerObj,
774 int argc, Jim_Obj * const *argv)
776 int scriptlen = 0;
778 if (argc == 0) {
779 /* Return current script */
780 if (*scriptHandlerObj) {
781 Jim_SetResult(interp, *scriptHandlerObj);
783 return JIM_OK;
786 if (*scriptHandlerObj) {
787 /* Delete old handler */
788 Jim_DeleteFileHandler(interp, af->fp);
789 *scriptHandlerObj = NULL;
792 /* Now possibly add the new script(s) */
793 Jim_GetString(argv[0], &scriptlen);
794 if (scriptlen == 0) {
795 /* Empty script, so done */
796 return JIM_OK;
799 /* A new script to add */
800 Jim_IncrRefCount(argv[0]);
801 *scriptHandlerObj = argv[0];
803 Jim_CreateFileHandler(interp, af->fp, mask,
804 JimAioFileEventHandler, *scriptHandlerObj, JimAioFileEventFinalizer);
806 return JIM_OK;
809 static int aio_cmd_readable(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
811 AioFile *af = Jim_CmdPrivData(interp);
813 return aio_eventinfo(interp, af, JIM_EVENT_READABLE, &af->rEvent, argc, argv);
816 static int aio_cmd_writable(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
818 AioFile *af = Jim_CmdPrivData(interp);
820 return aio_eventinfo(interp, af, JIM_EVENT_WRITABLE, &af->wEvent, argc, argv);
823 static int aio_cmd_onexception(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
825 AioFile *af = Jim_CmdPrivData(interp);
827 return aio_eventinfo(interp, af, JIM_EVENT_EXCEPTION, &af->wEvent, argc, argv);
829 #endif
831 static const jim_subcmd_type aio_command_table[] = {
832 { .cmd = "read",
833 .args = "?-nonewline? ?len?",
834 .function = aio_cmd_read,
835 .minargs = 0,
836 .maxargs = 2,
837 .description = "Read and return bytes from the stream. To eof if no len."
839 { .cmd = "copyto",
840 .args = "handle ?size?",
841 .function = aio_cmd_copy,
842 .minargs = 1,
843 .maxargs = 2,
844 .description = "Copy up to 'size' bytes to the given filehandle, or to eof if no size."
846 { .cmd = "gets",
847 .args = "?var?",
848 .function = aio_cmd_gets,
849 .minargs = 0,
850 .maxargs = 1,
851 .description = "Read one line and return it or store it in the var"
853 { .cmd = "puts",
854 .args = "?-nonewline? str",
855 .function = aio_cmd_puts,
856 .minargs = 1,
857 .maxargs = 2,
858 .description = "Write the string, with newline unless -nonewline"
860 #ifndef JIM_ANSIC
861 { .cmd = "recvfrom",
862 .args = "len ?addrvar?",
863 .function = aio_cmd_recvfrom,
864 .minargs = 1,
865 .maxargs = 2,
866 .description = "Receive up to 'len' bytes on the socket. Sets 'addrvar' with receive address, if set"
868 { .cmd = "sendto",
869 .args = "str address",
870 .function = aio_cmd_sendto,
871 .minargs = 2,
872 .maxargs = 2,
873 .description = "Send 'str' to the given address (dgram only)"
875 { .cmd = "accept",
876 .function = aio_cmd_accept,
877 .description = "Server socket only: Accept a connection and return stream"
879 #endif
880 { .cmd = "flush",
881 .function = aio_cmd_flush,
882 .description = "Flush the stream"
884 { .cmd = "eof",
885 .function = aio_cmd_eof,
886 .description = "Returns 1 if stream is at eof"
888 { .cmd = "close",
889 .flags = JIM_MODFLAG_FULLARGV,
890 .function = aio_cmd_close,
891 .description = "Closes the stream"
893 { .cmd = "seek",
894 .args = "offset ?start|current|end",
895 .function = aio_cmd_seek,
896 .minargs = 1,
897 .maxargs = 2,
898 .description = "Seeks in the stream (default 'current')"
900 { .cmd = "tell",
901 .function = aio_cmd_tell,
902 .description = "Returns the current seek position"
904 { .cmd = "filename",
905 .function = aio_cmd_filename,
906 .description = "Returns the original filename"
908 #ifdef O_NDELAY
909 { .cmd = "ndelay",
910 .args = "?0|1?",
911 .function = aio_cmd_ndelay,
912 .minargs = 0,
913 .maxargs = 1,
914 .description = "Set O_NDELAY (if arg). Returns current/new setting."
916 #endif
917 { .cmd = "buffering",
918 .args = "none|line|full",
919 .function = aio_cmd_buffering,
920 .minargs = 1,
921 .maxargs = 1,
922 .description = "Sets buffering"
924 #ifdef jim_ext_eventloop
925 { .cmd = "readable",
926 .args = "?readable-script?",
927 .minargs = 0,
928 .maxargs = 1,
929 .function = aio_cmd_readable,
930 .description = "Returns script, or invoke readable-script when readable, {} to remove",
932 { .cmd = "writable",
933 .args = "?writable-script?",
934 .minargs = 0,
935 .maxargs = 1,
936 .function = aio_cmd_writable,
937 .description = "Returns script, or invoke writable-script when writable, {} to remove",
939 { .cmd = "onexception",
940 .args = "?exception-script?",
941 .minargs = 0,
942 .maxargs = 1,
943 .function = aio_cmd_onexception,
944 .description = "Returns script, or invoke exception-script when oob data, {} to remove",
946 #endif
947 { 0 }
950 static int JimAioSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
952 return Jim_CallSubCmd(interp, Jim_ParseSubCmd(interp, aio_command_table, argc, argv), argc, argv);
955 static int JimAioOpenCommand(Jim_Interp *interp, int argc,
956 Jim_Obj *const *argv)
958 FILE *fp;
959 AioFile *af;
960 char buf[AIO_CMD_LEN];
961 int OpenFlags = 0;
962 const char *cmdname;
964 if (argc != 2 && argc != 3) {
965 Jim_WrongNumArgs(interp, 1, argv, "filename ?mode?");
966 return JIM_ERR;
968 cmdname = Jim_String(argv[1]);
969 if (Jim_CompareStringImmediate(interp, argv[1], "stdin")) {
970 OpenFlags |= AIO_KEEPOPEN;
971 fp = stdin;
973 else if (Jim_CompareStringImmediate(interp, argv[1], "stdout")) {
974 OpenFlags |= AIO_KEEPOPEN;
975 fp = stdout;
977 else if (Jim_CompareStringImmediate(interp, argv[1], "stderr")) {
978 OpenFlags |= AIO_KEEPOPEN;
979 fp = stderr;
981 else {
982 const char *mode = (argc == 3) ? Jim_String(argv[2]) : "r";
983 const char *filename = Jim_String(argv[1]);
985 #ifdef jim_ext_tclcompat
986 /* If the filename starts with '|', use popen instead */
987 if (*filename == '|') {
988 Jim_Obj *evalObj[3];
990 evalObj[0] = Jim_NewStringObj(interp, "popen", -1);
991 evalObj[1] = Jim_NewStringObj(interp, filename + 1, -1);
992 evalObj[2] = Jim_NewStringObj(interp, mode, -1);
994 return Jim_EvalObjVector(interp, 3, evalObj);
996 #endif
997 fp = fopen(filename, mode);
998 if (fp == NULL) {
999 JimAioSetError(interp, argv[1]);
1000 return JIM_ERR;
1002 /* Get the next file id */
1003 snprintf(buf, sizeof(buf), "aio.handle%ld", Jim_GetId(interp));
1004 cmdname = buf;
1007 /* Create the file command */
1008 af = Jim_Alloc(sizeof(*af));
1009 af->fp = fp;
1010 af->fd = fileno(fp);
1011 #ifdef FD_CLOEXEC
1012 if ((OpenFlags & AIO_KEEPOPEN) == 0) {
1013 fcntl(af->fd, F_SETFD, FD_CLOEXEC);
1015 #endif
1016 #ifdef O_NDELAY
1017 af->flags = fcntl(af->fd, F_GETFL);
1018 #endif
1019 af->filename = argv[1];
1020 Jim_IncrRefCount(af->filename);
1021 af->OpenFlags = OpenFlags;
1022 af->rEvent = NULL;
1023 af->wEvent = NULL;
1024 af->eEvent = NULL;
1025 Jim_CreateCommand(interp, cmdname, JimAioSubCmdProc, af, JimAioDelProc);
1026 Jim_SetResultString(interp, cmdname, -1);
1027 return JIM_OK;
1030 #ifndef JIM_ANSIC
1033 * Creates a channel for fd.
1035 * hdlfmt is a sprintf format for the filehandle. Anything with %ld at the end will do.
1036 * mode is usual "r+", but may be another fdopen() mode as required.
1038 * Creates the command and lappends the name of the command to the current result.
1041 static int JimMakeChannel(Jim_Interp *interp, Jim_Obj *filename, const char *hdlfmt, int fd, int family,
1042 const char *mode)
1044 AioFile *af;
1045 char buf[AIO_CMD_LEN];
1047 FILE *fp = fdopen(fd, mode);
1049 if (fp == NULL) {
1050 close(fd);
1051 JimAioSetError(interp, NULL);
1052 return JIM_ERR;
1055 /* Create the file command */
1056 af = Jim_Alloc(sizeof(*af));
1057 af->fp = fp;
1058 af->fd = fd;
1059 fcntl(af->fd, F_SETFD, FD_CLOEXEC);
1060 af->OpenFlags = 0;
1061 af->filename = filename;
1062 Jim_IncrRefCount(af->filename);
1063 #ifdef O_NDELAY
1064 af->flags = fcntl(af->fd, F_GETFL);
1065 #endif
1066 af->rEvent = NULL;
1067 af->wEvent = NULL;
1068 af->eEvent = NULL;
1069 af->addr_family = family;
1070 snprintf(buf, sizeof(buf), hdlfmt, Jim_GetId(interp));
1071 Jim_CreateCommand(interp, buf, JimAioSubCmdProc, af, JimAioDelProc);
1073 Jim_ListAppendElement(interp, Jim_GetResult(interp), Jim_NewStringObj(interp, buf, -1));
1075 return JIM_OK;
1078 static int JimAioSockCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1080 const char *hdlfmt = "aio.unknown%ld";
1081 const char *socktypes[] = {
1082 "unix",
1083 "unix.server",
1084 "dgram",
1085 "dgram.server",
1086 "stream",
1087 "stream.server",
1088 "pipe",
1089 NULL
1091 enum
1093 SOCK_UNIX,
1094 SOCK_UNIX_SERVER,
1095 SOCK_DGRAM_CLIENT,
1096 SOCK_DGRAM_SERVER,
1097 SOCK_STREAM_CLIENT,
1098 SOCK_STREAM_SERVER,
1099 SOCK_STREAM_PIPE,
1100 SOCK_DGRAM6_CLIENT,
1101 SOCK_DGRAM6_SERVER,
1102 SOCK_STREAM6_CLIENT,
1103 SOCK_STREAM6_SERVER,
1105 int socktype;
1106 int sock;
1107 const char *hostportarg = NULL;
1108 int res;
1109 int on = 1;
1110 const char *mode = "r+";
1111 int family = PF_INET;
1112 Jim_Obj *argv0 = argv[0];
1113 int ipv6 = 0;
1115 if (argc > 1 && Jim_CompareStringImmediate(interp, argv[1], "-ipv6")) {
1116 if (!IPV6) {
1117 Jim_SetResultString(interp, "ipv6 not supported", -1);
1118 return JIM_ERR;
1120 ipv6 = 1;
1121 family = PF_INET6;
1123 argc -= ipv6;
1124 argv += ipv6;
1126 if (argc < 2) {
1127 wrongargs:
1128 Jim_WrongNumArgs(interp, 1, &argv0, "?-ipv6? type ?address?");
1129 return JIM_ERR;
1132 if (Jim_GetEnum(interp, argv[1], socktypes, &socktype, "socket type", JIM_ERRMSG) != JIM_OK)
1133 return JIM_ERR;
1135 Jim_SetResultString(interp, "", 0);
1137 hdlfmt = "aio.sock%ld";
1139 if (argc > 2) {
1140 hostportarg = Jim_String(argv[2]);
1143 switch (socktype) {
1144 case SOCK_DGRAM_CLIENT:
1145 if (argc == 2) {
1146 /* No address, so an unconnected dgram socket */
1147 sock = socket(family, SOCK_DGRAM, 0);
1148 if (sock < 0) {
1149 JimAioSetError(interp, NULL);
1150 return JIM_ERR;
1152 break;
1154 /* fall through */
1155 case SOCK_STREAM_CLIENT:
1157 union sockaddr_any sa;
1158 int salen;
1160 if (argc != 3) {
1161 goto wrongargs;
1164 if (ipv6) {
1165 if (JimParseIPv6Address(interp, hostportarg, &sa, &salen) != JIM_OK) {
1166 return JIM_ERR;
1169 else if (JimParseIpAddress(interp, hostportarg, &sa, &salen) != JIM_OK) {
1170 return JIM_ERR;
1172 sock = socket(family, (socktype == SOCK_DGRAM_CLIENT) ? SOCK_DGRAM : SOCK_STREAM, 0);
1173 if (sock < 0) {
1174 JimAioSetError(interp, NULL);
1175 return JIM_ERR;
1177 res = connect(sock, &sa.sa, salen);
1178 if (res) {
1179 JimAioSetError(interp, argv[2]);
1180 close(sock);
1181 return JIM_ERR;
1184 break;
1186 case SOCK_STREAM_SERVER:
1187 case SOCK_DGRAM_SERVER:
1189 union sockaddr_any sa;
1190 int salen;
1192 if (argc != 3) {
1193 goto wrongargs;
1196 if (ipv6) {
1197 if (JimParseIPv6Address(interp, hostportarg, &sa, &salen) != JIM_OK) {
1198 return JIM_ERR;
1201 else if (JimParseIpAddress(interp, hostportarg, &sa, &salen) != JIM_OK) {
1202 return JIM_ERR;
1204 sock = socket(family, (socktype == SOCK_DGRAM_SERVER) ? SOCK_DGRAM : SOCK_STREAM, 0);
1205 if (sock < 0) {
1206 JimAioSetError(interp, NULL);
1207 return JIM_ERR;
1210 /* Enable address reuse */
1211 setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on));
1213 res = bind(sock, &sa.sa, salen);
1214 if (res) {
1215 JimAioSetError(interp, argv[2]);
1216 close(sock);
1217 return JIM_ERR;
1219 if (socktype == SOCK_STREAM_SERVER) {
1220 res = listen(sock, 5);
1221 if (res) {
1222 JimAioSetError(interp, NULL);
1223 close(sock);
1224 return JIM_ERR;
1227 hdlfmt = "aio.socksrv%ld";
1229 break;
1231 #ifdef HAVE_SYS_UN_H
1232 case SOCK_UNIX:
1234 struct sockaddr_un sa;
1235 socklen_t len;
1237 if (argc != 3 || ipv6) {
1238 goto wrongargs;
1241 if (JimParseDomainAddress(interp, hostportarg, &sa) != JIM_OK) {
1242 JimAioSetError(interp, argv[2]);
1243 return JIM_ERR;
1245 family = PF_UNIX;
1246 sock = socket(PF_UNIX, SOCK_STREAM, 0);
1247 if (sock < 0) {
1248 JimAioSetError(interp, NULL);
1249 return JIM_ERR;
1251 len = strlen(sa.sun_path) + 1 + sizeof(sa.sun_family);
1252 res = connect(sock, (struct sockaddr *)&sa, len);
1253 if (res) {
1254 JimAioSetError(interp, argv[2]);
1255 close(sock);
1256 return JIM_ERR;
1258 hdlfmt = "aio.sockunix%ld";
1259 break;
1262 case SOCK_UNIX_SERVER:
1264 struct sockaddr_un sa;
1265 socklen_t len;
1267 if (argc != 3 || ipv6) {
1268 goto wrongargs;
1271 if (JimParseDomainAddress(interp, hostportarg, &sa) != JIM_OK) {
1272 JimAioSetError(interp, argv[2]);
1273 return JIM_ERR;
1275 family = PF_UNIX;
1276 sock = socket(PF_UNIX, SOCK_STREAM, 0);
1277 if (sock < 0) {
1278 JimAioSetError(interp, NULL);
1279 return JIM_ERR;
1281 len = strlen(sa.sun_path) + 1 + sizeof(sa.sun_family);
1282 res = bind(sock, (struct sockaddr *)&sa, len);
1283 if (res) {
1284 JimAioSetError(interp, argv[2]);
1285 close(sock);
1286 return JIM_ERR;
1288 res = listen(sock, 5);
1289 if (res) {
1290 JimAioSetError(interp, NULL);
1291 close(sock);
1292 return JIM_ERR;
1294 hdlfmt = "aio.sockunixsrv%ld";
1295 break;
1297 #endif
1299 #ifdef HAVE_PIPE
1300 case SOCK_STREAM_PIPE:
1302 int p[2];
1304 if (argc != 2 || ipv6) {
1305 goto wrongargs;
1308 if (pipe(p) < 0) {
1309 JimAioSetError(interp, NULL);
1310 return JIM_ERR;
1313 hdlfmt = "aio.pipe%ld";
1314 if (JimMakeChannel(interp, argv[1], hdlfmt, p[0], family, "r") != JIM_OK) {
1315 close(p[0]);
1316 close(p[1]);
1317 JimAioSetError(interp, NULL);
1318 return JIM_ERR;
1320 /* Note, if this fails it will leave p[0] open, but this should never happen */
1321 mode = "w";
1322 sock = p[1];
1324 break;
1325 #endif
1326 default:
1327 Jim_SetResultString(interp, "Unsupported socket type", -1);
1328 return JIM_ERR;
1331 return JimMakeChannel(interp, argv[1], hdlfmt, sock, family, mode);
1333 #endif
1335 FILE *Jim_AioFilehandle(Jim_Interp *interp, Jim_Obj *command)
1337 Jim_Cmd *cmdPtr = Jim_GetCommand(interp, command, JIM_ERRMSG);
1339 if (cmdPtr && !cmdPtr->isproc && cmdPtr->u.native.cmdProc == JimAioSubCmdProc) {
1340 return ((AioFile *) cmdPtr->u.native.privData)->fp;
1342 Jim_SetResultFormatted(interp, "Not a filehandle: \"%#s\"", command);
1343 return NULL;
1346 int Jim_aioInit(Jim_Interp *interp)
1348 if (Jim_PackageProvide(interp, "aio", "1.0", JIM_ERRMSG))
1349 return JIM_ERR;
1351 Jim_CreateCommand(interp, "open", JimAioOpenCommand, NULL, NULL);
1352 #ifndef JIM_ANSIC
1353 Jim_CreateCommand(interp, "socket", JimAioSockCommand, NULL, NULL);
1354 #endif
1356 /* Takeover stdin, stdout and stderr */
1357 Jim_EvalGlobal(interp, "open stdin; open stdout; open stderr");
1359 return JIM_OK;