aio recvfrom was not null terminating the result
[jimtcl.git] / jim-aio.c
blobeadac7585445220eb9d51b9246fc91fc78e79d52
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 128
64 #define AIO_BUF_LEN 1024
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)) {
316 /* I/O error */
317 Jim_FreeNewObj(interp, objPtr);
318 JimAioSetError(interp, af->filename);
319 return JIM_ERR;
321 if (nonewline) {
322 int len;
323 const char *s = Jim_GetString(objPtr, &len);
325 if (len > 0 && s[len - 1] == '\n') {
326 objPtr->length--;
327 objPtr->bytes[objPtr->length] = '\0';
330 Jim_SetResult(interp, objPtr);
331 return JIM_OK;
334 static int aio_cmd_gets(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
336 AioFile *af = Jim_CmdPrivData(interp);
337 char buf[AIO_BUF_LEN];
338 Jim_Obj *objPtr;
340 objPtr = Jim_NewStringObj(interp, NULL, 0);
341 while (1) {
342 int more = 0;
344 buf[AIO_BUF_LEN - 1] = '_';
345 if (fgets(buf, AIO_BUF_LEN, af->fp) == NULL)
346 break;
347 if (buf[AIO_BUF_LEN - 1] == '\0' && buf[AIO_BUF_LEN - 2] != '\n')
348 more = 1;
349 if (more) {
350 Jim_AppendString(interp, objPtr, buf, AIO_BUF_LEN - 1);
352 else {
353 int len = strlen(buf);
355 if (len) {
356 int hasnl = (buf[len - 1] == '\n');
358 /* strip "\n" */
359 Jim_AppendString(interp, objPtr, buf, strlen(buf) - hasnl);
362 if (!more)
363 break;
365 if (ferror(af->fp) && (errno != EAGAIN)) {
366 /* I/O error */
367 Jim_FreeNewObj(interp, objPtr);
368 JimAioSetError(interp, af->filename);
369 return JIM_ERR;
371 /* On EOF returns -1 if varName was specified, or the empty string. */
372 if (feof(af->fp) && Jim_Length(objPtr) == 0) {
373 Jim_FreeNewObj(interp, objPtr);
374 if (argc) {
375 Jim_SetResultInt(interp, -1);
377 return JIM_OK;
379 if (argc) {
380 int totLen;
382 Jim_GetString(objPtr, &totLen);
383 if (Jim_SetVariable(interp, argv[0], objPtr) != JIM_OK) {
384 Jim_FreeNewObj(interp, objPtr);
385 return JIM_ERR;
387 Jim_SetResultInt(interp, totLen);
389 else {
390 Jim_SetResult(interp, objPtr);
392 return JIM_OK;
395 static int aio_cmd_puts(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
397 AioFile *af = Jim_CmdPrivData(interp);
398 int wlen;
399 const char *wdata;
400 Jim_Obj *strObj;
402 if (argc == 2) {
403 if (!Jim_CompareStringImmediate(interp, argv[0], "-nonewline")) {
404 return -1;
406 strObj = argv[1];
408 else {
409 strObj = argv[0];
412 wdata = Jim_GetString(strObj, &wlen);
413 if (fwrite(wdata, 1, wlen, af->fp) == (unsigned)wlen) {
414 if (argc == 2 || putc('\n', af->fp) != EOF) {
415 return JIM_OK;
418 JimAioSetError(interp, af->filename);
419 return JIM_ERR;
422 #ifndef JIM_ANSIC
423 static int aio_cmd_recvfrom(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
425 AioFile *af = Jim_CmdPrivData(interp);
426 char *buf;
427 union sockaddr_any sa;
428 long len;
429 socklen_t salen = sizeof(sa);
430 int rlen;
432 if (Jim_GetLong(interp, argv[0], &len) != JIM_OK) {
433 return JIM_ERR;
436 buf = Jim_Alloc(len + 1);
438 rlen = recvfrom(fileno(af->fp), buf, len, 0, &sa.sa, &salen);
439 if (rlen < 0) {
440 perror("recvfrom");
441 Jim_Free(buf);
442 JimAioSetError(interp, NULL);
443 return JIM_ERR;
445 buf[rlen] = 0;
446 Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, buf, rlen));
448 if (argc > 1) {
449 char addrbuf[100];
451 #if IPV6
452 if (sa.sa.sa_family == AF_INET6) {
453 addrbuf[0] = '[';
454 /* Allow 9 for []:65535\0 */
455 inet_ntop(sa.sa.sa_family, &sa.sin6.sin6_addr, addrbuf + 1, sizeof(addrbuf) - 9);
456 snprintf(addrbuf + strlen(addrbuf), 8, "]:%d", ntohs(sa.sin.sin_port));
458 else
459 #endif
461 /* Allow 7 for :65535\0 */
462 inet_ntop(sa.sa.sa_family, &sa.sin.sin_addr, addrbuf, sizeof(addrbuf) - 7);
463 snprintf(addrbuf + strlen(addrbuf), 7, ":%d", ntohs(sa.sin.sin_port));
466 if (Jim_SetVariable(interp, argv[1], Jim_NewStringObj(interp, addrbuf, -1)) != JIM_OK) {
467 return JIM_ERR;
471 return JIM_OK;
475 static int aio_cmd_sendto(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
477 AioFile *af = Jim_CmdPrivData(interp);
478 int wlen;
479 int len;
480 const char *wdata;
481 union sockaddr_any sa;
482 const char *addr = Jim_GetString(argv[1], NULL);
483 int salen;
485 if (IPV6 && af->addr_family == AF_INET6) {
486 if (JimParseIPv6Address(interp, addr, &sa, &salen) != JIM_OK) {
487 return JIM_ERR;
490 else if (JimParseIpAddress(interp, addr, &sa, &salen) != JIM_OK) {
491 return JIM_ERR;
493 wdata = Jim_GetString(argv[0], &wlen);
495 /* Note that we don't validate the socket type. Rely on sendto() failing if appropriate */
496 len = sendto(fileno(af->fp), wdata, wlen, 0, &sa.sa, salen);
497 if (len < 0) {
498 perror("sendto");
499 JimAioSetError(interp, NULL);
500 return JIM_ERR;
502 Jim_SetResultInt(interp, len);
503 return JIM_OK;
506 static int aio_cmd_accept(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
508 AioFile *serv_af = Jim_CmdPrivData(interp);
509 int sock;
510 union sockaddr_any sa;
511 socklen_t addrlen = sizeof(sa);
512 AioFile *af;
513 char buf[AIO_CMD_LEN];
514 long fileId;
516 sock = accept(serv_af->fd, &sa.sa, &addrlen);
517 if (sock < 0)
518 return JIM_ERR;
520 /* Get the next file id */
521 fileId = Jim_GetId(interp);
523 /* Create the file command */
524 af = Jim_Alloc(sizeof(*af));
525 af->fd = sock;
526 af->filename = Jim_NewStringObj(interp, "accept", -1);
527 Jim_IncrRefCount(af->filename);
528 af->fp = fdopen(sock, "r+");
529 af->OpenFlags = 0;
530 #ifdef O_NDELAY
531 af->flags = fcntl(af->fd, F_GETFL);
532 #endif
533 af->rEvent = NULL;
534 af->wEvent = NULL;
535 af->eEvent = NULL;
536 af->addr_family = serv_af->addr_family;
537 sprintf(buf, "aio.sockstream%ld", fileId);
538 Jim_CreateCommand(interp, buf, JimAioSubCmdProc, af, JimAioDelProc);
539 Jim_SetResultString(interp, buf, -1);
540 return JIM_OK;
543 #endif
545 static int aio_cmd_flush(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
547 AioFile *af = Jim_CmdPrivData(interp);
549 if (fflush(af->fp) == EOF) {
550 JimAioSetError(interp, af->filename);
551 return JIM_ERR;
553 return JIM_OK;
556 static int aio_cmd_eof(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
558 AioFile *af = Jim_CmdPrivData(interp);
560 Jim_SetResultInt(interp, feof(af->fp));
561 return JIM_OK;
564 static int aio_cmd_close(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
566 Jim_DeleteCommand(interp, Jim_GetString(argv[0], NULL));
567 return JIM_OK;
570 static int aio_cmd_seek(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
572 AioFile *af = Jim_CmdPrivData(interp);
573 int orig = SEEK_SET;
574 long offset;
576 if (argc == 2) {
577 if (Jim_CompareStringImmediate(interp, argv[1], "start"))
578 orig = SEEK_SET;
579 else if (Jim_CompareStringImmediate(interp, argv[1], "current"))
580 orig = SEEK_CUR;
581 else if (Jim_CompareStringImmediate(interp, argv[1], "end"))
582 orig = SEEK_END;
583 else {
584 return -1;
587 if (Jim_GetLong(interp, argv[0], &offset) != JIM_OK) {
588 return JIM_ERR;
590 if (fseek(af->fp, offset, orig) == -1) {
591 JimAioSetError(interp, af->filename);
592 return JIM_ERR;
594 return JIM_OK;
597 static int aio_cmd_tell(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
599 AioFile *af = Jim_CmdPrivData(interp);
601 Jim_SetResultInt(interp, ftell(af->fp));
602 return JIM_OK;
605 #ifdef O_NDELAY
606 static int aio_cmd_ndelay(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
608 AioFile *af = Jim_CmdPrivData(interp);
610 int fmode = af->flags;
612 if (argc) {
613 long nb;
615 if (Jim_GetLong(interp, argv[0], &nb) != JIM_OK) {
616 return JIM_ERR;
618 if (nb) {
619 fmode |= O_NDELAY;
621 else {
622 fmode &= ~O_NDELAY;
624 fcntl(af->fd, F_SETFL, fmode);
625 af->flags = fmode;
627 Jim_SetResultInt(interp, (fmode & O_NONBLOCK) ? 1 : 0);
628 return JIM_OK;
630 #endif
632 #ifdef jim_ext_eventloop
633 static void JimAioFileEventFinalizer(Jim_Interp *interp, void *clientData)
635 Jim_Obj *objPtr = clientData;
637 Jim_DecrRefCount(interp, objPtr);
640 static int JimAioFileEventHandler(Jim_Interp *interp, void *clientData, int mask)
642 Jim_Obj *objPtr = clientData;
643 Jim_Obj *scrPtr = NULL;
645 if (mask == (JIM_EVENT_READABLE | JIM_EVENT_FEOF)) {
646 Jim_ListIndex(interp, objPtr, 1, &scrPtr, 0);
648 else {
649 Jim_ListIndex(interp, objPtr, 0, &scrPtr, 0);
651 Jim_EvalObjBackground(interp, scrPtr);
652 return 0;
655 static int aio_eventinfo(Jim_Interp *interp, AioFile * af, unsigned mask, Jim_Obj **scriptListObj,
656 Jim_Obj *script1, Jim_Obj *script2)
658 int scriptlen = 0;
660 if (script1 == NULL) {
661 /* Return current script */
662 if (*scriptListObj) {
663 Jim_SetResult(interp, *scriptListObj);
665 return JIM_OK;
668 if (*scriptListObj) {
669 /* Delete old handler */
670 Jim_DeleteFileHandler(interp, af->fp);
671 *scriptListObj = NULL;
674 /* Now possibly add the new script(s) */
675 Jim_GetString(script1, &scriptlen);
676 if (scriptlen == 0) {
677 /* Empty script, so done */
678 return JIM_OK;
681 /* A new script to add */
682 *scriptListObj = Jim_NewListObj(interp, NULL, 0);
683 Jim_IncrRefCount(*scriptListObj);
685 if (Jim_IsShared(script1)) {
686 script1 = Jim_DuplicateObj(interp, script1);
688 Jim_ListAppendElement(interp, *scriptListObj, script1);
690 if (script2) {
691 if (Jim_IsShared(script2)) {
692 script2 = Jim_DuplicateObj(interp, script2);
694 Jim_ListAppendElement(interp, *scriptListObj, script2);
697 Jim_CreateFileHandler(interp, af->fp, mask,
698 JimAioFileEventHandler, *scriptListObj, JimAioFileEventFinalizer);
700 return JIM_OK;
703 static int aio_cmd_readable(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
705 AioFile *af = Jim_CmdPrivData(interp);
706 Jim_Obj *eofScript = NULL;
707 int mask = JIM_EVENT_READABLE;
710 if (argc == 2) {
711 mask |= JIM_EVENT_FEOF;
712 eofScript = argv[1];
715 return aio_eventinfo(interp, af, mask, &af->rEvent, argc ? argv[0] : NULL, eofScript);
718 static int aio_cmd_writable(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
720 AioFile *af = Jim_CmdPrivData(interp);
721 int mask = JIM_EVENT_WRITABLE;
723 return aio_eventinfo(interp, af, mask, &af->wEvent, argc ? argv[0] : NULL, NULL);
726 static int aio_cmd_onexception(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
728 AioFile *af = Jim_CmdPrivData(interp);
729 int mask = JIM_EVENT_EXCEPTION;
731 return aio_eventinfo(interp, af, mask, &af->eEvent, argc ? argv[0] : NULL, NULL);
733 #endif
735 static const jim_subcmd_type aio_command_table[] = {
736 { .cmd = "read",
737 .args = "?-nonewline? ?len?",
738 .function = aio_cmd_read,
739 .minargs = 0,
740 .maxargs = 2,
741 .description = "Read and return bytes from the stream. To eof if no len."
743 { .cmd = "gets",
744 .args = "?var?",
745 .function = aio_cmd_gets,
746 .minargs = 0,
747 .maxargs = 1,
748 .description = "Read one line and return it or store it in the var"
750 { .cmd = "puts",
751 .args = "?-nonewline? str",
752 .function = aio_cmd_puts,
753 .minargs = 1,
754 .maxargs = 2,
755 .description = "Write the string, with newline unless -nonewline"
757 #ifndef JIM_ANSIC
758 { .cmd = "recvfrom",
759 .args = "len ?addrvar?",
760 .function = aio_cmd_recvfrom,
761 .minargs = 1,
762 .maxargs = 2,
763 .description = "Receive up to 'len' bytes on the socket. Sets 'addrvar' with receive address, if set"
765 { .cmd = "sendto",
766 .args = "str address",
767 .function = aio_cmd_sendto,
768 .minargs = 2,
769 .maxargs = 2,
770 .description = "Send 'str' to the given address (dgram only)"
772 { .cmd = "accept",
773 .function = aio_cmd_accept,
774 .description = "Server socket only: Accept a connection and return stream"
776 #endif
777 { .cmd = "flush",
778 .function = aio_cmd_flush,
779 .description = "Flush the stream"
781 { .cmd = "eof",
782 .function = aio_cmd_eof,
783 .description = "Returns 1 if stream is at eof"
785 { .cmd = "close",
786 .flags = JIM_MODFLAG_FULLARGV,
787 .function = aio_cmd_close,
788 .description = "Closes the stream"
790 { .cmd = "seek",
791 .args = "offset ?start|current|end",
792 .function = aio_cmd_seek,
793 .minargs = 1,
794 .maxargs = 2,
795 .description = "Seeks in the stream (default 'current')"
797 { .cmd = "tell",
798 .function = aio_cmd_tell,
799 .description = "Returns the current seek position"
801 #ifdef O_NDELAY
802 { .cmd = "ndelay",
803 .args = "?0|1?",
804 .function = aio_cmd_ndelay,
805 .minargs = 0,
806 .maxargs = 1,
807 .description = "Set O_NDELAY (if arg). Returns current/new setting."
809 #endif
810 #ifdef jim_ext_eventloop
811 { .cmd = "readable",
812 .args = "?readable-script ?eof-script??",
813 .minargs = 0,
814 .maxargs = 2,
815 .function = aio_cmd_readable,
816 .description = "Returns script, or invoke readable-script when readable, eof-script on eof, {} to remove",
818 { .cmd = "writable",
819 .args = "?writable-script?",
820 .minargs = 0,
821 .maxargs = 1,
822 .function = aio_cmd_writable,
823 .description = "Returns script, or invoke writable-script when writable, {} to remove",
825 { .cmd = "onexception",
826 .args = "?exception-script?",
827 .minargs = 0,
828 .maxargs = 1,
829 .function = aio_cmd_onexception,
830 .description = "Returns script, or invoke exception-script when oob data, {} to remove",
832 #endif
833 { 0 }
836 static int JimAioSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
838 return Jim_CallSubCmd(interp, Jim_ParseSubCmd(interp, aio_command_table, argc, argv), argc, argv);
841 static int JimAioOpenCommand(Jim_Interp *interp, int argc,
842 Jim_Obj *const *argv)
844 FILE *fp;
845 AioFile *af;
846 char buf[AIO_CMD_LEN];
847 long fileId;
848 int OpenFlags = 0;
849 const char *cmdname;
851 if (argc != 2 && argc != 3) {
852 Jim_WrongNumArgs(interp, 1, argv, "filename ?mode?");
853 return JIM_ERR;
855 cmdname = Jim_GetString(argv[1], NULL);
856 if (Jim_CompareStringImmediate(interp, argv[1], "stdin")) {
857 OpenFlags |= AIO_KEEPOPEN;
858 fp = stdin;
860 else if (Jim_CompareStringImmediate(interp, argv[1], "stdout")) {
861 OpenFlags |= AIO_KEEPOPEN;
862 fp = stdout;
864 else if (Jim_CompareStringImmediate(interp, argv[1], "stderr")) {
865 OpenFlags |= AIO_KEEPOPEN;
866 fp = stderr;
868 else {
869 const char *mode = "r";
871 if (argc == 3) {
872 mode = Jim_GetString(argv[2], NULL);
874 fp = fopen(Jim_GetString(argv[1], NULL), mode);
875 if (fp == NULL) {
876 JimAioSetError(interp, argv[1]);
877 return JIM_ERR;
879 /* Get the next file id */
880 fileId = Jim_GetId(interp);
881 sprintf(buf, "aio.handle%ld", fileId);
882 cmdname = buf;
885 /* Create the file command */
886 af = Jim_Alloc(sizeof(*af));
887 af->fp = fp;
888 af->fd = fileno(fp);
889 #ifdef O_NDELAY
890 af->flags = fcntl(af->fd, F_GETFL);
891 #endif
892 af->filename = argv[1];
893 Jim_IncrRefCount(af->filename);
894 af->OpenFlags = OpenFlags;
895 af->rEvent = NULL;
896 af->wEvent = NULL;
897 af->eEvent = NULL;
898 Jim_CreateCommand(interp, cmdname, JimAioSubCmdProc, af, JimAioDelProc);
899 Jim_SetResultString(interp, cmdname, -1);
900 return JIM_OK;
903 #ifndef JIM_ANSIC
906 * Creates a channel for fd.
908 * hdlfmt is a sprintf format for the filehandle. Anything with %ld at the end will do.
909 * mode is usual "r+", but may be another fdopen() mode as required.
911 * Creates the command and lappends the name of the command to the current result.
914 static int JimMakeChannel(Jim_Interp *interp, Jim_Obj *filename, const char *hdlfmt, int fd, int family,
915 const char *mode)
917 long fileId;
918 AioFile *af;
919 char buf[AIO_CMD_LEN];
921 FILE *fp = fdopen(fd, mode);
923 if (fp == NULL) {
924 close(fd);
925 JimAioSetError(interp, NULL);
926 return JIM_ERR;
929 /* Get the next file id */
930 fileId = Jim_GetId(interp);
932 /* Create the file command */
933 af = Jim_Alloc(sizeof(*af));
934 af->fp = fp;
935 af->fd = fd;
936 af->OpenFlags = 0;
937 af->filename = filename;
938 Jim_IncrRefCount(af->filename);
939 #ifdef O_NDELAY
940 af->flags = fcntl(af->fd, F_GETFL);
941 #endif
942 af->rEvent = NULL;
943 af->wEvent = NULL;
944 af->eEvent = NULL;
945 af->addr_family = family;
946 sprintf(buf, hdlfmt, fileId);
947 Jim_CreateCommand(interp, buf, JimAioSubCmdProc, af, JimAioDelProc);
949 Jim_ListAppendElement(interp, Jim_GetResult(interp), Jim_NewStringObj(interp, buf, -1));
951 return JIM_OK;
954 static int JimAioSockCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
956 const char *hdlfmt = "aio.unknown%ld";
957 const char *socktypes[] = {
958 "unix",
959 "unix.server",
960 "dgram",
961 "dgram.server",
962 "stream",
963 "stream.server",
964 "pipe",
965 NULL
967 enum
969 SOCK_UNIX,
970 SOCK_UNIX_SERVER,
971 SOCK_DGRAM_CLIENT,
972 SOCK_DGRAM_SERVER,
973 SOCK_STREAM_CLIENT,
974 SOCK_STREAM_SERVER,
975 SOCK_STREAM_PIPE,
976 SOCK_DGRAM6_CLIENT,
977 SOCK_DGRAM6_SERVER,
978 SOCK_STREAM6_CLIENT,
979 SOCK_STREAM6_SERVER,
981 int socktype;
982 int sock;
983 const char *hostportarg = NULL;
984 int res;
985 int on = 1;
986 const char *mode = "r+";
987 int family = AF_INET;
988 Jim_Obj *argv0 = argv[0];
989 int ipv6 = 0;
991 if (argc > 1 && Jim_CompareStringImmediate(interp, argv[1], "-ipv6")) {
992 if (!IPV6) {
993 Jim_SetResultString(interp, "ipv6 not supported", -1);
994 return JIM_ERR;
996 ipv6 = 1;
997 family = AF_INET6;
999 argc -= ipv6;
1000 argv += ipv6;
1002 if (argc < 2) {
1003 wrongargs:
1004 Jim_WrongNumArgs(interp, 1, &argv0, "?-ipv6? type ?address?");
1005 return JIM_ERR;
1008 if (Jim_GetEnum(interp, argv[1], socktypes, &socktype, "socket type", JIM_ERRMSG) != JIM_OK)
1009 return JIM_ERR;
1011 Jim_SetResultString(interp, "", 0);
1013 hdlfmt = "aio.sock%ld";
1015 switch (socktype) {
1016 case SOCK_DGRAM_CLIENT:
1017 if (argc == 2) {
1018 /* No address, so an unconnected dgram socket */
1019 sock = socket(family, SOCK_DGRAM, 0);
1020 if (sock < 0) {
1021 JimAioSetError(interp, NULL);
1022 return JIM_ERR;
1024 break;
1026 /* fall through */
1027 case SOCK_STREAM_CLIENT:
1029 union sockaddr_any sa;
1030 int salen;
1032 if (argc != 3) {
1033 goto wrongargs;
1036 hostportarg = Jim_GetString(argv[2], NULL);
1038 if (ipv6) {
1039 if (JimParseIPv6Address(interp, hostportarg, &sa, &salen) != JIM_OK) {
1040 return JIM_ERR;
1043 else if (JimParseIpAddress(interp, hostportarg, &sa, &salen) != JIM_OK) {
1044 return JIM_ERR;
1046 sock = socket(family, (socktype == SOCK_DGRAM_CLIENT) ? SOCK_DGRAM : SOCK_STREAM, 0);
1047 if (sock < 0) {
1048 JimAioSetError(interp, NULL);
1049 return JIM_ERR;
1051 res = connect(sock, &sa.sa, salen);
1052 if (res) {
1053 perror("connect");
1054 JimAioSetError(interp, argv[2]);
1055 close(sock);
1056 return JIM_ERR;
1059 break;
1061 case SOCK_STREAM_SERVER:
1062 case SOCK_DGRAM_SERVER:
1064 union sockaddr_any sa;
1065 int salen;
1067 if (argc != 3) {
1068 goto wrongargs;
1071 hostportarg = Jim_GetString(argv[2], NULL);
1073 if (ipv6) {
1074 if (JimParseIPv6Address(interp, hostportarg, &sa, &salen) != JIM_OK) {
1075 return JIM_ERR;
1078 else if (JimParseIpAddress(interp, hostportarg, &sa, &salen) != JIM_OK) {
1079 return JIM_ERR;
1081 sock = socket(family, (socktype == SOCK_DGRAM_SERVER) ? SOCK_DGRAM : SOCK_STREAM, 0);
1082 if (sock < 0) {
1083 JimAioSetError(interp, NULL);
1084 return JIM_ERR;
1087 /* Enable address reuse */
1088 setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
1090 res = bind(sock, &sa.sa, salen);
1091 if (res) {
1092 JimAioSetError(interp, argv[2]);
1093 close(sock);
1094 return JIM_ERR;
1096 if (socktype == SOCK_STREAM_SERVER) {
1097 res = listen(sock, 5);
1098 if (res) {
1099 JimAioSetError(interp, NULL);
1100 close(sock);
1101 return JIM_ERR;
1104 hdlfmt = "aio.socksrv%ld";
1106 break;
1108 case SOCK_UNIX:
1110 struct sockaddr_un sa;
1111 socklen_t len;
1113 if (argc != 3 || ipv6) {
1114 goto wrongargs;
1117 if (JimParseDomainAddress(interp, hostportarg, &sa) != JIM_OK) {
1118 JimAioSetError(interp, argv[2]);
1119 return JIM_ERR;
1121 family = PF_UNIX;
1122 sock = socket(PF_UNIX, SOCK_STREAM, 0);
1123 if (sock < 0) {
1124 JimAioSetError(interp, NULL);
1125 return JIM_ERR;
1127 len = strlen(sa.sun_path) + 1 + sizeof(sa.sun_family);
1128 res = connect(sock, (struct sockaddr *)&sa, len);
1129 if (res) {
1130 JimAioSetError(interp, argv[2]);
1131 close(sock);
1132 return JIM_ERR;
1134 hdlfmt = "aio.sockunix%ld";
1135 break;
1138 case SOCK_UNIX_SERVER:
1140 struct sockaddr_un sa;
1141 socklen_t len;
1143 if (argc != 3 || ipv6) {
1144 goto wrongargs;
1147 if (JimParseDomainAddress(interp, hostportarg, &sa) != JIM_OK) {
1148 JimAioSetError(interp, argv[2]);
1149 return JIM_ERR;
1151 family = PF_UNIX;
1152 sock = socket(PF_UNIX, SOCK_STREAM, 0);
1153 if (sock < 0) {
1154 JimAioSetError(interp, NULL);
1155 return JIM_ERR;
1157 len = strlen(sa.sun_path) + 1 + sizeof(sa.sun_family);
1158 res = bind(sock, (struct sockaddr *)&sa, len);
1159 if (res) {
1160 JimAioSetError(interp, argv[2]);
1161 close(sock);
1162 return JIM_ERR;
1164 res = listen(sock, 5);
1165 if (res) {
1166 JimAioSetError(interp, NULL);
1167 close(sock);
1168 return JIM_ERR;
1170 hdlfmt = "aio.sockunixsrv%ld";
1171 break;
1174 case SOCK_STREAM_PIPE:
1176 int p[2];
1178 if (argc != 2 || ipv6) {
1179 goto wrongargs;
1182 if (pipe(p) < 0) {
1183 JimAioSetError(interp, NULL);
1184 return JIM_ERR;
1187 hdlfmt = "aio.pipe%ld";
1188 if (JimMakeChannel(interp, argv[1], hdlfmt, p[0], family, "r") != JIM_OK) {
1189 close(p[0]);
1190 close(p[1]);
1191 JimAioSetError(interp, NULL);
1192 return JIM_ERR;
1194 /* Note, if this fails it will leave p[0] open, but this should never happen */
1195 mode = "w";
1196 sock = p[1];
1198 break;
1200 default:
1201 Jim_SetResultString(interp, "Unsupported socket type", -1);
1202 return JIM_ERR;
1205 return JimMakeChannel(interp, argv[1], hdlfmt, sock, family, mode);
1207 #endif
1209 FILE *Jim_AioFilehandle(Jim_Interp *interp, Jim_Obj *command)
1211 Jim_Cmd *cmdPtr = Jim_GetCommand(interp, command, JIM_ERRMSG);
1213 if (cmdPtr && cmdPtr->cmdProc == JimAioSubCmdProc) {
1214 return ((AioFile *) cmdPtr->privData)->fp;
1216 return NULL;
1219 #ifdef JIM_TCL_COMPAT
1220 static int JimAioTclCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1222 Jim_Obj *newargv[4];
1223 int ret;
1224 int i;
1225 int nonewline = 0;
1227 if (argc > 1 && Jim_CompareStringImmediate(interp, argv[1], "-nonewline")) {
1228 nonewline = 1;
1230 if (argc < 2 + nonewline || argc > 4) {
1231 Jim_WrongNumArgs(interp, 1, argv, "channel");
1232 return JIM_ERR;
1235 if (nonewline) {
1236 /* read -nonewline $f ... => $f read -nonewline ... */
1237 newargv[0] = argv[2];
1238 newargv[1] = argv[0];
1239 newargv[2] = argv[1];
1241 else {
1242 /* cmd $f ... => $f cmd ... */
1243 newargv[0] = argv[1];
1244 newargv[1] = argv[0];
1247 for (i = 2 + nonewline; i < argc; i++) {
1248 newargv[i] = argv[i];
1251 ret = Jim_EvalObjVector(interp, argc, newargv);
1253 return ret;
1256 static int JimAioPutsCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1258 Jim_Obj *newargv[4];
1259 int nonewline = 0;
1261 int off = 1;
1263 if (argc > 1 && Jim_CompareStringImmediate(interp, argv[1], "-nonewline")) {
1264 nonewline = 1;
1267 if (argc < 2 + nonewline || argc > 3 + nonewline) {
1268 Jim_WrongNumArgs(interp, 1, argv, "?-nonewline? ?channel? string");
1269 return JIM_ERR;
1272 /* "puts" */
1273 newargv[off++] = argv[0];
1275 if (nonewline) {
1276 newargv[off++] = argv[1];
1277 argv++;
1278 argc--;
1281 if (argc == 2) {
1282 /* Missing channel, so use stdout */
1283 newargv[0] = Jim_NewStringObj(interp, "stdout", -1);
1284 newargv[off++] = argv[1];
1286 else {
1287 newargv[0] = argv[1];
1288 newargv[off++] = argv[2];
1291 return Jim_EvalObjVector(interp, off, newargv);
1294 static void JimAioTclCompat(Jim_Interp *interp)
1296 static const char *tclcmds[] = { "read", "gets", "flush", "close", "eof", "seek", "tell", 0 };
1297 int i;
1299 for (i = 0; tclcmds[i]; i++) {
1300 Jim_CreateCommand(interp, tclcmds[i], JimAioTclCmd, NULL, NULL);
1302 Jim_CreateCommand(interp, "puts", JimAioPutsCmd, NULL, NULL);
1304 #endif
1306 int Jim_aioInit(Jim_Interp *interp)
1308 Jim_CreateCommand(interp, "open", JimAioOpenCommand, NULL, NULL);
1309 #ifndef JIM_ANSIC
1310 Jim_CreateCommand(interp, "socket", JimAioSockCommand, NULL, NULL);
1311 #endif
1313 /* Takeover stdin, stdout and stderr */
1314 Jim_EvalGlobal(interp, "open stdin; open stdout; open stderr");
1316 #ifdef JIM_TCL_COMPAT
1317 JimAioTclCompat(interp);
1318 #endif
1320 return JIM_OK;