summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog7
-rw-r--r--Doc/Zsh/zle.yo47
-rw-r--r--Src/Zle/zle_main.c39
3 files changed, 72 insertions, 21 deletions
diff --git a/ChangeLog b/ChangeLog
index e215b590c..285d110f6 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+2014-02-23  Barton E. Schaefer  <schaefer@zsh.org>
+
+	* 32427: Doc/Zsh/zle.yo, Src/Zle/zle_main.c: avoid busy loop
+	on closed descriptors for "zle -F" handlers.  Assure that the
+	handlers are called on error conditions and document the extra
+	argument that is passed in the error case.
+
 2014-02-19  Peter Stephenson  <p.stephenson@samsung.com>
 
 	* 32414: Src/glob.c: improved error message for missing glob
diff --git a/Doc/Zsh/zle.yo b/Doc/Zsh/zle.yo
index 6d3bb4bd0..127b4c45b 100644
--- a/Doc/Zsh/zle.yo
+++ b/Doc/Zsh/zle.yo
@@ -492,26 +492,35 @@ Only available if your system supports one of the `poll' or `select' system
 calls; most modern systems do.
 
 Installs var(handler) (the name of a shell function) to handle input from
-file descriptor var(fd).  When zle is attempting to read data, it will
-examine both the terminal and the list of handled var(fd)'s.  If data
-becomes available on a handled var(fd), zle will call var(handler) with
-the fd which is ready for reading as the only argument.  If the handler
-produces output to the terminal, it should call `tt(zle -I)' before doing
-so (see below).  The handler should not attempt to read from the terminal.
-Note that zle makes no attempt to check whether this fd is actually
+file descriptor var(fd).  Installing a handler for an var(fd) which is
+already handled causes the existing handler to be replaced.  Any number of
+handlers for any number of readable file descriptors may be installed.
+Note that zle makes no attempt to check whether this var(fd) is actually
 readable when installing the handler.  The user must make their own
 arrangements for handling the file descriptor when zle is not active.
 
-If the option tt(-w) is also given, the var(handler) is instead a
-line editor widget, typically a shell function made into a widget using
-tt(zle -N).  In that case var(handler) can use all the facilities of
-zle to update the current editing line.  Note, however, that as handling
-var(fd) takes place at a low level changes to the display will not
-automatically appear; the widget should call tt(zle -R) to force redisplay.
-
-Any number of handlers for any number of readable file descriptors may be
-installed.  Installing a handler for an var(fd) which is already handled
-causes the existing handler to be replaced.
+When zle is attempting to read data, it will examine both the terminal and
+the list of handled var(fd)'s.  If data becomes available on a handled
+var(fd), zle calls var(handler) with the fd which is ready for reading
+as the first argument.  Under normal circumstances this is the only
+argument, but if an error was detected, a second argument provides
+details: `tt(hup)' for a disconnect, `tt(nval)' for a closed or otherwise
+invalid descriptor, or `tt(err)' for any other condition.  Systems that
+support only the `select' system call always use `tt(err)'.
+
+If the option tt(-w) is also given, the var(handler) is instead a line
+editor widget, typically a shell function made into a widget using
+`tt(zle -N)'.  In that case var(handler) can use all the facilities of zle
+to update the current editing line.  Note, however, that as handling var(fd)
+takes place at a low level changes to the display will not automatically
+appear; the widget should call `tt(zle -R)' to force redisplay.  As of this
+writing, widget handlers only support a single argument and thus are never
+passed a string for error state, so widgets must be prepared to test the
+descriptor themselves.
+
+If either type of handler produces output to the terminal, it should call
+`tt(zle -I)' before doing so (see below).  Handlers should not attempt to
+read from the terminal.
 
 If no var(handler) is given, but an var(fd) is present, any handler for
 that var(fd) is removed.  If there is none, an error message is printed
@@ -526,7 +535,8 @@ silently return status 1.
 
 Note that this feature should be used with care.  Activity on one of the
 var(fd)'s which is not properly handled can cause the terminal to become
-unusable.
+unusable.  Removing an var(fd) handler from within a signal trap may cause
+unpredictable behavior.
 
 Here is a simple example of using this feature.  A connection to a remote
 TCP port is created using the ztcp command; see 
@@ -536,6 +546,7 @@ which simply prints out any data which arrives on this connection.  Note
 that `select' will indicate that the file descriptor needs handling
 if the remote side has closed the connection; we handle that by testing
 for a failed read.
+
 example(if ztcp pwspc 2811; then
   tcpfd=$REPLY
   handler+LPAR()RPAR() {
diff --git a/Src/Zle/zle_main.c b/Src/Zle/zle_main.c
index b0010fc33..442c31995 100644
--- a/Src/Zle/zle_main.c
+++ b/Src/Zle/zle_main.c
@@ -525,7 +525,8 @@ raw_getbyte(long do_keytmout, char *cptr)
 #endif
 #ifndef HAVE_POLL
 # ifdef HAVE_SELECT
-    fd_set foofd;
+    fd_set foofd, errfd;
+    FD_ZERO(&errfd);
 # endif
 #endif
 
@@ -613,11 +614,14 @@ raw_getbyte(long do_keytmout, char *cptr)
 	    if (!errtry) {
 		for (i = 0; i < nwatch; i++) {
 		    int fd = watch_fds[i].fd;
+		    if (FD_ISSET(fd, &errfd))
+			continue;
 		    FD_SET(fd, &foofd);
 		    if (fd > fdmax)
 			fdmax = fd;
 		}
 	    }
+	    FD_ZERO(&errfd);
 
 	    if (tmout.tp != ZTM_NONE) {
 		expire_tv.tv_sec = tmout.exp100ths / 100;
@@ -732,9 +736,10 @@ raw_getbyte(long do_keytmout, char *cptr)
 		    Watch_fd lwatch_fd = lwatch_fds + i;
 		    if (
 # ifdef HAVE_POLL
-			(fds[i+1].revents & POLLIN)
+			(fds[i+1].revents & (POLLIN|POLLERR|POLLHUP|POLLNVAL))
 # else
-			FD_ISSET(lwatch_fd->fd, &foofd)
+			FD_ISSET(lwatch_fd->fd, &foofd) ||
+			FD_ISSET(lwatch_fd->fd, &errfd)
 # endif
 			) {
 			/* Handle the fd. */
@@ -765,6 +770,9 @@ raw_getbyte(long do_keytmout, char *cptr)
 			    if (fds[i+1].revents & POLLNVAL)
 				zaddlinknode(funcargs, ztrdup("nval"));
 #  endif
+# else
+			    if (FD_ISSET(lwatch_fd->fd, &errfd))
+				zaddlinknode(funcargs, ztrdup("err"));
 # endif
 			    callhookfunc(lwatch_fd->func, funcargs, 0, NULL);
 			    freelinklist(funcargs, freestr);
@@ -786,6 +794,31 @@ raw_getbyte(long do_keytmout, char *cptr)
 		for (i = 0; i < lnwatch; i++)
 		    zsfree(lwatch_fds[i].func);
 		zfree(lwatch_fds, lnwatch*sizeof(struct watch_fd));
+
+# ifdef HAVE_POLL
+		/* Function may have added or removed handlers */
+		nfds = 1 + nwatch;
+		if (nfds > 1) {
+		    fds = zrealloc(fds, sizeof(struct pollfd) * nfds);
+		    for (i = 0; i < nwatch; i++) {
+			/*
+			 * This is imperfect because it assumes fds[] and
+			 * watch_fds[] remain in sync, which may be false
+			 * if handlers are shuffled.  However, it should
+			 * be harmless (e.g., produce one extra pass of
+			 * the loop) in the event they fall out of sync.
+			 */
+			if (fds[i+1].fd == watch_fds[i].fd &&
+			    (fds[i+1].revents & (POLLERR|POLLHUP|POLLNVAL))) {
+			    fds[i+1].events = 0;	/* Don't poll this */
+			} else {
+			    fds[i+1].fd = watch_fds[i].fd;
+			    fds[i+1].events = POLLIN;
+			}
+			fds[i+1].revents = 0;
+		    }
+		}
+# endif
 	    }
 	}
 # ifdef HAVE_POLL