diff options
Diffstat (limited to 'Src')
56 files changed, 2601 insertions, 1228 deletions
diff --git a/Src/Modules/curses.c b/Src/Modules/curses.c index 63c6748f5..a60dfcbf8 100644 --- a/Src/Modules/curses.c +++ b/Src/Modules/curses.c @@ -1082,15 +1082,7 @@ zccmd_input(const char *nam, char **args) #endif /* - * Some documentation for wgetch() says: - - The behavior of getch and friends in the presence of handled signals - is unspecified in the SVr4 and XSI Curses documentation. Under his- - torical curses implementations, it varied depending on whether the - operating system's implementation of handled signal receipt interrupts - a read(2) call in progress or not, and also (in some implementations) - depending on whether an input timeout or non-blocking mode has been - set. + * Linux, OS X, FreeBSD documentation for wgetch() mentions: Programmers concerned about portability should be prepared for either of two cases: (a) signal receipt does not interrupt getch; (b) signal @@ -1098,21 +1090,16 @@ zccmd_input(const char *nam, char **args) EINTR. Under the ncurses implementation, handled signals never inter- rupt getch. - * The observed behavior, however, is different: wgetch() consistently - * returns ERR with EINTR when a signal is handled by the shell "trap" - * command mechanism. Further, it consistently returns ERR twice, the - * second time without even attempting to repeat the interrupted read, - * which has the side-effect of NOT updating errno. A third call will - * then begin reading again. - * - * Therefore, to properly implement signal trapping, we must (1) call - * wgetch() in a loop as long as errno remains EINTR, and (2) clear - * errno only before beginning the loop, not on every pass. + * Some observed behavior: wgetch() returns ERR with EINTR when a signal is + * handled by the shell "trap" command mechanism. Observed that it returns + * ERR twice, the second time without even attempting to repeat the + * interrupted read. Third call will then begin reading again. * - * There remains a potential bug here in that, if the caller has set - * a timeout for the read [see zccmd_timeout()] the countdown is very - * likely restarted on every call to wgetch(), so an interrupted call - * might wait much longer than desired. + * Because of widespread of previous implementation that called wget*ch + * possibly indefinitely many times after ERR/EINTR, and because of the + * above observation, wget_wch call is repeated after each ERR/EINTR, but + * errno is being reset (it wasn't) and the loop to all means should break. + * Problem: the timeout may be waited twice. */ errno = 0; @@ -1120,6 +1107,7 @@ zccmd_input(const char *nam, char **args) while ((ret = wget_wch(w->win, &wi)) == ERR) { if (errno != EINTR || errflag || retflag || breaks || exit_pending) break; + errno = 0; } switch (ret) { case OK: @@ -1146,6 +1134,7 @@ zccmd_input(const char *nam, char **args) while ((ci = wgetch(w->win)) == ERR) { if (errno != EINTR || errflag || retflag || breaks || exit_pending) return 1; + errno = 0; } if (ci >= 256) { keypadnum = ci; @@ -1501,6 +1490,74 @@ zccmd_touch(const char *nam, char **args) return ret; } +static int +zccmd_resize(const char *nam, char **args) +{ +#ifdef HAVE_RESIZE_TERM + int y, x, do_endwin=0, do_save=1; + LinkNode stdscr_win = zcurses_getwindowbyname("stdscr"); + + if (stdscr_win) { + y = atoi(args[0]); + x = atoi(args[1]); + if (args[2]) { + if (0 == strcmp(args[2], "endwin")) { + do_endwin=1; + } else if (0 == strcmp(args[2], "endwin_nosave")) { + do_endwin=1; + do_save=0; + } else if (0 == strcmp(args[2], "nosave")) { + do_save=0; + } else { + zwarnnam(nam, "`resize' expects `endwin', `nosave' or `endwin_nosave' for third argument, if given"); + } + } + + if (y == 0 && x == 0 && args[2] == NULL) { + // Special case to just test that curses has resize_term. #ifdef + // HAVE_RESIZE_TERM will result in return value 2 if resize_term + // is not available. + return 0; + } else { + // Without this call some window moves are innacurate. Tested on + // OS X ncurses 5.4, Homebrew ncursesw 6.0-2, Arch Linux ncursesw + // 6.0, Ubuntu 14.04 ncurses 5.9, FreeBSD ncursesw.so.8 + // + // On the other hand, the whole resize goal can be (from tests) + // accomplished by calling endwin and refresh. But to secure any + // future problems, resize_term is provided, and it is featured + // with endwin, so that users have multiple options. + if (do_endwin) { + endwin(); + } + + if( resize_term( y, x ) == OK ) { + // Things work without this, but we need to get out from + // endwin (i.e. call refresh), and in theory store new + // curses state (the resize might have changed it), which + // should be presented to terminal only after refresh. + if (do_endwin || do_save) { + ZCWin w; + w = (ZCWin)getdata(stdscr_win); + wnoutrefresh(w->win); + doupdate(); + } + + if (do_save) { + gettyinfo(&curses_tty_state); + } + return 0; + } else { + return 1; + } + } + } else { + return 1; + } +#else + return 2; +#endif +} /********************* Main builtin handler @@ -1534,6 +1591,7 @@ bin_zcurses(char *nam, char **args, UNUSED(Options ops), UNUSED(int func)) {"mouse", zccmd_mouse, 0, -1}, {"querychar", zccmd_querychar, 1, 2}, {"touch", zccmd_touch, 1, -1}, + {"resize", zccmd_resize, 2, 3}, {NULL, (zccmd_t)0, 0, 0} }; diff --git a/Src/Modules/datetime.c b/Src/Modules/datetime.c index bb82c542f..6e9047bc5 100644 --- a/Src/Modules/datetime.c +++ b/Src/Modules/datetime.c @@ -133,11 +133,15 @@ output_strftime(char *nam, char **argv, Options ops, UNUSED(int func)) len = 0; for (x=0; x < 4; x++) { - if ((len = ztrftime(buffer, bufsize, argv[0], t, 0L)) >= 0) + if ((len = ztrftime(buffer, bufsize, argv[0], t, 0L)) >= 0 || x==3) break; buffer = zrealloc(buffer, bufsize *= 2); } - DPUTS(len < 0, "bad output from ztrftime"); + if (len < 0) { + zwarnnam(nam, "bad/unsupported format: '%s'", argv[0]); + zfree(buffer, bufsize); + return 1; + } if (scalar) { setsparam(scalar, metafy(buffer, len, META_DUP)); diff --git a/Src/Modules/db_gdbm.c b/Src/Modules/db_gdbm.c index 8dd60fc0d..cf1322459 100644 --- a/Src/Modules/db_gdbm.c +++ b/Src/Modules/db_gdbm.c @@ -6,6 +6,9 @@ * Copyright (c) 2008 Clint Adams * All rights reserved. * + * Modifications copyright (c) 2017 Sebastian Gniazdowski + * All rights reserved. + * * Permission is hereby granted, without written agreement and without * license or royalty fees, to use, copy, modify, and distribute this * software and to distribute modified versions of this software for any @@ -31,6 +34,18 @@ #include "db_gdbm.mdh" #include "db_gdbm.pro" +#ifndef PM_UPTODATE +#define PM_UPTODATE (1<<19) /* Parameter has up-to-date data (e.g. loaded from DB) */ +#endif + +static Param createhash( char *name, int flags ); +static int append_tied_name( const char *name ); +static int remove_tied_name( const char *name ); +static char *unmetafy_zalloc(const char *to_copy, int *new_len); +static void myfreeparamnode(HashNode hn); + +static int no_database_action = 0; + /* * Make sure we have all the bits I'm using for memory mapping, otherwise * I don't know what I'm doing. @@ -41,8 +56,34 @@ static char *backtype = "db/gdbm"; -static const struct gsu_scalar gdbm_gsu = -{ gdbmgetfn, gdbmsetfn, gdbmunsetfn }; +/* + * Longer GSU structure, to carry GDBM_FILE of owning + * database. Every parameter (hash value) receives GSU + * pointer and thus also receives GDBM_FILE - this way + * parameters can access proper database. + * + * Main HashTable parameter has the same instance of + * the custom GSU struct in u.hash->tmpdata field. + * When database is closed, `dbf` field is set to NULL + * and hash values know to not access database when + * being unset (total purge at zuntie). + * + * When database closing is ended, custom GSU struct + * is freed. Only new ztie creates new custom GSU + * struct instance. + */ + +struct gsu_scalar_ext { + struct gsu_scalar std; /* Size of three pointers */ + GDBM_FILE dbf; + char *dbfile_path; +}; + +/* Source structure - will be copied to allocated one, + * with `dbf` filled. `dbf` allocation <-> gsu allocation. */ +static const struct gsu_scalar_ext gdbm_gsu_ext = +{ { gdbmgetfn, gdbmsetfn, gdbmunsetfn }, 0, 0 }; + /**/ static const struct gsu_hash gdbm_hash_gsu = { hashgetfn, gdbmhashsetfn, gdbmhashunsetfn }; @@ -50,12 +91,24 @@ static const struct gsu_hash gdbm_hash_gsu = static struct builtin bintab[] = { BUILTIN("ztie", 0, bin_ztie, 1, -1, 0, "d:f:r", NULL), BUILTIN("zuntie", 0, bin_zuntie, 1, -1, 0, "u", NULL), + BUILTIN("zgdbmpath", 0, bin_zgdbmpath, 1, -1, 0, "", NULL), +}; + +#define ROARRPARAMDEF(name, var) \ + { name, PM_ARRAY | PM_READONLY, (void *) var, NULL, NULL, NULL, NULL } + +/* Holds names of all tied parameters */ +char **zgdbm_tied; + +static struct paramdef patab[] = { + ROARRPARAMDEF( "zgdbm_tied", &zgdbm_tied ), }; /**/ static int bin_ztie(char *nam, char **args, Options ops, UNUSED(int func)) { + struct gsu_scalar_ext *dbf_carrier; char *resource_name, *pmname; GDBM_FILE dbf = NULL; int read_write = GDBM_SYNC, pmflags = PM_REMOVABLE; @@ -77,8 +130,7 @@ bin_ztie(char *nam, char **args, Options ops, UNUSED(int func)) } /* Here should be a lookup of the backend type against - * a registry. - */ + * a registry, if generam DB mechanism is to be added */ if (strcmp(OPT_ARG(ops, 'd'), backtype) != 0) { zwarnnam(nam, "unsupported backend type `%s'", OPT_ARG(ops, 'd')); return 1; @@ -92,7 +144,8 @@ bin_ztie(char *nam, char **args, Options ops, UNUSED(int func)) /* * Unset any existing parameter. Note there's no implicit * "local" here, but if the existing parameter is local - * that will be reflected in the new one. + * then new parameter will be also local without following + * unset. * * We need to do this before attempting to open the DB * in case this variable is already tied to a DB. @@ -105,24 +158,40 @@ bin_ztie(char *nam, char **args, Options ops, UNUSED(int func)) return 1; } + gdbm_errno=0; dbf = gdbm_open(resource_name, 0, read_write, 0666, 0); - if(dbf) - addmodulefd(gdbm_fdesc(dbf), FDT_INTERNAL); - else { - zwarnnam(nam, "error opening database file %s", resource_name); + if(dbf == NULL) { + zwarnnam(nam, "error opening database file %s (%s)", resource_name, gdbm_strerror(gdbm_errno)); return 1; } - if (!(tied_param = createspecialhash(pmname, &getgdbmnode, &scangdbmkeys, - pmflags))) { + if (!(tied_param = createhash(pmname, pmflags))) { zwarnnam(nam, "cannot create the requested parameter %s", pmname); - fdtable[gdbm_fdesc(dbf)] = FDT_UNUSED; gdbm_close(dbf); return 1; } tied_param->gsu.h = &gdbm_hash_gsu; - tied_param->u.hash->tmpdata = (void *)dbf; + + /* Allocate parameter sub-gsu, fill dbf field. + * dbf allocation is 1 to 1 accompanied by + * gsu_scalar_ext allocation. */ + + dbf_carrier = (struct gsu_scalar_ext *) zalloc(sizeof(struct gsu_scalar_ext)); + dbf_carrier->std = gdbm_gsu_ext.std; + dbf_carrier->dbf = dbf; + tied_param->u.hash->tmpdata = (void *)dbf_carrier; + + /* Fill also file path field */ + if (*resource_name != '/') { + /* Code copied from check_autoload() */ + resource_name = zhtricat(metafy(zgetcwd(), -1, META_HEAPDUP), "/", resource_name); + resource_name = xsymlink(resource_name, 1); + } + dbf_carrier->dbfile_path = ztrdup(resource_name); + + addmodulefd(gdbm_fdesc(dbf), FDT_INTERNAL); + append_tied_name(pmname); return 0; } @@ -149,8 +218,9 @@ bin_zuntie(char *nam, char **args, Options ops, UNUSED(int func)) } queue_signals(); - if (OPT_ISSET(ops,'u')) - gdbmuntie(pm); /* clear read-only-ness */ + if (OPT_ISSET(ops,'u')) { + pm->node.flags &= ~PM_READONLY; + } if (unsetparam_pm(pm, 0, 1)) { /* assume already reported */ ret = 1; @@ -162,25 +232,112 @@ bin_zuntie(char *nam, char **args, Options ops, UNUSED(int func)) } /**/ +static int +bin_zgdbmpath(char *nam, char **args, Options ops, UNUSED(int func)) +{ + Param pm; + char *pmname; + + pmname = *args; + + if (!pmname) { + zwarnnam(nam, "parameter name (whose path is to be written to $REPLY) is required"); + return 1; + } + + pm = (Param) paramtab->getnode(paramtab, pmname); + if(!pm) { + zwarnnam(nam, "no such parameter: %s", pmname); + return 1; + } + + if (pm->gsu.h != &gdbm_hash_gsu) { + zwarnnam(nam, "not a tied gdbm parameter: %s", pmname); + return 1; + } + + /* Paranoia, it *will* be always set */ + if (((struct gsu_scalar_ext *)pm->u.hash->tmpdata)->dbfile_path) { + setsparam("REPLY", ztrdup(((struct gsu_scalar_ext *)pm->u.hash->tmpdata)->dbfile_path)); + } else { + setsparam("REPLY", ztrdup("")); + } + + return 0; +} + +/* + * The param is actual param in hash – always, because + * getgdbmnode creates every new key seen. However, it + * might be not PM_UPTODATE - which means that database + * wasn't yet queried. + * + * It will be left in this state if database doesn't + * contain such key. That might be a drawback, maybe + * setting to empty value has sense. + */ + +/**/ static char * gdbmgetfn(Param pm) { datum key, content; - int ret; + int ret, umlen; + char *umkey; GDBM_FILE dbf; - key.dptr = pm->node.nam; - key.dsize = strlen(key.dptr) + 1; + /* Key already retrieved? There is no sense of asking the + * database again, because: + * - there can be only multiple readers + * - so, no writer + reader use is allowed + * + * Thus: + * - if we are writers, we for sure have newest copy of data + * - if we are readers, we for sure have newest copy of data + */ + if ( pm->node.flags & PM_UPTODATE ) { + return pm->u.str ? pm->u.str : ""; + } + + /* Unmetafy key. GDBM fits nice into this + * process, as it uses length of data */ + umlen = 0; + umkey = unmetafy_zalloc(pm->node.nam,¨en); + + key.dptr = umkey; + key.dsize = umlen; + + dbf = ((struct gsu_scalar_ext *)pm->gsu.s)->dbf; + + if((ret = gdbm_exists(dbf, key))) { + /* We have data – store it, return it */ + pm->node.flags |= PM_UPTODATE; - dbf = (GDBM_FILE)(pm->u.hash->tmpdata); - ret = gdbm_exists(dbf, key); - if(ret) { content = gdbm_fetch(dbf, key); - } else { - content.dptr = dupstring(""); + + /* Ensure there's no leak */ + if (pm->u.str) { + zsfree(pm->u.str); + pm->u.str = NULL; + } + + /* Metafy returned data. All fits - metafy + * can obtain data length to avoid using \0 */ + pm->u.str = metafy(content.dptr, content.dsize, META_DUP); + /* gdbm allocates with malloc */ + free(content.dptr); + + /* Free key */ + zfree(umkey, umlen+1); + + /* Can return pointer, correctly saved inside hash */ + return pm->u.str; } - return content.dptr; + /* Free key */ + zfree(umkey, umlen+1); + + return ""; } /**/ @@ -190,78 +347,128 @@ gdbmsetfn(Param pm, char *val) datum key, content; GDBM_FILE dbf; - key.dptr = pm->node.nam; - key.dsize = strlen(key.dptr) + 1; - content.dptr = val; - content.dsize = strlen(content.dptr) + 1; + /* Set is done on parameter and on database. + * See the allowed workers / readers comment + * at gdbmgetfn() */ - dbf = (GDBM_FILE)(pm->u.hash->tmpdata); - (void)gdbm_store(dbf, key, content, GDBM_REPLACE); + /* Parameter */ + if (pm->u.str) { + zsfree(pm->u.str); + pm->u.str = NULL; + pm->node.flags &= ~(PM_UPTODATE); + } + + if (val) { + pm->u.str = ztrdup(val); + pm->node.flags |= PM_UPTODATE; + } + + /* Database */ + dbf = ((struct gsu_scalar_ext *)pm->gsu.s)->dbf; + if (dbf && no_database_action == 0) { + int umlen = 0; + char *umkey = unmetafy_zalloc(pm->node.nam,¨en); + + key.dptr = umkey; + key.dsize = umlen; + + if (val) { + /* Unmetafy with exact zalloc size */ + char *umval = unmetafy_zalloc(val,¨en); + + /* Store */ + content.dptr = umval; + content.dsize = umlen; + (void)gdbm_store(dbf, key, content, GDBM_REPLACE); + + /* Free */ + zfree(umval, umlen+1); + } else { + (void)gdbm_delete(dbf, key); + } + + /* Free key */ + zfree(umkey, key.dsize+1); + } } /**/ static void gdbmunsetfn(Param pm, UNUSED(int um)) { - datum key; - GDBM_FILE dbf; - - key.dptr = pm->node.nam; - key.dsize = strlen(key.dptr) + 1; - - dbf = (GDBM_FILE)(pm->u.hash->tmpdata); - (void)gdbm_delete(dbf, key); + /* Set with NULL */ + gdbmsetfn(pm, NULL); } /**/ static HashNode getgdbmnode(HashTable ht, const char *name) { - int len; - char *nameu; - Param pm = NULL; - - nameu = dupstring(name); - unmetafy(nameu, &len); - - pm = (Param) hcalloc(sizeof(struct param)); - pm->node.nam = nameu; - pm->node.flags = PM_SCALAR; - pm->gsu.s = &gdbm_gsu; - pm->u.hash = ht; + HashNode hn = gethashnode2( ht, name ); + Param val_pm = (Param) hn; + + /* Entry for key doesn't exist? Create it now, + * it will be interfacing between the database + * and Zsh - through special gdbm_gsu. So, any + * seen key results in new interfacing parameter. + * + * Previous code was returning heap arena Param + * that wasn't actually added to the hash. It was + * plainly name / database-key holder. Here we + * add the Param to its hash, it is not PM_UPTODATE. + * It will be loaded from database *and filled* + * or left in that state if the database doesn't + * contain it. + * + * No heap arena memory is used, memory usage is + * now limited - by number of distinct keys seen, + * not by number of key *uses*. + * */ + + if ( ! val_pm ) { + val_pm = (Param) zshcalloc( sizeof (*val_pm) ); + val_pm->node.flags = PM_SCALAR | PM_HASHELEM; /* no PM_UPTODATE */ + val_pm->gsu.s = (GsuScalar) ht->tmpdata; + ht->addnode( ht, ztrdup( name ), val_pm ); /* sets pm->node.nam */ + } - return &pm->node; + return (HashNode) val_pm; } /**/ static void scangdbmkeys(HashTable ht, ScanFunc func, int flags) { - Param pm = NULL; - datum key, content; - GDBM_FILE dbf = (GDBM_FILE)(ht->tmpdata); - - pm = (Param) hcalloc(sizeof(struct param)); - - pm->node.flags = PM_SCALAR; - pm->gsu.s = &nullsetscalar_gsu; + datum key, prev_key; + GDBM_FILE dbf = ((struct gsu_scalar_ext *)ht->tmpdata)->dbf; + /* Iterate keys adding them to hash, so + * we have Param to use in `func` */ key = gdbm_firstkey(dbf); while(key.dptr) { - content = gdbm_fetch(dbf, key); - - pm->node.nam = key.dptr; - pm->u.str = content.dptr; - pm->gsu.s = &nullsetscalar_gsu; - - func(&pm->node, flags); - + /* This returns database-interfacing Param, + * it will return u.str or first fetch data + * if not PM_UPTODATE (newly created) */ + char *zkey = metafy(key.dptr, key.dsize, META_DUP); + HashNode hn = getgdbmnode(ht, zkey); + zsfree( zkey ); + + func(hn, flags); + + /* Iterate - no problem as interfacing Param + * will do at most only fetches, not stores */ + prev_key = key; key = gdbm_nextkey(dbf, key); + free(prev_key.dptr); } } +/* + * Replace database with new hash + */ + /**/ static void gdbmhashsetfn(Param pm, HashTable ht) @@ -274,7 +481,7 @@ gdbmhashsetfn(Param pm, HashTable ht) if (!pm->u.hash || pm->u.hash == ht) return; - if (!(dbf = (GDBM_FILE)(pm->u.hash->tmpdata))) + if (!(dbf = ((struct gsu_scalar_ext *)pm->u.hash->tmpdata)->dbf)) return; key = gdbm_firstkey(dbf); @@ -286,48 +493,78 @@ gdbmhashsetfn(Param pm, HashTable ht) key = gdbm_firstkey(dbf); } - /* just deleted everything, clean up */ - (void)gdbm_reorganize(dbf); + /* Just deleted everything, clean up if no new data. + * User can also reorganize via gdbmtool. */ + if (!ht || ht->hsize == 0) { + (void)gdbm_reorganize(dbf); + } + + no_database_action = 1; + emptyhashtable(pm->u.hash); + no_database_action = 0; if (!ht) return; - for (i = 0; i < ht->hsize; i++) + /* Put new strings into database, waiting + * for their interfacing-Params to be created */ + + for (i = 0; i < ht->hsize; i++) { for (hn = ht->nodes[i]; hn; hn = hn->next) { struct value v; + int umlen = 0; + char *umkey, *umval; v.isarr = v.flags = v.start = 0; v.end = -1; v.arr = NULL; v.pm = (Param) hn; - key.dptr = v.pm->node.nam; - key.dsize = strlen(key.dptr) + 1; + /* Unmetafy key */ + umkey = unmetafy_zalloc(v.pm->node.nam,¨en); + + key.dptr = umkey; + key.dsize = umlen; queue_signals(); - content.dptr = getstrvalue(&v); - content.dsize = strlen(content.dptr) + 1; + /* Unmetafy */ + umval = unmetafy_zalloc(getstrvalue(&v),¨en); + /* Store */ + content.dptr = umval; + content.dsize = umlen; (void)gdbm_store(dbf, key, content, GDBM_REPLACE); + /* Free - unmetafy_zalloc allocates + * exact required space + 1 null byte */ + zfree(umval, content.dsize+1); + zfree(umkey, key.dsize+1); + unqueue_signals(); } + } + /* We reuse our hash, the input is to be deleted */ + deleteparamtable(ht); } /**/ static void gdbmuntie(Param pm) { - GDBM_FILE dbf = (GDBM_FILE)(pm->u.hash->tmpdata); + GDBM_FILE dbf = ((struct gsu_scalar_ext *)pm->u.hash->tmpdata)->dbf; HashTable ht = pm->u.hash; if (dbf) { /* paranoia */ fdtable[gdbm_fdesc(dbf)] = FDT_UNUSED; - gdbm_close(dbf); - } + gdbm_close(dbf); + + /* Let hash fields know there's no backend */ + ((struct gsu_scalar_ext *)ht->tmpdata)->dbf = NULL; - ht->tmpdata = NULL; + /* Remove from list of tied parameters */ + remove_tied_name(pm->node.nam); + } /* for completeness ... createspecialhash() should have an inverse */ ht->getnode = ht->getnode2 = gethashnode2; @@ -341,21 +578,31 @@ gdbmuntie(Param pm) static void gdbmhashunsetfn(Param pm, UNUSED(int exp)) { + struct gsu_scalar_ext * gsu_ext; + gdbmuntie(pm); - /* hash table is now normal, so proceed normally... */ + + /* Remember custom GSU structure assigned to + * u.hash->tmpdata before hash gets deleted */ + gsu_ext = pm->u.hash->tmpdata; + + /* Uses normal unsetter (because gdbmuntie is called above). + * Will delete all owned field-parameters and also hashtable. */ pm->gsu.h->setfn(pm, NULL); + + /* Don't need custom GSU structure with its + * GDBM_FILE pointer anymore */ + zsfree( gsu_ext->dbfile_path ); + zfree( gsu_ext, sizeof(struct gsu_scalar_ext)); + pm->node.flags |= PM_UNSET; } -#else -# error no gdbm -#endif /* have gdbm */ - static struct features module_features = { bintab, sizeof(bintab)/sizeof(*bintab), NULL, 0, NULL, 0, - NULL, 0, + patab, sizeof(patab)/sizeof(*patab), 0 }; @@ -385,6 +632,7 @@ enables_(Module m, int **enables) int boot_(UNUSED(Module m)) { + zgdbm_tied = zshcalloc((1) * sizeof(char *)); return 0; } @@ -392,6 +640,7 @@ boot_(UNUSED(Module m)) int cleanup_(Module m) { + /* This frees `zgdbm_tied` */ return setfeatureenables(m, &module_features, NULL); } @@ -401,3 +650,169 @@ finish_(UNUSED(Module m)) { return 0; } + +/********************* + * Utility functions * + *********************/ + +static Param createhash( char *name, int flags ) { + Param pm; + HashTable ht; + + pm = createparam(name, flags | PM_SPECIAL | PM_HASHED); + if (!pm) { + return NULL; + } + + if (pm->old) + pm->level = locallevel; + + /* This creates standard hash. */ + ht = pm->u.hash = newparamtable(17, name); + if (!pm->u.hash) { + paramtab->removenode(paramtab, name); + paramtab->freenode(&pm->node); + zwarnnam(name, "out of memory when allocating hash"); + return NULL; + } + + /* Does free Param (unsetfn is called) */ + ht->freenode = myfreeparamnode; + + /* These provide special features */ + ht->getnode = ht->getnode2 = getgdbmnode; + ht->scantab = scangdbmkeys; + + return pm; +} + +/* + * Adds parameter name to `zgdbm_tied` + */ + +static int append_tied_name( const char *name ) { + int old_len = arrlen(zgdbm_tied); + char **new_zgdbm_tied = zshcalloc( (old_len+2) * sizeof(char *)); + + /* Copy */ + char **p = zgdbm_tied; + char **dst = new_zgdbm_tied; + while (*p) { + *dst++ = *p++; + } + + /* Append new one */ + *dst = ztrdup(name); + + /* Substitute, free old one */ + zfree(zgdbm_tied, sizeof(char *) * (old_len + 1)); + zgdbm_tied = new_zgdbm_tied; + + return 0; +} + +/* + * Removes parameter name from `zgdbm_tied` + */ + +static int remove_tied_name( const char *name ) { + int old_len = arrlen(zgdbm_tied); + int new_len; + + /* Two stage, to always have arrlen() == zfree-size - 1. + * Could do allocation and revert when `not found`, but + * what would be better about that. */ + + /* Find one to remove */ + char **p = zgdbm_tied; + while (*p) { + if (0==strcmp(name,*p)) { + break; + } + p++; + } + + /* Copy x+1 to x */ + while (*p) { + *p=*(p+1); + p++; + } + + /* Second stage. Size changed? Only old_size-1 + * change is possible, but.. paranoia way */ + new_len = arrlen(zgdbm_tied); + if (new_len != old_len) { + char **dst; + char **new_zgdbm_tied = zshcalloc((new_len+1) * sizeof(char *)); + + /* Copy */ + p = zgdbm_tied; + dst = new_zgdbm_tied; + while (*p) { + *dst++ = *p++; + } + *dst = NULL; + + /* Substitute, free old one */ + zfree(zgdbm_tied, sizeof(char *) * (old_len + 1)); + zgdbm_tied = new_zgdbm_tied; + } + + return 0; +} + +/* + * Unmetafy that: + * - duplicates bufer to work on it, + * - does zalloc of exact size for the new string, + * - restores work buffer to original content, to restore strlen + */ +static char * +unmetafy_zalloc(const char *to_copy, int *new_len) { + char *work, *to_return; + int my_new_len = 0; + + work = ztrdup(to_copy); + work = unmetafy(work,&my_new_len); + + if (new_len) + *new_len = my_new_len; + + /* This string can be correctly zsfree()-d */ + to_return = (char *) zalloc((my_new_len+1)*sizeof(char)); + memcpy(to_return, work, sizeof(char)*my_new_len); /* memcpy handles $'\0' */ + to_return[my_new_len]='\0'; + + /* Restore original strlen and correctly free */ + strcpy(work, to_copy); + zsfree(work); + + return to_return; +} + +static void +myfreeparamnode(HashNode hn) +{ + Param pm = (Param) hn; + + /* Upstream: The second argument of unsetfn() is used by modules to + * differentiate "exp"licit unset from implicit unset, as when + * a parameter is going out of scope. It's not clear which + * of these applies here, but passing 1 has always worked. + */ + + /* if (delunset) */ + pm->gsu.s->unsetfn(pm, 1); + + zsfree(pm->node.nam); + /* If this variable was tied by the user, ename was ztrdup'd */ + if (pm->node.flags & PM_TIED && pm->ename) { + zsfree(pm->ename); + pm->ename = NULL; + } + zfree(pm, sizeof(struct param)); +} + +#else +# error no gdbm +#endif /* have gdbm */ diff --git a/Src/Modules/db_gdbm.mdd b/Src/Modules/db_gdbm.mdd index ce7926bd9..210c22177 100644 --- a/Src/Modules/db_gdbm.mdd +++ b/Src/Modules/db_gdbm.mdd @@ -7,6 +7,6 @@ fi ' load=no -autofeatures="b:ztie b:zuntie" +autofeatures="b:ztie b:zuntie b:zgdbmpath p:zgdbm_tied" objects="db_gdbm.o" diff --git a/Src/Modules/example.c b/Src/Modules/example.c index 45ca2cffa..c80c9e7b2 100644 --- a/Src/Modules/example.c +++ b/Src/Modules/example.c @@ -69,7 +69,8 @@ bin_example(char *nam, char **args, Options ops, UNUSED(int func)) intparam = i; zsfree(strparam); strparam = ztrdup(*oargs ? *oargs : ""); - freearray(arrparam); + if (arrparam) + freearray(arrparam); arrparam = zarrdup(oargs); return 0; } diff --git a/Src/Modules/parameter.c b/Src/Modules/parameter.c index 98bcaba6e..10c47d214 100644 --- a/Src/Modules/parameter.c +++ b/Src/Modules/parameter.c @@ -167,7 +167,7 @@ unsetpmcommand(Param pm, UNUSED(int exp)) /**/ static void -setpmcommands(UNUSED(Param pm), HashTable ht) +setpmcommands(Param pm, HashTable ht) { int i; HashNode hn; @@ -190,7 +190,15 @@ setpmcommands(UNUSED(Param pm), HashTable ht) cmdnamtab->addnode(cmdnamtab, ztrdup(hn->nam), &cn->node); } - deleteparamtable(ht); + /* + * On full-array assignment ht is a temporary hash with the default + * get/set functions, whereas pm->u.hash has the special $commands + * get/set functions. Do not assign ht to pm, just delete it. + * + * On append, ht and pm->u.hash are the same table, don't delete. + */ + if (ht != pm->u.hash) + deleteparamtable(ht); } static const struct gsu_scalar pmcommand_gsu = @@ -330,7 +338,7 @@ unsetpmfunction(Param pm, UNUSED(int exp)) /**/ static void -setfunctions(UNUSED(Param pm), HashTable ht, int dis) +setfunctions(Param pm, HashTable ht, int dis) { int i; HashNode hn; @@ -349,7 +357,9 @@ setfunctions(UNUSED(Param pm), HashTable ht, int dis) setfunction(hn->nam, ztrdup(getstrvalue(&v)), dis); } - deleteparamtable(ht); + /* See setpmcommands() above */ + if (ht != pm->u.hash) + deleteparamtable(ht); } /**/ @@ -515,6 +525,98 @@ scanpmdisfunctions(HashTable ht, ScanFunc func, int flags) scanfunctions(ht, func, flags, DISABLED); } +/* Functions for the functions_source special parameter. */ + +/* Retrieve the source file for a function by explicit name */ + +/**/ +static HashNode +getfunction_source(UNUSED(HashTable ht), const char *name, int dis) +{ + Shfunc shf; + Param pm = NULL; + + pm = (Param) hcalloc(sizeof(struct param)); + pm->node.nam = dupstring(name); + pm->node.flags = PM_SCALAR|PM_READONLY; + pm->gsu.s = dis ? &pmdisfunction_gsu : &pmfunction_gsu; + + if ((shf = (Shfunc) shfunctab->getnode2(shfunctab, name)) && + (dis ? (shf->node.flags & DISABLED) : !(shf->node.flags & DISABLED))) { + pm->u.str = getshfuncfile(shf); + if (!pm->u.str) + pm->u.str = dupstring(""); + } + return &pm->node; +} + +/* Retrieve the source file for functions by scanning the table */ + +/**/ +static void +scanfunctions_source(UNUSED(HashTable ht), ScanFunc func, int flags, int dis) +{ + struct param pm; + int i; + HashNode hn; + + memset((void *)&pm, 0, sizeof(struct param)); + pm.node.flags = PM_SCALAR|PM_READONLY; + pm.gsu.s = dis ? &pmdisfunction_gsu : &pmfunction_gsu; + + for (i = 0; i < shfunctab->hsize; i++) { + for (hn = shfunctab->nodes[i]; hn; hn = hn->next) { + if (dis ? (hn->flags & DISABLED) : !(hn->flags & DISABLED)) { + pm.node.nam = hn->nam; + if (func != scancountparams && + ((flags & (SCANPM_WANTVALS|SCANPM_MATCHVAL)) || + !(flags & SCANPM_WANTKEYS))) { + pm.u.str = getshfuncfile((Shfunc)hn); + if (!pm.u.str) + pm.u.str = dupstring(""); + } + func(&pm.node, flags); + } + } + } +} + +/* Param table entry for retrieving functions_source element */ + +/**/ +static HashNode +getpmfunction_source(HashTable ht, const char *name) +{ + return getfunction_source(ht, name, 0); +} + +/* Param table entry for retrieving ds_functions_source element */ + +/**/ +static HashNode +getpmdisfunction_source(HashTable ht, const char *name) +{ + return getfunction_source(ht, name, 1); +} + +/* Param table entry for scanning functions_source table */ + +/**/ +static void +scanpmfunction_source(HashTable ht, ScanFunc func, int flags) +{ + scanfunctions_source(ht, func, flags, 0); +} + +/* Param table entry for scanning dis_functions_source table */ + +/**/ +static void +scanpmdisfunction_source(HashTable ht, ScanFunc func, int flags) +{ + scanfunctions_source(ht, func, flags, 1); +} + /* Functions for the funcstack special parameter. */ /**/ @@ -845,7 +947,7 @@ unsetpmoption(Param pm, UNUSED(int exp)) /**/ static void -setpmoptions(UNUSED(Param pm), HashTable ht) +setpmoptions(Param pm, HashTable ht) { int i; HashNode hn; @@ -870,7 +972,9 @@ setpmoptions(UNUSED(Param pm), HashTable ht) (val && strcmp(val, "off")), 0, opts)) zwarn("can't change option: %s", hn->nam); } - deleteparamtable(ht); + /* See setpmcommands() above */ + if (ht != pm->u.hash) + deleteparamtable(ht); } static const struct gsu_scalar pmoption_gsu = @@ -1409,7 +1513,7 @@ unsetpmnameddir(Param pm, UNUSED(int exp)) /**/ static void -setpmnameddirs(UNUSED(Param pm), HashTable ht) +setpmnameddirs(Param pm, HashTable ht) { int i; HashNode hn, next, hd; @@ -1451,7 +1555,9 @@ setpmnameddirs(UNUSED(Param pm), HashTable ht) i = opts[INTERACTIVE]; opts[INTERACTIVE] = 0; - deleteparamtable(ht); + /* See setpmcommands() above */ + if (ht != pm->u.hash) + deleteparamtable(ht); opts[INTERACTIVE] = i; } @@ -1632,7 +1738,7 @@ unsetpmsalias(Param pm, UNUSED(int exp)) /**/ static void -setaliases(HashTable alht, UNUSED(Param pm), HashTable ht, int flags) +setaliases(HashTable alht, Param pm, HashTable ht, int flags) { int i; HashNode hn, next, hd; @@ -1668,7 +1774,9 @@ setaliases(HashTable alht, UNUSED(Param pm), HashTable ht, int flags) alht->addnode(alht, ztrdup(hn->nam), createaliasnode(ztrdup(val), flags)); } - deleteparamtable(ht); + /* See setpmcommands() above */ + if (ht != pm->u.hash) + deleteparamtable(ht); } /**/ @@ -2095,6 +2203,8 @@ static struct paramdef partab[] = { NULL, getpmdisbuiltin, scanpmdisbuiltins), SPECIALPMDEF("dis_functions", 0, &pmdisfunctions_gsu, getpmdisfunction, scanpmdisfunctions), + SPECIALPMDEF("dis_functions_source", PM_READONLY, NULL, + getpmdisfunction_source, scanpmdisfunction_source), SPECIALPMDEF("dis_galiases", 0, &pmdisgaliases_gsu, getpmdisgalias, scanpmdisgaliases), SPECIALPMDEF("dis_patchars", PM_ARRAY|PM_READONLY, @@ -2111,6 +2221,8 @@ static struct paramdef partab[] = { &funcstack_gsu, NULL, NULL), SPECIALPMDEF("functions", 0, &pmfunctions_gsu, getpmfunction, scanpmfunctions), + SPECIALPMDEF("functions_source", PM_READONLY, NULL, + getpmfunction_source, scanpmfunction_source), SPECIALPMDEF("functrace", PM_ARRAY|PM_READONLY, &functrace_gsu, NULL, NULL), SPECIALPMDEF("galiases", 0, diff --git a/Src/Modules/pcre.c b/Src/Modules/pcre.c index 5fd67963d..27191d709 100644 --- a/Src/Modules/pcre.c +++ b/Src/Modules/pcre.c @@ -75,7 +75,7 @@ zpcre_utf8_enabled(void) static int bin_pcre_compile(char *nam, char **args, Options ops, UNUSED(int func)) { - int pcre_opts = 0, pcre_errptr; + int pcre_opts = 0, pcre_errptr, target_len; const char *pcre_error; char *target; @@ -89,15 +89,19 @@ bin_pcre_compile(char *nam, char **args, Options ops, UNUSED(int func)) pcre_opts |= PCRE_UTF8; pcre_hints = NULL; /* Is this necessary? */ - + if (pcre_pattern) pcre_free(pcre_pattern); target = ztrdup(*args); - unmetafy(target, NULL); + unmetafy(target, &target_len); + + if ((int)strlen(target) != target_len) { + zwarnnam(nam, "embedded NULs in PCRE pattern terminate pattern"); + } pcre_pattern = pcre_compile(target, pcre_opts, &pcre_error, &pcre_errptr, NULL); - + free(target); if (pcre_pattern == NULL) @@ -167,7 +171,12 @@ zpcre_get_substrings(char *arg, int *ovec, int ret, char *matchvar, sprintf(offset_all, "%d %d", ovec[0], ovec[1]); setsparam("ZPCRE_OP", ztrdup(offset_all)); } - match_all = metafy(captures[0], -1, META_DUP); + /* + * Result strings can contain embedded NULs; the length of each is the + * difference between the two values in each paired entry in ovec. + * ovec is length 2*(1+capture_list_length) + */ + match_all = metafy(captures[0], ovec[1] - ovec[0], META_DUP); setsparam(matchvar, match_all); /* * If we're setting match, mbegin, mend we only do @@ -176,13 +185,16 @@ zpcre_get_substrings(char *arg, int *ovec, int ret, char *matchvar, */ if (!want_begin_end || nelem) { char **x, **y; + int vec_off; y = &captures[capture_start]; matches = x = (char **) zalloc(sizeof(char *) * (arrlen(y) + 1)); + vec_off = 2; do { if (*y) - *x++ = metafy(*y, -1, META_DUP); + *x++ = metafy(*y, ovec[vec_off+1]-ovec[vec_off], META_DUP); else *x++ = NULL; + vec_off += 2; } while (*y++); setaparam(substravar, matches); } @@ -318,8 +330,7 @@ bin_pcre_match(char *nam, char **args, Options ops, UNUSED(int func)) ovec = zalloc(ovecsize*sizeof(int)); plaintext = ztrdup(*args); - unmetafy(plaintext, NULL); - subject_len = (int)strlen(plaintext); + unmetafy(plaintext, &subject_len); if (offset_start > 0 && offset_start >= subject_len) ret = PCRE_ERROR_NOMATCH; @@ -351,6 +362,7 @@ cond_pcre_match(char **a, int id) const char *pcre_err; char *lhstr, *rhre, *lhstr_plain, *rhre_plain, *avar=NULL; int r = 0, pcre_opts = 0, pcre_errptr, capcnt, *ov, ovsize; + int lhstr_plain_len, rhre_plain_len; int return_value = 0; if (zpcre_utf8_enabled()) @@ -362,8 +374,8 @@ cond_pcre_match(char **a, int id) rhre = cond_str(a,1,0); lhstr_plain = ztrdup(lhstr); rhre_plain = ztrdup(rhre); - unmetafy(lhstr_plain, NULL); - unmetafy(rhre_plain, NULL); + unmetafy(lhstr_plain, &lhstr_plain_len); + unmetafy(rhre_plain, &rhre_plain_len); pcre_pat = NULL; ov = NULL; ovsize = 0; @@ -373,6 +385,9 @@ cond_pcre_match(char **a, int id) switch(id) { case CPCRE_PLAIN: + if ((int)strlen(rhre_plain) != rhre_plain_len) { + zwarn("embedded NULs in PCRE pattern terminate pattern"); + } pcre_pat = pcre_compile(rhre_plain, pcre_opts, &pcre_err, &pcre_errptr, NULL); if (pcre_pat == NULL) { zwarn("failed to compile regexp /%s/: %s", rhre, pcre_err); @@ -381,7 +396,7 @@ cond_pcre_match(char **a, int id) pcre_fullinfo(pcre_pat, NULL, PCRE_INFO_CAPTURECOUNT, &capcnt); ovsize = (capcnt+1)*3; ov = zalloc(ovsize*sizeof(int)); - r = pcre_exec(pcre_pat, NULL, lhstr_plain, strlen(lhstr_plain), 0, 0, ov, ovsize); + r = pcre_exec(pcre_pat, NULL, lhstr_plain, lhstr_plain_len, 0, 0, ov, ovsize); /* r < 0 => error; r==0 match but not enough size in ov * r > 0 => (r-1) substrings found; r==1 => no substrings */ diff --git a/Src/Modules/regex.c b/Src/Modules/regex.c index edb7234d4..d02769ef0 100644 --- a/Src/Modules/regex.c +++ b/Src/Modules/regex.c @@ -111,7 +111,7 @@ zcond_regex_match(char **a, int id) *x = NULL; } if (isset(BASHREMATCH)) { - setaparam("BASH_REMATCH", arr); + assignaparam("BASH_REMATCH", arr, 0); } else { zlong offs; char *ptr; @@ -119,7 +119,7 @@ zcond_regex_match(char **a, int id) m = matches; s = metafy(lhstr + m->rm_so, m->rm_eo - m->rm_so, META_DUP); - setsparam("MATCH", s); + assignsparam("MATCH", s, 0); /* * Count the characters before the match. */ @@ -133,7 +133,7 @@ zcond_regex_match(char **a, int id) ptr += clen; leftlen -= clen; } - setiparam("MBEGIN", offs + !isset(KSHARRAYS)); + assigniparam("MBEGIN", offs + !isset(KSHARRAYS), 0); /* * Add on the characters in the match. */ @@ -144,7 +144,7 @@ zcond_regex_match(char **a, int id) ptr += clen; leftlen -= clen; } - setiparam("MEND", offs + !isset(KSHARRAYS) - 1); + assigniparam("MEND", offs + !isset(KSHARRAYS) - 1, 0); if (nelem) { char **mbegin, **mend, **bptr, **eptr; bptr = mbegin = (char **)zalloc(sizeof(char *)*(nelem+1)); diff --git a/Src/Modules/system.c b/Src/Modules/system.c index afaec262a..3eecd7e95 100644 --- a/Src/Modules/system.c +++ b/Src/Modules/system.c @@ -313,7 +313,7 @@ bin_sysopen(char *nam, char **args, Options ops, UNUSED(int func)) int flags = O_NOCTTY | append | ((append || write) ? (read ? O_RDWR : O_WRONLY) : O_RDONLY); char *opt, *ptr, *nextopt, *fdvar; - int o, fd, explicit = -1; + int o, fd, moved_fd, explicit = -1; mode_t perms = 0666; #if defined(FD_CLOEXEC) && !defined(O_CLOEXEC) int fdflags; @@ -376,22 +376,32 @@ bin_sysopen(char *nam, char **args, Options ops, UNUSED(int func)) zwarnnam(nam, "can't open file %s: %e", *args, errno); return 1; } - fd = (explicit > -1) ? redup(fd, explicit) : movefd(fd); - if (fd == -1) { + moved_fd = (explicit > -1) ? redup(fd, explicit) : movefd(fd); + if (moved_fd == -1) { zwarnnam(nam, "can't open file %s", *args); return 1; } -#if defined(FD_CLOEXEC) && !defined(O_CLOEXEC) +#ifdef FD_CLOEXEC +#ifdef O_CLOEXEC + /* + * the O_CLOEXEC is a flag attached to the *file descriptor*, not the + * *open file description* so it doesn't survive a dup(). If that flag was + * requested and the fd was moved, we need to reapply it to the moved fd + * even if the original one was open with O_CLOEXEC + */ + if ((flags & O_CLOEXEC) && fd != moved_fd) +#else if (fdflags) - fcntl(fd, F_SETFD, FD_CLOEXEC); -#endif +#endif /* O_CLOEXEC */ + fcntl(moved_fd, F_SETFD, FD_CLOEXEC); +#endif /* FD_CLOEXEC */ if (explicit == -1) { - fdtable[fd] = FDT_EXTERNAL; - setiparam(fdvar, fd); - /* if setting the variable failed, close fd to avoid leak */ + fdtable[moved_fd] = FDT_EXTERNAL; + setiparam(fdvar, moved_fd); + /* if setting the variable failed, close moved_fd to avoid leak */ if (errflag) - zclose(fd); + zclose(moved_fd); } return 0; diff --git a/Src/Modules/tcp.c b/Src/Modules/tcp.c index dec12142b..0bbce5d49 100644 --- a/Src/Modules/tcp.c +++ b/Src/Modules/tcp.c @@ -343,7 +343,8 @@ bin_ztcp(char *nam, char **args, Options ops, UNUSED(int func)) { int herrno, err=1, destport, force=0, verbose=0, test=0, targetfd=0; ZSOCKLEN_T len; - char **addrp, *desthost, *localname, *remotename; + char **addrp, *desthost; + const char *localname, *remotename; struct hostent *zthost = NULL, *ztpeer = NULL; struct servent *srv; Tcp_session sess = NULL; diff --git a/Src/Modules/zftp.c b/Src/Modules/zftp.c index deed35e2f..24f4b4200 100644 --- a/Src/Modules/zftp.c +++ b/Src/Modules/zftp.c @@ -3177,7 +3177,7 @@ static struct features module_features = { int setup_(UNUSED(Module m)) { - return (require_module("zsh/net/tcp", NULL) == 1); + return (require_module("zsh/net/tcp", NULL, 0) == 1); } /**/ diff --git a/Src/Modules/zpty.c b/Src/Modules/zpty.c index 0ef753915..3c1bef58f 100644 --- a/Src/Modules/zpty.c +++ b/Src/Modules/zpty.c @@ -331,6 +331,7 @@ newptycmd(char *nam, char *pname, char **args, int echo, int nblock) /* This code copied from the clone module, except for getting * * the descriptor from get_pty() and duplicating it to 0/1/2. */ + deletehookfunc("exit", ptyhook); clearjobtab(0); ppid = getppid(); mypid = getpid(); @@ -544,7 +545,8 @@ ptyread(char *nam, Ptycmd cmd, char **args, int noblock, int mustmatch) p = dupstring(args[1]); tokenize(p); remnulargs(p); - if (!(prog = patcompile(p, PAT_STATIC, NULL))) { + /* Signals handlers might stomp PAT_STATIC */ + if (!(prog = patcompile(p, PAT_ZDUP, NULL))) { zwarnnam(nam, "bad pattern: %s", args[1]); return 1; } @@ -682,9 +684,14 @@ ptyread(char *nam, Ptycmd cmd, char **args, int noblock, int mustmatch) write_loop(1, buf, used); } - if (seen && (!prog || matchok || !mustmatch)) - return 0; - return cmd->fin + 1; + { + int ret = cmd->fin + 1; + if (seen && (!prog || matchok || !mustmatch)) + ret = 0; + if (prog) + freepatprog(prog); + return ret; + } } static int @@ -846,6 +853,7 @@ bin_zpty(char *nam, char **args, Options ops, UNUSED(int func)) } } +/**/ static int ptyhook(UNUSED(Hookdef d), UNUSED(void *dummy)) { diff --git a/Src/Modules/zutil.c b/Src/Modules/zutil.c index d95c0c254..19a8306b5 100644 --- a/Src/Modules/zutil.c +++ b/Src/Modules/zutil.c @@ -510,25 +510,33 @@ bin_zstyle(char *nam, char **args, UNUSED(Options ops), UNUSED(int func)) zwarnnam(nam, "too many arguments"); return 1; } + + queue_signals(); /* Protect PAT_STATIC */ + if (context) { tokenize(context); zstyle_contprog = patcompile(context, PAT_STATIC, NULL); - if (!zstyle_contprog) + if (!zstyle_contprog) { + unqueue_signals(); return 1; + } } else zstyle_contprog = NULL; if (stylename) { s = (Style)zstyletab->getnode2(zstyletab, stylename); - if (!s) + if (!s) { + unqueue_signals(); return 1; + } zstyletab->printnode(&s->node, list); } else { scanhashtable(zstyletab, 1, 0, 0, zstyletab->printnode, list); } + unqueue_signals(); return 0; } switch (args[0][1]) { @@ -675,14 +683,20 @@ bin_zstyle(char *nam, char **args, UNUSED(Options ops), UNUSED(int func)) char **vals; Patprog prog; + queue_signals(); /* Protect PAT_STATIC */ + tokenize(args[3]); if ((vals = lookupstyle(args[1], args[2])) && (prog = patcompile(args[3], PAT_STATIC, NULL))) { while (*vals) - if (pattry(prog, *vals++)) + if (pattry(prog, *vals++)) { + unqueue_signals(); return 0; + } } + + unqueue_signals(); return 1; } break; diff --git a/Src/Zle/compcore.c b/Src/Zle/compcore.c index d1cf7a08a..52b0c173f 100644 --- a/Src/Zle/compcore.c +++ b/Src/Zle/compcore.c @@ -3135,7 +3135,9 @@ matchcmp(Cmatch *a, Cmatch *b) if ((*b)->disp && !((*b)->flags & CMF_MORDER)) return 1; - return zstrbcmp((*a)->str, (*b)->str); + return zstrcmp((*a)->str, (*b)->str, (SORTIT_IGNORING_BACKSLASHES| + (isset(NUMERICGLOBSORT) ? + SORTIT_NUMERICALLY : 0))); } /* This tests whether two matches are equal (would produce the same diff --git a/Src/Zle/compctl.c b/Src/Zle/compctl.c index 52c6f1233..5414b8ff6 100644 --- a/Src/Zle/compctl.c +++ b/Src/Zle/compctl.c @@ -99,7 +99,7 @@ freecompctlp(HashNode hn) } /**/ -void +static void freecompctl(Compctl cc) { if (cc == &cc_default || @@ -142,7 +142,7 @@ freecompctl(Compctl cc) } /**/ -void +static void freecompcond(void *a) { Compcond cc = (Compcond) a; @@ -186,7 +186,7 @@ freecompcond(void *a) } /**/ -int +static int compctlread(char *name, char **args, Options ops, char *reply) { char *buf, *bptr; @@ -1564,6 +1564,8 @@ bin_compctl(char *name, char **argv, UNUSED(Options ops), UNUSED(int func)) Compctl cc = NULL; int ret = 0; + queue_signals(); + /* clear static flags */ cclist = 0; showmask = 0; @@ -1571,12 +1573,15 @@ bin_compctl(char *name, char **argv, UNUSED(Options ops), UNUSED(int func)) /* Parse all the arguments */ if (*argv) { /* Let's see if this is a global matcher definition. */ - if ((ret = get_gmatcher(name, argv))) + if ((ret = get_gmatcher(name, argv))) { + unqueue_signals(); return ret - 1; + } cc = (Compctl) zshcalloc(sizeof(*cc)); if (get_compctl(name, &argv, cc, 1, 0, 0)) { freecompctl(cc); + unqueue_signals(); return 1; } @@ -1604,6 +1609,7 @@ bin_compctl(char *name, char **argv, UNUSED(Options ops), UNUSED(int func)) printcompctl((cclist & COMP_LIST) ? "" : "DEFAULT", &cc_default, 0, 0); printcompctl((cclist & COMP_LIST) ? "" : "FIRST", &cc_first, 0, 0); print_gmatcher((cclist & COMP_LIST)); + unqueue_signals(); return ret; } @@ -1642,6 +1648,7 @@ bin_compctl(char *name, char **argv, UNUSED(Options ops), UNUSED(int func)) printcompctl("", &cc_first, 0, 0); if (cclist & COMP_LISTMATCH) print_gmatcher(COMP_LIST); + unqueue_signals(); return ret; } @@ -1656,6 +1663,7 @@ bin_compctl(char *name, char **argv, UNUSED(Options ops), UNUSED(int func)) compctl_process_cc(argv, cc); } + unqueue_signals(); return ret; } @@ -1667,12 +1675,18 @@ bin_compctl(char *name, char **argv, UNUSED(Options ops), UNUSED(int func)) static int bin_compcall(char *name, UNUSED(char **argv), Options ops, UNUSED(int func)) { + int ret; + if (incompfunc != 1) { zwarnnam(name, "can only be called from completion function"); return 1; } - return makecomplistctl((OPT_ISSET(ops,'T') ? 0 : CFN_FIRST) | - (OPT_ISSET(ops,'D') ? 0 : CFN_DEFAULT)); + + queue_signals(); + ret = makecomplistctl((OPT_ISSET(ops,'T') ? 0 : CFN_FIRST) | + (OPT_ISSET(ops,'D') ? 0 : CFN_DEFAULT)); + unqueue_signals(); + return ret; } /* @@ -1756,6 +1770,8 @@ ccmakehookfn(UNUSED(Hookdef dummy), struct ccmakedat *dat) int onm = nmatches, odm = diffmatches, osi = movefd(0); LinkNode n; + queue_signals(); + /* We build a copy of the list of matchers to use to make sure that this * works even if a shell function called from the completion code changes * the global matchers. */ @@ -1851,6 +1867,7 @@ ccmakehookfn(UNUSED(Hookdef dummy), struct ccmakedat *dat) redup(osi, 0); dat->lst = 0; + unqueue_signals(); return 0; } if (lastmatches) { @@ -1874,6 +1891,7 @@ ccmakehookfn(UNUSED(Hookdef dummy), struct ccmakedat *dat) redup(osi, 0); dat->lst = 0; + unqueue_signals(); return 0; } if (!m || !(m = m->next)) @@ -1883,6 +1901,8 @@ ccmakehookfn(UNUSED(Hookdef dummy), struct ccmakedat *dat) } redup(osi, 0); dat->lst = 1; + + unqueue_signals(); return 0; } @@ -2044,7 +2064,7 @@ maketildelist(void) /* This does the check for compctl -x `n' and `N' patterns. */ /**/ -int +static int getcpat(char *str, int cpatindex, char *cpat, int class) { char *s, *t, *p; diff --git a/Src/Zle/complete.c b/Src/Zle/complete.c index 48fcd4751..68bdf2332 100644 --- a/Src/Zle/complete.c +++ b/Src/Zle/complete.c @@ -901,7 +901,7 @@ do_comp_vars(int test, int na, char *sa, int nb, char *sb, int mod) return 0; singsub(&sa); - pp = patcompile(sa, PAT_STATIC, NULL); + pp = patcompile(sa, PAT_HEAPDUP, NULL); for (i--, p = compwords + i; i >= 0; p--, i--) { if (pattry(pp, *p)) { @@ -955,7 +955,7 @@ do_comp_vars(int test, int na, char *sa, int nb, char *sb, int mod) if (!na) return 0; - if (!(pp = patcompile(sa, PAT_STATIC, 0))) + if (!(pp = patcompile(sa, PAT_HEAPDUP, 0))) return 0; if (test == CVT_PREPAT) { diff --git a/Src/Zle/complist.c b/Src/Zle/complist.c index 2edaf6eca..a83daeff9 100644 --- a/Src/Zle/complist.c +++ b/Src/Zle/complist.c @@ -1993,7 +1993,8 @@ complistmatches(UNUSED(Hookdef dummy), Chdata dat) if (noselect > 0) noselect = 0; - if ((minfo.asked == 2 && mselect < 0) || nlnct >= zterm_lines) { + if ((minfo.asked == 2 && mselect < 0) || nlnct >= zterm_lines || + errflag) { showinglist = 0; amatches = oamatches; return (noselect = 1); @@ -2333,11 +2334,6 @@ msearch(Cmatch **ptr, char *ins, int back, int rep, int *wrapp) } } if (x == ex && y == ey) { - if (wrap) { - msearchstate = MS_FAILED | owrap; - break; - } - msearchstate |= MS_WRAPPED; if (back) { x = mcols - 1; @@ -2349,6 +2345,13 @@ msearch(Cmatch **ptr, char *ins, int back, int rep, int *wrapp) } ex = mcol; ey = mline; + + if (wrap || (x == ex && y == ey)) { + msearchstate = MS_FAILED | owrap; + break; + } + + msearchstate |= MS_WRAPPED; wrap = 1; *wrapp = 1; } diff --git a/Src/Zle/compmatch.c b/Src/Zle/compmatch.c index aedf463fc..1cdbb8a48 100644 --- a/Src/Zle/compmatch.c +++ b/Src/Zle/compmatch.c @@ -1548,27 +1548,11 @@ pattern_match(Cpattern p, char *s, Cpattern wp, char *ws) { convchar_t c, wc; convchar_t ind, wind; - int len = 0, wlen, mt, wmt; -#ifdef MULTIBYTE_SUPPORT - mbstate_t lstate, wstate; - - memset(&lstate, 0, sizeof(lstate)); - memset(&wstate, 0, sizeof(wstate)); -#endif + int len = 0, wlen = 0, mt, wmt; while (p && wp && *s && *ws) { /* First test the word character */ -#ifdef MULTIBYTE_SUPPORT - wlen = mb_metacharlenconv_r(ws, &wc, &wstate); -#else - if (*ws == Meta) { - wc = STOUC(ws[1]) ^ 32; - wlen = 2; - } else { - wc = STOUC(*ws); - wlen = 1; - } -#endif + wc = unmeta_one(ws, &wlen); wind = pattern_match1(wp, wc, &wmt); if (!wind) return 0; @@ -1576,18 +1560,7 @@ pattern_match(Cpattern p, char *s, Cpattern wp, char *ws) /* * Now the line character. */ -#ifdef MULTIBYTE_SUPPORT - len = mb_metacharlenconv_r(s, &c, &lstate); -#else - /* We have the character itself. */ - if (*s == Meta) { - c = STOUC(s[1]) ^ 32; - len = 2; - } else { - c = STOUC(*s); - len = 1; - } -#endif + c = unmeta_one(s, &len); /* * If either is "?", they match each other; no further tests. * Apply this even if the character wasn't convertable; @@ -1627,17 +1600,7 @@ pattern_match(Cpattern p, char *s, Cpattern wp, char *ws) } while (p && *s) { -#ifdef MULTIBYTE_SUPPORT - len = mb_metacharlenconv_r(s, &c, &lstate); -#else - if (*s == Meta) { - c = STOUC(s[1]) ^ 32; - len = 2; - } else { - c = STOUC(*s); - len = 1; - } -#endif + c = unmeta_one(s, &len); if (!pattern_match1(p, c, &mt)) return 0; p = p->next; @@ -1645,17 +1608,7 @@ pattern_match(Cpattern p, char *s, Cpattern wp, char *ws) } while (wp && *ws) { -#ifdef MULTIBYTE_SUPPORT - wlen = mb_metacharlenconv_r(ws, &wc, &wstate); -#else - if (*ws == Meta) { - wc = STOUC(ws[1]) ^ 32; - wlen = 2; - } else { - wc = STOUC(*ws); - wlen = 1; - } -#endif + wc = unmeta_one(ws, &wlen); if (!pattern_match1(wp, wc, &wmt)) return 0; wp = wp->next; diff --git a/Src/Zle/computil.c b/Src/Zle/computil.c index 192ddeab9..e704f9ffa 100644 --- a/Src/Zle/computil.c +++ b/Src/Zle/computil.c @@ -917,7 +917,6 @@ struct cadef { int argsactive; /* if normal arguments are still allowed */ /* used while parsing a command line */ char *set; /* set name prefix (<name>-), shared */ - char *sname; /* set name */ int flags; /* see CDF_* below */ char *nonarg; /* pattern for non-args (-A argument) */ }; @@ -935,7 +934,7 @@ struct caopt { Caarg args; /* option arguments */ int active; /* still allowed on command line */ int num; /* it's the num'th option */ - char *set; /* set name, shared */ + char *gsname; /* group or set name, shared */ int not; /* don't complete this option (`!...') */ }; @@ -956,10 +955,10 @@ struct caarg { char *end; /* end-pattern for ::<pat>:... */ char *opt; /* option name if for an option */ int num; /* it's the num'th argument */ - int min; /* it's also this argument, using opt. args */ + int min; /* earliest possible arg pos, given optional args */ int direct; /* true if argument number was given explicitly */ int active; /* still allowed on command line */ - char *set; /* set name, shared */ + char *gsname; /* group or set name, shared */ }; #define CAA_NORMAL 1 @@ -1020,7 +1019,6 @@ freecadef(Cadef d) s = d->snext; zsfree(d->match); zsfree(d->set); - zsfree(d->sname); if (d->defs) freearray(d->defs); @@ -1098,7 +1096,7 @@ parse_caarg(int mult, int type, int num, int opt, char *oname, char **def, ret->type = type; ret->opt = ztrdup(oname); ret->direct = 0; - ret->set = set; + ret->gsname = set; /* Get the description. */ @@ -1147,8 +1145,11 @@ alloc_cadef(char **args, int single, char *match, char *nonarg, int flags) ret->defs = NULL; ret->ndefs = 0; } + ret->nopts = 0; + ret->ndopts = 0; + ret->nodopts = 0; ret->lastt = time(0); - ret->set = ret->sname = NULL; + ret->set = NULL; if (single) { ret->single = (Caopt *) zalloc(256 * sizeof(Caopt)); memset(ret->single, 0, 256 * sizeof(Caopt)); @@ -1182,12 +1183,10 @@ parse_cadef(char *nam, char **args) Cadef all, ret; Caopt *optp; char **orig_args = args, *p, *q, *match = "r:|[_-]=* r:|=*", **xor, **sargs; - char *adpre, *adsuf, *axor = NULL, *doset = NULL, **setp = NULL; + char *adpre, *adsuf, *axor = NULL, *doset = NULL, **pendset = NULL, **curset = NULL; char *nonarg = NULL; - int single = 0, anum = 1, xnum, nopts, ndopts, nodopts, flags = 0; - int state = 0, not = 0; - - nopts = ndopts = nodopts = 0; + int single = 0, anum = 1, xnum, flags = 0; + int foreignset = 0, not = 0; /* First string is the auto-description definition. */ @@ -1250,51 +1249,72 @@ parse_cadef(char *nam, char **args) if (nonarg) tokenize(nonarg = dupstring(nonarg)); - /* Looks good. Optimistically allocate the cadef structure. */ all = ret = alloc_cadef(orig_args, single, match, nonarg, flags); optp = &(ret->opts); - anum = 1; - sargs = args; /* Get the definitions. */ - for (; *args; args++) { + for (; *args || pendset; args++) { + if (!*args) { + /* start new set */ + args = sargs; /* go back and repeat parse of common options */ + doset = NULL; + set_cadef_opts(ret); + ret = ret->snext = alloc_cadef(NULL, single, match, nonarg, flags); + optp = &(ret->opts); + anum = 1; + foreignset = 0; + curset = pendset; + pendset = 0; + } if (args[0][0] == '-' && !args[0][1] && args[1]) { - if (!state) { - char *p; - int l; - - if (setp) - args = setp; - p = *++args; - l = strlen(p) - 1; + if ((foreignset = curset && args != curset)) { + if (!pendset && args > curset) + pendset = args; /* mark pointer to next pending set */ + ++args; + } else { + /* Carrying on: this is the current set */ + char *p = *++args; + int l = strlen(p) - 1; + if (*p == '(' && p[l] == ')') { axor = p = dupstring(p + 1); p[l - 1] = '\0'; } else axor = NULL; + if (!*p) { + freecadef(all); + zwarnnam(nam, "empty set name"); + return NULL; + } ret->set = doset = tricat(p, "-", ""); - ret->sname = ztrdup(p); - state = 1; - } else { - setp = args; - state = 0; - args = sargs - 1; - doset = NULL; - ret->nopts = nopts; - ret->ndopts = ndopts; - ret->nodopts = nodopts; - set_cadef_opts(ret); - ret = ret->snext = alloc_cadef(NULL, single, NULL, nonarg, flags); - optp = &(ret->opts); - nopts = ndopts = nodopts = 0; - anum = 1; + curset = args; /* needed for the first set */ } continue; - } + } else if (args[0][0] == '+' && !args[0][1] && args[1]) { + char *p; + int l; + + foreignset = 0; /* group not in any set, don't want to skip it */ + p = *++args; + l = strlen(p) - 1; + if (*p == '(' && p[l] == ')') { + axor = p = dupstring(p + 1); + p[l - 1] = '\0'; + } else + axor = NULL; + if (!*p) { + freecadef(all); + zwarnnam(nam, "empty group name"); + return NULL; + } + doset = tricat(p, "-", ""); + continue; + } else if (foreignset) /* skipping over a different set */ + continue; p = dupstring(*args); xnum = 0; if ((not = (*p == '!'))) @@ -1506,7 +1526,7 @@ parse_cadef(char *nam, char **args) optp = &((*optp)->next); opt->next = NULL; - opt->set = doset; + opt->gsname = doset; opt->name = ztrdup(rembslashcolon(name)); if (descr) opt->descr = ztrdup(descr); @@ -1526,13 +1546,13 @@ parse_cadef(char *nam, char **args) opt->xor = (again == 1 && xor ? zarrdup(xor) : xor); opt->type = otype; opt->args = oargs; - opt->num = nopts++; + opt->num = ret->nopts++; opt->not = not; if (otype == CAO_DIRECT || otype == CAO_EQUAL) - ndopts++; + ret->ndopts++; else if (otype == CAO_ODIRECT || otype == CAO_OEQUAL) - nodopts++; + ret->nodopts++; /* If this is for single-letter option we also store a * pointer for the definition in the array for fast lookup. @@ -1584,7 +1604,7 @@ parse_cadef(char *nam, char **args) continue; if ((direct = idigit(*p))) { - /* Argment number is given. */ + /* Argument number is given. */ int num = 0; while (*p && idigit(*p)) @@ -1630,9 +1650,6 @@ parse_cadef(char *nam, char **args) ret->args = arg; } } - ret->nopts = nopts; - ret->ndopts = ndopts; - ret->nodopts = nodopts; set_cadef_opts(ret); return all; @@ -1751,6 +1768,27 @@ ca_get_sopt(Cadef d, char *line, char **end, LinkList *lp) return pp; } +/* Search for an option in all sets except the current one. + * Return true if found */ + +static int +ca_foreign_opt(Cadef curset, Cadef all, char *option) +{ + Cadef d; + Caopt p; + + for (d = all; d; d = d->snext) { + if (d == curset) + continue; + + for (p = d->opts; p; p = p->next) { + if (!strcmp(p->name, option)) + return 1; + } + } + return 0; +} + /* Return the n'th argument definition. */ static Caarg @@ -1776,77 +1814,95 @@ ca_get_arg(Cadef d, int n) * d: option definitions for a set * pass either: * xor: a list if exclusions - * opts: if set, all options excluded leaving only nornal/rest arguments - * if ca_xor list initialised, exclusions are added to it */ - -static LinkList ca_xor; + * opts: if set, all options excluded leaving only nornal/rest arguments */ -static int -ca_inactive(Cadef d, char **xor, int cur, int opts, char *optname) +static void +ca_inactive(Cadef d, char **xor, int cur, int opts) { if ((xor || opts) && cur <= compcurrent) { Caopt opt; char *x; - int sl = (d->set ? (int)strlen(d->set) : -1), set = 0; + /* current word could be a prefix of a longer one so only do + * exclusions for single-letter options (for option clumping) */ + int single = (cur == compcurrent); for (; (x = (opts ? "-" : *xor)); xor++) { - if (optname && optname[0] == x[0] && strcmp(optname, x)) - continue; - if (ca_xor) - addlinknode(ca_xor, x); - set = 0; - if (sl > 0) { - if (strpfx(d->set, x)) { - x += sl; - set = 1; - } else if (!strncmp(d->set, x, sl - 1)) { - Caopt p; - - for (p = d->opts; p; p = p->next) - if (p->set) - p->active = 0; - - x = ":"; - set = 1; + int excludeall = 0; + char *grp = NULL; + size_t grplen; + char *next, *sep = x; + + while (*sep != '+' && *sep != '-' && *sep != ':' && *sep != '*' && !idigit(*sep)) { + if (!(next = strchr(sep, '-')) || !*++next) { + /* exclusion is just the name of a set or group */ + excludeall = 1; /* excluding options and args */ + sep += strlen(sep); + /* A trailing '-' is included in the various gsname fields but is not + * there for this branch. This is why we add excludeall to grplen + * when checking for the null in a few places below */ + break; } + sep = next; + } + if (sep > x) { /* exclusion included a set or group name */ + grp = x; + grplen = sep - grp; + x = sep; } - if (x[0] == ':' && !x[1]) { - if (set) { + + if (excludeall || (x[0] == ':' && !x[1])) { + if (grp) { Caarg a; for (a = d->args; a; a = a->next) - if (a->set) + if (a->gsname && !strncmp(a->gsname, grp, grplen) && + !a->gsname[grplen + excludeall]) a->active = 0; - if (d->rest && (!set || d->rest->set)) + if (d->rest && d->rest->gsname && + !strncmp(d->rest->gsname, grp, grplen) && + !d->rest->gsname[grplen + excludeall]) d->rest->active = 0; } else d->argsactive = 0; - } else if (x[0] == '-' && !x[1]) { + } + + if (excludeall || (x[0] == '-' && !x[1])) { Caopt p; for (p = d->opts; p; p = p->next) - if (!set || p->set) + if ((!grp || (p->gsname && !strncmp(p->gsname, grp, grplen) && + !p->gsname[grplen + excludeall])) && + !(single && *p->name && p->name[1] && p->name[2])) p->active = 0; - } else if (x[0] == '*' && !x[1]) { - if (d->rest && (!set || d->rest->set)) - d->rest->active = 0; - } else if (idigit(x[0])) { - int n = atoi(x); - Caarg a = d->args; - - while (a && a->num < n) - a = a->next; + } - if (a && a->num == n && (!set || a->set)) - a->active = 0; - } else if ((opt = ca_get_opt(d, x, 1, NULL)) && (!set || opt->set)) - opt->active = 0; + if (excludeall || (x[0] == '*' && !x[1])) { + if (d->rest && (!grp || (d->rest->gsname && + !strncmp(d->rest->gsname, grp, grplen) && + !d->rest->gsname[grplen + excludeall]))) + d->rest->active = 0; + } - if (opts) - break; + if (!excludeall) { + if (idigit(x[0])) { + int n = atoi(x); + Caarg a = d->args; + + while (a && a->num < n) + a = a->next; + + if (a && a->num == n && (!grp || (a->gsname && + !strncmp(a->gsname, grp, grplen)))) + a->active = 0; + } else if ((opt = ca_get_opt(d, x, 1, NULL)) && + (!grp || (opt->gsname && !strncmp(opt->gsname, grp, grplen))) && + !(single && *opt->name && opt->name[1] && opt->name[2])) + opt->active = 0; + if (opts) + break; + } } } - return 0; } /* State when parsing a command line. */ @@ -1875,7 +1931,6 @@ struct castate { int curpos; /* current word position */ int argend; /* total number of words */ int inopt; /* set to current word pos if word is a recognised option */ - int inrest; /* unused */ int inarg; /* in a normal argument */ int nth; /* number of current normal arg */ int doff; /* length of current option */ @@ -1934,7 +1989,7 @@ ca_opt_arg(Caopt opt, char *line) * existing options on the line. */ static int -ca_parse_line(Cadef d, int multi, int first) +ca_parse_line(Cadef d, Cadef all, int multi, int first) { Caarg adef, ddef; Caopt ptr, wasopt = NULL, dopt; @@ -1978,7 +2033,7 @@ ca_parse_line(Cadef d, int multi, int first) state.argbeg = state.optbeg = state.nargbeg = state.restbeg = state.actopts = state.nth = state.inopt = state.inarg = state.opt = state.arg = 1; state.argend = argend = arrlen(compwords) - 1; - state.inrest = state.doff = state.singles = state.oopt = 0; + state.doff = state.singles = state.oopt = 0; state.curpos = compcurrent; state.args = znewlinklist(); state.oargs = (LinkList *) zalloc(d->nopts * sizeof(LinkList)); @@ -2025,10 +2080,9 @@ ca_parse_line(Cadef d, int multi, int first) remnulargs(line); untokenize(line); - if (ca_inactive(d, argxor, cur, 0, NULL) || - ((d->flags & CDF_SEP) && cur != compcurrent && !strcmp(line, "--"))) { - if (ca_inactive(d, NULL, cur, 1, NULL)) - return 1; + ca_inactive(d, argxor, cur, 0); + if ((d->flags & CDF_SEP) && cur != compcurrent && !strcmp(line, "--")) { + ca_inactive(d, NULL, cur, 1); continue; } @@ -2104,9 +2158,7 @@ ca_parse_line(Cadef d, int multi, int first) if (!state.oargs[state.curopt->num]) state.oargs[state.curopt->num] = znewlinklist(); - if (ca_inactive(d, state.curopt->xor, cur, 0, - (cur == compcurrent ? state.curopt->name : NULL))) - return 1; + ca_inactive(d, state.curopt->xor, cur, 0); /* Collect the argument strings. Maybe. */ @@ -2159,9 +2211,7 @@ ca_parse_line(Cadef d, int multi, int first) if (!state.oargs[tmpopt->num]) state.oargs[tmpopt->num] = znewlinklist(); - if (ca_inactive(d, tmpopt->xor, cur, 0, - (cur == compcurrent ? tmpopt->name : NULL))) - return 1; + ca_inactive(d, tmpopt->xor, cur, 0); } } if (state.def && @@ -2183,20 +2233,13 @@ ca_parse_line(Cadef d, int multi, int first) else state.curopt = NULL; } else if (multi && (*line == '-' || *line == '+') && cur != compcurrent -#if 0 - /**** Ouch. Using this will disable the mutual exclusion - of different sets. Not using it will make the -A - pattern be effectively ignored with multiple sets. */ - && (!napat || !pattry(napat, line)) -#endif - ) + && (ca_foreign_opt(d, all, line))) return 1; else if (state.arg && (!napat || cur <= compcurrent || !pattry(napat, line))) { /* Otherwise it's a normal argument. */ - if (napat && cur <= compcurrent && - ca_inactive(d, NULL, cur + 1, 1, NULL)) - return 1; + if (napat && cur <= compcurrent) + ca_inactive(d, NULL, cur + 1, 1); arglast = 1; /* if this is the first normal arg after an option, may have been @@ -2231,7 +2274,6 @@ ca_parse_line(Cadef d, int multi, int first) if (ca_laststate.def) break; - state.inrest = 0; state.opt = (cur == state.nargbeg + 1 && (!multi || !*line || *line == '-' || *line == '+')); @@ -2421,19 +2463,19 @@ ca_set_data(LinkList descr, LinkList act, LinkList subc, restrict_range(ca_laststate.argbeg, ca_laststate.argend); } if (arg->opt) { - buf = (char *) zhalloc((arg->set ? strlen(arg->set) : 0) + + buf = (char *) zhalloc((arg->gsname ? strlen(arg->gsname) : 0) + strlen(arg->opt) + 40); if (arg->num > 0 && arg->type < CAA_REST) sprintf(buf, "%soption%s-%d", - (arg->set ? arg->set : ""), arg->opt, arg->num); + (arg->gsname ? arg->gsname : ""), arg->opt, arg->num); else sprintf(buf, "%soption%s-rest", - (arg->set ? arg->set : ""), arg->opt); + (arg->gsname ? arg->gsname : ""), arg->opt); } else if (arg->num > 0) { sprintf(nbuf, "argument-%d", arg->num); - buf = (arg->set ? dyncat(arg->set, nbuf) : dupstring(nbuf)); + buf = (arg->gsname ? dyncat(arg->gsname, nbuf) : dupstring(nbuf)); } else - buf = (arg->set ? dyncat(arg->set, "argument-rest") : + buf = (arg->gsname ? dyncat(arg->gsname, "argument-rest") : dupstring("argument-rest")); addlinknode(subc, buf); @@ -2537,47 +2579,29 @@ bin_comparguments(char *nam, char **args, UNUSED(Options ops), UNUSED(int func)) * auto-description string, the optional -s, -S, -A and -M options * given to _arguments and the specs. */ if (compcurrent > 1 && compwords[0]) { - Cadef def; + Cadef def, all; int cap = ca_parsed, multi, first = 1, use, ret = 0; - LinkList cax = ca_xor, nx; - LinkNode node; Castate states = NULL, sp; - char *xor[2]; ca_parsed = 0; - xor[1] = NULL; - if (!(def = get_cadef(nam, args + 1))) + if (!(def = all = get_cadef(nam, args + 1))) return 1; multi = !!def->snext; /* if we have sets */ ca_parsed = cap; - ca_xor = (multi ? newlinklist() : NULL); while (def) { /* for each set */ - use = !ca_parse_line(def, multi, first); - nx = ca_xor; - ca_xor = NULL; /* don't want to duplicate the xors in the list */ - while ((def = def->snext)) { - if (nx) { - for (node = firstnode(nx); node; incnode(node)) { - xor[0] = (char *) getdata(node); - if (!strcmp(xor[0], def->sname) || - ca_inactive(def, xor, compcurrent, 0, NULL)) - break; /* exclude this whole set */ - } - if (!node) /* continue with this set */ - break; - } - /* entire set was excluded, continue to next set */ - } - ca_xor = nx; + use = !ca_parse_line(def, all, multi, first); + def = def->snext; if (use && def) { + /* entry needed so save it into list */ sp = (Castate) zalloc(sizeof(*sp)); memcpy(sp, &ca_laststate, sizeof(*sp)); sp->snext = states; states = sp; } else if (!use && !def) { + /* final entry not needed */ if (states) { freecastate(&ca_laststate); memcpy(&ca_laststate, states, sizeof(*sp)); @@ -2589,7 +2613,6 @@ bin_comparguments(char *nam, char **args, UNUSED(Options ops), UNUSED(int func)) } first = 0; } - ca_xor = cax; ca_parsed = 1; ca_laststate.snext = states; @@ -2602,7 +2625,7 @@ bin_comparguments(char *nam, char **args, UNUSED(Options ops), UNUSED(int func)) * things _arguments has to execute at this place on the line (the * sub-contexts are used as tags). * The return value is particularly important here, it says if - * there are arguments to completely at all. */ + * there are arguments to complete at all. */ { LinkList descr, act, subc; Caarg arg; @@ -2805,7 +2828,7 @@ bin_comparguments(char *nam, char **args, UNUSED(Options ops), UNUSED(int func)) for (s = lstate; s; s = s->snext) for (o = s->d->opts, a = s->oargs; o; o = o->next, a++) if (*a) { - *p++ = (o->set ? tricat(o->set, o->name, "") : + *p++ = (o->gsname ? tricat(o->gsname, o->name, "") : ztrdup(o->name)); *p++ = ca_colonlist(*a); } @@ -3546,8 +3569,8 @@ bin_compvalues(char *nam, char **args, UNUSED(Options ops), UNUSED(int func)) Cvval val = cv_get_val(cv_laststate.d, args[1]); if (val && val->arg) { - setsparam(args[2], val->arg->descr); - setsparam(args[3], val->arg->action); + setsparam(args[2], ztrdup(val->arg->descr)); + setsparam(args[3], ztrdup(val->arg->action)); if (args[4]) setsparam(args[4], ztrdup(val->name)); @@ -3905,6 +3928,8 @@ bin_comptry(char *nam, char **args, UNUSED(Options ops), UNUSED(int func)) if (*q) { char *qq, *qqq; + queue_signals(); + if (c) *c = '\0'; @@ -3976,6 +4001,8 @@ bin_comptry(char *nam, char **args, UNUSED(Options ops), UNUSED(int func)) } if (c) *c = ':'; + + unqueue_signals(); } } if (num) { @@ -4438,17 +4465,24 @@ cfp_matcher_pats(char *matcher, char *add) if (m && m != pcm_err) { char *tmp; int al = strlen(add), zl = ztrlen(add), tl, cl; - VARARR(Cmatcher, ms, zl); + VARARR(Cmatcher, ms, zl); /* One Cmatcher per character */ Cmatcher *mp; Cpattern stopp; int stopl = 0; + /* zl >= (number of wide characters) is guaranteed */ memset(ms, 0, zl * sizeof(Cmatcher)); for (; m && *add; m = m->next) { stopp = NULL; if (!(m->flags & (CMF_LEFT|CMF_RIGHT))) { if (m->llen == 1 && m->wlen == 1) { + /* + * In this loop and similar loops below we step + * through tmp one (possibly wide) character at a + * time. pattern_match() compares only the first + * character using unmeta_one() so keep in step. + */ for (tmp = add, tl = al, mp = ms; tl; ) { if (pattern_match(m->line, tmp, NULL, NULL)) { if (*mp) { @@ -4458,10 +4492,10 @@ cfp_matcher_pats(char *matcher, char *add) } else *mp = m; } - cl = (*tmp == Meta) ? 2 : 1; + (void) unmeta_one(tmp, &cl); tl -= cl; tmp += cl; - mp += cl; + mp++; } } else { stopp = m->line; @@ -4478,10 +4512,10 @@ cfp_matcher_pats(char *matcher, char *add) } else *mp = m; } - cl = (*tmp == Meta) ? 2 : 1; + (void) unmeta_one(tmp, &cl); tl -= cl; tmp += cl; - mp += cl; + mp++; } } else if (m->llen) { stopp = m->line; @@ -4504,7 +4538,7 @@ cfp_matcher_pats(char *matcher, char *add) al = tmp - add; break; } - cl = (*tmp == Meta) ? 2 : 1; + (void) unmeta_one(tmp, &cl); tl -= cl; tmp += cl; } @@ -4685,6 +4719,8 @@ cfp_add_sdirs(LinkList final, LinkList orig, char *skipped, if (!*p) continue; + queue_signals(); /* Protect PAT_STATIC */ + tokenize(f); pprog = patcompile(f, PAT_STATIC, NULL); untokenize(f); @@ -4717,6 +4753,8 @@ cfp_add_sdirs(LinkList final, LinkList orig, char *skipped, } } } + + unqueue_signals(); } } } diff --git a/Src/Zle/textobjects.c b/Src/Zle/textobjects.c index 3db0781ff..bf83906f2 100644 --- a/Src/Zle/textobjects.c +++ b/Src/Zle/textobjects.c @@ -48,9 +48,10 @@ int selectword(UNUSED(char **args)) { int n = zmult; - int all = (bindk == t_selectaword || bindk == t_selectablankword); - int (*viclass)(ZLE_CHAR_T) = (bindk == t_selectaword || - bindk == t_selectinword) ? wordclass : blankwordclass; + int all = IS_THINGY(bindk, selectaword) || + IS_THINGY(bindk, selectablankword); + int (*viclass)(ZLE_CHAR_T) = (IS_THINGY(bindk, selectaword) || + IS_THINGY(bindk, selectinword)) ? wordclass : blankwordclass; int sclass = viclass(zleline[zlecs]); int doblanks = all && sclass; @@ -288,7 +289,7 @@ selectargument(UNUSED(char **args)) free(stringaszleline(linein, wstarts[wcur], &zlecs, &tmpsz, &mark)); free(linein); - if (bindk == t_selectinshellword) { + if (IS_THINGY(bindk, selectinshellword)) { ZLE_CHAR_T *match = ZWS("`\'\""); ZLE_CHAR_T *lmatch = ZWS("\'({"), *rmatch = ZWS("\')}"); ZLE_CHAR_T *ematch = match, *found; diff --git a/Src/Zle/zle.h b/Src/Zle/zle.h index 8f92e5611..07b310180 100644 --- a/Src/Zle/zle.h +++ b/Src/Zle/zle.h @@ -230,6 +230,13 @@ struct thingy { /* DISABLED is (1<<0) */ #define TH_IMMORTAL (1<<1) /* can't refer to a different widget */ +/* + * Check if bindk refers to named thingy (a set of bare characters), + * also checking the special .thingy widget. + */ +#define IS_THINGY(bindk, name) \ + ((bindk) == t_ ## name || (bindk) == t_D ## name) + /* command modifier prefixes */ struct modifier { diff --git a/Src/Zle/zle_hist.c b/Src/Zle/zle_hist.c index abd6e1749..581ca4979 100644 --- a/Src/Zle/zle_hist.c +++ b/Src/Zle/zle_hist.c @@ -1220,13 +1220,14 @@ doisearch(char **args, int dir, int pattern) char *patbuf = ztrdup(sbuf); char *patstring; /* - * Use static pattern buffer since we don't need - * to maintain it and won't call other pattern functions - * meanwhile. - * Use PAT_NOANCH because we don't need the match - * anchored to the end, even if it is at the start. + * Do not use static pattern buffer (PAT_STATIC) since we + * call zle hooks, which might call other pattern + * functions. Use PAT_ZDUP because we re-use the pattern + * in subsequent loops, so we can't pushheap/popheap. + * Use PAT_NOANCH because we don't need the match anchored + * to the end, even if it is at the start. */ - int patflags = PAT_STATIC|PAT_NOANCH; + int patflags = PAT_ZDUP|PAT_NOANCH; if (sbuf[0] == '^') { /* * We'll handle the anchor later when @@ -1521,6 +1522,7 @@ doisearch(char **args, int dir, int pattern) if (only_one || !top_spot || old_sbptr != sbptr) break; } + freepatprog(patprog); patprog = NULL; nosearch = 1; skip_pos = 0; @@ -1632,6 +1634,7 @@ doisearch(char **args, int dir, int pattern) } strcpy(sbuf + sbptr, paste); sbptr += pastelen; + freepatprog(patprog); patprog = NULL; free(paste); } else if (cmd == Th(z_acceptsearch)) { @@ -1682,6 +1685,7 @@ doisearch(char **args, int dir, int pattern) * always valid at this point. */ sbptr += zlecharasstring(LASTFULLCHAR, sbuf + sbptr); + freepatprog(patprog); patprog = NULL; } if (feep) @@ -1702,6 +1706,7 @@ doisearch(char **args, int dir, int pattern) zsfree(okeymap); if (matchlist) freematchlist(matchlist); + freepatprog(patprog); isearch_active = 0; /* * Don't allow unused characters provided as a string to the diff --git a/Src/Zle/zle_keymap.c b/Src/Zle/zle_keymap.c index 04eb70675..2e96ac780 100644 --- a/Src/Zle/zle_keymap.c +++ b/Src/Zle/zle_keymap.c @@ -961,7 +961,7 @@ bin_bindkey_meta(char *name, char *kmname, Keymap km, UNUSED(char **argv), UNUSE m[0] = i; metafy(m, 1, META_NOALLOC); fn = keybind(km, m, &str); - if(fn == t_selfinsert || fn == t_undefinedkey) + if(IS_THINGY(fn, selfinsert) || fn == t_undefinedkey) bindkey(km, m, refthingy(Th(metabind[i - 128])), NULL); } return 0; diff --git a/Src/Zle/zle_main.c b/Src/Zle/zle_main.c index 15ea79643..be2b062b0 100644 --- a/Src/Zle/zle_main.c +++ b/Src/Zle/zle_main.c @@ -1245,6 +1245,7 @@ zleread(char **lp, char **rp, int flags, int context, char *init, char *finish) resetneeded = 0; fetchttyinfo = 0; trashedzle = 0; + clearflag = 0; raw_lp = lp; lpromptbuf = promptexpand(lp ? *lp : NULL, 1, NULL, NULL, &pmpt_attr); raw_rp = rp; @@ -1484,6 +1485,13 @@ execzlefunc(Thingy func, char **args, int set_bindk) int inuse = w->flags & WIDGET_INUSE; w->flags |= WIDGET_INUSE; + if (osi > 0) { + /* + * Many commands don't like having a closed stdin, open on + * /dev/null instead + */ + open("/dev/null", O_RDWR | O_NOCTTY); /* ignore failure */ + } if (*args) { largs = newlinklist(); addlinknode(largs, dupstring(w->u.fnnam)); diff --git a/Src/Zle/zle_params.c b/Src/Zle/zle_params.c index 1e4c5b832..0a922d2d6 100644 --- a/Src/Zle/zle_params.c +++ b/Src/Zle/zle_params.c @@ -85,6 +85,8 @@ static const struct gsu_integer cursor_gsu = { get_cursor, set_cursor, zleunsetfn }; static const struct gsu_integer histno_gsu = { get_histno, set_histno, zleunsetfn }; +static const struct gsu_integer keys_queued_count_gsu = +{ get_keys_queued_count, NULL, zleunsetfn }; static const struct gsu_integer mark_gsu = { get_mark, set_mark, zleunsetfn }; static const struct gsu_integer numeric_gsu = @@ -146,6 +148,8 @@ static struct zleparam { { "HISTNO", PM_INTEGER, GSU(histno_gsu), NULL }, { "KEYMAP", PM_SCALAR | PM_READONLY, GSU(keymap_gsu), NULL }, { "KEYS", PM_SCALAR | PM_READONLY, GSU(keys_gsu), NULL }, + { "KEYS_QUEUED_COUNT", PM_INTEGER | PM_READONLY, GSU(keys_queued_count_gsu), + NULL}, { "killring", PM_ARRAY, GSU(killring_gsu), NULL }, { "LASTABORTEDSEARCH", PM_SCALAR | PM_READONLY, GSU(lastabortedsearch_gsu), NULL }, @@ -458,6 +462,13 @@ get_keys(UNUSED(Param pm)) } /**/ +static zlong +get_keys_queued_count(UNUSED(Param pm)) +{ + return kungetct; +} + +/**/ static void set_numeric(UNUSED(Param pm), zlong x) { diff --git a/Src/Zle/zle_refresh.c b/Src/Zle/zle_refresh.c index 8d173cda1..d0dd1ef06 100644 --- a/Src/Zle/zle_refresh.c +++ b/Src/Zle/zle_refresh.c @@ -1278,7 +1278,7 @@ zrefresh(void) #ifdef __STDC_ISO_10646__ !ZSH_INVALID_WCHAR_TEST(*t) && #endif - iswprint(*t) && (width = WCWIDTH(*t)) > 0) { + WC_ISPRINT(*t) && (width = WCWIDTH(*t)) > 0) { int ichars; if (width > rpms.sen - rpms.s) { int started = 0; @@ -1460,7 +1460,7 @@ zrefresh(void) u = outputline; for (; u < outputline + outll; u++) { #ifdef MULTIBYTE_SUPPORT - if (iswprint(*u)) { + if (WC_ISPRINT(*u)) { int width = WCWIDTH(*u); /* Handle wide characters as above */ if (width > rpms.sen - rpms.s) { @@ -2434,8 +2434,8 @@ redisplay(UNUSED(char **args)) moveto(0, 0); zputc(&zr_cr); /* extra care */ tc_upcurs(lprompth - 1); - resetneeded = !showinglist; - clearflag = showinglist; + resetneeded = 1; + clearflag = 0; return 0; } @@ -2468,7 +2468,7 @@ singlerefresh(ZLE_STRING_T tmpline, int tmpll, int tmpcs) if (tmpline[t0] == ZWC('\t')) vsiz = (vsiz | 7) + 2; #ifdef MULTIBYTE_SUPPORT - else if (iswprint(tmpline[t0]) && ((width = WCWIDTH(tmpline[t0])) > 0)) { + else if (WC_ISPRINT(tmpline[t0]) && ((width = WCWIDTH(tmpline[t0])) > 0)) { vsiz += width; if (isset(COMBININGCHARS) && IS_BASECHAR(tmpline[t0])) { while (t0 < tmpll-1 && IS_COMBINING(tmpline[t0+1])) @@ -2556,7 +2556,7 @@ singlerefresh(ZLE_STRING_T tmpline, int tmpll, int tmpcs) vp->atr = all_atr_on | all_atr_off; vp++; #ifdef MULTIBYTE_SUPPORT - } else if (iswprint(tmpline[t0]) && + } else if (WC_ISPRINT(tmpline[t0]) && (width = WCWIDTH(tmpline[t0])) > 0) { int ichars; if (isset(COMBININGCHARS) && IS_BASECHAR(tmpline[t0])) { diff --git a/Src/Zle/zle_thingy.c b/Src/Zle/zle_thingy.c index c7092854a..f7e9829c2 100644 --- a/Src/Zle/zle_thingy.c +++ b/Src/Zle/zle_thingy.c @@ -602,7 +602,7 @@ bin_zle_complete(char *name, char **args, UNUSED(Options ops), UNUSED(char func) Thingy t; Widget w, cw; - if (require_module("zsh/complete", NULL) == 1) { + if (require_module("zsh/complete", NULL, 0) == 1) { zwarnnam(name, "can't load complete module"); return 1; } @@ -703,7 +703,7 @@ bin_zle_call(char *name, char **args, UNUSED(Options ops), UNUSED(char func)) { Thingy t; struct modifier modsave = zmod; - int ret, saveflag = 0, setbindk = 0; + int ret, saveflag = 0, setbindk = 0, remetafy; char *wname = *args++, *keymap_restore = NULL, *keymap_tmp; if (!wname) @@ -714,7 +714,15 @@ bin_zle_call(char *name, char **args, UNUSED(Options ops), UNUSED(char func)) return 1; } - UNMETACHECK(); + /* + * zle is callable in traps, so we can't be sure the line is + * in its normal state. + */ + if (zlemetaline) { + unmetafy_line(); + remetafy = 1; + } else + remetafy = 0; while (*args && **args == '-') { char *num; @@ -728,6 +736,8 @@ bin_zle_call(char *name, char **args, UNUSED(Options ops), UNUSED(char func)) num = args[0][1] ? args[0]+1 : args[1]; if (!num) { zwarnnam(name, "number expected after -%c", **args); + if (remetafy) + metafy_line(); return 1; } if (!args[0][1]) @@ -745,19 +755,26 @@ bin_zle_call(char *name, char **args, UNUSED(Options ops), UNUSED(char func)) keymap_tmp = args[0][1] ? args[0]+1 : args[1]; if (!keymap_tmp) { zwarnnam(name, "keymap expected after -%c", **args); + if (remetafy) + metafy_line(); return 1; } if (!args[0][1]) *++args = "" - 1; keymap_restore = dupstring(curkeymapname); - if (selectkeymap(keymap_tmp, 0)) + if (selectkeymap(keymap_tmp, 0)) { + if (remetafy) + metafy_line(); return 1; + } break; case 'w': setbindk = 1; break; default: zwarnnam(name, "unknown option: %s", *args); + if (remetafy) + metafy_line(); return 1; } } @@ -775,6 +792,8 @@ bin_zle_call(char *name, char **args, UNUSED(Options ops), UNUSED(char func)) zmod = modsave; if (keymap_restore) selectkeymap(keymap_restore, 0); + if (remetafy) + metafy_line(); return ret; } diff --git a/Src/Zle/zle_tricky.c b/Src/Zle/zle_tricky.c index 3d8679119..5a9cccb6f 100644 --- a/Src/Zle/zle_tricky.c +++ b/Src/Zle/zle_tricky.c @@ -2407,53 +2407,6 @@ sfxlen(char *s, char *t) } #endif -/* This is zstrcmp with ignoring backslashes. */ - -/**/ -mod_export int -zstrbcmp(const char *a, const char *b) -{ - const char *astart = a; - - while (*a && *b) { - if (*a == '\\') - a++; - if (*b == '\\') - b++; - if (*a != *b || !*a) - break; - a++; - b++; - } - if (isset(NUMERICGLOBSORT) && (idigit(*a) || idigit(*b))) { - for (; a > astart && idigit(a[-1]); a--, b--); - if (idigit(*a) && idigit(*b)) { - while (*a == '0') - a++; - while (*b == '0') - b++; - for (; idigit(*a) && *a == *b; a++, b++); - if (idigit(*a) || idigit(*b)) { - int cmp = (int) STOUC(*a) - (int) STOUC(*b); - - while (idigit(*a) && idigit(*b)) - a++, b++; - if (idigit(*a) && !idigit(*b)) - return 1; - if (idigit(*b) && !idigit(*a)) - return -1; - - return cmp; - } - } - } -#ifndef HAVE_STRCOLL - return (int)(*a - *b); -#else - return strcoll(a,b); -#endif -} - /* This is used to print the strings (e.g. explanations). * * It returns the number of lines printed. */ diff --git a/Src/builtin.c b/Src/builtin.c index 0f04d149f..2e72ba20a 100644 --- a/Src/builtin.c +++ b/Src/builtin.c @@ -46,7 +46,7 @@ static struct builtin builtins[] = BUILTIN(".", BINF_PSPECIAL, bin_dot, 1, -1, 0, NULL, NULL), BUILTIN(":", BINF_PSPECIAL, bin_true, 0, -1, 0, NULL, NULL), BUILTIN("alias", BINF_MAGICEQUALS | BINF_PLUSOPTS, bin_alias, 0, -1, 0, "Lgmrs", NULL), - BUILTIN("autoload", BINF_PLUSOPTS, bin_functions, 0, -1, 0, "mktTUwXz", "u"), + BUILTIN("autoload", BINF_PLUSOPTS, bin_functions, 0, -1, 0, "dmktrRTUwWXz", "u"), BUILTIN("bg", 0, bin_fg, 0, -1, BIN_BG, NULL, NULL), BUILTIN("break", BINF_PSPECIAL, bin_break, 0, 1, BIN_BREAK, NULL, NULL), BUILTIN("bye", 0, bin_break, 0, 1, BIN_EXIT, NULL, NULL), @@ -72,7 +72,7 @@ static struct builtin builtins[] = BUILTIN("fc", 0, bin_fc, 0, -1, BIN_FC, "aAdDe:EfiIlLmnpPrRt:W", NULL), BUILTIN("fg", 0, bin_fg, 0, -1, BIN_FG, NULL, NULL), BUILTIN("float", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "E:%F:%HL:%R:%Z:%ghlprtux", "E"), - BUILTIN("functions", BINF_PLUSOPTS, bin_functions, 0, -1, 0, "kmMtTuUx:z", NULL), + BUILTIN("functions", BINF_PLUSOPTS, bin_functions, 0, -1, 0, "kmMstTuUWx:z", NULL), BUILTIN("getln", 0, bin_read, 0, -1, 0, "ecnAlE", "zr"), BUILTIN("getopts", 0, bin_getopts, 2, -1, 0, NULL, NULL), BUILTIN("hash", BINF_MAGICEQUALS, bin_hash, 0, -1, 0, "Ldfmrv", NULL), @@ -131,7 +131,7 @@ static struct builtin builtins[] = BUILTIN("whence", 0, bin_whence, 0, -1, 0, "acmpvfsSwx:", NULL), BUILTIN("where", 0, bin_whence, 0, -1, 0, "pmsSwx:", "ca"), BUILTIN("which", 0, bin_whence, 0, -1, 0, "ampsSwx:", "c"), - BUILTIN("zmodload", 0, bin_zmodload, 0, -1, 0, "AFRILP:abcfdilmpue", NULL), + BUILTIN("zmodload", 0, bin_zmodload, 0, -1, 0, "AFRILP:abcfdilmpsue", NULL), BUILTIN("zcompile", 0, bin_zcompile, 0, -1, 0, "tUMRcmzka", NULL), }; @@ -539,18 +539,18 @@ bin_enable(char *name, char **argv, Options ops, int func) /* With -m option, treat arguments as glob patterns. */ if (OPT_ISSET(ops,'m')) { for (; *argv; argv++) { + queue_signals(); + /* parse pattern */ tokenize(*argv); - if ((pprog = patcompile(*argv, PAT_STATIC, 0))) { - queue_signals(); + if ((pprog = patcompile(*argv, PAT_STATIC, 0))) match += scanmatchtable(ht, pprog, 0, 0, 0, scanfunc, 0); - unqueue_signals(); - } else { untokenize(*argv); zwarnnam(name, "bad pattern : %s", *argv); returnval = 1; } + unqueue_signals(); } /* If we didn't match anything, we return 1. */ if (!match) @@ -796,8 +796,8 @@ set_pwd_env(void) unsetparam_pm(pm, 0, 1); } - setsparam("PWD", ztrdup(pwd)); - setsparam("OLDPWD", ztrdup(oldpwd)); + assignsparam("PWD", ztrdup(pwd), 0); + assignsparam("OLDPWD", ztrdup(oldpwd), 0); pm = (Param) paramtab->getnode(paramtab, "PWD"); if (!(pm->node.flags & PM_EXPORTED)) @@ -880,8 +880,13 @@ cd_get_dest(char *nam, char **argv, int hard, int func) dir = nextnode(firstnode(dirstack)); if (dir) zinsertlinknode(dirstack, dir, getlinknode(dirstack)); - else if (func != BIN_POPD) + else if (func != BIN_POPD) { + if (!home) { + zwarnnam(nam, "HOME not set"); + return NULL; + } zpushnode(dirstack, ztrdup(home)); + } } else if (!argv[1]) { int dd; char *end; @@ -936,6 +941,10 @@ cd_get_dest(char *nam, char **argv, int hard, int func) if (!dir) { dir = firstnode(dirstack); } + if (!dir || !getdata(dir)) { + DPUTS(1, "Directory not set, not detected early enough"); + return NULL; + } if (!(dest = cd_do_chdir(nam, getdata(dir), hard))) { if (!target) zsfree(getlinknode(dirstack)); @@ -2922,9 +2931,61 @@ eval_autoload(Shfunc shf, char *name, Options ops, int func) } return !loadautofn(shf, (OPT_ISSET(ops,'k') ? 2 : - (OPT_ISSET(ops,'z') ? 0 : 1)), 1); + (OPT_ISSET(ops,'z') ? 0 : 1)), 1, + OPT_ISSET(ops,'d')); } +/* Helper for bin_functions() for -X and -r options */ + +/**/ +static int +check_autoload(Shfunc shf, char *name, Options ops, int func) +{ + if (OPT_ISSET(ops,'X')) + { + return eval_autoload(shf, name, ops, func); + } + if ((OPT_ISSET(ops,'r') || OPT_ISSET(ops,'R')) && + (shf->node.flags & PM_UNDEFINED)) + { + char *dir_path; + if (shf->filename && (shf->node.flags & PM_LOADDIR)) { + char *spec_path[2]; + spec_path[0] = shf->filename; + spec_path[1] = NULL; + if (getfpfunc(shf->node.nam, NULL, &dir_path, spec_path, 1)) { + /* shf->filename is already correct. */ + return 0; + } + if (!OPT_ISSET(ops,'d')) { + if (OPT_ISSET(ops,'R')) { + zerr("%s: function definition file not found", + shf->node.nam); + return 1; + } + return 0; + } + } + if (getfpfunc(shf->node.nam, NULL, &dir_path, NULL, 1)) { + dircache_set(&shf->filename, NULL); + if (*dir_path != '/') { + dir_path = zhtricat(metafy(zgetcwd(), -1, META_HEAPDUP), + "/", dir_path); + dir_path = xsymlink(dir_path, 1); + } + dircache_set(&shf->filename, dir_path); + shf->node.flags |= PM_LOADDIR; + return 0; + } + if (OPT_ISSET(ops,'R')) { + zerr("%s: function definition file not found", + shf->node.nam); + return 1; + } + /* with -r, we don't flag an error, just let it be found later. */ + } + return 0; +} /* List a user-defined math function. */ static void @@ -2941,7 +3002,7 @@ listusermathfunc(MathFunc p) else showargs = 0; - printf("functions -M %s", p->name); + printf("functions -M%s %s", (p->flags & MFF_STR) ? "s" : "", p->name); if (showargs) { printf(" %d", p->minargs); showargs--; @@ -2962,6 +3023,66 @@ listusermathfunc(MathFunc p) } +static void +add_autoload_function(Shfunc shf, char *funcname) +{ + char *nam; + if (*funcname == '/' && funcname[1] && + (nam = strrchr(funcname, '/')) && nam[1] && + (shf->node.flags & PM_UNDEFINED)) { + char *dir; + nam = strrchr(funcname, '/'); + if (nam == funcname) { + dir = "/"; + } else { + *nam++ = '\0'; + dir = funcname; + } + dircache_set(&shf->filename, NULL); + dircache_set(&shf->filename, dir); + shf->node.flags |= PM_LOADDIR; + shf->node.flags |= PM_ABSPATH_USED; + shfunctab->addnode(shfunctab, ztrdup(nam), shf); + } else { + Shfunc shf2; + Funcstack fs; + const char *calling_f = NULL; + char buf[PATH_MAX+1]; + + /* Find calling function */ + for (fs = funcstack; fs; fs = fs->prev) { + if (fs->tp == FS_FUNC && fs->name && (!shf->node.nam || 0 != strcmp(fs->name,shf->node.nam))) { + calling_f = fs->name; + break; + } + } + + /* Get its directory */ + if (calling_f) { + /* Should contain load directory, and be loaded via absolute path */ + if ((shf2 = (Shfunc) shfunctab->getnode2(shfunctab, calling_f)) + && (shf2->node.flags & PM_LOADDIR) && (shf2->node.flags & PM_ABSPATH_USED) + && shf2->filename) + { + if (strlen(shf2->filename) + strlen(funcname) + 1 < PATH_MAX) + { + sprintf(buf, "%s/%s", shf2->filename, funcname); + /* Set containing directory if the function file + * exists (do normal FPATH processing otherwise) */ + if (!access(buf, R_OK)) { + dircache_set(&shf->filename, NULL); + dircache_set(&shf->filename, shf2->filename); + shf->node.flags |= PM_LOADDIR; + shf->node.flags |= PM_ABSPATH_USED; + } + } + } + } + + shfunctab->addnode(shfunctab, ztrdup(funcname), shf); + } +} + /* Display or change the attributes of shell functions. * * If called as autoload, it will define a new autoloaded * * (undefined) shell function. */ @@ -2992,6 +3113,10 @@ bin_functions(char *name, char **argv, Options ops, int func) on |= PM_TAGGED_LOCAL; else if (OPT_PLUS(ops,'T')) off |= PM_TAGGED_LOCAL; + if (OPT_MINUS(ops,'W')) + on |= PM_WARNNESTED; + else if (OPT_PLUS(ops,'W')) + off |= PM_WARNNESTED; roff = off; if (OPT_MINUS(ops,'z')) { on |= PM_ZSHSTORED; @@ -3007,10 +3132,17 @@ bin_functions(char *name, char **argv, Options ops, int func) off |= PM_KSHSTORED; roff |= PM_KSHSTORED; } + if (OPT_MINUS(ops,'d')) { + on |= PM_CUR_FPATH; + off |= PM_CUR_FPATH; + } else if (OPT_PLUS(ops,'d')) { + off |= PM_CUR_FPATH; + roff |= PM_CUR_FPATH; + } if ((off & PM_UNDEFINED) || (OPT_ISSET(ops,'k') && OPT_ISSET(ops,'z')) || (OPT_ISSET(ops,'x') && !OPT_HASARG(ops,'x')) || - (OPT_MINUS(ops,'X') && (OPT_ISSET(ops,'m') || *argv || !scriptname))) { + (OPT_MINUS(ops,'X') && (OPT_ISSET(ops,'m') || !scriptname))) { zwarnnam(name, "invalid option(s)"); return 1; } @@ -3049,9 +3181,9 @@ bin_functions(char *name, char **argv, Options ops, int func) } else if (OPT_ISSET(ops,'m')) { /* List matching functions. */ for (; *argv; argv++) { + queue_signals(); tokenize(*argv); if ((pprog = patcompile(*argv, PAT_STATIC, 0))) { - queue_signals(); for (p = mathfuncs, q = NULL; p; q = p) { MathFunc next; do { @@ -3070,12 +3202,12 @@ bin_functions(char *name, char **argv, Options ops, int func) if (p) p = p->next; } - unqueue_signals(); } else { untokenize(*argv); zwarnnam(name, "bad pattern : %s", *argv); returnval = 1; } + unqueue_signals(); } } else if (OPT_PLUS(ops,'M')) { /* Delete functions. -m is allowed but is handled above. */ @@ -3097,11 +3229,18 @@ bin_functions(char *name, char **argv, Options ops, int func) } } else { /* Add a function */ - int minargs = 0, maxargs = -1; + int minargs, maxargs; char *funcname = *argv++; char *modname = NULL; char *ptr; + if (OPT_ISSET(ops,'s')) { + minargs = maxargs = 1; + } else { + minargs = 0; + maxargs = -1; + } + ptr = itype_end(funcname, IIDENT, 0); if (idigit(*funcname) || funcname == ptr || *ptr) { zwarnnam(name, "-M %s: bad math function name", funcname); @@ -3115,6 +3254,10 @@ bin_functions(char *name, char **argv, Options ops, int func) *argv); return 1; } + if (OPT_ISSET(ops,'s') && minargs != 1) { + zwarnnam(name, "-Ms: must take a single string argument"); + return 1; + } maxargs = minargs; argv++; } @@ -3128,6 +3271,10 @@ bin_functions(char *name, char **argv, Options ops, int func) *argv); return 1; } + if (OPT_ISSET(ops,'s') && maxargs != 1) { + zwarnnam(name, "-Ms: must take a single string argument"); + return 1; + } argv++; } if (*argv) @@ -3140,6 +3287,8 @@ bin_functions(char *name, char **argv, Options ops, int func) p = (MathFunc)zshcalloc(sizeof(struct mathfunc)); p->name = ztrdup(funcname); p->flags = MFF_USERFUNC; + if (OPT_ISSET(ops,'s')) + p->flags |= MFF_STR; p->module = modname ? ztrdup(modname) : NULL; p->minargs = minargs; p->maxargs = maxargs; @@ -3165,47 +3314,58 @@ bin_functions(char *name, char **argv, Options ops, int func) return returnval; } - /* If no arguments given, we will print functions. If flags * - * are given, we will print only functions containing these * - * flags, else we'll print them all. */ - if (!*argv) { - int ret = 0; - + if (OPT_MINUS(ops,'X')) { + Funcstack fs; + char *funcname = NULL; + int ret; + if (*argv && argv[1]) { + zwarnnam(name, "-X: too many arguments"); + return 1; + } queue_signals(); - if (OPT_MINUS(ops,'X')) { - Funcstack fs; - char *funcname = NULL; - for (fs = funcstack; fs; fs = fs->prev) { - if (fs->tp == FS_FUNC) { - /* - * dupstring here is paranoia but unlikely to be - * problematic - */ - funcname = dupstring(fs->name); - break; - } + for (fs = funcstack; fs; fs = fs->prev) { + if (fs->tp == FS_FUNC) { + /* + * dupstring here is paranoia but unlikely to be + * problematic + */ + funcname = dupstring(fs->name); + break; } - if (!funcname) - { - zerrnam(name, "bad autoload"); - ret = 1; + } + if (!funcname) + { + zerrnam(name, "bad autoload"); + ret = 1; + } else { + if ((shf = (Shfunc) shfunctab->getnode(shfunctab, funcname))) { + DPUTS(!shf->funcdef, + "BUG: Calling autoload from empty function"); } else { - if ((shf = (Shfunc) shfunctab->getnode(shfunctab, funcname))) { - DPUTS(!shf->funcdef, - "BUG: Calling autoload from empty function"); - } else { - shf = (Shfunc) zshcalloc(sizeof *shf); - shfunctab->addnode(shfunctab, ztrdup(funcname), shf); - } - shf->node.flags = on; - ret = eval_autoload(shf, funcname, ops, func); + shf = (Shfunc) zshcalloc(sizeof *shf); + shfunctab->addnode(shfunctab, ztrdup(funcname), shf); } - } else { - if (OPT_ISSET(ops,'U') && !OPT_ISSET(ops,'u')) + if (*argv) { + dircache_set(&shf->filename, NULL); + dircache_set(&shf->filename, *argv); + on |= PM_LOADDIR; + } + shf->node.flags = on; + ret = eval_autoload(shf, funcname, ops, func); + } + unqueue_signals(); + return ret; + } else if (!*argv) { + /* If no arguments given, we will print functions. If flags * + * are given, we will print only functions containing these * + * flags, else we'll print them all. */ + int ret = 0; + + queue_signals(); + if (OPT_ISSET(ops,'U') && !OPT_ISSET(ops,'u')) on &= ~PM_UNDEFINED; scanshfunc(1, on|off, DISABLED, shfunctab->printnode, pflags, expand); - } unqueue_signals(); return ret; } @@ -3214,11 +3374,11 @@ bin_functions(char *name, char **argv, Options ops, int func) if (OPT_ISSET(ops,'m')) { on &= ~PM_UNDEFINED; for (; *argv; argv++) { + queue_signals(); /* expand argument */ tokenize(*argv); if ((pprog = patcompile(*argv, PAT_STATIC, 0))) { /* with no options, just print all functions matching the glob pattern */ - queue_signals(); if (!(on|off) && !OPT_ISSET(ops,'X')) { scanmatchshfunc(pprog, 1, 0, DISABLED, shfunctab->printnode, pflags, expand); @@ -3231,19 +3391,19 @@ bin_functions(char *name, char **argv, Options ops, int func) !(shf->node.flags & DISABLED)) { shf->node.flags = (shf->node.flags | (on & ~PM_UNDEFINED)) & ~off; - if (OPT_ISSET(ops,'X') && - eval_autoload(shf, shf->node.nam, ops, func)) { + if (check_autoload(shf, shf->node.nam, + ops, func)) { returnval = 1; } } } } - unqueue_signals(); } else { untokenize(*argv); zwarnnam(name, "bad pattern : %s", *argv); returnval = 1; } + unqueue_signals(); } return returnval; } @@ -3258,8 +3418,7 @@ bin_functions(char *name, char **argv, Options ops, int func) if (on|off) { /* turn on/off the given flags */ shf->node.flags = (shf->node.flags | (on & ~PM_UNDEFINED)) & ~off; - if (OPT_ISSET(ops,'X') && - eval_autoload(shf, shf->node.nam, ops, func)) + if (check_autoload(shf, shf->node.nam, ops, func)) returnval = 1; } else /* no flags, so just print */ @@ -3276,13 +3435,38 @@ bin_functions(char *name, char **argv, Options ops, int func) removetrapnode(signum); } + if (**argv == '/') { + char *base = strrchr(*argv, '/') + 1; + if (*base && + (shf = (Shfunc) shfunctab->getnode(shfunctab, base))) { + char *dir; + /* turn on/off the given flags */ + shf->node.flags = + (shf->node.flags | (on & ~PM_UNDEFINED)) & ~off; + if (shf->node.flags & PM_UNDEFINED) { + /* update path if not yet loaded */ + if (base == *argv + 1) + dir = "/"; + else { + dir = *argv; + base[-1] = '\0'; + } + dircache_set(&shf->filename, NULL); + dircache_set(&shf->filename, dir); + } + if (check_autoload(shf, shf->node.nam, ops, func)) + returnval = 1; + continue; + } + } + /* Add a new undefined (autoloaded) function to the * * hash table with the corresponding flags set. */ shf = (Shfunc) zshcalloc(sizeof *shf); shf->node.flags = on; shf->funcdef = mkautofn(shf); shfunc_set_sticky(shf); - shfunctab->addnode(shfunctab, ztrdup(*argv), shf); + add_autoload_function(shf, *argv); if (signum != -1) { if (settrap(signum, NULL, ZSIG_FUNC)) { @@ -3293,8 +3477,7 @@ bin_functions(char *name, char **argv, Options ops, int func) } } - if (ok && OPT_ISSET(ops,'X') && - eval_autoload(shf, shf->node.nam, ops, func)) + if (ok && check_autoload(shf, shf->node.nam, ops, func)) returnval = 1; } else returnval = 1; @@ -3348,11 +3531,11 @@ bin_unset(char *name, char **argv, Options ops, int func) /* with -m option, treat arguments as glob patterns */ if (OPT_ISSET(ops,'m')) { while ((s = *argv++)) { + queue_signals(); /* expand */ tokenize(s); if ((pprog = patcompile(s, PAT_STATIC, NULL))) { /* Go through the parameter table, and unset any matches */ - queue_signals(); for (i = 0; i < paramtab->hsize; i++) { for (pm = (Param) paramtab->nodes[i]; pm; pm = next) { /* record pointer to next, since we may free this one */ @@ -3365,12 +3548,12 @@ bin_unset(char *name, char **argv, Options ops, int func) } } } - unqueue_signals(); } else { untokenize(s); zwarnnam(name, "bad pattern : %s", s); returnval = 1; } + unqueue_signals(); } /* If we didn't match anything, we return 1. */ if (!match) @@ -3534,6 +3717,7 @@ bin_whence(char *nam, char **argv, Options ops, int func) pushheap(); matchednodes = newlinklist(); } + queue_signals(); for (; *argv; argv++) { /* parse the pattern */ tokenize(*argv); @@ -3543,7 +3727,6 @@ bin_whence(char *nam, char **argv, Options ops, int func) returnval = 1; continue; } - queue_signals(); if (!OPT_ISSET(ops,'p')) { /* -p option is for path search only. * * We're not using it, so search for ... */ @@ -3574,9 +3757,9 @@ bin_whence(char *nam, char **argv, Options ops, int func) scanmatchtable(cmdnamtab, pprog, 1, 0, 0, (all ? fetchcmdnamnode : cmdnamtab->printnode), printflags); - - unqueue_signals(); + run_queued_signals(); } + unqueue_signals(); if (all) { allmatched = argv = zlinklist2array(matchednodes); matchednodes = NULL; @@ -3653,9 +3836,11 @@ bin_whence(char *nam, char **argv, Options ops, int func) if (wd) { printf("%s: command\n", *argv); } else { - if (v && !csh) + if (v && !csh) { zputs(*argv, stdout), fputs(" is ", stdout); - zputs(buf, stdout); + quotedzputs(buf, stdout); + } else + zputs(buf, stdout); if (OPT_ISSET(ops,'s') || OPT_ISSET(ops, 'S')) print_if_link(buf, OPT_ISSET(ops, 'S')); fputc('\n', stdout); @@ -3685,9 +3870,11 @@ bin_whence(char *nam, char **argv, Options ops, int func) if (wd) { printf("%s: command\n", *argv); } else { - if (v && !csh) + if (v && !csh) { zputs(*argv, stdout), fputs(" is ", stdout); - zputs(cnam, stdout); + quotedzputs(cnam, stdout); + } else + zputs(cnam, stdout); if (OPT_ISSET(ops,'s') || OPT_ISSET(ops,'S')) print_if_link(cnam, OPT_ISSET(ops,'S')); fputc('\n', stdout); @@ -3899,11 +4086,11 @@ bin_unhash(char *name, char **argv, Options ops, int func) * "unhash -m '*'" is legal, but not recommended. */ if (OPT_ISSET(ops,'m')) { for (; *argv; argv++) { + queue_signals(); /* expand argument */ tokenize(*argv); if ((pprog = patcompile(*argv, PAT_STATIC, NULL))) { /* remove all nodes matching glob pattern */ - queue_signals(); for (i = 0; i < ht->hsize; i++) { for (hn = ht->nodes[i]; hn; hn = nhn) { /* record pointer to next, since we may free this one */ @@ -3914,12 +4101,12 @@ bin_unhash(char *name, char **argv, Options ops, int func) } } } - unqueue_signals(); } else { untokenize(*argv); zwarnnam(name, "bad pattern : %s", *argv); returnval = 1; } + unqueue_signals(); } /* If we didn't match anything, we return 1. */ if (!match) @@ -4002,18 +4189,18 @@ bin_alias(char *name, char **argv, Options ops, UNUSED(int func)) * glob patterns of aliases to display. */ if (OPT_ISSET(ops,'m')) { for (; *argv; argv++) { + queue_signals(); tokenize(*argv); /* expand argument */ if ((pprog = patcompile(*argv, PAT_STATIC, NULL))) { /* display the matching aliases */ - queue_signals(); scanmatchtable(ht, pprog, 1, flags1, flags2, ht->printnode, printflags); - unqueue_signals(); } else { untokenize(*argv); zwarnnam(name, "bad pattern : %s", *argv); returnval = 1; } + unqueue_signals(); } return returnval; } @@ -4223,10 +4410,12 @@ bin_print(char *name, char **args, Options ops, int func) zwarnnam(name, "no pattern specified"); return 1; } + queue_signals(); tokenize(*args); if (!(pprog = patcompile(*args, PAT_STATIC, NULL))) { untokenize(*args); zwarnnam(name, "bad pattern: %s", *args); + unqueue_signals(); return 1; } for (t = p = ++args; *p; p++) @@ -4234,6 +4423,7 @@ bin_print(char *name, char **args, Options ops, int func) *t++ = *p; *t = NULL; first = args; + unqueue_signals(); if (fmt && !*args) return 0; } /* compute lengths, and interpret according to -P, -D, -e, etc. */ @@ -5319,7 +5509,7 @@ bin_break(char *name, char **argv, UNUSED(Options ops), int func) } /*FALLTHROUGH*/ case BIN_EXIT: - if (locallevel > forklevel) { + if (locallevel > forklevel && shell_exiting != -1) { /* * We don't exit directly from functions to allow tidying * up, in particular EXIT traps. We still need to perform @@ -5328,6 +5518,9 @@ bin_break(char *name, char **argv, UNUSED(Options ops), int func) * * If we are forked, we exit the shell at the function depth * at which we became a subshell, hence the comparison. + * + * If we are already exiting... give this all up as + * a bad job. */ if (stopmsg || (zexit(0,2), !stopmsg)) { retflag = 1; @@ -5374,6 +5567,14 @@ checkjobs(void) } } +/* + * -1 if the shell is already committed to exit. + * positive if zexit() was already called. + */ + +/**/ +int shell_exiting; + /* exit the shell. val is the return value of the shell. * * from_where is * 1 if zexit is called because of a signal @@ -5385,10 +5586,8 @@ checkjobs(void) mod_export void zexit(int val, int from_where) { - static int in_exit; - /* Don't do anything recursively: see below */ - if (in_exit == -1) + if (shell_exiting == -1) return; if (isset(MONITOR) && !stopmsg && from_where != 1) { @@ -5401,14 +5600,14 @@ zexit(int val, int from_where) } } /* Positive in_exit means we have been here before */ - if (from_where == 2 || (in_exit++ && from_where)) + if (from_where == 2 || (shell_exiting++ && from_where)) return; /* - * We're now committed to exiting. Set in_exit to -1 to + * We're now committed to exiting. Set shell_exiting to -1 to * indicate we shouldn't do any recursive processing. */ - in_exit = -1; + shell_exiting = -1; /* * We want to do all remaining processing regardless of preceding * errors, even user interrupts. diff --git a/Src/compat.c b/Src/compat.c index a2956946f..a130d9264 100644 --- a/Src/compat.c +++ b/Src/compat.c @@ -672,7 +672,7 @@ strtoul(nptr, endptr, base) /**/ int -mk_wcwidth(wchar_t ucs) +u9_wcwidth(wchar_t ucs) { int w = wcwidth9(ucs); if (w < -1) @@ -681,326 +681,16 @@ mk_wcwidth(wchar_t ucs) } /**/ -#elif defined(BROKEN_WCWIDTH) && (defined(__STDC_ISO_10646__) || defined(__APPLE__)) - -/* - * This is an implementation of wcwidth() and wcswidth() (defined in - * IEEE Std 1002.1-2001) for Unicode. - * - * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html - * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html - * - * In fixed-width output devices, Latin characters all occupy a single - * "cell" position of equal width, whereas ideographic CJK characters - * occupy two such cells. Interoperability between terminal-line - * applications and (teletype-style) character terminals using the - * UTF-8 encoding requires agreement on which character should advance - * the cursor by how many cell positions. No established formal - * standards exist at present on which Unicode character shall occupy - * how many cell positions on character terminals. These routines are - * a first attempt of defining such behavior based on simple rules - * applied to data provided by the Unicode Consortium. - * - * For some graphical characters, the Unicode standard explicitly - * defines a character-cell width via the definition of the East Asian - * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes. - * In all these cases, there is no ambiguity about which width a - * terminal shall use. For characters in the East Asian Ambiguous (A) - * class, the width choice depends purely on a preference of backward - * compatibility with either historic CJK or Western practice. - * Choosing single-width for these characters is easy to justify as - * the appropriate long-term solution, as the CJK practice of - * displaying these characters as double-width comes from historic - * implementation simplicity (8-bit encoded characters were displayed - * single-width and 16-bit ones double-width, even for Greek, - * Cyrillic, etc.) and not any typographic considerations. - * - * Much less clear is the choice of width for the Not East Asian - * (Neutral) class. Existing practice does not dictate a width for any - * of these characters. It would nevertheless make sense - * typographically to allocate two character cells to characters such - * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be - * represented adequately with a single-width glyph. The following - * routines at present merely assign a single-cell width to all - * neutral characters, in the interest of simplicity. This is not - * entirely satisfactory and should be reconsidered before - * establishing a formal standard in this area. At the moment, the - * decision which Not East Asian (Neutral) characters should be - * represented by double-width glyphs cannot yet be answered by - * applying a simple rule from the Unicode database content. Setting - * up a proper standard for the behavior of UTF-8 character terminals - * will require a careful analysis not only of each Unicode character, - * but also of each presentation form, something the author of these - * routines has avoided to do so far. - * - * http://www.unicode.org/unicode/reports/tr11/ - * - * Markus Kuhn -- 2007-05-26 (Unicode 5.0) - * - * Permission to use, copy, modify, and distribute this software - * for any purpose and without fee is hereby granted. The author - * disclaims all warranties with regard to this software. - * - * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c - */ - -struct interval { - int first; - int last; -}; - -/* auxiliary function for binary search in interval table */ -static int bisearch(wchar_t ucs, const struct interval *table, int max) { - int min = 0; - int mid; - - if (ucs < table[0].first || ucs > table[max].last) - return 0; - while (max >= min) { - mid = (min + max) / 2; - if (ucs > table[mid].last) - min = mid + 1; - else if (ucs < table[mid].first) - max = mid - 1; - else - return 1; - } - - return 0; -} - - -/* The following two functions define the column width of an ISO 10646 - * character as follows: - * - * - The null character (U+0000) has a column width of 0. - * - * - Other C0/C1 control characters and DEL will lead to a return - * value of -1. - * - * - Non-spacing and enclosing combining characters (general - * category code Mn or Me in the Unicode database) have a - * column width of 0. - * - * - SOFT HYPHEN (U+00AD) has a column width of 1. - * - * - Other format characters (general category code Cf in the Unicode - * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0. - * - * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF) - * have a column width of 0. - * - * - Spacing characters in the East Asian Wide (W) or East Asian - * Full-width (F) category as defined in Unicode Technical - * Report #11 have a column width of 2. - * - * - All remaining characters (including all printable - * ISO 8859-1 and WGL4 characters, Unicode control characters, - * etc.) have a column width of 1. - * - * This implementation assumes that wchar_t characters are encoded - * in ISO 10646. - */ - -/**/ int -mk_wcwidth(wchar_t ucs) -{ - /* sorted list of non-overlapping intervals of non-spacing characters */ - /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */ - static const struct interval combining[] = { - { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 }, - { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, - { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 }, - { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 }, - { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, - { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A }, - { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 }, - { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D }, - { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, - { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD }, - { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C }, - { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D }, - { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC }, - { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD }, - { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C }, - { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D }, - { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 }, - { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 }, - { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC }, - { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD }, - { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D }, - { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, - { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, - { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, - { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, - { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, - { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 }, - { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, - { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 }, - { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F }, - { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, - { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, - { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, - { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, - { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B }, - { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 }, - { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 }, - { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF }, - { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 }, - { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F }, - { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B }, - { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, - { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }, - { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F }, - { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 }, - { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD }, - { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F }, - { 0xE0100, 0xE01EF } - }; - - /* test for 8-bit control characters */ - if (ucs == 0) - return 0; - if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) - return -1; - - /* binary search in table of non-spacing characters */ - if (bisearch(ucs, combining, - sizeof(combining) / sizeof(struct interval) - 1)) - return 0; - - /* if we arrive here, ucs is not a combining or C0/C1 control character */ - - return 1 + - (ucs >= 0x1100 && - (ucs <= 0x115f || /* Hangul Jamo init. consonants */ - ucs == 0x2329 || ucs == 0x232a || - (ucs >= 0x2e80 && ucs <= 0xa4cf && - ucs != 0x303f) || /* CJK ... Yi */ - (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */ - (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */ - (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */ - (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */ - (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */ - (ucs >= 0xffe0 && ucs <= 0xffe6) || - (ucs >= 0x20000 && ucs <= 0x2fffd) || - (ucs >= 0x30000 && ucs <= 0x3fffd))); -} - - -/* - * The following functions are part of the original wcwidth.c: - * we don't use them but I've kept them in case - pws. - */ -#if 0 -int mk_wcswidth(const wchar_t *pwcs, size_t n) -{ - int w, width = 0; - - for (;*pwcs && n-- > 0; pwcs++) - if ((w = mk_wcwidth(*pwcs)) < 0) - return -1; - else - width += w; - - return width; -} - - -/* - * The following functions are the same as mk_wcwidth() and - * mk_wcswidth(), except that spacing characters in the East Asian - * Ambiguous (A) category as defined in Unicode Technical Report #11 - * have a column width of 2. This variant might be useful for users of - * CJK legacy encodings who want to migrate to UCS without changing - * the traditional terminal character-width behaviour. It is not - * otherwise recommended for general use. - */ -int mk_wcwidth_cjk(wchar_t ucs) +u9_iswprint(wint_t ucs) { - /* sorted list of non-overlapping intervals of East Asian Ambiguous - * characters, generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c" */ - static const struct interval ambiguous[] = { - { 0x00A1, 0x00A1 }, { 0x00A4, 0x00A4 }, { 0x00A7, 0x00A8 }, - { 0x00AA, 0x00AA }, { 0x00AE, 0x00AE }, { 0x00B0, 0x00B4 }, - { 0x00B6, 0x00BA }, { 0x00BC, 0x00BF }, { 0x00C6, 0x00C6 }, - { 0x00D0, 0x00D0 }, { 0x00D7, 0x00D8 }, { 0x00DE, 0x00E1 }, - { 0x00E6, 0x00E6 }, { 0x00E8, 0x00EA }, { 0x00EC, 0x00ED }, - { 0x00F0, 0x00F0 }, { 0x00F2, 0x00F3 }, { 0x00F7, 0x00FA }, - { 0x00FC, 0x00FC }, { 0x00FE, 0x00FE }, { 0x0101, 0x0101 }, - { 0x0111, 0x0111 }, { 0x0113, 0x0113 }, { 0x011B, 0x011B }, - { 0x0126, 0x0127 }, { 0x012B, 0x012B }, { 0x0131, 0x0133 }, - { 0x0138, 0x0138 }, { 0x013F, 0x0142 }, { 0x0144, 0x0144 }, - { 0x0148, 0x014B }, { 0x014D, 0x014D }, { 0x0152, 0x0153 }, - { 0x0166, 0x0167 }, { 0x016B, 0x016B }, { 0x01CE, 0x01CE }, - { 0x01D0, 0x01D0 }, { 0x01D2, 0x01D2 }, { 0x01D4, 0x01D4 }, - { 0x01D6, 0x01D6 }, { 0x01D8, 0x01D8 }, { 0x01DA, 0x01DA }, - { 0x01DC, 0x01DC }, { 0x0251, 0x0251 }, { 0x0261, 0x0261 }, - { 0x02C4, 0x02C4 }, { 0x02C7, 0x02C7 }, { 0x02C9, 0x02CB }, - { 0x02CD, 0x02CD }, { 0x02D0, 0x02D0 }, { 0x02D8, 0x02DB }, - { 0x02DD, 0x02DD }, { 0x02DF, 0x02DF }, { 0x0391, 0x03A1 }, - { 0x03A3, 0x03A9 }, { 0x03B1, 0x03C1 }, { 0x03C3, 0x03C9 }, - { 0x0401, 0x0401 }, { 0x0410, 0x044F }, { 0x0451, 0x0451 }, - { 0x2010, 0x2010 }, { 0x2013, 0x2016 }, { 0x2018, 0x2019 }, - { 0x201C, 0x201D }, { 0x2020, 0x2022 }, { 0x2024, 0x2027 }, - { 0x2030, 0x2030 }, { 0x2032, 0x2033 }, { 0x2035, 0x2035 }, - { 0x203B, 0x203B }, { 0x203E, 0x203E }, { 0x2074, 0x2074 }, - { 0x207F, 0x207F }, { 0x2081, 0x2084 }, { 0x20AC, 0x20AC }, - { 0x2103, 0x2103 }, { 0x2105, 0x2105 }, { 0x2109, 0x2109 }, - { 0x2113, 0x2113 }, { 0x2116, 0x2116 }, { 0x2121, 0x2122 }, - { 0x2126, 0x2126 }, { 0x212B, 0x212B }, { 0x2153, 0x2154 }, - { 0x215B, 0x215E }, { 0x2160, 0x216B }, { 0x2170, 0x2179 }, - { 0x2190, 0x2199 }, { 0x21B8, 0x21B9 }, { 0x21D2, 0x21D2 }, - { 0x21D4, 0x21D4 }, { 0x21E7, 0x21E7 }, { 0x2200, 0x2200 }, - { 0x2202, 0x2203 }, { 0x2207, 0x2208 }, { 0x220B, 0x220B }, - { 0x220F, 0x220F }, { 0x2211, 0x2211 }, { 0x2215, 0x2215 }, - { 0x221A, 0x221A }, { 0x221D, 0x2220 }, { 0x2223, 0x2223 }, - { 0x2225, 0x2225 }, { 0x2227, 0x222C }, { 0x222E, 0x222E }, - { 0x2234, 0x2237 }, { 0x223C, 0x223D }, { 0x2248, 0x2248 }, - { 0x224C, 0x224C }, { 0x2252, 0x2252 }, { 0x2260, 0x2261 }, - { 0x2264, 0x2267 }, { 0x226A, 0x226B }, { 0x226E, 0x226F }, - { 0x2282, 0x2283 }, { 0x2286, 0x2287 }, { 0x2295, 0x2295 }, - { 0x2299, 0x2299 }, { 0x22A5, 0x22A5 }, { 0x22BF, 0x22BF }, - { 0x2312, 0x2312 }, { 0x2460, 0x24E9 }, { 0x24EB, 0x254B }, - { 0x2550, 0x2573 }, { 0x2580, 0x258F }, { 0x2592, 0x2595 }, - { 0x25A0, 0x25A1 }, { 0x25A3, 0x25A9 }, { 0x25B2, 0x25B3 }, - { 0x25B6, 0x25B7 }, { 0x25BC, 0x25BD }, { 0x25C0, 0x25C1 }, - { 0x25C6, 0x25C8 }, { 0x25CB, 0x25CB }, { 0x25CE, 0x25D1 }, - { 0x25E2, 0x25E5 }, { 0x25EF, 0x25EF }, { 0x2605, 0x2606 }, - { 0x2609, 0x2609 }, { 0x260E, 0x260F }, { 0x2614, 0x2615 }, - { 0x261C, 0x261C }, { 0x261E, 0x261E }, { 0x2640, 0x2640 }, - { 0x2642, 0x2642 }, { 0x2660, 0x2661 }, { 0x2663, 0x2665 }, - { 0x2667, 0x266A }, { 0x266C, 0x266D }, { 0x266F, 0x266F }, - { 0x273D, 0x273D }, { 0x2776, 0x277F }, { 0xE000, 0xF8FF }, - { 0xFFFD, 0xFFFD }, { 0xF0000, 0xFFFFD }, { 0x100000, 0x10FFFD } - }; - - /* binary search in table of non-spacing characters */ - if (bisearch(ucs, ambiguous, - sizeof(ambiguous) / sizeof(struct interval) - 1)) - return 2; - - return mk_wcwidth(ucs); + if (ucs == 0) + return 0; + return wcwidth9(ucs) != -1; } - -int mk_wcswidth_cjk(const wchar_t *pwcs, size_t n) -{ - int w, width = 0; - - for (;*pwcs && n-- > 0; pwcs++) - if ((w = mk_wcwidth_cjk(*pwcs)) < 0) - return -1; - else - width += w; - - return width; -} -#endif /* 0 */ - /**/ -#endif /* BROKEN_WCWIDTH && (__STDC_ISO_10646__ || __APPLE__) */ +#endif /* ENABLE_UNICODE9 */ /**/ #if defined(__APPLE__) && defined(BROKEN_ISPRINT) diff --git a/Src/cond.c b/Src/cond.c index 42e9de30f..b9a47cea5 100644 --- a/Src/cond.c +++ b/Src/cond.c @@ -138,13 +138,13 @@ evalcond(Estate state, char *fromtest) strs = arrdup(sbuf); l = 2; } - if (name && name[0] == '-') - errname = name; - else if (strs[0] && *strs[0] == '-') - errname = strs[0]; + if (name && IS_DASH(name[0])) + untokenize(errname = dupstring(name)); + else if (strs[0] && IS_DASH(*strs[0])) + untokenize(errname = strs[0]); else errname = "<null>"; - if (name && name[0] == '-' && + if (name && IS_DASH(name[0]) && (cd = getconddef((ctype == COND_MODI), name + 1, 1))) { if (ctype == COND_MOD && (l < cd->min || (cd->max >= 0 && l > cd->max))) { @@ -171,7 +171,7 @@ evalcond(Estate state, char *fromtest) strs[0] = dupstring(name); name = s; - if (name && name[0] == '-' && + if (name && IS_DASH(name[0]) && (cd = getconddef(0, name + 1, 1))) { if (l < cd->min || (cd->max >= 0 && l > cd->max)) { zwarnnam(fromtest, "unknown condition: %s", @@ -295,6 +295,8 @@ evalcond(Estate state, char *fromtest) int test, npat = state->pc[1]; Patprog pprog = state->prog->pats[npat]; + queue_signals(); + if (pprog == dummy_patprog1 || pprog == dummy_patprog2) { char *opat; int save; @@ -308,6 +310,7 @@ evalcond(Estate state, char *fromtest) if (!(pprog = patcompile(right, (save ? PAT_ZDUP : PAT_STATIC), NULL))) { zwarnnam(fromtest, "bad pattern: %s", right); + unqueue_signals(); return 2; } else if (save) @@ -316,6 +319,8 @@ evalcond(Estate state, char *fromtest) state->pc += 2; test = (pprog && pattry(pprog, left)); + unqueue_signals(); + return !(ctype == COND_STRNEQ ? !test : test); } case COND_STRLT: diff --git a/Src/exec.c b/Src/exec.c index a439aec7f..f339dd6d0 100644 --- a/Src/exec.c +++ b/Src/exec.c @@ -975,9 +975,8 @@ entersubsh(int flags) int sig, monitor, job_control_ok; if (!(flags & ESUB_KEEPTRAP)) - for (sig = 0; sig < VSIGCOUNT; sig++) - if (!(sigtrapped[sig] & ZSIG_FUNC) && - sig != SIGDEBUG && sig != SIGZERR) + for (sig = 0; sig < SIGCOUNT; sig++) + if (!(sigtrapped[sig] & ZSIG_FUNC)) unsettrap(sig); monitor = isset(MONITOR); job_control_ok = monitor && (flags & ESUB_JOB_CONTROL) && isset(POSIXJOBS); @@ -1068,6 +1067,18 @@ entersubsh(int flags) } if (!(sigtrapped[SIGQUIT] & ZSIG_IGNORED)) signal_default(SIGQUIT); + /* + * sigtrapped[sig] == ZSIG_IGNORED for signals that remain ignored, + * but other trapped signals are temporarily blocked when intrap, + * and must be unblocked before continuing into the subshell. This + * is orthogonal to what the default handler for the signal may be. + * + * Start loop at 1 because 0 is SIGEXIT + */ + if (intrap) + for (sig = 1; sig < SIGCOUNT; sig++) + if (sigtrapped[sig] && sigtrapped[sig] != ZSIG_IGNORED) + signal_unblock(signal_mask(sig)); if (!job_control_ok) opts[MONITOR] = 0; opts[USEZLE] = 0; @@ -1601,6 +1612,7 @@ execpline(Estate state, wordcode slcode, int how, int last1) zclose(opipe[0]); } if (how & Z_DISOWN) { + pipecleanfilelist(jobtab[thisjob].filelist, 0); deletejob(jobtab + thisjob, 1); thisjob = -1; } @@ -1848,7 +1860,7 @@ execpline2(Estate state, wordcode pcode, lineno = WC_PIPE_LINENO(pcode) - 1; if (pline_level == 1) { - if ((how & Z_ASYNC) || (!sfcontext && !sourcelevel)) + if ((how & Z_ASYNC) || !sfcontext) strcpy(list_pipe_text, getjobtext(state->prog, state->pc + (WC_PIPE_TYPE(pcode) == WC_PIPE_END ? @@ -2379,9 +2391,7 @@ addvars(Estate state, Wordcode pc, int addflags) * to be restored after the command, since then the assignment * is implicitly scoped. */ - flags = (!(addflags & ADDVAR_RESTORE) && - locallevel > forklevel && isset(WARNCREATEGLOBAL)) ? - ASSPM_WARN_CREATE : 0; + flags = !(addflags & ADDVAR_RESTORE) ? ASSPM_WARN : 0; xtr = isset(XTRACE); if (xtr) { printprompt4(); @@ -2633,6 +2643,27 @@ execcmd_analyse(Estate state, Execcmd_params eparams) } /* + * Transfer the first node of args to preargs, performing + * prefork expansion on the way if necessary. + */ +static void execcmd_getargs(LinkList preargs, LinkList args, int expand) +{ + if (!firstnode(args)) { + return; + } else if (expand) { + local_list0(svl); + init_list0(svl); + /* not init_list1, as we need real nodes */ + addlinknode(&svl, uremnode(args, firstnode(args))); + /* Analysing commands, so vanilla options to prefork */ + prefork(&svl, 0, NULL); + joinlists(preargs, &svl); + } else { + addlinknode(preargs, uremnode(args, firstnode(args))); + } +} + +/* * Execute a command at the lowest level of the hierarchy. */ @@ -2649,7 +2680,7 @@ execcmd_exec(Estate state, Execcmd_params eparams, char *text; int save[10]; int fil, dfil, is_cursh, do_exec = 0, redir_err = 0, i; - int nullexec = 0, assign = 0, forked = 0; + int nullexec = 0, magic_assign = 0, forked = 0; int is_shfunc = 0, is_builtin = 0, is_exec = 0, use_defpath = 0; /* Various flags to the command. */ int cflags = 0, orig_cflags = 0, checked = 0, oautocont = -1; @@ -2662,6 +2693,11 @@ execcmd_exec(Estate state, Execcmd_params eparams, LinkList redir = eparams->redir; Wordcode varspc = eparams->varspc; int type = eparams->type; + /* + * preargs comes from expanding the head of the args list + * in order to check for prefix commands. + */ + LinkList preargs; doneps4 = 0; @@ -2716,9 +2752,19 @@ execcmd_exec(Estate state, Execcmd_params eparams, * command if it contains some tokens (e.g. x=ex; ${x}port), so this * * only works in simple cases. has_token() is called to make sure * * this really is a simple case. */ - if (type == WC_SIMPLE || type == WC_TYPESET) { - while (args && nonempty(args)) { - char *cmdarg = (char *) peekfirst(args); + if ((type == WC_SIMPLE || type == WC_TYPESET) && args) { + /* + * preargs contains args that have been expanded by prefork. + * Running execcmd_getargs() causes the any argument available + * in args to be exanded where necessary and transferred to + * preargs. We call execcmd_getargs() every time we need to + * analyse an argument not available in preargs, though there is + * no guarantee a further argument will be available. + */ + preargs = newlinklist(); + execcmd_getargs(preargs, args, eparams->htok); + while (nonempty(preargs)) { + char *cmdarg = (char *) peekfirst(preargs); checked = !has_token(cmdarg); if (!checked) break; @@ -2732,6 +2778,12 @@ execcmd_exec(Estate state, Execcmd_params eparams, * Reserved words take precedence over shell functions. */ checked = 1; + } else if (isset(POSIXBUILTINS) && (cflags & BINF_EXEC)) { + /* + * POSIX doesn't allow "exec" to operate on builtins + * or shell functions. + */ + break; } else { if (!(cflags & (BINF_BUILTIN | BINF_COMMAND)) && (hn = shfunctab->getnode(shfunctab, cmdarg))) { @@ -2752,11 +2804,24 @@ execcmd_exec(Estate state, Execcmd_params eparams, /* autoload the builtin if necessary */ if (!(hn = resolvebuiltin(cmdarg, hn))) return; - assign = (hn->flags & BINF_MAGICEQUALS); + if (type != WC_TYPESET) + magic_assign = (hn->flags & BINF_MAGICEQUALS); break; } checked = 0; - if ((cflags & BINF_COMMAND) && nextnode(firstnode(args))) { + /* + * We usually don't need the argument containing the + * precommand modifier itself. Exception: when "command" + * will implemented by a call to "whence", in which case + * we'll simply re-insert the argument. + */ + uremnode(preargs, firstnode(preargs)); + if (!firstnode(preargs)) { + execcmd_getargs(preargs, args, eparams->htok); + if (!firstnode(preargs)) + break; + } + if ((cflags & BINF_COMMAND)) { /* * Check for options to "command". * If just -p, this is handled here: use the default @@ -2766,13 +2831,15 @@ execcmd_exec(Estate state, Execcmd_params eparams, * Otherwise, just leave marked as BINF_COMMAND * modifier with no additional action. */ - LinkNode argnode = nextnode(firstnode(args)); - char *argdata = (char *) getdata(argnode); - char *cmdopt; + LinkNode argnode, oldnode, pnode = NULL; + char *argdata, *cmdopt; int has_p = 0, has_vV = 0, has_other = 0; - while (*argdata == '-') { + argnode = firstnode(preargs); + argdata = (char *) getdata(argnode); + while (IS_DASH(*argdata)) { /* Just to be definite, stop on single "-", too, */ - if (!argdata[1] || (argdata[1] == '-' && !argdata[2])) + if (!argdata[1] || + (IS_DASH(argdata[1]) && !argdata[2])) break; for (cmdopt = argdata+1; *cmdopt; cmdopt++) { switch (*cmdopt) { @@ -2785,6 +2852,7 @@ execcmd_exec(Estate state, Execcmd_params eparams, * also traditional behaviour. */ has_p = 1; + pnode = argnode; break; case 'v': case 'V': @@ -2801,42 +2869,53 @@ execcmd_exec(Estate state, Execcmd_params eparams, break; } + oldnode = argnode; argnode = nextnode(argnode); - if (!argnode) - break; + if (!argnode) { + execcmd_getargs(preargs, args, eparams->htok); + if (!(argnode = nextnode(oldnode))) + break; + } argdata = (char *) getdata(argnode); } if (has_vV) { - /* Leave everything alone, dispatch to whence */ + /* + * Leave everything alone, dispatch to whence. + * We need to put the name back in the list. + */ + pushnode(preargs, "command"); hn = &commandbn.node; is_builtin = 1; break; } else if (has_p) { - /* Use default path; absorb command and option. */ - uremnode(args, firstnode(args)); + /* Use default path */ use_defpath = 1; - if ((argnode = nextnode(firstnode(args)))) - argdata = (char *) getdata(argnode); + /* + * We don't need this node as we're not treating + * "command" as a builtin this time. + */ + if (pnode) + uremnode(preargs, pnode); } /* - * Else just absorb command and any trailing + * Else just any trailing * end-of-options marker. This can only occur * if we just had -p or something including more * than just -p, -v and -V, in which case we behave * as if this is command [non-option-stuff]. This * isn't a good place for standard option handling. */ - if (!strcmp(argdata, "--")) - uremnode(args, firstnode(args)); - } - if ((cflags & BINF_EXEC) && nextnode(firstnode(args))) { + if (IS_DASH(argdata[0]) && IS_DASH(argdata[1]) && !argdata[2]) + uremnode(preargs, argnode); + } else if (cflags & BINF_EXEC) { /* * Check for compatibility options to exec builtin. * It would be nice to do these more generically, * but currently we don't have a mechanism for * precommand modifiers. */ - char *next = (char *) getdata(nextnode(firstnode(args))); + LinkNode argnode = firstnode(preargs), oldnode; + char *argdata = (char *) getdata(argnode); char *cmdopt, *exec_argv0 = NULL; /* * Careful here: we want to make sure a final dash @@ -2846,17 +2925,23 @@ execcmd_exec(Estate state, Execcmd_params eparams, * people aren't likely to mix the option style * with the zsh style. */ - while (next && *next == '-' && strlen(next) >= 2) { - if (!firstnode(args)) { + while (argdata && IS_DASH(*argdata) && strlen(argdata) >= 2) { + oldnode = argnode; + argnode = nextnode(oldnode); + if (!argnode) { + execcmd_getargs(preargs, args, eparams->htok); + argnode = nextnode(oldnode); + } + if (!argnode) { zerr("exec requires a command to execute"); lastval = 1; errflag |= ERRFLAG_ERROR; goto done; } - uremnode(args, firstnode(args)); - if (!strcmp(next, "--")) + uremnode(preargs, oldnode); + if (IS_DASH(argdata[0]) && IS_DASH(argdata[1]) && !argdata[2]) break; - for (cmdopt = &next[1]; *cmdopt; ++cmdopt) { + for (cmdopt = &argdata[1]; *cmdopt; ++cmdopt) { switch (*cmdopt) { case 'a': /* argument is ARGV0 string */ @@ -2865,21 +2950,25 @@ execcmd_exec(Estate state, Execcmd_params eparams, /* position on last non-NULL character */ cmdopt += strlen(cmdopt+1); } else { - if (!firstnode(args)) { + if (!argnode) { zerr("exec requires a command to execute"); lastval = 1; errflag |= ERRFLAG_ERROR; goto done; } - if (!nextnode(firstnode(args))) { + if (!nextnode(argnode)) + execcmd_getargs(preargs, args, + eparams->htok); + if (!nextnode(argnode)) { zerr("exec flag -a requires a parameter"); lastval = 1; errflag |= ERRFLAG_ERROR; goto done; } - exec_argv0 = (char *) - getdata(nextnode(firstnode(args))); - uremnode(args, firstnode(args)); + exec_argv0 = (char *) getdata(argnode); + oldnode = argnode; + argnode = nextnode(argnode); + uremnode(args, oldnode); } break; case 'c': @@ -2895,8 +2984,9 @@ execcmd_exec(Estate state, Execcmd_params eparams, return; } } - if (firstnode(args) && nextnode(firstnode(args))) - next = (char *) getdata(nextnode(firstnode(args))); + if (!argnode) + break; + argdata = (char *) getdata(argnode); } if (exec_argv0) { char *str, *s; @@ -2908,21 +2998,41 @@ execcmd_exec(Estate state, Execcmd_params eparams, zputenv(str); } } - uremnode(args, firstnode(args)); hn = NULL; if ((cflags & BINF_COMMAND) && unset(POSIXBUILTINS)) break; + if (!nonempty(preargs)) + execcmd_getargs(preargs, args, eparams->htok); } - } + } else + preargs = NULL; /* if we get this far, it is OK to pay attention to lastval again */ if (noerrexit == 2 && !is_shfunc) noerrexit = 0; - /* Do prefork substitutions */ - esprefork = (assign || isset(MAGICEQUALSUBST)) ? PREFORK_TYPESET : 0; - if (args && eparams->htok) - prefork(args, esprefork, NULL); + /* Do prefork substitutions. + * + * Decide if we need "magic" handling of ~'s etc. in + * assignment-like arguments. + * - If magic_assign is set, we are using a builtin of the + * tyepset family, but did not recognise this as a keyword, + * so need guess-o-matic behaviour. + * - Otherwise, if we did recognise the keyword, we never need + * guess-o-matic behaviour as the argument was properly parsed + * as such. + * - Otherwise, use the behaviour specified by the MAGIC_EQUAL_SUBST + * option. + */ + esprefork = (magic_assign || + (isset(MAGICEQUALSUBST) && type != WC_TYPESET)) ? + PREFORK_TYPESET : 0; + if (args) { + if (eparams->htok) + prefork(args, esprefork, NULL); + if (preargs) + args = joinlists(preargs, args); + } if (type == WC_SIMPLE || type == WC_TYPESET) { int unglobbed = 0; @@ -3019,10 +3129,14 @@ execcmd_exec(Estate state, Execcmd_params eparams, * - we have determined there are options which would * require us to use the "command" builtin); or * - we aren't using POSIX and so BINF_COMMAND indicates a zsh - * precommand modifier is being used in place of the builtin + * precommand modifier is being used in place of the + * builtin + * - we are using POSIX and this is an EXEC, so we can't + * execute a builtin or function. */ if (errflag || checked || is_builtin || - (unset(POSIXBUILTINS) && (cflags & BINF_COMMAND))) + (isset(POSIXBUILTINS) ? + (cflags & BINF_EXEC) : (cflags & BINF_COMMAND))) break; cmdarg = (char *) peekfirst(args); @@ -3065,7 +3179,7 @@ execcmd_exec(Estate state, Execcmd_params eparams, /* Get the text associated with this command. */ if ((how & Z_ASYNC) || - (!sfcontext && !sourcelevel && (jobbing || (how & Z_TIMED)))) + (!sfcontext && (jobbing || (how & Z_TIMED)))) text = getjobtext(state->prog, eparams->beg); else text = NULL; @@ -3124,7 +3238,7 @@ execcmd_exec(Estate state, Execcmd_params eparams, if (is_shfunc) shf = (Shfunc)hn; else { - shf = loadautofn(state->prog->shf, 1, 0); + shf = loadautofn(state->prog->shf, 1, 0, 0); if (shf) state->prog->shf = shf; else { @@ -3700,7 +3814,7 @@ execcmd_exec(Estate state, Execcmd_params eparams, * Save if it's got "command" in front or it's * not a magic-equals assignment. */ - if ((cflags & (BINF_COMMAND|BINF_ASSIGN)) || !assign) + if ((cflags & (BINF_COMMAND|BINF_ASSIGN)) || !magic_assign) do_save = 1; } if (do_save && varspc) @@ -3987,6 +4101,7 @@ execcmd_exec(Estate state, Execcmd_params eparams, * classify as a builtin) we treat all errors as fatal. * The "command" builtin is not special so resets this behaviour. */ + forked |= zsh_subshell; fatal: if (redir_err || errflag) { if (!isset(INTERACTIVE)) { @@ -4465,7 +4580,7 @@ getoutputfile(char *cmd, char **eptr) } if (!(prog = parsecmd(cmd, eptr))) return NULL; - if (!(nam = gettempname(NULL, 0))) + if (!(nam = gettempname(NULL, 1))) return NULL; if ((s = simple_redir_name(prog, REDIR_HERESTR))) { @@ -4496,7 +4611,7 @@ getoutputfile(char *cmd, char **eptr) suffix = dyncat(nam, unmeta(suffix)); if (link(nam, suffix) == 0) { addfilelist(nam, 0); - nam = ztrdup(suffix); + nam = suffix; } } } @@ -4902,6 +5017,7 @@ execfuncdef(Estate state, Eprog redir_prog) shf = (Shfunc) zalloc(sizeof(*shf)); shf->funcdef = prog; shf->node.flags = 0; + /* No dircache here, not a directory */ shf->filename = ztrdup(scriptfilename); shf->lineno = lineno; /* @@ -4934,7 +5050,7 @@ execfuncdef(Estate state, Eprog redir_prog) freeeprog(shf->funcdef); if (shf->redir) /* shouldn't be */ freeeprog(shf->redir); - zsfree(shf->filename); + dircache_set(&shf->filename, NULL); zfree(shf, sizeof(*shf)); state->pc = end; return 1; @@ -4965,7 +5081,7 @@ execfuncdef(Estate state, Eprog redir_prog) freeeprog(shf->funcdef); if (shf->redir) /* shouldn't be */ freeeprog(shf->redir); - zsfree(shf->filename); + dircache_set(&shf->filename, NULL); zfree(shf, sizeof(*shf)); break; } else { @@ -4974,7 +5090,7 @@ execfuncdef(Estate state, Eprog redir_prog) (signum = getsignum(s + 4)) != -1) { if (settrap(signum, NULL, ZSIG_FUNC)) { freeeprog(shf->funcdef); - zsfree(shf->filename); + dircache_set(&shf->filename, NULL); zfree(shf, sizeof(*shf)); state->pc = end; return 1; @@ -5123,12 +5239,12 @@ execautofn_basic(Estate state, UNUSED(int do_exec)) * defined yet. */ if (funcstack && !funcstack->filename) - funcstack->filename = dupstring(shf->filename); + funcstack->filename = getshfuncfile(shf); oldscriptname = scriptname; oldscriptfilename = scriptfilename; scriptname = dupstring(shf->node.nam); - scriptfilename = dupstring(shf->filename); + scriptfilename = getshfuncfile(shf); execode(shf->funcdef, 1, 0, "loadautofunc"); scriptname = oldscriptname; scriptfilename = oldscriptfilename; @@ -5142,25 +5258,71 @@ execautofn(Estate state, UNUSED(int do_exec)) { Shfunc shf; - if (!(shf = loadautofn(state->prog->shf, 1, 0))) + if (!(shf = loadautofn(state->prog->shf, 1, 0, 0))) return 1; state->prog->shf = shf; return execautofn_basic(state, 0); } +/* + * Helper function to install the source file name of a shell function + * just autoloaded. + * + * We attempt to do this efficiently as the typical case is the + * directory part is a well-known directory, which is cached, and + * the non-directory part is the same as the node name. + */ + +/**/ +static void +loadautofnsetfile(Shfunc shf, char *fdir) +{ + /* + * If shf->filename is already the load directory --- + * keep it as we can still use it to get the load file. + * This makes autoload with an absolute path particularly efficient. + */ + if (!(shf->node.flags & PM_LOADDIR) || + strcmp(shf->filename, fdir) != 0) { + /* Old directory name not useful... */ + dircache_set(&shf->filename, NULL); + if (fdir) { + /* ...can still cache directory */ + shf->node.flags |= PM_LOADDIR; + dircache_set(&shf->filename, fdir); + } else { + /* ...no separate directory part to cache, for some reason. */ + shf->node.flags &= ~PM_LOADDIR; + shf->filename = ztrdup(shf->node.nam); + } + } +} + /**/ Shfunc -loadautofn(Shfunc shf, int fksh, int autol) +loadautofn(Shfunc shf, int fksh, int autol, int current_fpath) { int noalias = noaliases, ksh = 1; Eprog prog; - char *fname; + char *fdir; /* Directory path where func found */ pushheap(); noaliases = (shf->node.flags & PM_UNALIASED); - prog = getfpfunc(shf->node.nam, &ksh, &fname); + if (shf->filename && shf->filename[0] == '/' && + (shf->node.flags & PM_LOADDIR)) + { + char *spec_path[2]; + spec_path[0] = dupstring(shf->filename); + spec_path[1] = NULL; + prog = getfpfunc(shf->node.nam, &ksh, &fdir, spec_path, 0); + if (prog == &dummy_eprog && + (current_fpath || (shf->node.flags & PM_CUR_FPATH))) + prog = getfpfunc(shf->node.nam, &ksh, &fdir, NULL, 0); + } + else + prog = getfpfunc(shf->node.nam, &ksh, &fdir, NULL, 0); noaliases = noalias; if (ksh == 1) { @@ -5179,7 +5341,6 @@ loadautofn(Shfunc shf, int fksh, int autol) return NULL; } if (!prog) { - zsfree(fname); popheap(); return NULL; } @@ -5193,7 +5354,7 @@ loadautofn(Shfunc shf, int fksh, int autol) else shf->funcdef = dupeprog(prog, 0); shf->node.flags &= ~PM_UNDEFINED; - shf->filename = fname; + loadautofnsetfile(shf, fdir); } else { VARARR(char, n, strlen(shf->node.nam) + 1); strcpy(n, shf->node.nam); @@ -5205,7 +5366,6 @@ loadautofn(Shfunc shf, int fksh, int autol) zwarn("%s: function not defined by file", n); locallevel++; popheap(); - zsfree(fname); return NULL; } } @@ -5216,7 +5376,7 @@ loadautofn(Shfunc shf, int fksh, int autol) else shf->funcdef = dupeprog(stripkshdef(prog, shf->node.nam), 0); shf->node.flags &= ~PM_UNDEFINED; - shf->filename = fname; + loadautofnsetfile(shf, fdir); } popheap(); @@ -5386,6 +5546,14 @@ doshfunc(Shfunc shfunc, LinkList doshargs, int noreturnval) else opts[XTRACE] = 0; } + if (flags & PM_WARNNESTED) + opts[WARNNESTEDVAR] = 1; + else if (oflags & PM_WARNNESTED) { + if (shfunc->node.nam == ANONYMOUS_FUNCTION_NAME) + flags |= PM_WARNNESTED; + else + opts[WARNNESTEDVAR] = 0; + } ooflags = oflags; /* * oflags is static, because we compare it on the next recursive @@ -5436,7 +5604,7 @@ doshfunc(Shfunc shfunc, LinkList doshargs, int noreturnval) funcstack = &fstack; fstack.flineno = shfunc->lineno; - fstack.filename = dupstring(shfunc->filename); + fstack.filename = getshfuncfile(shfunc); prog = shfunc->funcdef; if (prog->flags & EF_RUN) { @@ -5504,6 +5672,7 @@ doshfunc(Shfunc shfunc, LinkList doshargs, int noreturnval) opts[PRINTEXITVALUE] = saveopts[PRINTEXITVALUE]; opts[LOCALOPTIONS] = saveopts[LOCALOPTIONS]; opts[LOCALLOOPS] = saveopts[LOCALLOOPS]; + opts[WARNNESTEDVAR] = saveopts[WARNNESTEDVAR]; } if (opts[LOCALLOOPS]) { @@ -5537,8 +5706,11 @@ doshfunc(Shfunc shfunc, LinkList doshargs, int noreturnval) * the only likely case where we need that second test is * when we have an "always" block. The endparamscope() has * already happened, hence the "+1" here. + * + * If we are in an exit trap, finish it first... we wouldn't set + * exit_pending if we were already in one. */ - if (exit_pending && exit_level >= locallevel+1) { + if (exit_pending && exit_level >= locallevel+1 && !in_exit_trap) { if (locallevel > forklevel) { /* Still functions to return: force them to do so. */ retflag = 1; @@ -5602,12 +5774,21 @@ runshfunc(Eprog prog, FuncWrap wrap, char *name) unqueue_signals(); } -/* Search fpath for an undefined function. Finds the file, and returns the * - * list of its contents. */ +/* + * Search fpath for an undefined function. Finds the file, and returns the + * list of its contents. + * + * If test is 0, load the function. + * + * If test_only is 1, don't load function, just test for it: + * Non-null return means function was found + * + * *fdir points to path at which found (as passed in, not duplicated) + */ /**/ Eprog -getfpfunc(char *s, int *ksh, char **fname) +getfpfunc(char *s, int *ksh, char **fdir, char **alt_path, int test_only) { char **pp, buf[PATH_MAX+1]; off_t len; @@ -5616,7 +5797,7 @@ getfpfunc(char *s, int *ksh, char **fname) Eprog r; int fd; - pp = fpath; + pp = alt_path ? alt_path : fpath; for (; *pp; pp++) { if (strlen(*pp) + strlen(s) + 1 >= PATH_MAX) continue; @@ -5624,9 +5805,9 @@ getfpfunc(char *s, int *ksh, char **fname) sprintf(buf, "%s/%s", *pp, s); else strcpy(buf, s); - if ((r = try_dump_file(*pp, s, buf, ksh))) { - if (fname) - *fname = ztrdup(buf); + if ((r = try_dump_file(*pp, s, buf, ksh, test_only))) { + if (fdir) + *fdir = *pp; return r; } unmetafy(buf, NULL); @@ -5634,6 +5815,12 @@ getfpfunc(char *s, int *ksh, char **fname) struct stat st; if (!fstat(fd, &st) && S_ISREG(st.st_mode) && (len = lseek(fd, 0, 2)) != -1) { + if (test_only) { + close(fd); + if (fdir) + *fdir = *pp; + return &dummy_eprog; + } d = (char *) zalloc(len + 1); lseek(fd, 0, 0); if ((rlen = read(fd, d, len)) >= 0) { @@ -5647,8 +5834,8 @@ getfpfunc(char *s, int *ksh, char **fname) r = parse_string(d, 1); scriptname = oldscriptname; - if (fname) - *fname = ztrdup(buf); + if (fdir) + *fdir = *pp; zfree(d, len + 1); @@ -5661,7 +5848,7 @@ getfpfunc(char *s, int *ksh, char **fname) close(fd); } } - return &dummy_eprog; + return test_only ? NULL : &dummy_eprog; } /* Handle the most common type of ksh-style autoloading, when doing a * diff --git a/Src/glob.c b/Src/glob.c index 623e6f1d6..c9ec97e0e 100644 --- a/Src/glob.c +++ b/Src/glob.c @@ -1314,6 +1314,7 @@ zglob(LinkList list, LinkNode np, int nountok) sense ^= 1; break; case '-': + case Dash: /* Toggle matching of symbolic links */ sense ^= 2; break; @@ -1608,7 +1609,7 @@ zglob(LinkList list, LinkNode np, int nountok) ++s; } /* See if it's greater than, equal to, or less than */ - if ((g_range = *s == '+' ? 1 : *s == '-' ? -1 : 0)) + if ((g_range = *s == '+' ? 1 : IS_DASH(*s) ? -1 : 0)) ++s; data = qgetnum(&s); break; @@ -2025,13 +2026,13 @@ hasbraces(char *str) if (bracechardots(str-1, NULL, NULL)) return 1; lbr = str - 1; - if (*str == '-') + if (IS_DASH(*str)) str++; while (idigit(*str)) str++; if (*str == '.' && str[1] == '.') { str++; str++; - if (*str == '-') + if (IS_DASH(*str)) str++; while (idigit(*str)) str++; @@ -2040,7 +2041,7 @@ hasbraces(char *str) return 1; else if (*str == '.' && str[1] == '.') { str++; str++; - if (*str == '-') + if (IS_DASH(*str)) str++; while (idigit(*str)) str++; @@ -2123,7 +2124,7 @@ xpandredir(struct redir *fn, LinkList redirtab) fn->name = s; untokenize(s); if (fn->type == REDIR_MERGEIN || fn->type == REDIR_MERGEOUT) { - if (s[0] == '-' && !s[1]) + if (IS_DASH(s[0]) && !s[1]) fn->type = REDIR_CLOSE; else if (s[0] == 'p' && !s[1]) fn->fd2 = -2; @@ -2193,6 +2194,8 @@ bracechardots(char *str, convchar_t *c1p, convchar_t *c2p) pnext[0] != '.' || pnext[1] != '.') return 0; pnext += 2; + if (!*pnext) + return 0; if (itok(*pnext)) { if (*pnext == Inbrace) return 0; @@ -2329,12 +2332,14 @@ xpandbraces(LinkList list, LinkNode *np) * str+1 is the first number in the range, dots+2 the last, * and dots2+2 is the increment if that's given. */ /* TODO: sorry about this */ - int minw = (str[1] == '0' || (str[1] == '-' && str[2] == '0')) + int minw = (str[1] == '0' || + (IS_DASH(str[1]) && str[2] == '0')) ? wid1 - : (dots[2] == '0' || (dots[2] == '-' && dots[3] == '0')) + : (dots[2] == '0' || + (IS_DASH(dots[2]) && dots[3] == '0')) ? wid2 : (dots2 && (dots2[2] == '0' || - (dots2[2] == '-' && dots2[3] == '0'))) + (IS_DASH(dots2[2]) && dots2[3] == '0'))) ? wid3 : 0; if (rincr < 0) { @@ -2392,7 +2397,8 @@ xpandbraces(LinkList list, LinkNode *np) c2 = ztokens[c2 - STOUC(Pound)]; if ((char) c2 == Meta) c2 = 32 ^ p[1]; - if (c1 == '-' && lastch >= 0 && p < str2 && lastch <= (int)c2) { + if (IS_DASH((char)c1) && lastch >= 0 && + p < str2 && lastch <= (int)c2) { while (lastch < (int)c2) ccl[lastch++] = 1; lastch = -1; @@ -2462,13 +2468,20 @@ xpandbraces(LinkList list, LinkNode *np) int matchpat(char *a, char *b) { - Patprog p = patcompile(b, PAT_STATIC, NULL); + Patprog p; + int ret; - if (!p) { + queue_signals(); /* Protect PAT_STATIC */ + + if (!(p = patcompile(b, PAT_STATIC, NULL))) { zerr("bad pattern: %s", b); - return 0; - } - return pattry(p, a); + ret = 0; + } else + ret = pattry(p, a); + + unqueue_signals(); + + return ret; } /* do the ${foo%%bar}, ${foo#bar} stuff */ @@ -2918,7 +2931,7 @@ igetmatch(char **sp, Patprog p, int fl, int n, char *replstr, * move forward along string until we get a match. * * Again there's no optimisation. */ mb_charinit(); - for (ioff = 0, t = s, umlen = umltot; t < send ; ioff++) { + for (ioff = 0, t = s, umlen = umltot; t <= send ; ioff++) { set_pat_start(p, t-s); if (pattrylen(p, t, umlen, 0, &patstralloc, ioff)) { *sp = get_match_ret(&imd, t-s, umltot); @@ -2926,6 +2939,8 @@ igetmatch(char **sp, Patprog p, int fl, int n, char *replstr, } if (fl & SUB_START) break; + if (t == send) + break; umlen -= iincchar(&t, send - t); } if (!(fl & SUB_START) && pattrylen(p, send, 0, 0, @@ -2958,7 +2973,7 @@ igetmatch(char **sp, Patprog p, int fl, int n, char *replstr, do { /* loop over all matches for global substitution */ matched = 0; - for (; t < send; ioff++) { + for (; t <= send; ioff++) { /* Find the longest match from this position. */ set_pat_start(p, t-s); if (pattrylen(p, t, umlen, 0, &patstralloc, ioff)) { @@ -3007,15 +3022,19 @@ igetmatch(char **sp, Patprog p, int fl, int n, char *replstr, * which is already marked for replacement. */ matched = 1; + if (t == send) + break; while (t < mpos) { ioff++; umlen -= iincchar(&t, send - t); } break; } + if (t == send) + break; umlen -= iincchar(&t, send - t); } - } while (matched); + } while (matched && t < send); /* * check if we can match a blank string, if so do it * at the start. Goodness knows if this is a good idea @@ -3521,7 +3540,7 @@ zshtokenize(char *s, int flags) } t = s; while (idigit(*++s)); - if (*s != '-') + if (!IS_DASH(*s)) goto cont; while (idigit(*++s)); if (*s != '>') diff --git a/Src/hashtable.c b/Src/hashtable.c index 7c3367568..6ec2ed220 100644 --- a/Src/hashtable.c +++ b/Src/hashtable.c @@ -889,7 +889,7 @@ freeshfuncnode(HashNode hn) freeeprog(shf->funcdef); if (shf->redir) freeeprog(shf->redir); - zsfree(shf->filename); + dircache_set(&shf->filename, NULL); if (shf->sticky) { if (shf->sticky->n_on_opts) zfree(shf->sticky->on_opts, @@ -926,10 +926,13 @@ printshfuncnode(HashNode hn, int printflags) (f->node.flags & PM_UNDEFINED) ? " is an autoload shell function" : " is a shell function"); - if (f->filename && (printflags & PRINT_WHENCE_VERBOSE) && - strcmp(f->filename, f->node.nam) != 0) { + if ((printflags & PRINT_WHENCE_VERBOSE) && f->filename) { printf(" from "); quotedzputs(f->filename, stdout); + if (f->node.flags & PM_LOADDIR) { + printf("/"); + quotedzputs(f->node.nam, stdout); + } } putchar('\n'); return; @@ -949,16 +952,20 @@ printshfuncnode(HashNode hn, int printflags) zoutputtab(stdout); } if (!t) { - char *fopt = "UtTkz"; + char *fopt = "UtTkzc"; int flgs[] = { PM_UNALIASED, PM_TAGGED, PM_TAGGED_LOCAL, - PM_KSHSTORED, PM_ZSHSTORED, 0 + PM_KSHSTORED, PM_ZSHSTORED, PM_CUR_FPATH, 0 }; int fl;; zputs("builtin autoload -X", stdout); for (fl=0;fopt[fl];fl++) if (f->node.flags & flgs[fl]) putchar(fopt[fl]); + if (f->filename && (f->node.flags & PM_LOADDIR)) { + putchar(' '); + zputs(f->filename, stdout); + } } else { zputs(t, stdout); zsfree(t); @@ -1037,6 +1044,24 @@ printshfuncexpand(HashNode hn, int printflags, int expand) text_expand_tabs = save_expand; } +/* + * Get a heap-duplicated name of the shell function, for + * use in tracing. + */ + +/**/ +mod_export char * +getshfuncfile(Shfunc shf) +{ + if (shf->node.flags & PM_LOADDIR) { + return zhtricat(shf->filename, "/", shf->node.nam); + } else if (shf->filename) { + return dupstring(shf->filename); + } else { + return NULL; + } +} + /**************************************/ /* Reserved Word Hash Table Functions */ /**************************************/ @@ -1420,6 +1445,9 @@ freehistdata(Histent he, int unlink) if (!he) return; + if (he == &curline) + return; + if (!(he->node.flags & (HIST_DUP | HIST_TMPSTORE))) removehashnode(histtab, he->node.nam); @@ -1438,3 +1466,150 @@ freehistdata(Histent he, int unlink) } } } + + +/*********************************************************************** + * Directory name cache mechanism + * + * The idea of this is that there are various shell structures, + * notably functions, that record the directories with which they + * are associated. Rather than store the full string each time, + * we store a pointer to the same location and count the references. + * This is optimised so that retrieval is quick at the expense of + * searching the list when setting up the structure, which is a much + * rarer operation. + * + * There is nothing special about the fact that the strings are + * directories, except for the assumptions for efficiency that many + * structures will point to the same one, and that there are not too + * many different directories associated with the shell. + **********************************************************************/ + +struct dircache_entry +{ + /* Name of directory in cache */ + char *name; + /* Number of references to it */ + int refs; +}; + +/* + * dircache is the cache, of length dircache_size. + * dircache_lastentry is the last entry used, an optimisation + * for multiple references to the same directory, e.g + * "autoload /blah/blah/\*". + */ +static struct dircache_entry *dircache, *dircache_lastentry; +static int dircache_size; + +/* + * Set *name to point to a cached version of value. + * value is copied so may come from any source. + * + * If value is NULL, look for the existing value of *name (safe if this + * too is NULL) and remove a reference to it from the cache. If it's + * not found in the cache, it's assumed to be an allocated string and + * freed --- this currently occurs for a shell function that's been + * loaded as the filename is now a full path, not just a directory, + * though we may one day optimise this to a cached directory plus a + * name, too. Note --- the function does *not* otherwise check + * if *name points to something already cached, so this is + * necessary any time *name may already be in the cache. + */ + +/**/ +mod_export void +dircache_set(char **name, char *value) +{ + struct dircache_entry *dcptr, *dcnew; + + if (!value) { + if (!*name) + return; + if (!dircache_size) { + zsfree(*name); + *name = NULL; + return; + } + + for (dcptr = dircache; dcptr < dircache + dircache_size; dcptr++) + { + /* Must be a pointer much, not a string match */ + if (*name == dcptr->name) + { + --dcptr->refs; + if (!dcptr->refs) { + ptrdiff_t ind = dcptr - dircache; + zsfree(dcptr->name); + --dircache_size; + + if (!dircache_size) { + zfree(dircache, sizeof(*dircache)); + dircache = NULL; + dircache_lastentry = NULL; + *name = NULL; + return; + } + dcnew = (struct dircache_entry *) + zalloc(dircache_size * sizeof(*dcnew)); + if (ind) + memcpy(dcnew, dircache, ind * sizeof(*dcnew)); + if (ind < dircache_size) + memcpy(dcnew + ind, dcptr + 1, + (dircache_size - ind) * sizeof(*dcnew)); + zfree(dircache, (dircache_size+1)*sizeof(*dcnew)); + dircache = dcnew; + dircache_lastentry = NULL; + } + *name = NULL; + return; + } + } + zsfree(*name); + *name = NULL; + } else { + /* + * As the function path has been resolved to a particular + * location, we'll store it as an absolute path. + */ + if (*value != '/') { + value = zhtricat(metafy(zgetcwd(), -1, META_HEAPDUP), + "/", value); + value = xsymlink(value, 1); + } + /* + * We'll maintain the cache at exactly the right size rather + * than overallocating. The rationale here is that typically + * we'll get a lot of functions in a small number of directories + * so the complexity overhead of maintaining a separate count + * isn't really matched by the efficiency gain. + */ + if (dircache_lastentry && + !strcmp(value, dircache_lastentry->name)) { + *name = dircache_lastentry->name; + ++dircache_lastentry->refs; + return; + } else if (!dircache_size) { + dircache_size = 1; + dcptr = dircache = + (struct dircache_entry *)zalloc(sizeof(*dircache)); + } else { + for (dcptr = dircache; dcptr < dircache + dircache_size; dcptr++) + { + if (!strcmp(value, dcptr->name)) { + *name = dcptr->name; + ++dcptr->refs; + return; + } + } + ++dircache_size; + dircache = (struct dircache_entry *) + zrealloc(dircache, sizeof(*dircache) * dircache_size); + dcptr = dircache + dircache_size - 1; + } + dcptr->name = ztrdup(value); + *name = dcptr->name; + dcptr->refs = 1; + dircache_lastentry = dcptr; + } +} diff --git a/Src/hist.c b/Src/hist.c index 97fd34039..da5a8b29f 100644 --- a/Src/hist.c +++ b/Src/hist.c @@ -253,6 +253,7 @@ hist_context_save(struct hist_stack *hs, int toplevel) hs->hwend = hwend; hs->addtoline = addtoline; hs->hlinesz = hlinesz; + hs->defev = defev; /* * We save and restore the command stack with history * as it's visible to the user interactively, so if @@ -296,6 +297,7 @@ hist_context_restore(const struct hist_stack *hs, int toplevel) hwend = hs->hwend; addtoline = hs->addtoline; hlinesz = hs->hlinesz; + defev = hs->defev; if (cmdstack) zfree(cmdstack, CMDSTACKSZ); cmdstack = hs->cstack; @@ -1418,7 +1420,7 @@ hend(Eprog prog) DPUTS(hptr < chline, "History end pointer off start of line"); *hptr = '\0'; } - { + if (*chline) { LinkList hookargs = newlinklist(); int save_errflag = errflag; errflag = 0; @@ -1427,6 +1429,7 @@ hend(Eprog prog) addlinknode(hookargs, chline); callhookfunc("zshaddhistory", hookargs, 1, &hookret); + errflag &= ~ERRFLAG_ERROR; errflag |= save_errflag; } /* For history sharing, lock history file once for both read and write */ diff --git a/Src/init.c b/Src/init.c index c12043b88..d8c26aca2 100644 --- a/Src/init.c +++ b/Src/init.c @@ -376,12 +376,12 @@ parseopts(char *nam, char ***argvp, char *new_opts, char **cmdp, *argv = "--"; while (*++*argv) { if (**argv == '-') { - if(!argv[0][1]) { + if (!argv[0][1]) { /* The pseudo-option `--' signifies the end of options. */ argv++; goto doneoptions; } - if(*argv != args+1 || **argv != '-') + if (nam || *argv != args+1 || **argv != '-') goto badoptionstring; /* GNU-style long options */ ++*argv; @@ -790,7 +790,7 @@ init_term(void) tcstr[TCCLEARSCREEN] = ztrdup("\14"); tclen[TCCLEARSCREEN] = 1; } - rprompt_indent = 1; + rprompt_indent = 1; /* If you change this, update rprompt_indent_unsetfn() */ /* The following is an attempt at a heuristic, * but it fails in some cases */ /* rprompt_indent = ((hasam && !hasbw) || hasye || !tccan(TCLEFT)); */ diff --git a/Src/input.c b/Src/input.c index eb968ea72..9787dedf6 100644 --- a/Src/input.c +++ b/Src/input.c @@ -144,9 +144,10 @@ shingetline(void) int q = queue_signal_level(); p = buf; + winch_unblock(); + dont_queue_signals(); for (;;) { - winch_unblock(); - dont_queue_signals(); + /* Can't fgets() here because we need to accept '\0' bytes */ do { errno = 0; c = fgetc(bshin); @@ -176,7 +177,8 @@ shingetline(void) ll += p - buf; line[ll] = '\0'; p = buf; - unqueue_signals(); + winch_unblock(); + dont_queue_signals(); } } } @@ -670,3 +672,30 @@ ingetptr(void) { return inbufptr; } + +/* + * Check if the current input line, including continuations, is + * expanding an alias. This does not detect alias expansions that + * have been fully processed and popped from the input stack. + * If there is an alias, the most recently expanded is returned, + * else NULL. + */ + +/**/ +char *input_hasalias(void) +{ + int flags = inbufflags; + struct instacks *instackptr = instacktop; + + for (;;) + { + if (!(flags & INP_CONT)) + break; + instackptr--; + if (instackptr->alias) + return instackptr->alias->node.nam; + flags = instackptr->flags; + } + + return NULL; +} diff --git a/Src/jobs.c b/Src/jobs.c index d1b98ac4d..66dfb5a7e 100644 --- a/Src/jobs.c +++ b/Src/jobs.c @@ -277,6 +277,10 @@ handle_sub(int job, int fg) (!jn->procs->next || cp || jn->procs->pid != jn->gleader)) attachtty(jn->gleader); kill(sj->other, SIGCONT); + if (jn->stat & STAT_DISOWN) + { + deletejob(jn, 1); + } } curjob = jn - jobtab; } else if (sj->stat & STAT_STOPPED) { @@ -2288,8 +2292,10 @@ bin_fg(char *name, char **argv, Options ops, int func) case BIN_FG: case BIN_BG: case BIN_WAIT: - if (func == BIN_BG) + if (func == BIN_BG) { jobtab[job].stat |= STAT_NOSTTY; + jobtab[job].stat &= ~STAT_CURSH; + } if ((stopped = (jobtab[job].stat & STAT_STOPPED))) { makerunning(jobtab + job); if (func == BIN_BG) { @@ -2373,6 +2379,10 @@ bin_fg(char *name, char **argv, Options ops, int func) printjob(job + (oldjobtab ? oldjobtab : jobtab), lng, 2); break; case BIN_DISOWN: + if (jobtab[job].stat & STAT_SUPERJOB) { + jobtab[job].stat |= STAT_DISOWN; + continue; + } if (jobtab[job].stat & STAT_STOPPED) { char buf[20], *pids = ""; @@ -1359,17 +1359,13 @@ gettokstr(int c, int sub) case LX2_DASH: /* * - shouldn't be treated as a special character unless - * we're in a pattern. Howeve,simply counting "[" doesn't - * work as []a-z] is a valid expression and we don't know - * down here what this "[" is for as $foo[stuff] is valid - * in zsh. So just detect an opening [, which is enough - * to turn this into a pattern; the Dash will be harmlessly - * untokenised if not wanted. + * we're in a pattern. Unfortunately, working out for + * sure in complicated expressions whether we're in a + * pattern is tricky. So we'll make it special and + * turn it back any time we don't need it special. + * This is not ideal as it's a lot of work. */ - if (seen_brct) - c = Dash; - else - c = '-'; + c = Dash; break; case LX2_BANG: /* @@ -2064,9 +2060,7 @@ skipcomm(void) int new_lexstop, new_lex_add_raw; int save_infor = infor; struct lexbufstate new_lexbuf; - int noalias = noaliases; - noaliases = 1; infor = 0; cmdpush(CS_CMDSUBST); SETPARBEGIN @@ -2193,7 +2187,6 @@ skipcomm(void) SETPAREND cmdpop(); infor = save_infor; - noaliases = noalias; return lexstop; #endif diff --git a/Src/linklist.c b/Src/linklist.c index 3aa8125d9..85d9bb367 100644 --- a/Src/linklist.c +++ b/Src/linklist.c @@ -348,6 +348,35 @@ newsizedlist(int size) } /* + * Join two linked lists. Neither may be null, though either + * may be empty. + * + * It is assumed the pieces come from the heap, but if not it is + * safe to free LinkList second. + */ + +/**/ +mod_export LinkList +joinlists(LinkList first, LinkList second) +{ + LinkNode moveme = firstnode(second); + if (moveme) { + if (firstnode(first)) { + LinkNode anchor = lastnode(first); + anchor->next = moveme; + moveme->prev = anchor; + } else { + first->list.first = moveme; + moveme->prev = &first->node; + } + first->list.last = second->list.last; + + second->list.first = second->list.last = NULL; + } + return first; +} + +/* * Return the node whose data is the pointer "dat", else NULL. * Can be used as a boolean test. */ diff --git a/Src/loop.c b/Src/loop.c index ae87b2f5f..f7eae307b 100644 --- a/Src/loop.c +++ b/Src/loop.c @@ -620,7 +620,9 @@ execcase(Estate state, int do_exec) spprog = state->prog->pats + npat; pprog = NULL; pat = NULL; - + + queue_signals(); + if (isset(XTRACE)) { int htok = 0; pat = dupstring(ecrawstr(state->prog, state->pc, &htok)); @@ -657,6 +659,8 @@ execcase(Estate state, int do_exec) patok = anypatok = 1; state->pc += 2; nalts--; + + unqueue_signals(); } state->pc += 2 * nalts; if (isset(XTRACE)) { diff --git a/Src/math.c b/Src/math.c index 37981cf22..f9613001a 100644 --- a/Src/math.c +++ b/Src/math.c @@ -463,7 +463,7 @@ lexconstant(void) char *nptr; nptr = ptr; - if (*nptr == '-') + if (IS_DASH(*nptr)) nptr++; if (*nptr == '0') { @@ -527,7 +527,7 @@ lexconstant(void) } if (*nptr == 'e' || *nptr == 'E') { nptr++; - if (*nptr == '+' || *nptr == '-') + if (*nptr == '+' || IS_DASH(*nptr)) nptr++; while (idigit(*nptr) || *nptr == '_') nptr++; @@ -599,7 +599,8 @@ zzlex(void) } return (unary) ? UPLUS : PLUS; case '-': - if (*ptr == '-') { + case Dash: + if (IS_DASH(*ptr)) { ptr++; return (unary) ? PREMINUS : POSTMINUS; } @@ -974,7 +975,7 @@ callmathfunc(char *o) a[strlen(a) - 1] = '\0'; if ((f = getmathfunc(n, 1))) { - if (f->flags & MFF_STR) { + if ((f->flags & (MFF_STR|MFF_USERFUNC)) == MFF_STR) { return f->sfunc(n, a, f->funcid); } else { int argc = 0; @@ -987,22 +988,34 @@ callmathfunc(char *o) addlinknode(l, n); } - while (iblank(*a)) - a++; + if (f->flags & MFF_STR) { + if (!*a) { + addlinknode(l, dupstring("")); + argc++; + } + } else { + while (iblank(*a)) + a++; + } while (*a) { if (*a) { argc++; if (f->flags & MFF_USERFUNC) { /* need to pass strings */ char *str; - marg = mathevall(a, MPREC_ARG, &a); - if (marg.type & MN_FLOAT) { - /* convfloat is off the heap */ - str = convfloat(marg.u.d, 0, 0, NULL); + if (f->flags & MFF_STR) { + str = dupstring(a); + a = ""; } else { - char buf[BDIGBUFSIZE]; - convbase(buf, marg.u.l, 10); - str = dupstring(buf); + marg = mathevall(a, MPREC_ARG, &a); + if (marg.type & MN_FLOAT) { + /* convfloat is off the heap */ + str = convfloat(marg.u.d, 0, 0, NULL); + } else { + char buf[BDIGBUFSIZE]; + convbase(buf, marg.u.l, 10); + str = dupstring(buf); + } } addlinknode(l, str); } else { diff --git a/Src/module.c b/Src/module.c index 41f142adb..21d68b1ac 100644 --- a/Src/module.c +++ b/Src/module.c @@ -2326,7 +2326,7 @@ load_module(char const *name, Feature_enables enablesarr, int silent) /**/ mod_export int -require_module(const char *module, Feature_enables features) +require_module(const char *module, Feature_enables features, int silent) { Module m = NULL; int ret = 0; @@ -2336,7 +2336,7 @@ require_module(const char *module, Feature_enables features) m = find_module(module, FINDMOD_ALIASP, &module); if (!m || !m->u.handle || (m->node.flags & MOD_UNLOAD)) - ret = load_module(module, features, 0); + ret = load_module(module, features, silent); else ret = do_module_features(m, features, 0); unqueue_signals(); @@ -2972,7 +2972,7 @@ bin_zmodload_load(char *nam, char **args, Options ops) } else { /* load modules */ for (; *args; args++) { - int tmpret = require_module(*args, NULL); + int tmpret = require_module(*args, NULL, OPT_ISSET(ops,'s')); if (tmpret && ret != 1) ret = tmpret; } @@ -3242,7 +3242,7 @@ bin_zmodload_features(const char *nam, char **args, Options ops) fep->str = NULL; fep->pat = NULL; - return require_module(modname, features); + return require_module(modname, features, OPT_ISSET(ops,'s')); } @@ -3403,14 +3403,14 @@ ensurefeature(const char *modname, const char *prefix, const char *feature) struct feature_enables features[2]; if (!feature) - return require_module(modname, NULL); + return require_module(modname, NULL, 0); f = dyncat(prefix, feature); features[0].str = f; features[0].pat = NULL; features[1].str = NULL; features[1].pat = NULL; - return require_module(modname, features); + return require_module(modname, features, 0); } /* diff --git a/Src/options.c b/Src/options.c index 18619c851..2b5795bab 100644 --- a/Src/options.c +++ b/Src/options.c @@ -78,6 +78,7 @@ mod_export HashTable optiontab; */ static struct optname optns[] = { {{NULL, "aliases", OPT_EMULATE|OPT_ALL}, ALIASESOPT}, +{{NULL, "aliasfuncdef", OPT_EMULATE|OPT_BOURNE}, ALIASFUNCDEF}, {{NULL, "allexport", OPT_EMULATE}, ALLEXPORT}, {{NULL, "alwayslastprompt", OPT_ALL}, ALWAYSLASTPROMPT}, {{NULL, "alwaystoend", 0}, ALWAYSTOEND}, @@ -257,6 +258,7 @@ static struct optname optns[] = { {{NULL, "verbose", 0}, VERBOSE}, {{NULL, "vi", 0}, VIMODE}, {{NULL, "warncreateglobal", OPT_EMULATE}, WARNCREATEGLOBAL}, +{{NULL, "warnnestedvar", OPT_EMULATE}, WARNNESTEDVAR}, {{NULL, "xtrace", 0}, XTRACE}, {{NULL, "zle", OPT_SPECIAL}, USEZLE}, {{NULL, "braceexpand", OPT_ALIAS}, /* ksh/bash */ -IGNOREBRACES}, @@ -645,7 +647,7 @@ bin_setopt(char *nam, char **args, UNUSED(Options ops), int isun) /* Expand the current arg. */ tokenize(s); - if (!(pprog = patcompile(s, PAT_STATIC, NULL))) { + if (!(pprog = patcompile(s, PAT_HEAPDUP, NULL))) { zwarnnam(nam, "bad pattern: %s", *args); continue; } diff --git a/Src/params.c b/Src/params.c index c64d7486b..6fbee880c 100644 --- a/Src/params.c +++ b/Src/params.c @@ -128,6 +128,11 @@ struct timeval shtimer; /**/ mod_export int termflags; +/* Forward declaration */ + +static void +rprompt_indent_unsetfn(Param pm, int exp); + /* Standard methods for get/set/unset pointers in parameters */ /**/ @@ -241,6 +246,9 @@ static const struct gsu_integer argc_gsu = static const struct gsu_array pipestatus_gsu = { pipestatgetfn, pipestatsetfn, stdunsetfn }; +static const struct gsu_integer rprompt_indent_gsu = +{ intvargetfn, zlevarsetfn, rprompt_indent_unsetfn }; + /* Nodes for special parameters for parameter hash table */ #ifdef HAVE_UNION_INIT @@ -327,7 +335,7 @@ IPDEF4("ZSH_SUBSHELL", &zsh_subshell), #define IPDEF5U(A,B,F) {{NULL,A,PM_INTEGER|PM_SPECIAL|PM_UNSET},BR((void *)B),GSU(F),10,0,NULL,NULL,NULL,0} IPDEF5("COLUMNS", &zterm_columns, zlevar_gsu), IPDEF5("LINES", &zterm_lines, zlevar_gsu), -IPDEF5U("ZLE_RPROMPT_INDENT", &rprompt_indent, zlevar_gsu), +IPDEF5U("ZLE_RPROMPT_INDENT", &rprompt_indent, rprompt_indent_gsu), IPDEF5("SHLVL", &shlvl, varinteger_gsu), /* Don't import internal integer status variables. */ @@ -353,6 +361,17 @@ IPDEF7("PS3", &prompt3), IPDEF7R("PS4", &prompt4), IPDEF7("SPROMPT", &sprompt), +#define IPDEF9F(A,B,C,D) {{NULL,A,D|PM_ARRAY|PM_SPECIAL|PM_DONTIMPORT},BR((void *)B),GSU(vararray_gsu),0,0,NULL,C,NULL,0} +#define IPDEF9(A,B,C) IPDEF9F(A,B,C,0) +IPDEF9F("*", &pparams, NULL, PM_ARRAY|PM_SPECIAL|PM_DONTIMPORT|PM_READONLY), +IPDEF9F("@", &pparams, NULL, PM_ARRAY|PM_SPECIAL|PM_DONTIMPORT|PM_READONLY), + +/* + * This empty row indicates the end of parameters available in + * all emulations. + */ +{{NULL,NULL,0},BR(NULL),NULL_GSU,0,0,NULL,NULL,NULL,0}, + #define IPDEF8(A,B,C,D) {{NULL,A,D|PM_SCALAR|PM_SPECIAL},BR((void *)B),GSU(colonarr_gsu),0,0,NULL,C,NULL,0} IPDEF8("CDPATH", &cdpath, "cdpath", 0), IPDEF8("FIGNORE", &fignore, "fignore", 0), @@ -366,17 +385,6 @@ IPDEF8("ZSH_EVAL_CONTEXT", &zsh_eval_context, "zsh_eval_context", PM_READONLY), /* MODULE_PATH is not imported for security reasons */ IPDEF8("MODULE_PATH", &module_path, "module_path", PM_DONTIMPORT|PM_RESTRICTED), -#define IPDEF9F(A,B,C,D) {{NULL,A,D|PM_ARRAY|PM_SPECIAL|PM_DONTIMPORT},BR((void *)B),GSU(vararray_gsu),0,0,NULL,C,NULL,0} -#define IPDEF9(A,B,C) IPDEF9F(A,B,C,0) -IPDEF9F("*", &pparams, NULL, PM_ARRAY|PM_SPECIAL|PM_DONTIMPORT|PM_READONLY), -IPDEF9F("@", &pparams, NULL, PM_ARRAY|PM_SPECIAL|PM_DONTIMPORT|PM_READONLY), - -/* - * This empty row indicates the end of parameters available in - * all emulations. - */ -{{NULL,NULL,0},BR(NULL),NULL_GSU,0,0,NULL,NULL,NULL,0}, - #define IPDEF10(A,B) {{NULL,A,PM_ARRAY|PM_SPECIAL},BR(NULL),GSU(B),10,0,NULL,NULL,NULL,0} /* @@ -416,6 +424,26 @@ IPDEF10("pipestatus", pipestatus_gsu), }; /* + * Alternative versions of colon-separated path parameters for + * sh emulation. These don't link to the array versions. + */ +static initparam special_params_sh[] = { +IPDEF8("CDPATH", &cdpath, NULL, 0), +IPDEF8("FIGNORE", &fignore, NULL, 0), +IPDEF8("FPATH", &fpath, NULL, 0), +IPDEF8("MAILPATH", &mailpath, NULL, 0), +IPDEF8("WATCH", &watch, NULL, 0), +IPDEF8("PATH", &path, NULL, PM_RESTRICTED), +IPDEF8("PSVAR", &psvar, NULL, 0), +IPDEF8("ZSH_EVAL_CONTEXT", &zsh_eval_context, NULL, PM_READONLY), + +/* MODULE_PATH is not imported for security reasons */ +IPDEF8("MODULE_PATH", &module_path, NULL, PM_DONTIMPORT|PM_RESTRICTED), + +{{NULL,NULL,0},BR(NULL),NULL_GSU,0,0,NULL,NULL,NULL,0}, +}; + +/* * Special way of referring to the positional parameters. Unlike $* * and $@, this is not readonly. This parameter is not directly * visible in user space. @@ -745,9 +773,13 @@ createparamtable(void) /* Add the special parameters to the hash table */ for (ip = special_params; ip->node.nam; ip++) paramtab->addnode(paramtab, ztrdup(ip->node.nam), ip); - if (!EMULATION(EMULATE_SH|EMULATE_KSH)) + if (EMULATION(EMULATE_SH|EMULATE_KSH)) { + for (ip = special_params_sh; ip->node.nam; ip++) + paramtab->addnode(paramtab, ztrdup(ip->node.nam), ip); + } else { while ((++ip)->node.nam) paramtab->addnode(paramtab, ztrdup(ip->node.nam), ip); + } argvparam = (Param) &argvparam_pm; @@ -1175,7 +1207,7 @@ getarg(char **str, int *inv, Value v, int a2, zlong *w, int *prevcharlen, int *nextcharlen) { int hasbeg = 0, word = 0, rev = 0, ind = 0, down = 0, l, i, ishash; - int keymatch = 0, needtok = 0, arglen, len; + int keymatch = 0, needtok = 0, arglen, len, inpar = 0; char *s = *str, *sep = NULL, *t, sav, *d, **ta, **p, *tt, c; zlong num = 1, beg = 0, r = 0, quote_arg = 0; Patprog pprog = NULL; @@ -1314,8 +1346,9 @@ getarg(char **str, int *inv, Value v, int a2, zlong *w, } for (t = s, i = 0; - (c = *t) && ((c != Outbrack && - (ishash || c != ',')) || i); t++) { + (c = *t) && + ((c != Outbrack && (ishash || c != ',')) || i || inpar); + t++) { /* Untokenize inull() except before brackets and double-quotes */ if (inull(c)) { c = t[1]; @@ -1336,6 +1369,10 @@ getarg(char **str, int *inv, Value v, int a2, zlong *w, i++; else if (c == ']' || c == Outbrack) i--; + if (c == '(' || c == Inpar) + inpar++; + else if (c == ')' || c == Outpar) + inpar--; if (ispecial(c)) needtok = 1; } @@ -1979,7 +2016,9 @@ fetchvalue(Value v, char **pptr, int bracks, int flags) *s++ = '$'; else if (c == Star) *s++ = '*'; - else if (c == '#' || c == '-' || c == '?' || c == '$' || + else if (IS_DASH(c)) + *s++ = '-'; + else if (c == '#' || c == '?' || c == '$' || c == '!' || c == '@' || c == '*') s++; else @@ -2708,24 +2747,73 @@ setarrvalue(Value v, char **val) post_assignment_length += pre_assignment_length - v->end; } - p = new = (char **) zalloc(sizeof(char *) - * (post_assignment_length + 1)); + if (pre_assignment_length == post_assignment_length + && v->pm->gsu.a->setfn == arrsetfn + /* ... and isn't something that arrsetfn() treats specially */ + && 0 == (v->pm->node.flags & (PM_SPECIAL|PM_UNIQUE)) + && NULL == v->pm->ename) + { + /* v->start is 0-based */ + p = old + v->start; + for (r = val; *r;) { + /* Free previous string */ + zsfree(*p); + /* Give away ownership of the string */ + *p++ = *r++; + } + } else { + /* arr+=( ... ) + * arr[${#arr}+x,...]=( ... ) */ + if (post_assignment_length > pre_assignment_length && + pre_assignment_length <= v->start && + pre_assignment_length > 0 && + v->pm->gsu.a->setfn == arrsetfn) + { + p = new = (char **) zrealloc(old, sizeof(char *) + * (post_assignment_length + 1)); + + p += pre_assignment_length; /* after old elements */ + + /* Consider 1 < 0, case for a=( 1 ); a[1,..] = + * 1 < 1, case for a=( 1 ); a[2,..] = */ + if (pre_assignment_length < v->start) { + for (i = pre_assignment_length; i < v->start; i++) { + *p++ = ztrdup(""); + } + } + + for (r = val; *r;) { + /* Give away ownership of the string */ + *p++ = *r++; + } + + /* v->end doesn't matter: + * a=( 1 2 ); a[4,100]=( a b ); echo "${(q@)a}" + * 1 2 '' a b */ + *p = NULL; + + v->pm->u.arr = NULL; + v->pm->gsu.a->setfn(v->pm, new); + } else { + p = new = (char **) zalloc(sizeof(char *) + * (post_assignment_length + 1)); + for (i = 0; i < v->start; i++) + *p++ = i < pre_assignment_length ? ztrdup(*q++) : ztrdup(""); + for (r = val; *r;) { + /* Give away ownership of the string */ + *p++ = *r++; + } + if (v->end < pre_assignment_length) + for (q = old + v->end; *q;) + *p++ = ztrdup(*q++); + *p = NULL; + + v->pm->gsu.a->setfn(v->pm, new); + } - for (i = 0; i < v->start; i++) - *p++ = i < pre_assignment_length ? ztrdup(*q++) : ztrdup(""); - for (r = val; *r;) { - /* Give away ownership of the string */ - *p++ = *r++; + DPUTS2(p - new != post_assignment_length, "setarrvalue: wrong allocation: %d 1= %lu", + post_assignment_length, (unsigned long)(p - new)); } - if (v->end < pre_assignment_length) - for (q = old + v->end; *q;) - *p++ = ztrdup(*q++); - *p = NULL; - - DPUTS2(p - new != post_assignment_length, "setarrvalue: wrong allocation: %d 1= %lu", - post_assignment_length, (unsigned long)(p - new)); - - v->pm->gsu.a->setfn(v->pm, new); /* Ownership of all strings has been * given away, can plainly free */ @@ -2833,20 +2921,51 @@ gethkparam(char *s) return NULL; } +/* + * Function behind WARNCREATEGLOBAL and WARNNESTEDVAR option. + * + * For WARNNESTEDVAR: + * Called when the variable is created. + * Apply heuristics to see if this variable was just created + * globally but in a local context. + * + * For WARNNESTEDVAR: + * Called when the variable already exists and is set. + * Apply heuristics to see if this variable is setting + * a variable that was created in a less nested function + * or globally. + */ + /**/ static void -check_warn_create(Param pm, const char *pmtype) +check_warn_pm(Param pm, const char *pmtype, int created, + int may_warn_about_nested_vars) { Funcstack i; - if (pm->level != 0 || (pm->node.flags & PM_SPECIAL)) + if (!may_warn_about_nested_vars && !created) + return; + + if (created && isset(WARNCREATEGLOBAL)) { + if (locallevel <= forklevel || pm->level != 0) + return; + } else if (!created && isset(WARNNESTEDVAR)) { + if (pm->level >= locallevel) + return; + } else + return; + + if (pm->node.flags & PM_SPECIAL) return; for (i = funcstack; i; i = i->prev) { if (i->tp == FS_FUNC) { + char *msg; DPUTS(!i->name, "funcstack entry with no name"); - zwarn("%s parameter %s created globally in function %s", - pmtype, pm->node.nam, i->name); + msg = created ? + "%s parameter %s created globally in function %s" : + "%s parameter %s set in enclosing scope in function %s"; + zwarn(msg, pmtype, pm->node.nam, i->name); break; } } @@ -2862,7 +2981,7 @@ assignsparam(char *s, char *val, int flags) char *ss, *copy, *var; size_t lvar; mnumber lhs, rhs; - int sstart; + int sstart, created = 0; if (!isident(s)) { zerr("not an identifier: %s", s); @@ -2873,9 +2992,10 @@ assignsparam(char *s, char *val, int flags) queue_signals(); if ((ss = strchr(s, '['))) { *ss = '\0'; - if (!(v = getvalue(&vbuf, &s, 1))) + if (!(v = getvalue(&vbuf, &s, 1))) { createparam(t, PM_ARRAY); - else { + created = 1; + } else { if (v->pm->node.flags & PM_READONLY) { zerr("read-only variable: %s", v->pm->node.nam); *ss = '['; @@ -2883,23 +3003,27 @@ assignsparam(char *s, char *val, int flags) unqueue_signals(); return NULL; } - flags &= ~ASSPM_WARN_CREATE; + /* + * Parameter defined here is a temporary bogus one. + * Don't warn about anything. + */ + flags &= ~ASSPM_WARN; } *ss = '['; v = NULL; } else { - if (!(v = getvalue(&vbuf, &s, 1))) + if (!(v = getvalue(&vbuf, &s, 1))) { createparam(t, PM_SCALAR); - else if ((((v->pm->node.flags & PM_ARRAY) && !(flags & ASSPM_AUGMENT)) || + created = 1; + } else if ((((v->pm->node.flags & PM_ARRAY) && !(flags & ASSPM_AUGMENT)) || (v->pm->node.flags & PM_HASHED)) && !(v->pm->node.flags & (PM_SPECIAL|PM_TIED)) && unset(KSHARRAYS)) { unsetparam(t); createparam(t, PM_SCALAR); + /* not regarded as a new creation */ v = NULL; } - else - flags &= ~ASSPM_WARN_CREATE; } if (!v && !(v = getvalue(&vbuf, &t, 1))) { unqueue_signals(); @@ -2907,8 +3031,8 @@ assignsparam(char *s, char *val, int flags) /* errflag |= ERRFLAG_ERROR; */ return NULL; } - if (flags & ASSPM_WARN_CREATE) - check_warn_create(v->pm, "scalar"); + if (flags & ASSPM_WARN) + check_warn_pm(v->pm, "scalar", created, 1); if (flags & ASSPM_AUGMENT) { if (v->start == 0 && v->end == -1) { switch (PM_TYPE(v->pm->node.flags)) { @@ -2989,9 +3113,7 @@ assignsparam(char *s, char *val, int flags) mod_export Param setsparam(char *s, char *val) { - return assignsparam( - s, val, isset(WARNCREATEGLOBAL) && locallevel > forklevel ? - ASSPM_WARN_CREATE : 0); + return assignsparam(s, val, ASSPM_WARN); } /**/ @@ -3002,6 +3124,8 @@ assignaparam(char *s, char **val, int flags) Value v; char *t = s; char *ss; + int created = 0; + int may_warn_about_nested_vars = 1; if (!isident(s)) { zerr("not an identifier: %s", s); @@ -3012,10 +3136,12 @@ assignaparam(char *s, char **val, int flags) queue_signals(); if ((ss = strchr(s, '['))) { *ss = '\0'; - if (!(v = getvalue(&vbuf, &s, 1))) + if (!(v = getvalue(&vbuf, &s, 1))) { createparam(t, PM_ARRAY); - else - flags &= ~ASSPM_WARN_CREATE; + created = 1; + } else { + may_warn_about_nested_vars = 0; + } *ss = '['; if (v && PM_TYPE(v->pm->node.flags) == PM_HASHED) { unqueue_signals(); @@ -3027,9 +3153,10 @@ assignaparam(char *s, char **val, int flags) } v = NULL; } else { - if (!(v = fetchvalue(&vbuf, &s, 1, SCANPM_ASSIGNING))) + if (!(v = fetchvalue(&vbuf, &s, 1, SCANPM_ASSIGNING))) { createparam(t, PM_ARRAY); - else if (!(PM_TYPE(v->pm->node.flags) & (PM_ARRAY|PM_HASHED)) && + created = 1; + } else if (!(PM_TYPE(v->pm->node.flags) & (PM_ARRAY|PM_HASHED)) && !(v->pm->node.flags & (PM_SPECIAL|PM_TIED))) { int uniq = v->pm->node.flags & PM_UNIQUE; if (flags & ASSPM_AUGMENT) { @@ -3047,8 +3174,6 @@ assignaparam(char *s, char **val, int flags) createparam(t, PM_ARRAY | uniq); v = NULL; } - else - flags &= ~ASSPM_WARN_CREATE; } if (!v) if (!(v = fetchvalue(&vbuf, &t, 1, SCANPM_ASSIGNING))) { @@ -3058,8 +3183,8 @@ assignaparam(char *s, char **val, int flags) return NULL; } - if (flags & ASSPM_WARN_CREATE) - check_warn_create(v->pm, "array"); + if (flags & ASSPM_WARN) + check_warn_pm(v->pm, "array", created, may_warn_about_nested_vars); if (flags & ASSPM_AUGMENT) { if (v->start == 0 && v->end == -1) { if (PM_TYPE(v->pm->node.flags) & PM_ARRAY) { @@ -3087,9 +3212,7 @@ assignaparam(char *s, char **val, int flags) mod_export Param setaparam(char *s, char **aval) { - return assignaparam( - s, aval, isset(WARNCREATEGLOBAL) && locallevel > forklevel ? - ASSPM_WARN_CREATE : 0); + return assignaparam(s, aval, ASSPM_WARN); } /**/ @@ -3118,13 +3241,18 @@ sethparam(char *s, char **val) queue_signals(); if (!(v = fetchvalue(&vbuf, &s, 1, SCANPM_ASSIGNING))) { createparam(t, PM_HASHED); - checkcreate = isset(WARNCREATEGLOBAL) && locallevel > forklevel; - } else if (!(PM_TYPE(v->pm->node.flags) & PM_HASHED) && - !(v->pm->node.flags & PM_SPECIAL)) { - unsetparam(t); - /* no WARNCREATEGLOBAL check here as parameter already existed */ - createparam(t, PM_HASHED); - v = NULL; + checkcreate = 1; + } else if (!(PM_TYPE(v->pm->node.flags) & PM_HASHED)) { + if (!(v->pm->node.flags & PM_SPECIAL)) { + unsetparam(t); + /* no WARNCREATEGLOBAL check here as parameter already existed */ + createparam(t, PM_HASHED); + v = NULL; + } else { + zerr("%s: can't change type of a special parameter", t); + unqueue_signals(); + return NULL; + } } if (!v) if (!(v = fetchvalue(&vbuf, &t, 1, SCANPM_ASSIGNING))) { @@ -3132,8 +3260,7 @@ sethparam(char *s, char **val) /* errflag |= ERRFLAG_ERROR; */ return NULL; } - if (checkcreate) - check_warn_create(v->pm, "associative array"); + check_warn_pm(v->pm, "associative array", checkcreate, 1); setarrvalue(v, val); unqueue_signals(); return v->pm; @@ -3142,11 +3269,12 @@ sethparam(char *s, char **val) /* * Set a generic shell number, floating point or integer. + * Option to warn on setting. */ /**/ -Param -setnparam(char *s, mnumber val) +mod_export Param +assignnparam(char *s, mnumber val, int flags) { struct value vbuf; Value v; @@ -3198,14 +3326,41 @@ setnparam(char *s, mnumber val) unqueue_signals(); return NULL; } - if (!was_unset && isset(WARNCREATEGLOBAL) && locallevel > forklevel) - check_warn_create(v->pm, "numeric"); + if (flags & ASSPM_WARN) + check_warn_pm(v->pm, "numeric", !was_unset, 1); + } else { + if (flags & ASSPM_WARN) + check_warn_pm(v->pm, "numeric", 0, 1); } setnumvalue(v, val); unqueue_signals(); return v->pm; } +/* + * Set a generic shell number, floating point or integer. + * Warn on setting based on option. + */ + +/**/ +mod_export Param +setnparam(char *s, mnumber val) +{ + return assignnparam(s, val, ASSPM_WARN); +} + +/* Simplified interface to assignnparam */ + +/**/ +mod_export Param +assigniparam(char *s, zlong val, int flags) +{ + mnumber mnval; + mnval.type = MN_INTEGER; + mnval.u.l = val; + return assignnparam(s, mnval, flags); +} + /* Simplified interface to setnparam */ /**/ @@ -3215,7 +3370,7 @@ setiparam(char *s, zlong val) mnumber mnval; mnval.type = MN_INTEGER; mnval.u.l = val; - return setnparam(s, mnval); + return assignnparam(s, mnval, ASSPM_WARN); } /* @@ -3234,10 +3389,7 @@ setiparam_no_convert(char *s, zlong val) */ char buf[BDIGBUFSIZE]; convbase(buf, val, 10); - return assignsparam( - s, ztrdup(buf), - isset(WARNCREATEGLOBAL) && locallevel > forklevel ? - ASSPM_WARN_CREATE : 0); + return assignsparam(s, ztrdup(buf), ASSPM_WARN); } /* Unset a parameter */ @@ -3458,6 +3610,8 @@ strsetfn(Param pm, char *x) pm->node.flags |= PM_NAMEDDIR; adduserdir(pm->node.nam, x, 0, 0); } + /* If you update this function, you may need to update the + * `Implement remainder of strsetfn' block in assignstrvalue(). */ } /* Function to get value of an array parameter */ @@ -3485,6 +3639,8 @@ arrsetfn(Param pm, char **x) /* Arrays tied to colon-arrays may need to fix the environment */ if (pm->ename && x) arrfixenv(pm->ename, x); + /* If you extend this function, update the list of conditions in + * setarrvalue(). */ } /* Function to get value of an association parameter */ @@ -3621,6 +3777,16 @@ zlevarsetfn(Param pm, zlong x) adjustwinsize(2 + (p == &zterm_columns)); } + +/* Implements gsu_integer.unsetfn for ZLE_RPROMPT_INDENT; see stdunsetfn() */ + +static void +rprompt_indent_unsetfn(Param pm, int exp) +{ + stdunsetfn(pm, exp); + rprompt_indent = 1; /* Keep this in sync with init_term() */ +} + /* Function to set value of generic special scalar * * parameter. data is pointer to a character pointer * * representing the scalar (string). */ @@ -3720,8 +3886,7 @@ colonarrsetfn(Param pm, char *x) *dptr = colonsplit(x, pm->node.flags & PM_UNIQUE); else *dptr = mkarray(NULL); - if (pm->ename) - arrfixenv(pm->node.nam, *dptr); + arrfixenv(pm->node.nam, *dptr); zsfree(x); } diff --git a/Src/parse.c b/Src/parse.c index 50a0d5f9f..ba9cd61eb 100644 --- a/Src/parse.c +++ b/Src/parse.c @@ -394,9 +394,12 @@ ecdel(int p) static wordcode ecstrcode(char *s) { - int l, t = has_token(s); + int l, t; + + unsigned val = hasher(s); if ((l = strlen(s) + 1) && l <= 4) { + t = has_token(s); wordcode c = (t ? 3 : 2); switch (l) { case 4: c |= ((wordcode) STOUC(s[2])) << 19; @@ -410,16 +413,21 @@ ecstrcode(char *s) int cmp; for (pp = &ecstrs; (p = *pp); ) { - if (!(cmp = p->nfunc - ecnfunc) && !(cmp = strcmp(p->str, s))) + if (!(cmp = p->nfunc - ecnfunc) && !(cmp = (((signed)p->hashval) - ((signed)val))) && !(cmp = strcmp(p->str, s))) { return p->offs; + } pp = (cmp < 0 ? &(p->left) : &(p->right)); } + + t = has_token(s); + p = *pp = (Eccstr) zhalloc(sizeof(*p)); p->left = p->right = 0; p->offs = ((ecsoffs - ecssub) << 2) | (t ? 1 : 0); p->aoffs = ecsoffs; p->str = s; p->nfunc = ecnfunc; + p->hashval = val; ecsoffs += l; return p->offs; @@ -1738,6 +1746,7 @@ par_simple(int *cmplx, int nr) { int oecused = ecused, isnull = 1, r, argc = 0, p, isfunc = 0, sr = 0; int c = *cmplx, nrediradd, assignments = 0, ppost = 0, is_typeset = 0; + char *hasalias = input_hasalias(); wordcode postassigns = 0; r = ecused; @@ -1809,6 +1818,8 @@ par_simple(int *cmplx, int nr) } else break; zshlex(); + if (!hasalias) + hasalias = input_hasalias(); } if (tok == AMPER || tok == AMPERBANG) YYERROR(oecused); @@ -1833,12 +1844,14 @@ par_simple(int *cmplx, int nr) if (*ptr == Outbrace && ptr > tokstr + 1) { - if (itype_end(tokstr+1, IIDENT, 0) >= ptr - 1) + if (itype_end(tokstr+1, IIDENT, 0) >= ptr) { char *toksave = tokstr; char *idstring = dupstrpfx(tokstr+1, eptr-tokstr-1); redir_var = 1; zshlex(); + if (!hasalias) + hasalias = input_hasalias(); if (IS_REDIROP(tok) && tokfd == -1) { @@ -1874,6 +1887,8 @@ par_simple(int *cmplx, int nr) argc++; } zshlex(); + if (!hasalias) + hasalias = input_hasalias(); } } else if (IS_REDIROP(tok)) { *cmplx = c = 1; @@ -1902,6 +1917,8 @@ par_simple(int *cmplx, int nr) ecstr(name); ecstr(str); zshlex(); + if (!hasalias) + hasalias = input_hasalias(); } else if (tok == ENVARRAY) { int n, parr; @@ -1936,6 +1953,11 @@ par_simple(int *cmplx, int nr) /* Error if preceding assignments */ if (assignments || postassigns) YYERROR(oecused); + if (hasalias && !isset(ALIASFUNCDEF) && argc && + hasalias != input_hasalias()) { + zwarn("defining function based on alias `%s'", hasalias); + YYERROR(oecused); + } *cmplx = c; lineno = 0; @@ -2016,10 +2038,21 @@ par_simple(int *cmplx, int nr) /* Unnamed function */ int parg = ecadd(0); ecadd(0); - while (tok == STRING) { - ecstr(tokstr); - argc++; - zshlex(); + while (tok == STRING || IS_REDIROP(tok)) { + if (tok == STRING) + { + ecstr(tokstr); + argc++; + zshlex(); + } else { + *cmplx = c = 1; + nrediradd = par_redir(&r, NULL); + p += nrediradd; + if (ppost) + ppost += nrediradd; + sr += nrediradd; + parg += nrediradd; + } } if (argc > 0) *cmplx = 1; @@ -2129,7 +2162,7 @@ par_redir(int *rp, char *idstring) * the definition of WC_REDIR_WORDS. */ ecispace(r, ncodes); *rp = r + ncodes; - ecbuf[r] = WCB_REDIR(type); + ecbuf[r] = WCB_REDIR(type | REDIR_FROM_HEREDOC_MASK); ecbuf[r + 1] = fd1; /* @@ -2303,6 +2336,19 @@ par_cond_1(void) } /* + * Return 1 if condition matches. This also works for non-elided options. + * + * input is test string, may begin - or Dash. + * cond is condition following the -. + */ +static int check_cond(const char *input, const char *cond) +{ + if (!IS_DASH(input[0])) + return 0; + return !strcmp(input + 1, cond); +} + +/* * cond_2 : BANG cond_2 | INPAR { SEPER } cond_2 { SEPER } OUTPAR | STRING STRING STRING @@ -2328,7 +2374,7 @@ par_cond_2(void) s1 = tokstr; condlex(); /* ksh behavior: [ -t ] means [ -t 1 ]; bash disagrees */ - if (unset(POSIXBUILTINS) && !strcmp(s1, "-t")) + if (unset(POSIXBUILTINS) && check_cond(s1, "t")) return par_cond_double(s1, dupstring("1")); return par_cond_double(dupstring("-n"), s1); } @@ -2338,7 +2384,7 @@ par_cond_2(void) if (!strcmp(*testargs, "=") || !strcmp(*testargs, "==") || !strcmp(*testargs, "!=") || - (**testargs == '-' && get_cond_num(*testargs + 1) >= 0)) { + (IS_DASH(**testargs) && get_cond_num(*testargs + 1) >= 0)) { s1 = tokstr; condlex(); s2 = tokstr; @@ -2360,8 +2406,8 @@ par_cond_2(void) * In "test" compatibility mode, "! -a ..." and "! -o ..." * are treated as "[string] [and] ..." and "[string] [or] ...". */ - if (!(n_testargs > 1 && - (!strcmp(*testargs, "-a") || !strcmp(*testargs, "-o")))) + if (!(n_testargs > 1 && (check_cond(*testargs, "a") || + check_cond(*testargs, "o")))) { condlex(); ecadd(WCB_COND(COND_NOT, 0)); @@ -2383,7 +2429,7 @@ par_cond_2(void) return r; } s1 = tokstr; - dble = (s1 && *s1 == '-' + dble = (s1 && IS_DASH(*s1) && (!n_testargs || strspn(s1+1, "abcdefghknoprstuvwxzLONGS") == 1) && !s1[2]); @@ -2397,7 +2443,7 @@ par_cond_2(void) YYERROR(ecused); } condlex(); - if (n_testargs == 2 && tok != STRING && tokstr && s1[0] == '-') { + if (n_testargs == 2 && tok != STRING && tokstr && IS_DASH(s1[0])) { /* * Something like "test -z" followed by a token. * We'll turn the token into a string (we've also @@ -2432,9 +2478,9 @@ par_cond_2(void) } else YYERROR(ecused); } - s2 = tokstr; + s2 = tokstr; if (!n_testargs) - dble = (s2 && *s2 == '-' && !s2[2]); + dble = (s2 && IS_DASH(*s2) && !s2[2]); incond++; /* parentheses do globbing */ do condlex(); while (COND_SEP()); incond--; /* parentheses do grouping */ @@ -2462,7 +2508,7 @@ par_cond_2(void) static int par_cond_double(char *a, char *b) { - if (a[0] != '-' || !a[1]) + if (!IS_DASH(a[0]) || !a[1]) COND_ERROR("parse error: condition expected: %s", a); else if (!a[2] && strspn(a+1, "abcdefgknoprstuvwxzhLONGS") == 1) { ecadd(WCB_COND(a[1], 0)); @@ -2520,7 +2566,7 @@ par_cond_triple(char *a, char *b, char *c) ecadd(WCB_COND(COND_REGEX, 0)); ecstr(a); ecstr(c); - } else if (b[0] == '-') { + } else if (IS_DASH(b[0])) { if ((t0 = get_cond_num(b + 1)) > -1) { ecadd(WCB_COND(t0 + COND_NT, 0)); ecstr(a); @@ -2531,7 +2577,7 @@ par_cond_triple(char *a, char *b, char *c) ecstr(a); ecstr(c); } - } else if (a[0] == '-' && a[1]) { + } else if (IS_DASH(a[0]) && a[1]) { ecadd(WCB_COND(COND_MOD, 2)); ecstr(a); ecstr(b); @@ -2546,7 +2592,7 @@ par_cond_triple(char *a, char *b, char *c) static int par_cond_multi(char *a, LinkList l) { - if (a[0] != '-' || !a[1]) + if (!IS_DASH(a[0]) || !a[1]) COND_ERROR("condition expected: %s", a); else { LinkNode n; @@ -3242,10 +3288,10 @@ build_dump(char *nam, char *dump, char **files, int ali, int map, int flags) for (hlen = FD_PRELEN, tlen = 0; *files; files++) { struct stat st; - if (!strcmp(*files, "-k")) { + if (check_cond(*files, "k")) { flags = (flags & ~(FDHF_KSHLOAD | FDHF_ZSHLOAD)) | FDHF_KSHLOAD; continue; - } else if (!strcmp(*files, "-z")) { + } else if (check_cond(*files, "z")) { flags = (flags & ~(FDHF_KSHLOAD | FDHF_ZSHLOAD)) | FDHF_ZSHLOAD; continue; } @@ -3324,7 +3370,7 @@ cur_add_func(char *nam, Shfunc shf, LinkList names, LinkList progs, return 1; } noaliases = (shf->node.flags & PM_UNALIASED); - if (!(prog = getfpfunc(shf->node.nam, NULL, NULL)) || + if (!(prog = getfpfunc(shf->node.nam, NULL, NULL, NULL, 0)) || prog == &dummy_eprog) { noaliases = ona; zwarnnam(nam, "can't load function: %s", shf->node.nam); @@ -3399,6 +3445,7 @@ build_cur_dump(char *nam, char *dump, char **names, int match, int map, for (; *names; names++) { tokenize(pat = dupstring(*names)); + /* Signal-safe here, caller queues signals */ if (!(pprog = patcompile(pat, PAT_STATIC, NULL))) { zwarnnam(nam, "bad pattern: %s", *names); close(dfd); @@ -3566,7 +3613,7 @@ load_dump_file(char *dump, struct stat *sbuf, int other, int len) /**/ Eprog -try_dump_file(char *path, char *name, char *file, int *ksh) +try_dump_file(char *path, char *name, char *file, int *ksh, int test_only) { Eprog prog; struct stat std, stc, stn; @@ -3575,7 +3622,7 @@ try_dump_file(char *path, char *name, char *file, int *ksh) if (strsfx(FD_EXT, path)) { queue_signals(); - prog = check_dump_file(path, NULL, name, ksh); + prog = check_dump_file(path, NULL, name, ksh, test_only); unqueue_signals(); return prog; } @@ -3594,14 +3641,14 @@ try_dump_file(char *path, char *name, char *file, int *ksh) if (!rd && (rc || std.st_mtime > stc.st_mtime) && (rn || std.st_mtime > stn.st_mtime) && - (prog = check_dump_file(dig, &std, name, ksh))) { + (prog = check_dump_file(dig, &std, name, ksh, test_only))) { unqueue_signals(); return prog; } /* No digest file. Now look for the per-function compiled file. */ if (!rc && (rn || stc.st_mtime > stn.st_mtime) && - (prog = check_dump_file(wc, &stc, name, ksh))) { + (prog = check_dump_file(wc, &stc, name, ksh, test_only))) { unqueue_signals(); return prog; } @@ -3629,7 +3676,7 @@ try_source_file(char *file) if (strsfx(FD_EXT, file)) { queue_signals(); - prog = check_dump_file(file, NULL, tail, NULL); + prog = check_dump_file(file, NULL, tail, NULL, 0); unqueue_signals(); return prog; } @@ -3640,7 +3687,7 @@ try_source_file(char *file) queue_signals(); if (!rc && (rn || stc.st_mtime > stn.st_mtime) && - (prog = check_dump_file(wc, &stc, tail, NULL))) { + (prog = check_dump_file(wc, &stc, tail, NULL, 0))) { unqueue_signals(); return prog; } @@ -3653,7 +3700,8 @@ try_source_file(char *file) /**/ static Eprog -check_dump_file(char *file, struct stat *sbuf, char *name, int *ksh) +check_dump_file(char *file, struct stat *sbuf, char *name, int *ksh, + int test_only) { int isrec = 0; Wordcode d; @@ -3695,6 +3743,11 @@ check_dump_file(char *file, struct stat *sbuf, char *name, int *ksh) if ((h = dump_find_func(d, name))) { /* Found the name. If the file is already mapped, return the eprog, * otherwise map it and just go up. */ + if (test_only) + { + /* This is all we need. Just return dummy. */ + return &dummy_eprog; + } #ifdef USE_MMAP @@ -3731,7 +3784,7 @@ check_dump_file(char *file, struct stat *sbuf, char *name, int *ksh) #endif - { + { Eprog prog; Patprog *pp; int np, fd, po = h->npats * sizeof(Patprog); diff --git a/Src/pattern.c b/Src/pattern.c index 1f2e94bd9..fc7c73739 100644 --- a/Src/pattern.c +++ b/Src/pattern.c @@ -668,15 +668,9 @@ patcompile(char *exp, int inflags, char **endexp) if (imeta(*mtest)) nmeta++; if (nmeta) { - char *oldpatout = patout; - ptrdiff_t pd; patadd(NULL, 0, nmeta, 0); - /* - * Yuk. - */ p = (Patprog)patout; - pd = patout - oldpatout; - opnd += pd; + opnd = dupstring_wlen(opnd, oplen); dst = patout + startoff; } @@ -1527,7 +1521,7 @@ patcomppiece(int *flagp, int paren) patparse = nptr; len |= 1; } - DPUTS(*patparse != '-', "BUG: - missing from numeric glob"); + DPUTS(!IS_DASH(*patparse), "BUG: - missing from numeric glob"); patparse++; if (idigit(*patparse)) { to = (zrange_t) zstrtol((char *)patparse, @@ -3631,7 +3625,7 @@ mb_patmatchrange(char *range, wchar_t ch, int zmb_ind, wint_t *indptr, int *mtp) return 1; break; case PP_PRINT: - if (iswprint(ch)) + if (WC_ISPRINT(ch)) return 1; break; case PP_PUNCT: diff --git a/Src/prompt.c b/Src/prompt.c index ee77c8bc8..c478e69fb 100644 --- a/Src/prompt.c +++ b/Src/prompt.c @@ -399,7 +399,7 @@ putpromptchar(int doprint, int endchar, unsigned int *txtchangep) test = 1; break; case 'V': - if (arrlen_ge(psvar, arg)) { + if (psvar && *psvar && arrlen_ge(psvar, arg)) { if (*psvar[(arg ? arg : 1) - 1]) test = 1; } @@ -920,6 +920,7 @@ addbufspc(int need) if(need & 255) need = (need | 255) + 1; bv->buf = realloc(bv->buf, bv->bufspc += need); + memset(bv->buf + bv->bufspc - need, 0, need); bv->bp = bv->buf + bo; if(bo1 != -1) bv->bp1 = bv->buf + bo1; diff --git a/Src/signals.c b/Src/signals.c index 9e05add09..cad40f4eb 100644 --- a/Src/signals.c +++ b/Src/signals.c @@ -55,6 +55,11 @@ mod_export Eprog siglists[VSIGCOUNT]; /**/ mod_export int nsigtrapped; +/* Running an exit trap? */ + +/**/ +int in_exit_trap; + /* * Flag that exit trap has been set in POSIX mode. * The setter's expectation is therefore that it is run @@ -522,6 +527,11 @@ wait_for_processes(void) #if defined(HAVE_WAIT3) && defined(HAVE_GETRUSAGE) struct timezone dummy_tz; gettimeofday(&pn->endtime, &dummy_tz); +#ifdef WIFCONTINUED + if (WIFCONTINUED(status)) + pn->status = SP_RUNNING; + else +#endif pn->status = status; pn->ti = ru; #else @@ -811,7 +821,11 @@ dosavetrap(int sig, int level) newshf->node.nam = ztrdup(shf->node.nam); newshf->node.flags = shf->node.flags; newshf->funcdef = dupeprog(shf->funcdef, 0); - newshf->filename = ztrdup(shf->filename); + if (shf->node.flags & PM_LOADDIR) { + dircache_set(&newshf->filename, shf->filename); + } else { + newshf->filename = ztrdup(shf->filename); + } if (shf->sticky) { newshf->sticky = sticky_emulation_dup(shf->sticky, 0); } else @@ -1426,7 +1440,13 @@ dotrap(int sig) dont_queue_signals(); + if (sig == SIGEXIT) + ++in_exit_trap; + dotrapargs(sig, sigtrapped+sig, funcprog); + if (sig == SIGEXIT) + --in_exit_trap; + restore_queue_signals(q); } diff --git a/Src/string.c b/Src/string.c index a8da14fe0..9e14ef949 100644 --- a/Src/string.c +++ b/Src/string.c @@ -52,7 +52,8 @@ dupstring_wlen(const char *s, unsigned len) if (!s) return NULL; t = (char *) zhalloc(len + 1); - strcpy(t, s); + memcpy(t, s, len); + t[len] = '\0'; return t; } diff --git a/Src/subst.c b/Src/subst.c index 64b440027..5b1bf8988 100644 --- a/Src/subst.c +++ b/Src/subst.c @@ -446,7 +446,7 @@ singsub(char **s) * NULL to use IFS). The return value is true iff the expansion resulted * in an empty list. * - * *ms_flags is set to bits in the enum above as neeed. + * *ms_flags is set to bits in the enum above as needed. */ /**/ @@ -481,6 +481,8 @@ multsub(char **s, int pf_flags, char ***a, int *isarr, char *sep, for ( ; *x; x += l) { int rawc = -1; convchar_t c; + if (*x == Dash) + *x = '-'; if (itok(STOUC(*x))) { /* token, can't be separator, must be single byte */ rawc = *x; @@ -622,7 +624,7 @@ filesub(char **namptr, int assign) char * equalsubstr(char *str, int assign, int nomatch) { - char *pp, *cnam, *cmdstr, *ret; + char *pp, *cnam, *cmdstr; for (pp = str; !isend2(*pp); pp++) ; @@ -634,10 +636,10 @@ equalsubstr(char *str, int assign, int nomatch) zerr("%s not found", cmdstr); return NULL; } - ret = dupstring(cnam); if (*pp) - ret = dyncat(ret, pp); - return ret; + return dyncat(cnam, pp); + else + return cnam; /* already duplicated */ } /**/ @@ -1766,7 +1768,8 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags, */ c = *s; if (itype_end(s, IIDENT, 1) == s && *s != '#' && c != Pound && - c != '-' && c != '!' && c != '$' && c != String && c != Qstring && + !IS_DASH(c) && + c != '!' && c != '$' && c != String && c != Qstring && c != '?' && c != Quest && c != '*' && c != Star && c != '@' && c != '{' && c != Inbrace && c != '=' && c != Equals && c != Hat && @@ -1895,13 +1898,13 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags, if (quotetype == QT_DOLLARS || quotetype == QT_BACKSLASH_PATTERN) goto flagerr; - if (s[1] == '-' || s[1] == '+') { + if (IS_DASH(s[1]) || s[1] == '+') { if (quotemod) goto flagerr; s++; quotemod = 1; - quotetype = (*s == '-') ? QT_SINGLE_OPTIONAL : - QT_QUOTEDZPUTS; + quotetype = (*s == '+') ? QT_QUOTEDZPUTS : + QT_SINGLE_OPTIONAL; } else { if (quotetype == QT_SINGLE_OPTIONAL) { /* extra q's after '-' not allowed */ @@ -2208,9 +2211,9 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags, * properly in the first place we wouldn't * have this nonsense. */ - || ((cc == '#' || cc == Pound) && - s[2] == Outbrace) - || cc == '-' || (cc == ':' && s[2] == '-') + || ((cc == '#' || cc == Pound) && s[2] == Outbrace) + || IS_DASH(cc) + || (cc == ':' && IS_DASH(s[2])) || (isstring(cc) && (s[2] == Inbrace || s[2] == Inpar)))) { getlen = 1 + whichlen, s++; /* @@ -2605,14 +2608,17 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags, * Again, this duplicates tests for characters we're about to * examine properly later on. */ - if (inbrace && - (c = *s) != '-' && c != '+' && c != ':' && c != '%' && c != '/' && - c != '=' && c != Equals && - c != '#' && c != Pound && - c != '?' && c != Quest && - c != '}' && c != Outbrace) { - zerr("bad substitution"); - return NULL; + if (inbrace) { + c = *s; + if (!IS_DASH(c) && + c != '+' && c != ':' && c != '%' && c != '/' && + c != '=' && c != Equals && + c != '#' && c != Pound && + c != '?' && c != Quest && + c != '}' && c != Outbrace) { + zerr("bad substitution"); + return NULL; + } } /* * Join arrays up if we're in quotes and there isn't some @@ -2690,8 +2696,8 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags, /* Check for ${..?..} or ${..=..} or one of those. * * Only works if the name is in braces. */ - if (inbrace && ((c = *s) == '-' || - c == '+' || + if (inbrace && ((c = *s) == '+' || + IS_DASH(c) || c == ':' || /* i.e. a doubled colon */ c == '=' || c == Equals || c == '%' || @@ -2802,6 +2808,7 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags, vunset = 1; /* Fall Through! */ case '-': + case Dash: if (vunset) { int split_flags; val = dupstring(s); @@ -2902,6 +2909,7 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags, } else setaparam(idbeg, a); isarr = 1; + arrasg = 0; } else { untokenize(val); setsparam(idbeg, ztrdup(val)); @@ -3066,7 +3074,10 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags, if (sval) zip = hmkarray(sval); } - if (!isarr) aval = mkarray(val); + if (!isarr) { + aval = mkarray(val); + isarr = 1; + } if (zip) { char **out; int alen, ziplen, outlen, i = 0; @@ -3089,7 +3100,6 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags, out[i*2] = NULL; aval = out; copied = 1; - isarr = 1; } } else { if (unset(UNSET)) { @@ -3473,8 +3483,8 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags, if (nojoin == 0 || sep) { val = sepjoin(aval, sep, 1); isarr = 0; - ms_flags = 0; - } else if (force_split && (spsep || nojoin == 2)) { + } else if (force_split && + (spsep || nojoin == 2 || (!ifs && isarr < 0))) { /* Hack to simulate splitting individual elements: * forced joining as previously determined, or * join on what we later use to forcibly split @@ -3482,6 +3492,8 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags, val = sepjoin(aval, (nojoin == 1 ? NULL : spsep), 1); isarr = 0; } + if (!isarr) + ms_flags = 0; } if (force_split && !isarr) { aval = sepsplit(val, spsep, 0, 1); @@ -3767,6 +3779,13 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags, * as a scalar.) */ + if (isarr && ssub) { + /* prefork() wants a scalar, so join no matter what else */ + val = sepjoin(aval, NULL, 1); + isarr = 0; + l->list.flags &= ~LF_ARRAY; + } + /* * If a multsub result had whitespace at the start and we're * splitting and there's a previous string, now's the time to do so. @@ -3780,6 +3799,16 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags, insertlinknode(l, n, dupstring(fstr)); /* appended, no incnode */ *fstr = '\0'; } + if (arrasg && !isarr) { + /* + * Caller requested this be forced to an array even if scalar. + * Any point in distinguishing arrasg == 2 (assoc array) here? + */ + l->list.flags |= LF_ARRAY; + aval = hmkarray(val); + isarr = 1; + DPUTS(!val, "value is NULL in paramsubst, empty array"); + } if (isarr) { char *x; char *y; @@ -4302,7 +4331,11 @@ modify(char **str, char **ptr) break; case 'P': if (*copy != '/') { - copy = zhtricat(metafy(zgetcwd(), -1, META_HEAPDUP), "/", copy); + char *here = zgetcwd(); + if (here[strlen(here)-1] != '/') + copy = zhtricat(metafy(here, -1, META_HEAPDUP), "/", copy); + else + copy = dyncat(here, copy); } copy = xsymlink(copy, 1); break; @@ -4384,7 +4417,11 @@ modify(char **str, char **ptr) break; case 'P': if (**str != '/') { - *str = zhtricat(metafy(zgetcwd(), -1, META_HEAPDUP), "/", *str); + char *here = zgetcwd(); + if (here[strlen(here)-1] != '/') + *str = zhtricat(metafy(here, -1, META_HEAPDUP), "/", *str); + else + *str = dyncat(here, *str); } *str = xsymlink(*str, 1); break; diff --git a/Src/utils.c b/Src/utils.c index 7f3ddad40..5055d69fe 100644 --- a/Src/utils.c +++ b/Src/utils.c @@ -629,7 +629,7 @@ wcs_nicechar_sel(wchar_t c, size_t *widthp, char **swidep, int quotable) } s = buf; - if (!iswprint(c) && (c < 0x80 || !isset(PRINTEIGHTBIT))) { + if (!WC_ISPRINT(c) && (c < 0x80 || !isset(PRINTEIGHTBIT))) { if (c == 0x7f) { if (quotable) { *s++ = '\\'; @@ -734,7 +734,7 @@ wcs_nicechar(wchar_t c, size_t *widthp, char **swidep) /**/ mod_export int is_wcs_nicechar(wchar_t c) { - if (!iswprint(c) && (c < 0x80 || !isset(PRINTEIGHTBIT))) { + if (!WC_ISPRINT(c) && (c < 0x80 || !isset(PRINTEIGHTBIT))) { if (c == 0x7f || c == L'\n' || c == L'\t' || c < 0x20) return 1; if (c >= 0x80) { @@ -886,7 +886,7 @@ xsymlinks(char *s, int full) char **pp, **opp; char xbuf2[PATH_MAX*3+1], xbuf3[PATH_MAX*2+1]; int t0, ret = 0; - zulong xbuflen = strlen(xbuf); + zulong xbuflen = strlen(xbuf), pplen; opp = pp = slashsplit(s); for (; xbuflen < sizeof(xbuf) && *pp && ret >= 0; pp++) { @@ -907,10 +907,18 @@ xsymlinks(char *s, int full) xbuflen--; continue; } - sprintf(xbuf2, "%s/%s", xbuf, *pp); + /* Includes null byte. */ + pplen = strlen(*pp) + 1; + if (xbuflen + pplen + 1 > sizeof(xbuf2)) { + *xbuf = 0; + ret = -1; + break; + } + memcpy(xbuf2, xbuf, xbuflen); + xbuf2[xbuflen] = '/'; + memcpy(xbuf2 + xbuflen + 1, *pp, pplen); t0 = readlink(unmeta(xbuf2), xbuf3, PATH_MAX); if (t0 == -1) { - zulong pplen = strlen(*pp) + 1; if ((xbuflen += pplen) < sizeof(xbuf)) { strcat(xbuf, "/"); strcat(xbuf, *pp); @@ -1230,13 +1238,13 @@ adduserdir(char *s, char *t, int flags, int always) * named directory, since these are sometimes used for * special purposes. */ - nd->dir = ztrdup(t); + nd->dir = metafy(t, -1, META_DUP); } else - nd->dir = ztrduppfx(t, eptr - t); + nd->dir = metafy(t, eptr - t, META_DUP); /* The variables PWD and OLDPWD are not to be displayed as ~PWD etc. */ if (!strcmp(s, "PWD") || !strcmp(s, "OLDPWD")) nd->node.flags |= ND_NOABBREV; - nameddirtab->addnode(nameddirtab, ztrdup(s), nd); + nameddirtab->addnode(nameddirtab, metafy(s, -1, META_DUP), nd); } /* Get a named directory: this function can cause a directory name * @@ -2376,7 +2384,7 @@ zstrtol_underscore(const char *s, char **t, int base, int underscore) while (inblank(*s)) s++; - if ((neg = (*s == '-'))) + if ((neg = IS_DASH(*s))) s++; else if (*s == '+') s++; @@ -4788,6 +4796,41 @@ unmeta(const char *file_name) } /* + * Unmetafy just one character and store the number of bytes it occupied. + */ +/**/ +mod_export convchar_t +unmeta_one(const char *in, int *sz) +{ + convchar_t wc; + int newsz; +#ifdef MULTIBYTE_SUPPORT + mbstate_t wstate; +#endif + + if (!sz) + sz = &newsz; + *sz = 0; + + if (!in || !*in) + return 0; + +#ifdef MULTIBYTE_SUPPORT + memset(&wstate, 0, sizeof(wstate)); + *sz = mb_metacharlenconv_r(in, &wc, &wstate); +#else + if (in[0] == Meta) { + *sz = 2; + wc = STOUC(in[1] ^ 32); + } else { + *sz = 1; + wc = STOUC(in[0]); + } +#endif + return wc; +} + +/* * Unmetafy and compare two strings, comparing unsigned character values. * "a\0" sorts after "a". * @@ -5076,7 +5119,7 @@ niceztrlen(char const *s) * If flags contains NICEFLAG_HEAP, use the heap for *outstrp, else * zalloc. * If flags contsins NICEFLAG_QUOTE, the output is going to be within - * $'...', so quote "'" with a backslash. + * $'...', so quote "'" and "\" with a backslash. */ /**/ @@ -5132,6 +5175,10 @@ mb_niceformat(const char *s, FILE *stream, char **outstrp, int flags) fmt = "\\'"; newl = 2; } + else if (c == L'\\' && (flags & NICEFLAG_QUOTE)) { + fmt = "\\\\"; + newl = 2; + } else fmt = wcs_nicechar_sel(c, &newl, NULL, flags & NICEFLAG_QUOTE); break; @@ -5374,7 +5421,7 @@ mb_metastrlenend(char *ptr, int width, char *eptr) int num, num_in_char, complete; if (!isset(MULTIBYTE)) - return ztrlen(ptr); + return eptr ? (int)(eptr - ptr) : ztrlen(ptr); laststart = ptr; ret = MB_INVALID; @@ -6118,7 +6165,9 @@ quotedzputs(char const *s, FILE *stream) } else *ptr++ = '\''; while(*s) { - if (*s == Meta) + if (*s == Dash) + c = '-'; + else if (*s == Meta) c = *++s ^ 32; else c = *s; @@ -6155,7 +6204,9 @@ quotedzputs(char const *s, FILE *stream) } else { /* use Bourne-style quoting, avoiding empty quoted strings */ while (*s) { - if (*s == Meta) + if (*s == Dash) + c = '-'; + else if (*s == Meta) c = *++s ^ 32; else c = *s; diff --git a/Src/watch.c b/Src/watch.c index c804913ad..cd7dc643d 100644 --- a/Src/watch.c +++ b/Src/watch.c @@ -87,6 +87,15 @@ #if !defined(WATCH_STRUCT_UTMP) && defined(HAVE_STRUCT_UTMPX) && defined(REAL_UTMPX_FILE) # define WATCH_STRUCT_UTMP struct utmpx +# if defined(HAVE_SETUTXENT) && defined(HAVE_GETUTXENT) && defined(HAVE_ENDUTXENT) +# define setutent setutxent +# define getutent getutxent +# define endutent endutxent +# ifndef HAVE_GETUTENT +# define HAVE_GETUTENT 1 +# endif +# endif + /* * In utmpx, the ut_name field is replaced by ut_user. * Howver, on some systems ut_name may already be defined this @@ -141,9 +150,9 @@ char const * const default_watchfmt = DEFAULT_WATCHFMT; # define WATCH_WTMP_FILE "/dev/null" # endif -static int wtabsz; -static WATCH_STRUCT_UTMP *wtab; -static time_t lastutmpcheck; +static int wtabsz = 0; +static WATCH_STRUCT_UTMP *wtab = NULL; +static time_t lastutmpcheck = 0; /* get the time of login/logout for WATCH */ @@ -473,34 +482,60 @@ ucmp(WATCH_STRUCT_UTMP *u, WATCH_STRUCT_UTMP *v) /* initialize the user List */ /**/ -static void -readwtab(void) +static int +readwtab(WATCH_STRUCT_UTMP **head, int initial_sz) { WATCH_STRUCT_UTMP *uptr; - int wtabmax = 32; + int wtabmax = initial_sz < 2 ? 32 : initial_sz; + int sz = 0; +# ifdef HAVE_GETUTENT + WATCH_STRUCT_UTMP *tmp; +# else FILE *in; +# endif - wtabsz = 0; + uptr = *head = (WATCH_STRUCT_UTMP *) + zalloc(wtabmax * sizeof(WATCH_STRUCT_UTMP)); +# ifdef HAVE_GETUTENT + setutent(); + while ((tmp = getutent()) != NULL) { + memcpy(uptr, tmp, sizeof (WATCH_STRUCT_UTMP)); +# else if (!(in = fopen(WATCH_UTMP_FILE, "r"))) - return; - uptr = wtab = (WATCH_STRUCT_UTMP *)zalloc(wtabmax * sizeof(WATCH_STRUCT_UTMP)); - while (fread(uptr, sizeof(WATCH_STRUCT_UTMP), 1, in)) + return 0; + while (fread(uptr, sizeof(WATCH_STRUCT_UTMP), 1, in)) { +# endif # ifdef USER_PROCESS - if (uptr->ut_type == USER_PROCESS) + if (uptr->ut_type == USER_PROCESS) # else /* !USER_PROCESS */ - if (uptr->ut_name[0]) + if (uptr->ut_name[0]) # endif /* !USER_PROCESS */ { uptr++; - if (++wtabsz == wtabmax) - uptr = (wtab = (WATCH_STRUCT_UTMP *)realloc((void *) wtab, (wtabmax *= 2) * - sizeof(WATCH_STRUCT_UTMP))) + wtabsz; + if (++sz == wtabmax) { + uptr = (WATCH_STRUCT_UTMP *) + realloc(*head, (wtabmax *= 2) * sizeof(WATCH_STRUCT_UTMP)); + if (uptr == NULL) { + /* memory pressure - so stop consuming and use, what we have + * Other option is to exit() here, as zmalloc does on error */ + sz--; + break; + } + *head = uptr; + uptr += sz; + } } + } +# ifdef HAVE_GETUTENT + endutent(); +# else fclose(in); +# endif - if (wtabsz) - qsort((void *) wtab, wtabsz, sizeof(WATCH_STRUCT_UTMP), + if (sz) + qsort((void *) *head, sz, sizeof(WATCH_STRUCT_UTMP), (int (*) _((const void *, const void *)))ucmp); + return sz; } /* Check for login/logout events; executed before * @@ -510,55 +545,28 @@ readwtab(void) void dowatch(void) { - FILE *in; WATCH_STRUCT_UTMP *utab, *uptr, *wptr; struct stat st; char **s; char *fmt; - int utabsz = 0, utabmax = wtabsz + 4; - int uct, wct; + int utabsz, uct, wct; s = watch; holdintr(); - if (!wtab) { - readwtab(); - noholdintr(); - return; - } + if (!wtab) + wtabsz = readwtab(&wtab, 32); if ((stat(WATCH_UTMP_FILE, &st) == -1) || (st.st_mtime <= lastutmpcheck)) { noholdintr(); return; } lastutmpcheck = st.st_mtime; - uptr = utab = (WATCH_STRUCT_UTMP *) zalloc(utabmax * sizeof(WATCH_STRUCT_UTMP)); - - if (!(in = fopen(WATCH_UTMP_FILE, "r"))) { - free(utab); - noholdintr(); - return; - } - while (fread(uptr, sizeof *uptr, 1, in)) -# ifdef USER_PROCESS - if (uptr->ut_type == USER_PROCESS) -# else /* !USER_PROCESS */ - if (uptr->ut_name[0]) -# endif /* !USER_PROCESS */ - { - uptr++; - if (++utabsz == utabmax) - uptr = (utab = (WATCH_STRUCT_UTMP *)realloc((void *) utab, (utabmax *= 2) * - sizeof(WATCH_STRUCT_UTMP))) + utabsz; - } - fclose(in); + utabsz = readwtab(&utab, wtabsz + 4); noholdintr(); if (errflag) { free(utab); return; } - if (utabsz) - qsort((void *) utab, utabsz, sizeof(WATCH_STRUCT_UTMP), - (int (*) _((const void *, const void *)))ucmp); wct = wtabsz; uct = utabsz; @@ -571,13 +579,14 @@ dowatch(void) queue_signals(); if (!(fmt = getsparam_u("WATCHFMT"))) fmt = DEFAULT_WATCHFMT; - while ((uct || wct) && !errflag) + while ((uct || wct) && !errflag) { if (!uct || (wct && ucmp(uptr, wptr) > 0)) wct--, watchlog(0, wptr++, s, fmt); else if (!wct || (uct && ucmp(uptr, wptr) < 0)) uct--, watchlog(1, uptr++, s, fmt); else uptr++, wptr++, wct--, uct--; + } unqueue_signals(); free(wtab); wtab = utab; diff --git a/Src/wcwidth9.h b/Src/wcwidth9.h index 07e6bae1c..448f548e9 100644 --- a/Src/wcwidth9.h +++ b/Src/wcwidth9.h @@ -22,6 +22,7 @@ static const struct wcwidth9_interval wcwidth9_nonprint[] = { {0x070f, 0x070f}, {0x180b, 0x180e}, {0x200b, 0x200f}, + {0x2028, 0x2029}, {0x202a, 0x202e}, {0x206a, 0x206f}, {0xd800, 0xdfff}, @@ -1283,6 +1284,9 @@ static inline bool wcwidth9_intable(const struct wcwidth9_interval *table, size_ } static inline int wcwidth9(int c) { + if (c == 0) { + return 0; + } if (c < 0|| c > 0x10ffff) { return -1; } @@ -1292,7 +1296,7 @@ static inline int wcwidth9(int c) { } if (wcwidth9_intable(wcwidth9_combining, WCWIDTH9_ARRAY_SIZE(wcwidth9_combining), c)) { - return -1; + return 0; } if (wcwidth9_intable(wcwidth9_not_assigned, WCWIDTH9_ARRAY_SIZE(wcwidth9_not_assigned), c)) { @@ -238,6 +238,16 @@ struct mathfunc { #define PATCHARS "#^*()|[]<>?~\\" /* + * Check for a possibly tokenized dash. + * + * A dash only needs to be a token in a character range, [a-z], but + * it's difficult in general to ensure that. So it's turned into + * a token at the usual point in the lexer. However, we need + * to check for a literal dash at many points. + */ +#define IS_DASH(x) ((x) == '-' || (x) == Dash) + +/* * Types of quote. This is used in various places, so care needs * to be taken when changing them. (Oooh, don't you look surprised.) * - Passed to quotestring() to indicate style. This is the ultimate @@ -803,6 +813,7 @@ struct eccstr { char *str; wordcode offs, aoffs; int nfunc; + int hashval; }; #define EC_NODUP 0 @@ -1019,6 +1030,7 @@ struct job { #define STAT_BUILTIN (0x4000) /* job at tail of pipeline is a builtin */ #define STAT_SUBJOB_ORPHANED (0x8000) /* STAT_SUBJOB with STAT_SUPERJOB exited */ +#define STAT_DISOWN (0x10000) /* STAT_SUPERJOB with disown pending */ #define SP_RUNNING -1 /* fake status for jobs currently running */ @@ -1233,7 +1245,9 @@ struct cmdnam { struct shfunc { struct hashnode node; - char *filename; /* Name of file located in */ + char *filename; /* Name of file located in. + For not yet autoloaded file, name + of explicit directory, if not NULL. */ zlong lineno; /* line number in above file */ Eprog funcdef; /* function definition */ Eprog redir; /* redirections to apply */ @@ -1529,6 +1543,7 @@ struct patstralloc { /* Flags used in pattern matchers (Patprog) and passed down to patcompile */ +#define PAT_HEAPDUP 0x0000 /* Dummy flag for default behavior */ #define PAT_FILE 0x0001 /* Pattern is a file name */ #define PAT_FILET 0x0002 /* Pattern is top level file, affects ~ */ #define PAT_ANY 0x0004 /* Match anything (cheap "*") */ @@ -1804,6 +1819,7 @@ struct tieddata { #define PM_READONLY (1<<10) /* readonly */ #define PM_TAGGED (1<<11) /* tagged */ #define PM_EXPORTED (1<<12) /* exported */ +#define PM_ABSPATH_USED (1<<12) /* (function): loaded using absolute path */ /* The following are the same since they * * both represent -U option to typeset */ @@ -1811,7 +1827,9 @@ struct tieddata { #define PM_UNALIASED (1<<13) /* do not expand aliases when autoloading */ #define PM_HIDE (1<<14) /* Special behaviour hidden by local */ +#define PM_CUR_FPATH (1<<14) /* (function): can use $fpath with filename */ #define PM_HIDEVAL (1<<15) /* Value not shown in `typeset' commands */ +#define PM_WARNNESTED (1<<15) /* (function): non-recursive WARNNESTEDVAR */ #define PM_TIED (1<<16) /* array tied to colon-path or v.v. */ #define PM_TAGGED_LOCAL (1<<16) /* (function): non-recursive PM_TAGGED */ @@ -1820,6 +1838,7 @@ struct tieddata { /* Remaining flags do not correspond directly to command line arguments */ #define PM_DONTIMPORT_SUID (1<<19) /* do not import if running setuid */ +#define PM_LOADDIR (1<<19) /* (function) filename gives load directory */ #define PM_SINGLE (1<<20) /* special can only have a single instance */ #define PM_LOCAL (1<<21) /* this parameter will be made local */ #define PM_SPECIAL (1<<22) /* special builtin parameter */ @@ -2012,9 +2031,15 @@ struct paramdef { * Flags for assignsparam and assignaparam. */ enum { + /* Add to rather than override value */ ASSPM_AUGMENT = 1 << 0, + /* Test for warning if creating global variable in function */ ASSPM_WARN_CREATE = 1 << 1, - ASSPM_ENV_IMPORT = 1 << 2 + /* Test for warning if using nested variable in function */ + ASSPM_WARN_NESTED = 1 << 2, + ASSPM_WARN = (ASSPM_WARN_CREATE|ASSPM_WARN_NESTED), + /* Import from environment, so exercise care evaluating value */ + ASSPM_ENV_IMPORT = 1 << 3, }; /* node for named directory hash table (nameddirtab) */ @@ -2222,6 +2247,7 @@ struct histent { enum { OPT_INVALID, ALIASESOPT, + ALIASFUNCDEF, ALLEXPORT, ALWAYSLASTPROMPT, ALWAYSTOEND, @@ -2395,6 +2421,7 @@ enum { VERBOSE, VIMODE, WARNCREATEGLOBAL, + WARNNESTEDVAR, XTRACE, USEZLE, DVORAK, @@ -2893,6 +2920,7 @@ struct hist_stack { int histdone; int stophist; int hlinesz; + zlong defev; char *hline; char *hptr; short *chwords; @@ -3133,9 +3161,7 @@ typedef wint_t convchar_t; * works on MacOS which doesn't define that. */ #ifdef ENABLE_UNICODE9 -#define WCWIDTH(wc) mk_wcwidth(wc) -#elif defined(BROKEN_WCWIDTH) && (defined(__STDC_ISO_10646__) || defined(__APPLE__)) -#define WCWIDTH(wc) mk_wcwidth(wc) +#define WCWIDTH(wc) u9_wcwidth(wc) #else #define WCWIDTH(wc) wcwidth(wc) #endif @@ -3180,15 +3206,7 @@ typedef wint_t convchar_t; * sense throughout the shell. I am not aware of a way of * detecting the Unicode trait in standard libraries. */ -#ifdef BROKEN_WCWIDTH -/* - * We can't be quite sure the wcwidth we've provided is entirely - * in agreement with the system's, so be extra safe. - */ -#define IS_COMBINING(wc) (wc != 0 && WCWIDTH(wc) == 0 && !iswcntrl(wc)) -#else #define IS_COMBINING(wc) (wc != 0 && WCWIDTH(wc) == 0) -#endif /* * Test for the base of a combining character. * diff --git a/Src/ztype.h b/Src/ztype.h index 76589b152..ae7236774 100644 --- a/Src/ztype.h +++ b/Src/ztype.h @@ -72,7 +72,11 @@ #ifdef MULTIBYTE_SUPPORT #define WC_ZISTYPE(X,Y) wcsitype((X),(Y)) -#define WC_ISPRINT(X) iswprint(X) +# ifdef ENABLE_UNICODE9 +# define WC_ISPRINT(X) u9_iswprint(X) +# else +# define WC_ISPRINT(X) iswprint(X) +# endif #else #define WC_ZISTYPE(X,Y) zistype((X),(Y)) #define WC_ISPRINT(X) isprint(X) |