Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

PATCH: more selection



This patch is more advanced warning of future pain than anything else,
in case anyone wants to scream at the very idea.

The following patch adds an fd-watching facility to zle.  You tell zle
(via zle -F) to select on an fd and give it a function handler.  When data
is available for reading on that handle (only reading, at the moment),
the handler will be called, and zle will sail on without returning.
This allows you to handle input from other sources while the shell is
inactive waiting for terminal input.  It's all handled synchronously
within zle, so it should be free of the hazards of asynchronous input.

Unfortunately, this has to be part of the core zle, since it needs to
be tied into getkey(), so it's not an optional feature.

I've been getting good results with this so far (I don't even seem to
have screwed up key timeouts, yet!) but I want to do more work, even if
nobody else wants this, or would prefer it to be a compile-time option.
In particular, it might be better to make the list of fds and functions
a list rather than a pair of arrays.  Also, once again it's perfectly
possible to use poll() instead of select(), but I haven't bothered
adding it.

The could easily be more nasties lurking within the fact that the
call to the handler function is rather deep within zle.

Index: Doc/Zsh/zle.yo
===================================================================
RCS file: /cvsroot/zsh/zsh/Doc/Zsh/zle.yo,v
retrieving revision 1.17
diff -u -r1.17 zle.yo
--- Doc/Zsh/zle.yo	5 Mar 2002 16:33:21 -0000	1.17
+++ Doc/Zsh/zle.yo	13 May 2002 15:18:49 -0000
@@ -314,6 +314,7 @@
 xitem(tt(zle) tt(-M) var(string))
 xitem(tt(zle) tt(-U) var(string))
 xitem(tt(zle) tt(-K) var(keymap))
+xitem(tt(zle) tt(-F) [ tt(-L) ] [ var(fd) [ var(handler) ] ])
 xitem(tt(zle) tt(-I))
 xitem(tt(zle) var(widget) tt([ -n) var(num) tt(]) tt([ -N ]) var(args) ...)
 item(tt(zle))(
@@ -410,6 +411,65 @@
 This keymap selection affects the interpretation of following keystrokes
 within this invocation of ZLE.  Any following invocation (e.g., the next
 command line) will start as usual with the `tt(main)' keymap selected.
+)
+item(tt(-F) [ tt(-L) ] [ var(fd) [ var(handler) ] ])(
+Only available if your system support the `select' system call; 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
+readable when installing the handler.  The user must make their own
+arrangments for handling the file descriptor when zle is not active.
+
+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.
+
+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
+and status 1 is returned.
+
+If no arguments are given, or the tt(-L) option is supplied, a list of
+handlers is printed in a form which can be stored for later execution.
+
+An var(fd) (but not a var(handler)) may optionally be given with the tt(-L)
+option; in this case, the function will list the handler if any, else
+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.
+
+Here is a simple example of using this feature.  A connection to a remote
+TCP port is created using the ztcp command; see 
+ifzman(the description of the tt(zsh/net/tcp) module in zmanref(zshmodules))\
+ifnzman(noderef(The zsh/net/tcp Module)).  Then a handler is installed
+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() {
+    zle -I
+    local line
+    if ! read line <&$1; then
+      # select marks this fd if we reach EOF,
+      # so handle this specially.
+      print "[Read on fd $1 failed, removing.]" >&2
+      zle -F $1
+      return 1
+    fi
+    print -r - $line
+  }
+  zle -F $tcpfd handler
+fi)
 )
 item(tt(-I))(
 Unusually, this option is only useful em(outside) ordinary widget functions.
Index: Src/Zle/zle_main.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/Zle/zle_main.c,v
retrieving revision 1.22
diff -u -r1.22 zle_main.c
--- Src/Zle/zle_main.c	1 Mar 2002 10:42:02 -0000	1.22
+++ Src/Zle/zle_main.c	13 May 2002 15:18:49 -0000
@@ -141,6 +141,17 @@
 static int delayzsetterm;
 #endif
 
+/*
+ * File descriptors we are watching as well as the terminal fd. 
+ * These are all for reading; we don't watch for writes or exceptions.
+ */
+/**/
+int nwatch;		/* Number of fd's we are watching */
+/**/
+int *watch_fds;		/* The list of fds, not terminated! */
+/**/
+char **watch_funcs;	/* The corresponding functions to call, normal array */
+
 /* set up terminal */
 
 /**/
@@ -324,86 +335,169 @@
 # define read    breakread
 #endif
 
-/**/
-mod_export int
-getkey(int keytmout)
+static int
+raw_getkey(int keytmout, char *cptr)
 {
-    char cc;
-    unsigned int ret;
     long exp100ths;
-    int die = 0, r, icnt = 0;
-    int old_errno = errno, obreaks = breaks;
-
+    int ret;
 #ifdef HAVE_SELECT
     fd_set foofd;
-
 #else
 # ifdef HAS_TIO
     struct ttyinfo ti;
-
 # endif
 #endif
 
-    if (kungetct)
-	ret = STOUC(kungetbuf[--kungetct]);
-    else {
-#ifdef FIONREAD
-	if (delayzsetterm) {
-	    int val;
-	    ioctl(SHTTY, FIONREAD, (char *)&val);
-	    if (!val)
-		zsetterm();
-	}
-#endif
-	if (keytmout
+    /*
+     * Handle timeouts and watched fd's.  We only do one at once;
+     * key timeouts take precedence.  This saves tricky timing
+     * problems with the key timeout.
+     */
+    if ((nwatch || keytmout)
 #ifdef FIONREAD
-	    && ! delayzsetterm
+	&& ! delayzsetterm
 #endif
-	    ) {
-	    if (keytimeout > 500)
-		exp100ths = 500;
-	    else if (keytimeout > 0)
-		exp100ths = keytimeout;
-	    else
-		exp100ths = 0;
+	) {
+	if (!keytmout || keytimeout <= 0)
+	    exp100ths = 0;
+	else if (keytimeout > 500)
+	    exp100ths = 500;
+	else
+	    exp100ths = keytimeout;
 #ifdef HAVE_SELECT
+	if (!keytmout || exp100ths) {
+	    struct timeval *tvptr = NULL;
+	    struct timeval expire_tv;
+	    int i, fdmax = SHTTY, errtry = 0;
 	    if (exp100ths) {
-		struct timeval expire_tv;
-
 		expire_tv.tv_sec = exp100ths / 100;
 		expire_tv.tv_usec = (exp100ths % 100) * 10000L;
+		tvptr = &expire_tv;
+	    }
+	    do {
+		int selret;
 		FD_ZERO(&foofd);
 		FD_SET(SHTTY, &foofd);
-		if (select(SHTTY+1, (SELECT_ARG_2_T) & foofd,
-			   NULL, NULL, &expire_tv) <= 0)
-		    return EOF;
-	    }
+		if (!keytmout && !errtry) {
+		    for (i = 0; i < nwatch; i++) {
+			int fd = watch_fds[i];
+			FD_SET(fd, &foofd);
+			if (fd > fdmax)
+			    fdmax = fd;
+		    }
+		}
+		selret = select(fdmax+1, (SELECT_ARG_2_T) & foofd,
+				NULL, NULL, tvptr);
+		/*
+		 * Try to avoid errors on our special fd's from
+		 * messing up reads from the terminal.  Try first
+		 * with all fds, then try unsetting the special ones.
+		 */
+		if (selret < 0 && !keytmout && !errtry) {
+		    errtry = 1;
+		    continue;
+		}
+		if (selret == 0) {
+		    /* Special value -2 signals nothing ready */
+		    return -2;
+		} else if (selret < 0)
+		    return selret;
+		if (!keytmout && nwatch) {
+		    /*
+		     * Copy the details of the watch fds in case the
+		     * user decides to delete one from inside the
+		     * handler function.
+		     */
+		    int lnwatch = nwatch;
+		    int *lwatch_fds = zalloc(lnwatch*sizeof(int));
+		    char **lwatch_funcs = zarrdup(watch_funcs);
+		    memcpy(lwatch_fds, watch_fds, lnwatch*sizeof(int));
+		    for (i = 0; i < lnwatch; i++) {
+			if (FD_ISSET(lwatch_fds[i], &foofd)) {
+			    /* Handle the fd. */
+			    LinkList funcargs = znewlinklist();
+			    zaddlinknode(funcargs, ztrdup(lwatch_funcs[i]));
+			    {
+				char buf[BDIGBUFSIZE];
+				convbase(buf, lwatch_fds[i], 10);
+				zaddlinknode(funcargs, ztrdup(buf));
+			    }
+
+			    callhookfunc(lwatch_funcs[i], funcargs);
+			    if (errflag) {
+				/* No sensible way of handling errors here */
+				errflag = 0;
+				/*
+				 * Paranoia: don't run the hooks again this
+				 * time.
+				 */
+				errtry = 1;
+			    }
+			    freelinklist(funcargs, freestr);
+			}
+		    }
+		    /* Function may have invalidated the display. */
+		    if (resetneeded)
+			zrefresh();
+		    zfree(lwatch_fds, lnwatch*sizeof(int));
+		    freearray(lwatch_funcs);
+		}
+	    } while (!FD_ISSET(SHTTY, &foofd));
+	}
 #else
 # ifdef HAS_TIO
-	    ti = shttyinfo;
-	    ti.tio.c_lflag &= ~ICANON;
-	    ti.tio.c_cc[VMIN] = 0;
-	    ti.tio.c_cc[VTIME] = exp100ths / 10;
+	ti = shttyinfo;
+	ti.tio.c_lflag &= ~ICANON;
+	ti.tio.c_cc[VMIN] = 0;
+	ti.tio.c_cc[VTIME] = exp100ths / 10;
 #  ifdef HAVE_TERMIOS_H
-	    tcsetattr(SHTTY, TCSANOW, &ti.tio);
+	tcsetattr(SHTTY, TCSANOW, &ti.tio);
 #  else
-	    ioctl(SHTTY, TCSETA, &ti.tio);
+	ioctl(SHTTY, TCSETA, &ti.tio);
 #  endif
-	    r = read(SHTTY, &cc, 1);
+	ret = read(SHTTY, cptr, 1);
 #  ifdef HAVE_TERMIOS_H
-	    tcsetattr(SHTTY, TCSANOW, &shttyinfo.tio);
+	tcsetattr(SHTTY, TCSANOW, &shttyinfo.tio);
 #  else
-	    ioctl(SHTTY, TCSETA, &shttyinfo.tio);
+	ioctl(SHTTY, TCSETA, &shttyinfo.tio);
 #  endif
-	    return (r <= 0) ? EOF : cc;
+	return (ret <= 0) ? ret : *cptr;
 # endif
 #endif
+    }
+
+    ret = read(SHTTY, cptr, 1);
+
+    return ret;
+}
+
+/**/
+mod_export int
+getkey(int keytmout)
+{
+    char cc;
+    unsigned int ret;
+    int die = 0, r, icnt = 0;
+    int old_errno = errno, obreaks = breaks;
+
+    if (kungetct)
+	ret = STOUC(kungetbuf[--kungetct]);
+    else {
+#ifdef FIONREAD
+	if (delayzsetterm) {
+	    int val;
+	    ioctl(SHTTY, FIONREAD, (char *)&val);
+	    if (!val)
+		zsetterm();
 	}
+#endif
 	for (;;) {
 	    int q = queue_signal_level();
 	    dont_queue_signals();
-	    r = read(SHTTY, &cc, 1);
+	    r = raw_getkey(keytmout, &cc);
 	    restore_queue_signals(q);
+	    if (r == -2)	/* timeout */
+		return EOF;
 	    if (r == 1)
 		break;
 	    if (r == 0) {
@@ -1101,7 +1195,7 @@
 static struct builtin bintab[] = {
     BUILTIN("bindkey", 0, bin_bindkey, 0, -1, 0, "evaMldDANmrsLRp", NULL),
     BUILTIN("vared",   0, bin_vared,   1,  7, 0, NULL,             NULL),
-    BUILTIN("zle",     0, bin_zle,     0, -1, 0, "lDANCLmMgGcRaUKI", NULL),
+    BUILTIN("zle",     0, bin_zle,     0, -1, 0, "aAcCDFgGIKlLmMNRU", NULL),
 };
 
 /* The order of the entries in this table has to match the *HOOK
Index: Src/Zle/zle_thingy.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/Zle/zle_thingy.c,v
retrieving revision 1.4
diff -u -r1.4 zle_thingy.c
--- Src/Zle/zle_thingy.c	3 Sep 2001 01:39:20 -0000	1.4
+++ Src/Zle/zle_thingy.c	13 May 2002 15:18:49 -0000
@@ -341,6 +341,7 @@
 	{ 'U', bin_zle_unget, 1, 1 },
 	{ 'K', bin_zle_keymap, 1, 1 },
 	{ 'I', bin_zle_invalidate, 0, 0 },
+	{ 'F', bin_zle_fd, 0, 2 },
 	{ 0,   bin_zle_call, 0, -1 },
     };
     struct opn const *op, *opp;
@@ -676,6 +677,108 @@
 	return 0;
     } else
 	return 1;
+}
+
+/**/
+static int
+bin_zle_fd(char *name, char **args, char *ops, char func)
+{
+    int fd = 0, i, found = 0;
+    char *endptr;
+
+    if (*args) {
+	fd = (int)zstrtol(*args, &endptr, 10);
+
+	if (*endptr || fd < 0) {
+	    zwarnnam(name, "Bad file descriptor number for -F: %s", *args, 0);
+	    return 1;
+	}
+    }
+
+    if (ops['L'] || !*args) {
+	/* Listing handlers. */
+	if (args[1]) {
+	    zwarnnam(name, "too many arguments for -FL", NULL, 0);
+	    return 1;
+	}
+	for (i = 0; i < nwatch; i++) {
+	    if (*args && watch_fds[i] != fd)
+		continue;
+	    found = 1;
+	    printf("%s -F %d %s\n", name, watch_fds[i], watch_funcs[i]);
+	}
+	/* only return status 1 if fd given and not found */
+	return *args && !found;
+    }
+
+    if (args[1]) {
+	/* Adding or replacing a handler */
+	char *funcnam = ztrdup(args[1]);
+	if (nwatch) {
+	    for (i = 0; i < nwatch; i++) {
+		if (watch_fds[i] == fd) {
+		    zsfree(watch_funcs[i]);
+		    watch_funcs[i] = funcnam;
+		    found = 1;
+		    break;
+		}
+	    }
+	}
+	if (!found) {
+	    /* zrealloc handles NULL pointers, so OK for first time through */
+	    int newnwatch = nwatch+1;
+	    watch_fds = (int *)zrealloc(watch_fds, 
+					newnwatch * sizeof(int));
+	    watch_funcs = (char **)zrealloc(watch_funcs,
+					    (newnwatch+1) * sizeof(char *));
+	    watch_fds[nwatch] = fd;
+	    watch_funcs[nwatch] = funcnam;
+	    watch_funcs[newnwatch] = NULL;
+	    nwatch = newnwatch;
+	}
+    } else {
+	/* Deleting a handler */
+	for (i = 0; i < nwatch; i++) {
+	    if (watch_fds[i] == fd) {
+		int newnwatch = nwatch-1;
+		int *new_fds;
+		char **new_funcs;
+
+		zsfree(watch_funcs[i]);
+		if (newnwatch) {
+		    new_fds = zalloc(newnwatch*sizeof(int));
+		    new_funcs = zalloc((newnwatch+1)*sizeof(char*));
+		    if (i) {
+			memcpy(new_fds, watch_fds, i*sizeof(int));
+			memcpy(new_funcs, new_fds, i*sizeof(char *));
+		    }
+		    if (i < newnwatch) {
+			memcpy(new_fds+i, watch_fds+i+1,
+			       (newnwatch-i)*sizeof(int));
+			memcpy(new_funcs+i, watch_funcs+i+1,
+			       (newnwatch-i)*sizeof(char *));
+		    }
+		    new_funcs[newnwatch] = NULL;
+		} else {
+		    new_fds = NULL;
+		    new_funcs = NULL;
+		}
+		zfree(watch_fds, nwatch*sizeof(int));
+		zfree(watch_funcs, (nwatch+1)*sizeof(char *));
+		watch_fds = new_fds;
+		watch_funcs = new_funcs;
+		nwatch = newnwatch;
+		found = 1;
+		break;
+	    }
+	}
+	if (!found) {
+	    zwarnnam(name, "No handler installed for fd %d", NULL, fd);
+	    return 1;
+	}
+    }
+
+    return 0;
 }
 
 /*******************/

-- 
Peter Stephenson <pws@xxxxxxx>                  Software Engineer
CSR Ltd., Science Park, Milton Road,
Cambridge, CB4 0WH, UK                          Tel: +44 (0)1223 392070


**********************************************************************
The information transmitted is intended only for the person or
entity to which it is addressed and may contain confidential 
and/or privileged material. 
Any review, retransmission, dissemination or other use of, or
taking of any action in reliance upon, this information by 
persons or entities other than the intended recipient is 
prohibited.  
If you received this in error, please contact the sender and 
delete the material from any computer.
**********************************************************************



Messages sorted by: Reverse Date, Date, Thread, Author