From afe1b00e16c7efc5c93c958fa4f357325977a2a1 Mon Sep 17 00:00:00 2001 From: Peter Stephenson Date: Tue, 21 May 2002 11:10:13 +0000 Subject: 17141 plus mods: add `zle -F fd handler' feature. --- ChangeLog | 7 ++ Doc/Zsh/zle.yo | 60 ++++++++++++++++ Src/Zle/zle_main.c | 195 ++++++++++++++++++++++++++++++++++++++------------- Src/Zle/zle_thingy.c | 103 +++++++++++++++++++++++++++ Src/utils.c | 46 ++++++------ 5 files changed, 343 insertions(+), 68 deletions(-) diff --git a/ChangeLog b/ChangeLog index 985095b1b..d7c502d51 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2002-05-21 Peter Stephenson + + * 17141 plus mods: Src/utils.c, Src/Zle/zle_main.c, + Src/Zle/zle_thingy.c, Doc/Zsh/zle.yo: `zle -F fd handler' + installs shell function handler for when data becomes available + on fd while zle is active. Requires select(). + 2002-05-21 Sven Wischnowsky * 17195: Src/Zle/comp.h, Src/Zle/compcore.c, Src/Zle/compctl.c, diff --git a/Doc/Zsh/zle.yo b/Doc/Zsh/zle.yo index aee624eb7..67e66830d 100644 --- a/Doc/Zsh/zle.yo +++ b/Doc/Zsh/zle.yo @@ -314,6 +314,7 @@ xitem(tt(zle) tt(-R) [ tt(-c) ] [ var(display-string) ] [ var(string) ... ]) 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))( @@ -411,6 +412,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 -r 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. It invalidates the current zle display in preparation for output; usually diff --git a/Src/Zle/zle_main.c b/Src/Zle/zle_main.c index 5af18fbac..56797a9a9 100644 --- a/Src/Zle/zle_main.c +++ b/Src/Zle/zle_main.c @@ -141,6 +141,17 @@ mod_export char *zlenoargs[1] = { NULL }; 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,174 @@ breakread(int fd, char *buf, int n) # 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 { + /* + * 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 - if (delayzsetterm) { - int val; - ioctl(SHTTY, FIONREAD, (char *)&val); - if (!val) - zsetterm(); - } + && ! delayzsetterm #endif - if (keytmout -#ifdef FIONREAD - && ! 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); + /* + * Make sure a user interrupt gets passed on straight away. + */ + if (selret < 0 && errflag) + return selret; + /* + * 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 +1200,7 @@ zleaftertrap(Hookdef dummy, void *dat) 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 diff --git a/Src/Zle/zle_thingy.c b/Src/Zle/zle_thingy.c index dd2aea40d..7cf558f97 100644 --- a/Src/Zle/zle_thingy.c +++ b/Src/Zle/zle_thingy.c @@ -341,6 +341,7 @@ bin_zle(char *name, char **args, char *ops, int func) { '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; @@ -678,6 +679,108 @@ bin_zle_invalidate(char *name, char **args, char *ops, char func) 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, watch_funcs, 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; +} + /*******************/ /* initialiasation */ /*******************/ diff --git a/Src/utils.c b/Src/utils.c index 6a0c27e29..24d336b7a 100644 --- a/Src/utils.c +++ b/Src/utils.c @@ -624,6 +624,30 @@ time_t lastmailcheck; /**/ time_t lastwatch; +/**/ +mod_export int +callhookfunc(char *name, LinkList lnklst) +{ + Eprog prog; + + if ((prog = getshfunc(name)) != &dummy_eprog) { + /* + * Save stopmsg, since user doesn't get a chance to respond + * to a list of jobs generated in a hook. + */ + int osc = sfcontext, osm = stopmsg; + + sfcontext = SFC_HOOK; + doshfunc(name, prog, lnklst, 0, 1); + sfcontext = osc; + stopmsg = osm; + + return 0; + } + + return 1; +} + /* do pre-prompt stuff */ /**/ @@ -632,7 +656,6 @@ preprompt(void) { static time_t lastperiodic; LinkNode ln; - Eprog prog; int period = getiparam("PERIOD"); int mailcheck = getiparam("MAILCHECK"); @@ -645,18 +668,7 @@ preprompt(void) /* If a shell function named "precmd" exists, * * then execute it. */ - if ((prog = getshfunc("precmd")) != &dummy_eprog) { - /* - * Save stopmsg, since user doesn't get a chance to respond - * to a list of jobs generated in precmd. - */ - int osc = sfcontext, osm = stopmsg; - - sfcontext = SFC_HOOK; - doshfunc("precmd", prog, NULL, 0, 1); - sfcontext = osc; - stopmsg = osm; - } + callhookfunc("precmd", NULL); if (errflag) return; @@ -664,14 +676,8 @@ preprompt(void) * "periodic" exists, 3) it's been greater than PERIOD since we * * executed "periodic", then execute it now. */ if (period && (time(NULL) > lastperiodic + period) && - (prog = getshfunc("periodic")) != &dummy_eprog) { - int osc = sfcontext; - - sfcontext = SFC_HOOK; - doshfunc("periodic", prog, NULL, 0, 1); - sfcontext = osc; + !callhookfunc("periodic", NULL)) lastperiodic = time(NULL); - } if (errflag) return; -- cgit 1.4.1