diff options
-rw-r--r-- | jim-aio.c | 134 | ||||
-rw-r--r-- | jim_tcl.txt | 23 | ||||
-rw-r--r-- | tests/aio.test | 8 | ||||
-rw-r--r-- | tests/ssl.test | 10 |
4 files changed, 131 insertions, 44 deletions
@@ -95,6 +95,7 @@ #define AIO_KEEPOPEN 1 #define AIO_NODELETE 2 +#define AIO_EOF 4 #if defined(JIM_IPV6) #define IPV6 1 @@ -150,6 +151,8 @@ typedef struct { int (*error)(const struct AioFile *af); const char *(*strerror)(struct AioFile *af); int (*verify)(struct AioFile *af); + int (*eof)(struct AioFile *af); + int (*pending)(struct AioFile *af); } JimAioFopsType; typedef struct AioFile @@ -157,7 +160,7 @@ typedef struct AioFile FILE *fp; Jim_Obj *filename; int type; - int openFlags; /* AIO_KEEPOPEN? keep FILE* */ + int flags; /* AIO_KEEPOPEN? keep FILE* */ int fd; Jim_Obj *rEvent; Jim_Obj *wEvent; @@ -210,13 +213,20 @@ static const char *stdio_strerror(struct AioFile *af) return strerror(errno); } +static int stdio_eof(struct AioFile *af) +{ + return feof(af->fp); +} + static const JimAioFopsType stdio_fops = { stdio_writer, stdio_reader, stdio_getline, stdio_error, stdio_strerror, - NULL + NULL, /* verify */ + stdio_eof, + NULL, /* pending */ }; #if defined(JIM_SSL) && !defined(JIM_BOOTSTRAP) @@ -228,35 +238,64 @@ static int ssl_writer(struct AioFile *af, const char *buf, int len) return SSL_write(af->ssl, buf, len); } +static int ssl_pending(struct AioFile *af) +{ + return SSL_pending(af->ssl); +} + static int ssl_reader(struct AioFile *af, char *buf, int len) { - return SSL_read(af->ssl, buf, len); + int ret = SSL_read(af->ssl, buf, len); + switch (SSL_get_error(af->ssl, ret)) { + case SSL_ERROR_NONE: + return ret; + case SSL_ERROR_SYSCALL: + case SSL_ERROR_ZERO_RETURN: + if (errno != EAGAIN) { + af->flags |= AIO_EOF; + } + return 0; + case SSL_ERROR_SSL: + default: + if (errno == EAGAIN) { + return 0; + } + af->flags |= AIO_EOF; + return -1; + } +} + +static int ssl_eof(struct AioFile *af) +{ + return (af->flags & AIO_EOF); } static const char *ssl_getline(struct AioFile *af, char *buf, int len) { size_t i; - for (i = 0; i < len + 1; i++) { - if (SSL_read(af->ssl, &buf[i], 1) != 1) { - if (i == 0) { - return NULL; - } + for (i = 0; i < len - 1 && !ssl_eof(af); i++) { + int ret = ssl_reader(af, &buf[i], 1); + if (ret != 1) { break; } if (buf[i] == '\n') { + i++; break; } } buf[i] = '\0'; + if (i == 0 && ssl_eof(af)) { + return NULL; + } return buf; } static int ssl_error(const struct AioFile *af) { - if (ERR_peek_error() == 0) { - return JIM_OK; + int ret = SSL_get_error(af->ssl, 0); + if (ret == SSL_ERROR_SYSCALL || ret == 0) { + return stdio_error(af); } - return JIM_ERR; } @@ -295,7 +334,9 @@ static const JimAioFopsType ssl_fops = { ssl_getline, ssl_error, ssl_strerror, - ssl_verify + ssl_verify, + ssl_eof, + ssl_pending, }; #endif /* JIM_BOOTSTRAP */ @@ -605,7 +646,7 @@ static void JimAioDelProc(Jim_Interp *interp, void *privData) JIM_NOTUSED(interp); #if UNIX_SOCKETS - if (af->addr_family == PF_UNIX && (af->openFlags & AIO_NODELETE) == 0) { + if (af->addr_family == PF_UNIX && (af->flags & AIO_NODELETE) == 0) { /* If this is bound, delete the socket file now */ Jim_Obj *filenameObj = aio_sockname(interp, af); if (filenameObj) { @@ -629,7 +670,7 @@ static void JimAioDelProc(Jim_Interp *interp, void *privData) SSL_free(af->ssl); } #endif - if (!(af->openFlags & AIO_KEEPOPEN)) { + if (!(af->flags & AIO_KEEPOPEN)) { fclose(af->fp); } @@ -642,22 +683,42 @@ static int aio_cmd_read(Jim_Interp *interp, int argc, Jim_Obj *const *argv) char buf[AIO_BUF_LEN]; Jim_Obj *objPtr; int nonewline = 0; + int pending = 0; jim_wide neededLen = -1; /* -1 is "read as much as possible" */ + static const char * const options[] = { "-pending", "-nonewline", NULL }; + enum { OPT_PENDING, OPT_NONEWLINE }; + int option; - 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; + if (argc) { + if (*Jim_String(argv[0]) == '-') { + if (Jim_GetEnum(interp, argv[0], options, &option, NULL, JIM_ERRMSG) != JIM_OK) { + return JIM_ERR; + } + switch (option) { + case OPT_PENDING: + if (!af->fops->pending) { + Jim_SetResultString(interp, "-pending not supported on this connection type", -1); + return JIM_ERR; + } + pending++; + break; + case OPT_NONEWLINE: + nonewline++; + break; + } } + else { + 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; + } + } + argc--; + argv++; } - else if (argc) { + if (argc) { return -1; } objPtr = Jim_NewStringObj(interp, NULL, 0); @@ -671,15 +732,22 @@ static int aio_cmd_read(Jim_Interp *interp, int argc, Jim_Obj *const *argv) else { readlen = (neededLen > AIO_BUF_LEN ? AIO_BUF_LEN : neededLen); } - retval = af->fops->reader(af, buf, readlen); + retval = af->fops->reader(af, buf, pending ? 1 : readlen); if (retval > 0) { Jim_AppendString(interp, objPtr, buf, retval); if (neededLen != -1) { neededLen -= retval; } + else if (pending) { + /* If pending was specified, after we do the initial read, + * we do a second read to fetch any buffered data + */ + neededLen = af->fops->pending(af); + } } - if (retval != readlen) + if (retval <= 0) { break; + } } /* Check for error conditions */ if (JimCheckStreamError(interp, af)) { @@ -823,7 +891,7 @@ static int aio_cmd_gets(Jim_Interp *interp, int argc, Jim_Obj *const *argv) len = Jim_Length(objPtr); - if (len == 0 && feof(af->fp)) { + if (len == 0 && af->fops->eof(af)) { /* On EOF returns -1 if varName was specified */ len = -1; } @@ -1016,7 +1084,7 @@ 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)); + Jim_SetResultInt(interp, !!af->fops->eof(af)); return JIM_OK; } @@ -1046,7 +1114,7 @@ static int aio_cmd_close(Jim_Interp *interp, int argc, Jim_Obj *const *argv) #if UNIX_SOCKETS case OPT_NODELETE: if (af->addr_family == PF_UNIX) { - af->openFlags |= AIO_NODELETE; + af->flags |= AIO_NODELETE; break; } /* fall through */ @@ -1542,7 +1610,7 @@ static int aio_cmd_tty(Jim_Interp *interp, int argc, Jim_Obj *const *argv) static const jim_subcmd_type aio_command_table[] = { { "read", - "?-nonewline? ?len?", + "?-nonewline|-pending|len?", aio_cmd_read, 0, 2, @@ -1889,7 +1957,7 @@ static AioFile *JimMakeChannel(Jim_Interp *interp, FILE *fh, int fd, Jim_Obj *fi memset(af, 0, sizeof(*af)); af->fp = fh; af->filename = filename; - af->openFlags = flags; + af->flags = flags; #ifndef JIM_ANSIC af->fd = fileno(fh); #ifdef FD_CLOEXEC diff --git a/jim_tcl.txt b/jim_tcl.txt index 0ba2e5b..d0f3b25 100644 --- a/jim_tcl.txt +++ b/jim_tcl.txt @@ -3574,6 +3574,10 @@ read +'fileId' *read* 'numBytes'+ ++*read* ?*-pending*? 'fileId'+ + ++'fileId' *read* ?*-pending*?+ + In the first form, all of the remaining bytes are read from the file given by +'fileId'+; they are returned as the result of the command. If the +-nonewline+ switch is specified then the last @@ -3584,6 +3588,21 @@ exactly this many bytes will be read and returned, unless there are fewer than +'numBytes'+ bytes left in the file; in this case, all the remaining bytes are returned. +The third form is currently only useful with SSL sockets. It reads at least 1 byte +and then any additional data that is buffered. This allows for use in an event handler. +e.g. + +---- + $sock readable { + set buf [$sock read -pending] + } +---- + +This is necessary because otherwise pending data may be buffered, but +the underlying socket will not be marked 'readable'. This featured is not +currently supported for regular sockets, and so these sockets must be +set to unbufferred (+$sock buffering false+) to work in an event loop. + +'fileId'+ must be +stdin+ or the return value from a previous call to `open`; it must refer to a file that was opened for reading. @@ -4753,8 +4772,8 @@ aio +$handle *puts ?-nonewline?* 'str'+:: Write the string, with newline unless -nonewline -+$handle *read ?-nonewline?* '?len?'+:: - Read and return bytes from the stream. To eof if no len. ++$handle *read ?-nonewline|-pending*|len?'+:: + Read and return bytes from the stream. To eof if no len. See `read`. +$handle *recvfrom* 'maxlen ?addrvar?'+:: Receives a message from the handle via recvfrom(2) and returns it. diff --git a/tests/aio.test b/tests/aio.test index 501b9a6..361acdc 100644 --- a/tests/aio.test +++ b/tests/aio.test @@ -58,7 +58,7 @@ test aio-1.9 {seek bad pos} -body { test aio-2.1 {read usage} -body { $f read -nonoption -} -returnCodes error -result {expected integer but got "-nonoption"} +} -returnCodes error -result {bad option "-nonoption": must be -nonewline, or -pending} test aio-2.2 {read usage} -body { $f read badint @@ -70,7 +70,11 @@ test aio-2.3 {read -ve len} -body { test aio-2.4 {read too many args} -body { $f read 20 extra -} -returnCodes error -match glob -result {wrong # args: should be "* read ?-nonewline? ?len?"} +} -returnCodes error -match glob -result {wrong # args: should be "* read ?-nonewline|-pending|len?"} + +test aio-2.5 {read -pending on non-ssl} -body { + $f read -pending +} -returnCodes error -result {-pending not supported on this connection type} test aio-3.1 {copy to invalid fh} -body { $f copy lambda diff --git a/tests/ssl.test b/tests/ssl.test index a15c4d6..f07391e 100644 --- a/tests/ssl.test +++ b/tests/ssl.test @@ -2,7 +2,6 @@ source [file dirname [info script]]/testing.tcl needs constraint jim needs cmd socket -needs cmd alarm needs cmd os.fork testCmdConstraints load_ssl_certs @@ -16,11 +15,12 @@ set s [socket stream.server 1443] if {[os.fork] == 0} { # child set c [[socket stream [$s sockname]] ssl] - $c buffering none $s close sleep 0.25 $c readable { - set buf [$c read 1] + # when we read we need to also read any pending data, + # otherwise readable won't retrigger + set buf [$c read -pending] if {[string length $buf] == 0} { incr ssldone $c close @@ -47,9 +47,6 @@ test ssl-1.1 {puts/gets} { $cs gets } hello -# XXX this test does not work because of the interaction between -# ssl buffering and readable -alarm 1 test ssl-1.2 {puts/gets} { $cs puts -nonewline again lmap p [range 5] { @@ -57,7 +54,6 @@ test ssl-1.2 {puts/gets} { set c } } {a g a i n} -alarm 0 test ssl-2.1 {https to google.com, gets} -body { set c [[socket stream www.google.com:443] ssl] |