From 19a6714a587ae20a2fdb79b9a8a71a37170a8883 Mon Sep 17 00:00:00 2001 From: "Tarn W. Burton" Date: Mon, 16 Jan 2023 10:46:24 -0500 Subject: [PATCH 1/2] Add regression test for LISTEN on files --- src/tests/normal-tests/mixed.lsp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/tests/normal-tests/mixed.lsp b/src/tests/normal-tests/mixed.lsp index b52c7bf1d..821bba1ab 100644 --- a/src/tests/normal-tests/mixed.lsp +++ b/src/tests/normal-tests/mixed.lsp @@ -479,3 +479,15 @@ (is (equal (format nil "a~4:A" nil) "a() ")) (is (equal (format nil "a~4:@A" nil) "a ()")) (is (equal (format nil "a~4@:A" nil) "a ()"))) + +(test mix.0026.file-listen + (is (equal (progn + (with-open-file (blah "nada.txt" :direction :output + :if-does-not-exist :create + :if-exists :supersede) + (write-char #\a blah)) + (with-open-file (blah "nada.txt" :direction :input) + (list (listen blah) + (read-char blah) + (listen blah)))) + (list t #\a nil)))) -- GitLab From 0ee30654ba2c03ac43d698d69bfdc01010f51cce Mon Sep 17 00:00:00 2001 From: "Tarn W. Burton" Date: Mon, 16 Jan 2023 10:46:53 -0500 Subject: [PATCH 2/2] When poll/select indicate non-blocking then check for bytes --- src/c/file.d | 117 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 81 insertions(+), 36 deletions(-) diff --git a/src/c/file.d b/src/c/file.d index 126135ef7..74c6e07b1 100644 --- a/src/c/file.d +++ b/src/c/file.d @@ -69,8 +69,8 @@ static cl_index ecl_write_byte8(cl_object stream, unsigned char *c, cl_index n); struct ecl_file_ops *duplicate_dispatch_table(const struct ecl_file_ops *ops); const struct ecl_file_ops *stream_dispatch_table(cl_object strm); -static int flisten(cl_object, FILE *); -static int file_listen(cl_object, int); +static int file_listen(cl_object, FILE *); +static int fd_listen(cl_object, int); static cl_object alloc_stream(); @@ -2725,7 +2725,7 @@ io_file_listen(cl_object strm) } } } - return file_listen(strm, IO_FILE_DESCRIPTOR(strm)); + return fd_listen(strm, IO_FILE_DESCRIPTOR(strm)); } #if defined(ECL_MS_WINDOWS_HOST) @@ -2751,7 +2751,7 @@ io_file_clear_input(cl_object strm) /* Do not stop here: the FILE structure needs also to be flushed */ } #endif - while (file_listen(strm, f) == ECL_LISTEN_AVAILABLE) { + while (fd_listen(strm, f) == ECL_LISTEN_AVAILABLE) { ecl_character c = eformat_read_char(strm); if (c == EOF) return; } @@ -3553,7 +3553,7 @@ io_stream_listen(cl_object strm) { if (strm->stream.byte_stack != ECL_NIL) return ECL_LISTEN_AVAILABLE; - return flisten(strm, IO_STREAM_FILE(strm)); + return file_listen(strm, IO_STREAM_FILE(strm)); } static void @@ -3569,7 +3569,7 @@ io_stream_clear_input(cl_object strm) /* Do not stop here: the FILE structure needs also to be flushed */ } #endif - while (flisten(strm, fp) == ECL_LISTEN_AVAILABLE) { + while (file_listen(strm, fp) == ECL_LISTEN_AVAILABLE) { ecl_disable_interrupts(); getc(fp); ecl_enable_interrupts(); @@ -5630,7 +5630,7 @@ ecl_open_stream(cl_object fn, enum ecl_smmode smm, cl_object if_exists, #if defined(ECL_MS_WINDOWS_HOST) static int -file_listen(cl_object stream, int fileno) +fd_listen(cl_object stream, int fileno) { HANDLE hnd = (HANDLE)_get_osfhandle(fileno); switch (GetFileType(hnd)) { @@ -5687,44 +5687,89 @@ file_listen(cl_object stream, int fileno) } #else static int -file_listen(cl_object stream, int fileno) -{ -# if defined(HAVE_SELECT) +fd_listen(cl_object stream, int fileno) +{ + /* Method 1: poll, see POLL(2) + Method 2: select, see SELECT(2) + Method 3: ioctl FIONREAD, see FILIO(4) + Method 4: read a byte. Use non-blocking I/O if poll or select were not + available. */ + int result; +#if defined(HAVE_POLL) + struct pollfd fd = {fileno, POLLIN, 0}; +restart_poll: + result = poll(&fd, 1, 0); + if (ecl_unlikely(result < 0)) { + if (errno == EINTR) + goto restart_poll; + goto listen_error; + } + if (fd.revents == 0) { + return ECL_LISTEN_NO_CHAR; + } + /* When read() returns a result without blocking, this can also be + EOF! (Example: Linux and pipes.) We therefore refrain from simply + doing { return ECL_LISTEN_AVAILABLE; } and instead try methods + 3 and 4. */ +#elif defined(HAVE_SELECT) fd_set fds; - int retv; - struct timeval tv = { 0, 0 }; - /* - * Note that the following code is fragile. If the file is closed (/dev/null) - * then select() may return 1 (at least on OS X), so that we return a flag - * saying characters are available but will find none to read. See also the - * code in cl_clear_input(). - */ + struct timeval tv = {0, 0}; FD_ZERO(&fds); FD_SET(fileno, &fds); - retv = select(fileno + 1, &fds, NULL, NULL, &tv); - if (ecl_unlikely(retv < 0)) - file_libc_error(@[stream-error], stream, "Error while listening to stream.", 0); - /* XXX: for FIFO there should be also peek-byte (not implemented and peek-char - doesn't work for binary streams). */ - else if ((retv > 0) /* && (generic_peek_char(stream) != EOF) */) { - return ECL_LISTEN_AVAILABLE; - } - else { +restart_select: + result = select(fileno + 1, &fds, NULL, NULL, &tv); + if (ecl_unlikely(result < 0)) { + if (errno == EINTR) + goto restart_select; + if (errno != EBADF) /* UNIX_LINUX returns EBADF for files! */ + goto listen_error; + } else if (result == 0) { return ECL_LISTEN_NO_CHAR; } -# elif defined(FIONREAD) - { - long c = 0; - ioctl(fileno, FIONREAD, &c); - return (c > 0)? ECL_LISTEN_AVAILABLE : ECL_LISTEN_NO_CHAR; +#endif +#ifdef FIONREAD + long c = 0; + if (ioctl(fileno, FIONREAD, &c) < 0) { + if (!((errno == ENOTTY) || IS_EINVAL)) + goto listen_error; + return (c > 0) ? ECL_LISTEN_AVAILABLE : ECL_LISTEN_EOF; } -# endif - return ECL_LISTEN_FALLBACK; +#endif +#if !defined(HAVE_POLL) && !defined(HAVE_SELECT) + int flags = fcntl(fd, F_GETFL, 0); +#endif + int read_errno; + cl_index b; +restart_read: +#if !defined(HAVE_POLL) && !defined(HAVE_SELECT) + fcntl(fd, F_SETFL, flags | O_NONBLOCK); +#endif + result = read(fileno, &b, 1); + read_errno = errno; +#if !defined(HAVE_POLL) && !defined(HAVE_SELECT) + fcntl(fd, F_SETFL, flags); +#endif + if (result < 0) { + if (read_errno == EINTR) + goto restart_read; + if (read_errno == EAGAIN || read_errno == EWOULDBLOCK) + return ECL_LISTEN_NO_CHAR; + goto listen_error; + } + + if (result == 0) { + return ECL_LISTEN_EOF; + } + + stream->stream.byte_stack = CONS(ecl_make_fixnum(b), stream->stream.byte_stack); + return ECL_LISTEN_AVAILABLE; +listen_error: + file_libc_error(@[stream-error], stream, "Error while listening to stream.", 0); } #endif static int -flisten(cl_object stream, FILE *fp) +file_listen(cl_object stream, FILE *fp) { int aux; if (feof(fp) || ferror(fp)) @@ -5733,7 +5778,7 @@ flisten(cl_object stream, FILE *fp) if (FILE_CNT(fp) > 0) return ECL_LISTEN_AVAILABLE; #endif - aux = file_listen(stream, fileno(fp)); + aux = fd_listen(stream, fileno(fp)); if (aux != ECL_LISTEN_FALLBACK) return aux; /* This code is portable, and implements the expected behavior for regular files. -- GitLab