aio: ssl: Allow SNI to be specified
[jimtcl.git] / jim-aio.c
blob56c8adfe0f69592f202a56e3d29b9e58d33eddeb
1 /* Jim - A small embeddable Tcl interpreter
3 * Copyright 2005 Salvatore Sanfilippo <antirez@invece.org>
4 * Copyright 2005 Clemens Hintze <c.hintze@gmx.net>
5 * Copyright 2005 patthoyts - Pat Thoyts <patthoyts@users.sf.net>
6 * Copyright 2008 oharboe - Øyvind Harboe - oyvind.harboe@zylin.com
7 * Copyright 2008 Andrew Lunn <andrew@lunn.ch>
8 * Copyright 2008 Duane Ellis <openocd@duaneellis.com>
9 * Copyright 2008 Uwe Klein <uklein@klein-messgeraete.de>
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above
18 * copyright notice, this list of conditions and the following
19 * disclaimer in the documentation and/or other materials
20 * provided with the distribution.
22 * THIS SOFTWARE IS PROVIDED BY THE JIM TCL PROJECT ``AS IS'' AND ANY
23 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
24 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
25 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26 * JIM TCL PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
27 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
28 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
31 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
33 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 * The views and conclusions contained in the software and documentation
36 * are those of the authors and should not be interpreted as representing
37 * official policies, either expressed or implied, of the Jim Tcl Project.
38 **/
40 #include "jimautoconf.h"
42 #ifndef _GNU_SOURCE
43 #define _GNU_SOURCE
44 #endif
45 #include <stdio.h>
46 #include <string.h>
47 #include <errno.h>
48 #include <fcntl.h>
49 #include <assert.h>
50 #ifdef HAVE_UNISTD_H
51 #include <unistd.h>
52 #include <sys/stat.h>
53 #endif
55 #include "jim.h"
56 #include "jimiocompat.h"
58 #if defined(HAVE_SYS_SOCKET_H) && defined(HAVE_SELECT) && defined(HAVE_NETINET_IN_H) && defined(HAVE_NETDB_H) && defined(HAVE_ARPA_INET_H)
59 #include <sys/socket.h>
60 #include <netinet/in.h>
61 #include <netinet/tcp.h>
62 #include <arpa/inet.h>
63 #include <netdb.h>
64 #ifdef HAVE_SYS_UN_H
65 #include <sys/un.h>
66 #endif
67 #define HAVE_SOCKETS
68 #elif defined (__MINGW32__)
69 /* currently mingw32 doesn't support sockets, but has pipe, fdopen */
70 #else
71 #define JIM_ANSIC
72 #endif
74 #if defined(JIM_SSL)
75 #include <openssl/ssl.h>
76 #include <openssl/err.h>
77 #endif
79 #ifdef HAVE_TERMIOS_H
80 #include <jim-tty.h>
81 #endif
83 #include "jim-eventloop.h"
84 #include "jim-subcmd.h"
86 #define AIO_CMD_LEN 32 /* e.g. aio.handleXXXXXX */
87 #define AIO_BUF_LEN 256 /* Can keep this small and rely on stdio buffering */
89 #ifndef HAVE_FTELLO
90 #define ftello ftell
91 #endif
92 #ifndef HAVE_FSEEKO
93 #define fseeko fseek
94 #endif
96 #define AIO_KEEPOPEN 1
97 #define AIO_NODELETE 2
98 #define AIO_EOF 4
100 #if defined(JIM_IPV6)
101 #define IPV6 1
102 #else
103 #define IPV6 0
104 #ifndef PF_INET6
105 #define PF_INET6 0
106 #endif
107 #endif
108 #if defined(HAVE_SYS_UN_H) && defined(PF_UNIX)
109 #define UNIX_SOCKETS 1
110 #else
111 #define UNIX_SOCKETS 0
112 #endif
114 #ifdef JIM_ANSIC
115 /* no fdopen() with ANSIC, so can't support these */
116 #undef HAVE_PIPE
117 #undef HAVE_SOCKETPAIR
118 #endif
120 #if defined(HAVE_SOCKETS) && !defined(JIM_BOOTSTRAP)
121 /* Avoid type punned pointers */
122 union sockaddr_any {
123 struct sockaddr sa;
124 struct sockaddr_in sin;
125 #if IPV6
126 struct sockaddr_in6 sin6;
127 #endif
128 #if UNIX_SOCKETS
129 struct sockaddr_un sun;
130 #endif
133 #ifndef HAVE_INET_NTOP
134 const char *inet_ntop(int af, const void *src, char *dst, int size)
136 if (af != PF_INET) {
137 return NULL;
139 snprintf(dst, size, "%s", inet_ntoa(((struct sockaddr_in *)src)->sin_addr));
140 return dst;
142 #endif
143 #endif /* JIM_BOOTSTRAP */
145 struct AioFile;
147 typedef struct {
148 int (*writer)(struct AioFile *af, const char *buf, int len);
149 int (*reader)(struct AioFile *af, char *buf, int len);
150 const char *(*getline)(struct AioFile *af, char *buf, int len);
151 int (*error)(const struct AioFile *af);
152 const char *(*strerror)(struct AioFile *af);
153 int (*verify)(struct AioFile *af);
154 int (*eof)(struct AioFile *af);
155 int (*pending)(struct AioFile *af);
156 } JimAioFopsType;
158 typedef struct AioFile
160 FILE *fp;
161 Jim_Obj *filename;
162 int type;
163 int flags; /* AIO_KEEPOPEN? keep FILE* */
164 int fd;
165 Jim_Obj *rEvent;
166 Jim_Obj *wEvent;
167 Jim_Obj *eEvent;
168 int addr_family;
169 void *ssl;
170 const JimAioFopsType *fops;
171 } AioFile;
173 static int stdio_writer(struct AioFile *af, const char *buf, int len)
175 return fwrite(buf, 1, len, af->fp);
178 static int stdio_reader(struct AioFile *af, char *buf, int len)
180 return fread(buf, 1, len, af->fp);
183 static const char *stdio_getline(struct AioFile *af, char *buf, int len)
185 return fgets(buf, len, af->fp);
188 static int stdio_error(const AioFile *af)
190 if (!ferror(af->fp)) {
191 return JIM_OK;
193 clearerr(af->fp);
194 /* EAGAIN and similar are not error conditions. Just treat them like eof */
195 if (feof(af->fp) || errno == EAGAIN || errno == EINTR) {
196 return JIM_OK;
198 #ifdef ECONNRESET
199 if (errno == ECONNRESET) {
200 return JIM_OK;
202 #endif
203 #ifdef ECONNABORTED
204 if (errno == ECONNABORTED) {
205 return JIM_OK;
207 #endif
208 return JIM_ERR;
211 static const char *stdio_strerror(struct AioFile *af)
213 return strerror(errno);
216 static int stdio_eof(struct AioFile *af)
218 return feof(af->fp);
221 static const JimAioFopsType stdio_fops = {
222 stdio_writer,
223 stdio_reader,
224 stdio_getline,
225 stdio_error,
226 stdio_strerror,
227 NULL, /* verify */
228 stdio_eof,
229 NULL, /* pending */
232 #if defined(JIM_SSL) && !defined(JIM_BOOTSTRAP)
234 static SSL_CTX *JimAioSslCtx(Jim_Interp *interp);
236 static int ssl_writer(struct AioFile *af, const char *buf, int len)
238 return SSL_write(af->ssl, buf, len);
241 static int ssl_pending(struct AioFile *af)
243 return SSL_pending(af->ssl);
246 static int ssl_reader(struct AioFile *af, char *buf, int len)
248 int ret = SSL_read(af->ssl, buf, len);
249 switch (SSL_get_error(af->ssl, ret)) {
250 case SSL_ERROR_NONE:
251 return ret;
252 case SSL_ERROR_SYSCALL:
253 case SSL_ERROR_ZERO_RETURN:
254 if (errno != EAGAIN) {
255 af->flags |= AIO_EOF;
257 return 0;
258 case SSL_ERROR_SSL:
259 default:
260 if (errno == EAGAIN) {
261 return 0;
263 af->flags |= AIO_EOF;
264 return -1;
268 static int ssl_eof(struct AioFile *af)
270 return (af->flags & AIO_EOF);
273 static const char *ssl_getline(struct AioFile *af, char *buf, int len)
275 size_t i;
276 for (i = 0; i < len - 1 && !ssl_eof(af); i++) {
277 int ret = ssl_reader(af, &buf[i], 1);
278 if (ret != 1) {
279 break;
281 if (buf[i] == '\n') {
282 i++;
283 break;
286 buf[i] = '\0';
287 if (i == 0 && ssl_eof(af)) {
288 return NULL;
290 return buf;
293 static int ssl_error(const struct AioFile *af)
295 int ret = SSL_get_error(af->ssl, 0);
296 if (ret == SSL_ERROR_SYSCALL || ret == 0) {
297 return stdio_error(af);
299 return JIM_ERR;
302 static const char *ssl_strerror(struct AioFile *af)
304 int err = ERR_get_error();
306 if (err) {
307 return ERR_error_string(err, NULL);
309 else {
310 return stdio_strerror(af);
314 static int ssl_verify(struct AioFile *af)
316 X509 *cert;
318 cert = SSL_get_peer_certificate(af->ssl);
319 if (!cert) {
320 return JIM_ERR;
322 X509_free(cert);
324 if (SSL_get_verify_result(af->ssl) == X509_V_OK) {
325 return JIM_OK;
328 return JIM_ERR;
331 static const JimAioFopsType ssl_fops = {
332 ssl_writer,
333 ssl_reader,
334 ssl_getline,
335 ssl_error,
336 ssl_strerror,
337 ssl_verify,
338 ssl_eof,
339 ssl_pending,
341 #endif /* JIM_BOOTSTRAP */
343 static int JimAioSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv);
344 static AioFile *JimMakeChannel(Jim_Interp *interp, FILE *fh, int fd, Jim_Obj *filename,
345 const char *hdlfmt, int family, const char *mode, int flags);
347 #if defined(HAVE_SOCKETS) && !defined(JIM_BOOTSTRAP)
348 #ifndef HAVE_GETADDRINFO
350 * Poor man's getaddrinfo().
351 * hints->ai_family must be set and must be PF_INET or PF_INET6
352 * Only returns the first matching result.
353 * servname must be numeric.
355 struct addrinfo {
356 int ai_family;
357 socklen_t ai_addrlen;
358 struct sockaddr *ai_addr; /* simply points to ai_storage */
359 union sockaddr_any ai_storage;
362 static int getaddrinfo(const char *hostname, const char *servname,
363 const struct addrinfo *hints, struct addrinfo **res)
365 struct hostent *he;
366 char *end;
367 unsigned long port = strtoul(servname, &end, 10);
368 if (port == 0 || port > 65536 || *end) {
369 errno = EINVAL;
370 return -1;
373 if ((he = gethostbyname(hostname)) != NULL) {
374 int i;
375 for (i = 0; he->h_addr_list[i]; i++) {
376 if (he->h_addrtype == hints->ai_family) {
377 struct addrinfo *ai = malloc(sizeof(*ai));
378 memset(ai, 0, sizeof(*ai));
379 ai->ai_family = he->h_addrtype;
380 ai->ai_addr = &ai->ai_storage.sa;
381 if (ai->ai_family == PF_INET) {
382 ai->ai_addrlen = sizeof(ai->ai_storage.sin);
383 ai->ai_storage.sin.sin_family = he->h_addrtype;
384 assert(sizeof(ai->ai_storage.sin.sin_addr) == he->h_length);
385 memcpy(&ai->ai_storage.sin.sin_addr, he->h_addr_list[i], he->h_length);
386 ai->ai_storage.sin.sin_port = htons(port);
388 #if IPV6
389 else {
390 ai->ai_addrlen = sizeof(ai->ai_storage.sin6);
391 ai->ai_storage.sin6.sin6_family = he->h_addrtype;
392 assert(sizeof(ai->ai_storage.sin6.sin6_addr) == he->h_length);
393 memcpy(&ai->ai_storage.sin6.sin6_addr, he->h_addr_list[i], he->h_length);
394 ai->ai_storage.sin6.sin6_port = htons(port);
396 #endif
397 *res = ai;
398 return 0;
402 return ENOENT;
405 static void freeaddrinfo(struct addrinfo *ai)
407 free(ai);
409 #endif
411 static int JimParseIPv6Address(Jim_Interp *interp, const char *hostport, union sockaddr_any *sa, socklen_t *salen)
413 #if IPV6
415 * An IPv6 addr/port looks like:
416 * [::1]
417 * [::1]:2000
418 * [fe80::223:6cff:fe95:bdc0%en1]:2000
419 * [::]:2000
420 * 2000
422 * Note that the "any" address is ::, which is the same as when no address is specified.
424 char *sthost = NULL;
425 const char *stport;
426 int ret = JIM_OK;
427 struct addrinfo req;
428 struct addrinfo *ai;
430 stport = strrchr(hostport, ':');
431 if (!stport) {
432 /* No : so, the whole thing is the port */
433 stport = hostport;
434 hostport = "::";
435 sthost = Jim_StrDup(hostport);
437 else {
438 stport++;
441 if (*hostport == '[') {
442 /* This is a numeric ipv6 address */
443 char *pt = strchr(++hostport, ']');
444 if (pt) {
445 sthost = Jim_StrDupLen(hostport, pt - hostport);
449 if (!sthost) {
450 sthost = Jim_StrDupLen(hostport, stport - hostport - 1);
453 memset(&req, '\0', sizeof(req));
454 req.ai_family = PF_INET6;
456 if (getaddrinfo(sthost, stport, &req, &ai)) {
457 Jim_SetResultFormatted(interp, "Not a valid address: %s:%s", sthost, stport);
458 ret = JIM_ERR;
460 else {
461 memcpy(&sa->sin6, ai->ai_addr, ai->ai_addrlen);
462 *salen = ai->ai_addrlen;
463 freeaddrinfo(ai);
465 Jim_Free(sthost);
467 return ret;
468 #else
469 Jim_SetResultString(interp, "ipv6 not supported", -1);
470 return JIM_ERR;
471 #endif
474 static int JimParseIpAddress(Jim_Interp *interp, const char *hostport, union sockaddr_any *sa, socklen_t *salen)
476 /* An IPv4 addr/port looks like:
477 * 192.168.1.5
478 * 192.168.1.5:2000
479 * 2000
481 * If the address is missing, INADDR_ANY is used.
482 * If the port is missing, 0 is used (only useful for server sockets).
484 char *sthost = NULL;
485 const char *stport;
486 int ret = JIM_OK;
487 struct addrinfo req;
488 struct addrinfo *ai;
490 stport = strrchr(hostport, ':');
491 if (!stport) {
492 /* No : so, the whole thing is the port */
493 stport = hostport;
494 sthost = Jim_StrDup("0.0.0.0");
496 else {
497 sthost = Jim_StrDupLen(hostport, stport - hostport);
498 stport++;
501 memset(&req, '\0', sizeof(req));
502 req.ai_family = PF_INET;
504 if (getaddrinfo(sthost, stport, &req, &ai)) {
505 ret = JIM_ERR;
507 else {
508 memcpy(&sa->sin, ai->ai_addr, ai->ai_addrlen);
509 *salen = ai->ai_addrlen;
510 freeaddrinfo(ai);
512 Jim_Free(sthost);
514 if (ret != JIM_OK) {
515 Jim_SetResultFormatted(interp, "Not a valid address: %s", hostport);
518 return ret;
521 #if UNIX_SOCKETS
522 static int JimParseDomainAddress(Jim_Interp *interp, const char *path, union sockaddr_any *sa, socklen_t *salen)
524 sa->sun.sun_family = PF_UNIX;
525 snprintf(sa->sun.sun_path, sizeof(sa->sun.sun_path), "%s", path);
526 *salen = strlen(sa->sun.sun_path) + 1 + sizeof(sa->sun.sun_family);
528 return JIM_OK;
530 #endif
532 static int JimParseSocketAddress(Jim_Interp *interp, int family, const char *addr, union sockaddr_any *sa, socklen_t *salen)
534 switch (family) {
535 #if UNIX_SOCKETS
536 case PF_UNIX:
537 return JimParseDomainAddress(interp, addr, sa, salen);
538 #endif
539 case PF_INET6:
540 return JimParseIPv6Address(interp, addr, sa, salen);
541 case PF_INET:
542 return JimParseIpAddress(interp, addr, sa, salen);
544 return JIM_ERR;
548 * Format that address in 'sa' as a string and return it as a zero-refcount object.
551 static Jim_Obj *JimFormatSocketAddress(Jim_Interp *interp, const union sockaddr_any *sa, socklen_t salen)
553 /* INET6_ADDRSTRLEN is 46. Add some for [] and port */
554 char addrbuf[60];
555 const char *addr = addrbuf;
556 int addrlen = -1;
558 switch (sa->sa.sa_family) {
559 #if UNIX_SOCKETS
560 case PF_UNIX:
561 addr = sa->sun.sun_path;
562 addrlen = salen - 1 - sizeof(sa->sun.sun_family);
563 if (addrlen < 0) {
564 addrlen = 0;
566 break;
567 #endif
568 #if IPV6
569 case PF_INET6:
570 addrbuf[0] = '[';
571 /* Allow 9 for []:65535\0 */
572 inet_ntop(sa->sa.sa_family, &sa->sin6.sin6_addr, addrbuf + 1, sizeof(addrbuf) - 9);
573 snprintf(addrbuf + strlen(addrbuf), 8, "]:%d", ntohs(sa->sin6.sin6_port));
574 break;
575 #endif
576 case PF_INET:
577 /* Allow 7 for :65535\0 */
578 inet_ntop(sa->sa.sa_family, &sa->sin.sin_addr, addrbuf, sizeof(addrbuf) - 7);
579 snprintf(addrbuf + strlen(addrbuf), 7, ":%d", ntohs(sa->sin.sin_port));
580 break;
582 default:
583 /* Otherwise just an empty address */
584 addr = "";
585 break;
588 return Jim_NewStringObj(interp, addr, addrlen);
591 static int JimSetVariableSocketAddress(Jim_Interp *interp, Jim_Obj *varObjPtr, const union sockaddr_any *sa, socklen_t salen)
593 int ret;
594 Jim_Obj *objPtr = JimFormatSocketAddress(interp, sa, salen);
595 Jim_IncrRefCount(objPtr);
596 ret = Jim_SetVariable(interp, varObjPtr, objPtr);
597 Jim_DecrRefCount(interp, objPtr);
598 return ret;
601 static Jim_Obj *aio_sockname(Jim_Interp *interp, AioFile *af)
603 union sockaddr_any sa;
604 socklen_t salen = sizeof(sa);
606 if (getsockname(af->fd, &sa.sa, &salen) < 0) {
607 return NULL;
609 return JimFormatSocketAddress(interp, &sa, salen);
611 #endif /* JIM_BOOTSTRAP */
613 static const char *JimAioErrorString(AioFile *af)
615 if (af && af->fops)
616 return af->fops->strerror(af);
618 return strerror(errno);
621 static void JimAioSetError(Jim_Interp *interp, Jim_Obj *name)
623 AioFile *af = Jim_CmdPrivData(interp);
625 if (name) {
626 Jim_SetResultFormatted(interp, "%#s: %s", name, JimAioErrorString(af));
628 else {
629 Jim_SetResultString(interp, JimAioErrorString(af), -1);
633 static int JimCheckStreamError(Jim_Interp *interp, AioFile *af)
635 int ret = af->fops->error(af);
636 if (ret) {
637 JimAioSetError(interp, af->filename);
639 return ret;
642 static void JimAioDelProc(Jim_Interp *interp, void *privData)
644 AioFile *af = privData;
646 JIM_NOTUSED(interp);
648 #if UNIX_SOCKETS
649 if (af->addr_family == PF_UNIX && (af->flags & AIO_NODELETE) == 0) {
650 /* If this is bound, delete the socket file now */
651 Jim_Obj *filenameObj = aio_sockname(interp, af);
652 if (filenameObj) {
653 if (Jim_Length(filenameObj)) {
654 remove(Jim_String(filenameObj));
656 Jim_FreeNewObj(interp, filenameObj);
659 #endif
661 Jim_DecrRefCount(interp, af->filename);
663 #ifdef jim_ext_eventloop
664 /* remove all existing EventHandlers */
665 Jim_DeleteFileHandler(interp, af->fd, JIM_EVENT_READABLE | JIM_EVENT_WRITABLE | JIM_EVENT_EXCEPTION);
666 #endif
668 #if defined(JIM_SSL)
669 if (af->ssl != NULL) {
670 SSL_free(af->ssl);
672 #endif
673 if (!(af->flags & AIO_KEEPOPEN)) {
674 fclose(af->fp);
677 Jim_Free(af);
680 static int aio_cmd_read(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
682 AioFile *af = Jim_CmdPrivData(interp);
683 char buf[AIO_BUF_LEN];
684 Jim_Obj *objPtr;
685 int nonewline = 0;
686 int pending = 0;
687 jim_wide neededLen = -1; /* -1 is "read as much as possible" */
688 static const char * const options[] = { "-pending", "-nonewline", NULL };
689 enum { OPT_PENDING, OPT_NONEWLINE };
690 int option;
692 if (argc) {
693 if (*Jim_String(argv[0]) == '-') {
694 if (Jim_GetEnum(interp, argv[0], options, &option, NULL, JIM_ERRMSG) != JIM_OK) {
695 return JIM_ERR;
697 switch (option) {
698 case OPT_PENDING:
699 if (!af->fops->pending) {
700 Jim_SetResultString(interp, "-pending not supported on this connection type", -1);
701 return JIM_ERR;
703 pending++;
704 break;
705 case OPT_NONEWLINE:
706 nonewline++;
707 break;
710 else {
711 if (Jim_GetWide(interp, argv[0], &neededLen) != JIM_OK)
712 return JIM_ERR;
713 if (neededLen < 0) {
714 Jim_SetResultString(interp, "invalid parameter: negative len", -1);
715 return JIM_ERR;
718 argc--;
719 argv++;
721 if (argc) {
722 return -1;
724 objPtr = Jim_NewStringObj(interp, NULL, 0);
725 while (neededLen != 0) {
726 int retval;
727 int readlen;
729 if (neededLen == -1) {
730 readlen = AIO_BUF_LEN;
732 else {
733 readlen = (neededLen > AIO_BUF_LEN ? AIO_BUF_LEN : neededLen);
735 retval = af->fops->reader(af, buf, pending ? 1 : readlen);
736 if (retval > 0) {
737 Jim_AppendString(interp, objPtr, buf, retval);
738 if (neededLen != -1) {
739 neededLen -= retval;
741 else if (pending) {
742 /* If pending was specified, after we do the initial read,
743 * we do a second read to fetch any buffered data
745 neededLen = af->fops->pending(af);
748 if (retval <= 0) {
749 break;
752 /* Check for error conditions */
753 if (JimCheckStreamError(interp, af)) {
754 Jim_FreeNewObj(interp, objPtr);
755 return JIM_ERR;
757 if (nonewline) {
758 int len;
759 const char *s = Jim_GetString(objPtr, &len);
761 if (len > 0 && s[len - 1] == '\n') {
762 objPtr->length--;
763 objPtr->bytes[objPtr->length] = '\0';
766 Jim_SetResult(interp, objPtr);
767 return JIM_OK;
770 AioFile *Jim_AioFile(Jim_Interp *interp, Jim_Obj *command)
772 Jim_Cmd *cmdPtr = Jim_GetCommand(interp, command, JIM_ERRMSG);
774 /* XXX: There ought to be a supported API for this */
775 if (cmdPtr && !cmdPtr->isproc && cmdPtr->u.native.cmdProc == JimAioSubCmdProc) {
776 return (AioFile *) cmdPtr->u.native.privData;
778 Jim_SetResultFormatted(interp, "Not a filehandle: \"%#s\"", command);
779 return NULL;
782 FILE *Jim_AioFilehandle(Jim_Interp *interp, Jim_Obj *command)
784 AioFile *af;
786 af = Jim_AioFile(interp, command);
787 if (af == NULL) {
788 return NULL;
791 return af->fp;
794 static int aio_cmd_getfd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
796 AioFile *af = Jim_CmdPrivData(interp);
798 fflush(af->fp);
799 Jim_SetResultInt(interp, fileno(af->fp));
801 return JIM_OK;
804 static int aio_cmd_copy(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
806 AioFile *af = Jim_CmdPrivData(interp);
807 jim_wide count = 0;
808 jim_wide maxlen = JIM_WIDE_MAX;
809 AioFile *outf = Jim_AioFile(interp, argv[0]);
811 if (outf == NULL) {
812 return JIM_ERR;
815 if (argc == 2) {
816 if (Jim_GetWide(interp, argv[1], &maxlen) != JIM_OK) {
817 return JIM_ERR;
821 while (count < maxlen) {
822 /* A reasonable compromise between stack size and speed */
823 char buf[AIO_BUF_LEN];
824 jim_wide len = maxlen - count;
825 if (len > sizeof(buf)) {
826 len = sizeof(buf);
829 len = af->fops->reader(af, buf, len);
830 if (len <= 0) {
831 break;
833 if (outf->fops->writer(outf, buf, len) != len) {
834 break;
836 count += len;
839 if (JimCheckStreamError(interp, af) || JimCheckStreamError(interp, outf)) {
840 return JIM_ERR;
843 Jim_SetResultInt(interp, count);
845 return JIM_OK;
848 static int aio_cmd_gets(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
850 AioFile *af = Jim_CmdPrivData(interp);
851 char buf[AIO_BUF_LEN];
852 Jim_Obj *objPtr;
853 int len;
855 errno = 0;
857 objPtr = Jim_NewStringObj(interp, NULL, 0);
858 while (1) {
859 buf[AIO_BUF_LEN - 1] = '_';
861 if (af->fops->getline(af, buf, AIO_BUF_LEN) == NULL)
862 break;
864 if (buf[AIO_BUF_LEN - 1] == '\0' && buf[AIO_BUF_LEN - 2] != '\n') {
865 Jim_AppendString(interp, objPtr, buf, AIO_BUF_LEN - 1);
867 else {
868 len = strlen(buf);
870 if (len && (buf[len - 1] == '\n')) {
871 /* strip "\n" */
872 len--;
875 Jim_AppendString(interp, objPtr, buf, len);
876 break;
880 if (JimCheckStreamError(interp, af)) {
881 /* I/O error */
882 Jim_FreeNewObj(interp, objPtr);
883 return JIM_ERR;
886 if (argc) {
887 if (Jim_SetVariable(interp, argv[0], objPtr) != JIM_OK) {
888 Jim_FreeNewObj(interp, objPtr);
889 return JIM_ERR;
892 len = Jim_Length(objPtr);
894 if (len == 0 && af->fops->eof(af)) {
895 /* On EOF returns -1 if varName was specified */
896 len = -1;
898 Jim_SetResultInt(interp, len);
900 else {
901 Jim_SetResult(interp, objPtr);
903 return JIM_OK;
906 static int aio_cmd_puts(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
908 AioFile *af = Jim_CmdPrivData(interp);
909 int wlen;
910 const char *wdata;
911 Jim_Obj *strObj;
913 if (argc == 2) {
914 if (!Jim_CompareStringImmediate(interp, argv[0], "-nonewline")) {
915 return -1;
917 strObj = argv[1];
919 else {
920 strObj = argv[0];
923 wdata = Jim_GetString(strObj, &wlen);
924 if (af->fops->writer(af, wdata, wlen) == wlen) {
925 if (argc == 2 || af->fops->writer(af, "\n", 1) == 1) {
926 return JIM_OK;
929 JimAioSetError(interp, af->filename);
930 return JIM_ERR;
933 static int aio_cmd_isatty(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
935 #ifdef HAVE_ISATTY
936 AioFile *af = Jim_CmdPrivData(interp);
937 Jim_SetResultInt(interp, isatty(fileno(af->fp)));
938 #else
939 Jim_SetResultInt(interp, 0);
940 #endif
942 return JIM_OK;
945 #if defined(HAVE_SOCKETS) && !defined(JIM_BOOTSTRAP)
946 static int aio_cmd_recvfrom(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
948 AioFile *af = Jim_CmdPrivData(interp);
949 char *buf;
950 union sockaddr_any sa;
951 long len;
952 socklen_t salen = sizeof(sa);
953 int rlen;
955 if (Jim_GetLong(interp, argv[0], &len) != JIM_OK) {
956 return JIM_ERR;
959 buf = Jim_Alloc(len + 1);
961 rlen = recvfrom(fileno(af->fp), buf, len, 0, &sa.sa, &salen);
962 if (rlen < 0) {
963 Jim_Free(buf);
964 JimAioSetError(interp, NULL);
965 return JIM_ERR;
967 buf[rlen] = 0;
968 Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, buf, rlen));
970 if (argc > 1) {
971 return JimSetVariableSocketAddress(interp, argv[1], &sa, salen);
974 return JIM_OK;
978 static int aio_cmd_sendto(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
980 AioFile *af = Jim_CmdPrivData(interp);
981 int wlen;
982 int len;
983 const char *wdata;
984 union sockaddr_any sa;
985 const char *addr = Jim_String(argv[1]);
986 socklen_t salen;
988 if (JimParseSocketAddress(interp, af->addr_family, addr, &sa, &salen) != JIM_OK) {
989 return JIM_ERR;
991 wdata = Jim_GetString(argv[0], &wlen);
993 /* Note that we don't validate the socket type. Rely on sendto() failing if appropriate */
994 len = sendto(fileno(af->fp), wdata, wlen, 0, &sa.sa, salen);
995 if (len < 0) {
996 JimAioSetError(interp, NULL);
997 return JIM_ERR;
999 Jim_SetResultInt(interp, len);
1000 return JIM_OK;
1003 static int aio_cmd_accept(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1005 AioFile *af = Jim_CmdPrivData(interp);
1006 int sock;
1007 union sockaddr_any sa;
1008 socklen_t salen = sizeof(sa);
1010 sock = accept(af->fd, &sa.sa, &salen);
1011 if (sock < 0) {
1012 JimAioSetError(interp, NULL);
1013 return JIM_ERR;
1016 if (argc > 0) {
1017 if (JimSetVariableSocketAddress(interp, argv[0], &sa, salen) != JIM_OK) {
1018 return JIM_ERR;
1022 /* Create the file command */
1023 return JimMakeChannel(interp, NULL, sock, Jim_NewStringObj(interp, "accept", -1),
1024 "aio.sockstream%ld", af->addr_family, "r+", AIO_NODELETE) ? JIM_OK : JIM_ERR;
1027 static int aio_cmd_sockname(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1029 AioFile *af = Jim_CmdPrivData(interp);
1030 Jim_Obj *objPtr = aio_sockname(interp, af);
1032 if (objPtr == NULL) {
1033 JimAioSetError(interp, NULL);
1034 return JIM_ERR;
1036 Jim_SetResult(interp, objPtr);
1037 return JIM_OK;
1040 static int aio_cmd_peername(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1042 AioFile *af = Jim_CmdPrivData(interp);
1043 union sockaddr_any sa;
1044 socklen_t salen = sizeof(sa);
1046 if (getpeername(af->fd, &sa.sa, &salen) < 0) {
1047 JimAioSetError(interp, NULL);
1048 return JIM_ERR;
1050 Jim_SetResult(interp, JimFormatSocketAddress(interp, &sa, salen));
1051 return JIM_OK;
1054 static int aio_cmd_listen(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1056 AioFile *af = Jim_CmdPrivData(interp);
1057 long backlog;
1059 if (Jim_GetLong(interp, argv[0], &backlog) != JIM_OK) {
1060 return JIM_ERR;
1063 if (listen(af->fd, backlog)) {
1064 JimAioSetError(interp, NULL);
1065 return JIM_ERR;
1068 return JIM_OK;
1070 #endif /* JIM_BOOTSTRAP */
1072 static int aio_cmd_flush(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1074 AioFile *af = Jim_CmdPrivData(interp);
1076 if (fflush(af->fp) == EOF) {
1077 JimAioSetError(interp, af->filename);
1078 return JIM_ERR;
1080 return JIM_OK;
1083 static int aio_cmd_eof(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1085 AioFile *af = Jim_CmdPrivData(interp);
1087 Jim_SetResultInt(interp, !!af->fops->eof(af));
1088 return JIM_OK;
1091 static int aio_cmd_close(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1093 if (argc == 3) {
1094 int option = -1;
1095 #if defined(HAVE_SOCKETS)
1096 static const char * const options[] = { "r", "w", "-nodelete", NULL };
1097 enum { OPT_R, OPT_W, OPT_NODELETE };
1098 AioFile *af = Jim_CmdPrivData(interp);
1100 if (Jim_GetEnum(interp, argv[2], options, &option, NULL, JIM_ERRMSG) != JIM_OK) {
1101 return JIM_ERR;
1103 #endif
1104 switch (option) {
1105 #if defined(HAVE_SHUTDOWN)
1106 case OPT_R:
1107 case OPT_W:
1108 if (shutdown(af->fd, option == OPT_R ? SHUT_RD : SHUT_WR) == 0) {
1109 return JIM_OK;
1111 JimAioSetError(interp, NULL);
1112 return JIM_ERR;
1113 #endif
1114 #if UNIX_SOCKETS
1115 case OPT_NODELETE:
1116 if (af->addr_family == PF_UNIX) {
1117 af->flags |= AIO_NODELETE;
1118 break;
1120 /* fall through */
1121 #endif
1122 default:
1123 Jim_SetResultString(interp, "not supported", -1);
1124 return JIM_ERR;
1128 return Jim_DeleteCommand(interp, Jim_String(argv[0]));
1131 static int aio_cmd_seek(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1133 AioFile *af = Jim_CmdPrivData(interp);
1134 int orig = SEEK_SET;
1135 jim_wide offset;
1137 if (argc == 2) {
1138 if (Jim_CompareStringImmediate(interp, argv[1], "start"))
1139 orig = SEEK_SET;
1140 else if (Jim_CompareStringImmediate(interp, argv[1], "current"))
1141 orig = SEEK_CUR;
1142 else if (Jim_CompareStringImmediate(interp, argv[1], "end"))
1143 orig = SEEK_END;
1144 else {
1145 return -1;
1148 if (Jim_GetWide(interp, argv[0], &offset) != JIM_OK) {
1149 return JIM_ERR;
1151 if (fseeko(af->fp, offset, orig) == -1) {
1152 JimAioSetError(interp, af->filename);
1153 return JIM_ERR;
1155 return JIM_OK;
1158 static int aio_cmd_tell(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1160 AioFile *af = Jim_CmdPrivData(interp);
1162 Jim_SetResultInt(interp, ftello(af->fp));
1163 return JIM_OK;
1166 static int aio_cmd_filename(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1168 AioFile *af = Jim_CmdPrivData(interp);
1170 Jim_SetResult(interp, af->filename);
1171 return JIM_OK;
1174 #ifdef O_NDELAY
1175 static int aio_cmd_ndelay(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1177 AioFile *af = Jim_CmdPrivData(interp);
1179 int fmode = fcntl(af->fd, F_GETFL);
1181 if (argc) {
1182 long nb;
1184 if (Jim_GetLong(interp, argv[0], &nb) != JIM_OK) {
1185 return JIM_ERR;
1187 if (nb) {
1188 fmode |= O_NDELAY;
1190 else {
1191 fmode &= ~O_NDELAY;
1193 (void)fcntl(af->fd, F_SETFL, fmode);
1195 Jim_SetResultInt(interp, (fmode & O_NONBLOCK) ? 1 : 0);
1196 return JIM_OK;
1198 #endif
1200 #if defined(HAVE_SOCKETS) && !defined(JIM_BOOTSTRAP)
1201 #define SOCKOPT_BOOL 0
1202 #define SOCKOPT_INT 1
1203 #define SOCKOPT_TIMEVAL 2 /* not currently supported */
1205 static const struct sockopt_def {
1206 const char *name;
1207 int level;
1208 int opt;
1209 int type; /* SOCKOPT_xxx */
1210 } sockopts[] = {
1211 #ifdef SOL_SOCKET
1212 #ifdef SO_BROADCAST
1213 { "broadcast", SOL_SOCKET, SO_BROADCAST },
1214 #endif
1215 #ifdef SO_DEBUG
1216 { "debug", SOL_SOCKET, SO_DEBUG },
1217 #endif
1218 #ifdef SO_KEEPALIVE
1219 { "keepalive", SOL_SOCKET, SO_KEEPALIVE },
1220 #endif
1221 #ifdef SO_NOSIGPIPE
1222 { "nosigpipe", SOL_SOCKET, SO_NOSIGPIPE },
1223 #endif
1224 #ifdef SO_OOBINLINE
1225 { "oobinline", SOL_SOCKET, SO_OOBINLINE },
1226 #endif
1227 #ifdef SO_SNDBUF
1228 { "sndbuf", SOL_SOCKET, SO_SNDBUF, SOCKOPT_INT },
1229 #endif
1230 #ifdef SO_RCVBUF
1231 { "rcvbuf", SOL_SOCKET, SO_RCVBUF, SOCKOPT_INT },
1232 #endif
1233 #if 0 && defined(SO_SNDTIMEO)
1234 { "sndtimeo", SOL_SOCKET, SO_SNDTIMEO, SOCKOPT_TIMEVAL },
1235 #endif
1236 #if 0 && defined(SO_RCVTIMEO)
1237 { "rcvtimeo", SOL_SOCKET, SO_RCVTIMEO, SOCKOPT_TIMEVAL },
1238 #endif
1239 #endif
1240 #ifdef IPPROTO_TCP
1241 #ifdef TCP_NODELAY
1242 { "tcp_nodelay", IPPROTO_TCP, TCP_NODELAY },
1243 #endif
1244 #endif
1247 static int aio_cmd_sockopt(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1249 AioFile *af = Jim_CmdPrivData(interp);
1250 size_t i;
1252 if (argc == 0) {
1253 Jim_Obj *dictObjPtr = Jim_NewListObj(interp, NULL, 0);
1254 for (i = 0; i < sizeof(sockopts) / sizeof(*sockopts); i++) {
1255 int value = 0;
1256 socklen_t len = sizeof(value);
1257 if (getsockopt(af->fd, sockopts[i].level, sockopts[i].opt, (void *)&value, &len) == 0) {
1258 if (sockopts[i].type == SOCKOPT_BOOL) {
1259 value = !!value;
1261 Jim_ListAppendElement(interp, dictObjPtr, Jim_NewStringObj(interp, sockopts[i].name, -1));
1262 Jim_ListAppendElement(interp, dictObjPtr, Jim_NewIntObj(interp, value));
1265 Jim_SetResult(interp, dictObjPtr);
1266 return JIM_OK;
1268 if (argc == 1) {
1269 return -1;
1272 /* Set an option */
1273 for (i = 0; i < sizeof(sockopts) / sizeof(*sockopts); i++) {
1274 if (strcmp(Jim_String(argv[0]), sockopts[i].name) == 0) {
1275 int on;
1276 if (sockopts[i].type == SOCKOPT_BOOL) {
1277 if (Jim_GetBoolean(interp, argv[1], &on) != JIM_OK) {
1278 return JIM_ERR;
1281 else {
1282 long longval;
1283 if (Jim_GetLong(interp, argv[1], &longval) != JIM_OK) {
1284 return JIM_ERR;
1286 on = longval;
1288 if (setsockopt(af->fd, sockopts[i].level, sockopts[i].opt, (void *)&on, sizeof(on)) < 0) {
1289 Jim_SetResultFormatted(interp, "Failed to set %#s: %s", argv[0], strerror(errno));
1290 return JIM_ERR;
1292 return JIM_OK;
1295 /* Not found */
1296 Jim_SetResultFormatted(interp, "Unknown sockopt %#s", argv[0]);
1297 return JIM_ERR;
1299 #endif /* JIM_BOOTSTRAP */
1301 #ifdef HAVE_FSYNC
1302 static int aio_cmd_sync(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1304 AioFile *af = Jim_CmdPrivData(interp);
1306 fflush(af->fp);
1307 fsync(af->fd);
1308 return JIM_OK;
1310 #endif
1312 static int aio_cmd_buffering(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1314 AioFile *af = Jim_CmdPrivData(interp);
1316 static const char * const options[] = {
1317 "none",
1318 "line",
1319 "full",
1320 NULL
1322 enum
1324 OPT_NONE,
1325 OPT_LINE,
1326 OPT_FULL,
1328 int option;
1330 if (Jim_GetEnum(interp, argv[0], options, &option, NULL, JIM_ERRMSG) != JIM_OK) {
1331 return JIM_ERR;
1333 switch (option) {
1334 case OPT_NONE:
1335 setvbuf(af->fp, NULL, _IONBF, 0);
1336 break;
1337 case OPT_LINE:
1338 setvbuf(af->fp, NULL, _IOLBF, BUFSIZ);
1339 break;
1340 case OPT_FULL:
1341 setvbuf(af->fp, NULL, _IOFBF, BUFSIZ);
1342 break;
1344 return JIM_OK;
1347 #ifdef jim_ext_eventloop
1348 static void JimAioFileEventFinalizer(Jim_Interp *interp, void *clientData)
1350 Jim_Obj **objPtrPtr = clientData;
1352 Jim_DecrRefCount(interp, *objPtrPtr);
1353 *objPtrPtr = NULL;
1356 static int JimAioFileEventHandler(Jim_Interp *interp, void *clientData, int mask)
1358 Jim_Obj **objPtrPtr = clientData;
1360 return Jim_EvalObjBackground(interp, *objPtrPtr);
1363 static int aio_eventinfo(Jim_Interp *interp, AioFile * af, unsigned mask, Jim_Obj **scriptHandlerObj,
1364 int argc, Jim_Obj * const *argv)
1366 if (argc == 0) {
1367 /* Return current script */
1368 if (*scriptHandlerObj) {
1369 Jim_SetResult(interp, *scriptHandlerObj);
1371 return JIM_OK;
1374 if (*scriptHandlerObj) {
1375 /* Delete old handler */
1376 Jim_DeleteFileHandler(interp, af->fd, mask);
1379 /* Now possibly add the new script(s) */
1380 if (Jim_Length(argv[0]) == 0) {
1381 /* Empty script, so done */
1382 return JIM_OK;
1385 /* A new script to add */
1386 Jim_IncrRefCount(argv[0]);
1387 *scriptHandlerObj = argv[0];
1389 Jim_CreateFileHandler(interp, af->fd, mask,
1390 JimAioFileEventHandler, scriptHandlerObj, JimAioFileEventFinalizer);
1392 return JIM_OK;
1395 static int aio_cmd_readable(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1397 AioFile *af = Jim_CmdPrivData(interp);
1399 return aio_eventinfo(interp, af, JIM_EVENT_READABLE, &af->rEvent, argc, argv);
1402 static int aio_cmd_writable(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1404 AioFile *af = Jim_CmdPrivData(interp);
1406 return aio_eventinfo(interp, af, JIM_EVENT_WRITABLE, &af->wEvent, argc, argv);
1409 static int aio_cmd_onexception(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1411 AioFile *af = Jim_CmdPrivData(interp);
1413 return aio_eventinfo(interp, af, JIM_EVENT_EXCEPTION, &af->eEvent, argc, argv);
1415 #endif
1417 #if defined(JIM_SSL) && !defined(JIM_BOOTSTRAP)
1418 static int aio_cmd_ssl(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1420 AioFile *af = Jim_CmdPrivData(interp);
1421 SSL *ssl;
1422 SSL_CTX *ssl_ctx;
1423 int server = 0;
1424 const char *sni = NULL;
1426 if (argc > 2) {
1427 static const char * const options[] = { "-server", "-sni", NULL };
1428 enum { OPT_SERVER, OPT_SNI };
1429 int option;
1431 if (Jim_GetEnum(interp, argv[2], options, &option, NULL, JIM_ERRMSG) != JIM_OK) {
1432 return JIM_ERR;
1434 switch (option) {
1435 case OPT_SERVER:
1436 if (argc != 4 && argc != 5) {
1437 return JIM_ERR;
1439 server = 1;
1440 break;
1442 case OPT_SNI:
1443 if (argc != 4) {
1444 return JIM_ERR;
1446 sni = Jim_String(argv[3]);
1447 break;
1451 if (af->ssl) {
1452 Jim_SetResultFormatted(interp, "%#s: stream is already ssl", argv[0]);
1453 return JIM_ERR;
1456 ssl_ctx = JimAioSslCtx(interp);
1457 if (ssl_ctx == NULL) {
1458 return JIM_ERR;
1461 ssl = SSL_new(ssl_ctx);
1462 if (ssl == NULL) {
1463 goto out;
1466 SSL_set_cipher_list(ssl, "ALL");
1468 if (SSL_set_fd(ssl, fileno(af->fp)) == 0) {
1469 goto out;
1472 if (server) {
1473 const char *certfile = Jim_String(argv[3]);
1474 const char *keyfile = (argc == 4) ? certfile : Jim_String(argv[4]);
1475 if (SSL_use_certificate_file(ssl, certfile, SSL_FILETYPE_PEM) != 1) {
1476 goto out;
1478 if (SSL_use_PrivateKey_file(ssl, keyfile, SSL_FILETYPE_PEM) != 1) {
1479 goto out;
1482 if (SSL_accept(ssl) != 1) {
1483 goto out;
1486 else {
1487 if (sni) {
1488 /* Set server name indication if requested */
1489 SSL_set_tlsext_host_name(ssl, sni);
1491 if (SSL_connect(ssl) != 1) {
1492 goto out;
1496 af->ssl = ssl;
1497 af->fops = &ssl_fops;
1499 /* Set the command name as the result */
1500 Jim_SetResult(interp, argv[0]);
1502 return JIM_OK;
1504 out:
1505 if (ssl) {
1506 SSL_free(ssl);
1508 Jim_SetResultString(interp, ERR_error_string(ERR_get_error(), NULL), -1);
1509 return JIM_ERR;
1512 static int aio_cmd_verify(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1514 AioFile *af = Jim_CmdPrivData(interp);
1515 int ret;
1517 if (!af->fops->verify) {
1518 return JIM_OK;
1521 ret = af->fops->verify(af);
1522 if (ret != JIM_OK) {
1523 if (JimCheckStreamError(interp, af) == JIM_OK) {
1524 Jim_SetResultString(interp, "failed to verify the connection authenticity", -1);
1527 return ret;
1529 #endif /* JIM_BOOTSTRAP */
1531 #if defined(HAVE_STRUCT_FLOCK) && !defined(JIM_BOOTSTRAP)
1532 static int aio_cmd_lock(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1534 AioFile *af = Jim_CmdPrivData(interp);
1535 struct flock fl;
1536 int lockmode = F_SETLK;
1538 if (argc == 1) {
1539 if (!Jim_CompareStringImmediate(interp, argv[0], "-wait")) {
1540 return -1;
1542 lockmode = F_SETLKW;
1545 fl.l_start = 0;
1546 fl.l_len = 0;
1547 fl.l_type = F_WRLCK;
1548 fl.l_whence = SEEK_SET;
1550 switch (fcntl(af->fd, lockmode, &fl))
1552 case 0:
1553 Jim_SetResultInt(interp, 1);
1554 break;
1555 case -1:
1556 if (errno == EACCES || errno == EAGAIN)
1557 Jim_SetResultInt(interp, 0);
1558 else
1560 Jim_SetResultFormatted(interp, "lock failed: %s",
1561 strerror(errno));
1562 return JIM_ERR;
1564 break;
1565 default:
1566 Jim_SetResultInt(interp, 0);
1567 break;
1570 return JIM_OK;
1573 static int aio_cmd_unlock(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1575 AioFile *af = Jim_CmdPrivData(interp);
1576 struct flock fl;
1577 fl.l_start = 0;
1578 fl.l_len = 0;
1579 fl.l_type = F_UNLCK;
1580 fl.l_whence = SEEK_SET;
1582 Jim_SetResultInt(interp, fcntl(af->fd, F_SETLK, &fl) == 0);
1583 return JIM_OK;
1585 #endif /* JIM_BOOTSTRAP */
1587 #if defined(HAVE_TERMIOS_H) && !defined(JIM_BOOTSTRAP)
1588 static int aio_cmd_tty(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1590 AioFile *af = Jim_CmdPrivData(interp);
1591 Jim_Obj *dictObjPtr;
1592 int ret;
1594 if (argc == 0) {
1595 /* get the current settings as a dictionary */
1596 dictObjPtr = Jim_GetTtySettings(interp, af->fd);
1597 if (dictObjPtr == NULL) {
1598 JimAioSetError(interp, NULL);
1599 return JIM_ERR;
1601 Jim_SetResult(interp, dictObjPtr);
1602 return JIM_OK;
1605 if (argc > 1) {
1606 /* Convert name value arguments to a dictionary */
1607 dictObjPtr = Jim_NewListObj(interp, argv, argc);
1609 else {
1610 /* The settings are already given as a list */
1611 dictObjPtr = argv[0];
1613 Jim_IncrRefCount(dictObjPtr);
1615 if (Jim_ListLength(interp, dictObjPtr) % 2) {
1616 /* Must be a valid dictionary */
1617 Jim_DecrRefCount(interp, dictObjPtr);
1618 return -1;
1621 ret = Jim_SetTtySettings(interp, af->fd, dictObjPtr);
1622 if (ret < 0) {
1623 JimAioSetError(interp, NULL);
1624 ret = JIM_ERR;
1626 Jim_DecrRefCount(interp, dictObjPtr);
1628 return ret;
1630 #endif /* JIM_BOOTSTRAP */
1632 static const jim_subcmd_type aio_command_table[] = {
1633 { "read",
1634 "?-nonewline|-pending|len?",
1635 aio_cmd_read,
1638 /* Description: Read and return bytes from the stream. To eof if no len. */
1640 { "copyto",
1641 "handle ?size?",
1642 aio_cmd_copy,
1645 /* Description: Copy up to 'size' bytes to the given filehandle, or to eof if no size. */
1647 { "getfd",
1648 NULL,
1649 aio_cmd_getfd,
1652 /* Description: Internal command to return the underlying file descriptor. */
1654 { "gets",
1655 "?var?",
1656 aio_cmd_gets,
1659 /* Description: Read one line and return it or store it in the var */
1661 { "puts",
1662 "?-nonewline? str",
1663 aio_cmd_puts,
1666 /* Description: Write the string, with newline unless -nonewline */
1668 { "isatty",
1669 NULL,
1670 aio_cmd_isatty,
1673 /* Description: Is the file descriptor a tty? */
1675 #if defined(HAVE_SOCKETS) && !defined(JIM_BOOTSTRAP)
1676 { "recvfrom",
1677 "len ?addrvar?",
1678 aio_cmd_recvfrom,
1681 /* Description: Receive up to 'len' bytes on the socket. Sets 'addrvar' with receive address, if set */
1683 { "sendto",
1684 "str address",
1685 aio_cmd_sendto,
1688 /* Description: Send 'str' to the given address (dgram only) */
1690 { "accept",
1691 "?addrvar?",
1692 aio_cmd_accept,
1695 /* Description: Server socket only: Accept a connection and return stream */
1697 { "listen",
1698 "backlog",
1699 aio_cmd_listen,
1702 /* Description: Set the listen backlog for server socket */
1704 { "sockopt",
1705 "?opt 0|1?",
1706 aio_cmd_sockopt,
1709 /* Description: Return a dictionary of sockopts, or set the value of a sockopt */
1711 { "sockname",
1712 NULL,
1713 aio_cmd_sockname,
1716 /* Description: Returns the local address of the socket, if any */
1718 { "peername",
1719 NULL,
1720 aio_cmd_peername,
1723 /* Description: Returns the remote address of the socket, if any */
1725 #endif /* JIM_BOOTSTRAP */
1726 { "flush",
1727 NULL,
1728 aio_cmd_flush,
1731 /* Description: Flush the stream */
1733 { "eof",
1734 NULL,
1735 aio_cmd_eof,
1738 /* Description: Returns 1 if stream is at eof */
1740 { "close",
1741 "?r(ead)|w(rite)?",
1742 aio_cmd_close,
1745 JIM_MODFLAG_FULLARGV,
1746 /* Description: Closes the stream. */
1748 { "seek",
1749 "offset ?start|current|end",
1750 aio_cmd_seek,
1753 /* Description: Seeks in the stream (default 'current') */
1755 { "tell",
1756 NULL,
1757 aio_cmd_tell,
1760 /* Description: Returns the current seek position */
1762 { "filename",
1763 NULL,
1764 aio_cmd_filename,
1767 /* Description: Returns the original filename */
1769 #ifdef O_NDELAY
1770 { "ndelay",
1771 "?0|1?",
1772 aio_cmd_ndelay,
1775 /* Description: Set O_NDELAY (if arg). Returns current/new setting. */
1777 #endif
1778 #ifdef HAVE_FSYNC
1779 { "sync",
1780 NULL,
1781 aio_cmd_sync,
1784 /* Description: Flush and fsync() the stream */
1786 #endif
1787 { "buffering",
1788 "none|line|full",
1789 aio_cmd_buffering,
1792 /* Description: Sets buffering */
1794 #ifdef jim_ext_eventloop
1795 { "readable",
1796 "?readable-script?",
1797 aio_cmd_readable,
1800 /* Description: Returns script, or invoke readable-script when readable, {} to remove */
1802 { "writable",
1803 "?writable-script?",
1804 aio_cmd_writable,
1807 /* Description: Returns script, or invoke writable-script when writable, {} to remove */
1809 { "onexception",
1810 "?exception-script?",
1811 aio_cmd_onexception,
1814 /* Description: Returns script, or invoke exception-script when oob data, {} to remove */
1816 #endif
1817 #if !defined(JIM_BOOTSTRAP)
1818 #if defined(JIM_SSL)
1819 { "ssl",
1820 "?-server cert ?priv?|-sni servername?",
1821 aio_cmd_ssl,
1824 JIM_MODFLAG_FULLARGV
1825 /* Description: Wraps a stream socket with SSL/TLS and returns a new channel */
1827 { "verify",
1828 NULL,
1829 aio_cmd_verify,
1832 /* Description: Verifies the certificate of a SSL/TLS channel */
1834 #endif
1835 #if defined(HAVE_STRUCT_FLOCK)
1836 { "lock ?-wait?",
1837 NULL,
1838 aio_cmd_lock,
1841 /* Description: Attempt to get a lock, possibly waiting */
1843 { "unlock",
1844 NULL,
1845 aio_cmd_unlock,
1848 /* Description: Relase a lock. */
1850 #endif
1851 #if defined(HAVE_TERMIOS_H)
1852 { "tty",
1853 "?baud rate? ?data bits? ?stop bits? ?parity even|odd|none? ?handshake xonxoff|rtscts|none? ?input raw|cooked? ?output raw|cooked? ?echo 0|1? ?vmin n? ?vtime n?",
1854 aio_cmd_tty,
1857 /* Description: Get or set tty settings - valid only on a tty */
1859 #endif
1860 #endif /* JIM_BOOTSTRAP */
1861 { NULL }
1864 static int JimAioSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1866 return Jim_CallSubCmd(interp, Jim_ParseSubCmd(interp, aio_command_table, argc, argv), argc, argv);
1869 static int JimAioOpenCommand(Jim_Interp *interp, int argc,
1870 Jim_Obj *const *argv)
1872 const char *mode;
1874 if (argc != 2 && argc != 3) {
1875 Jim_WrongNumArgs(interp, 1, argv, "filename ?mode?");
1876 return JIM_ERR;
1879 mode = (argc == 3) ? Jim_String(argv[2]) : "r";
1881 #ifdef jim_ext_tclcompat
1883 const char *filename = Jim_String(argv[1]);
1885 /* If the filename starts with '|', use popen instead */
1886 if (*filename == '|') {
1887 Jim_Obj *evalObj[3];
1889 evalObj[0] = Jim_NewStringObj(interp, "::popen", -1);
1890 evalObj[1] = Jim_NewStringObj(interp, filename + 1, -1);
1891 evalObj[2] = Jim_NewStringObj(interp, mode, -1);
1893 return Jim_EvalObjVector(interp, 3, evalObj);
1896 #endif
1897 return JimMakeChannel(interp, NULL, -1, argv[1], "aio.handle%ld", 0, mode, 0) ? JIM_OK : JIM_ERR;
1900 #if defined(JIM_SSL) && !defined(JIM_BOOTSTRAP)
1901 static void JimAioSslContextDelProc(struct Jim_Interp *interp, void *privData)
1903 SSL_CTX_free((SSL_CTX *)privData);
1904 ERR_free_strings();
1907 #ifdef USE_TLSv1_2_method
1908 #define TLS_method TLSv1_2_method
1909 #endif
1911 static SSL_CTX *JimAioSslCtx(Jim_Interp *interp)
1913 SSL_CTX *ssl_ctx = (SSL_CTX *)Jim_GetAssocData(interp, "ssl_ctx");
1914 if (ssl_ctx == NULL) {
1915 SSL_load_error_strings();
1916 SSL_library_init();
1917 ssl_ctx = SSL_CTX_new(TLS_method());
1918 if (ssl_ctx && SSL_CTX_set_default_verify_paths(ssl_ctx)) {
1919 SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL);
1920 Jim_SetAssocData(interp, "ssl_ctx", JimAioSslContextDelProc, ssl_ctx);
1921 } else {
1922 Jim_SetResultString(interp, ERR_error_string(ERR_get_error(), NULL), -1);
1925 return ssl_ctx;
1927 #endif /* JIM_BOOTSTRAP */
1930 * Creates a channel for fh/fd/filename.
1932 * If fh is not NULL, uses that as the channel (and sets AIO_KEEPOPEN).
1933 * Otherwise, if fd is >= 0, uses that as the channel.
1934 * Otherwise opens 'filename' with mode 'mode'.
1936 * hdlfmt is a sprintf format for the filehandle. Anything with %ld at the end will do.
1937 * mode is used for open or fdopen.
1939 * Creates the command and sets the name as the current result.
1940 * Returns the AioFile pointer on sucess or NULL on failure.
1942 static AioFile *JimMakeChannel(Jim_Interp *interp, FILE *fh, int fd, Jim_Obj *filename,
1943 const char *hdlfmt, int family, const char *mode, int flags)
1945 AioFile *af;
1946 char buf[AIO_CMD_LEN];
1948 snprintf(buf, sizeof(buf), hdlfmt, Jim_GetId(interp));
1949 if (!filename) {
1950 filename = Jim_NewStringObj(interp, buf, -1);
1953 Jim_IncrRefCount(filename);
1955 if (fh == NULL) {
1956 if (fd >= 0) {
1957 #ifndef JIM_ANSIC
1958 fh = fdopen(fd, mode);
1959 #endif
1961 else
1962 fh = fopen(Jim_String(filename), mode);
1964 if (fh == NULL) {
1965 JimAioSetError(interp, filename);
1966 #ifndef JIM_ANSIC
1967 if (fd >= 0) {
1968 close(fd);
1970 #endif
1971 Jim_DecrRefCount(interp, filename);
1972 return NULL;
1976 /* Create the file command */
1977 af = Jim_Alloc(sizeof(*af));
1978 memset(af, 0, sizeof(*af));
1979 af->fp = fh;
1980 af->filename = filename;
1981 af->flags = flags;
1982 #ifndef JIM_ANSIC
1983 af->fd = fileno(fh);
1984 #ifdef FD_CLOEXEC
1985 if ((flags & AIO_KEEPOPEN) == 0) {
1986 (void)fcntl(af->fd, F_SETFD, FD_CLOEXEC);
1988 #endif
1989 #endif
1990 af->addr_family = family;
1991 af->fops = &stdio_fops;
1992 af->ssl = NULL;
1994 Jim_CreateCommand(interp, buf, JimAioSubCmdProc, af, JimAioDelProc);
1996 /* Note that the command must use the global namespace, even if
1997 * the current namespace is something different
1999 Jim_SetResult(interp, Jim_MakeGlobalNamespaceName(interp, Jim_NewStringObj(interp, buf, -1)));
2001 return af;
2004 #if defined(HAVE_PIPE) || (defined(HAVE_SOCKETPAIR) && UNIX_SOCKETS)
2006 * Create a pair of channels. e.g. from pipe() or socketpair()
2008 static int JimMakeChannelPair(Jim_Interp *interp, int p[2], Jim_Obj *filename,
2009 const char *hdlfmt, int family, const char * const mode[2])
2011 if (JimMakeChannel(interp, NULL, p[0], filename, hdlfmt, family, mode[0], 0)) {
2012 Jim_Obj *objPtr = Jim_NewListObj(interp, NULL, 0);
2013 Jim_ListAppendElement(interp, objPtr, Jim_GetResult(interp));
2014 if (JimMakeChannel(interp, NULL, p[1], filename, hdlfmt, family, mode[1], 0)) {
2015 Jim_ListAppendElement(interp, objPtr, Jim_GetResult(interp));
2016 Jim_SetResult(interp, objPtr);
2017 return JIM_OK;
2021 /* Can only be here if fdopen() failed */
2022 close(p[0]);
2023 close(p[1]);
2024 JimAioSetError(interp, NULL);
2025 return JIM_ERR;
2027 #endif
2029 #ifdef HAVE_PIPE
2030 static int JimAioPipeCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
2032 int p[2];
2033 static const char * const mode[2] = { "r", "w" };
2035 if (argc != 1) {
2036 Jim_WrongNumArgs(interp, 1, argv, "");
2037 return JIM_ERR;
2040 if (pipe(p) != 0) {
2041 JimAioSetError(interp, NULL);
2042 return JIM_ERR;
2045 return JimMakeChannelPair(interp, p, argv[0], "aio.pipe%ld", 0, mode);
2047 #endif
2049 #if defined(HAVE_SOCKETS) && !defined(JIM_BOOTSTRAP)
2051 static int JimAioSockCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
2053 const char *socktypes[] = {
2054 "unix",
2055 "unix.server",
2056 "unix.dgram",
2057 "unix.dgram.server",
2058 "dgram",
2059 "dgram.server",
2060 "stream",
2061 "stream.server",
2062 "pipe",
2063 "pair",
2064 NULL
2066 enum
2068 SOCK_UNIX,
2069 SOCK_UNIX_SERVER,
2070 SOCK_UNIX_DGRAM,
2071 SOCK_UNIX_DGRAM_SERVER,
2072 SOCK_DGRAM_CLIENT,
2073 SOCK_DGRAM_SERVER,
2074 SOCK_STREAM_CLIENT,
2075 SOCK_STREAM_SERVER,
2076 SOCK_STREAM_PIPE,
2077 SOCK_STREAM_SOCKETPAIR,
2079 int socktype;
2080 int sock;
2081 const char *addr = NULL;
2082 const char *bind_addr = NULL;
2083 const char *connect_addr = NULL;
2084 union sockaddr_any sa;
2085 socklen_t salen;
2086 int on = 1;
2087 int reuse = 0;
2088 int do_listen = 0;
2089 int family = PF_INET;
2090 int type = SOCK_STREAM;
2091 Jim_Obj *argv0 = argv[0];
2092 int ipv6 = 0;
2094 if (argc > 1 && Jim_CompareStringImmediate(interp, argv[1], "-ipv6")) {
2095 if (!IPV6) {
2096 Jim_SetResultString(interp, "ipv6 not supported", -1);
2097 return JIM_ERR;
2099 ipv6 = 1;
2100 family = PF_INET6;
2102 argc -= ipv6;
2103 argv += ipv6;
2105 if (argc < 2) {
2106 wrongargs:
2107 Jim_WrongNumArgs(interp, 1, &argv0, "?-ipv6? type ?address?");
2108 return JIM_ERR;
2111 if (Jim_GetEnum(interp, argv[1], socktypes, &socktype, "socket type", JIM_ERRMSG) != JIM_OK)
2112 return Jim_CheckShowCommands(interp, argv[1], socktypes);
2114 Jim_SetEmptyResult(interp);
2116 if (argc > 2) {
2117 addr = Jim_String(argv[2]);
2120 #if defined(HAVE_SOCKETPAIR) && UNIX_SOCKETS
2121 if (socktype == SOCK_STREAM_SOCKETPAIR) {
2122 int p[2];
2123 static const char * const mode[2] = { "r+", "r+" };
2125 if (addr || ipv6) {
2126 goto wrongargs;
2129 if (socketpair(PF_UNIX, SOCK_STREAM, 0, p) < 0) {
2130 JimAioSetError(interp, NULL);
2131 return JIM_ERR;
2133 return JimMakeChannelPair(interp, p, argv[1], "aio.sockpair%ld", PF_UNIX, mode);
2135 #endif
2137 #if defined(HAVE_PIPE)
2138 if (socktype == SOCK_STREAM_PIPE) {
2139 if (addr || ipv6) {
2140 goto wrongargs;
2142 return JimAioPipeCommand(interp, 1, &argv[1]);
2144 #endif
2146 /* Now all these socket types are very similar */
2147 switch (socktype) {
2148 case SOCK_DGRAM_CLIENT:
2149 connect_addr = addr;
2150 type = SOCK_DGRAM;
2151 break;
2153 case SOCK_STREAM_CLIENT:
2154 if (addr == NULL) {
2155 goto wrongargs;
2157 connect_addr = addr;
2158 break;
2160 case SOCK_STREAM_SERVER:
2161 if (addr == NULL) {
2162 goto wrongargs;
2164 bind_addr = addr;
2165 reuse = 1;
2166 do_listen = 1;
2167 break;
2169 case SOCK_DGRAM_SERVER:
2170 if (addr == NULL) {
2171 goto wrongargs;
2173 bind_addr = addr;
2174 type = SOCK_DGRAM;
2175 reuse = 1;
2176 break;
2178 #if UNIX_SOCKETS
2179 case SOCK_UNIX:
2180 if (addr == NULL) {
2181 goto wrongargs;
2183 connect_addr = addr;
2184 family = PF_UNIX;
2185 break;
2187 case SOCK_UNIX_DGRAM:
2188 connect_addr = addr;
2189 type = SOCK_DGRAM;
2190 family = PF_UNIX;
2191 /* A dgram unix domain socket client needs to bind
2192 * to a temporary address to allow the server to
2193 * send responses
2196 int tmpfd = Jim_MakeTempFile(interp, NULL, 1);
2197 if (tmpfd < 0) {
2198 return JIM_ERR;
2200 close(tmpfd);
2201 /* This will be valid until a result is next set, which is long enough here */
2202 bind_addr = Jim_String(Jim_GetResult(interp));
2204 break;
2206 case SOCK_UNIX_SERVER:
2207 if (addr == NULL) {
2208 goto wrongargs;
2210 bind_addr = addr;
2211 family = PF_UNIX;
2212 do_listen = 1;
2213 break;
2215 case SOCK_UNIX_DGRAM_SERVER:
2216 if (addr == NULL) {
2217 goto wrongargs;
2219 bind_addr = addr;
2220 type = SOCK_DGRAM;
2221 family = PF_UNIX;
2222 break;
2223 #endif
2225 default:
2226 Jim_SetResultString(interp, "Unsupported socket type", -1);
2227 return JIM_ERR;
2230 /* Now do all the steps necessary for the given socket type */
2231 sock = socket(family, type, 0);
2232 if (sock < 0) {
2233 JimAioSetError(interp, NULL);
2234 return JIM_ERR;
2236 if (bind_addr) {
2237 if (JimParseSocketAddress(interp, family, bind_addr, &sa, &salen) != JIM_OK) {
2238 close(sock);
2239 return JIM_ERR;
2241 if (reuse) {
2242 setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on));
2244 if (bind(sock, &sa.sa, salen)) {
2245 Jim_SetResultFormatted(interp, "%s: bind: %s", bind_addr, strerror(errno));
2246 close(sock);
2247 return JIM_ERR;
2250 if (connect_addr) {
2251 if (JimParseSocketAddress(interp, family, connect_addr, &sa, &salen) != JIM_OK) {
2252 close(sock);
2253 return JIM_ERR;
2255 if (connect(sock, &sa.sa, salen)) {
2256 Jim_SetResultFormatted(interp, "%s: connect: %s", connect_addr, strerror(errno));
2257 close(sock);
2258 return JIM_ERR;
2261 if (do_listen) {
2262 if (listen(sock, 5)) {
2263 Jim_SetResultFormatted(interp, "listen: %s", strerror(errno));
2264 close(sock);
2265 return JIM_ERR;
2269 return JimMakeChannel(interp, NULL, sock, argv[1], "aio.sock%ld", family, "r+", 0) ? JIM_OK : JIM_ERR;
2271 #endif /* JIM_BOOTSTRAP */
2273 #if defined(JIM_SSL) && !defined(JIM_BOOTSTRAP)
2274 static int JimAioLoadSSLCertsCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
2276 SSL_CTX *ssl_ctx;
2278 if (argc != 2) {
2279 Jim_WrongNumArgs(interp, 1, argv, "dir");
2280 return JIM_ERR;
2283 ssl_ctx = JimAioSslCtx(interp);
2284 if (!ssl_ctx) {
2285 return JIM_ERR;
2287 if (SSL_CTX_load_verify_locations(ssl_ctx, NULL, Jim_String(argv[1])) == 1) {
2288 return JIM_OK;
2290 Jim_SetResultString(interp, ERR_error_string(ERR_get_error(), NULL), -1);
2291 return JIM_ERR;
2293 #endif /* JIM_BOOTSTRAP */
2295 int Jim_aioInit(Jim_Interp *interp)
2297 if (Jim_PackageProvide(interp, "aio", "1.0", JIM_ERRMSG))
2298 return JIM_ERR;
2300 #if defined(JIM_SSL)
2301 Jim_CreateCommand(interp, "load_ssl_certs", JimAioLoadSSLCertsCommand, NULL, NULL);
2302 #endif
2304 Jim_CreateCommand(interp, "open", JimAioOpenCommand, NULL, NULL);
2305 #ifdef HAVE_SOCKETS
2306 Jim_CreateCommand(interp, "socket", JimAioSockCommand, NULL, NULL);
2307 #endif
2308 #ifdef HAVE_PIPE
2309 Jim_CreateCommand(interp, "pipe", JimAioPipeCommand, NULL, NULL);
2310 #endif
2312 /* Create filehandles for stdin, stdout and stderr */
2313 JimMakeChannel(interp, stdin, -1, NULL, "stdin", 0, "r", AIO_KEEPOPEN);
2314 JimMakeChannel(interp, stdout, -1, NULL, "stdout", 0, "w", AIO_KEEPOPEN);
2315 JimMakeChannel(interp, stderr, -1, NULL, "stderr", 0, "w", AIO_KEEPOPEN);
2317 return JIM_OK;