summaryrefslogtreecommitdiff
path: root/jim-aio.c
diff options
context:
space:
mode:
Diffstat (limited to 'jim-aio.c')
-rw-r--r--jim-aio.c1934
1 files changed, 1934 insertions, 0 deletions
diff --git a/jim-aio.c b/jim-aio.c
new file mode 100644
index 0000000..fb8577f
--- /dev/null
+++ b/jim-aio.c
@@ -0,0 +1,1934 @@
+/* Jim - A small embeddable Tcl interpreter
+ *
+ * Copyright 2005 Salvatore Sanfilippo <antirez@invece.org>
+ * Copyright 2005 Clemens Hintze <c.hintze@gmx.net>
+ * Copyright 2005 patthoyts - Pat Thoyts <patthoyts@users.sf.net>
+ * Copyright 2008 oharboe - Øyvind Harboe - oyvind.harboe@zylin.com
+ * Copyright 2008 Andrew Lunn <andrew@lunn.ch>
+ * Copyright 2008 Duane Ellis <openocd@duaneellis.com>
+ * Copyright 2008 Uwe Klein <uklein@klein-messgeraete.de>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE JIM TCL PROJECT ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * JIM TCL PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation
+ * are those of the authors and should not be interpreted as representing
+ * official policies, either expressed or implied, of the Jim Tcl Project.
+ **/
+
+#include "jimautoconf.h"
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#include <sys/stat.h>
+#endif
+
+#include "jim.h"
+
+#if defined(HAVE_SYS_SOCKET_H) && defined(HAVE_SELECT) && defined(HAVE_NETINET_IN_H) && defined(HAVE_NETDB_H) && defined(HAVE_ARPA_INET_H)
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#ifdef HAVE_SYS_UN_H
+#include <sys/un.h>
+#endif
+#else
+#define JIM_ANSIC
+#endif
+
+#if defined(JIM_SSL)
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#endif
+
+#include "jim-eventloop.h"
+#include "jim-subcmd.h"
+
+#define AIO_CMD_LEN 32 /* e.g. aio.handleXXXXXX */
+#define AIO_BUF_LEN 256 /* Can keep this small and rely on stdio buffering */
+
+#ifndef HAVE_FTELLO
+ #define ftello ftell
+#endif
+#ifndef HAVE_FSEEKO
+ #define fseeko fseek
+#endif
+
+#define AIO_KEEPOPEN 1
+
+#if defined(JIM_IPV6)
+#define IPV6 1
+#else
+#define IPV6 0
+#ifndef PF_INET6
+#define PF_INET6 0
+#endif
+#endif
+
+#define JimCheckStreamError(interp, af) af->fops->error(af)
+
+#if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)
+union sockaddr_any {
+ struct sockaddr sa;
+ struct sockaddr_in sin;
+#if IPV6
+ struct sockaddr_in6 sin6;
+#endif
+};
+
+#ifndef HAVE_INET_NTOP
+const char *inet_ntop(int af, const void *src, char *dst, int size)
+{
+ if (af != PF_INET) {
+ return NULL;
+ }
+ snprintf(dst, size, "%s", inet_ntoa(((struct sockaddr_in *)src)->sin_addr));
+ return dst;
+}
+#endif
+#endif /* JIM_BOOTSTRAP */
+
+struct AioFile;
+
+typedef struct {
+ int (*writer)(struct AioFile *af, const char *buf, int len);
+ int (*reader)(struct AioFile *af, char *buf, int len);
+ const char *(*getline)(struct AioFile *af, char *buf, int len);
+ int (*error)(const struct AioFile *af);
+ const char *(*strerror)(struct AioFile *af);
+ int (*verify)(struct AioFile *af);
+} JimAioFopsType;
+
+typedef struct AioFile
+{
+ FILE *fp;
+ Jim_Obj *filename;
+ int type;
+ int openFlags; /* AIO_KEEPOPEN? keep FILE* */
+ int fd;
+ Jim_Obj *rEvent;
+ Jim_Obj *wEvent;
+ Jim_Obj *eEvent;
+ int addr_family;
+ void *ssl;
+ const JimAioFopsType *fops;
+} AioFile;
+
+static int stdio_writer(struct AioFile *af, const char *buf, int len)
+{
+ return fwrite(buf, 1, len, af->fp);
+}
+
+static int stdio_reader(struct AioFile *af, char *buf, int len)
+{
+ return fread(buf, 1, len, af->fp);
+}
+
+static const char *stdio_getline(struct AioFile *af, char *buf, int len)
+{
+ return fgets(buf, len, af->fp);
+}
+
+static int stdio_error(const AioFile *af)
+{
+ if (!ferror(af->fp)) {
+ return JIM_OK;
+ }
+ clearerr(af->fp);
+ /* EAGAIN and similar are not error conditions. Just treat them like eof */
+ if (feof(af->fp) || errno == EAGAIN || errno == EINTR) {
+ return JIM_OK;
+ }
+#ifdef ECONNRESET
+ if (errno == ECONNRESET) {
+ return JIM_OK;
+ }
+#endif
+#ifdef ECONNABORTED
+ if (errno != ECONNABORTED) {
+ return JIM_OK;
+ }
+#endif
+ return JIM_ERR;
+}
+
+static const char *stdio_strerror(struct AioFile *af)
+{
+ return strerror(errno);
+}
+
+static const JimAioFopsType stdio_fops = {
+ stdio_writer,
+ stdio_reader,
+ stdio_getline,
+ stdio_error,
+ stdio_strerror,
+ NULL
+};
+
+#if defined(JIM_SSL) && !defined(JIM_BOOTSTRAP)
+
+static SSL_CTX *JimAioSslCtx(Jim_Interp *interp);
+
+static int ssl_writer(struct AioFile *af, const char *buf, int len)
+{
+ return SSL_write(af->ssl, buf, len);
+}
+
+static int ssl_reader(struct AioFile *af, char *buf, int len)
+{
+ return SSL_read(af->ssl, buf, len);
+}
+
+static const char *ssl_getline(struct AioFile *af, char *buf, int len)
+{
+ int i;
+ for (i = 0; i < len + 1; i++) {
+ if (SSL_read(af->ssl, &buf[i], 1) != 1) {
+ if (i == 0) {
+ return NULL;
+ }
+ break;
+ }
+ if (buf[i] == '\n') {
+ break;
+ }
+ }
+ buf[i] = '\0';
+ return buf;
+}
+
+static int ssl_error(const struct AioFile *af)
+{
+ if (ERR_peek_error() == 0) {
+ return JIM_OK;
+ }
+
+ return JIM_ERR;
+}
+
+static const char *ssl_strerror(struct AioFile *af)
+{
+ int err = ERR_get_error();
+
+ if (err) {
+ return ERR_error_string(err, NULL);
+ }
+
+ /* should not happen */
+ return "unknown SSL error";
+}
+
+static int ssl_verify(struct AioFile *af)
+{
+ X509 *cert;
+
+ cert = SSL_get_peer_certificate(af->ssl);
+ if (!cert) {
+ return JIM_ERR;
+ }
+ X509_free(cert);
+
+ if (SSL_get_verify_result(af->ssl) == X509_V_OK) {
+ return JIM_OK;
+ }
+
+ return JIM_ERR;
+}
+
+static const JimAioFopsType ssl_fops = {
+ ssl_writer,
+ ssl_reader,
+ ssl_getline,
+ ssl_error,
+ ssl_strerror,
+ ssl_verify
+};
+#endif /* JIM_BOOTSTRAP */
+
+static int JimAioSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv);
+static AioFile *JimMakeChannel(Jim_Interp *interp, FILE *fh, int fd, Jim_Obj *filename,
+ const char *hdlfmt, int family, const char *mode);
+
+#if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)
+static int JimParseIPv6Address(Jim_Interp *interp, const char *hostport, union sockaddr_any *sa, int *salen)
+{
+#if IPV6
+ /*
+ * An IPv6 addr/port looks like:
+ * [::1]
+ * [::1]:2000
+ * [fe80::223:6cff:fe95:bdc0%en1]:2000
+ * [::]:2000
+ * 2000
+ *
+ * Note that the "any" address is ::, which is the same as when no address is specified.
+ */
+ char *sthost = NULL;
+ const char *stport;
+ int ret = JIM_OK;
+ struct addrinfo req;
+ struct addrinfo *ai;
+
+ stport = strrchr(hostport, ':');
+ if (!stport) {
+ /* No : so, the whole thing is the port */
+ stport = hostport;
+ hostport = "::";
+ sthost = Jim_StrDup(hostport);
+ }
+ else {
+ stport++;
+ }
+
+ if (*hostport == '[') {
+ /* This is a numeric ipv6 address */
+ char *pt = strchr(++hostport, ']');
+ if (pt) {
+ sthost = Jim_StrDupLen(hostport, pt - hostport);
+ }
+ }
+
+ if (!sthost) {
+ sthost = Jim_StrDupLen(hostport, stport - hostport - 1);
+ }
+
+ memset(&req, '\0', sizeof(req));
+ req.ai_family = PF_INET6;
+
+ if (getaddrinfo(sthost, NULL, &req, &ai)) {
+ Jim_SetResultFormatted(interp, "Not a valid address: %s", hostport);
+ ret = JIM_ERR;
+ }
+ else {
+ memcpy(&sa->sin, ai->ai_addr, ai->ai_addrlen);
+ *salen = ai->ai_addrlen;
+
+ sa->sin.sin_port = htons(atoi(stport));
+
+ freeaddrinfo(ai);
+ }
+ Jim_Free(sthost);
+
+ return ret;
+#else
+ Jim_SetResultString(interp, "ipv6 not supported", -1);
+ return JIM_ERR;
+#endif
+}
+
+static int JimParseIpAddress(Jim_Interp *interp, const char *hostport, union sockaddr_any *sa, int *salen)
+{
+ /* An IPv4 addr/port looks like:
+ * 192.168.1.5
+ * 192.168.1.5:2000
+ * 2000
+ *
+ * If the address is missing, INADDR_ANY is used.
+ * If the port is missing, 0 is used (only useful for server sockets).
+ */
+ char *sthost = NULL;
+ const char *stport;
+ int ret = JIM_OK;
+
+ stport = strrchr(hostport, ':');
+ if (!stport) {
+ /* No : so, the whole thing is the port */
+ stport = hostport;
+ sthost = Jim_StrDup("0.0.0.0");
+ }
+ else {
+ sthost = Jim_StrDupLen(hostport, stport - hostport);
+ stport++;
+ }
+
+ {
+#ifdef HAVE_GETADDRINFO
+ struct addrinfo req;
+ struct addrinfo *ai;
+ memset(&req, '\0', sizeof(req));
+ req.ai_family = PF_INET;
+
+ if (getaddrinfo(sthost, NULL, &req, &ai)) {
+ ret = JIM_ERR;
+ }
+ else {
+ memcpy(&sa->sin, ai->ai_addr, ai->ai_addrlen);
+ *salen = ai->ai_addrlen;
+ freeaddrinfo(ai);
+ }
+#else
+ struct hostent *he;
+
+ ret = JIM_ERR;
+
+ if ((he = gethostbyname(sthost)) != NULL) {
+ if (he->h_length == sizeof(sa->sin.sin_addr)) {
+ *salen = sizeof(sa->sin);
+ sa->sin.sin_family= he->h_addrtype;
+ memcpy(&sa->sin.sin_addr, he->h_addr, he->h_length); /* set address */
+ ret = JIM_OK;
+ }
+ }
+#endif
+
+ sa->sin.sin_port = htons(atoi(stport));
+ }
+ Jim_Free(sthost);
+
+ if (ret != JIM_OK) {
+ Jim_SetResultFormatted(interp, "Not a valid address: %s", hostport);
+ }
+
+ return ret;
+}
+
+#ifdef HAVE_SYS_UN_H
+static int JimParseDomainAddress(Jim_Interp *interp, const char *path, struct sockaddr_un *sa)
+{
+ sa->sun_family = PF_UNIX;
+ snprintf(sa->sun_path, sizeof(sa->sun_path), "%s", path);
+
+ return JIM_OK;
+}
+#endif
+
+/**
+ * Format that address in 'sa' as a string and store in variable 'varObjPtr'
+ */
+static int JimFormatIpAddress(Jim_Interp *interp, Jim_Obj *varObjPtr, const union sockaddr_any *sa)
+{
+ /* INET6_ADDRSTRLEN is 46. Add some for [] and port */
+ char addrbuf[60];
+
+#if IPV6
+ if (sa->sa.sa_family == PF_INET6) {
+ addrbuf[0] = '[';
+ /* Allow 9 for []:65535\0 */
+ inet_ntop(sa->sa.sa_family, &sa->sin6.sin6_addr, addrbuf + 1, sizeof(addrbuf) - 9);
+ snprintf(addrbuf + strlen(addrbuf), 8, "]:%d", ntohs(sa->sin.sin_port));
+ }
+ else
+#endif
+ if (sa->sa.sa_family == PF_INET) {
+ /* Allow 7 for :65535\0 */
+ inet_ntop(sa->sa.sa_family, &sa->sin.sin_addr, addrbuf, sizeof(addrbuf) - 7);
+ snprintf(addrbuf + strlen(addrbuf), 7, ":%d", ntohs(sa->sin.sin_port));
+ }
+ else {
+ /* recvfrom still works on unix domain sockets, etc */
+ addrbuf[0] = 0;
+ }
+
+ return Jim_SetVariable(interp, varObjPtr, Jim_NewStringObj(interp, addrbuf, -1));
+}
+
+#endif /* JIM_BOOTSTRAP */
+
+static const char *JimAioErrorString(AioFile *af)
+{
+ if (af && af->fops)
+ return af->fops->strerror(af);
+
+ return strerror(errno);
+}
+
+static void JimAioSetError(Jim_Interp *interp, Jim_Obj *name)
+{
+ AioFile *af = Jim_CmdPrivData(interp);
+
+ if (name) {
+ Jim_SetResultFormatted(interp, "%#s: %s", name, JimAioErrorString(af));
+ }
+ else {
+ Jim_SetResultString(interp, JimAioErrorString(af), -1);
+ }
+}
+
+static void JimAioDelProc(Jim_Interp *interp, void *privData)
+{
+ AioFile *af = privData;
+
+ JIM_NOTUSED(interp);
+
+ Jim_DecrRefCount(interp, af->filename);
+
+#ifdef jim_ext_eventloop
+ /* remove all existing EventHandlers */
+ Jim_DeleteFileHandler(interp, af->fd, JIM_EVENT_READABLE | JIM_EVENT_WRITABLE | JIM_EVENT_EXCEPTION);
+#endif
+
+#if defined(JIM_SSL)
+ if (af->ssl != NULL) {
+ SSL_free(af->ssl);
+ }
+#endif
+
+ if (!(af->openFlags & AIO_KEEPOPEN)) {
+ fclose(af->fp);
+ }
+
+ Jim_Free(af);
+}
+
+static int aio_cmd_read(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ AioFile *af = Jim_CmdPrivData(interp);
+ char buf[AIO_BUF_LEN];
+ Jim_Obj *objPtr;
+ int nonewline = 0;
+ jim_wide neededLen = -1; /* -1 is "read as much as possible" */
+
+ if (argc && Jim_CompareStringImmediate(interp, argv[0], "-nonewline")) {
+ nonewline = 1;
+ argv++;
+ argc--;
+ }
+ if (argc == 1) {
+ if (Jim_GetWide(interp, argv[0], &neededLen) != JIM_OK)
+ return JIM_ERR;
+ if (neededLen < 0) {
+ Jim_SetResultString(interp, "invalid parameter: negative len", -1);
+ return JIM_ERR;
+ }
+ }
+ else if (argc) {
+ return -1;
+ }
+ objPtr = Jim_NewStringObj(interp, NULL, 0);
+ while (neededLen != 0) {
+ int retval;
+ int readlen;
+
+ if (neededLen == -1) {
+ readlen = AIO_BUF_LEN;
+ }
+ else {
+ readlen = (neededLen > AIO_BUF_LEN ? AIO_BUF_LEN : neededLen);
+ }
+ retval = af->fops->reader(af, buf, readlen);
+ if (retval > 0) {
+ Jim_AppendString(interp, objPtr, buf, retval);
+ if (neededLen != -1) {
+ neededLen -= retval;
+ }
+ }
+ if (retval != readlen)
+ break;
+ }
+ /* Check for error conditions */
+ if (JimCheckStreamError(interp, af)) {
+ Jim_FreeNewObj(interp, objPtr);
+ return JIM_ERR;
+ }
+ if (nonewline) {
+ int len;
+ const char *s = Jim_GetString(objPtr, &len);
+
+ if (len > 0 && s[len - 1] == '\n') {
+ objPtr->length--;
+ objPtr->bytes[objPtr->length] = '\0';
+ }
+ }
+ Jim_SetResult(interp, objPtr);
+ return JIM_OK;
+}
+
+AioFile *Jim_AioFile(Jim_Interp *interp, Jim_Obj *command)
+{
+ Jim_Cmd *cmdPtr = Jim_GetCommand(interp, command, JIM_ERRMSG);
+
+ /* XXX: There ought to be a supported API for this */
+ if (cmdPtr && !cmdPtr->isproc && cmdPtr->u.native.cmdProc == JimAioSubCmdProc) {
+ return (AioFile *) cmdPtr->u.native.privData;
+ }
+ Jim_SetResultFormatted(interp, "Not a filehandle: \"%#s\"", command);
+ return NULL;
+}
+
+FILE *Jim_AioFilehandle(Jim_Interp *interp, Jim_Obj *command)
+{
+ AioFile *af;
+
+ af = Jim_AioFile(interp, command);
+ if (af == NULL) {
+ return NULL;
+ }
+
+ return af->fp;
+}
+
+static int aio_cmd_copy(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ AioFile *af = Jim_CmdPrivData(interp);
+ jim_wide count = 0;
+ jim_wide maxlen = JIM_WIDE_MAX;
+ AioFile *outf = Jim_AioFile(interp, argv[0]);
+
+ if (outf == NULL) {
+ return JIM_ERR;
+ }
+
+ if (argc == 2) {
+ if (Jim_GetWide(interp, argv[1], &maxlen) != JIM_OK) {
+ return JIM_ERR;
+ }
+ }
+
+ while (count < maxlen) {
+ char ch;
+
+ if (af->fops->reader(af, &ch, 1) != 1) {
+ break;
+ }
+ if (outf->fops->writer(outf, &ch, 1) != 1) {
+ break;
+ }
+ count++;
+ }
+
+ if (JimCheckStreamError(interp, af) || JimCheckStreamError(interp, outf)) {
+ return JIM_ERR;
+ }
+
+ Jim_SetResultInt(interp, count);
+
+ return JIM_OK;
+}
+
+static int aio_cmd_gets(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ AioFile *af = Jim_CmdPrivData(interp);
+ char buf[AIO_BUF_LEN];
+ Jim_Obj *objPtr;
+ int len;
+
+ errno = 0;
+
+ objPtr = Jim_NewStringObj(interp, NULL, 0);
+ while (1) {
+ buf[AIO_BUF_LEN - 1] = '_';
+
+ if (af->fops->getline(af, buf, AIO_BUF_LEN) == NULL)
+ break;
+
+ if (buf[AIO_BUF_LEN - 1] == '\0' && buf[AIO_BUF_LEN - 2] != '\n') {
+ Jim_AppendString(interp, objPtr, buf, AIO_BUF_LEN - 1);
+ }
+ else {
+ len = strlen(buf);
+
+ if (len && (buf[len - 1] == '\n')) {
+ /* strip "\n" */
+ len--;
+ }
+
+ Jim_AppendString(interp, objPtr, buf, len);
+ break;
+ }
+ }
+
+ if (JimCheckStreamError(interp, af)) {
+ /* I/O error */
+ Jim_FreeNewObj(interp, objPtr);
+ return JIM_ERR;
+ }
+
+ if (argc) {
+ if (Jim_SetVariable(interp, argv[0], objPtr) != JIM_OK) {
+ Jim_FreeNewObj(interp, objPtr);
+ return JIM_ERR;
+ }
+
+ len = Jim_Length(objPtr);
+
+ if (len == 0 && feof(af->fp)) {
+ /* On EOF returns -1 if varName was specified */
+ len = -1;
+ }
+ Jim_SetResultInt(interp, len);
+ }
+ else {
+ Jim_SetResult(interp, objPtr);
+ }
+ return JIM_OK;
+}
+
+static int aio_cmd_puts(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ AioFile *af = Jim_CmdPrivData(interp);
+ int wlen;
+ const char *wdata;
+ Jim_Obj *strObj;
+
+ if (argc == 2) {
+ if (!Jim_CompareStringImmediate(interp, argv[0], "-nonewline")) {
+ return -1;
+ }
+ strObj = argv[1];
+ }
+ else {
+ strObj = argv[0];
+ }
+
+ wdata = Jim_GetString(strObj, &wlen);
+ if (af->fops->writer(af, wdata, wlen) == wlen) {
+ if (argc == 2 || af->fops->writer(af, "\n", 1) == 1) {
+ return JIM_OK;
+ }
+ }
+ JimAioSetError(interp, af->filename);
+ return JIM_ERR;
+}
+
+static int aio_cmd_isatty(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+#ifdef HAVE_ISATTY
+ AioFile *af = Jim_CmdPrivData(interp);
+ Jim_SetResultInt(interp, isatty(fileno(af->fp)));
+#else
+ Jim_SetResultInt(interp, 0);
+#endif
+
+ return JIM_OK;
+}
+
+#if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)
+static int aio_cmd_recvfrom(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ AioFile *af = Jim_CmdPrivData(interp);
+ char *buf;
+ union sockaddr_any sa;
+ long len;
+ socklen_t salen = sizeof(sa);
+ int rlen;
+
+ if (Jim_GetLong(interp, argv[0], &len) != JIM_OK) {
+ return JIM_ERR;
+ }
+
+ buf = Jim_Alloc(len + 1);
+
+ rlen = recvfrom(fileno(af->fp), buf, len, 0, &sa.sa, &salen);
+ if (rlen < 0) {
+ Jim_Free(buf);
+ JimAioSetError(interp, NULL);
+ return JIM_ERR;
+ }
+ buf[rlen] = 0;
+ Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, buf, rlen));
+
+ if (argc > 1) {
+ return JimFormatIpAddress(interp, argv[1], &sa);
+ }
+
+ return JIM_OK;
+}
+
+
+static int aio_cmd_sendto(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ AioFile *af = Jim_CmdPrivData(interp);
+ int wlen;
+ int len;
+ const char *wdata;
+ union sockaddr_any sa;
+ const char *addr = Jim_String(argv[1]);
+ int salen;
+
+ if (IPV6 && af->addr_family == PF_INET6) {
+ if (JimParseIPv6Address(interp, addr, &sa, &salen) != JIM_OK) {
+ return JIM_ERR;
+ }
+ }
+ else if (JimParseIpAddress(interp, addr, &sa, &salen) != JIM_OK) {
+ return JIM_ERR;
+ }
+ wdata = Jim_GetString(argv[0], &wlen);
+
+ /* Note that we don't validate the socket type. Rely on sendto() failing if appropriate */
+ len = sendto(fileno(af->fp), wdata, wlen, 0, &sa.sa, salen);
+ if (len < 0) {
+ JimAioSetError(interp, NULL);
+ return JIM_ERR;
+ }
+ Jim_SetResultInt(interp, len);
+ return JIM_OK;
+}
+
+static int aio_cmd_accept(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ AioFile *af = Jim_CmdPrivData(interp);
+ int sock;
+ union sockaddr_any sa;
+ socklen_t addrlen = sizeof(sa);
+
+ sock = accept(af->fd, &sa.sa, &addrlen);
+ if (sock < 0) {
+ JimAioSetError(interp, NULL);
+ return JIM_ERR;
+ }
+
+ if (argc > 0) {
+ if (JimFormatIpAddress(interp, argv[0], &sa) != JIM_OK) {
+ return JIM_ERR;
+ }
+ }
+
+ /* Create the file command */
+ return JimMakeChannel(interp, NULL, sock, Jim_NewStringObj(interp, "accept", -1),
+ "aio.sockstream%ld", af->addr_family, "r+") ? JIM_OK : JIM_ERR;
+}
+
+static int aio_cmd_listen(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ AioFile *af = Jim_CmdPrivData(interp);
+ long backlog;
+
+ if (Jim_GetLong(interp, argv[0], &backlog) != JIM_OK) {
+ return JIM_ERR;
+ }
+
+ if (listen(af->fd, backlog)) {
+ JimAioSetError(interp, NULL);
+ return JIM_ERR;
+ }
+
+ return JIM_OK;
+}
+#endif /* JIM_BOOTSTRAP */
+
+static int aio_cmd_flush(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ AioFile *af = Jim_CmdPrivData(interp);
+
+ if (fflush(af->fp) == EOF) {
+ JimAioSetError(interp, af->filename);
+ return JIM_ERR;
+ }
+ return JIM_OK;
+}
+
+static int aio_cmd_eof(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ AioFile *af = Jim_CmdPrivData(interp);
+
+ Jim_SetResultInt(interp, feof(af->fp));
+ return JIM_OK;
+}
+
+static int aio_cmd_close(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ if (argc == 3) {
+#if !defined(JIM_ANSIC) && defined(HAVE_SHUTDOWN)
+ static const char * const options[] = { "r", "w", NULL };
+ enum { OPT_R, OPT_W, };
+ int option;
+ AioFile *af = Jim_CmdPrivData(interp);
+
+ if (Jim_GetEnum(interp, argv[2], options, &option, NULL, JIM_ERRMSG) != JIM_OK) {
+ return JIM_ERR;
+ }
+ if (shutdown(af->fd, option == OPT_R ? SHUT_RD : SHUT_WR) == 0) {
+ return JIM_OK;
+ }
+ JimAioSetError(interp, NULL);
+#else
+ Jim_SetResultString(interp, "async close not supported", -1);
+#endif
+ return JIM_ERR;
+ }
+
+ return Jim_DeleteCommand(interp, Jim_String(argv[0]));
+}
+
+static int aio_cmd_seek(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ AioFile *af = Jim_CmdPrivData(interp);
+ int orig = SEEK_SET;
+ jim_wide offset;
+
+ if (argc == 2) {
+ if (Jim_CompareStringImmediate(interp, argv[1], "start"))
+ orig = SEEK_SET;
+ else if (Jim_CompareStringImmediate(interp, argv[1], "current"))
+ orig = SEEK_CUR;
+ else if (Jim_CompareStringImmediate(interp, argv[1], "end"))
+ orig = SEEK_END;
+ else {
+ return -1;
+ }
+ }
+ if (Jim_GetWide(interp, argv[0], &offset) != JIM_OK) {
+ return JIM_ERR;
+ }
+ if (fseeko(af->fp, offset, orig) == -1) {
+ JimAioSetError(interp, af->filename);
+ return JIM_ERR;
+ }
+ return JIM_OK;
+}
+
+static int aio_cmd_tell(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ AioFile *af = Jim_CmdPrivData(interp);
+
+ Jim_SetResultInt(interp, ftello(af->fp));
+ return JIM_OK;
+}
+
+static int aio_cmd_filename(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ AioFile *af = Jim_CmdPrivData(interp);
+
+ Jim_SetResult(interp, af->filename);
+ return JIM_OK;
+}
+
+#ifdef O_NDELAY
+static int aio_cmd_ndelay(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ AioFile *af = Jim_CmdPrivData(interp);
+
+ int fmode = fcntl(af->fd, F_GETFL);
+
+ if (argc) {
+ long nb;
+
+ if (Jim_GetLong(interp, argv[0], &nb) != JIM_OK) {
+ return JIM_ERR;
+ }
+ if (nb) {
+ fmode |= O_NDELAY;
+ }
+ else {
+ fmode &= ~O_NDELAY;
+ }
+ (void)fcntl(af->fd, F_SETFL, fmode);
+ }
+ Jim_SetResultInt(interp, (fmode & O_NONBLOCK) ? 1 : 0);
+ return JIM_OK;
+}
+#endif
+
+#ifdef HAVE_FSYNC
+static int aio_cmd_sync(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ AioFile *af = Jim_CmdPrivData(interp);
+
+ fflush(af->fp);
+ fsync(af->fd);
+ return JIM_OK;
+}
+#endif
+
+static int aio_cmd_buffering(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ AioFile *af = Jim_CmdPrivData(interp);
+
+ static const char * const options[] = {
+ "none",
+ "line",
+ "full",
+ NULL
+ };
+ enum
+ {
+ OPT_NONE,
+ OPT_LINE,
+ OPT_FULL,
+ };
+ int option;
+
+ if (Jim_GetEnum(interp, argv[0], options, &option, NULL, JIM_ERRMSG) != JIM_OK) {
+ return JIM_ERR;
+ }
+ switch (option) {
+ case OPT_NONE:
+ setvbuf(af->fp, NULL, _IONBF, 0);
+ break;
+ case OPT_LINE:
+ setvbuf(af->fp, NULL, _IOLBF, BUFSIZ);
+ break;
+ case OPT_FULL:
+ setvbuf(af->fp, NULL, _IOFBF, BUFSIZ);
+ break;
+ }
+ return JIM_OK;
+}
+
+#ifdef jim_ext_eventloop
+static void JimAioFileEventFinalizer(Jim_Interp *interp, void *clientData)
+{
+ Jim_Obj **objPtrPtr = clientData;
+
+ Jim_DecrRefCount(interp, *objPtrPtr);
+ *objPtrPtr = NULL;
+}
+
+static int JimAioFileEventHandler(Jim_Interp *interp, void *clientData, int mask)
+{
+ Jim_Obj **objPtrPtr = clientData;
+
+ return Jim_EvalObjBackground(interp, *objPtrPtr);
+}
+
+static int aio_eventinfo(Jim_Interp *interp, AioFile * af, unsigned mask, Jim_Obj **scriptHandlerObj,
+ int argc, Jim_Obj * const *argv)
+{
+ if (argc == 0) {
+ /* Return current script */
+ if (*scriptHandlerObj) {
+ Jim_SetResult(interp, *scriptHandlerObj);
+ }
+ return JIM_OK;
+ }
+
+ if (*scriptHandlerObj) {
+ /* Delete old handler */
+ Jim_DeleteFileHandler(interp, af->fd, mask);
+ }
+
+ /* Now possibly add the new script(s) */
+ if (Jim_Length(argv[0]) == 0) {
+ /* Empty script, so done */
+ return JIM_OK;
+ }
+
+ /* A new script to add */
+ Jim_IncrRefCount(argv[0]);
+ *scriptHandlerObj = argv[0];
+
+ Jim_CreateFileHandler(interp, af->fd, mask,
+ JimAioFileEventHandler, scriptHandlerObj, JimAioFileEventFinalizer);
+
+ return JIM_OK;
+}
+
+static int aio_cmd_readable(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ AioFile *af = Jim_CmdPrivData(interp);
+
+ return aio_eventinfo(interp, af, JIM_EVENT_READABLE, &af->rEvent, argc, argv);
+}
+
+static int aio_cmd_writable(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ AioFile *af = Jim_CmdPrivData(interp);
+
+ return aio_eventinfo(interp, af, JIM_EVENT_WRITABLE, &af->wEvent, argc, argv);
+}
+
+static int aio_cmd_onexception(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ AioFile *af = Jim_CmdPrivData(interp);
+
+ return aio_eventinfo(interp, af, JIM_EVENT_EXCEPTION, &af->eEvent, argc, argv);
+}
+#endif
+
+#if defined(JIM_SSL) && !defined(JIM_BOOTSTRAP)
+static int aio_cmd_ssl(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ AioFile *af = Jim_CmdPrivData(interp);
+ SSL *ssl;
+ SSL_CTX *ssl_ctx;
+ int fd, server = 0;
+
+ if (argc == 5) {
+ if (!Jim_CompareStringImmediate(interp, argv[2], "-server")) {
+ return JIM_ERR;
+ }
+ server = 1;
+ }
+ else if (argc != 2) {
+ Jim_WrongNumArgs(interp, 2, argv, "?-server cert priv?");
+ return JIM_ERR;
+ }
+
+ fd = fileno(af->fp);
+#if defined(HAVE_DUP)
+ fd = dup(fd);
+ if (fd < 0) {
+ return JIM_ERR;
+ }
+#endif
+ ssl_ctx = JimAioSslCtx(interp);
+ if (ssl_ctx == NULL) {
+ return JIM_ERR;
+ }
+
+ ssl = SSL_new(ssl_ctx);
+ if (ssl == NULL) {
+#if defined(HAVE_DUP)
+ close(fd);
+#endif
+ Jim_SetResultString(interp, ERR_error_string(ERR_get_error(), NULL), -1);
+ return JIM_ERR;
+ }
+
+ SSL_set_cipher_list(ssl, "ALL");
+
+ if (SSL_set_fd(ssl, fileno(af->fp)) == 0) {
+ goto out;
+ }
+
+ if (server) {
+ if (SSL_use_certificate_file(ssl, Jim_String(argv[3]), SSL_FILETYPE_PEM) != 1) {
+ goto out;
+ }
+
+ if (SSL_use_PrivateKey_file(ssl, Jim_String(argv[4]), SSL_FILETYPE_PEM) != 1) {
+ goto out;
+ }
+
+ if (SSL_accept(ssl) != 1) {
+ goto out;
+ }
+ }
+ else {
+ if (SSL_connect(ssl) != 1) {
+ goto out;
+ }
+ }
+
+ af = JimMakeChannel(interp, NULL, fd, NULL, "aio.sslstream%ld", af->addr_family, "r+");
+ if (af == NULL) {
+ goto out;
+ }
+
+ af->ssl = ssl;
+ af->fops = &ssl_fops;
+
+ return JIM_OK;
+
+out:
+#if defined(HAVE_DUP)
+ close(fd);
+#endif
+ SSL_free(ssl);
+ Jim_SetResultString(interp, ERR_error_string(ERR_get_error(), NULL), -1);
+ return JIM_ERR;
+}
+
+static int aio_cmd_verify(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ AioFile *af = Jim_CmdPrivData(interp);
+ int ret;
+
+ if (!af->fops->verify) {
+ return JIM_OK;
+ }
+
+ ret = af->fops->verify(af);
+ if (ret != JIM_OK) {
+ if (JimCheckStreamError(interp, af)) {
+ JimAioSetError(interp, af->filename);
+ } else {
+ Jim_SetResultString(interp, "failed to verify the connection authenticity", -1);
+ }
+ }
+ return ret;
+}
+#endif /* JIM_BOOTSTRAP */
+
+#if defined(HAVE_STRUCT_FLOCK) && !defined(JIM_BOOTSTRAP)
+static int aio_cmd_lock(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ AioFile *af = Jim_CmdPrivData(interp);
+ struct flock fl;
+
+ fl.l_start = 0;
+ fl.l_len = 0;
+ fl.l_type = F_WRLCK;
+ fl.l_whence = SEEK_SET;
+
+ switch (fcntl(af->fd, F_SETLK, &fl))
+ {
+ case 0:
+ Jim_SetResultInt(interp, 1);
+ break;
+ case -1:
+ if (errno == EACCES || errno == EAGAIN)
+ Jim_SetResultInt(interp, 0);
+ else
+ {
+ Jim_SetResultFormatted(interp, "lock failed: %s",
+ strerror(errno));
+ return JIM_ERR;
+ }
+ break;
+ default:
+ Jim_SetResultInt(interp, 0);
+ break;
+ }
+
+ return JIM_OK;
+}
+
+static int aio_cmd_unlock(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ AioFile *af = Jim_CmdPrivData(interp);
+ struct flock fl;
+ fl.l_start = 0;
+ fl.l_len = 0;
+ fl.l_type = F_UNLCK;
+ fl.l_whence = SEEK_SET;
+
+ Jim_SetResultInt(interp, fcntl(af->fd, F_SETLK, &fl) == 0);
+ return JIM_OK;
+}
+#endif /* JIM_BOOTSTRAP */
+
+static const jim_subcmd_type aio_command_table[] = {
+ { "read",
+ "?-nonewline? ?len?",
+ aio_cmd_read,
+ 0,
+ 2,
+ /* Description: Read and return bytes from the stream. To eof if no len. */
+ },
+ { "copyto",
+ "handle ?size?",
+ aio_cmd_copy,
+ 1,
+ 2,
+ /* Description: Copy up to 'size' bytes to the given filehandle, or to eof if no size. */
+ },
+ { "gets",
+ "?var?",
+ aio_cmd_gets,
+ 0,
+ 1,
+ /* Description: Read one line and return it or store it in the var */
+ },
+ { "puts",
+ "?-nonewline? str",
+ aio_cmd_puts,
+ 1,
+ 2,
+ /* Description: Write the string, with newline unless -nonewline */
+ },
+ { "isatty",
+ NULL,
+ aio_cmd_isatty,
+ 0,
+ 0,
+ /* Description: Is the file descriptor a tty? */
+ },
+#if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)
+ { "recvfrom",
+ "len ?addrvar?",
+ aio_cmd_recvfrom,
+ 1,
+ 2,
+ /* Description: Receive up to 'len' bytes on the socket. Sets 'addrvar' with receive address, if set */
+ },
+ { "sendto",
+ "str address",
+ aio_cmd_sendto,
+ 2,
+ 2,
+ /* Description: Send 'str' to the given address (dgram only) */
+ },
+ { "accept",
+ "?addrvar?",
+ aio_cmd_accept,
+ 0,
+ 1,
+ /* Description: Server socket only: Accept a connection and return stream */
+ },
+ { "listen",
+ "backlog",
+ aio_cmd_listen,
+ 1,
+ 1,
+ /* Description: Set the listen backlog for server socket */
+ },
+#endif /* JIM_BOOTSTRAP */
+ { "flush",
+ NULL,
+ aio_cmd_flush,
+ 0,
+ 0,
+ /* Description: Flush the stream */
+ },
+ { "eof",
+ NULL,
+ aio_cmd_eof,
+ 0,
+ 0,
+ /* Description: Returns 1 if stream is at eof */
+ },
+ { "close",
+ "?r(ead)|w(rite)?",
+ aio_cmd_close,
+ 0,
+ 1,
+ JIM_MODFLAG_FULLARGV,
+ /* Description: Closes the stream. */
+ },
+ { "seek",
+ "offset ?start|current|end",
+ aio_cmd_seek,
+ 1,
+ 2,
+ /* Description: Seeks in the stream (default 'current') */
+ },
+ { "tell",
+ NULL,
+ aio_cmd_tell,
+ 0,
+ 0,
+ /* Description: Returns the current seek position */
+ },
+ { "filename",
+ NULL,
+ aio_cmd_filename,
+ 0,
+ 0,
+ /* Description: Returns the original filename */
+ },
+#ifdef O_NDELAY
+ { "ndelay",
+ "?0|1?",
+ aio_cmd_ndelay,
+ 0,
+ 1,
+ /* Description: Set O_NDELAY (if arg). Returns current/new setting. */
+ },
+#endif
+#ifdef HAVE_FSYNC
+ { "sync",
+ NULL,
+ aio_cmd_sync,
+ 0,
+ 0,
+ /* Description: Flush and fsync() the stream */
+ },
+#endif
+ { "buffering",
+ "none|line|full",
+ aio_cmd_buffering,
+ 1,
+ 1,
+ /* Description: Sets buffering */
+ },
+#ifdef jim_ext_eventloop
+ { "readable",
+ "?readable-script?",
+ aio_cmd_readable,
+ 0,
+ 1,
+ /* Description: Returns script, or invoke readable-script when readable, {} to remove */
+ },
+ { "writable",
+ "?writable-script?",
+ aio_cmd_writable,
+ 0,
+ 1,
+ /* Description: Returns script, or invoke writable-script when writable, {} to remove */
+ },
+ { "onexception",
+ "?exception-script?",
+ aio_cmd_onexception,
+ 0,
+ 1,
+ /* Description: Returns script, or invoke exception-script when oob data, {} to remove */
+ },
+#endif
+#if defined(JIM_SSL) && !defined(JIM_BOOTSTRAP)
+ { "ssl",
+ "?-server cert priv?",
+ aio_cmd_ssl,
+ 0,
+ 3,
+ JIM_MODFLAG_FULLARGV
+ /* Description: Wraps a stream socket with SSL/TLS and returns a new channel */
+ },
+ { "verify",
+ NULL,
+ aio_cmd_verify,
+ 0,
+ 0,
+ /* Description: Verifies the certificate of a SSL/TLS channel */
+ },
+#endif /* JIM_BOOTSTRAP */
+#if defined(HAVE_STRUCT_FLOCK) && !defined(JIM_BOOTSTRAP)
+ { "lock",
+ NULL,
+ aio_cmd_lock,
+ 0,
+ 0,
+ /* Description: Attempt to get a lock. */
+ },
+ { "unlock",
+ NULL,
+ aio_cmd_unlock,
+ 0,
+ 0,
+ /* Description: Relase a lock. */
+ },
+#endif /* JIM_BOOTSTRAP */
+ { NULL }
+};
+
+static int JimAioSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ return Jim_CallSubCmd(interp, Jim_ParseSubCmd(interp, aio_command_table, argc, argv), argc, argv);
+}
+
+static int JimAioOpenCommand(Jim_Interp *interp, int argc,
+ Jim_Obj *const *argv)
+{
+ const char *mode;
+
+ if (argc != 2 && argc != 3) {
+ Jim_WrongNumArgs(interp, 1, argv, "filename ?mode?");
+ return JIM_ERR;
+ }
+
+ mode = (argc == 3) ? Jim_String(argv[2]) : "r";
+
+#ifdef jim_ext_tclcompat
+ {
+ const char *filename = Jim_String(argv[1]);
+
+ /* If the filename starts with '|', use popen instead */
+ if (*filename == '|') {
+ Jim_Obj *evalObj[3];
+
+ evalObj[0] = Jim_NewStringObj(interp, "::popen", -1);
+ evalObj[1] = Jim_NewStringObj(interp, filename + 1, -1);
+ evalObj[2] = Jim_NewStringObj(interp, mode, -1);
+
+ return Jim_EvalObjVector(interp, 3, evalObj);
+ }
+ }
+#endif
+ return JimMakeChannel(interp, NULL, -1, argv[1], "aio.handle%ld", 0, mode) ? JIM_OK : JIM_ERR;
+}
+
+#if defined(JIM_SSL) && !defined(JIM_BOOTSTRAP)
+static void JimAioSslContextDelProc(struct Jim_Interp *interp, void *privData)
+{
+ SSL_CTX_free((SSL_CTX *)privData);
+ ERR_free_strings();
+}
+
+static SSL_CTX *JimAioSslCtx(Jim_Interp *interp)
+{
+ SSL_CTX *ssl_ctx = (SSL_CTX *)Jim_GetAssocData(interp, "ssl_ctx");
+ if (ssl_ctx == NULL) {
+ SSL_load_error_strings();
+ SSL_library_init();
+ ssl_ctx = SSL_CTX_new(TLSv1_2_method());
+ if (ssl_ctx && SSL_CTX_set_default_verify_paths(ssl_ctx)) {
+ SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL);
+ Jim_SetAssocData(interp, "ssl_ctx", JimAioSslContextDelProc, ssl_ctx);
+ } else {
+ Jim_SetResultString(interp, ERR_error_string(ERR_get_error(), NULL), -1);
+ }
+ }
+ return ssl_ctx;
+}
+#endif /* JIM_BOOTSTRAP */
+
+/**
+ * Creates a channel for fh/fd/filename.
+ *
+ * If fh is not NULL, uses that as the channel (and sets AIO_KEEPOPEN).
+ * Otherwise, if fd is >= 0, uses that as the channel.
+ * Otherwise opens 'filename' with mode 'mode'.
+ *
+ * hdlfmt is a sprintf format for the filehandle. Anything with %ld at the end will do.
+ * mode is used for open or fdopen.
+ *
+ * Creates the command and sets the name as the current result.
+ * Returns the AioFile pointer on sucess or NULL on failure.
+ */
+static AioFile *JimMakeChannel(Jim_Interp *interp, FILE *fh, int fd, Jim_Obj *filename,
+ const char *hdlfmt, int family, const char *mode)
+{
+ AioFile *af;
+ char buf[AIO_CMD_LEN];
+ int openFlags = 0;
+
+ snprintf(buf, sizeof(buf), hdlfmt, Jim_GetId(interp));
+
+ if (fh) {
+ openFlags = AIO_KEEPOPEN;
+ }
+
+ snprintf(buf, sizeof(buf), hdlfmt, Jim_GetId(interp));
+ if (!filename) {
+ filename = Jim_NewStringObj(interp, buf, -1);
+ }
+
+ Jim_IncrRefCount(filename);
+
+ if (fh == NULL) {
+#if !defined(JIM_ANSIC)
+ if (fd >= 0) {
+ fh = fdopen(fd, mode);
+ }
+ else
+#endif
+ fh = fopen(Jim_String(filename), mode);
+
+ if (fh == NULL) {
+ JimAioSetError(interp, filename);
+#if !defined(JIM_ANSIC)
+ if (fd >= 0) {
+ close(fd);
+ }
+#endif
+ Jim_DecrRefCount(interp, filename);
+ return NULL;
+ }
+ }
+
+ /* Create the file command */
+ af = Jim_Alloc(sizeof(*af));
+ memset(af, 0, sizeof(*af));
+ af->fp = fh;
+ af->fd = fileno(fh);
+ af->filename = filename;
+#ifdef FD_CLOEXEC
+ if ((openFlags & AIO_KEEPOPEN) == 0) {
+ (void)fcntl(af->fd, F_SETFD, FD_CLOEXEC);
+ }
+#endif
+ af->openFlags = openFlags;
+ af->addr_family = family;
+ af->fops = &stdio_fops;
+ af->ssl = NULL;
+
+ Jim_CreateCommand(interp, buf, JimAioSubCmdProc, af, JimAioDelProc);
+
+ /* Note that the command must use the global namespace, even if
+ * the current namespace is something different
+ */
+ Jim_SetResult(interp, Jim_MakeGlobalNamespaceName(interp, Jim_NewStringObj(interp, buf, -1)));
+
+ return af;
+}
+
+#if defined(HAVE_PIPE) || (defined(HAVE_SOCKETPAIR) && defined(HAVE_SYS_UN_H))
+/**
+ * Create a pair of channels. e.g. from pipe() or socketpair()
+ */
+static int JimMakeChannelPair(Jim_Interp *interp, int p[2], Jim_Obj *filename,
+ const char *hdlfmt, int family, const char *mode[2])
+{
+ if (JimMakeChannel(interp, NULL, p[0], filename, hdlfmt, family, mode[0])) {
+ Jim_Obj *objPtr = Jim_NewListObj(interp, NULL, 0);
+ Jim_ListAppendElement(interp, objPtr, Jim_GetResult(interp));
+
+ if (JimMakeChannel(interp, NULL, p[1], filename, hdlfmt, family, mode[1])) {
+ Jim_ListAppendElement(interp, objPtr, Jim_GetResult(interp));
+ Jim_SetResult(interp, objPtr);
+ return JIM_OK;
+ }
+ }
+
+ /* Can only be here if fdopen() failed */
+ close(p[0]);
+ close(p[1]);
+ JimAioSetError(interp, NULL);
+ return JIM_ERR;
+}
+#endif
+
+#if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)
+
+static int JimAioSockCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ const char *hdlfmt = "aio.unknown%ld";
+ const char *socktypes[] = {
+ "unix",
+ "unix.server",
+ "dgram",
+ "dgram.server",
+ "stream",
+ "stream.server",
+ "pipe",
+ "pair",
+ NULL
+ };
+ enum
+ {
+ SOCK_UNIX,
+ SOCK_UNIX_SERVER,
+ SOCK_DGRAM_CLIENT,
+ SOCK_DGRAM_SERVER,
+ SOCK_STREAM_CLIENT,
+ SOCK_STREAM_SERVER,
+ SOCK_STREAM_PIPE,
+ SOCK_STREAM_SOCKETPAIR,
+ };
+ int socktype;
+ int sock;
+ const char *hostportarg = NULL;
+ int res;
+ int on = 1;
+ const char *mode = "r+";
+ int family = PF_INET;
+ Jim_Obj *argv0 = argv[0];
+ int ipv6 = 0;
+
+ if (argc > 1 && Jim_CompareStringImmediate(interp, argv[1], "-ipv6")) {
+ if (!IPV6) {
+ Jim_SetResultString(interp, "ipv6 not supported", -1);
+ return JIM_ERR;
+ }
+ ipv6 = 1;
+ family = PF_INET6;
+ }
+ argc -= ipv6;
+ argv += ipv6;
+
+ if (argc < 2) {
+ wrongargs:
+ Jim_WrongNumArgs(interp, 1, &argv0, "?-ipv6? type ?address?");
+ return JIM_ERR;
+ }
+
+ if (Jim_GetEnum(interp, argv[1], socktypes, &socktype, "socket type", JIM_ERRMSG) != JIM_OK)
+ return JIM_ERR;
+
+ Jim_SetEmptyResult(interp);
+
+ hdlfmt = "aio.sock%ld";
+
+ if (argc > 2) {
+ hostportarg = Jim_String(argv[2]);
+ }
+
+ switch (socktype) {
+ case SOCK_DGRAM_CLIENT:
+ if (argc == 2) {
+ /* No address, so an unconnected dgram socket */
+ sock = socket(family, SOCK_DGRAM, 0);
+ if (sock < 0) {
+ JimAioSetError(interp, NULL);
+ return JIM_ERR;
+ }
+ break;
+ }
+ /* fall through */
+ case SOCK_STREAM_CLIENT:
+ {
+ union sockaddr_any sa;
+ int salen;
+
+ if (argc != 3) {
+ goto wrongargs;
+ }
+
+ if (ipv6) {
+ if (JimParseIPv6Address(interp, hostportarg, &sa, &salen) != JIM_OK) {
+ return JIM_ERR;
+ }
+ }
+ else if (JimParseIpAddress(interp, hostportarg, &sa, &salen) != JIM_OK) {
+ return JIM_ERR;
+ }
+ sock = socket(family, (socktype == SOCK_DGRAM_CLIENT) ? SOCK_DGRAM : SOCK_STREAM, 0);
+ if (sock < 0) {
+ JimAioSetError(interp, NULL);
+ return JIM_ERR;
+ }
+ res = connect(sock, &sa.sa, salen);
+ if (res) {
+ JimAioSetError(interp, argv[2]);
+ close(sock);
+ return JIM_ERR;
+ }
+ }
+ break;
+
+ case SOCK_STREAM_SERVER:
+ case SOCK_DGRAM_SERVER:
+ {
+ union sockaddr_any sa;
+ int salen;
+
+ if (argc != 3) {
+ goto wrongargs;
+ }
+
+ if (ipv6) {
+ if (JimParseIPv6Address(interp, hostportarg, &sa, &salen) != JIM_OK) {
+ return JIM_ERR;
+ }
+ }
+ else if (JimParseIpAddress(interp, hostportarg, &sa, &salen) != JIM_OK) {
+ return JIM_ERR;
+ }
+ sock = socket(family, (socktype == SOCK_DGRAM_SERVER) ? SOCK_DGRAM : SOCK_STREAM, 0);
+ if (sock < 0) {
+ JimAioSetError(interp, NULL);
+ return JIM_ERR;
+ }
+
+ /* Enable address reuse */
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on));
+
+ res = bind(sock, &sa.sa, salen);
+ if (res) {
+ JimAioSetError(interp, argv[2]);
+ close(sock);
+ return JIM_ERR;
+ }
+ if (socktype == SOCK_STREAM_SERVER) {
+ res = listen(sock, 5);
+ if (res) {
+ JimAioSetError(interp, NULL);
+ close(sock);
+ return JIM_ERR;
+ }
+ }
+ hdlfmt = "aio.socksrv%ld";
+ }
+ break;
+
+#ifdef HAVE_SYS_UN_H
+ case SOCK_UNIX:
+ {
+ struct sockaddr_un sa;
+ socklen_t len;
+
+ if (argc != 3 || ipv6) {
+ goto wrongargs;
+ }
+
+ if (JimParseDomainAddress(interp, hostportarg, &sa) != JIM_OK) {
+ JimAioSetError(interp, argv[2]);
+ return JIM_ERR;
+ }
+ family = PF_UNIX;
+ sock = socket(PF_UNIX, SOCK_STREAM, 0);
+ if (sock < 0) {
+ JimAioSetError(interp, NULL);
+ return JIM_ERR;
+ }
+ len = strlen(sa.sun_path) + 1 + sizeof(sa.sun_family);
+ res = connect(sock, (struct sockaddr *)&sa, len);
+ if (res) {
+ JimAioSetError(interp, argv[2]);
+ close(sock);
+ return JIM_ERR;
+ }
+ hdlfmt = "aio.sockunix%ld";
+ break;
+ }
+
+ case SOCK_UNIX_SERVER:
+ {
+ struct sockaddr_un sa;
+ socklen_t len;
+
+ if (argc != 3 || ipv6) {
+ goto wrongargs;
+ }
+
+ if (JimParseDomainAddress(interp, hostportarg, &sa) != JIM_OK) {
+ JimAioSetError(interp, argv[2]);
+ return JIM_ERR;
+ }
+ family = PF_UNIX;
+ sock = socket(PF_UNIX, SOCK_STREAM, 0);
+ if (sock < 0) {
+ JimAioSetError(interp, NULL);
+ return JIM_ERR;
+ }
+ len = strlen(sa.sun_path) + 1 + sizeof(sa.sun_family);
+ res = bind(sock, (struct sockaddr *)&sa, len);
+ if (res) {
+ JimAioSetError(interp, argv[2]);
+ close(sock);
+ return JIM_ERR;
+ }
+ res = listen(sock, 5);
+ if (res) {
+ JimAioSetError(interp, NULL);
+ close(sock);
+ return JIM_ERR;
+ }
+ hdlfmt = "aio.sockunixsrv%ld";
+ break;
+ }
+#endif
+
+#if defined(HAVE_SOCKETPAIR) && defined(HAVE_SYS_UN_H)
+ case SOCK_STREAM_SOCKETPAIR:
+ {
+ int p[2];
+ static const char *mode[2] = { "r+", "r+" };
+
+ if (argc != 2 || ipv6) {
+ goto wrongargs;
+ }
+
+ if (socketpair(PF_UNIX, SOCK_STREAM, 0, p) < 0) {
+ JimAioSetError(interp, NULL);
+ return JIM_ERR;
+ }
+ return JimMakeChannelPair(interp, p, argv[1], "aio.sockpair%ld", PF_UNIX, mode);
+ }
+ break;
+#endif
+
+#if defined(HAVE_PIPE)
+ case SOCK_STREAM_PIPE:
+ {
+ int p[2];
+ static const char *mode[2] = { "r", "w" };
+
+ if (argc != 2 || ipv6) {
+ goto wrongargs;
+ }
+
+ if (pipe(p) < 0) {
+ JimAioSetError(interp, NULL);
+ return JIM_ERR;
+ }
+
+ return JimMakeChannelPair(interp, p, argv[1], "aio.pipe%ld", 0, mode);
+ }
+ break;
+#endif
+
+ default:
+ Jim_SetResultString(interp, "Unsupported socket type", -1);
+ return JIM_ERR;
+ }
+
+ return JimMakeChannel(interp, NULL, sock, argv[1], hdlfmt, family, mode) ? JIM_OK : JIM_ERR;
+}
+#endif /* JIM_BOOTSTRAP */
+
+/**
+ * Returns the file descriptor of a writable, newly created temp file
+ * or -1 on error.
+ *
+ * On success, leaves the filename in the interpreter result, otherwise
+ * leaves an error message.
+ */
+int Jim_MakeTempFile(Jim_Interp *interp, const char *template)
+{
+#ifdef HAVE_MKSTEMP
+ int fd;
+ mode_t mask;
+ Jim_Obj *filenameObj;
+
+ if (template == NULL) {
+ const char *tmpdir = getenv("TMPDIR");
+ if (tmpdir == NULL || *tmpdir == '\0' || access(tmpdir, W_OK) != 0) {
+ tmpdir = "/tmp/";
+ }
+ filenameObj = Jim_NewStringObj(interp, tmpdir, -1);
+ if (tmpdir[0] && tmpdir[strlen(tmpdir) - 1] != '/') {
+ Jim_AppendString(interp, filenameObj, "/", 1);
+ }
+ Jim_AppendString(interp, filenameObj, "tcl.tmp.XXXXXX", -1);
+ }
+ else {
+ filenameObj = Jim_NewStringObj(interp, template, -1);
+ }
+
+#if defined(S_IRWXG) && defined(S_IRWXO)
+ mask = umask(S_IXUSR | S_IRWXG | S_IRWXO);
+#else
+ /* MinGW does not have group/owner permissions */
+ mask = umask(S_IXUSR);
+#endif
+
+ /* Update the template name directly with the filename */
+ fd = mkstemp(filenameObj->bytes);
+ umask(mask);
+ if (fd < 0) {
+ JimAioSetError(interp, filenameObj);
+ Jim_FreeNewObj(interp, filenameObj);
+ return -1;
+ }
+
+ Jim_SetResult(interp, filenameObj);
+ return fd;
+#else
+ Jim_SetResultString(interp, "platform has no tempfile support", -1);
+ return -1;
+#endif
+}
+
+#if defined(JIM_SSL) && !defined(JIM_BOOTSTRAP)
+static int JimAioLoadSSLCertsCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ SSL_CTX *ssl_ctx;
+
+ if (argc != 2) {
+ Jim_WrongNumArgs(interp, 1, argv, "dir");
+ return JIM_ERR;
+ }
+
+ ssl_ctx = JimAioSslCtx(interp);
+ if (!ssl_ctx) {
+ return JIM_ERR;
+ }
+ if (SSL_CTX_load_verify_locations(ssl_ctx, NULL, Jim_String(argv[1])) == 1) {
+ return JIM_OK;
+ }
+ Jim_SetResultString(interp, ERR_error_string(ERR_get_error(), NULL), -1);
+ return JIM_ERR;
+}
+#endif /* JIM_BOOTSTRAP */
+
+int Jim_aioInit(Jim_Interp *interp)
+{
+ if (Jim_PackageProvide(interp, "aio", "1.0", JIM_ERRMSG))
+ return JIM_ERR;
+
+#if defined(JIM_SSL)
+ Jim_CreateCommand(interp, "load_ssl_certs", JimAioLoadSSLCertsCommand, NULL, NULL);
+#endif
+
+ Jim_CreateCommand(interp, "open", JimAioOpenCommand, NULL, NULL);
+#ifndef JIM_ANSIC
+ Jim_CreateCommand(interp, "socket", JimAioSockCommand, NULL, NULL);
+#endif
+
+ /* Create filehandles for stdin, stdout and stderr */
+ JimMakeChannel(interp, stdin, -1, NULL, "stdin", 0, "r");
+ JimMakeChannel(interp, stdout, -1, NULL, "stdout", 0, "w");
+ JimMakeChannel(interp, stderr, -1, NULL, "stderr", 0, "w");
+
+ return JIM_OK;
+}