Fix a couple of minor build issues
[jimtcl.git] / jim-aio.c
blobec3e99b74a169e36193c255330b2984bb6e646ce
2 /* Jim - A small embeddable Tcl interpreter
4 * Copyright 2005 Salvatore Sanfilippo <antirez@invece.org>
5 * Copyright 2005 Clemens Hintze <c.hintze@gmx.net>
6 * Copyright 2005 patthoyts - Pat Thoyts <patthoyts@users.sf.net>
7 * Copyright 2008 oharboe - Øyvind Harboe - oyvind.harboe@zylin.com
8 * Copyright 2008 Andrew Lunn <andrew@lunn.ch>
9 * Copyright 2008 Duane Ellis <openocd@duaneellis.com>
10 * Copyright 2008 Uwe Klein <uklein@klein-messgeraete.de>
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
16 * 1. Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
18 * 2. Redistributions in binary form must reproduce the above
19 * copyright notice, this list of conditions and the following
20 * disclaimer in the documentation and/or other materials
21 * provided with the distribution.
23 * THIS SOFTWARE IS PROVIDED BY THE JIM TCL PROJECT ``AS IS'' AND ANY
24 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
25 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
26 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
27 * JIM TCL PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
28 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
29 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
32 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
34 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 * The views and conclusions contained in the software and documentation
37 * are those of the authors and should not be interpreted as representing
38 * official policies, either expressed or implied, of the Jim Tcl Project.
39 **/
41 #include "jimautoconf.h"
43 #include <stdio.h>
44 #include <string.h>
45 #include <errno.h>
46 #include <fcntl.h>
47 #ifdef HAVE_UNISTD_H
48 #include <unistd.h>
49 #endif
51 #include "jim.h"
53 #if defined(HAVE_SYS_SOCKET_H) && defined(HAVE_SELECT) && defined(HAVE_NETINET_IN_H) && defined(HAVE_NETDB_H) && defined(HAVE_ARPA_INET_H)
54 #include <sys/socket.h>
55 #include <netinet/in.h>
56 #include <arpa/inet.h>
57 #include <netdb.h>
58 #include <unistd.h>
59 #ifdef HAVE_SYS_UN_H
60 #include <sys/un.h>
61 #endif
62 #else
63 #define JIM_ANSIC
64 #endif
66 #include "jim-eventloop.h"
67 #include "jim-subcmd.h"
69 #define AIO_CMD_LEN 32 /* e.g. aio.handleXXXXXX */
70 #define AIO_BUF_LEN 256 /* Can keep this small and rely on stdio buffering */
72 #ifndef HAVE_FTELLO
73 #define ftello ftell
74 #endif
75 #ifndef HAVE_FSEEKO
76 #define fseeko fseek
77 #endif
79 #define AIO_KEEPOPEN 1
81 #if defined(JIM_IPV6)
82 #define IPV6 1
83 #else
84 #define IPV6 0
85 #ifndef PF_INET6
86 #define PF_INET6 0
87 #endif
88 #endif
90 #if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)
91 union sockaddr_any {
92 struct sockaddr sa;
93 struct sockaddr_in sin;
94 #if IPV6
95 struct sockaddr_in6 sin6;
96 #endif
99 #ifndef HAVE_INET_NTOP
100 const char *inet_ntop(int af, const void *src, char *dst, int size)
102 if (af != PF_INET) {
103 return NULL;
105 snprintf(dst, size, "%s", inet_ntoa(((struct sockaddr_in *)src)->sin_addr));
106 return dst;
108 #endif
109 #endif /* JIM_BOOTSTRAP */
111 typedef struct AioFile
113 FILE *fp;
114 Jim_Obj *filename;
115 int type;
116 int OpenFlags; /* AIO_KEEPOPEN? keep FILE* */
117 int fd;
118 #ifdef O_NDELAY
119 int flags;
120 #endif
121 Jim_Obj *rEvent;
122 Jim_Obj *wEvent;
123 Jim_Obj *eEvent;
124 int addr_family;
125 } AioFile;
127 static int JimAioSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv);
128 static int JimMakeChannel(Jim_Interp *interp, FILE *fh, int fd, Jim_Obj *filename,
129 const char *hdlfmt, int family, const char *mode);
131 #if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)
132 static int JimParseIPv6Address(Jim_Interp *interp, const char *hostport, union sockaddr_any *sa, int *salen)
134 #if IPV6
136 * An IPv6 addr/port looks like:
137 * [::1]
138 * [::1]:2000
139 * [fe80::223:6cff:fe95:bdc0%en1]:2000
140 * [::]:2000
141 * 2000
143 * Note that the "any" address is ::, which is the same as when no address is specified.
145 char *sthost = NULL;
146 const char *stport;
147 int ret = JIM_OK;
148 struct addrinfo req;
149 struct addrinfo *ai;
151 stport = strrchr(hostport, ':');
152 if (!stport) {
153 /* No : so, the whole thing is the port */
154 stport = hostport;
155 hostport = "::";
156 sthost = Jim_StrDup(hostport);
158 else {
159 stport++;
162 if (*hostport == '[') {
163 /* This is a numeric ipv6 address */
164 char *pt = strchr(++hostport, ']');
165 if (pt) {
166 sthost = Jim_StrDupLen(hostport, pt - hostport);
170 if (!sthost) {
171 sthost = Jim_StrDupLen(hostport, stport - hostport - 1);
174 memset(&req, '\0', sizeof(req));
175 req.ai_family = PF_INET6;
177 if (getaddrinfo(sthost, NULL, &req, &ai)) {
178 Jim_SetResultFormatted(interp, "Not a valid address: %s", hostport);
179 ret = JIM_ERR;
181 else {
182 memcpy(&sa->sin, ai->ai_addr, ai->ai_addrlen);
183 *salen = ai->ai_addrlen;
185 sa->sin.sin_port = htons(atoi(stport));
187 freeaddrinfo(ai);
189 Jim_Free(sthost);
191 return ret;
192 #else
193 Jim_SetResultString(interp, "ipv6 not supported", -1);
194 return JIM_ERR;
195 #endif
198 static int JimParseIpAddress(Jim_Interp *interp, const char *hostport, union sockaddr_any *sa, int *salen)
200 /* An IPv4 addr/port looks like:
201 * 192.168.1.5
202 * 192.168.1.5:2000
203 * 2000
205 * If the address is missing, INADDR_ANY is used.
206 * If the port is missing, 0 is used (only useful for server sockets).
208 char *sthost = NULL;
209 const char *stport;
210 int ret = JIM_OK;
212 stport = strrchr(hostport, ':');
213 if (!stport) {
214 /* No : so, the whole thing is the port */
215 stport = hostport;
216 sthost = Jim_StrDup("0.0.0.0");
218 else {
219 sthost = Jim_StrDupLen(hostport, stport - hostport);
220 stport++;
224 #ifdef HAVE_GETADDRINFO
225 struct addrinfo req;
226 struct addrinfo *ai;
227 memset(&req, '\0', sizeof(req));
228 req.ai_family = PF_INET;
230 if (getaddrinfo(sthost, NULL, &req, &ai)) {
231 ret = JIM_ERR;
233 else {
234 memcpy(&sa->sin, ai->ai_addr, ai->ai_addrlen);
235 *salen = ai->ai_addrlen;
236 freeaddrinfo(ai);
238 #else
239 struct hostent *he;
241 ret = JIM_ERR;
243 if ((he = gethostbyname(sthost)) != NULL) {
244 if (he->h_length == sizeof(sa->sin.sin_addr)) {
245 *salen = sizeof(sa->sin);
246 sa->sin.sin_family= he->h_addrtype;
247 memcpy(&sa->sin.sin_addr, he->h_addr, he->h_length); /* set address */
248 ret = JIM_OK;
251 #endif
253 sa->sin.sin_port = htons(atoi(stport));
255 Jim_Free(sthost);
257 if (ret != JIM_OK) {
258 Jim_SetResultFormatted(interp, "Not a valid address: %s", hostport);
261 return ret;
264 #ifdef HAVE_SYS_UN_H
265 static int JimParseDomainAddress(Jim_Interp *interp, const char *path, struct sockaddr_un *sa)
267 sa->sun_family = PF_UNIX;
268 snprintf(sa->sun_path, sizeof(sa->sun_path), "%s", path);
270 return JIM_OK;
272 #endif
273 #endif /* JIM_BOOTSTRAP */
275 static void JimAioSetError(Jim_Interp *interp, Jim_Obj *name)
277 if (name) {
278 Jim_SetResultFormatted(interp, "%#s: %s", name, strerror(errno));
280 else {
281 Jim_SetResultString(interp, strerror(errno), -1);
285 static void JimAioDelProc(Jim_Interp *interp, void *privData)
287 AioFile *af = privData;
289 JIM_NOTUSED(interp);
291 if (!(af->OpenFlags & AIO_KEEPOPEN)) {
292 fclose(af->fp);
295 Jim_DecrRefCount(interp, af->filename);
297 #ifdef jim_ext_eventloop
298 /* remove all existing EventHandlers */
299 Jim_DeleteFileHandler(interp, af->fp, JIM_EVENT_READABLE | JIM_EVENT_WRITABLE | JIM_EVENT_EXCEPTION);
300 #endif
301 Jim_Free(af);
304 static int JimCheckStreamError(Jim_Interp *interp, AioFile *af)
306 if (!ferror(af->fp)) {
307 return JIM_OK;
309 clearerr(af->fp);
310 /* EAGAIN and similar are not error conditions. Just treat them like eof */
311 if (feof(af->fp) || errno == EAGAIN || errno == EINTR) {
312 return JIM_OK;
314 #ifdef ECONNRESET
315 if (errno == ECONNRESET) {
316 return JIM_OK;
318 #endif
319 #ifdef ECONNABORTED
320 if (errno != ECONNABORTED) {
321 return JIM_OK;
323 #endif
324 JimAioSetError(interp, af->filename);
325 return JIM_ERR;
328 static int aio_cmd_read(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
330 AioFile *af = Jim_CmdPrivData(interp);
331 char buf[AIO_BUF_LEN];
332 Jim_Obj *objPtr;
333 int nonewline = 0;
334 jim_wide neededLen = -1; /* -1 is "read as much as possible" */
336 if (argc && Jim_CompareStringImmediate(interp, argv[0], "-nonewline")) {
337 nonewline = 1;
338 argv++;
339 argc--;
341 if (argc == 1) {
342 if (Jim_GetWide(interp, argv[0], &neededLen) != JIM_OK)
343 return JIM_ERR;
344 if (neededLen < 0) {
345 Jim_SetResultString(interp, "invalid parameter: negative len", -1);
346 return JIM_ERR;
349 else if (argc) {
350 return -1;
352 objPtr = Jim_NewStringObj(interp, NULL, 0);
353 while (neededLen != 0) {
354 int retval;
355 int readlen;
357 if (neededLen == -1) {
358 readlen = AIO_BUF_LEN;
360 else {
361 readlen = (neededLen > AIO_BUF_LEN ? AIO_BUF_LEN : neededLen);
363 retval = fread(buf, 1, readlen, af->fp);
364 if (retval > 0) {
365 Jim_AppendString(interp, objPtr, buf, retval);
366 if (neededLen != -1) {
367 neededLen -= retval;
370 if (retval != readlen)
371 break;
373 /* Check for error conditions */
374 if (JimCheckStreamError(interp, af)) {
375 Jim_FreeNewObj(interp, objPtr);
376 return JIM_ERR;
378 if (nonewline) {
379 int len;
380 const char *s = Jim_GetString(objPtr, &len);
382 if (len > 0 && s[len - 1] == '\n') {
383 objPtr->length--;
384 objPtr->bytes[objPtr->length] = '\0';
387 Jim_SetResult(interp, objPtr);
388 return JIM_OK;
391 static int aio_cmd_copy(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
393 AioFile *af = Jim_CmdPrivData(interp);
394 jim_wide count = 0;
395 jim_wide maxlen = JIM_WIDE_MAX;
396 FILE *outfh = Jim_AioFilehandle(interp, argv[0]);
398 if (outfh == NULL) {
399 return JIM_ERR;
402 if (argc == 2) {
403 if (Jim_GetWide(interp, argv[1], &maxlen) != JIM_OK) {
404 return JIM_ERR;
408 while (count < maxlen) {
409 int ch = fgetc(af->fp);
411 if (ch == EOF || fputc(ch, outfh) == EOF) {
412 break;
414 count++;
417 if (ferror(af->fp)) {
418 Jim_SetResultFormatted(interp, "error while reading: %s", strerror(errno));
419 clearerr(af->fp);
420 return JIM_ERR;
423 if (ferror(outfh)) {
424 Jim_SetResultFormatted(interp, "error while writing: %s", strerror(errno));
425 clearerr(outfh);
426 return JIM_ERR;
429 Jim_SetResultInt(interp, count);
431 return JIM_OK;
434 static int aio_cmd_gets(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
436 AioFile *af = Jim_CmdPrivData(interp);
437 char buf[AIO_BUF_LEN];
438 Jim_Obj *objPtr;
439 int len;
441 errno = 0;
443 objPtr = Jim_NewStringObj(interp, NULL, 0);
444 while (1) {
445 buf[AIO_BUF_LEN - 1] = '_';
446 if (fgets(buf, AIO_BUF_LEN, af->fp) == NULL)
447 break;
449 if (buf[AIO_BUF_LEN - 1] == '\0' && buf[AIO_BUF_LEN - 2] != '\n') {
450 Jim_AppendString(interp, objPtr, buf, AIO_BUF_LEN - 1);
452 else {
453 len = strlen(buf);
455 if (len && (buf[len - 1] == '\n')) {
456 /* strip "\n" */
457 len--;
460 Jim_AppendString(interp, objPtr, buf, len);
461 break;
464 if (JimCheckStreamError(interp, af)) {
465 /* I/O error */
466 Jim_FreeNewObj(interp, objPtr);
467 return JIM_ERR;
470 if (argc) {
471 if (Jim_SetVariable(interp, argv[0], objPtr) != JIM_OK) {
472 Jim_FreeNewObj(interp, objPtr);
473 return JIM_ERR;
476 len = Jim_Length(objPtr);
478 if (len == 0 && feof(af->fp)) {
479 /* On EOF returns -1 if varName was specified */
480 len = -1;
482 Jim_SetResultInt(interp, len);
484 else {
485 Jim_SetResult(interp, objPtr);
487 return JIM_OK;
490 static int aio_cmd_puts(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
492 AioFile *af = Jim_CmdPrivData(interp);
493 int wlen;
494 const char *wdata;
495 Jim_Obj *strObj;
497 if (argc == 2) {
498 if (!Jim_CompareStringImmediate(interp, argv[0], "-nonewline")) {
499 return -1;
501 strObj = argv[1];
503 else {
504 strObj = argv[0];
507 wdata = Jim_GetString(strObj, &wlen);
508 if (fwrite(wdata, 1, wlen, af->fp) == (unsigned)wlen) {
509 if (argc == 2 || putc('\n', af->fp) != EOF) {
510 return JIM_OK;
513 JimAioSetError(interp, af->filename);
514 return JIM_ERR;
517 static int aio_cmd_isatty(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
519 #ifdef HAVE_ISATTY
520 AioFile *af = Jim_CmdPrivData(interp);
521 Jim_SetResultInt(interp, isatty(fileno(af->fp)));
522 #else
523 Jim_SetResultInt(interp, 0);
524 #endif
526 return JIM_OK;
529 #if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)
530 static int aio_cmd_recvfrom(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
532 AioFile *af = Jim_CmdPrivData(interp);
533 char *buf;
534 union sockaddr_any sa;
535 long len;
536 socklen_t salen = sizeof(sa);
537 int rlen;
539 if (Jim_GetLong(interp, argv[0], &len) != JIM_OK) {
540 return JIM_ERR;
543 buf = Jim_Alloc(len + 1);
545 rlen = recvfrom(fileno(af->fp), buf, len, 0, &sa.sa, &salen);
546 if (rlen < 0) {
547 Jim_Free(buf);
548 JimAioSetError(interp, NULL);
549 return JIM_ERR;
551 buf[rlen] = 0;
552 Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, buf, rlen));
554 if (argc > 1) {
555 /* INET6_ADDRSTRLEN is 46. Add some for [] and port */
556 char addrbuf[60];
558 #if IPV6
559 if (sa.sa.sa_family == PF_INET6) {
560 addrbuf[0] = '[';
561 /* Allow 9 for []:65535\0 */
562 inet_ntop(sa.sa.sa_family, &sa.sin6.sin6_addr, addrbuf + 1, sizeof(addrbuf) - 9);
563 snprintf(addrbuf + strlen(addrbuf), 8, "]:%d", ntohs(sa.sin.sin_port));
565 else
566 #endif
567 if (sa.sa.sa_family == PF_INET) {
568 /* Allow 7 for :65535\0 */
569 inet_ntop(sa.sa.sa_family, &sa.sin.sin_addr, addrbuf, sizeof(addrbuf) - 7);
570 snprintf(addrbuf + strlen(addrbuf), 7, ":%d", ntohs(sa.sin.sin_port));
572 else {
573 /* recvfrom still works on unix domain sockets, etc */
574 addrbuf[0] = 0;
577 if (Jim_SetVariable(interp, argv[1], Jim_NewStringObj(interp, addrbuf, -1)) != JIM_OK) {
578 return JIM_ERR;
582 return JIM_OK;
586 static int aio_cmd_sendto(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
588 AioFile *af = Jim_CmdPrivData(interp);
589 int wlen;
590 int len;
591 const char *wdata;
592 union sockaddr_any sa;
593 const char *addr = Jim_String(argv[1]);
594 int salen;
596 if (IPV6 && af->addr_family == PF_INET6) {
597 if (JimParseIPv6Address(interp, addr, &sa, &salen) != JIM_OK) {
598 return JIM_ERR;
601 else if (JimParseIpAddress(interp, addr, &sa, &salen) != JIM_OK) {
602 return JIM_ERR;
604 wdata = Jim_GetString(argv[0], &wlen);
606 /* Note that we don't validate the socket type. Rely on sendto() failing if appropriate */
607 len = sendto(fileno(af->fp), wdata, wlen, 0, &sa.sa, salen);
608 if (len < 0) {
609 JimAioSetError(interp, NULL);
610 return JIM_ERR;
612 Jim_SetResultInt(interp, len);
613 return JIM_OK;
616 static int aio_cmd_accept(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
618 AioFile *af = Jim_CmdPrivData(interp);
619 int sock;
620 union sockaddr_any sa;
621 socklen_t addrlen = sizeof(sa);
623 sock = accept(af->fd, &sa.sa, &addrlen);
624 if (sock < 0) {
625 JimAioSetError(interp, NULL);
626 return JIM_ERR;
629 /* Create the file command */
630 return JimMakeChannel(interp, NULL, sock, Jim_NewStringObj(interp, "accept", -1),
631 "aio.sockstream%ld", af->addr_family, "r+");
634 static int aio_cmd_listen(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
636 AioFile *af = Jim_CmdPrivData(interp);
637 long backlog;
639 if (Jim_GetLong(interp, argv[0], &backlog) != JIM_OK) {
640 return JIM_ERR;
643 if (listen(af->fd, backlog)) {
644 JimAioSetError(interp, NULL);
645 return JIM_ERR;
648 return JIM_OK;
650 #endif /* JIM_BOOTSTRAP */
652 static int aio_cmd_flush(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
654 AioFile *af = Jim_CmdPrivData(interp);
656 if (fflush(af->fp) == EOF) {
657 JimAioSetError(interp, af->filename);
658 return JIM_ERR;
660 return JIM_OK;
663 static int aio_cmd_eof(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
665 AioFile *af = Jim_CmdPrivData(interp);
667 Jim_SetResultInt(interp, feof(af->fp));
668 return JIM_OK;
671 static int aio_cmd_close(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
673 if (argc == 3) {
674 #ifdef HAVE_SHUTDOWN
675 static const char * const options[] = { "r", "w", NULL };
676 enum { OPT_R, OPT_W, };
677 int option;
678 AioFile *af = Jim_CmdPrivData(interp);
680 if (Jim_GetEnum(interp, argv[2], options, &option, NULL, JIM_ERRMSG) != JIM_OK) {
681 return JIM_ERR;
683 if (shutdown(af->fd, option == OPT_R ? SHUT_RD : SHUT_WR) == 0) {
684 return JIM_OK;
686 JimAioSetError(interp, NULL);
687 #else
688 Jim_SetResultString(interp, "async close not supported", -1);
689 #endif
690 return JIM_ERR;
693 return Jim_DeleteCommand(interp, Jim_String(argv[0]));
696 static int aio_cmd_seek(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
698 AioFile *af = Jim_CmdPrivData(interp);
699 int orig = SEEK_SET;
700 jim_wide offset;
702 if (argc == 2) {
703 if (Jim_CompareStringImmediate(interp, argv[1], "start"))
704 orig = SEEK_SET;
705 else if (Jim_CompareStringImmediate(interp, argv[1], "current"))
706 orig = SEEK_CUR;
707 else if (Jim_CompareStringImmediate(interp, argv[1], "end"))
708 orig = SEEK_END;
709 else {
710 return -1;
713 if (Jim_GetWide(interp, argv[0], &offset) != JIM_OK) {
714 return JIM_ERR;
716 if (fseeko(af->fp, offset, orig) == -1) {
717 JimAioSetError(interp, af->filename);
718 return JIM_ERR;
720 return JIM_OK;
723 static int aio_cmd_tell(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
725 AioFile *af = Jim_CmdPrivData(interp);
727 Jim_SetResultInt(interp, ftello(af->fp));
728 return JIM_OK;
731 static int aio_cmd_filename(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
733 AioFile *af = Jim_CmdPrivData(interp);
735 Jim_SetResult(interp, af->filename);
736 return JIM_OK;
739 #ifdef O_NDELAY
740 static int aio_cmd_ndelay(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
742 AioFile *af = Jim_CmdPrivData(interp);
744 int fmode = af->flags;
746 if (argc) {
747 long nb;
749 if (Jim_GetLong(interp, argv[0], &nb) != JIM_OK) {
750 return JIM_ERR;
752 if (nb) {
753 fmode |= O_NDELAY;
755 else {
756 fmode &= ~O_NDELAY;
758 fcntl(af->fd, F_SETFL, fmode);
759 af->flags = fmode;
761 Jim_SetResultInt(interp, (fmode & O_NONBLOCK) ? 1 : 0);
762 return JIM_OK;
764 #endif
766 static int aio_cmd_buffering(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
768 AioFile *af = Jim_CmdPrivData(interp);
770 static const char * const options[] = {
771 "none",
772 "line",
773 "full",
774 NULL
776 enum
778 OPT_NONE,
779 OPT_LINE,
780 OPT_FULL,
782 int option;
784 if (Jim_GetEnum(interp, argv[0], options, &option, NULL, JIM_ERRMSG) != JIM_OK) {
785 return JIM_ERR;
787 switch (option) {
788 case OPT_NONE:
789 setvbuf(af->fp, NULL, _IONBF, 0);
790 break;
791 case OPT_LINE:
792 setvbuf(af->fp, NULL, _IOLBF, BUFSIZ);
793 break;
794 case OPT_FULL:
795 setvbuf(af->fp, NULL, _IOFBF, BUFSIZ);
796 break;
798 return JIM_OK;
801 #ifdef jim_ext_eventloop
802 static void JimAioFileEventFinalizer(Jim_Interp *interp, void *clientData)
804 Jim_Obj **objPtrPtr = clientData;
806 Jim_DecrRefCount(interp, *objPtrPtr);
807 *objPtrPtr = NULL;
810 static int JimAioFileEventHandler(Jim_Interp *interp, void *clientData, int mask)
812 Jim_Obj **objPtrPtr = clientData;
814 return Jim_EvalObjBackground(interp, *objPtrPtr);
817 static int aio_eventinfo(Jim_Interp *interp, AioFile * af, unsigned mask, Jim_Obj **scriptHandlerObj,
818 int argc, Jim_Obj * const *argv)
820 if (argc == 0) {
821 /* Return current script */
822 if (*scriptHandlerObj) {
823 Jim_SetResult(interp, *scriptHandlerObj);
825 return JIM_OK;
828 if (*scriptHandlerObj) {
829 /* Delete old handler */
830 Jim_DeleteFileHandler(interp, af->fp, mask);
833 /* Now possibly add the new script(s) */
834 if (Jim_Length(argv[0]) == 0) {
835 /* Empty script, so done */
836 return JIM_OK;
839 /* A new script to add */
840 Jim_IncrRefCount(argv[0]);
841 *scriptHandlerObj = argv[0];
843 Jim_CreateFileHandler(interp, af->fp, mask,
844 JimAioFileEventHandler, scriptHandlerObj, JimAioFileEventFinalizer);
846 return JIM_OK;
849 static int aio_cmd_readable(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
851 AioFile *af = Jim_CmdPrivData(interp);
853 return aio_eventinfo(interp, af, JIM_EVENT_READABLE, &af->rEvent, argc, argv);
856 static int aio_cmd_writable(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
858 AioFile *af = Jim_CmdPrivData(interp);
860 return aio_eventinfo(interp, af, JIM_EVENT_WRITABLE, &af->wEvent, argc, argv);
863 static int aio_cmd_onexception(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
865 AioFile *af = Jim_CmdPrivData(interp);
867 return aio_eventinfo(interp, af, JIM_EVENT_EXCEPTION, &af->wEvent, argc, argv);
869 #endif
871 static const jim_subcmd_type aio_command_table[] = {
872 { "read",
873 "?-nonewline? ?len?",
874 aio_cmd_read,
877 /* Description: Read and return bytes from the stream. To eof if no len. */
879 { "copyto",
880 "handle ?size?",
881 aio_cmd_copy,
884 /* Description: Copy up to 'size' bytes to the given filehandle, or to eof if no size. */
886 { "gets",
887 "?var?",
888 aio_cmd_gets,
891 /* Description: Read one line and return it or store it in the var */
893 { "puts",
894 "?-nonewline? str",
895 aio_cmd_puts,
898 /* Description: Write the string, with newline unless -nonewline */
900 { "isatty",
901 NULL,
902 aio_cmd_isatty,
905 /* Description: Is the file descriptor a tty? */
907 #if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)
908 { "recvfrom",
909 "len ?addrvar?",
910 aio_cmd_recvfrom,
913 /* Description: Receive up to 'len' bytes on the socket. Sets 'addrvar' with receive address, if set */
915 { "sendto",
916 "str address",
917 aio_cmd_sendto,
920 /* Description: Send 'str' to the given address (dgram only) */
922 { "accept",
923 NULL,
924 aio_cmd_accept,
927 /* Description: Server socket only: Accept a connection and return stream */
929 { "listen",
930 "backlog",
931 aio_cmd_listen,
934 /* Description: Set the listen backlog for server socket */
936 #endif /* JIM_BOOTSTRAP */
937 { "flush",
938 NULL,
939 aio_cmd_flush,
942 /* Description: Flush the stream */
944 { "eof",
945 NULL,
946 aio_cmd_eof,
949 /* Description: Returns 1 if stream is at eof */
951 { "close",
952 "?r(ead)|w(rite)?",
953 aio_cmd_close,
956 JIM_MODFLAG_FULLARGV,
957 /* Description: Closes the stream. */
959 { "seek",
960 "offset ?start|current|end",
961 aio_cmd_seek,
964 /* Description: Seeks in the stream (default 'current') */
966 { "tell",
967 NULL,
968 aio_cmd_tell,
971 /* Description: Returns the current seek position */
973 { "filename",
974 NULL,
975 aio_cmd_filename,
978 /* Description: Returns the original filename */
980 #ifdef O_NDELAY
981 { "ndelay",
982 "?0|1?",
983 aio_cmd_ndelay,
986 /* Description: Set O_NDELAY (if arg). Returns current/new setting. */
988 #endif
989 { "buffering",
990 "none|line|full",
991 aio_cmd_buffering,
994 /* Description: Sets buffering */
996 #ifdef jim_ext_eventloop
997 { "readable",
998 "?readable-script?",
999 aio_cmd_readable,
1002 /* Description: Returns script, or invoke readable-script when readable, {} to remove */
1004 { "writable",
1005 "?writable-script?",
1006 aio_cmd_writable,
1009 /* Description: Returns script, or invoke writable-script when writable, {} to remove */
1011 { "onexception",
1012 "?exception-script?",
1013 aio_cmd_onexception,
1016 /* Description: Returns script, or invoke exception-script when oob data, {} to remove */
1018 #endif
1019 { NULL }
1022 static int JimAioSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1024 return Jim_CallSubCmd(interp, Jim_ParseSubCmd(interp, aio_command_table, argc, argv), argc, argv);
1027 static int JimAioOpenCommand(Jim_Interp *interp, int argc,
1028 Jim_Obj *const *argv)
1030 const char *mode;
1031 const char *filename;
1033 if (argc != 2 && argc != 3) {
1034 Jim_WrongNumArgs(interp, 1, argv, "filename ?mode?");
1035 return JIM_ERR;
1038 mode = (argc == 3) ? Jim_String(argv[2]) : "r";
1039 filename = Jim_String(argv[1]);
1041 #ifdef jim_ext_tclcompat
1042 /* If the filename starts with '|', use popen instead */
1043 if (*filename == '|') {
1044 Jim_Obj *evalObj[3];
1046 evalObj[0] = Jim_NewStringObj(interp, "popen", -1);
1047 evalObj[1] = Jim_NewStringObj(interp, filename + 1, -1);
1048 evalObj[2] = Jim_NewStringObj(interp, mode, -1);
1050 return Jim_EvalObjVector(interp, 3, evalObj);
1052 #endif
1053 return JimMakeChannel(interp, NULL, -1, argv[1], "aio.handle%ld", 0, mode);
1057 * Creates a channel for fh/fd/filename.
1059 * If fh is not NULL, uses that as the channel (and set AIO_KEEPOPEN).
1060 * Otherwise, if fd is >= 0, uses that as the chanel.
1061 * Otherwise opens 'filename' with mode 'mode'.
1063 * hdlfmt is a sprintf format for the filehandle. Anything with %ld at the end will do.
1064 * mode is used for open or fdopen.
1066 * Creates the command and sets the name as the current result.
1068 static int JimMakeChannel(Jim_Interp *interp, FILE *fh, int fd, Jim_Obj *filename,
1069 const char *hdlfmt, int family, const char *mode)
1071 AioFile *af;
1072 char buf[AIO_CMD_LEN];
1073 int OpenFlags = 0;
1075 if (filename == NULL) {
1076 filename = Jim_NewStringObj(interp, hdlfmt, -1);
1079 Jim_IncrRefCount(filename);
1081 if (fh == NULL) {
1082 if (fd < 0) {
1083 fh = fopen(Jim_String(filename), mode);
1085 else {
1086 fh = fdopen(fd, mode);
1089 else {
1090 OpenFlags = AIO_KEEPOPEN;
1093 if (fh == NULL) {
1094 JimAioSetError(interp, filename);
1095 #if !defined(JIM_ANSIC)
1096 if (fd >= 0) {
1097 close(fd);
1099 #endif
1100 Jim_DecrRefCount(interp, filename);
1101 return JIM_ERR;
1104 /* Create the file command */
1105 af = Jim_Alloc(sizeof(*af));
1106 memset(af, 0, sizeof(*af));
1107 af->fp = fh;
1108 af->fd = fileno(fh);
1109 af->filename = filename;
1110 #ifdef FD_CLOEXEC
1111 if ((OpenFlags & AIO_KEEPOPEN) == 0) {
1112 fcntl(af->fd, F_SETFD, FD_CLOEXEC);
1114 #endif
1115 af->OpenFlags = OpenFlags;
1116 #ifdef O_NDELAY
1117 af->flags = fcntl(af->fd, F_GETFL);
1118 #endif
1119 af->addr_family = family;
1120 snprintf(buf, sizeof(buf), hdlfmt, Jim_GetId(interp));
1121 Jim_CreateCommand(interp, buf, JimAioSubCmdProc, af, JimAioDelProc);
1123 /* Note that the command must use the global namespace, even if
1124 * the current namespace is something different
1126 Jim_SetResult(interp, Jim_MakeGlobalNamespaceName(interp, Jim_NewStringObj(interp, buf, -1)));
1128 return JIM_OK;
1131 static int JimMakeChannelPair(Jim_Interp *interp, int p[2], Jim_Obj *filename,
1132 const char *hdlfmt, int family, const char *mode[2])
1134 if (JimMakeChannel(interp, NULL, p[0], filename, hdlfmt, family, mode[0]) == JIM_OK) {
1135 Jim_Obj *objPtr = Jim_NewListObj(interp, NULL, 0);
1136 Jim_ListAppendElement(interp, objPtr, Jim_GetResult(interp));
1138 if (JimMakeChannel(interp, NULL, p[1], filename, hdlfmt, family, mode[1]) == JIM_OK) {
1139 Jim_ListAppendElement(interp, objPtr, Jim_GetResult(interp));
1140 Jim_SetResult(interp, objPtr);
1141 return JIM_OK;
1145 /* Can only be here if fdopen() failed */
1146 close(p[0]);
1147 close(p[1]);
1148 JimAioSetError(interp, NULL);
1149 return JIM_ERR;
1152 #if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)
1154 static int JimAioSockCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1156 const char *hdlfmt = "aio.unknown%ld";
1157 const char *socktypes[] = {
1158 "unix",
1159 "unix.server",
1160 "dgram",
1161 "dgram.server",
1162 "stream",
1163 "stream.server",
1164 "pipe",
1165 "pair",
1166 NULL
1168 enum
1170 SOCK_UNIX,
1171 SOCK_UNIX_SERVER,
1172 SOCK_DGRAM_CLIENT,
1173 SOCK_DGRAM_SERVER,
1174 SOCK_STREAM_CLIENT,
1175 SOCK_STREAM_SERVER,
1176 SOCK_STREAM_PIPE,
1177 SOCK_STREAM_SOCKETPAIR,
1179 int socktype;
1180 int sock;
1181 const char *hostportarg = NULL;
1182 int res;
1183 int on = 1;
1184 const char *mode = "r+";
1185 int family = PF_INET;
1186 Jim_Obj *argv0 = argv[0];
1187 int ipv6 = 0;
1189 if (argc > 1 && Jim_CompareStringImmediate(interp, argv[1], "-ipv6")) {
1190 if (!IPV6) {
1191 Jim_SetResultString(interp, "ipv6 not supported", -1);
1192 return JIM_ERR;
1194 ipv6 = 1;
1195 family = PF_INET6;
1197 argc -= ipv6;
1198 argv += ipv6;
1200 if (argc < 2) {
1201 wrongargs:
1202 Jim_WrongNumArgs(interp, 1, &argv0, "?-ipv6? type ?address?");
1203 return JIM_ERR;
1206 if (Jim_GetEnum(interp, argv[1], socktypes, &socktype, "socket type", JIM_ERRMSG) != JIM_OK)
1207 return JIM_ERR;
1209 Jim_SetEmptyResult(interp);
1211 hdlfmt = "aio.sock%ld";
1213 if (argc > 2) {
1214 hostportarg = Jim_String(argv[2]);
1217 switch (socktype) {
1218 case SOCK_DGRAM_CLIENT:
1219 if (argc == 2) {
1220 /* No address, so an unconnected dgram socket */
1221 sock = socket(family, SOCK_DGRAM, 0);
1222 if (sock < 0) {
1223 JimAioSetError(interp, NULL);
1224 return JIM_ERR;
1226 break;
1228 /* fall through */
1229 case SOCK_STREAM_CLIENT:
1231 union sockaddr_any sa;
1232 int salen;
1234 if (argc != 3) {
1235 goto wrongargs;
1238 if (ipv6) {
1239 if (JimParseIPv6Address(interp, hostportarg, &sa, &salen) != JIM_OK) {
1240 return JIM_ERR;
1243 else if (JimParseIpAddress(interp, hostportarg, &sa, &salen) != JIM_OK) {
1244 return JIM_ERR;
1246 sock = socket(family, (socktype == SOCK_DGRAM_CLIENT) ? SOCK_DGRAM : SOCK_STREAM, 0);
1247 if (sock < 0) {
1248 JimAioSetError(interp, NULL);
1249 return JIM_ERR;
1251 res = connect(sock, &sa.sa, salen);
1252 if (res) {
1253 JimAioSetError(interp, argv[2]);
1254 close(sock);
1255 return JIM_ERR;
1258 break;
1260 case SOCK_STREAM_SERVER:
1261 case SOCK_DGRAM_SERVER:
1263 union sockaddr_any sa;
1264 int salen;
1266 if (argc != 3) {
1267 goto wrongargs;
1270 if (ipv6) {
1271 if (JimParseIPv6Address(interp, hostportarg, &sa, &salen) != JIM_OK) {
1272 return JIM_ERR;
1275 else if (JimParseIpAddress(interp, hostportarg, &sa, &salen) != JIM_OK) {
1276 return JIM_ERR;
1278 sock = socket(family, (socktype == SOCK_DGRAM_SERVER) ? SOCK_DGRAM : SOCK_STREAM, 0);
1279 if (sock < 0) {
1280 JimAioSetError(interp, NULL);
1281 return JIM_ERR;
1284 /* Enable address reuse */
1285 setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on));
1287 res = bind(sock, &sa.sa, salen);
1288 if (res) {
1289 JimAioSetError(interp, argv[2]);
1290 close(sock);
1291 return JIM_ERR;
1293 if (socktype == SOCK_STREAM_SERVER) {
1294 res = listen(sock, 5);
1295 if (res) {
1296 JimAioSetError(interp, NULL);
1297 close(sock);
1298 return JIM_ERR;
1301 hdlfmt = "aio.socksrv%ld";
1303 break;
1305 #ifdef HAVE_SYS_UN_H
1306 case SOCK_UNIX:
1308 struct sockaddr_un sa;
1309 socklen_t len;
1311 if (argc != 3 || ipv6) {
1312 goto wrongargs;
1315 if (JimParseDomainAddress(interp, hostportarg, &sa) != JIM_OK) {
1316 JimAioSetError(interp, argv[2]);
1317 return JIM_ERR;
1319 family = PF_UNIX;
1320 sock = socket(PF_UNIX, SOCK_STREAM, 0);
1321 if (sock < 0) {
1322 JimAioSetError(interp, NULL);
1323 return JIM_ERR;
1325 len = strlen(sa.sun_path) + 1 + sizeof(sa.sun_family);
1326 res = connect(sock, (struct sockaddr *)&sa, len);
1327 if (res) {
1328 JimAioSetError(interp, argv[2]);
1329 close(sock);
1330 return JIM_ERR;
1332 hdlfmt = "aio.sockunix%ld";
1333 break;
1336 case SOCK_UNIX_SERVER:
1338 struct sockaddr_un sa;
1339 socklen_t len;
1341 if (argc != 3 || ipv6) {
1342 goto wrongargs;
1345 if (JimParseDomainAddress(interp, hostportarg, &sa) != JIM_OK) {
1346 JimAioSetError(interp, argv[2]);
1347 return JIM_ERR;
1349 family = PF_UNIX;
1350 sock = socket(PF_UNIX, SOCK_STREAM, 0);
1351 if (sock < 0) {
1352 JimAioSetError(interp, NULL);
1353 return JIM_ERR;
1355 len = strlen(sa.sun_path) + 1 + sizeof(sa.sun_family);
1356 res = bind(sock, (struct sockaddr *)&sa, len);
1357 if (res) {
1358 JimAioSetError(interp, argv[2]);
1359 close(sock);
1360 return JIM_ERR;
1362 res = listen(sock, 5);
1363 if (res) {
1364 JimAioSetError(interp, NULL);
1365 close(sock);
1366 return JIM_ERR;
1368 hdlfmt = "aio.sockunixsrv%ld";
1369 break;
1371 #endif
1373 #if defined(HAVE_SOCKETPAIR) && defined(HAVE_SYS_UN_H)
1374 case SOCK_STREAM_SOCKETPAIR:
1376 int p[2];
1377 static const char *mode[2] = { "r+", "r+" };
1379 if (argc != 2 || ipv6) {
1380 goto wrongargs;
1383 if (socketpair(PF_UNIX, SOCK_STREAM, 0, p) < 0) {
1384 JimAioSetError(interp, NULL);
1385 return JIM_ERR;
1387 return JimMakeChannelPair(interp, p, argv[1], "aio.sockpair%ld", PF_UNIX, mode);
1389 break;
1390 #endif
1392 #if defined(HAVE_PIPE)
1393 case SOCK_STREAM_PIPE:
1395 int p[2];
1396 static const char *mode[2] = { "r", "w" };
1398 if (argc != 2 || ipv6) {
1399 goto wrongargs;
1402 if (pipe(p) < 0) {
1403 JimAioSetError(interp, NULL);
1404 return JIM_ERR;
1407 return JimMakeChannelPair(interp, p, argv[1], "aio.pipe%ld", 0, mode);
1409 break;
1410 #endif
1412 default:
1413 Jim_SetResultString(interp, "Unsupported socket type", -1);
1414 return JIM_ERR;
1417 return JimMakeChannel(interp, NULL, sock, argv[1], hdlfmt, family, mode);
1419 #endif /* JIM_BOOTSTRAP */
1421 FILE *Jim_AioFilehandle(Jim_Interp *interp, Jim_Obj *command)
1423 Jim_Cmd *cmdPtr = Jim_GetCommand(interp, command, JIM_ERRMSG);
1425 if (cmdPtr && !cmdPtr->isproc && cmdPtr->u.native.cmdProc == JimAioSubCmdProc) {
1426 return ((AioFile *) cmdPtr->u.native.privData)->fp;
1428 Jim_SetResultFormatted(interp, "Not a filehandle: \"%#s\"", command);
1429 return NULL;
1432 int Jim_aioInit(Jim_Interp *interp)
1434 if (Jim_PackageProvide(interp, "aio", "1.0", JIM_ERRMSG))
1435 return JIM_ERR;
1437 Jim_CreateCommand(interp, "open", JimAioOpenCommand, NULL, NULL);
1438 #ifndef JIM_ANSIC
1439 Jim_CreateCommand(interp, "socket", JimAioSockCommand, NULL, NULL);
1440 #endif
1442 /* Create filehandles for stdin, stdout and stderr */
1443 JimMakeChannel(interp, stdin, -1, NULL, "stdin", 0, "r");
1444 JimMakeChannel(interp, stdout, -1, NULL, "stdout", 0, "w");
1445 JimMakeChannel(interp, stderr, -1, NULL, "stderr", 0, "w");
1447 return JIM_OK;