It was possible to create a bad ref
[jimtcl.git] / jim-aio.c
blobf4bd0e3eeb4ef864cffe08d5524aa583fbfe489e
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 - o/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 * The FreeBSD license
14 * Redistribution and use in source and binary forms, with or without
15 * modification, are permitted provided that the following conditions
16 * are met:
18 * 1. Redistributions of source code must retain the above copyright
19 * notice, this list of conditions and the following disclaimer.
20 * 2. Redistributions in binary form must reproduce the above
21 * copyright notice, this list of conditions and the following
22 * disclaimer in the documentation and/or other materials
23 * provided with the distribution.
25 * THIS SOFTWARE IS PROVIDED BY THE JIM TCL PROJECT ``AS IS'' AND ANY
26 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
27 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
28 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
29 * JIM TCL PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
30 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
31 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
34 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
36 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 * The views and conclusions contained in the software and documentation
39 * are those of the authors and should not be interpreted as representing
40 * official policies, either expressed or implied, of the Jim Tcl Project.
41 **/
43 #include <unistd.h>
44 #include <stdio.h>
45 #include <string.h>
46 #include <errno.h>
47 #include <fcntl.h>
49 #include "jim.h"
51 #ifndef JIM_ANSIC
52 #include <sys/socket.h>
53 #include <sys/un.h>
54 #include <netinet/in.h>
55 #include <arpa/inet.h>
56 #include <netdb.h>
57 #endif
59 #include "jim-eventloop.h"
60 #include "jim-subcmd.h"
63 #define AIO_CMD_LEN 32 /* e.g. aio.handleXXXXXX */
64 #define AIO_BUF_LEN 256 /* Can keep this small and rely on stdio buffering */
66 #define AIO_KEEPOPEN 1
68 #if defined(JIM_IPV6)
69 #define IPV6 1
70 #else
71 #define IPV6 0
72 #ifndef AF_INET6
73 #define AF_INET6 0
74 #endif
75 #endif
77 #ifndef JIM_ANSIC
78 union sockaddr_any {
79 struct sockaddr sa;
80 struct sockaddr_in sin;
81 #if IPV6
82 struct sockaddr_in6 sin6;
83 #endif
85 #endif
87 typedef struct AioFile
89 FILE *fp;
90 Jim_Obj *filename;
91 int type;
92 int OpenFlags; /* AIO_KEEPOPEN? keep FILE* */
93 int fd;
94 #ifdef O_NDELAY
95 int flags;
96 #endif
97 Jim_Obj *rEvent;
98 Jim_Obj *wEvent;
99 Jim_Obj *eEvent;
100 #ifndef JIM_ANSIC
101 int addr_family;
102 #endif
103 } AioFile;
105 static int JimAioSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv);
107 #ifndef JIM_ANSIC
108 static int JimParseIPv6Address(Jim_Interp *interp, const char *hostport, union sockaddr_any *sa, int *salen)
110 #if IPV6
112 * An IPv6 addr/port looks like:
113 * [::1]
114 * [::1]:2000
115 * [fe80::223:6cff:fe95:bdc0%en1]:2000
116 * [::]:2000
117 * 2000
119 * Note that the "any" address is ::, which is the same as when no address is specified.
121 char *sthost = NULL;
122 const char *stport;
123 int ret = JIM_OK;
124 struct addrinfo req;
125 struct addrinfo *ai;
127 stport = strrchr(hostport, ':');
128 if (!stport) {
129 /* No : so, the whole thing is the port */
130 stport = hostport;
131 hostport = "::";
132 sthost = Jim_StrDup(hostport);
134 else {
135 stport++;
138 if (*hostport == '[') {
139 /* This is a numeric ipv6 address */
140 char *pt = strchr(++hostport, ']');
141 if (pt) {
142 sthost = Jim_StrDupLen(hostport, pt - hostport);
146 if (!sthost) {
147 sthost = Jim_StrDupLen(hostport, stport - hostport - 1);
150 memset(&req, '\0', sizeof(req));
151 req.ai_family = PF_INET6;
153 if (getaddrinfo(sthost, NULL, &req, &ai)) {
154 Jim_SetResultFormatted(interp, "Not a valid address: %s", hostport);
155 ret = JIM_ERR;
157 else {
158 memcpy(&sa->sin, ai->ai_addr, ai->ai_addrlen);
159 *salen = ai->ai_addrlen;
161 sa->sin.sin_port = htons(atoi(stport));
163 freeaddrinfo(ai);
165 Jim_Free(sthost);
167 return ret;
168 #else
169 Jim_SetResultString(interp, "ipv6 not supported", -1);
170 return JIM_ERR;
171 #endif
174 static int JimParseIpAddress(Jim_Interp *interp, const char *hostport, union sockaddr_any *sa, int *salen)
176 /* An IPv4 addr/port looks like:
177 * 192.168.1.5
178 * 192.168.1.5:2000
179 * 2000
181 * If the address is missing, INADDR_ANY is used.
182 * If the port is missing, 0 is used (only useful for server sockets).
184 char *sthost = NULL;
185 const char *stport;
186 int ret = JIM_OK;
187 struct addrinfo req;
188 struct addrinfo *ai;
190 stport = strrchr(hostport, ':');
191 if (!stport) {
192 /* No : so, the whole thing is the port */
193 stport = hostport;
194 sthost = Jim_StrDup("0.0.0.0");
196 else {
197 sthost = Jim_StrDupLen(hostport, stport - hostport);
198 stport++;
201 memset(&req, '\0', sizeof(req));
202 req.ai_family = PF_INET;
204 if (getaddrinfo(sthost, NULL, &req, &ai)) {
205 Jim_SetResultFormatted(interp, "Not a valid address: %s", hostport);
206 ret = JIM_ERR;
208 else {
209 memcpy(&sa->sin, ai->ai_addr, ai->ai_addrlen);
210 *salen = ai->ai_addrlen;
212 sa->sin.sin_port = htons(atoi(stport));
214 freeaddrinfo(ai);
216 Jim_Free(sthost);
218 return ret;
221 static int JimParseDomainAddress(Jim_Interp *interp, const char *path, struct sockaddr_un *sa)
223 sa->sun_family = PF_UNIX;
224 strcpy(sa->sun_path, path);
226 return JIM_OK;
228 #endif
230 static void JimAioSetError(Jim_Interp *interp, Jim_Obj *name)
232 if (name) {
233 Jim_SetResultFormatted(interp, "%#s: %s", name, strerror(errno));
235 else {
236 Jim_SetResultString(interp, strerror(errno), -1);
240 static void JimAioDelProc(Jim_Interp *interp, void *privData)
242 AioFile *af = privData;
244 JIM_NOTUSED(interp);
246 Jim_DecrRefCount(interp, af->filename);
248 if (!(af->OpenFlags & AIO_KEEPOPEN)) {
249 fclose(af->fp);
251 #ifdef jim_ext_eventloop
252 /* remove existing EventHandlers */
253 if (af->rEvent) {
254 Jim_DeleteFileHandler(interp, af->fp);
256 if (af->wEvent) {
257 Jim_DeleteFileHandler(interp, af->fp);
259 if (af->eEvent) {
260 Jim_DeleteFileHandler(interp, af->fp);
262 #endif
263 Jim_Free(af);
266 static int aio_cmd_read(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
268 AioFile *af = Jim_CmdPrivData(interp);
269 char buf[AIO_BUF_LEN];
270 Jim_Obj *objPtr;
271 int nonewline = 0;
272 int neededLen = -1; /* -1 is "read as much as possible" */
274 if (argc && Jim_CompareStringImmediate(interp, argv[0], "-nonewline")) {
275 nonewline = 1;
276 argv++;
277 argc--;
279 if (argc == 1) {
280 jim_wide wideValue;
282 if (Jim_GetWide(interp, argv[0], &wideValue) != JIM_OK)
283 return JIM_ERR;
284 if (wideValue < 0) {
285 Jim_SetResultString(interp, "invalid parameter: negative len", -1);
286 return JIM_ERR;
288 neededLen = (int)wideValue;
290 else if (argc) {
291 return -1;
293 objPtr = Jim_NewStringObj(interp, NULL, 0);
294 while (neededLen != 0) {
295 int retval;
296 int readlen;
298 if (neededLen == -1) {
299 readlen = AIO_BUF_LEN;
301 else {
302 readlen = (neededLen > AIO_BUF_LEN ? AIO_BUF_LEN : neededLen);
304 retval = fread(buf, 1, readlen, af->fp);
305 if (retval > 0) {
306 Jim_AppendString(interp, objPtr, buf, retval);
307 if (neededLen != -1) {
308 neededLen -= retval;
311 if (retval != readlen)
312 break;
314 /* Check for error conditions */
315 if (ferror(af->fp) && errno != EAGAIN) {
316 /* I/O error */
317 Jim_FreeNewObj(interp, objPtr);
318 JimAioSetError(interp, af->filename);
319 clearerr(af->fp);
320 return JIM_ERR;
322 if (nonewline) {
323 int len;
324 const char *s = Jim_GetString(objPtr, &len);
326 if (len > 0 && s[len - 1] == '\n') {
327 objPtr->length--;
328 objPtr->bytes[objPtr->length] = '\0';
331 Jim_SetResult(interp, objPtr);
332 return JIM_OK;
335 static int aio_cmd_gets(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
337 AioFile *af = Jim_CmdPrivData(interp);
338 char buf[AIO_BUF_LEN];
339 Jim_Obj *objPtr;
341 errno = 0;
343 objPtr = Jim_NewStringObj(interp, NULL, 0);
344 while (1) {
345 int more = 0;
347 buf[AIO_BUF_LEN - 1] = '_';
348 if (fgets(buf, AIO_BUF_LEN, af->fp) == NULL)
349 break;
350 if (buf[AIO_BUF_LEN - 1] == '\0' && buf[AIO_BUF_LEN - 2] != '\n')
351 more = 1;
352 if (more) {
353 Jim_AppendString(interp, objPtr, buf, AIO_BUF_LEN - 1);
355 else {
356 int len = strlen(buf);
358 if (len) {
359 int hasnl = (buf[len - 1] == '\n');
361 /* strip "\n" */
362 Jim_AppendString(interp, objPtr, buf, strlen(buf) - hasnl);
365 if (!more)
366 break;
368 if (ferror(af->fp) && errno != EAGAIN && errno != EINTR) {
369 /* I/O error */
370 Jim_FreeNewObj(interp, objPtr);
371 JimAioSetError(interp, af->filename);
372 clearerr(af->fp);
373 return JIM_ERR;
375 /* On EOF returns -1 if varName was specified, or the empty string. */
376 if (feof(af->fp) && Jim_Length(objPtr) == 0) {
377 Jim_FreeNewObj(interp, objPtr);
378 if (argc) {
379 Jim_SetResultInt(interp, -1);
381 return JIM_OK;
383 if (argc) {
384 int totLen;
386 Jim_GetString(objPtr, &totLen);
387 if (Jim_SetVariable(interp, argv[0], objPtr) != JIM_OK) {
388 Jim_FreeNewObj(interp, objPtr);
389 return JIM_ERR;
391 Jim_SetResultInt(interp, totLen);
393 else {
394 Jim_SetResult(interp, objPtr);
396 return JIM_OK;
399 static int aio_cmd_puts(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
401 AioFile *af = Jim_CmdPrivData(interp);
402 int wlen;
403 const char *wdata;
404 Jim_Obj *strObj;
406 if (argc == 2) {
407 if (!Jim_CompareStringImmediate(interp, argv[0], "-nonewline")) {
408 return -1;
410 strObj = argv[1];
412 else {
413 strObj = argv[0];
416 wdata = Jim_GetString(strObj, &wlen);
417 if (fwrite(wdata, 1, wlen, af->fp) == (unsigned)wlen) {
418 if (argc == 2 || putc('\n', af->fp) != EOF) {
419 return JIM_OK;
422 JimAioSetError(interp, af->filename);
423 return JIM_ERR;
426 #ifndef JIM_ANSIC
427 static int aio_cmd_recvfrom(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
429 AioFile *af = Jim_CmdPrivData(interp);
430 char *buf;
431 union sockaddr_any sa;
432 long len;
433 socklen_t salen = sizeof(sa);
434 int rlen;
436 if (Jim_GetLong(interp, argv[0], &len) != JIM_OK) {
437 return JIM_ERR;
440 buf = Jim_Alloc(len + 1);
442 rlen = recvfrom(fileno(af->fp), buf, len, 0, &sa.sa, &salen);
443 if (rlen < 0) {
444 Jim_Free(buf);
445 JimAioSetError(interp, NULL);
446 return JIM_ERR;
448 buf[rlen] = 0;
449 Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, buf, rlen));
451 if (argc > 1) {
452 /* INET6_ADDRSTRLEN is 46. Add some for [] and port */
453 char addrbuf[60];
455 #if IPV6
456 if (sa.sa.sa_family == AF_INET6) {
457 addrbuf[0] = '[';
458 /* Allow 9 for []:65535\0 */
459 inet_ntop(sa.sa.sa_family, &sa.sin6.sin6_addr, addrbuf + 1, sizeof(addrbuf) - 9);
460 snprintf(addrbuf + strlen(addrbuf), 8, "]:%d", ntohs(sa.sin.sin_port));
462 else
463 #endif
465 /* Allow 7 for :65535\0 */
466 inet_ntop(sa.sa.sa_family, &sa.sin.sin_addr, addrbuf, sizeof(addrbuf) - 7);
467 snprintf(addrbuf + strlen(addrbuf), 7, ":%d", ntohs(sa.sin.sin_port));
470 if (Jim_SetVariable(interp, argv[1], Jim_NewStringObj(interp, addrbuf, -1)) != JIM_OK) {
471 return JIM_ERR;
475 return JIM_OK;
479 static int aio_cmd_sendto(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
481 AioFile *af = Jim_CmdPrivData(interp);
482 int wlen;
483 int len;
484 const char *wdata;
485 union sockaddr_any sa;
486 const char *addr = Jim_GetString(argv[1], NULL);
487 int salen;
489 if (IPV6 && af->addr_family == AF_INET6) {
490 if (JimParseIPv6Address(interp, addr, &sa, &salen) != JIM_OK) {
491 return JIM_ERR;
494 else if (JimParseIpAddress(interp, addr, &sa, &salen) != JIM_OK) {
495 return JIM_ERR;
497 wdata = Jim_GetString(argv[0], &wlen);
499 /* Note that we don't validate the socket type. Rely on sendto() failing if appropriate */
500 len = sendto(fileno(af->fp), wdata, wlen, 0, &sa.sa, salen);
501 if (len < 0) {
502 JimAioSetError(interp, NULL);
503 return JIM_ERR;
505 Jim_SetResultInt(interp, len);
506 return JIM_OK;
509 static int aio_cmd_accept(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
511 AioFile *serv_af = Jim_CmdPrivData(interp);
512 int sock;
513 union sockaddr_any sa;
514 socklen_t addrlen = sizeof(sa);
515 AioFile *af;
516 char buf[AIO_CMD_LEN];
518 sock = accept(serv_af->fd, &sa.sa, &addrlen);
519 if (sock < 0)
520 return JIM_ERR;
522 /* Create the file command */
523 af = Jim_Alloc(sizeof(*af));
524 af->fd = sock;
525 af->filename = Jim_NewStringObj(interp, "accept", -1);
526 Jim_IncrRefCount(af->filename);
527 af->fp = fdopen(sock, "r+");
528 af->OpenFlags = 0;
529 #ifdef O_NDELAY
530 af->flags = fcntl(af->fd, F_GETFL);
531 #endif
532 af->rEvent = NULL;
533 af->wEvent = NULL;
534 af->eEvent = NULL;
535 af->addr_family = serv_af->addr_family;
536 sprintf(buf, "aio.sockstream%ld", Jim_GetId(interp));
537 Jim_CreateCommand(interp, buf, JimAioSubCmdProc, af, JimAioDelProc);
538 Jim_SetResultString(interp, buf, -1);
539 return JIM_OK;
542 #endif
544 static int aio_cmd_flush(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
546 AioFile *af = Jim_CmdPrivData(interp);
548 if (fflush(af->fp) == EOF) {
549 JimAioSetError(interp, af->filename);
550 return JIM_ERR;
552 return JIM_OK;
555 static int aio_cmd_eof(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
557 AioFile *af = Jim_CmdPrivData(interp);
559 Jim_SetResultInt(interp, feof(af->fp));
560 return JIM_OK;
563 static int aio_cmd_close(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
565 Jim_DeleteCommand(interp, Jim_GetString(argv[0], NULL));
566 return JIM_OK;
569 static int aio_cmd_seek(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
571 AioFile *af = Jim_CmdPrivData(interp);
572 int orig = SEEK_SET;
573 long offset;
575 if (argc == 2) {
576 if (Jim_CompareStringImmediate(interp, argv[1], "start"))
577 orig = SEEK_SET;
578 else if (Jim_CompareStringImmediate(interp, argv[1], "current"))
579 orig = SEEK_CUR;
580 else if (Jim_CompareStringImmediate(interp, argv[1], "end"))
581 orig = SEEK_END;
582 else {
583 return -1;
586 if (Jim_GetLong(interp, argv[0], &offset) != JIM_OK) {
587 return JIM_ERR;
589 if (fseek(af->fp, offset, orig) == -1) {
590 JimAioSetError(interp, af->filename);
591 return JIM_ERR;
593 return JIM_OK;
596 static int aio_cmd_tell(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
598 AioFile *af = Jim_CmdPrivData(interp);
600 Jim_SetResultInt(interp, ftell(af->fp));
601 return JIM_OK;
604 #ifdef O_NDELAY
605 static int aio_cmd_ndelay(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
607 AioFile *af = Jim_CmdPrivData(interp);
609 int fmode = af->flags;
611 if (argc) {
612 long nb;
614 if (Jim_GetLong(interp, argv[0], &nb) != JIM_OK) {
615 return JIM_ERR;
617 if (nb) {
618 fmode |= O_NDELAY;
620 else {
621 fmode &= ~O_NDELAY;
623 fcntl(af->fd, F_SETFL, fmode);
624 af->flags = fmode;
626 Jim_SetResultInt(interp, (fmode & O_NONBLOCK) ? 1 : 0);
627 return JIM_OK;
629 #endif
631 #ifdef jim_ext_eventloop
632 static void JimAioFileEventFinalizer(Jim_Interp *interp, void *clientData)
634 Jim_Obj *objPtr = clientData;
636 Jim_DecrRefCount(interp, objPtr);
639 static int JimAioFileEventHandler(Jim_Interp *interp, void *clientData, int mask)
641 Jim_Obj *objPtr = clientData;
643 return Jim_EvalObjBackground(interp, objPtr);
646 static int aio_eventinfo(Jim_Interp *interp, AioFile * af, unsigned mask, Jim_Obj **scriptHandlerObj,
647 int argc, Jim_Obj * const *argv)
649 int scriptlen = 0;
651 if (argc == 0) {
652 /* Return current script */
653 if (*scriptHandlerObj) {
654 Jim_SetResult(interp, *scriptHandlerObj);
656 return JIM_OK;
659 if (*scriptHandlerObj) {
660 /* Delete old handler */
661 Jim_DeleteFileHandler(interp, af->fp);
662 *scriptHandlerObj = NULL;
665 /* Now possibly add the new script(s) */
666 Jim_GetString(argv[0], &scriptlen);
667 if (scriptlen == 0) {
668 /* Empty script, so done */
669 return JIM_OK;
672 /* A new script to add */
673 Jim_IncrRefCount(argv[0]);
674 *scriptHandlerObj = argv[0];
676 Jim_CreateFileHandler(interp, af->fp, mask,
677 JimAioFileEventHandler, *scriptHandlerObj, JimAioFileEventFinalizer);
679 return JIM_OK;
682 static int aio_cmd_readable(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
684 AioFile *af = Jim_CmdPrivData(interp);
686 return aio_eventinfo(interp, af, JIM_EVENT_READABLE, &af->rEvent, argc, argv);
689 static int aio_cmd_writable(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
691 AioFile *af = Jim_CmdPrivData(interp);
693 return aio_eventinfo(interp, af, JIM_EVENT_WRITABLE, &af->wEvent, argc, argv);
696 static int aio_cmd_onexception(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
698 AioFile *af = Jim_CmdPrivData(interp);
700 return aio_eventinfo(interp, af, JIM_EVENT_EXCEPTION, &af->wEvent, argc, argv);
702 #endif
704 static const jim_subcmd_type aio_command_table[] = {
705 { .cmd = "read",
706 .args = "?-nonewline? ?len?",
707 .function = aio_cmd_read,
708 .minargs = 0,
709 .maxargs = 2,
710 .description = "Read and return bytes from the stream. To eof if no len."
712 { .cmd = "gets",
713 .args = "?var?",
714 .function = aio_cmd_gets,
715 .minargs = 0,
716 .maxargs = 1,
717 .description = "Read one line and return it or store it in the var"
719 { .cmd = "puts",
720 .args = "?-nonewline? str",
721 .function = aio_cmd_puts,
722 .minargs = 1,
723 .maxargs = 2,
724 .description = "Write the string, with newline unless -nonewline"
726 #ifndef JIM_ANSIC
727 { .cmd = "recvfrom",
728 .args = "len ?addrvar?",
729 .function = aio_cmd_recvfrom,
730 .minargs = 1,
731 .maxargs = 2,
732 .description = "Receive up to 'len' bytes on the socket. Sets 'addrvar' with receive address, if set"
734 { .cmd = "sendto",
735 .args = "str address",
736 .function = aio_cmd_sendto,
737 .minargs = 2,
738 .maxargs = 2,
739 .description = "Send 'str' to the given address (dgram only)"
741 { .cmd = "accept",
742 .function = aio_cmd_accept,
743 .description = "Server socket only: Accept a connection and return stream"
745 #endif
746 { .cmd = "flush",
747 .function = aio_cmd_flush,
748 .description = "Flush the stream"
750 { .cmd = "eof",
751 .function = aio_cmd_eof,
752 .description = "Returns 1 if stream is at eof"
754 { .cmd = "close",
755 .flags = JIM_MODFLAG_FULLARGV,
756 .function = aio_cmd_close,
757 .description = "Closes the stream"
759 { .cmd = "seek",
760 .args = "offset ?start|current|end",
761 .function = aio_cmd_seek,
762 .minargs = 1,
763 .maxargs = 2,
764 .description = "Seeks in the stream (default 'current')"
766 { .cmd = "tell",
767 .function = aio_cmd_tell,
768 .description = "Returns the current seek position"
770 #ifdef O_NDELAY
771 { .cmd = "ndelay",
772 .args = "?0|1?",
773 .function = aio_cmd_ndelay,
774 .minargs = 0,
775 .maxargs = 1,
776 .description = "Set O_NDELAY (if arg). Returns current/new setting."
778 #endif
779 #ifdef jim_ext_eventloop
780 { .cmd = "readable",
781 .args = "?readable-script?",
782 .minargs = 0,
783 .maxargs = 1,
784 .function = aio_cmd_readable,
785 .description = "Returns script, or invoke readable-script when readable, {} to remove",
787 { .cmd = "writable",
788 .args = "?writable-script?",
789 .minargs = 0,
790 .maxargs = 1,
791 .function = aio_cmd_writable,
792 .description = "Returns script, or invoke writable-script when writable, {} to remove",
794 { .cmd = "onexception",
795 .args = "?exception-script?",
796 .minargs = 0,
797 .maxargs = 1,
798 .function = aio_cmd_onexception,
799 .description = "Returns script, or invoke exception-script when oob data, {} to remove",
801 #endif
802 { 0 }
805 static int JimAioSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
807 return Jim_CallSubCmd(interp, Jim_ParseSubCmd(interp, aio_command_table, argc, argv), argc, argv);
810 static int JimAioOpenCommand(Jim_Interp *interp, int argc,
811 Jim_Obj *const *argv)
813 FILE *fp;
814 AioFile *af;
815 char buf[AIO_CMD_LEN];
816 int OpenFlags = 0;
817 const char *cmdname;
819 if (argc != 2 && argc != 3) {
820 Jim_WrongNumArgs(interp, 1, argv, "filename ?mode?");
821 return JIM_ERR;
823 cmdname = Jim_GetString(argv[1], NULL);
824 if (Jim_CompareStringImmediate(interp, argv[1], "stdin")) {
825 OpenFlags |= AIO_KEEPOPEN;
826 fp = stdin;
828 else if (Jim_CompareStringImmediate(interp, argv[1], "stdout")) {
829 OpenFlags |= AIO_KEEPOPEN;
830 fp = stdout;
832 else if (Jim_CompareStringImmediate(interp, argv[1], "stderr")) {
833 OpenFlags |= AIO_KEEPOPEN;
834 fp = stderr;
836 else {
837 const char *mode = (argc == 3) ? Jim_GetString(argv[2], NULL) : "r";
838 const char *filename = Jim_GetString(argv[1], NULL);
840 #ifdef jim_ext_tclcompat
841 /* If the filename starts with '|', use popen instead */
842 if (*filename == '|') {
843 Jim_Obj *evalObj[3];
845 evalObj[0] = Jim_NewStringObj(interp, "popen", -1);
846 evalObj[1] = Jim_NewStringObj(interp, filename + 1, -1);
847 evalObj[2] = Jim_NewStringObj(interp, mode, -1);
849 return Jim_EvalObjVector(interp, 3, evalObj);
851 #endif
852 fp = fopen(filename, mode);
853 if (fp == NULL) {
854 JimAioSetError(interp, argv[1]);
855 return JIM_ERR;
857 /* Get the next file id */
858 sprintf(buf, "aio.handle%ld", Jim_GetId(interp));
859 cmdname = buf;
862 /* Create the file command */
863 af = Jim_Alloc(sizeof(*af));
864 af->fp = fp;
865 af->fd = fileno(fp);
866 #ifdef O_NDELAY
867 af->flags = fcntl(af->fd, F_GETFL);
868 #endif
869 af->filename = argv[1];
870 Jim_IncrRefCount(af->filename);
871 af->OpenFlags = OpenFlags;
872 af->rEvent = NULL;
873 af->wEvent = NULL;
874 af->eEvent = NULL;
875 Jim_CreateCommand(interp, cmdname, JimAioSubCmdProc, af, JimAioDelProc);
876 Jim_SetResultString(interp, cmdname, -1);
877 return JIM_OK;
880 #ifndef JIM_ANSIC
883 * Creates a channel for fd.
885 * hdlfmt is a sprintf format for the filehandle. Anything with %ld at the end will do.
886 * mode is usual "r+", but may be another fdopen() mode as required.
888 * Creates the command and lappends the name of the command to the current result.
891 static int JimMakeChannel(Jim_Interp *interp, Jim_Obj *filename, const char *hdlfmt, int fd, int family,
892 const char *mode)
894 AioFile *af;
895 char buf[AIO_CMD_LEN];
897 FILE *fp = fdopen(fd, mode);
899 if (fp == NULL) {
900 close(fd);
901 JimAioSetError(interp, NULL);
902 return JIM_ERR;
905 /* Create the file command */
906 af = Jim_Alloc(sizeof(*af));
907 af->fp = fp;
908 af->fd = fd;
909 af->OpenFlags = 0;
910 af->filename = filename;
911 Jim_IncrRefCount(af->filename);
912 #ifdef O_NDELAY
913 af->flags = fcntl(af->fd, F_GETFL);
914 #endif
915 af->rEvent = NULL;
916 af->wEvent = NULL;
917 af->eEvent = NULL;
918 af->addr_family = family;
919 snprintf(buf, sizeof(buf), hdlfmt, Jim_GetId(interp));
920 Jim_CreateCommand(interp, buf, JimAioSubCmdProc, af, JimAioDelProc);
922 Jim_ListAppendElement(interp, Jim_GetResult(interp), Jim_NewStringObj(interp, buf, -1));
924 return JIM_OK;
927 static int JimAioSockCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
929 const char *hdlfmt = "aio.unknown%ld";
930 const char *socktypes[] = {
931 "unix",
932 "unix.server",
933 "dgram",
934 "dgram.server",
935 "stream",
936 "stream.server",
937 "pipe",
938 NULL
940 enum
942 SOCK_UNIX,
943 SOCK_UNIX_SERVER,
944 SOCK_DGRAM_CLIENT,
945 SOCK_DGRAM_SERVER,
946 SOCK_STREAM_CLIENT,
947 SOCK_STREAM_SERVER,
948 SOCK_STREAM_PIPE,
949 SOCK_DGRAM6_CLIENT,
950 SOCK_DGRAM6_SERVER,
951 SOCK_STREAM6_CLIENT,
952 SOCK_STREAM6_SERVER,
954 int socktype;
955 int sock;
956 const char *hostportarg = NULL;
957 int res;
958 int on = 1;
959 const char *mode = "r+";
960 int family = AF_INET;
961 Jim_Obj *argv0 = argv[0];
962 int ipv6 = 0;
964 if (argc > 1 && Jim_CompareStringImmediate(interp, argv[1], "-ipv6")) {
965 if (!IPV6) {
966 Jim_SetResultString(interp, "ipv6 not supported", -1);
967 return JIM_ERR;
969 ipv6 = 1;
970 family = AF_INET6;
972 argc -= ipv6;
973 argv += ipv6;
975 if (argc < 2) {
976 wrongargs:
977 Jim_WrongNumArgs(interp, 1, &argv0, "?-ipv6? type ?address?");
978 return JIM_ERR;
981 if (Jim_GetEnum(interp, argv[1], socktypes, &socktype, "socket type", JIM_ERRMSG) != JIM_OK)
982 return JIM_ERR;
984 Jim_SetResultString(interp, "", 0);
986 hdlfmt = "aio.sock%ld";
988 switch (socktype) {
989 case SOCK_DGRAM_CLIENT:
990 if (argc == 2) {
991 /* No address, so an unconnected dgram socket */
992 sock = socket(family, SOCK_DGRAM, 0);
993 if (sock < 0) {
994 JimAioSetError(interp, NULL);
995 return JIM_ERR;
997 break;
999 /* fall through */
1000 case SOCK_STREAM_CLIENT:
1002 union sockaddr_any sa;
1003 int salen;
1005 if (argc != 3) {
1006 goto wrongargs;
1009 hostportarg = Jim_GetString(argv[2], NULL);
1011 if (ipv6) {
1012 if (JimParseIPv6Address(interp, hostportarg, &sa, &salen) != JIM_OK) {
1013 return JIM_ERR;
1016 else if (JimParseIpAddress(interp, hostportarg, &sa, &salen) != JIM_OK) {
1017 return JIM_ERR;
1019 sock = socket(family, (socktype == SOCK_DGRAM_CLIENT) ? SOCK_DGRAM : SOCK_STREAM, 0);
1020 if (sock < 0) {
1021 JimAioSetError(interp, NULL);
1022 return JIM_ERR;
1024 res = connect(sock, &sa.sa, salen);
1025 if (res) {
1026 JimAioSetError(interp, argv[2]);
1027 close(sock);
1028 return JIM_ERR;
1031 break;
1033 case SOCK_STREAM_SERVER:
1034 case SOCK_DGRAM_SERVER:
1036 union sockaddr_any sa;
1037 int salen;
1039 if (argc != 3) {
1040 goto wrongargs;
1043 hostportarg = Jim_GetString(argv[2], NULL);
1045 if (ipv6) {
1046 if (JimParseIPv6Address(interp, hostportarg, &sa, &salen) != JIM_OK) {
1047 return JIM_ERR;
1050 else if (JimParseIpAddress(interp, hostportarg, &sa, &salen) != JIM_OK) {
1051 return JIM_ERR;
1053 sock = socket(family, (socktype == SOCK_DGRAM_SERVER) ? SOCK_DGRAM : SOCK_STREAM, 0);
1054 if (sock < 0) {
1055 JimAioSetError(interp, NULL);
1056 return JIM_ERR;
1059 /* Enable address reuse */
1060 setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
1062 res = bind(sock, &sa.sa, salen);
1063 if (res) {
1064 JimAioSetError(interp, argv[2]);
1065 close(sock);
1066 return JIM_ERR;
1068 if (socktype == SOCK_STREAM_SERVER) {
1069 res = listen(sock, 5);
1070 if (res) {
1071 JimAioSetError(interp, NULL);
1072 close(sock);
1073 return JIM_ERR;
1076 hdlfmt = "aio.socksrv%ld";
1078 break;
1080 case SOCK_UNIX:
1082 struct sockaddr_un sa;
1083 socklen_t len;
1085 if (argc != 3 || ipv6) {
1086 goto wrongargs;
1089 if (JimParseDomainAddress(interp, hostportarg, &sa) != JIM_OK) {
1090 JimAioSetError(interp, argv[2]);
1091 return JIM_ERR;
1093 family = PF_UNIX;
1094 sock = socket(PF_UNIX, SOCK_STREAM, 0);
1095 if (sock < 0) {
1096 JimAioSetError(interp, NULL);
1097 return JIM_ERR;
1099 len = strlen(sa.sun_path) + 1 + sizeof(sa.sun_family);
1100 res = connect(sock, (struct sockaddr *)&sa, len);
1101 if (res) {
1102 JimAioSetError(interp, argv[2]);
1103 close(sock);
1104 return JIM_ERR;
1106 hdlfmt = "aio.sockunix%ld";
1107 break;
1110 case SOCK_UNIX_SERVER:
1112 struct sockaddr_un sa;
1113 socklen_t len;
1115 if (argc != 3 || ipv6) {
1116 goto wrongargs;
1119 if (JimParseDomainAddress(interp, hostportarg, &sa) != JIM_OK) {
1120 JimAioSetError(interp, argv[2]);
1121 return JIM_ERR;
1123 family = PF_UNIX;
1124 sock = socket(PF_UNIX, SOCK_STREAM, 0);
1125 if (sock < 0) {
1126 JimAioSetError(interp, NULL);
1127 return JIM_ERR;
1129 len = strlen(sa.sun_path) + 1 + sizeof(sa.sun_family);
1130 res = bind(sock, (struct sockaddr *)&sa, len);
1131 if (res) {
1132 JimAioSetError(interp, argv[2]);
1133 close(sock);
1134 return JIM_ERR;
1136 res = listen(sock, 5);
1137 if (res) {
1138 JimAioSetError(interp, NULL);
1139 close(sock);
1140 return JIM_ERR;
1142 hdlfmt = "aio.sockunixsrv%ld";
1143 break;
1146 case SOCK_STREAM_PIPE:
1148 int p[2];
1150 if (argc != 2 || ipv6) {
1151 goto wrongargs;
1154 if (pipe(p) < 0) {
1155 JimAioSetError(interp, NULL);
1156 return JIM_ERR;
1159 hdlfmt = "aio.pipe%ld";
1160 if (JimMakeChannel(interp, argv[1], hdlfmt, p[0], family, "r") != JIM_OK) {
1161 close(p[0]);
1162 close(p[1]);
1163 JimAioSetError(interp, NULL);
1164 return JIM_ERR;
1166 /* Note, if this fails it will leave p[0] open, but this should never happen */
1167 mode = "w";
1168 sock = p[1];
1170 break;
1172 default:
1173 Jim_SetResultString(interp, "Unsupported socket type", -1);
1174 return JIM_ERR;
1177 return JimMakeChannel(interp, argv[1], hdlfmt, sock, family, mode);
1179 #endif
1181 FILE *Jim_AioFilehandle(Jim_Interp *interp, Jim_Obj *command)
1183 Jim_Cmd *cmdPtr = Jim_GetCommand(interp, command, JIM_ERRMSG);
1185 if (cmdPtr && cmdPtr->cmdProc == JimAioSubCmdProc) {
1186 return ((AioFile *) cmdPtr->privData)->fp;
1188 return NULL;
1191 #ifdef JIM_TCL_COMPAT
1192 static int JimAioTclCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1194 Jim_Obj *newargv[4];
1195 int ret;
1196 int i;
1197 int nonewline = 0;
1199 if (argc > 1 && Jim_CompareStringImmediate(interp, argv[1], "-nonewline")) {
1200 nonewline = 1;
1202 if (argc < 2 + nonewline || argc > 4) {
1203 Jim_WrongNumArgs(interp, 1, argv, "channel");
1204 return JIM_ERR;
1207 if (nonewline) {
1208 /* read -nonewline $f ... => $f read -nonewline ... */
1209 newargv[0] = argv[2];
1210 newargv[1] = argv[0];
1211 newargv[2] = argv[1];
1213 else {
1214 /* cmd $f ... => $f cmd ... */
1215 newargv[0] = argv[1];
1216 newargv[1] = argv[0];
1219 for (i = 2 + nonewline; i < argc; i++) {
1220 newargv[i] = argv[i];
1223 ret = Jim_EvalObjVector(interp, argc, newargv);
1225 return ret;
1228 static int JimAioPutsCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1230 Jim_Obj *newargv[4];
1231 int nonewline = 0;
1233 int off = 1;
1235 if (argc > 1 && Jim_CompareStringImmediate(interp, argv[1], "-nonewline")) {
1236 nonewline = 1;
1239 if (argc < 2 + nonewline || argc > 3 + nonewline) {
1240 Jim_WrongNumArgs(interp, 1, argv, "?-nonewline? ?channel? string");
1241 return JIM_ERR;
1244 /* "puts" */
1245 newargv[off++] = argv[0];
1247 if (nonewline) {
1248 newargv[off++] = argv[1];
1249 argv++;
1250 argc--;
1253 if (argc == 2) {
1254 /* Missing channel, so use stdout */
1255 newargv[0] = Jim_NewStringObj(interp, "stdout", -1);
1256 newargv[off++] = argv[1];
1258 else {
1259 newargv[0] = argv[1];
1260 newargv[off++] = argv[2];
1263 return Jim_EvalObjVector(interp, off, newargv);
1266 static void JimAioTclCompat(Jim_Interp *interp)
1268 static const char *tclcmds[] = { "read", "gets", "flush", "close", "eof", "seek", "tell", 0 };
1269 int i;
1271 for (i = 0; tclcmds[i]; i++) {
1272 Jim_CreateCommand(interp, tclcmds[i], JimAioTclCmd, NULL, NULL);
1274 Jim_CreateCommand(interp, "puts", JimAioPutsCmd, NULL, NULL);
1276 #endif
1278 int Jim_aioInit(Jim_Interp *interp)
1280 Jim_CreateCommand(interp, "open", JimAioOpenCommand, NULL, NULL);
1281 #ifndef JIM_ANSIC
1282 Jim_CreateCommand(interp, "socket", JimAioSockCommand, NULL, NULL);
1283 #endif
1285 /* Takeover stdin, stdout and stderr */
1286 Jim_EvalGlobal(interp, "open stdin; open stdout; open stderr");
1288 #ifdef JIM_TCL_COMPAT
1289 JimAioTclCompat(interp);
1290 #endif
1292 return JIM_OK;