signal: minor code cleanup
[jimtcl.git] / jim-aio.c
blob294aab561232148e3be384dd8b788209c34f8246
1 /* Jim - A small embeddable Tcl interpreter
3 * Copyright 2005 Salvatore Sanfilippo <antirez@invece.org>
4 * Copyright 2005 Clemens Hintze <c.hintze@gmx.net>
5 * Copyright 2005 patthoyts - Pat Thoyts <patthoyts@users.sf.net>
6 * Copyright 2008 oharboe - Øyvind Harboe - oyvind.harboe@zylin.com
7 * Copyright 2008 Andrew Lunn <andrew@lunn.ch>
8 * Copyright 2008 Duane Ellis <openocd@duaneellis.com>
9 * Copyright 2008 Uwe Klein <uklein@klein-messgeraete.de>
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above
18 * copyright notice, this list of conditions and the following
19 * disclaimer in the documentation and/or other materials
20 * provided with the distribution.
22 * THIS SOFTWARE IS PROVIDED BY THE JIM TCL PROJECT ``AS IS'' AND ANY
23 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
24 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
25 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26 * JIM TCL PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
27 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
28 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
31 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
33 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 * The views and conclusions contained in the software and documentation
36 * are those of the authors and should not be interpreted as representing
37 * official policies, either expressed or implied, of the Jim Tcl Project.
38 **/
40 #include "jimautoconf.h"
42 #include <stdio.h>
43 #include <string.h>
44 #include <errno.h>
45 #include <fcntl.h>
46 #ifdef HAVE_UNISTD_H
47 #include <unistd.h>
48 #endif
50 #include "jim.h"
52 #if defined(HAVE_SYS_SOCKET_H) && defined(HAVE_SELECT) && defined(HAVE_NETINET_IN_H) && defined(HAVE_NETDB_H) && defined(HAVE_ARPA_INET_H)
53 #include <sys/socket.h>
54 #include <netinet/in.h>
55 #include <arpa/inet.h>
56 #include <netdb.h>
57 #include <unistd.h>
58 #ifdef HAVE_SYS_UN_H
59 #include <sys/un.h>
60 #endif
61 #else
62 #define JIM_ANSIC
63 #endif
65 #include "jim-eventloop.h"
66 #include "jim-subcmd.h"
68 #define AIO_CMD_LEN 32 /* e.g. aio.handleXXXXXX */
69 #define AIO_BUF_LEN 256 /* Can keep this small and rely on stdio buffering */
71 #ifndef HAVE_FTELLO
72 #define ftello ftell
73 #endif
74 #ifndef HAVE_FSEEKO
75 #define fseeko fseek
76 #endif
78 #define AIO_KEEPOPEN 1
80 #if defined(JIM_IPV6)
81 #define IPV6 1
82 #else
83 #define IPV6 0
84 #ifndef PF_INET6
85 #define PF_INET6 0
86 #endif
87 #endif
89 #if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)
90 union sockaddr_any {
91 struct sockaddr sa;
92 struct sockaddr_in sin;
93 #if IPV6
94 struct sockaddr_in6 sin6;
95 #endif
98 #ifndef HAVE_INET_NTOP
99 const char *inet_ntop(int af, const void *src, char *dst, int size)
101 if (af != PF_INET) {
102 return NULL;
104 snprintf(dst, size, "%s", inet_ntoa(((struct sockaddr_in *)src)->sin_addr));
105 return dst;
107 #endif
108 #endif /* JIM_BOOTSTRAP */
110 typedef struct AioFile
112 FILE *fp;
113 Jim_Obj *filename;
114 int type;
115 int OpenFlags; /* AIO_KEEPOPEN? keep FILE* */
116 int fd;
117 Jim_Obj *rEvent;
118 Jim_Obj *wEvent;
119 Jim_Obj *eEvent;
120 int addr_family;
121 } AioFile;
123 static int JimAioSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv);
124 static int JimMakeChannel(Jim_Interp *interp, FILE *fh, int fd, Jim_Obj *filename,
125 const char *hdlfmt, int family, const char *mode);
127 #if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)
128 static int JimParseIPv6Address(Jim_Interp *interp, const char *hostport, union sockaddr_any *sa, int *salen)
130 #if IPV6
132 * An IPv6 addr/port looks like:
133 * [::1]
134 * [::1]:2000
135 * [fe80::223:6cff:fe95:bdc0%en1]:2000
136 * [::]:2000
137 * 2000
139 * Note that the "any" address is ::, which is the same as when no address is specified.
141 char *sthost = NULL;
142 const char *stport;
143 int ret = JIM_OK;
144 struct addrinfo req;
145 struct addrinfo *ai;
147 stport = strrchr(hostport, ':');
148 if (!stport) {
149 /* No : so, the whole thing is the port */
150 stport = hostport;
151 hostport = "::";
152 sthost = Jim_StrDup(hostport);
154 else {
155 stport++;
158 if (*hostport == '[') {
159 /* This is a numeric ipv6 address */
160 char *pt = strchr(++hostport, ']');
161 if (pt) {
162 sthost = Jim_StrDupLen(hostport, pt - hostport);
166 if (!sthost) {
167 sthost = Jim_StrDupLen(hostport, stport - hostport - 1);
170 memset(&req, '\0', sizeof(req));
171 req.ai_family = PF_INET6;
173 if (getaddrinfo(sthost, NULL, &req, &ai)) {
174 Jim_SetResultFormatted(interp, "Not a valid address: %s", hostport);
175 ret = JIM_ERR;
177 else {
178 memcpy(&sa->sin, ai->ai_addr, ai->ai_addrlen);
179 *salen = ai->ai_addrlen;
181 sa->sin.sin_port = htons(atoi(stport));
183 freeaddrinfo(ai);
185 Jim_Free(sthost);
187 return ret;
188 #else
189 Jim_SetResultString(interp, "ipv6 not supported", -1);
190 return JIM_ERR;
191 #endif
194 static int JimParseIpAddress(Jim_Interp *interp, const char *hostport, union sockaddr_any *sa, int *salen)
196 /* An IPv4 addr/port looks like:
197 * 192.168.1.5
198 * 192.168.1.5:2000
199 * 2000
201 * If the address is missing, INADDR_ANY is used.
202 * If the port is missing, 0 is used (only useful for server sockets).
204 char *sthost = NULL;
205 const char *stport;
206 int ret = JIM_OK;
208 stport = strrchr(hostport, ':');
209 if (!stport) {
210 /* No : so, the whole thing is the port */
211 stport = hostport;
212 sthost = Jim_StrDup("0.0.0.0");
214 else {
215 sthost = Jim_StrDupLen(hostport, stport - hostport);
216 stport++;
220 #ifdef HAVE_GETADDRINFO
221 struct addrinfo req;
222 struct addrinfo *ai;
223 memset(&req, '\0', sizeof(req));
224 req.ai_family = PF_INET;
226 if (getaddrinfo(sthost, NULL, &req, &ai)) {
227 ret = JIM_ERR;
229 else {
230 memcpy(&sa->sin, ai->ai_addr, ai->ai_addrlen);
231 *salen = ai->ai_addrlen;
232 freeaddrinfo(ai);
234 #else
235 struct hostent *he;
237 ret = JIM_ERR;
239 if ((he = gethostbyname(sthost)) != NULL) {
240 if (he->h_length == sizeof(sa->sin.sin_addr)) {
241 *salen = sizeof(sa->sin);
242 sa->sin.sin_family= he->h_addrtype;
243 memcpy(&sa->sin.sin_addr, he->h_addr, he->h_length); /* set address */
244 ret = JIM_OK;
247 #endif
249 sa->sin.sin_port = htons(atoi(stport));
251 Jim_Free(sthost);
253 if (ret != JIM_OK) {
254 Jim_SetResultFormatted(interp, "Not a valid address: %s", hostport);
257 return ret;
260 #ifdef HAVE_SYS_UN_H
261 static int JimParseDomainAddress(Jim_Interp *interp, const char *path, struct sockaddr_un *sa)
263 sa->sun_family = PF_UNIX;
264 snprintf(sa->sun_path, sizeof(sa->sun_path), "%s", path);
266 return JIM_OK;
268 #endif
269 #endif /* JIM_BOOTSTRAP */
271 static void JimAioSetError(Jim_Interp *interp, Jim_Obj *name)
273 if (name) {
274 Jim_SetResultFormatted(interp, "%#s: %s", name, strerror(errno));
276 else {
277 Jim_SetResultString(interp, strerror(errno), -1);
281 static void JimAioDelProc(Jim_Interp *interp, void *privData)
283 AioFile *af = privData;
285 JIM_NOTUSED(interp);
287 if (!(af->OpenFlags & AIO_KEEPOPEN)) {
288 fclose(af->fp);
291 Jim_DecrRefCount(interp, af->filename);
293 #ifdef jim_ext_eventloop
294 /* remove all existing EventHandlers */
295 Jim_DeleteFileHandler(interp, af->fp, JIM_EVENT_READABLE | JIM_EVENT_WRITABLE | JIM_EVENT_EXCEPTION);
296 #endif
297 Jim_Free(af);
300 static int JimCheckStreamError(Jim_Interp *interp, AioFile *af)
302 if (!ferror(af->fp)) {
303 return JIM_OK;
305 clearerr(af->fp);
306 /* EAGAIN and similar are not error conditions. Just treat them like eof */
307 if (feof(af->fp) || errno == EAGAIN || errno == EINTR) {
308 return JIM_OK;
310 #ifdef ECONNRESET
311 if (errno == ECONNRESET) {
312 return JIM_OK;
314 #endif
315 #ifdef ECONNABORTED
316 if (errno != ECONNABORTED) {
317 return JIM_OK;
319 #endif
320 JimAioSetError(interp, af->filename);
321 return JIM_ERR;
324 static int aio_cmd_read(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
326 AioFile *af = Jim_CmdPrivData(interp);
327 char buf[AIO_BUF_LEN];
328 Jim_Obj *objPtr;
329 int nonewline = 0;
330 jim_wide neededLen = -1; /* -1 is "read as much as possible" */
332 if (argc && Jim_CompareStringImmediate(interp, argv[0], "-nonewline")) {
333 nonewline = 1;
334 argv++;
335 argc--;
337 if (argc == 1) {
338 if (Jim_GetWide(interp, argv[0], &neededLen) != JIM_OK)
339 return JIM_ERR;
340 if (neededLen < 0) {
341 Jim_SetResultString(interp, "invalid parameter: negative len", -1);
342 return JIM_ERR;
345 else if (argc) {
346 return -1;
348 objPtr = Jim_NewStringObj(interp, NULL, 0);
349 while (neededLen != 0) {
350 int retval;
351 int readlen;
353 if (neededLen == -1) {
354 readlen = AIO_BUF_LEN;
356 else {
357 readlen = (neededLen > AIO_BUF_LEN ? AIO_BUF_LEN : neededLen);
359 retval = fread(buf, 1, readlen, af->fp);
360 if (retval > 0) {
361 Jim_AppendString(interp, objPtr, buf, retval);
362 if (neededLen != -1) {
363 neededLen -= retval;
366 if (retval != readlen)
367 break;
369 /* Check for error conditions */
370 if (JimCheckStreamError(interp, af)) {
371 Jim_FreeNewObj(interp, objPtr);
372 return JIM_ERR;
374 if (nonewline) {
375 int len;
376 const char *s = Jim_GetString(objPtr, &len);
378 if (len > 0 && s[len - 1] == '\n') {
379 objPtr->length--;
380 objPtr->bytes[objPtr->length] = '\0';
383 Jim_SetResult(interp, objPtr);
384 return JIM_OK;
387 static int aio_cmd_copy(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
389 AioFile *af = Jim_CmdPrivData(interp);
390 jim_wide count = 0;
391 jim_wide maxlen = JIM_WIDE_MAX;
392 FILE *outfh = Jim_AioFilehandle(interp, argv[0]);
394 if (outfh == NULL) {
395 return JIM_ERR;
398 if (argc == 2) {
399 if (Jim_GetWide(interp, argv[1], &maxlen) != JIM_OK) {
400 return JIM_ERR;
404 while (count < maxlen) {
405 int ch = fgetc(af->fp);
407 if (ch == EOF || fputc(ch, outfh) == EOF) {
408 break;
410 count++;
413 if (ferror(af->fp)) {
414 Jim_SetResultFormatted(interp, "error while reading: %s", strerror(errno));
415 clearerr(af->fp);
416 return JIM_ERR;
419 if (ferror(outfh)) {
420 Jim_SetResultFormatted(interp, "error while writing: %s", strerror(errno));
421 clearerr(outfh);
422 return JIM_ERR;
425 Jim_SetResultInt(interp, count);
427 return JIM_OK;
430 static int aio_cmd_gets(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
432 AioFile *af = Jim_CmdPrivData(interp);
433 char buf[AIO_BUF_LEN];
434 Jim_Obj *objPtr;
435 int len;
437 errno = 0;
439 objPtr = Jim_NewStringObj(interp, NULL, 0);
440 while (1) {
441 buf[AIO_BUF_LEN - 1] = '_';
442 if (fgets(buf, AIO_BUF_LEN, af->fp) == NULL)
443 break;
445 if (buf[AIO_BUF_LEN - 1] == '\0' && buf[AIO_BUF_LEN - 2] != '\n') {
446 Jim_AppendString(interp, objPtr, buf, AIO_BUF_LEN - 1);
448 else {
449 len = strlen(buf);
451 if (len && (buf[len - 1] == '\n')) {
452 /* strip "\n" */
453 len--;
456 Jim_AppendString(interp, objPtr, buf, len);
457 break;
460 if (JimCheckStreamError(interp, af)) {
461 /* I/O error */
462 Jim_FreeNewObj(interp, objPtr);
463 return JIM_ERR;
466 if (argc) {
467 if (Jim_SetVariable(interp, argv[0], objPtr) != JIM_OK) {
468 Jim_FreeNewObj(interp, objPtr);
469 return JIM_ERR;
472 len = Jim_Length(objPtr);
474 if (len == 0 && feof(af->fp)) {
475 /* On EOF returns -1 if varName was specified */
476 len = -1;
478 Jim_SetResultInt(interp, len);
480 else {
481 Jim_SetResult(interp, objPtr);
483 return JIM_OK;
486 static int aio_cmd_puts(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
488 AioFile *af = Jim_CmdPrivData(interp);
489 int wlen;
490 const char *wdata;
491 Jim_Obj *strObj;
493 if (argc == 2) {
494 if (!Jim_CompareStringImmediate(interp, argv[0], "-nonewline")) {
495 return -1;
497 strObj = argv[1];
499 else {
500 strObj = argv[0];
503 wdata = Jim_GetString(strObj, &wlen);
504 if (fwrite(wdata, 1, wlen, af->fp) == (unsigned)wlen) {
505 if (argc == 2 || putc('\n', af->fp) != EOF) {
506 return JIM_OK;
509 JimAioSetError(interp, af->filename);
510 return JIM_ERR;
513 static int aio_cmd_isatty(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
515 #ifdef HAVE_ISATTY
516 AioFile *af = Jim_CmdPrivData(interp);
517 Jim_SetResultInt(interp, isatty(fileno(af->fp)));
518 #else
519 Jim_SetResultInt(interp, 0);
520 #endif
522 return JIM_OK;
525 #if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)
526 static int aio_cmd_recvfrom(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
528 AioFile *af = Jim_CmdPrivData(interp);
529 char *buf;
530 union sockaddr_any sa;
531 long len;
532 socklen_t salen = sizeof(sa);
533 int rlen;
535 if (Jim_GetLong(interp, argv[0], &len) != JIM_OK) {
536 return JIM_ERR;
539 buf = Jim_Alloc(len + 1);
541 rlen = recvfrom(fileno(af->fp), buf, len, 0, &sa.sa, &salen);
542 if (rlen < 0) {
543 Jim_Free(buf);
544 JimAioSetError(interp, NULL);
545 return JIM_ERR;
547 buf[rlen] = 0;
548 Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, buf, rlen));
550 if (argc > 1) {
551 /* INET6_ADDRSTRLEN is 46. Add some for [] and port */
552 char addrbuf[60];
554 #if IPV6
555 if (sa.sa.sa_family == PF_INET6) {
556 addrbuf[0] = '[';
557 /* Allow 9 for []:65535\0 */
558 inet_ntop(sa.sa.sa_family, &sa.sin6.sin6_addr, addrbuf + 1, sizeof(addrbuf) - 9);
559 snprintf(addrbuf + strlen(addrbuf), 8, "]:%d", ntohs(sa.sin.sin_port));
561 else
562 #endif
563 if (sa.sa.sa_family == PF_INET) {
564 /* Allow 7 for :65535\0 */
565 inet_ntop(sa.sa.sa_family, &sa.sin.sin_addr, addrbuf, sizeof(addrbuf) - 7);
566 snprintf(addrbuf + strlen(addrbuf), 7, ":%d", ntohs(sa.sin.sin_port));
568 else {
569 /* recvfrom still works on unix domain sockets, etc */
570 addrbuf[0] = 0;
573 if (Jim_SetVariable(interp, argv[1], Jim_NewStringObj(interp, addrbuf, -1)) != JIM_OK) {
574 return JIM_ERR;
578 return JIM_OK;
582 static int aio_cmd_sendto(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
584 AioFile *af = Jim_CmdPrivData(interp);
585 int wlen;
586 int len;
587 const char *wdata;
588 union sockaddr_any sa;
589 const char *addr = Jim_String(argv[1]);
590 int salen;
592 if (IPV6 && af->addr_family == PF_INET6) {
593 if (JimParseIPv6Address(interp, addr, &sa, &salen) != JIM_OK) {
594 return JIM_ERR;
597 else if (JimParseIpAddress(interp, addr, &sa, &salen) != JIM_OK) {
598 return JIM_ERR;
600 wdata = Jim_GetString(argv[0], &wlen);
602 /* Note that we don't validate the socket type. Rely on sendto() failing if appropriate */
603 len = sendto(fileno(af->fp), wdata, wlen, 0, &sa.sa, salen);
604 if (len < 0) {
605 JimAioSetError(interp, NULL);
606 return JIM_ERR;
608 Jim_SetResultInt(interp, len);
609 return JIM_OK;
612 static int aio_cmd_accept(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
614 AioFile *af = Jim_CmdPrivData(interp);
615 int sock;
616 union sockaddr_any sa;
617 socklen_t addrlen = sizeof(sa);
619 sock = accept(af->fd, &sa.sa, &addrlen);
620 if (sock < 0) {
621 JimAioSetError(interp, NULL);
622 return JIM_ERR;
625 /* Create the file command */
626 return JimMakeChannel(interp, NULL, sock, Jim_NewStringObj(interp, "accept", -1),
627 "aio.sockstream%ld", af->addr_family, "r+");
630 static int aio_cmd_listen(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
632 AioFile *af = Jim_CmdPrivData(interp);
633 long backlog;
635 if (Jim_GetLong(interp, argv[0], &backlog) != JIM_OK) {
636 return JIM_ERR;
639 if (listen(af->fd, backlog)) {
640 JimAioSetError(interp, NULL);
641 return JIM_ERR;
644 return JIM_OK;
646 #endif /* JIM_BOOTSTRAP */
648 static int aio_cmd_flush(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
650 AioFile *af = Jim_CmdPrivData(interp);
652 if (fflush(af->fp) == EOF) {
653 JimAioSetError(interp, af->filename);
654 return JIM_ERR;
656 return JIM_OK;
659 static int aio_cmd_eof(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
661 AioFile *af = Jim_CmdPrivData(interp);
663 Jim_SetResultInt(interp, feof(af->fp));
664 return JIM_OK;
667 static int aio_cmd_close(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
669 if (argc == 3) {
670 #if !defined(JIM_ANSIC) && defined(HAVE_SHUTDOWN)
671 static const char * const options[] = { "r", "w", NULL };
672 enum { OPT_R, OPT_W, };
673 int option;
674 AioFile *af = Jim_CmdPrivData(interp);
676 if (Jim_GetEnum(interp, argv[2], options, &option, NULL, JIM_ERRMSG) != JIM_OK) {
677 return JIM_ERR;
679 if (shutdown(af->fd, option == OPT_R ? SHUT_RD : SHUT_WR) == 0) {
680 return JIM_OK;
682 JimAioSetError(interp, NULL);
683 #else
684 Jim_SetResultString(interp, "async close not supported", -1);
685 #endif
686 return JIM_ERR;
689 return Jim_DeleteCommand(interp, Jim_String(argv[0]));
692 static int aio_cmd_seek(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
694 AioFile *af = Jim_CmdPrivData(interp);
695 int orig = SEEK_SET;
696 jim_wide offset;
698 if (argc == 2) {
699 if (Jim_CompareStringImmediate(interp, argv[1], "start"))
700 orig = SEEK_SET;
701 else if (Jim_CompareStringImmediate(interp, argv[1], "current"))
702 orig = SEEK_CUR;
703 else if (Jim_CompareStringImmediate(interp, argv[1], "end"))
704 orig = SEEK_END;
705 else {
706 return -1;
709 if (Jim_GetWide(interp, argv[0], &offset) != JIM_OK) {
710 return JIM_ERR;
712 if (fseeko(af->fp, offset, orig) == -1) {
713 JimAioSetError(interp, af->filename);
714 return JIM_ERR;
716 return JIM_OK;
719 static int aio_cmd_tell(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
721 AioFile *af = Jim_CmdPrivData(interp);
723 Jim_SetResultInt(interp, ftello(af->fp));
724 return JIM_OK;
727 static int aio_cmd_filename(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
729 AioFile *af = Jim_CmdPrivData(interp);
731 Jim_SetResult(interp, af->filename);
732 return JIM_OK;
735 #ifdef O_NDELAY
736 static int aio_cmd_ndelay(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
738 AioFile *af = Jim_CmdPrivData(interp);
740 int fmode = fcntl(af->fd, F_GETFL);
742 if (argc) {
743 long nb;
745 if (Jim_GetLong(interp, argv[0], &nb) != JIM_OK) {
746 return JIM_ERR;
748 if (nb) {
749 fmode |= O_NDELAY;
751 else {
752 fmode &= ~O_NDELAY;
754 fcntl(af->fd, F_SETFL, fmode);
756 Jim_SetResultInt(interp, (fmode & O_NONBLOCK) ? 1 : 0);
757 return JIM_OK;
759 #endif
761 static int aio_cmd_buffering(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
763 AioFile *af = Jim_CmdPrivData(interp);
765 static const char * const options[] = {
766 "none",
767 "line",
768 "full",
769 NULL
771 enum
773 OPT_NONE,
774 OPT_LINE,
775 OPT_FULL,
777 int option;
779 if (Jim_GetEnum(interp, argv[0], options, &option, NULL, JIM_ERRMSG) != JIM_OK) {
780 return JIM_ERR;
782 switch (option) {
783 case OPT_NONE:
784 setvbuf(af->fp, NULL, _IONBF, 0);
785 break;
786 case OPT_LINE:
787 setvbuf(af->fp, NULL, _IOLBF, BUFSIZ);
788 break;
789 case OPT_FULL:
790 setvbuf(af->fp, NULL, _IOFBF, BUFSIZ);
791 break;
793 return JIM_OK;
796 #ifdef jim_ext_eventloop
797 static void JimAioFileEventFinalizer(Jim_Interp *interp, void *clientData)
799 Jim_Obj **objPtrPtr = clientData;
801 Jim_DecrRefCount(interp, *objPtrPtr);
802 *objPtrPtr = NULL;
805 static int JimAioFileEventHandler(Jim_Interp *interp, void *clientData, int mask)
807 Jim_Obj **objPtrPtr = clientData;
809 return Jim_EvalObjBackground(interp, *objPtrPtr);
812 static int aio_eventinfo(Jim_Interp *interp, AioFile * af, unsigned mask, Jim_Obj **scriptHandlerObj,
813 int argc, Jim_Obj * const *argv)
815 if (argc == 0) {
816 /* Return current script */
817 if (*scriptHandlerObj) {
818 Jim_SetResult(interp, *scriptHandlerObj);
820 return JIM_OK;
823 if (*scriptHandlerObj) {
824 /* Delete old handler */
825 Jim_DeleteFileHandler(interp, af->fp, mask);
828 /* Now possibly add the new script(s) */
829 if (Jim_Length(argv[0]) == 0) {
830 /* Empty script, so done */
831 return JIM_OK;
834 /* A new script to add */
835 Jim_IncrRefCount(argv[0]);
836 *scriptHandlerObj = argv[0];
838 Jim_CreateFileHandler(interp, af->fp, mask,
839 JimAioFileEventHandler, scriptHandlerObj, JimAioFileEventFinalizer);
841 return JIM_OK;
844 static int aio_cmd_readable(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
846 AioFile *af = Jim_CmdPrivData(interp);
848 return aio_eventinfo(interp, af, JIM_EVENT_READABLE, &af->rEvent, argc, argv);
851 static int aio_cmd_writable(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
853 AioFile *af = Jim_CmdPrivData(interp);
855 return aio_eventinfo(interp, af, JIM_EVENT_WRITABLE, &af->wEvent, argc, argv);
858 static int aio_cmd_onexception(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
860 AioFile *af = Jim_CmdPrivData(interp);
862 return aio_eventinfo(interp, af, JIM_EVENT_EXCEPTION, &af->wEvent, argc, argv);
864 #endif
866 static const jim_subcmd_type aio_command_table[] = {
867 { "read",
868 "?-nonewline? ?len?",
869 aio_cmd_read,
872 /* Description: Read and return bytes from the stream. To eof if no len. */
874 { "copyto",
875 "handle ?size?",
876 aio_cmd_copy,
879 /* Description: Copy up to 'size' bytes to the given filehandle, or to eof if no size. */
881 { "gets",
882 "?var?",
883 aio_cmd_gets,
886 /* Description: Read one line and return it or store it in the var */
888 { "puts",
889 "?-nonewline? str",
890 aio_cmd_puts,
893 /* Description: Write the string, with newline unless -nonewline */
895 { "isatty",
896 NULL,
897 aio_cmd_isatty,
900 /* Description: Is the file descriptor a tty? */
902 #if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)
903 { "recvfrom",
904 "len ?addrvar?",
905 aio_cmd_recvfrom,
908 /* Description: Receive up to 'len' bytes on the socket. Sets 'addrvar' with receive address, if set */
910 { "sendto",
911 "str address",
912 aio_cmd_sendto,
915 /* Description: Send 'str' to the given address (dgram only) */
917 { "accept",
918 NULL,
919 aio_cmd_accept,
922 /* Description: Server socket only: Accept a connection and return stream */
924 { "listen",
925 "backlog",
926 aio_cmd_listen,
929 /* Description: Set the listen backlog for server socket */
931 #endif /* JIM_BOOTSTRAP */
932 { "flush",
933 NULL,
934 aio_cmd_flush,
937 /* Description: Flush the stream */
939 { "eof",
940 NULL,
941 aio_cmd_eof,
944 /* Description: Returns 1 if stream is at eof */
946 { "close",
947 "?r(ead)|w(rite)?",
948 aio_cmd_close,
951 JIM_MODFLAG_FULLARGV,
952 /* Description: Closes the stream. */
954 { "seek",
955 "offset ?start|current|end",
956 aio_cmd_seek,
959 /* Description: Seeks in the stream (default 'current') */
961 { "tell",
962 NULL,
963 aio_cmd_tell,
966 /* Description: Returns the current seek position */
968 { "filename",
969 NULL,
970 aio_cmd_filename,
973 /* Description: Returns the original filename */
975 #ifdef O_NDELAY
976 { "ndelay",
977 "?0|1?",
978 aio_cmd_ndelay,
981 /* Description: Set O_NDELAY (if arg). Returns current/new setting. */
983 #endif
984 { "buffering",
985 "none|line|full",
986 aio_cmd_buffering,
989 /* Description: Sets buffering */
991 #ifdef jim_ext_eventloop
992 { "readable",
993 "?readable-script?",
994 aio_cmd_readable,
997 /* Description: Returns script, or invoke readable-script when readable, {} to remove */
999 { "writable",
1000 "?writable-script?",
1001 aio_cmd_writable,
1004 /* Description: Returns script, or invoke writable-script when writable, {} to remove */
1006 { "onexception",
1007 "?exception-script?",
1008 aio_cmd_onexception,
1011 /* Description: Returns script, or invoke exception-script when oob data, {} to remove */
1013 #endif
1014 { NULL }
1017 static int JimAioSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1019 return Jim_CallSubCmd(interp, Jim_ParseSubCmd(interp, aio_command_table, argc, argv), argc, argv);
1022 static int JimAioOpenCommand(Jim_Interp *interp, int argc,
1023 Jim_Obj *const *argv)
1025 const char *mode;
1026 const char *filename;
1028 if (argc != 2 && argc != 3) {
1029 Jim_WrongNumArgs(interp, 1, argv, "filename ?mode?");
1030 return JIM_ERR;
1033 mode = (argc == 3) ? Jim_String(argv[2]) : "r";
1034 filename = Jim_String(argv[1]);
1036 #ifdef jim_ext_tclcompat
1037 /* If the filename starts with '|', use popen instead */
1038 if (*filename == '|') {
1039 Jim_Obj *evalObj[3];
1041 evalObj[0] = Jim_NewStringObj(interp, "::popen", -1);
1042 evalObj[1] = Jim_NewStringObj(interp, filename + 1, -1);
1043 evalObj[2] = Jim_NewStringObj(interp, mode, -1);
1045 return Jim_EvalObjVector(interp, 3, evalObj);
1047 #endif
1048 return JimMakeChannel(interp, NULL, -1, argv[1], "aio.handle%ld", 0, mode);
1052 * Creates a channel for fh/fd/filename.
1054 * If fh is not NULL, uses that as the channel (and set AIO_KEEPOPEN).
1055 * Otherwise, if fd is >= 0, uses that as the chanel.
1056 * Otherwise opens 'filename' with mode 'mode'.
1058 * hdlfmt is a sprintf format for the filehandle. Anything with %ld at the end will do.
1059 * mode is used for open or fdopen.
1061 * Creates the command and sets the name as the current result.
1063 static int JimMakeChannel(Jim_Interp *interp, FILE *fh, int fd, Jim_Obj *filename,
1064 const char *hdlfmt, int family, const char *mode)
1066 AioFile *af;
1067 char buf[AIO_CMD_LEN];
1068 int OpenFlags = 0;
1070 if (fh) {
1071 filename = Jim_NewStringObj(interp, hdlfmt, -1);
1072 OpenFlags = AIO_KEEPOPEN;
1075 Jim_IncrRefCount(filename);
1077 if (fh == NULL) {
1078 #if !defined(JIM_ANSIC)
1079 if (fd >= 0) {
1080 fh = fdopen(fd, mode);
1082 else
1083 #endif
1084 fh = fopen(Jim_String(filename), mode);
1086 if (fh == NULL) {
1087 JimAioSetError(interp, filename);
1088 #if !defined(JIM_ANSIC)
1089 if (fd >= 0) {
1090 close(fd);
1092 #endif
1093 Jim_DecrRefCount(interp, filename);
1094 return JIM_ERR;
1098 /* Create the file command */
1099 af = Jim_Alloc(sizeof(*af));
1100 memset(af, 0, sizeof(*af));
1101 af->fp = fh;
1102 af->fd = fileno(fh);
1103 af->filename = filename;
1104 #ifdef FD_CLOEXEC
1105 if ((OpenFlags & AIO_KEEPOPEN) == 0) {
1106 fcntl(af->fd, F_SETFD, FD_CLOEXEC);
1108 #endif
1109 af->OpenFlags = OpenFlags;
1110 af->addr_family = family;
1111 snprintf(buf, sizeof(buf), hdlfmt, Jim_GetId(interp));
1112 Jim_CreateCommand(interp, buf, JimAioSubCmdProc, af, JimAioDelProc);
1114 /* Note that the command must use the global namespace, even if
1115 * the current namespace is something different
1117 Jim_SetResult(interp, Jim_MakeGlobalNamespaceName(interp, Jim_NewStringObj(interp, buf, -1)));
1119 return JIM_OK;
1122 static int JimMakeChannelPair(Jim_Interp *interp, int p[2], Jim_Obj *filename,
1123 const char *hdlfmt, int family, const char *mode[2])
1125 if (JimMakeChannel(interp, NULL, p[0], filename, hdlfmt, family, mode[0]) == JIM_OK) {
1126 Jim_Obj *objPtr = Jim_NewListObj(interp, NULL, 0);
1127 Jim_ListAppendElement(interp, objPtr, Jim_GetResult(interp));
1129 if (JimMakeChannel(interp, NULL, p[1], filename, hdlfmt, family, mode[1]) == JIM_OK) {
1130 Jim_ListAppendElement(interp, objPtr, Jim_GetResult(interp));
1131 Jim_SetResult(interp, objPtr);
1132 return JIM_OK;
1136 /* Can only be here if fdopen() failed */
1137 close(p[0]);
1138 close(p[1]);
1139 JimAioSetError(interp, NULL);
1140 return JIM_ERR;
1143 #if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)
1145 static int JimAioSockCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1147 const char *hdlfmt = "aio.unknown%ld";
1148 const char *socktypes[] = {
1149 "unix",
1150 "unix.server",
1151 "dgram",
1152 "dgram.server",
1153 "stream",
1154 "stream.server",
1155 "pipe",
1156 "pair",
1157 NULL
1159 enum
1161 SOCK_UNIX,
1162 SOCK_UNIX_SERVER,
1163 SOCK_DGRAM_CLIENT,
1164 SOCK_DGRAM_SERVER,
1165 SOCK_STREAM_CLIENT,
1166 SOCK_STREAM_SERVER,
1167 SOCK_STREAM_PIPE,
1168 SOCK_STREAM_SOCKETPAIR,
1170 int socktype;
1171 int sock;
1172 const char *hostportarg = NULL;
1173 int res;
1174 int on = 1;
1175 const char *mode = "r+";
1176 int family = PF_INET;
1177 Jim_Obj *argv0 = argv[0];
1178 int ipv6 = 0;
1180 if (argc > 1 && Jim_CompareStringImmediate(interp, argv[1], "-ipv6")) {
1181 if (!IPV6) {
1182 Jim_SetResultString(interp, "ipv6 not supported", -1);
1183 return JIM_ERR;
1185 ipv6 = 1;
1186 family = PF_INET6;
1188 argc -= ipv6;
1189 argv += ipv6;
1191 if (argc < 2) {
1192 wrongargs:
1193 Jim_WrongNumArgs(interp, 1, &argv0, "?-ipv6? type ?address?");
1194 return JIM_ERR;
1197 if (Jim_GetEnum(interp, argv[1], socktypes, &socktype, "socket type", JIM_ERRMSG) != JIM_OK)
1198 return JIM_ERR;
1200 Jim_SetEmptyResult(interp);
1202 hdlfmt = "aio.sock%ld";
1204 if (argc > 2) {
1205 hostportarg = Jim_String(argv[2]);
1208 switch (socktype) {
1209 case SOCK_DGRAM_CLIENT:
1210 if (argc == 2) {
1211 /* No address, so an unconnected dgram socket */
1212 sock = socket(family, SOCK_DGRAM, 0);
1213 if (sock < 0) {
1214 JimAioSetError(interp, NULL);
1215 return JIM_ERR;
1217 break;
1219 /* fall through */
1220 case SOCK_STREAM_CLIENT:
1222 union sockaddr_any sa;
1223 int salen;
1225 if (argc != 3) {
1226 goto wrongargs;
1229 if (ipv6) {
1230 if (JimParseIPv6Address(interp, hostportarg, &sa, &salen) != JIM_OK) {
1231 return JIM_ERR;
1234 else if (JimParseIpAddress(interp, hostportarg, &sa, &salen) != JIM_OK) {
1235 return JIM_ERR;
1237 sock = socket(family, (socktype == SOCK_DGRAM_CLIENT) ? SOCK_DGRAM : SOCK_STREAM, 0);
1238 if (sock < 0) {
1239 JimAioSetError(interp, NULL);
1240 return JIM_ERR;
1242 res = connect(sock, &sa.sa, salen);
1243 if (res) {
1244 JimAioSetError(interp, argv[2]);
1245 close(sock);
1246 return JIM_ERR;
1249 break;
1251 case SOCK_STREAM_SERVER:
1252 case SOCK_DGRAM_SERVER:
1254 union sockaddr_any sa;
1255 int salen;
1257 if (argc != 3) {
1258 goto wrongargs;
1261 if (ipv6) {
1262 if (JimParseIPv6Address(interp, hostportarg, &sa, &salen) != JIM_OK) {
1263 return JIM_ERR;
1266 else if (JimParseIpAddress(interp, hostportarg, &sa, &salen) != JIM_OK) {
1267 return JIM_ERR;
1269 sock = socket(family, (socktype == SOCK_DGRAM_SERVER) ? SOCK_DGRAM : SOCK_STREAM, 0);
1270 if (sock < 0) {
1271 JimAioSetError(interp, NULL);
1272 return JIM_ERR;
1275 /* Enable address reuse */
1276 setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on));
1278 res = bind(sock, &sa.sa, salen);
1279 if (res) {
1280 JimAioSetError(interp, argv[2]);
1281 close(sock);
1282 return JIM_ERR;
1284 if (socktype == SOCK_STREAM_SERVER) {
1285 res = listen(sock, 5);
1286 if (res) {
1287 JimAioSetError(interp, NULL);
1288 close(sock);
1289 return JIM_ERR;
1292 hdlfmt = "aio.socksrv%ld";
1294 break;
1296 #ifdef HAVE_SYS_UN_H
1297 case SOCK_UNIX:
1299 struct sockaddr_un sa;
1300 socklen_t len;
1302 if (argc != 3 || ipv6) {
1303 goto wrongargs;
1306 if (JimParseDomainAddress(interp, hostportarg, &sa) != JIM_OK) {
1307 JimAioSetError(interp, argv[2]);
1308 return JIM_ERR;
1310 family = PF_UNIX;
1311 sock = socket(PF_UNIX, SOCK_STREAM, 0);
1312 if (sock < 0) {
1313 JimAioSetError(interp, NULL);
1314 return JIM_ERR;
1316 len = strlen(sa.sun_path) + 1 + sizeof(sa.sun_family);
1317 res = connect(sock, (struct sockaddr *)&sa, len);
1318 if (res) {
1319 JimAioSetError(interp, argv[2]);
1320 close(sock);
1321 return JIM_ERR;
1323 hdlfmt = "aio.sockunix%ld";
1324 break;
1327 case SOCK_UNIX_SERVER:
1329 struct sockaddr_un sa;
1330 socklen_t len;
1332 if (argc != 3 || ipv6) {
1333 goto wrongargs;
1336 if (JimParseDomainAddress(interp, hostportarg, &sa) != JIM_OK) {
1337 JimAioSetError(interp, argv[2]);
1338 return JIM_ERR;
1340 family = PF_UNIX;
1341 sock = socket(PF_UNIX, SOCK_STREAM, 0);
1342 if (sock < 0) {
1343 JimAioSetError(interp, NULL);
1344 return JIM_ERR;
1346 len = strlen(sa.sun_path) + 1 + sizeof(sa.sun_family);
1347 res = bind(sock, (struct sockaddr *)&sa, len);
1348 if (res) {
1349 JimAioSetError(interp, argv[2]);
1350 close(sock);
1351 return JIM_ERR;
1353 res = listen(sock, 5);
1354 if (res) {
1355 JimAioSetError(interp, NULL);
1356 close(sock);
1357 return JIM_ERR;
1359 hdlfmt = "aio.sockunixsrv%ld";
1360 break;
1362 #endif
1364 #if defined(HAVE_SOCKETPAIR) && defined(HAVE_SYS_UN_H)
1365 case SOCK_STREAM_SOCKETPAIR:
1367 int p[2];
1368 static const char *mode[2] = { "r+", "r+" };
1370 if (argc != 2 || ipv6) {
1371 goto wrongargs;
1374 if (socketpair(PF_UNIX, SOCK_STREAM, 0, p) < 0) {
1375 JimAioSetError(interp, NULL);
1376 return JIM_ERR;
1378 return JimMakeChannelPair(interp, p, argv[1], "aio.sockpair%ld", PF_UNIX, mode);
1380 break;
1381 #endif
1383 #if defined(HAVE_PIPE)
1384 case SOCK_STREAM_PIPE:
1386 int p[2];
1387 static const char *mode[2] = { "r", "w" };
1389 if (argc != 2 || ipv6) {
1390 goto wrongargs;
1393 if (pipe(p) < 0) {
1394 JimAioSetError(interp, NULL);
1395 return JIM_ERR;
1398 return JimMakeChannelPair(interp, p, argv[1], "aio.pipe%ld", 0, mode);
1400 break;
1401 #endif
1403 default:
1404 Jim_SetResultString(interp, "Unsupported socket type", -1);
1405 return JIM_ERR;
1408 return JimMakeChannel(interp, NULL, sock, argv[1], hdlfmt, family, mode);
1410 #endif /* JIM_BOOTSTRAP */
1412 FILE *Jim_AioFilehandle(Jim_Interp *interp, Jim_Obj *command)
1414 Jim_Cmd *cmdPtr = Jim_GetCommand(interp, command, JIM_ERRMSG);
1416 if (cmdPtr && !cmdPtr->isproc && cmdPtr->u.native.cmdProc == JimAioSubCmdProc) {
1417 return ((AioFile *) cmdPtr->u.native.privData)->fp;
1419 Jim_SetResultFormatted(interp, "Not a filehandle: \"%#s\"", command);
1420 return NULL;
1423 int Jim_aioInit(Jim_Interp *interp)
1425 if (Jim_PackageProvide(interp, "aio", "1.0", JIM_ERRMSG))
1426 return JIM_ERR;
1428 Jim_CreateCommand(interp, "open", JimAioOpenCommand, NULL, NULL);
1429 #ifndef JIM_ANSIC
1430 Jim_CreateCommand(interp, "socket", JimAioSockCommand, NULL, NULL);
1431 #endif
1433 /* Create filehandles for stdin, stdout and stderr */
1434 JimMakeChannel(interp, stdin, -1, NULL, "stdin", 0, "r");
1435 JimMakeChannel(interp, stdout, -1, NULL, "stdout", 0, "w");
1436 JimMakeChannel(interp, stderr, -1, NULL, "stderr", 0, "w");
1438 return JIM_OK;