diff --git a/NEWS b/NEWS index 7cf84b4c2..b6fdf57da 100644 --- a/NEWS +++ b/NEWS @@ -3,11 +3,6 @@ For full details, see the git log at: https://github.com/ksh93/ksh Any uppercase BUG_* names are modernish shell bug IDs. -2020-08-18: - -- Sped up the 'read' command on most systems by 15-25%. Fixed a hanging bug - on reading from a FIFO that could occur on macOS. - 2020-08-17: - 'command -p' incorrectly used the hash table entry (a.k.a. tracked alias) diff --git a/src/cmd/ksh93/include/version.h b/src/cmd/ksh93/include/version.h index 20c569990..8899cd5ca 100644 --- a/src/cmd/ksh93/include/version.h +++ b/src/cmd/ksh93/include/version.h @@ -17,4 +17,4 @@ * David Korn * * * ***********************************************************************/ -#define SH_RELEASE "93u+m 2020-08-18" +#define SH_RELEASE "93u+m 2020-08-17" diff --git a/src/cmd/ksh93/tests/io.sh b/src/cmd/ksh93/tests/io.sh index cf37d0ce5..2dfd036e9 100755 --- a/src/cmd/ksh93/tests/io.sh +++ b/src/cmd/ksh93/tests/io.sh @@ -583,19 +583,5 @@ then exec 3>&- err_exit "Open file descriptor leaks out of subshell" fi -# ====== -# On unpatched ksh on macOS, 'read' used to block when reading from a FIFO and there was no final newline. -if mkfifo "$tmp/fifo_no_lf" -then trap 'sleep_pid=0; kill "$ksh_pid"; err_exit "'\''read'\'' hangs on EOF without final linefeed when reading from FIFO"' TERM - (sleep 1; kill "$$") & - sleep_pid=$! - "$SHELL" -c 'print -n foo >$0 & while read f; do :; done <$0' "$tmp/fifo_no_lf" & - ksh_pid=$! - wait "$ksh_pid" - trap - TERM - ((sleep_pid)) && kill "$sleep_pid" -else err_exit "mkfifo failed; cannot test reading from FIFO" -fi - # ====== exit $((Errors<125?Errors:125)) diff --git a/src/lib/libast/sfio/sfpkrd.c b/src/lib/libast/sfio/sfpkrd.c index a48561f76..2572e6dc6 100644 --- a/src/lib/libast/sfio/sfpkrd.c +++ b/src/lib/libast/sfio/sfpkrd.c @@ -20,12 +20,11 @@ * * ***********************************************************************/ #include "sfhdr.h" - -#if !_socket_peek -#if __APPLE__ -#error The socket_peek feature is required. (Hey Apple, revert your src__lib__libast__features__lib.diff patch; it caused multiple regressions, and the hanging bug it fixed is now fixed correctly. See .) -#else -#error The socket_peek feature is required. +#if !_PACKAGE_ast +#ifndef FIONREAD +#if _sys_ioctl +#include +#endif #endif #endif @@ -34,6 +33,9 @@ ** Written by Kiem-Phong Vo. */ +#define STREAM_PEEK 001 +#define SOCKET_PEEK 002 + #if __STD_C ssize_t sfpkrd(int fd, Void_t* argbuf, size_t n, int rc, long tm, int action) #else @@ -50,28 +52,237 @@ int action; /* >0: peeking, if rc>=0, get action records, #endif { reg ssize_t r; - reg int t; + reg int ntry, t; reg char *buf = (char*)argbuf, *endbuf; if(rc < 0 && tm < 0 && action <= 0) return sysreadf(fd,buf,n); - r = -1; - t = action > 0 || rc >= 0; /* socket peek using recv(2)? */ - if(t) - { while(t && (r = recv(fd,(char*)buf,n,MSG_PEEK)) < 0) - { if(errno == EINTR) + t = (action > 0 || rc >= 0) ? (STREAM_PEEK|SOCKET_PEEK) : 0; +#if !_stream_peek + t &= ~STREAM_PEEK; +#endif +#if !_socket_peek + t &= ~SOCKET_PEEK; +#endif + + for(ntry = 0; ntry < 2; ++ntry) + { + r = -1; +#if _stream_peek + if((t&STREAM_PEEK) && (ntry == 1 || tm < 0) ) + { +#ifdef __sun + /* + * I_PEEK on stdin can hang rsh+ksh on solaris + * this kludge will have to do until sun^H^H^Horacle fixes I_PEEK/rsh + */ + static int stream_peek; + if (stream_peek == 0) /* this will be done just once */ + { char *e; + stream_peek = ( + getenv("LOGNAME") == 0 && + getenv("MAIL") == 0 && + ((e = getenv("LANG")) == 0 || strcmp(e, "C") == 0) && + ((e = getenv("PATH")) == 0 || strncmp(e, "/usr/bin:", 9) == 0) + ) ? -1 : 1; + } + if(stream_peek < 0) + t &= ~STREAM_PEEK; + else +#endif + { struct strpeek pbuf; + pbuf.flags = 0; + pbuf.ctlbuf.maxlen = -1; + pbuf.ctlbuf.len = 0; + pbuf.ctlbuf.buf = NIL(char*); + pbuf.databuf.maxlen = n; + pbuf.databuf.buf = buf; + pbuf.databuf.len = 0; + + if((r = ioctl(fd,I_PEEK,&pbuf)) < 0) + { if(errno == EINTR) + return -1; + t &= ~STREAM_PEEK; + } + else + { t &= ~SOCKET_PEEK; + if(r > 0 && (r = pbuf.databuf.len) <= 0) + { if(action <= 0) /* read past eof */ + r = sysreadf(fd,buf,1); + return r; + } + if(r == 0) + r = -1; + else if(r > 0) + break; + } + } + } +#endif /* stream_peek */ + + if(ntry == 1) + break; + + /* poll or select to see if data is present. */ + while(tm >= 0 || action > 0 || + /* block until there is data before peeking again */ + ((t&STREAM_PEEK) && rc >= 0) || + /* let select be interrupted instead of recv which autoresumes */ + (t&SOCKET_PEEK) ) + { r = -2; +#if _lib_poll + if(r == -2) + { + struct pollfd po; + po.fd = fd; + po.events = POLLIN; + po.revents = 0; + + if((r = SFPOLL(&po,1,tm)) < 0) + { if(errno == EINTR) + return -1; + else if(errno == EAGAIN) + { errno = 0; + continue; + } + else r = -2; + } + else r = (po.revents&POLLIN) ? 1 : -1; + } +#endif /*_lib_poll*/ +#if _lib_select + if(r == -2) + { +#if _hpux_threads && vt_threaded +#define fd_set int +#endif + fd_set rd; + struct timeval tmb, *tmp; + FD_ZERO(&rd); + FD_SET(fd,&rd); + if(tm < 0) + tmp = NIL(struct timeval*); + else + { tmp = &tmb; + tmb.tv_sec = tm/SECOND; + tmb.tv_usec = (tm%SECOND)*SECOND; + } + r = select(fd+1,&rd,NIL(fd_set*),NIL(fd_set*),tmp); + if(r < 0) + { if(errno == EINTR) + return -1; + else if(errno == EAGAIN) + { errno = 0; + continue; + } + else r = -2; + } + else r = FD_ISSET(fd,&rd) ? 1 : -1; + } +#endif /*_lib_select*/ + if(r == -2) + { +#if !_lib_poll && !_lib_select /* both poll and select can't be used */ +#ifdef FIONREAD /* quick and dirty check for availability */ + long nsec = tm < 0 ? 0 : (tm+999)/1000; + while(nsec > 0 && r < 0) + { long avail = -1; + if((r = ioctl(fd,FIONREAD,&avail)) < 0) + { if(errno == EINTR) + return -1; + else if(errno == EAGAIN) + { errno = 0; + continue; + } + else /* ioctl failed completely */ + { r = -2; + break; + } + } + else r = avail <= 0 ? -1 : (ssize_t)avail; + + if(r < 0 && nsec-- > 0) + sleep(1); + } +#endif +#endif + } + + if(r > 0) /* there is data now */ + { if(action <= 0 && rc < 0) + return sysreadf(fd,buf,n); + else r = -1; + } + else if(tm >= 0) /* timeout exceeded */ return -1; - else if(errno == EAGAIN) - errno = 0; - else t = 0; + else r = -1; + break; } - if(r == 0) - { /* read past eof */ - if(action <= 0) - r = sysreadf(fd,buf,1); - return r; + +#if _socket_peek + if(t&SOCKET_PEEK) + { +#if __MACH__ && __APPLE__ /* check 10.4 recv(MSG_PEEK) bug that consumes pipe data */ + static int recv_peek_pipe; + if (recv_peek_pipe == 0) /* this will be done just once */ + { int fds[2], r; + char tst[2]; + + tst[0] = 'a'; tst[1] = 'z'; + + /* open a pipe and write to it */ + recv_peek_pipe = 1; + if(recv_peek_pipe == 1 && pipe(fds) < 0) + recv_peek_pipe = -1; + if(recv_peek_pipe == 1 && write(fds[1], tst, 2) != 2) + recv_peek_pipe = -1; + + /* try recv() to see if it gets anything */ + tst[0] = tst[1] = 0; + if(recv_peek_pipe == 1 && (r = recv(fds[0], tst, 1, MSG_PEEK)) != 1) + recv_peek_pipe = -1; + if(recv_peek_pipe == 1 && tst[0] != 'a') + recv_peek_pipe = -1; + + /* make sure that recv() did not consume data */ + tst[0] = tst[1] = 0; + if(recv_peek_pipe == 1 && (r = recv(fds[0], tst, 2, MSG_PEEK)) != 2) + recv_peek_pipe = -1; + if(recv_peek_pipe == 1 && (tst[0] != 'a' || tst[1] != 'z') ) + recv_peek_pipe = -1; + + close(fds[0]); + close(fds[1]); + } + + if(recv_peek_pipe < 0) + { struct stat st; /* recv should work on sockets */ + if(fstat(fd, &st) < 0 || !S_ISSOCK(st.st_mode) ) + { r = -1; + t &= ~SOCKET_PEEK; + } + } +#endif + while((t&SOCKET_PEEK) && (r = recv(fd,(char*)buf,n,MSG_PEEK)) < 0) + { if(errno == EINTR) + return -1; + else if(errno == EAGAIN) + errno = 0; + else t &= ~SOCKET_PEEK; + } + if(r >= 0) + { t &= ~STREAM_PEEK; + if(r > 0) + break; + else /* read past eof */ + { if(action <= 0) + r = sysreadf(fd,buf,1); + return r; + } + } } +#endif } if(r < 0)