jim.c: preserve source info on interpolation
[jimtcl.git] / jim-aio.c
blob0ae45dab0c95f69ed1af064aef9ee4890acf0840
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 #ifdef HAVE_SYS_UN_H
58 #include <sys/un.h>
59 #endif
60 #else
61 #define JIM_ANSIC
62 #endif
64 #include "jim-eventloop.h"
65 #include "jim-subcmd.h"
67 #define AIO_CMD_LEN 32 /* e.g. aio.handleXXXXXX */
68 #define AIO_BUF_LEN 256 /* Can keep this small and rely on stdio buffering */
70 #ifndef HAVE_FTELLO
71 #define ftello ftell
72 #endif
73 #ifndef HAVE_FSEEKO
74 #define fseeko fseek
75 #endif
77 #define AIO_KEEPOPEN 1
79 #if defined(JIM_IPV6)
80 #define IPV6 1
81 #else
82 #define IPV6 0
83 #ifndef PF_INET6
84 #define PF_INET6 0
85 #endif
86 #endif
88 #if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)
89 union sockaddr_any {
90 struct sockaddr sa;
91 struct sockaddr_in sin;
92 #if IPV6
93 struct sockaddr_in6 sin6;
94 #endif
97 #ifndef HAVE_INET_NTOP
98 const char *inet_ntop(int af, const void *src, char *dst, int size)
100 if (af != PF_INET) {
101 return NULL;
103 snprintf(dst, size, "%s", inet_ntoa(((struct sockaddr_in *)src)->sin_addr));
104 return dst;
106 #endif
107 #endif /* JIM_BOOTSTRAP */
109 typedef struct AioFile
111 FILE *fp;
112 Jim_Obj *filename;
113 int type;
114 int openFlags; /* AIO_KEEPOPEN? keep FILE* */
115 int fd;
116 Jim_Obj *rEvent;
117 Jim_Obj *wEvent;
118 Jim_Obj *eEvent;
119 int addr_family;
120 } AioFile;
122 static int JimAioSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv);
123 static int JimMakeChannel(Jim_Interp *interp, FILE *fh, int fd, Jim_Obj *filename,
124 const char *hdlfmt, int family, const char *mode);
126 #if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)
127 static int JimParseIPv6Address(Jim_Interp *interp, const char *hostport, union sockaddr_any *sa, int *salen)
129 #if IPV6
131 * An IPv6 addr/port looks like:
132 * [::1]
133 * [::1]:2000
134 * [fe80::223:6cff:fe95:bdc0%en1]:2000
135 * [::]:2000
136 * 2000
138 * Note that the "any" address is ::, which is the same as when no address is specified.
140 char *sthost = NULL;
141 const char *stport;
142 int ret = JIM_OK;
143 struct addrinfo req;
144 struct addrinfo *ai;
146 stport = strrchr(hostport, ':');
147 if (!stport) {
148 /* No : so, the whole thing is the port */
149 stport = hostport;
150 hostport = "::";
151 sthost = Jim_StrDup(hostport);
153 else {
154 stport++;
157 if (*hostport == '[') {
158 /* This is a numeric ipv6 address */
159 char *pt = strchr(++hostport, ']');
160 if (pt) {
161 sthost = Jim_StrDupLen(hostport, pt - hostport);
165 if (!sthost) {
166 sthost = Jim_StrDupLen(hostport, stport - hostport - 1);
169 memset(&req, '\0', sizeof(req));
170 req.ai_family = PF_INET6;
172 if (getaddrinfo(sthost, NULL, &req, &ai)) {
173 Jim_SetResultFormatted(interp, "Not a valid address: %s", hostport);
174 ret = JIM_ERR;
176 else {
177 memcpy(&sa->sin, ai->ai_addr, ai->ai_addrlen);
178 *salen = ai->ai_addrlen;
180 sa->sin.sin_port = htons(atoi(stport));
182 freeaddrinfo(ai);
184 Jim_Free(sthost);
186 return ret;
187 #else
188 Jim_SetResultString(interp, "ipv6 not supported", -1);
189 return JIM_ERR;
190 #endif
193 static int JimParseIpAddress(Jim_Interp *interp, const char *hostport, union sockaddr_any *sa, int *salen)
195 /* An IPv4 addr/port looks like:
196 * 192.168.1.5
197 * 192.168.1.5:2000
198 * 2000
200 * If the address is missing, INADDR_ANY is used.
201 * If the port is missing, 0 is used (only useful for server sockets).
203 char *sthost = NULL;
204 const char *stport;
205 int ret = JIM_OK;
207 stport = strrchr(hostport, ':');
208 if (!stport) {
209 /* No : so, the whole thing is the port */
210 stport = hostport;
211 sthost = Jim_StrDup("0.0.0.0");
213 else {
214 sthost = Jim_StrDupLen(hostport, stport - hostport);
215 stport++;
219 #ifdef HAVE_GETADDRINFO
220 struct addrinfo req;
221 struct addrinfo *ai;
222 memset(&req, '\0', sizeof(req));
223 req.ai_family = PF_INET;
225 if (getaddrinfo(sthost, NULL, &req, &ai)) {
226 ret = JIM_ERR;
228 else {
229 memcpy(&sa->sin, ai->ai_addr, ai->ai_addrlen);
230 *salen = ai->ai_addrlen;
231 freeaddrinfo(ai);
233 #else
234 struct hostent *he;
236 ret = JIM_ERR;
238 if ((he = gethostbyname(sthost)) != NULL) {
239 if (he->h_length == sizeof(sa->sin.sin_addr)) {
240 *salen = sizeof(sa->sin);
241 sa->sin.sin_family= he->h_addrtype;
242 memcpy(&sa->sin.sin_addr, he->h_addr, he->h_length); /* set address */
243 ret = JIM_OK;
246 #endif
248 sa->sin.sin_port = htons(atoi(stport));
250 Jim_Free(sthost);
252 if (ret != JIM_OK) {
253 Jim_SetResultFormatted(interp, "Not a valid address: %s", hostport);
256 return ret;
259 #ifdef HAVE_SYS_UN_H
260 static int JimParseDomainAddress(Jim_Interp *interp, const char *path, struct sockaddr_un *sa)
262 sa->sun_family = PF_UNIX;
263 snprintf(sa->sun_path, sizeof(sa->sun_path), "%s", path);
265 return JIM_OK;
267 #endif
268 #endif /* JIM_BOOTSTRAP */
270 static void JimAioSetError(Jim_Interp *interp, Jim_Obj *name)
272 if (name) {
273 Jim_SetResultFormatted(interp, "%#s: %s", name, strerror(errno));
275 else {
276 Jim_SetResultString(interp, strerror(errno), -1);
280 static void JimAioDelProc(Jim_Interp *interp, void *privData)
282 AioFile *af = privData;
284 JIM_NOTUSED(interp);
286 if (!(af->openFlags & AIO_KEEPOPEN)) {
287 fclose(af->fp);
290 Jim_DecrRefCount(interp, af->filename);
292 #ifdef jim_ext_eventloop
293 /* remove all existing EventHandlers */
294 Jim_DeleteFileHandler(interp, af->fp, JIM_EVENT_READABLE | JIM_EVENT_WRITABLE | JIM_EVENT_EXCEPTION);
295 #endif
296 Jim_Free(af);
299 static int JimCheckStreamError(Jim_Interp *interp, AioFile *af)
301 if (!ferror(af->fp)) {
302 return JIM_OK;
304 clearerr(af->fp);
305 /* EAGAIN and similar are not error conditions. Just treat them like eof */
306 if (feof(af->fp) || errno == EAGAIN || errno == EINTR) {
307 return JIM_OK;
309 #ifdef ECONNRESET
310 if (errno == ECONNRESET) {
311 return JIM_OK;
313 #endif
314 #ifdef ECONNABORTED
315 if (errno != ECONNABORTED) {
316 return JIM_OK;
318 #endif
319 JimAioSetError(interp, af->filename);
320 return JIM_ERR;
323 static int aio_cmd_read(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
325 AioFile *af = Jim_CmdPrivData(interp);
326 char buf[AIO_BUF_LEN];
327 Jim_Obj *objPtr;
328 int nonewline = 0;
329 jim_wide neededLen = -1; /* -1 is "read as much as possible" */
331 if (argc && Jim_CompareStringImmediate(interp, argv[0], "-nonewline")) {
332 nonewline = 1;
333 argv++;
334 argc--;
336 if (argc == 1) {
337 if (Jim_GetWide(interp, argv[0], &neededLen) != JIM_OK)
338 return JIM_ERR;
339 if (neededLen < 0) {
340 Jim_SetResultString(interp, "invalid parameter: negative len", -1);
341 return JIM_ERR;
344 else if (argc) {
345 return -1;
347 objPtr = Jim_NewStringObj(interp, NULL, 0);
348 while (neededLen != 0) {
349 int retval;
350 int readlen;
352 if (neededLen == -1) {
353 readlen = AIO_BUF_LEN;
355 else {
356 readlen = (neededLen > AIO_BUF_LEN ? AIO_BUF_LEN : neededLen);
358 retval = fread(buf, 1, readlen, af->fp);
359 if (retval > 0) {
360 Jim_AppendString(interp, objPtr, buf, retval);
361 if (neededLen != -1) {
362 neededLen -= retval;
365 if (retval != readlen)
366 break;
368 /* Check for error conditions */
369 if (JimCheckStreamError(interp, af)) {
370 Jim_FreeNewObj(interp, objPtr);
371 return JIM_ERR;
373 if (nonewline) {
374 int len;
375 const char *s = Jim_GetString(objPtr, &len);
377 if (len > 0 && s[len - 1] == '\n') {
378 objPtr->length--;
379 objPtr->bytes[objPtr->length] = '\0';
382 Jim_SetResult(interp, objPtr);
383 return JIM_OK;
386 static int aio_cmd_copy(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
388 AioFile *af = Jim_CmdPrivData(interp);
389 jim_wide count = 0;
390 jim_wide maxlen = JIM_WIDE_MAX;
391 FILE *outfh = Jim_AioFilehandle(interp, argv[0]);
393 if (outfh == NULL) {
394 return JIM_ERR;
397 if (argc == 2) {
398 if (Jim_GetWide(interp, argv[1], &maxlen) != JIM_OK) {
399 return JIM_ERR;
403 while (count < maxlen) {
404 int ch = fgetc(af->fp);
406 if (ch == EOF || fputc(ch, outfh) == EOF) {
407 break;
409 count++;
412 if (ferror(af->fp)) {
413 Jim_SetResultFormatted(interp, "error while reading: %s", strerror(errno));
414 clearerr(af->fp);
415 return JIM_ERR;
418 if (ferror(outfh)) {
419 Jim_SetResultFormatted(interp, "error while writing: %s", strerror(errno));
420 clearerr(outfh);
421 return JIM_ERR;
424 Jim_SetResultInt(interp, count);
426 return JIM_OK;
429 static int aio_cmd_gets(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
431 AioFile *af = Jim_CmdPrivData(interp);
432 char buf[AIO_BUF_LEN];
433 Jim_Obj *objPtr;
434 int len;
436 errno = 0;
438 objPtr = Jim_NewStringObj(interp, NULL, 0);
439 while (1) {
440 buf[AIO_BUF_LEN - 1] = '_';
441 if (fgets(buf, AIO_BUF_LEN, af->fp) == NULL)
442 break;
444 if (buf[AIO_BUF_LEN - 1] == '\0' && buf[AIO_BUF_LEN - 2] != '\n') {
445 Jim_AppendString(interp, objPtr, buf, AIO_BUF_LEN - 1);
447 else {
448 len = strlen(buf);
450 if (len && (buf[len - 1] == '\n')) {
451 /* strip "\n" */
452 len--;
455 Jim_AppendString(interp, objPtr, buf, len);
456 break;
459 if (JimCheckStreamError(interp, af)) {
460 /* I/O error */
461 Jim_FreeNewObj(interp, objPtr);
462 return JIM_ERR;
465 if (argc) {
466 if (Jim_SetVariable(interp, argv[0], objPtr) != JIM_OK) {
467 Jim_FreeNewObj(interp, objPtr);
468 return JIM_ERR;
471 len = Jim_Length(objPtr);
473 if (len == 0 && feof(af->fp)) {
474 /* On EOF returns -1 if varName was specified */
475 len = -1;
477 Jim_SetResultInt(interp, len);
479 else {
480 Jim_SetResult(interp, objPtr);
482 return JIM_OK;
485 static int aio_cmd_puts(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
487 AioFile *af = Jim_CmdPrivData(interp);
488 int wlen;
489 const char *wdata;
490 Jim_Obj *strObj;
492 if (argc == 2) {
493 if (!Jim_CompareStringImmediate(interp, argv[0], "-nonewline")) {
494 return -1;
496 strObj = argv[1];
498 else {
499 strObj = argv[0];
502 wdata = Jim_GetString(strObj, &wlen);
503 if (fwrite(wdata, 1, wlen, af->fp) == (unsigned)wlen) {
504 if (argc == 2 || putc('\n', af->fp) != EOF) {
505 return JIM_OK;
508 JimAioSetError(interp, af->filename);
509 return JIM_ERR;
512 static int aio_cmd_isatty(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
514 #ifdef HAVE_ISATTY
515 AioFile *af = Jim_CmdPrivData(interp);
516 Jim_SetResultInt(interp, isatty(fileno(af->fp)));
517 #else
518 Jim_SetResultInt(interp, 0);
519 #endif
521 return JIM_OK;
524 #if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)
525 static int aio_cmd_recvfrom(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
527 AioFile *af = Jim_CmdPrivData(interp);
528 char *buf;
529 union sockaddr_any sa;
530 long len;
531 socklen_t salen = sizeof(sa);
532 int rlen;
534 if (Jim_GetLong(interp, argv[0], &len) != JIM_OK) {
535 return JIM_ERR;
538 buf = Jim_Alloc(len + 1);
540 rlen = recvfrom(fileno(af->fp), buf, len, 0, &sa.sa, &salen);
541 if (rlen < 0) {
542 Jim_Free(buf);
543 JimAioSetError(interp, NULL);
544 return JIM_ERR;
546 buf[rlen] = 0;
547 Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, buf, rlen));
549 if (argc > 1) {
550 /* INET6_ADDRSTRLEN is 46. Add some for [] and port */
551 char addrbuf[60];
553 #if IPV6
554 if (sa.sa.sa_family == PF_INET6) {
555 addrbuf[0] = '[';
556 /* Allow 9 for []:65535\0 */
557 inet_ntop(sa.sa.sa_family, &sa.sin6.sin6_addr, addrbuf + 1, sizeof(addrbuf) - 9);
558 snprintf(addrbuf + strlen(addrbuf), 8, "]:%d", ntohs(sa.sin.sin_port));
560 else
561 #endif
562 if (sa.sa.sa_family == PF_INET) {
563 /* Allow 7 for :65535\0 */
564 inet_ntop(sa.sa.sa_family, &sa.sin.sin_addr, addrbuf, sizeof(addrbuf) - 7);
565 snprintf(addrbuf + strlen(addrbuf), 7, ":%d", ntohs(sa.sin.sin_port));
567 else {
568 /* recvfrom still works on unix domain sockets, etc */
569 addrbuf[0] = 0;
572 if (Jim_SetVariable(interp, argv[1], Jim_NewStringObj(interp, addrbuf, -1)) != JIM_OK) {
573 return JIM_ERR;
577 return JIM_OK;
581 static int aio_cmd_sendto(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
583 AioFile *af = Jim_CmdPrivData(interp);
584 int wlen;
585 int len;
586 const char *wdata;
587 union sockaddr_any sa;
588 const char *addr = Jim_String(argv[1]);
589 int salen;
591 if (IPV6 && af->addr_family == PF_INET6) {
592 if (JimParseIPv6Address(interp, addr, &sa, &salen) != JIM_OK) {
593 return JIM_ERR;
596 else if (JimParseIpAddress(interp, addr, &sa, &salen) != JIM_OK) {
597 return JIM_ERR;
599 wdata = Jim_GetString(argv[0], &wlen);
601 /* Note that we don't validate the socket type. Rely on sendto() failing if appropriate */
602 len = sendto(fileno(af->fp), wdata, wlen, 0, &sa.sa, salen);
603 if (len < 0) {
604 JimAioSetError(interp, NULL);
605 return JIM_ERR;
607 Jim_SetResultInt(interp, len);
608 return JIM_OK;
611 static int aio_cmd_accept(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
613 AioFile *af = Jim_CmdPrivData(interp);
614 int sock;
615 union sockaddr_any sa;
616 socklen_t addrlen = sizeof(sa);
618 sock = accept(af->fd, &sa.sa, &addrlen);
619 if (sock < 0) {
620 JimAioSetError(interp, NULL);
621 return JIM_ERR;
624 /* Create the file command */
625 return JimMakeChannel(interp, NULL, sock, Jim_NewStringObj(interp, "accept", -1),
626 "aio.sockstream%ld", af->addr_family, "r+");
629 static int aio_cmd_listen(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
631 AioFile *af = Jim_CmdPrivData(interp);
632 long backlog;
634 if (Jim_GetLong(interp, argv[0], &backlog) != JIM_OK) {
635 return JIM_ERR;
638 if (listen(af->fd, backlog)) {
639 JimAioSetError(interp, NULL);
640 return JIM_ERR;
643 return JIM_OK;
645 #endif /* JIM_BOOTSTRAP */
647 static int aio_cmd_flush(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
649 AioFile *af = Jim_CmdPrivData(interp);
651 if (fflush(af->fp) == EOF) {
652 JimAioSetError(interp, af->filename);
653 return JIM_ERR;
655 return JIM_OK;
658 static int aio_cmd_eof(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
660 AioFile *af = Jim_CmdPrivData(interp);
662 Jim_SetResultInt(interp, feof(af->fp));
663 return JIM_OK;
666 static int aio_cmd_close(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
668 if (argc == 3) {
669 #if !defined(JIM_ANSIC) && defined(HAVE_SHUTDOWN)
670 static const char * const options[] = { "r", "w", NULL };
671 enum { OPT_R, OPT_W, };
672 int option;
673 AioFile *af = Jim_CmdPrivData(interp);
675 if (Jim_GetEnum(interp, argv[2], options, &option, NULL, JIM_ERRMSG) != JIM_OK) {
676 return JIM_ERR;
678 if (shutdown(af->fd, option == OPT_R ? SHUT_RD : SHUT_WR) == 0) {
679 return JIM_OK;
681 JimAioSetError(interp, NULL);
682 #else
683 Jim_SetResultString(interp, "async close not supported", -1);
684 #endif
685 return JIM_ERR;
688 return Jim_DeleteCommand(interp, Jim_String(argv[0]));
691 static int aio_cmd_seek(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
693 AioFile *af = Jim_CmdPrivData(interp);
694 int orig = SEEK_SET;
695 jim_wide offset;
697 if (argc == 2) {
698 if (Jim_CompareStringImmediate(interp, argv[1], "start"))
699 orig = SEEK_SET;
700 else if (Jim_CompareStringImmediate(interp, argv[1], "current"))
701 orig = SEEK_CUR;
702 else if (Jim_CompareStringImmediate(interp, argv[1], "end"))
703 orig = SEEK_END;
704 else {
705 return -1;
708 if (Jim_GetWide(interp, argv[0], &offset) != JIM_OK) {
709 return JIM_ERR;
711 if (fseeko(af->fp, offset, orig) == -1) {
712 JimAioSetError(interp, af->filename);
713 return JIM_ERR;
715 return JIM_OK;
718 static int aio_cmd_tell(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
720 AioFile *af = Jim_CmdPrivData(interp);
722 Jim_SetResultInt(interp, ftello(af->fp));
723 return JIM_OK;
726 static int aio_cmd_filename(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
728 AioFile *af = Jim_CmdPrivData(interp);
730 Jim_SetResult(interp, af->filename);
731 return JIM_OK;
734 #ifdef O_NDELAY
735 static int aio_cmd_ndelay(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
737 AioFile *af = Jim_CmdPrivData(interp);
739 int fmode = fcntl(af->fd, F_GETFL);
741 if (argc) {
742 long nb;
744 if (Jim_GetLong(interp, argv[0], &nb) != JIM_OK) {
745 return JIM_ERR;
747 if (nb) {
748 fmode |= O_NDELAY;
750 else {
751 fmode &= ~O_NDELAY;
753 fcntl(af->fd, F_SETFL, fmode);
755 Jim_SetResultInt(interp, (fmode & O_NONBLOCK) ? 1 : 0);
756 return JIM_OK;
758 #endif
760 static int aio_cmd_buffering(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
762 AioFile *af = Jim_CmdPrivData(interp);
764 static const char * const options[] = {
765 "none",
766 "line",
767 "full",
768 NULL
770 enum
772 OPT_NONE,
773 OPT_LINE,
774 OPT_FULL,
776 int option;
778 if (Jim_GetEnum(interp, argv[0], options, &option, NULL, JIM_ERRMSG) != JIM_OK) {
779 return JIM_ERR;
781 switch (option) {
782 case OPT_NONE:
783 setvbuf(af->fp, NULL, _IONBF, 0);
784 break;
785 case OPT_LINE:
786 setvbuf(af->fp, NULL, _IOLBF, BUFSIZ);
787 break;
788 case OPT_FULL:
789 setvbuf(af->fp, NULL, _IOFBF, BUFSIZ);
790 break;
792 return JIM_OK;
795 #ifdef jim_ext_eventloop
796 static void JimAioFileEventFinalizer(Jim_Interp *interp, void *clientData)
798 Jim_Obj **objPtrPtr = clientData;
800 Jim_DecrRefCount(interp, *objPtrPtr);
801 *objPtrPtr = NULL;
804 static int JimAioFileEventHandler(Jim_Interp *interp, void *clientData, int mask)
806 Jim_Obj **objPtrPtr = clientData;
808 return Jim_EvalObjBackground(interp, *objPtrPtr);
811 static int aio_eventinfo(Jim_Interp *interp, AioFile * af, unsigned mask, Jim_Obj **scriptHandlerObj,
812 int argc, Jim_Obj * const *argv)
814 if (argc == 0) {
815 /* Return current script */
816 if (*scriptHandlerObj) {
817 Jim_SetResult(interp, *scriptHandlerObj);
819 return JIM_OK;
822 if (*scriptHandlerObj) {
823 /* Delete old handler */
824 Jim_DeleteFileHandler(interp, af->fp, mask);
827 /* Now possibly add the new script(s) */
828 if (Jim_Length(argv[0]) == 0) {
829 /* Empty script, so done */
830 return JIM_OK;
833 /* A new script to add */
834 Jim_IncrRefCount(argv[0]);
835 *scriptHandlerObj = argv[0];
837 Jim_CreateFileHandler(interp, af->fp, mask,
838 JimAioFileEventHandler, scriptHandlerObj, JimAioFileEventFinalizer);
840 return JIM_OK;
843 static int aio_cmd_readable(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
845 AioFile *af = Jim_CmdPrivData(interp);
847 return aio_eventinfo(interp, af, JIM_EVENT_READABLE, &af->rEvent, argc, argv);
850 static int aio_cmd_writable(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
852 AioFile *af = Jim_CmdPrivData(interp);
854 return aio_eventinfo(interp, af, JIM_EVENT_WRITABLE, &af->wEvent, argc, argv);
857 static int aio_cmd_onexception(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
859 AioFile *af = Jim_CmdPrivData(interp);
861 return aio_eventinfo(interp, af, JIM_EVENT_EXCEPTION, &af->wEvent, argc, argv);
863 #endif
865 static const jim_subcmd_type aio_command_table[] = {
866 { "read",
867 "?-nonewline? ?len?",
868 aio_cmd_read,
871 /* Description: Read and return bytes from the stream. To eof if no len. */
873 { "copyto",
874 "handle ?size?",
875 aio_cmd_copy,
878 /* Description: Copy up to 'size' bytes to the given filehandle, or to eof if no size. */
880 { "gets",
881 "?var?",
882 aio_cmd_gets,
885 /* Description: Read one line and return it or store it in the var */
887 { "puts",
888 "?-nonewline? str",
889 aio_cmd_puts,
892 /* Description: Write the string, with newline unless -nonewline */
894 { "isatty",
895 NULL,
896 aio_cmd_isatty,
899 /* Description: Is the file descriptor a tty? */
901 #if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)
902 { "recvfrom",
903 "len ?addrvar?",
904 aio_cmd_recvfrom,
907 /* Description: Receive up to 'len' bytes on the socket. Sets 'addrvar' with receive address, if set */
909 { "sendto",
910 "str address",
911 aio_cmd_sendto,
914 /* Description: Send 'str' to the given address (dgram only) */
916 { "accept",
917 NULL,
918 aio_cmd_accept,
921 /* Description: Server socket only: Accept a connection and return stream */
923 { "listen",
924 "backlog",
925 aio_cmd_listen,
928 /* Description: Set the listen backlog for server socket */
930 #endif /* JIM_BOOTSTRAP */
931 { "flush",
932 NULL,
933 aio_cmd_flush,
936 /* Description: Flush the stream */
938 { "eof",
939 NULL,
940 aio_cmd_eof,
943 /* Description: Returns 1 if stream is at eof */
945 { "close",
946 "?r(ead)|w(rite)?",
947 aio_cmd_close,
950 JIM_MODFLAG_FULLARGV,
951 /* Description: Closes the stream. */
953 { "seek",
954 "offset ?start|current|end",
955 aio_cmd_seek,
958 /* Description: Seeks in the stream (default 'current') */
960 { "tell",
961 NULL,
962 aio_cmd_tell,
965 /* Description: Returns the current seek position */
967 { "filename",
968 NULL,
969 aio_cmd_filename,
972 /* Description: Returns the original filename */
974 #ifdef O_NDELAY
975 { "ndelay",
976 "?0|1?",
977 aio_cmd_ndelay,
980 /* Description: Set O_NDELAY (if arg). Returns current/new setting. */
982 #endif
983 { "buffering",
984 "none|line|full",
985 aio_cmd_buffering,
988 /* Description: Sets buffering */
990 #ifdef jim_ext_eventloop
991 { "readable",
992 "?readable-script?",
993 aio_cmd_readable,
996 /* Description: Returns script, or invoke readable-script when readable, {} to remove */
998 { "writable",
999 "?writable-script?",
1000 aio_cmd_writable,
1003 /* Description: Returns script, or invoke writable-script when writable, {} to remove */
1005 { "onexception",
1006 "?exception-script?",
1007 aio_cmd_onexception,
1010 /* Description: Returns script, or invoke exception-script when oob data, {} to remove */
1012 #endif
1013 { NULL }
1016 static int JimAioSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1018 return Jim_CallSubCmd(interp, Jim_ParseSubCmd(interp, aio_command_table, argc, argv), argc, argv);
1021 static int JimAioOpenCommand(Jim_Interp *interp, int argc,
1022 Jim_Obj *const *argv)
1024 const char *mode;
1026 if (argc != 2 && argc != 3) {
1027 Jim_WrongNumArgs(interp, 1, argv, "filename ?mode?");
1028 return JIM_ERR;
1031 mode = (argc == 3) ? Jim_String(argv[2]) : "r";
1033 #ifdef jim_ext_tclcompat
1035 const char *filename = Jim_String(argv[1]);
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);
1048 #endif
1049 return JimMakeChannel(interp, NULL, -1, argv[1], "aio.handle%ld", 0, mode);
1053 * Creates a channel for fh/fd/filename.
1055 * If fh is not NULL, uses that as the channel (and sets AIO_KEEPOPEN).
1056 * Otherwise, if fd is >= 0, uses that as the channel.
1057 * Otherwise opens 'filename' with mode 'mode'.
1059 * hdlfmt is a sprintf format for the filehandle. Anything with %ld at the end will do.
1060 * mode is used for open or fdopen.
1062 * Creates the command and sets the name as the current result.
1064 static int JimMakeChannel(Jim_Interp *interp, FILE *fh, int fd, Jim_Obj *filename,
1065 const char *hdlfmt, int family, const char *mode)
1067 AioFile *af;
1068 char buf[AIO_CMD_LEN];
1069 int openFlags = 0;
1071 if (fh) {
1072 filename = Jim_NewStringObj(interp, hdlfmt, -1);
1073 openFlags = AIO_KEEPOPEN;
1076 Jim_IncrRefCount(filename);
1078 if (fh == NULL) {
1079 #if !defined(JIM_ANSIC)
1080 if (fd >= 0) {
1081 fh = fdopen(fd, mode);
1083 else
1084 #endif
1085 fh = fopen(Jim_String(filename), mode);
1087 if (fh == NULL) {
1088 JimAioSetError(interp, filename);
1089 #if !defined(JIM_ANSIC)
1090 if (fd >= 0) {
1091 close(fd);
1093 #endif
1094 Jim_DecrRefCount(interp, filename);
1095 return JIM_ERR;
1099 /* Create the file command */
1100 af = Jim_Alloc(sizeof(*af));
1101 memset(af, 0, sizeof(*af));
1102 af->fp = fh;
1103 af->fd = fileno(fh);
1104 af->filename = filename;
1105 #ifdef FD_CLOEXEC
1106 if ((openFlags & AIO_KEEPOPEN) == 0) {
1107 fcntl(af->fd, F_SETFD, FD_CLOEXEC);
1109 #endif
1110 af->openFlags = openFlags;
1111 af->addr_family = family;
1112 snprintf(buf, sizeof(buf), hdlfmt, Jim_GetId(interp));
1113 Jim_CreateCommand(interp, buf, JimAioSubCmdProc, af, JimAioDelProc);
1115 /* Note that the command must use the global namespace, even if
1116 * the current namespace is something different
1118 Jim_SetResult(interp, Jim_MakeGlobalNamespaceName(interp, Jim_NewStringObj(interp, buf, -1)));
1120 return JIM_OK;
1123 static int JimMakeChannelPair(Jim_Interp *interp, int p[2], Jim_Obj *filename,
1124 const char *hdlfmt, int family, const char *mode[2])
1126 if (JimMakeChannel(interp, NULL, p[0], filename, hdlfmt, family, mode[0]) == JIM_OK) {
1127 Jim_Obj *objPtr = Jim_NewListObj(interp, NULL, 0);
1128 Jim_ListAppendElement(interp, objPtr, Jim_GetResult(interp));
1130 if (JimMakeChannel(interp, NULL, p[1], filename, hdlfmt, family, mode[1]) == JIM_OK) {
1131 Jim_ListAppendElement(interp, objPtr, Jim_GetResult(interp));
1132 Jim_SetResult(interp, objPtr);
1133 return JIM_OK;
1137 /* Can only be here if fdopen() failed */
1138 close(p[0]);
1139 close(p[1]);
1140 JimAioSetError(interp, NULL);
1141 return JIM_ERR;
1144 #if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)
1146 static int JimAioSockCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1148 const char *hdlfmt = "aio.unknown%ld";
1149 const char *socktypes[] = {
1150 "unix",
1151 "unix.server",
1152 "dgram",
1153 "dgram.server",
1154 "stream",
1155 "stream.server",
1156 "pipe",
1157 "pair",
1158 NULL
1160 enum
1162 SOCK_UNIX,
1163 SOCK_UNIX_SERVER,
1164 SOCK_DGRAM_CLIENT,
1165 SOCK_DGRAM_SERVER,
1166 SOCK_STREAM_CLIENT,
1167 SOCK_STREAM_SERVER,
1168 SOCK_STREAM_PIPE,
1169 SOCK_STREAM_SOCKETPAIR,
1171 int socktype;
1172 int sock;
1173 const char *hostportarg = NULL;
1174 int res;
1175 int on = 1;
1176 const char *mode = "r+";
1177 int family = PF_INET;
1178 Jim_Obj *argv0 = argv[0];
1179 int ipv6 = 0;
1181 if (argc > 1 && Jim_CompareStringImmediate(interp, argv[1], "-ipv6")) {
1182 if (!IPV6) {
1183 Jim_SetResultString(interp, "ipv6 not supported", -1);
1184 return JIM_ERR;
1186 ipv6 = 1;
1187 family = PF_INET6;
1189 argc -= ipv6;
1190 argv += ipv6;
1192 if (argc < 2) {
1193 wrongargs:
1194 Jim_WrongNumArgs(interp, 1, &argv0, "?-ipv6? type ?address?");
1195 return JIM_ERR;
1198 if (Jim_GetEnum(interp, argv[1], socktypes, &socktype, "socket type", JIM_ERRMSG) != JIM_OK)
1199 return JIM_ERR;
1201 Jim_SetEmptyResult(interp);
1203 hdlfmt = "aio.sock%ld";
1205 if (argc > 2) {
1206 hostportarg = Jim_String(argv[2]);
1209 switch (socktype) {
1210 case SOCK_DGRAM_CLIENT:
1211 if (argc == 2) {
1212 /* No address, so an unconnected dgram socket */
1213 sock = socket(family, SOCK_DGRAM, 0);
1214 if (sock < 0) {
1215 JimAioSetError(interp, NULL);
1216 return JIM_ERR;
1218 break;
1220 /* fall through */
1221 case SOCK_STREAM_CLIENT:
1223 union sockaddr_any sa;
1224 int salen;
1226 if (argc != 3) {
1227 goto wrongargs;
1230 if (ipv6) {
1231 if (JimParseIPv6Address(interp, hostportarg, &sa, &salen) != JIM_OK) {
1232 return JIM_ERR;
1235 else if (JimParseIpAddress(interp, hostportarg, &sa, &salen) != JIM_OK) {
1236 return JIM_ERR;
1238 sock = socket(family, (socktype == SOCK_DGRAM_CLIENT) ? SOCK_DGRAM : SOCK_STREAM, 0);
1239 if (sock < 0) {
1240 JimAioSetError(interp, NULL);
1241 return JIM_ERR;
1243 res = connect(sock, &sa.sa, salen);
1244 if (res) {
1245 JimAioSetError(interp, argv[2]);
1246 close(sock);
1247 return JIM_ERR;
1250 break;
1252 case SOCK_STREAM_SERVER:
1253 case SOCK_DGRAM_SERVER:
1255 union sockaddr_any sa;
1256 int salen;
1258 if (argc != 3) {
1259 goto wrongargs;
1262 if (ipv6) {
1263 if (JimParseIPv6Address(interp, hostportarg, &sa, &salen) != JIM_OK) {
1264 return JIM_ERR;
1267 else if (JimParseIpAddress(interp, hostportarg, &sa, &salen) != JIM_OK) {
1268 return JIM_ERR;
1270 sock = socket(family, (socktype == SOCK_DGRAM_SERVER) ? SOCK_DGRAM : SOCK_STREAM, 0);
1271 if (sock < 0) {
1272 JimAioSetError(interp, NULL);
1273 return JIM_ERR;
1276 /* Enable address reuse */
1277 setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on));
1279 res = bind(sock, &sa.sa, salen);
1280 if (res) {
1281 JimAioSetError(interp, argv[2]);
1282 close(sock);
1283 return JIM_ERR;
1285 if (socktype == SOCK_STREAM_SERVER) {
1286 res = listen(sock, 5);
1287 if (res) {
1288 JimAioSetError(interp, NULL);
1289 close(sock);
1290 return JIM_ERR;
1293 hdlfmt = "aio.socksrv%ld";
1295 break;
1297 #ifdef HAVE_SYS_UN_H
1298 case SOCK_UNIX:
1300 struct sockaddr_un sa;
1301 socklen_t len;
1303 if (argc != 3 || ipv6) {
1304 goto wrongargs;
1307 if (JimParseDomainAddress(interp, hostportarg, &sa) != JIM_OK) {
1308 JimAioSetError(interp, argv[2]);
1309 return JIM_ERR;
1311 family = PF_UNIX;
1312 sock = socket(PF_UNIX, SOCK_STREAM, 0);
1313 if (sock < 0) {
1314 JimAioSetError(interp, NULL);
1315 return JIM_ERR;
1317 len = strlen(sa.sun_path) + 1 + sizeof(sa.sun_family);
1318 res = connect(sock, (struct sockaddr *)&sa, len);
1319 if (res) {
1320 JimAioSetError(interp, argv[2]);
1321 close(sock);
1322 return JIM_ERR;
1324 hdlfmt = "aio.sockunix%ld";
1325 break;
1328 case SOCK_UNIX_SERVER:
1330 struct sockaddr_un sa;
1331 socklen_t len;
1333 if (argc != 3 || ipv6) {
1334 goto wrongargs;
1337 if (JimParseDomainAddress(interp, hostportarg, &sa) != JIM_OK) {
1338 JimAioSetError(interp, argv[2]);
1339 return JIM_ERR;
1341 family = PF_UNIX;
1342 sock = socket(PF_UNIX, SOCK_STREAM, 0);
1343 if (sock < 0) {
1344 JimAioSetError(interp, NULL);
1345 return JIM_ERR;
1347 len = strlen(sa.sun_path) + 1 + sizeof(sa.sun_family);
1348 res = bind(sock, (struct sockaddr *)&sa, len);
1349 if (res) {
1350 JimAioSetError(interp, argv[2]);
1351 close(sock);
1352 return JIM_ERR;
1354 res = listen(sock, 5);
1355 if (res) {
1356 JimAioSetError(interp, NULL);
1357 close(sock);
1358 return JIM_ERR;
1360 hdlfmt = "aio.sockunixsrv%ld";
1361 break;
1363 #endif
1365 #if defined(HAVE_SOCKETPAIR) && defined(HAVE_SYS_UN_H)
1366 case SOCK_STREAM_SOCKETPAIR:
1368 int p[2];
1369 static const char *mode[2] = { "r+", "r+" };
1371 if (argc != 2 || ipv6) {
1372 goto wrongargs;
1375 if (socketpair(PF_UNIX, SOCK_STREAM, 0, p) < 0) {
1376 JimAioSetError(interp, NULL);
1377 return JIM_ERR;
1379 return JimMakeChannelPair(interp, p, argv[1], "aio.sockpair%ld", PF_UNIX, mode);
1381 break;
1382 #endif
1384 #if defined(HAVE_PIPE)
1385 case SOCK_STREAM_PIPE:
1387 int p[2];
1388 static const char *mode[2] = { "r", "w" };
1390 if (argc != 2 || ipv6) {
1391 goto wrongargs;
1394 if (pipe(p) < 0) {
1395 JimAioSetError(interp, NULL);
1396 return JIM_ERR;
1399 return JimMakeChannelPair(interp, p, argv[1], "aio.pipe%ld", 0, mode);
1401 break;
1402 #endif
1404 default:
1405 Jim_SetResultString(interp, "Unsupported socket type", -1);
1406 return JIM_ERR;
1409 return JimMakeChannel(interp, NULL, sock, argv[1], hdlfmt, family, mode);
1411 #endif /* JIM_BOOTSTRAP */
1413 FILE *Jim_AioFilehandle(Jim_Interp *interp, Jim_Obj *command)
1415 Jim_Cmd *cmdPtr = Jim_GetCommand(interp, command, JIM_ERRMSG);
1417 /* XXX: There ought to be a supported API for this */
1418 if (cmdPtr && !cmdPtr->isproc && cmdPtr->u.native.cmdProc == JimAioSubCmdProc) {
1419 return ((AioFile *) cmdPtr->u.native.privData)->fp;
1421 Jim_SetResultFormatted(interp, "Not a filehandle: \"%#s\"", command);
1422 return NULL;
1425 int Jim_aioInit(Jim_Interp *interp)
1427 if (Jim_PackageProvide(interp, "aio", "1.0", JIM_ERRMSG))
1428 return JIM_ERR;
1430 Jim_CreateCommand(interp, "open", JimAioOpenCommand, NULL, NULL);
1431 #ifndef JIM_ANSIC
1432 Jim_CreateCommand(interp, "socket", JimAioSockCommand, NULL, NULL);
1433 #endif
1435 /* Create filehandles for stdin, stdout and stderr */
1436 JimMakeChannel(interp, stdin, -1, NULL, "stdin", 0, "r");
1437 JimMakeChannel(interp, stdout, -1, NULL, "stdout", 0, "w");
1438 JimMakeChannel(interp, stderr, -1, NULL, "stderr", 0, "w");
1440 return JIM_OK;