From b726ead94e911e1ce3e8b582c315b3d6f83a6eb7 Mon Sep 17 00:00:00 2001 From: Peter Stephenson Date: Sun, 10 Sep 2006 15:24:26 +0000 Subject: 22676, 22678: extend sched and make it able to run events when waiting for input --- ChangeLog | 7 + Doc/Zsh/mod_sched.yo | 39 ++++- README | 6 + Src/Builtins/sched.c | 295 +++++++++++++++++++++++---------- Src/Zle/zle_main.c | 459 ++++++++++++++++++++++++++++++++++----------------- Src/Zle/zle_thingy.c | 3 +- Src/init.c | 1 - Src/subst.c | 29 +++- Src/utils.c | 145 +++++++++++++++- Src/zsh.h | 72 +++++--- 10 files changed, 766 insertions(+), 290 deletions(-) diff --git a/ChangeLog b/ChangeLog index 12f1a78bd..22d4b1d34 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,12 @@ 2006-09-10 Peter Stephenson + * 22676, adapted as in 22678: README, Doc/Zsh/mod_sched.yo, + Src/init.c, Src/subst.c, Src/utils.c, Src/zsh.h, + Src/Builtins/sched.c, Src/Zle/zle_main.c, Src/Zle/zle_thingy.c: + make sched events work when waiting for input in zle; allow sched + to handle the trashzle(); improve the interface to sched both + internally and externally. + * 22681: configure.ac, Src/Modules/system.mdd: pass all files defining errnos to awk so that it finds them all on systems where the list of definitions is fragmented. diff --git a/Doc/Zsh/mod_sched.yo b/Doc/Zsh/mod_sched.yo index 1be550a79..82ba8550f 100644 --- a/Doc/Zsh/mod_sched.yo +++ b/Doc/Zsh/mod_sched.yo @@ -1,17 +1,42 @@ -texinode(The sched Module)(The stat Module)(The files Module)(Zsh Modules) -sect(The sched Module) -The tt(sched) module makes available one builtin command: +COMMENT(!MOD!zsh/sched +A builtin that provides a timed execution facility within the shell. +!MOD!) +The tt(zsh/sched) module makes available one builtin command: startitem() findex(sched) cindex(timed execution) cindex(execution, timed) -xitem(tt(sched) [tt(PLUS())]var(hh)tt(:)var(mm) var(command) ...) +xitem(tt(sched) [tt(-o)] [tt(PLUS())]var(hh)tt(:)var(mm)[:var(ss)] var(command) ...) +xitem(tt(sched) [tt(-o)] [tt(PLUS())]var(seconds) var(command) ...) item(tt(sched) [ tt(-)var(item) ])( Make an entry in the scheduled list of commands to execute. -The time may be specified in either absolute or relative time. -With no arguments, prints the list of scheduled commands. +The time may be specified in either absolute or relative time, +and either as hours, minutes and (optionally) seconds separated by a +colon, or seconds alone. +An absolute number of seconds indicates the time since the epoch +(1970/01/01 00:00); this is useful in combination with the features in +the tt(zsh/datetime) module, see +ifzman(the zsh/datetime module entry in zmanref(zshmodules))\ +ifnzman(noderef(The zsh/datetime Module))\ +. + +With no arguments, prints the list of scheduled commands. If the +scheduled command has the tt(-o) flag set, this is shown at the +start of the command. + With the argument `tt(-)var(item)', removes the given item -from the list. +from the list. The numbering of the list is continguous and entries are +in time order, so the numbering can change when entries are added or +deleted. + +Commands are executed either immediately before a prompt, or while +the shell's line editor is waiting for input. In the latter case +it is useful to be able to produce output that does not interfere +with the line being edited. Providing the option tt(-o) causes +the shell to clear the command line before the event and redraw it +afterwards. This should be used with any scheduled event that produces +visible output to the terminal; it is not needed, for example, with +output that updates a terminal emulatorʼs title bar. ) enditem() diff --git a/README b/README index 68ddfd61a..88b6be3a9 100644 --- a/README +++ b/README @@ -69,6 +69,12 @@ it is supported) there are three possible cases: are allowed in identifiers even though the shell will recognise alphanumeric multibyte characters. +The sched builtin now keeps entries in time order. This means that +after adding an entry the index of an existing entry used for deletion +may change, if that entry had a later time than the new entry. However, +deleting a entry with a later time will now never change the index of an +entry with an earlier time, which could happen with the previous method. + The completion style pine-directory must now be set to use completion for PINE mailbox folders; previously it had the default ~/mail. This change was necessary because otherwise recursive directories under diff --git a/Src/Builtins/sched.c b/Src/Builtins/sched.c index 558a00ba6..c32a5f219 100644 --- a/Src/Builtins/sched.c +++ b/Src/Builtins/sched.c @@ -34,61 +34,156 @@ typedef struct schedcmd *Schedcmd; +/* Flags for each scheduled event */ +enum schedflags { + /* Trash zle if necessary when event is activated */ + SCHEDFLAG_TRASH_ZLE = 1 +}; + struct schedcmd { struct schedcmd *next; char *cmd; /* command to run */ time_t time; /* when to run it */ + int flags; /* flags as above */ }; /* the list of sched jobs pending */ static struct schedcmd *schedcmds; +/* Check scheduled commands; call this function from time to time. */ + +/**/ +static void +checksched(void) +{ + time_t t; + struct schedcmd *sch; + + if(!schedcmds) + return; + t = time(NULL); + /* + * List is ordered, so we only need to consider the + * head element. + */ + while (schedcmds && schedcmds->time <= t) { + /* + * Remove the entry to be executed from the list + * before execution: this makes quite sure that + * the entry hasn't been monkeyed with when we + * free it. + */ + sch = schedcmds; + schedcmds = sch->next; + /* + * Delete from the timed function list now in case + * the called code reschedules. + */ + deltimedfn(checksched); + + if ((sch->flags & SCHEDFLAG_TRASH_ZLE) && zleactive) + trashzleptr(); + execstring(sch->cmd, 0, 0); + zsfree(sch->cmd); + zfree(sch, sizeof(struct schedcmd)); + + /* + * Fix time for future events. + * I had this outside the loop, for a little extra efficiency. + * However, it then occurred to me that having the list of + * forthcoming entries up to date could be regarded as + * a feature, and the inefficiency is negligible. + */ + if (schedcmds) { + /* + * We need to delete the function from the list again, + * in case called code rescheduled. This is almost + * as cheap as checking if it's in the list already. + */ + deltimedfn(checksched); + DPUTS(timedfns && firstnode(timedfns), "BUG: already timed fn (1)"); addtimedfn(checksched, schedcmds->time); + } + } +} + /**/ static int -bin_sched(UNUSED(char *nam), char **argv, UNUSED(Options ops), UNUSED(int func)) +bin_sched(char *nam, char **argv, UNUSED(Options ops), UNUSED(int func)) { - char *s = *argv++; + char *s, **argptr; time_t t; - long h, m; + long h, m, sec; struct tm *tm; struct schedcmd *sch, *sch2, *schl; - int sn; + int sn, flags = 0; /* If the argument begins with a -, remove the specified item from the schedule. */ - if (s && *s == '-') { - sn = atoi(s + 1); + for (argptr = argv; *argptr && **argptr == '-'; argptr++) { + char *arg = *argptr + 1; + if (idigit(*arg)) { + sn = atoi(arg); - if (!sn) { - zwarnnam("sched", "usage for delete: sched -."); - return 1; - } - for (schl = (struct schedcmd *)&schedcmds, sch = schedcmds, sn--; - sch && sn; sch = (schl = sch)->next, sn--); - if (!sch) { - zwarnnam("sched", "not that many entries"); + if (!sn) { + zwarnnam("sched", "usage for delete: sched -."); + return 1; + } + for (schl = NULL, sch = schedcmds, sn--; + sch && sn; sch = (schl = sch)->next, sn--); + if (!sch) { + zwarnnam("sched", "not that many entries"); + return 1; + } + if (schl) + schl->next = sch->next; + else { + deltimedfn(checksched); + schedcmds = sch->next; + if (schedcmds) { + DPUTS(timedfns && firstnode(timedfns), "BUG: already timed fn (2)"); + addtimedfn(checksched, schedcmds->time); + } + } + zsfree(sch->cmd); + zfree(sch, sizeof(struct schedcmd)); + + return 0; + } else if (*arg == '-') { + /* end of options */ + argptr++; + break; + } else if (!strcmp(arg, "o")) { + flags |= SCHEDFLAG_TRASH_ZLE; + } else { + if (*arg) + zwarnnam(nam, "bad option: -%c", *arg); + else + zwarnnam(nam, "option expected"); return 1; } - schl->next = sch->next; - zsfree(sch->cmd); - zfree(sch, sizeof(struct schedcmd)); - - return 0; } /* given no arguments, display the schedule list */ - if (!s) { - char tbuf[40]; + if (!*argptr) { + char tbuf[40], *flagstr, *endstr; for (sn = 1, sch = schedcmds; sch; sch = sch->next, sn++) { t = sch->time; tm = localtime(&t); ztrftime(tbuf, 20, "%a %b %e %k:%M:%S", tm); - printf("%3d %s %s\n", sn, tbuf, sch->cmd); + if (sch->flags & SCHEDFLAG_TRASH_ZLE) + flagstr = "-o "; + else + flagstr = ""; + if (*sch->cmd == '-') + endstr = "-- "; + else + endstr = ""; + printf("%3d %s %s%s%s\n", sn, tbuf, flagstr, endstr, sch->cmd); } return 0; - } else if (!*argv) { + } else if (!argptr[1]) { /* other than the two cases above, sched * *requires at least two arguments */ zwarnnam("sched", "not enough arguments"); @@ -97,86 +192,108 @@ bin_sched(UNUSED(char *nam), char **argv, UNUSED(Options ops), UNUSED(int func)) /* The first argument specifies the time to schedule the command for. The remaining arguments form the command. */ + s = *argptr++; if (*s == '+') { - /* + introduces a relative time. The rest of the argument is an - hour:minute offset from the current time. Once the hour and minute - numbers have been extracted, and the format verified, the resulting - offset is simply added to the current time. */ - h = zstrtol(s + 1, &s, 10); - if (*s != ':') { + /* + * + introduces a relative time. The rest of the argument may be an + * hour:minute offset from the current time. Once the hour and minute + * numbers have been extracted, and the format verified, the resulting + * offset is simply added to the current time. + */ + zlong zl = zstrtol(s + 1, &s, 10); + if (*s == ':') { + m = (long)zstrtol(s + 1, &s, 10); + if (*s == ':') + sec = (long)zstrtol(s + 1, &s, 10); + else + sec = 0; + if (*s) { + zwarnnam("sched", "bad time specifier"); + return 1; + } + t = time(NULL) + (long)zl * 3600 + m * 60 + sec; + } else if (!*s) { + /* + * Alternatively, it may simply be a number of seconds. + * This is here for consistency with absolute times. + */ + t = time(NULL) + (time_t)zl; + } else { zwarnnam("sched", "bad time specifier"); return 1; } - m = zstrtol(s + 1, &s, 10); - if (*s) { - zwarnnam("sched", "bad time specifier"); - return 1; - } - t = time(NULL) + h * 3600 + m * 60; } else { - /* If there is no +, an absolute time of day must have been given. - This is in hour:minute format, optionally followed by a string starting - with `a' or `p' (for a.m. or p.m.). Characters after the `a' or `p' - are ignored. */ - h = zstrtol(s, &s, 10); - if (*s != ':') { - zwarnnam("sched", "bad time specifier"); - return 1; - } - m = zstrtol(s + 1, &s, 10); - if (*s && *s != 'a' && *s != 'A' && *s != 'p' && *s != 'P') { + /* + * If there is no +, an absolute time must have been given. + * This may be in hour:minute format, optionally followed by a string + * starting with `a' or `p' (for a.m. or p.m.). Characters after the + * `a' or `p' are ignored. + */ + zlong zl = zstrtol(s, &s, 10); + if (*s == ':') { + h = (long)zl; + m = (long)zstrtol(s + 1, &s, 10); + if (*s == ':') + sec = (long)zstrtol(s + 1, &s, 10); + else + sec = 0; + if (*s && *s != 'a' && *s != 'A' && *s != 'p' && *s != 'P') { + zwarnnam("sched", "bad time specifier"); + return 1; + } + t = time(NULL); + tm = localtime(&t); + t -= tm->tm_sec + tm->tm_min * 60 + tm->tm_hour * 3600; + if (*s == 'p' || *s == 'P') + h += 12; + t += h * 3600 + m * 60 + sec; + /* + * If the specified time is before the current time, it must refer + * to tomorrow. + */ + if (t < time(NULL)) + t += 3600 * 24; + } else if (!*s) { + /* + * Otherwise, it must be a raw time specifier. + */ + t = (long)zl; + } else { zwarnnam("sched", "bad time specifier"); return 1; } - t = time(NULL); - tm = localtime(&t); - t -= tm->tm_sec + tm->tm_min * 60 + tm->tm_hour * 3600; - if (*s == 'p' || *s == 'P') - h += 12; - t += h * 3600 + m * 60; - /* If the specified time is before the current time, it must refer to - tomorrow. */ - if (t < time(NULL)) - t += 3600 * 24; } /* The time has been calculated; now add the new entry to the linked list of scheduled commands. */ - sch = (struct schedcmd *) zshcalloc(sizeof *sch); + sch = (struct schedcmd *) zalloc(sizeof *sch); sch->time = t; - sch->cmd = zjoin(argv, ' ', 0); - sch->next = NULL; - for (sch2 = (struct schedcmd *)&schedcmds; sch2->next; sch2 = sch2->next); - sch2->next = sch; - return 0; -} - -/* Check scheduled commands; call this function from time to time. */ - -/**/ -static void -checksched(void) -{ - time_t t; - struct schedcmd *sch, *schl; - - if(!schedcmds) - return; - t = time(NULL); - for (schl = (struct schedcmd *)&schedcmds, sch = schedcmds; sch; - sch = (schl = sch)->next) { - if (sch->time <= t) { - execstring(sch->cmd, 0, 0); - schl->next = sch->next; - zsfree(sch->cmd); - zfree(sch, sizeof(struct schedcmd)); - sch = schl; + sch->cmd = zjoin(argptr, ' ', 0); + sch->flags = flags; + /* Insert into list in time order */ + if (schedcmds) { + if (sch->time < schedcmds->time) { + deltimedfn(checksched); + sch->next = schedcmds; + schedcmds = sch; + DPUTS(timedfns && firstnode(timedfns), "BUG: already timed fn (3)"); + addtimedfn(checksched, t); + } else { + for (sch2 = schedcmds; + sch2->next && sch2->next->time < sch->time; + sch2 = sch2->next) + ; + sch->next = sch2->next; + sch2->next = sch; } + } else { + sch->next = NULL; + schedcmds = sch; + DPUTS(timedfns && firstnode(timedfns), "BUG: already timed fn (4)"); + addtimedfn(checksched, t); } + return 0; } -static void (*p_checksched) _((void)) = checksched; -static struct linknode n_checksched = { NULL, NULL, &p_checksched }; - static struct builtin bintab[] = { BUILTIN("sched", 0, bin_sched, 0, -1, 0, NULL, NULL), }; @@ -194,7 +311,7 @@ boot_(Module m) { if(!addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab))) return 1; - uaddlinknode(prepromptfns, &n_checksched); + addprepromptfn(&checksched); return 0; } @@ -209,7 +326,7 @@ cleanup_(Module m) zsfree(sch->cmd); zfree(sch, sizeof(*sch)); } - uremnode(prepromptfns, &n_checksched); + delprepromptfn(&checksched); deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); return 0; } diff --git a/Src/Zle/zle_main.c b/Src/Zle/zle_main.c index 1d4636937..0e34a3fc3 100644 --- a/Src/Zle/zle_main.c +++ b/Src/Zle/zle_main.c @@ -122,7 +122,11 @@ int insmode; mod_export int eofchar; static int eofsent; -static long keytimeout; +/* + * Key timeout in hundredths of a second: we use time_t so + * that we only have the limits on one integer type to worry about. + */ +static time_t keytimeout; #if defined(HAVE_SELECT) || defined(HAVE_POLL) /* Terminal baud rate */ @@ -387,11 +391,110 @@ breakread(int fd, char *buf, int n) # define read breakread #endif +/* + * Possible forms of timeout. + */ +enum ztmouttp { + /* No timeout in use. */ + ZTM_NONE, + /* + * Key timeout in use (do_keytmout flag set). If this goes off + * we return without anything being read. + */ + ZTM_KEY, + /* + * Function timeout in use (from timedfns list). + * If this goes off we call any functions which have reached + * the time and then continue processing. + */ + ZTM_FUNC, + /* + * Timeout hit the maximum allowed; if it fires we + * need to recalculate. As we may use poll() for the timeout, + * which takes an int value in milliseconds, we might need this + * for times long in the future. (We make no attempt to extend + * the range of time beyond that of time_t, however; that seems + * like a losing battle.) + * + * For key timeouts we just limit the value to + * ZMAXTIMEOUT; that's already absurdly large. + * + * The following is the maximum signed range over 1024 (2^10), which + * is a little more convenient than 1000, but done differently + * to avoid problems with unsigned integers. We assume 8-bit bytes; + * there's no general way to fix up if that's wrong. + */ + ZTM_MAX +#define ZMAXTIMEOUT ((time_t)(1 << (sizeof(time_t)*8-11))) +}; + +struct ztmout { + /* Type of timeout setting, see enum above */ + enum ztmouttp tp; + /* + * Value for timeout in 100ths of a second if type is not ZTM_NONE. + */ + time_t exp100ths; +}; + +/* + * See if we need a timeout either for a key press or for a + * timed function. + */ + +static void +calc_timeout(struct ztmout *tmoutp, int do_keytmout) +{ + if (do_keytmout && keytimeout > 0) { + if (keytimeout > ZMAXTIMEOUT * 100 /* 24 days for a keypress???? */) + tmoutp->exp100ths = ZMAXTIMEOUT * 100; + else + tmoutp->exp100ths = keytimeout; + tmoutp->tp = ZTM_KEY; + } else + tmoutp->tp = ZTM_NONE; + + if (timedfns) { + for (;;) { + LinkNode tfnode = firstnode(timedfns); + Timedfn tfdat; + time_t diff, exp100ths; + + if (!tfnode) + break; + + tfdat = (Timedfn)getdata(tfnode); + diff = tfdat->when - time(NULL); + if (diff < 0) { + /* Already due; call it and rescan. */ + tfdat->func(); + continue; + } + + if (diff > ZMAXTIMEOUT) { + tmoutp->exp100ths = ZMAXTIMEOUT * 100; + tmoutp->tp = ZTM_MAX; + } else if (diff > 0) { + exp100ths = diff * 100; + if (tmoutp->tp != ZTM_KEY || + exp100ths < tmoutp->exp100ths) { + tmoutp->exp100ths = exp100ths; + tmoutp->tp = ZTM_FUNC; + } + } + break; + } + /* In case we called a function which messed up the display... */ + if (resetneeded) + zrefresh(); + } +} + static int -raw_getbyte(int keytmout, char *cptr) +raw_getbyte(int do_keytmout, char *cptr) { - long exp100ths; int ret; + struct ztmout tmout; #if defined(HAS_TIO) && \ (defined(sun) || (!defined(HAVE_POLL) && !defined(HAVE_SELECT))) struct ttyinfo ti; @@ -402,204 +505,254 @@ raw_getbyte(int keytmout, char *cptr) # endif #endif + calc_timeout(&tmout, do_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. + * Handle timeouts and watched fd's. If a watched fd or a function + * timeout triggers we restart any key timeout. This is likely to + * be harmless: the combination is extremely rare and a function + * is likely to occupy the user for a little while anyway. We used + * to make timeouts take precedence, but we can't now that the + * timeouts may be external, so we may have both a permanent watched + * fd and a long-term timeout. */ - if ((nwatch || keytmout) + if ((nwatch || tmout.tp != ZTM_NONE) #ifdef FIONREAD && ! delayzsetterm #endif ) { - if (!keytmout || keytimeout <= 0) - exp100ths = 0; - else if (keytimeout > 500) - exp100ths = 500; - else - exp100ths = keytimeout; #if defined(HAVE_SELECT) || defined(HAVE_POLL) - if (!keytmout || exp100ths) { - int i, errtry = 0, selret; + int i, errtry = 0, selret; # ifdef HAVE_POLL - int poll_timeout; - int nfds; - struct pollfd *fds; -# else - int fdmax; - struct timeval *tvptr; - struct timeval expire_tv; + int nfds; + struct pollfd *fds; # endif # if defined(HAS_TIO) && defined(sun) - /* - * Yes, I know this is complicated. Yes, I know we - * already have three bits of code to poll the terminal - * down below. No, I don't want to do this either. - * However, it turns out on certain OSes, specifically - * Solaris, that you can't poll typeahead for love nor - * money without actually trying to read it. But - * if we are trying to select (and we need to if we - * are watching other fd's) we won't pick that up. - * So we just try and read it without blocking in - * the time-honoured (i.e. absurdly baroque) termios - * fashion. - */ - gettyinfo(&ti); - ti.tio.c_cc[VMIN] = 0; - settyinfo(&ti); - ret = read(SHTTY, cptr, 1); - ti.tio.c_cc[VMIN] = 1; - settyinfo(&ti); - if (ret > 0) - return 1; + /* + * Yes, I know this is complicated. Yes, I know we + * already have three bits of code to poll the terminal + * down below. No, I don't want to do this either. + * However, it turns out on certain OSes, specifically + * Solaris, that you can't poll typeahead for love nor + * money without actually trying to read it. But + * if we are trying to select (and we need to if we + * are watching other fd's) we won't pick that up. + * So we just try and read it without blocking in + * the time-honoured (i.e. absurdly baroque) termios + * fashion. + */ + gettyinfo(&ti); + ti.tio.c_cc[VMIN] = 0; + settyinfo(&ti); + ret = read(SHTTY, cptr, 1); + ti.tio.c_cc[VMIN] = 1; + settyinfo(&ti); + if (ret > 0) + return 1; # endif # ifdef HAVE_POLL - nfds = keytmout ? 1 : 1 + nwatch; - /* First pollfd is SHTTY, following are the nwatch fds */ - fds = zalloc(sizeof(struct pollfd) * nfds); - if (exp100ths) - poll_timeout = exp100ths * 10; + nfds = 1 + nwatch; + /* First pollfd is SHTTY, following are the nwatch fds */ + fds = zalloc(sizeof(struct pollfd) * nfds); + fds[0].fd = SHTTY; + /* + * POLLIN, POLLIN, POLLIN, + * Keep those fd's POLLIN... + */ + fds[0].events = POLLIN; + for (i = 0; i < nwatch; i++) { + fds[i+1].fd = watch_fds[i]; + fds[i+1].events = POLLIN; + } +# endif + do { +# ifdef HAVE_POLL + int poll_timeout; + + if (tmout.tp != ZTM_NONE) + poll_timeout = tmout.exp100ths * 10; else poll_timeout = -1; - fds[0].fd = SHTTY; - /* - * POLLIN, POLLIN, POLLIN, - * Keep those fd's POLLIN... - */ - fds[0].events = POLLIN; - if (!keytmout) { + selret = poll(fds, errtry ? 1 : nfds, poll_timeout); +# else + int fdmax = SHTTY; + struct timeval *tvptr; + struct timeval expire_tv; + + FD_ZERO(&foofd); + FD_SET(SHTTY, &foofd); + if (!errtry) { for (i = 0; i < nwatch; i++) { - fds[i+1].fd = watch_fds[i]; - fds[i+1].events = POLLIN; + int fd = watch_fds[i]; + FD_SET(fd, &foofd); + if (fd > fdmax) + fdmax = fd; } } -# else - fdmax = SHTTY; - tvptr = NULL; - if (exp100ths) { - expire_tv.tv_sec = exp100ths / 100; - expire_tv.tv_usec = (exp100ths % 100) * 10000L; + + if (tmout.tp != ZTM_NONE) { + expire_tv.tv_sec = tmout.exp100ths / 100; + expire_tv.tv_usec = (tmout.exp100ths % 100) * 10000L; tvptr = &expire_tv; } + else + tvptr = NULL; + + selret = select(fdmax+1, (SELECT_ARG_2_T) & foofd, + NULL, NULL, tvptr); # endif - do { -# ifdef HAVE_POLL - selret = poll(fds, errtry ? 1 : nfds, poll_timeout); -# else - FD_ZERO(&foofd); - FD_SET(SHTTY, &foofd); - 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); -# endif - /* - * Make sure a user interrupt gets passed on straight away. - */ - if (selret < 0 && errflag) - break; + /* + * Make sure a user interrupt gets passed on straight away. + */ + if (selret < 0 && errflag) + break; + /* + * 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 && !errtry) { + errtry = 1; + continue; + } + if (selret == 0) { /* - * 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. + * Nothing ready and no error, so we timed out. */ - if (selret < 0 && !keytmout && !errtry) { - errtry = 1; - continue; - } - if (selret == 0) { + switch (tmout.tp) { + case ZTM_NONE: + /* keeps compiler happy if not debugging */ +#ifdef DEBUG + dputs("BUG: timeout fired with no timeout set."); +#endif + /* treat as if a key timeout triggered */ + /*FALLTHROUGH*/ + case ZTM_KEY: /* Special value -2 signals nothing ready */ selret = -2; - } - if (selret < 0) break; - if (!keytmout && nwatch) { + + case ZTM_FUNC: + while (firstnode(timedfns)) { + Timedfn tfdat = (Timedfn)getdata(firstnode(timedfns)); + /* + * It's possible a previous function took + * a long time to run (though it can't + * call zle recursively), so recalculate + * the time on each iteration. + */ + time_t now = time(NULL); + if (tfdat->when > now) + break; + tfdat->func(); + } + /* Function may have messed up the display */ + if (resetneeded) + zrefresh(); + /* We need to recalculate the timeout */ + /*FALLTHROUGH*/ + case ZTM_MAX: /* - * Copy the details of the watch fds in case the - * user decides to delete one from inside the - * handler function. + * Reached the limit of our range, but not the + * actual timeout; recalculate the timeout. + * We're cheating with the key timeout here: + * if one clashed with a function timeout we + * reconsider the key timeout from scratch. + * The effect of this is microscopic. */ - 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 ( + calc_timeout(&tmout, do_keytmout); + break; + } + /* + * If we handled the timeout successfully, + * carry on. + */ + if (selret == 0) + continue; + } + /* If error or unhandled timeout, give up. */ + if (selret < 0) + break; + if (nwatch && !errtry) { + /* + * 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 ( # ifdef HAVE_POLL - (fds[i+1].revents & POLLIN) + (fds[i+1].revents & POLLIN) # else - FD_ISSET(lwatch_fds[i], &foofd) + FD_ISSET(lwatch_fds[i], &foofd) # endif - ) { - /* 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)); - } + ) { + /* 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)); + } # ifdef HAVE_POLL # ifdef POLLERR - if (fds[i+1].revents & POLLERR) - zaddlinknode(funcargs, ztrdup("err")); + if (fds[i+1].revents & POLLERR) + zaddlinknode(funcargs, ztrdup("err")); # endif # ifdef POLLHUP - if (fds[i+1].revents & POLLHUP) - zaddlinknode(funcargs, ztrdup("hup")); + if (fds[i+1].revents & POLLHUP) + zaddlinknode(funcargs, ztrdup("hup")); # endif # ifdef POLLNVAL - if (fds[i+1].revents & POLLNVAL) - zaddlinknode(funcargs, ztrdup("nval")); + if (fds[i+1].revents & POLLNVAL) + zaddlinknode(funcargs, ztrdup("nval")); # endif # endif - 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); + 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 (! + /* Function may have invalidated the display. */ + if (resetneeded) + zrefresh(); + zfree(lwatch_fds, lnwatch*sizeof(int)); + freearray(lwatch_funcs); + } + } while (! # ifdef HAVE_POLL - (fds[0].revents & POLLIN) + (fds[0].revents & POLLIN) # else - FD_ISSET(SHTTY, &foofd) + FD_ISSET(SHTTY, &foofd) # endif - ); + ); # ifdef HAVE_POLL - zfree(fds, sizeof(struct pollfd) * nfds); + zfree(fds, sizeof(struct pollfd) * nfds); # endif - if (selret < 0) - return selret; - } + if (selret < 0) + return selret; #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.tio.c_cc[VTIME] = tmout.exp100ths / 10; # ifdef HAVE_TERMIOS_H tcsetattr(SHTTY, TCSANOW, &ti.tio); # else @@ -623,7 +776,7 @@ raw_getbyte(int keytmout, char *cptr) /**/ mod_export int -getbyte(int keytmout, int *timeout) +getbyte(int do_keytmout, int *timeout) { char cc; unsigned int ret; @@ -656,7 +809,7 @@ getbyte(int keytmout, int *timeout) for (;;) { int q = queue_signal_level(); dont_queue_signals(); - r = raw_getbyte(keytmout, &cc); + r = raw_getbyte(do_keytmout, &cc); restore_queue_signals(q); if (r == -2) { /* timeout */ @@ -733,9 +886,9 @@ getbyte(int keytmout, int *timeout) /**/ mod_export ZLE_INT_T -getfullchar(int keytmout) +getfullchar(int do_keytmout) { - int inchar = getbyte(keytmout, NULL); + int inchar = getbyte(do_keytmout, NULL); #ifdef MULTIBYTE_SUPPORT return getrestchar(inchar); @@ -938,7 +1091,7 @@ zleread(char **lp, char **rp, int flags, int context) return shingetline(); } - keytimeout = getiparam("KEYTIMEOUT"); + keytimeout = (time_t)getiparam("KEYTIMEOUT"); if (!shout) { if (SHTTY != -1) init_shout(); @@ -1551,7 +1704,7 @@ zle_resetprompt(void) mod_export void trashzle(void) { - if (zleactive) { + if (zleactive && !trashedzle) { /* This zrefresh() is just to get the main editor display right and * * get the cursor in the right place. For that reason, we disable * * list display (which would otherwise result in infinite * diff --git a/Src/Zle/zle_thingy.c b/Src/Zle/zle_thingy.c index 72d3314ff..debd31a28 100644 --- a/Src/Zle/zle_thingy.c +++ b/Src/Zle/zle_thingy.c @@ -721,8 +721,7 @@ bin_zle_invalidate(UNUSED(char *name), UNUSED(char **args), UNUSED(Options ops), * true if a completion widget is active. */ if (zleactive) { - if (!trashedzle) - trashzle(); + trashzle(); return 0; } else return 1; diff --git a/Src/init.c b/Src/init.c index 7447842ab..9d6a0514e 100644 --- a/Src/init.c +++ b/Src/init.c @@ -869,7 +869,6 @@ setupvals(void) nohistsave = 1; dirstack = znewlinklist(); bufstack = znewlinklist(); - prepromptfns = znewlinklist(); hsubl = hsubr = NULL; lastpid = 0; bshin = SHIN ? fdopen(SHIN, "r") : stdin; diff --git a/Src/subst.c b/Src/subst.c index 9f2703326..67afd0f03 100644 --- a/Src/subst.c +++ b/Src/subst.c @@ -70,9 +70,24 @@ prefork(LinkList list, int flags) return; } } else { - if (isset(SHFILEEXPANSION)) - filesub((char **)getaddrdata(node), - flags & (PF_TYPESET|PF_ASSIGN)); + if (isset(SHFILEEXPANSION)) { + /* + * Here and below we avoid taking the address + * of a void * and then pretending it's a char ** + * instead of a void ** by a little inefficiency. + * This could be avoided with some extra linked list + * machinery, but that would need quite a lot of work + * to ensure consistency. What we really need is + * templates... + */ + char *cptr = (char *)getdata(node); + filesub(&cptr, flags & (PF_TYPESET|PF_ASSIGN)); + /* + * The assignment is so simple it's not worth + * testing if cptr changed... + */ + setdata(node, cptr); + } if (!(node = stringsubst(list, node, flags & PF_SINGLE, asssub))) { unqueue_signals(); return; @@ -92,9 +107,11 @@ prefork(LinkList list, int flags) xpandbraces(list, &node); } } - if (unset(SHFILEEXPANSION)) - filesub((char **)getaddrdata(node), - flags & (PF_TYPESET|PF_ASSIGN)); + if (unset(SHFILEEXPANSION)) { + char *cptr = (char *)getdata(node); + filesub(&cptr, flags & (PF_TYPESET|PF_ASSIGN)); + setdata(node, cptr); + } } else if (!(flags & PF_SINGLE) && !keep) uremnode(list, node); if (errflag) { diff --git a/Src/utils.c b/Src/utils.c index 2bfae667c..3a5246bd1 100644 --- a/Src/utils.c +++ b/Src/utils.c @@ -911,10 +911,141 @@ dircmp(char *s, char *t) return 1; } -/* extra functions to call before displaying the prompt */ +/* + * Extra functions to call before displaying the prompt. + * The data is a Prepromptfn. + */ + +static LinkList prepromptfns; + +/* Add a function to the list of pre-prompt functions. */ /**/ -mod_export LinkList prepromptfns; +mod_export void +addprepromptfn(voidvoidfnptr_t func) +{ + Prepromptfn ppdat = (Prepromptfn)zalloc(sizeof(struct prepromptfn)); + ppdat->func = func; + if (!prepromptfns) + prepromptfns = znewlinklist(); + zaddlinknode(prepromptfns, ppdat); +} + +/* Remove a function from the list of pre-prompt functions. */ + +/**/ +mod_export void +delprepromptfn(voidvoidfnptr_t func) +{ + LinkNode ln; + + for (ln = firstnode(prepromptfns); ln; ln = nextnode(ln)) { + Prepromptfn ppdat = (Prepromptfn)getdata(ln); + if (ppdat->func == func) { + (void)remnode(prepromptfns, ln); + zfree(ppdat, sizeof(struct prepromptfn)); + return; + } + } +#ifdef DEBUG + dputs("BUG: failed to delete node from prepromptfns"); +#endif +} + +/* + * Functions to call at a particular time even if not at + * the prompt. This is handled by zle. The data is a + * Timedfn. The functions must be in time order, but this + * is enforced by addtimedfn(). + * + * Note on debugging: the code in sched.c currently assumes it's + * the only user of timedfns for the purposes of checking whether + * there's a function on the list. If this becomes no longer the case, + * the DPUTS() tests in sched.c need rewriting. + */ + +/**/ +mod_export LinkList timedfns; + +/* Add a function to the list of timed functions. */ + +/**/ +mod_export void +addtimedfn(voidvoidfnptr_t func, time_t when) +{ + Timedfn tfdat = (Timedfn)zalloc(sizeof(struct timedfn)); + tfdat->func = func; + tfdat->when = when; + + if (!timedfns) { + timedfns = znewlinklist(); + zaddlinknode(timedfns, tfdat); + } else { + LinkNode ln = firstnode(timedfns); + + /* + * Insert the new element in the linked list. We do + * rather too much work here since the standard + * functions insert after a given node, whereas we + * want to insert the new data before the first element + * with a greater time. + * + * In practice, the only use of timed functions is + * sched, which only adds the one function; so this + * whole branch isn't used beyond the following block. + */ + if (!ln) { + zaddlinknode(timedfns, tfdat); + return; + } + for (;;) { + Timedfn tfdat2; + LinkNode next = nextnode(ln); + if (!next) { + zaddlinknode(timedfns, tfdat); + return; + } + tfdat2 = (Timedfn)getdata(next); + if (when < tfdat2->when) { + zinsertlinknode(timedfns, ln, tfdat); + return; + } + ln = next; + } + } +} + +/* + * Delete a function from the list of timed functions. + * Note that if the function apperas multiple times only + * the first occurrence will be removed. + * + * Note also that when zle calls the function it does *not* + * automatically delete the entry from the list. That must + * be done by the function called. This is recommended as otherwise + * the function will keep being called immediately. (It just so + * happens this "feature" fits in well with the only current use + * of timed functions.) + */ + +/**/ +mod_export void +deltimedfn(voidvoidfnptr_t func) +{ + LinkNode ln; + + for (ln = firstnode(timedfns); ln; ln = nextnode(ln)) { + Timedfn ppdat = (Timedfn)getdata(ln); + if (ppdat->func == func) { + (void)remnode(timedfns, ln); + zfree(ppdat, sizeof(struct timedfn)); + return; + } + } +#ifdef DEBUG + dputs("BUG: failed to delete node from timedfns"); +#endif +} /* the last time we checked mail */ @@ -1027,10 +1158,12 @@ preprompt(void) lastmailcheck = time(NULL); } - /* Some people have claimed that C performs type * - * checking, but they were later found to be lying. */ - for(ln = firstnode(prepromptfns); ln; ln = nextnode(ln)) - (**(void (**) _((void)))getdata(ln))(); + if (prepromptfns) { + for(ln = firstnode(prepromptfns); ln; ln = nextnode(ln)) { + Prepromptfn ppnode = (Prepromptfn)getdata(ln); + ppnode->func(); + } + } } /**/ diff --git a/Src/zsh.h b/Src/zsh.h index 3c455b939..19d8a368f 100644 --- a/Src/zsh.h +++ b/Src/zsh.h @@ -333,40 +333,38 @@ enum { /* Abstract types for zsh */ /**************************/ -typedef struct linknode *LinkNode; -typedef union linkroot *LinkList; -typedef struct hashnode *HashNode; -typedef struct hashtable *HashTable; - -typedef struct optname *Optname; -typedef struct reswd *Reswd; typedef struct alias *Alias; -typedef struct param *Param; -typedef struct paramdef *Paramdef; +typedef struct asgment *Asgment; +typedef struct builtin *Builtin; typedef struct cmdnam *Cmdnam; -typedef struct shfunc *Shfunc; +typedef struct complist *Complist; +typedef struct conddef *Conddef; typedef struct funcstack *Funcstack; typedef struct funcwrap *FuncWrap; -typedef struct options *Options; -typedef struct builtin *Builtin; -typedef struct nameddir *Nameddir; -typedef struct module *Module; -typedef struct linkedmod *Linkedmod; - -typedef struct patprog *Patprog; -typedef struct process *Process; -typedef struct job *Job; -typedef struct value *Value; -typedef struct conddef *Conddef; -typedef struct redir *Redir; -typedef struct complist *Complist; +typedef struct hashnode *HashNode; +typedef struct hashtable *HashTable; typedef struct heap *Heap; typedef struct heapstack *Heapstack; typedef struct histent *Histent; typedef struct hookdef *Hookdef; - -typedef struct asgment *Asgment; - +typedef struct job *Job; +typedef struct linkedmod *Linkedmod; +typedef struct linknode *LinkNode; +typedef union linkroot *LinkList; +typedef struct module *Module; +typedef struct nameddir *Nameddir; +typedef struct options *Options; +typedef struct optname *Optname; +typedef struct param *Param; +typedef struct paramdef *Paramdef; +typedef struct patprog *Patprog; +typedef struct prepromptfn *Prepromptfn; +typedef struct process *Process; +typedef struct redir *Redir; +typedef struct reswd *Reswd; +typedef struct shfunc *Shfunc; +typedef struct timedfn *Timedfn; +typedef struct value *Value; /********************************/ /* Definitions for linked lists */ @@ -432,6 +430,28 @@ union linkroot { __n0.dat = (void *) (V0); \ } while (0) +/*************************************/ +/* Specific elements of linked lists */ +/*************************************/ + +typedef void (*voidvoidfnptr_t) _((void)); + +/* + * Element of the prepromptfns list. + */ +struct prepromptfn { + voidvoidfnptr_t func; +}; + + +/* + * Element of the timedfns list. + */ +struct timedfn { + voidvoidfnptr_t func; + time_t when; +}; + /********************************/ /* Definitions for syntax trees */ /********************************/ -- cgit 1.4.1