about summary refs log tree commit diff
path: root/Src/Modules
diff options
context:
space:
mode:
Diffstat (limited to 'Src/Modules')
-rw-r--r--Src/Modules/curses.c8
-rw-r--r--Src/Modules/db_gdbm.c6
-rw-r--r--Src/Modules/files.c4
-rw-r--r--Src/Modules/hlgroup.c216
-rw-r--r--Src/Modules/hlgroup.mdd7
-rw-r--r--Src/Modules/ksh93.c287
-rw-r--r--Src/Modules/ksh93.mdd10
-rw-r--r--Src/Modules/mapfile.c10
-rw-r--r--Src/Modules/param_private.c161
-rw-r--r--Src/Modules/parameter.c138
-rw-r--r--Src/Modules/pcre.c327
-rw-r--r--Src/Modules/pcre.mdd2
-rw-r--r--Src/Modules/stat.c14
-rw-r--r--Src/Modules/system.c28
-rw-r--r--Src/Modules/terminfo.c4
-rw-r--r--Src/Modules/watch.c778
-rw-r--r--Src/Modules/watch.mdd7
-rw-r--r--Src/Modules/zftp.c17
-rw-r--r--Src/Modules/zprof.c6
-rw-r--r--Src/Modules/zpty.c2
-rw-r--r--Src/Modules/zutil.c105
21 files changed, 1828 insertions, 309 deletions
diff --git a/Src/Modules/curses.c b/Src/Modules/curses.c
index e46903916..8950cc153 100644
--- a/Src/Modules/curses.c
+++ b/Src/Modules/curses.c
@@ -1302,7 +1302,7 @@ zccmd_mouse(const char *nam, char **args)
 	    zlong delay;
 
 	    if (!*++args ||
-		((delay = zstrtol(*args, &eptr, 10)), eptr != NULL)) {
+		((delay = zstrtol(*args, &eptr, 10)), *eptr != '\0')) {
 		zwarnnam(nam, "mouse delay requires an integer argument");
 		return 1;
 	    }
@@ -1326,7 +1326,7 @@ zccmd_mouse(const char *nam, char **args)
 		if (old_mask != zcurses_mouse_mask)
 		    zcurses_flags |= ZCF_MOUSE_MASK_CHANGED;
 	    } else {
-		zwarnnam(nam, "unrecognised mouse command: %s", *arg);
+		zwarnnam(nam, "unrecognised mouse command: %s", arg);
 		return 1;
 	    }
 	}
@@ -1426,10 +1426,10 @@ zccmd_querychar(const char *nam, char **args)
     inc &= A_CHARTEXT;
     if (imeta(inc)) {
 	instr[0] = Meta;
-	instr[1] = STOUC(inc ^ 32);
+	instr[1] = (unsigned char) (inc ^ 32);
 	instr[2] = '\0';
     } else {
-	instr[0] = STOUC(inc);
+  	instr[0] = (unsigned char) inc;
 	instr[1] = '\0';
     }
     attrs = inc;
diff --git a/Src/Modules/db_gdbm.c b/Src/Modules/db_gdbm.c
index 7e11ec939..3fefd412b 100644
--- a/Src/Modules/db_gdbm.c
+++ b/Src/Modules/db_gdbm.c
@@ -34,8 +34,8 @@
 #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) */
+#ifndef PM_UPTODATE /* Parameter has up-to-date data (e.g. loaded from DB) */
+#define PM_UPTODATE     PM_DONTIMPORT_SUID	/* Safe PM_ bit to re-use */
 #endif
 
 static Param createhash( char *name, int flags );
@@ -111,7 +111,7 @@ 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;
+    int read_write = GDBM_SYNC, pmflags = PM_REMOVABLE|PM_SINGLE;
     Param tied_param;
 
     if(!OPT_ISSET(ops,'d')) {
diff --git a/Src/Modules/files.c b/Src/Modules/files.c
index bf0e8f8a8..a3fec1daa 100644
--- a/Src/Modules/files.c
+++ b/Src/Modules/files.c
@@ -29,8 +29,8 @@
 
 #include "files.mdh"
 
-typedef int (*MoveFunc) _((char const *, char const *));
-typedef int (*RecurseFunc) _((char *, char *, struct stat const *, void *));
+typedef int (*MoveFunc) (char const *, char const *);
+typedef int (*RecurseFunc) (char *, char *, struct stat const *, void *);
 
 struct recursivecmd;
 
diff --git a/Src/Modules/hlgroup.c b/Src/Modules/hlgroup.c
new file mode 100644
index 000000000..082762623
--- /dev/null
+++ b/Src/Modules/hlgroup.c
@@ -0,0 +1,216 @@
+/*
+ * hlgroup.c - Supporting parameters for highlight groups
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 2024 Oliver Kiddle
+ * 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
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Oliver Kiddle or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Oliver Kiddle and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Oliver Kiddle and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Oliver Kiddle and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "hlgroup.mdh"
+#include "hlgroup.pro"
+
+#define GROUPVAR ".zle.hlgroups"
+
+static const struct gsu_scalar pmesc_gsu =
+{ strgetfn, nullstrsetfn, nullunsetfn };
+
+/**/
+static char *
+convertattr(char *attrstr, int sgr)
+{
+    zattr atr;
+    char *r, *s;
+    int len;
+
+    match_highlight(attrstr, &atr, NULL);
+    s = zattrescape(atr, sgr ? NULL : &len);
+
+    if (sgr) {
+	char *c = s, *t = s - 1;
+
+	while (c[0] == '\033' && c[1] == '[') {
+	    c += 2;
+	    while (isdigit(*c) || *c == ';')
+		*++t = *c++;
+	    t++;
+	    if (*c != 'm')
+		break;
+	    *t = ';';
+	    c++;
+	}
+	if (t <= s) { /* always return at least "0" */
+	    *s = '0';
+	    t = s + 1;
+	}
+	*t = '\0';
+	len = t - s;
+    }
+
+    r = dupstring_wlen(s, len);
+    free(s);
+    return r;
+}
+
+/**/
+static HashNode
+getgroup(const char *name, int sgr)
+{
+    Param pm = NULL;
+    HashNode hn;
+    HashTable hlg;
+    Value v;
+    struct value vbuf;
+    char *var = GROUPVAR;
+
+    pm = (Param) hcalloc(sizeof(struct param));
+    pm->gsu.s = &pmesc_gsu;
+    pm->node.nam = dupstring(name);
+    pm->node.flags = PM_SCALAR|PM_SPECIAL;
+
+    if (!(v = getvalue(&vbuf, &var, 0)) ||
+	     PM_TYPE(v->pm->node.flags) != PM_HASHED ||
+	     !(hlg = v->pm->gsu.h->getfn(v->pm)) ||
+	     !(hn = gethashnode2(hlg, name)) ||
+	     (((Param) hn)->node.flags & PM_UNSET))
+    {
+	pm->u.str = dupstring("");
+	pm->node.flags |= PM_UNSET;
+    } else {
+	pm->u.str = convertattr(((Param) hn)->u.str, sgr);
+    }
+
+    return &pm->node;
+}
+
+/**/
+static void
+scangroup(ScanFunc func, int flags, int sgr)
+{
+    struct param pm;
+    int i;
+    HashNode hn;
+    HashTable hlg;
+    Value v;
+    struct value vbuf;
+    char *var = GROUPVAR;
+
+    if (!(v = getvalue(&vbuf, &var, 0)) ||
+	     PM_TYPE(v->pm->node.flags) != PM_HASHED)
+	return;
+    hlg = v->pm->gsu.h->getfn(v->pm);
+
+    memset((void *)&pm, 0, sizeof(struct param));
+    pm.node.flags = PM_SCALAR;
+    pm.gsu.s = &pmesc_gsu;
+
+    for (i = 0; i < hlg->hsize; i++)
+	for (hn = hlg->nodes[i]; hn; hn = hn->next) {
+	    pm.u.str = convertattr(((Param) hn)->u.str, sgr);
+	    pm.node.nam = hn->nam;
+	    func(&pm.node, flags);
+	}
+}
+/**/
+static HashNode
+getpmesc(UNUSED(HashTable ht), const char *name)
+{
+    return getgroup(name, 0);
+}
+
+/**/
+static void
+scanpmesc(UNUSED(HashTable ht), ScanFunc func, int flags)
+{
+    scangroup(func, flags, 0);
+}
+
+/**/
+static HashNode
+getpmsgr(UNUSED(HashTable ht), const char *name)
+{
+    return getgroup(name, 1);
+}
+
+/**/
+static void
+scanpmsgr(UNUSED(HashTable ht), ScanFunc func, int flags)
+{
+    scangroup(func, flags, 1);
+}
+
+static struct paramdef partab[] = {
+    SPECIALPMDEF(".zle.esc", PM_READONLY_SPECIAL, 0, getpmesc, scanpmesc),
+    SPECIALPMDEF(".zle.sgr", PM_READONLY_SPECIAL, 0, getpmsgr, scanpmsgr)
+};
+
+static struct features module_features = {
+    NULL, 0,
+    NULL, 0,
+    NULL, 0,
+    partab, sizeof(partab)/sizeof(*partab),
+    0
+};
+
+/**/
+int
+setup_(UNUSED(Module m))
+{
+    return 0;
+}
+
+/**/
+int
+features_(Module m, char ***features)
+{
+    *features = featuresarray(m, &module_features);
+    return 0;
+}
+
+/**/
+int
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m, &module_features, enables);
+}
+
+/**/
+int
+boot_(UNUSED(Module m))
+{
+    return 0;
+}
+
+/**/
+int
+cleanup_(Module m)
+{
+    return setfeatureenables(m, &module_features, NULL);
+}
+
+/**/
+int
+finish_(UNUSED(Module m))
+{
+    return 0;
+}
diff --git a/Src/Modules/hlgroup.mdd b/Src/Modules/hlgroup.mdd
new file mode 100644
index 000000000..ee3ba7260
--- /dev/null
+++ b/Src/Modules/hlgroup.mdd
@@ -0,0 +1,7 @@
+name=zsh/hlgroup
+link=either
+load=yes
+
+autofeatures="p:.zle.esc p:.zle.sgr"
+
+objects="hlgroup.o"
diff --git a/Src/Modules/ksh93.c b/Src/Modules/ksh93.c
new file mode 100644
index 000000000..3206c11f3
--- /dev/null
+++ b/Src/Modules/ksh93.c
@@ -0,0 +1,287 @@
+/*
+ * ksh93.c - support for more ksh93 features
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 2022 Barton E. Schaefer
+ * 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
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Barton E. Schaefer or the Zsh Development
+ * Group be liable to any party for direct, indirect, special, incidental, or
+ * consequential damages arising out of the use of this software and its
+ * documentation, even if Barton E. Schaefer and the Zsh
+ * Development Group have been advised of the possibility of such damage.
+ *
+ * Barton E. Schaefer and the Zsh Development Group
+ * specifically disclaim any warranties, including, but not limited to, the
+ * implied warranties of merchantability and fitness for a particular purpose.
+ * The software provided hereunder is on an "as is" basis, and
+ * Barton E. Schaefer and the Zsh Development Group have no
+ * obligation to provide maintenance, support, updates, enhancements, or
+ * modifications.
+ *
+ */
+
+#include "ksh93.mdh"
+#include "ksh93.pro"
+
+/* Implementing "namespace" requires creating a new keword.  Hrm. */
+
+/*
+ * Standard module configuration/linkage
+ */
+
+static struct builtin bintab[] = {
+    BUILTIN("nameref", BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "gur", "n")
+};
+
+#include "zsh.mdh"
+
+static void
+edcharsetfn(Param pm, char *x)
+{
+    /*
+     * To make this work like ksh, we must intercept $KEYS before the widget
+     * is looked up, so that changing the key sequence causes a different
+     * widget to be substituted.  Somewhat similar to "bindkey -s".
+     *
+     * Ksh93 adds SIGKEYBD to the trap list for this purpose.
+     */
+    ;
+}
+
+static char **
+matchgetfn(Param pm)
+{
+    char **zsh_match = getaparam("match");
+
+    /* For this to work accurately, ksh emulation should always imply
+     * that the (#m) and (#b) extendedglob operators are enabled.
+     *
+     * When we have a 0th element (ksharrays), it is $MATCH.  Elements
+     * 1st and larger mirror the $match array.
+     */
+
+    if (pm->u.arr)
+	freearray(pm->u.arr);
+    if (zsh_match && *zsh_match) {
+	if (isset(KSHARRAYS)) {
+	    char **ap =
+		(char **) zalloc(sizeof(char *) * (arrlen(zsh_match)+1));
+	    pm->u.arr = ap;
+	    *ap++ = ztrdup(getsparam("MATCH"));
+	    while (*zsh_match)
+		*ap = ztrdup(*zsh_match++);
+	} else
+	    pm->u.arr = zarrdup(zsh_match);
+    } else if (isset(KSHARRAYS)) {
+	pm->u.arr = mkarray(ztrdup(getsparam("MATCH")));
+    } else
+	pm->u.arr = NULL;
+
+    return arrgetfn(pm);
+}
+
+static const struct gsu_scalar constant_gsu =
+    { strgetfn, NULL, nullunsetfn };
+
+static const struct gsu_scalar sh_edchar_gsu =
+    { strvargetfn, edcharsetfn, nullunsetfn };
+static const struct gsu_scalar sh_edmode_gsu =
+    { strgetfn, nullstrsetfn, nullunsetfn };
+static const struct gsu_array sh_match_gsu =
+    { matchgetfn, arrsetfn, stdunsetfn };
+static const struct gsu_scalar sh_name_gsu =
+    { strvargetfn, nullstrsetfn, nullunsetfn };
+static const struct gsu_scalar sh_subscript_gsu =
+    { strvargetfn, nullstrsetfn, nullunsetfn };
+
+static char sh_unsetval[2];	/* Dummy to treat as NULL */
+static char *sh_name = sh_unsetval;
+static char *sh_subscript = sh_unsetval;
+static char *sh_edchar = sh_unsetval;
+static char sh_edmode[2];
+
+/*
+ * Some parameters listed here do not appear in ksh93.mdd autofeatures
+ * because they are only instantiated by ksh93_wrapper() below.  This
+ * obviously includes those commented out here.
+ */
+static struct paramdef partab[] = {
+    PARAMDEF(".sh.edchar", PM_SCALAR|PM_SPECIAL,
+	     &sh_edchar, &sh_edchar_gsu),
+    PARAMDEF(".sh.edmode", PM_SCALAR|PM_READONLY|PM_SPECIAL,
+	     &sh_edmode, &sh_edmode_gsu),
+    PARAMDEF(".sh.file", PM_NAMEREF|PM_READONLY, "ZSH_SCRIPT", &constant_gsu),
+    PARAMDEF(".sh.lineno", PM_NAMEREF|PM_READONLY, "LINENO", &constant_gsu),
+    PARAMDEF(".sh.match", PM_ARRAY|PM_READONLY, NULL, &sh_match_gsu),
+    PARAMDEF(".sh.name", PM_SCALAR|PM_READONLY|PM_SPECIAL,
+	     &sh_name, &sh_name_gsu),
+    PARAMDEF(".sh.subscript", PM_SCALAR|PM_READONLY|PM_SPECIAL,
+	     &sh_subscript, &sh_subscript_gsu),
+    PARAMDEF(".sh.subshell", PM_NAMEREF|PM_READONLY, "ZSH_SUBSHELL", &constant_gsu),
+    /* SPECIALPMDEF(".sh.value", 0, NULL, NULL, NULL), */
+    PARAMDEF(".sh.version", PM_NAMEREF|PM_READONLY, "ZSH_PATCHLEVEL", &constant_gsu)
+};
+
+static struct features module_features = {
+    bintab, sizeof(bintab)/sizeof(*bintab),
+    NULL, 0,
+    NULL, 0,
+    partab, sizeof(partab)/sizeof(*partab),
+    0
+};
+
+/**/
+static int
+ksh93_wrapper(Eprog prog, FuncWrap w, char *name)
+{
+    Funcstack f;
+    Param pm;
+    zlong num = funcstack->prev ? getiparam(".sh.level") : 0;
+
+    if (!EMULATION(EMULATE_KSH))
+	return 1;
+
+    if (num == 0)
+	for (f = funcstack; f; f = f->prev, num++);
+    else
+	num++;
+
+    queue_signals();
+    ++locallevel;		/* Make these local */
+#define LOCAL_NAMEREF (PM_LOCAL|PM_UNSET|PM_NAMEREF)
+    if ((pm = createparam(".sh.command", LOCAL_NAMEREF))) {
+	pm->level = locallevel;	/* Why is this necessary? */
+	/* Force scoping by assignent hack */
+	setloopvar(".sh.command", "ZSH_DEBUG_CMD");
+	pm->node.flags |= PM_READONLY;
+    }
+    /* .sh.edchar is in partab and below */
+    if (zleactive && (pm = createparam(".sh.edcol", LOCAL_NAMEREF))) {
+	pm->level = locallevel;
+	setloopvar(".sh.edcol", "CURSOR");
+	pm->node.flags |= (PM_NAMEREF|PM_READONLY);
+    }
+    /* .sh.edmode is in partab and below */
+    if (zleactive && (pm = createparam(".sh.edtext", LOCAL_NAMEREF))) {
+	pm->level = locallevel;
+	setloopvar(".sh.edtext", "BUFFER");
+	pm->node.flags |= PM_READONLY;
+    }
+
+    if ((pm = createparam(".sh.fun", PM_LOCAL|PM_UNSET))) {
+	pm->level = locallevel;
+	setsparam(".sh.fun", ztrdup(name));
+	pm->node.flags |= PM_READONLY;
+    }
+    if ((pm = createparam(".sh.level", PM_LOCAL|PM_UNSET))) {
+	pm->level = locallevel;
+	setiparam(".sh.level", num);
+    }
+    if (zleactive) {
+	extern mod_import_variable char *curkeymapname;	/* XXX */
+	extern mod_import_variable char *varedarg;	/* XXX */
+	/* bindkey -v forces VIMODE so this test is as good as any */
+	if (curkeymapname && isset(VIMODE) &&
+	    strcmp(curkeymapname, "main") == 0)
+	    strcpy(sh_edmode, "\033");
+	else
+	    strcpy(sh_edmode, "");
+	if (sh_edchar == sh_unsetval)
+	    sh_edchar = dupstring(getsparam("KEYS"));
+	if (varedarg) {
+	    char *ie = itype_end((sh_name = dupstring(varedarg)), INAMESPC, 0);
+	    if (ie && *ie) {
+		*ie++ = '\0';
+		/* Assume bin_vared has validated subscript */
+		sh_subscript = dupstring(ie);
+		ie = sh_subscript + strlen(sh_subscript);
+		*--ie = '\0';
+	    } else
+		sh_subscript = sh_unsetval;
+	    if ((pm = createparam(".sh.value", LOCAL_NAMEREF))) {
+		pm->level = locallevel;
+		setloopvar(".sh.value", "BUFFER");	/* Hack */
+		pm->node.flags |= PM_READONLY;
+	    }
+	} else
+	    sh_name = sh_subscript = sh_unsetval;
+    } else {
+	sh_edchar = sh_name = sh_subscript = sh_unsetval;
+	strcpy(sh_edmode, "");
+	/* TODO:
+	 * - disciplines
+	 * - special handling of .sh.value in math
+	 */
+    }
+    --locallevel;
+    unqueue_signals();
+
+    return 1;
+}
+
+static struct funcwrap wrapper[] = {
+    WRAPDEF(ksh93_wrapper),
+};
+
+/**/
+int
+setup_(UNUSED(Module m))
+{
+    return 0;
+}
+
+/**/
+int
+features_(Module m, char ***features)
+{
+    *features = featuresarray(m, &module_features);
+    return 0;
+}
+
+/**/
+int
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m, &module_features, enables);
+}
+
+/**/
+int
+boot_(Module m)
+{
+    return addwrapper(m, wrapper);
+}
+
+/**/
+int
+cleanup_(Module m)
+{
+    struct paramdef *p;
+
+    deletewrapper(m, wrapper);
+
+    /* Clean up namerefs, otherwise deleteparamdef() is confused */
+    for (p = partab; p < partab + sizeof(partab)/sizeof(*partab); ++p) {
+	if (p->flags & PM_NAMEREF) {
+	    HashNode hn = gethashnode2(paramtab, p->name);
+	    if (hn)
+		((Param)hn)->node.flags &= ~PM_NAMEREF;
+	}
+    }
+    return setfeatureenables(m, &module_features, NULL);
+}
+
+/**/
+int
+finish_(UNUSED(Module m))
+{
+    return 0;
+}
diff --git a/Src/Modules/ksh93.mdd b/Src/Modules/ksh93.mdd
new file mode 100644
index 000000000..85e35e9cb
--- /dev/null
+++ b/Src/Modules/ksh93.mdd
@@ -0,0 +1,10 @@
+name=zsh/ksh93
+link=either
+load=yes
+
+autofeatures="b:nameref"
+autofeatures_emu="b:nameref p:.sh.command p:.sh.edcol p:.sh.edtext p:.sh.file p:.sh.lineno p:.sh.match p:.sh.subshell p:.sh.version"
+
+moddeps="zsh/zle"
+
+objects="ksh93.o"
diff --git a/Src/Modules/mapfile.c b/Src/Modules/mapfile.c
index dd86fb596..84cdfea18 100644
--- a/Src/Modules/mapfile.c
+++ b/Src/Modules/mapfile.c
@@ -170,6 +170,8 @@ get_contents(char *fname)
 #ifdef USE_MMAP
     caddr_t mmptr;
     struct stat sbuf;
+#else
+    off_t size;
 #endif
     char *val;
     unmetafy(fname = ztrdup(fname), &fd);
@@ -196,12 +198,8 @@ get_contents(char *fname)
     close(fd);
 #else /* don't USE_MMAP */
     val = NULL;
-    if ((fd = open(fname, O_RDONLY | O_NOCTTY)) >= 0) {
-	LinkList ll;
-
-	if ((ll = readoutput(fd, 1, 0)))
-	    val = peekfirst(ll);
-    }
+    if ((size = zstuff(&val, fname)) > 0)
+	val = metafy(val, size, META_HEAPDUP);
 #endif /* USE_MMAP */
     free(fname);
     return val;
diff --git a/Src/Modules/param_private.c b/Src/Modules/param_private.c
index 24545819d..044617190 100644
--- a/Src/Modules/param_private.c
+++ b/Src/Modules/param_private.c
@@ -87,12 +87,55 @@ makeprivate(HashNode hn, UNUSED(int flags))
 	      ((pm->node.flags & (PM_SPECIAL|PM_REMOVABLE)) == PM_SPECIAL &&
 	       /* typeset_single() line 2300 discards PM_REMOVABLE -- why? */
 	       !is_private(pm->old))))) {
-	    zwarnnam("private", "can't change scope of existing param: %s",
-		     pm->node.nam);
-	    makeprivate_error = 1;
+	    if (is_private(pm->old)) {
+		if (pm->old->node.flags & PM_READONLY) {
+		    zerr("read-only variable: %s", pm->node.nam);
+		    makeprivate_error = 1;
+		} else if ((pm->node.flags | pm->old->node.flags) ==
+		    pm->old->node.flags) {
+		    /* private called twice on same parameter */
+		    Param tpm = pm;
+		    pm = pm->old;
+		    --locallevel;
+		    /* why have a union if we need this switch anyway? */
+		    switch (PM_TYPE(pm->node.flags)) {
+		    case PM_SCALAR:
+			pm->gsu.s->setfn(pm, tpm->u.str);
+			tpm->u.str = NULL;
+			break;
+		    case PM_INTEGER:
+			pm->gsu.i->setfn(pm, tpm->u.val);
+			break;
+		    case PM_EFLOAT:
+		    case PM_FFLOAT:
+			pm->gsu.f->setfn(pm, tpm->u.dval);
+			break;
+		    case PM_ARRAY:
+			pm->gsu.a->setfn(pm, tpm->u.arr);
+			tpm->u.arr = NULL;
+			break;
+		    case PM_HASHED:
+			pm->gsu.h->setfn(pm, tpm->u.hash);
+			tpm->u.hash = NULL;
+			break;
+		    }
+		    ++locallevel;
+		    if (!(tpm->node.flags & PM_UNSET))
+			pm->node.flags &= ~PM_UNSET;
+		} else {
+		    zerrnam("private",
+			    "can't change type of private param: %s",
+			    pm->node.nam);
+		    makeprivate_error = 1;
+		}
+	    } else {
+		zerrnam("private", "can't change scope of existing param: %s",
+			pm->node.nam);
+		makeprivate_error = 1;
+	    }
 	    return;
 	}
-	struct gsu_closure *gsu = zhalloc(sizeof(struct gsu_closure));
+	struct gsu_closure *gsu = zalloc(sizeof(struct gsu_closure));
 	switch (PM_TYPE(pm->node.flags)) {
 	case PM_SCALAR:
 	    gsu->g = (void *)(pm->gsu.s);
@@ -122,10 +165,11 @@ makeprivate(HashNode hn, UNUSED(int flags))
 	    break;
 	default:
 	    makeprivate_error = 1;
+	    zfree(gsu, sizeof(struct gsu_closure));
 	    break;
 	}
 	/* PM_HIDE so new parameters in deeper scopes do not shadow */
-	pm->node.flags |= (PM_HIDE|PM_SPECIAL|PM_REMOVABLE);
+	pm->node.flags |= (PM_HIDE|PM_SPECIAL|PM_REMOVABLE|PM_RO_BY_DESIGN);
 	pm->level -= 1;
     }
 }
@@ -171,7 +215,7 @@ bin_private(char *nam, char **args, LinkList assigns, Options ops, int func)
 {
     int from_typeset = 1;
     int ofake = fakelevel;	/* paranoia in case of recursive call */
-    int hasargs = *args != NULL || (assigns && firstnode(assigns));
+    int hasargs = /* *args != NULL || */ (assigns && firstnode(assigns));
     makeprivate_error = 0;
 
     if (!OPT_ISSET(ops, 'P')) {
@@ -190,8 +234,10 @@ bin_private(char *nam, char **args, LinkList assigns, Options ops, int func)
 	return bin_typeset("private", args, assigns, ops, func);
     }
 
-    ops->ind['g'] = 2;	/* force bin_typeset() to behave as "local" */
-    if (OPT_ISSET(ops, 'p') || (!hasargs && OPT_ISSET(ops, '+'))) {
+    if (!(OPT_ISSET(ops, 'm') || OPT_ISSET(ops, '+')))
+	ops->ind['g'] = 2;	/* force bin_typeset() to behave as "local" */
+    if (OPT_ISSET(ops, 'p') || OPT_ISSET(ops, 'm') ||
+	(!hasargs && OPT_ISSET(ops, '+'))) {
 	return bin_typeset("private", args, assigns, ops, func);
     }
 
@@ -227,7 +273,9 @@ setfn_error(Param pm)
  * calling the original unsetfn.  This assures that if the old unsetfn
  * wants to use its getfn or setfn, they're unconditionally present.
  * The "explicit" flag indicates that "unset" was called, if zero the
- * parameter is going out of scope (see params.c).
+ * parameter is going out of scope (see params.c).  PM_DECLARED is
+ * asserted as if TYPESET_TO_UNSET were in use so that the private
+ * parameter is re-used rather than re-created when assigned again.
  *
  */
 
@@ -250,7 +298,7 @@ pps_setfn(Param pm, char *x)
 {
     struct gsu_closure *c = (struct gsu_closure *)(pm->gsu.s);
     GsuScalar gsu = (GsuScalar)(c->g);
-    if (locallevel == pm->level)
+    if (locallevel == pm->level || locallevel > private_wraplevel)
 	gsu->setfn(pm, x);
     else
 	setfn_error(pm);
@@ -265,8 +313,11 @@ pps_unsetfn(Param pm, int explicit)
     pm->gsu.s = gsu;
     if (locallevel <= pm->level)
 	gsu->unsetfn(pm, explicit);
-    if (explicit)
+    if (explicit) {
+	pm->node.flags |= PM_DECLARED;
 	pm->gsu.s = (GsuScalar)c;
+    } else
+	zfree(c, sizeof(struct gsu_closure));
 }
 
 /**/
@@ -287,7 +338,7 @@ ppi_setfn(Param pm, zlong x)
 {
     struct gsu_closure *c = (struct gsu_closure *)(pm->gsu.i);
     GsuInteger gsu = (GsuInteger)(c->g);
-    if (locallevel == pm->level)
+    if (locallevel == pm->level || locallevel > private_wraplevel)
 	gsu->setfn(pm, x);
     else
 	setfn_error(pm);
@@ -302,8 +353,11 @@ ppi_unsetfn(Param pm, int explicit)
     pm->gsu.i = gsu;
     if (locallevel <= pm->level)
 	gsu->unsetfn(pm, explicit);
-    if (explicit)
+    if (explicit) {
+	pm->node.flags |= PM_DECLARED;
 	pm->gsu.i = (GsuInteger)c;
+    } else
+	zfree(c, sizeof(struct gsu_closure));
 }
 
 /**/
@@ -324,7 +378,7 @@ ppf_setfn(Param pm, double x)
 {
     struct gsu_closure *c = (struct gsu_closure *)(pm->gsu.f);
     GsuFloat gsu = (GsuFloat)(c->g);
-    if (locallevel == pm->level)
+    if (locallevel == pm->level || locallevel > private_wraplevel)
 	gsu->setfn(pm, x);
     else
 	setfn_error(pm);
@@ -339,8 +393,11 @@ ppf_unsetfn(Param pm, int explicit)
     pm->gsu.f = gsu;
     if (locallevel <= pm->level)
 	gsu->unsetfn(pm, explicit);
-    if (explicit)
+    if (explicit) {
+	pm->node.flags |= PM_DECLARED;
 	pm->gsu.f = (GsuFloat)c;
+    } else
+	zfree(c, sizeof(struct gsu_closure));
 }
 
 /**/
@@ -362,7 +419,7 @@ ppa_setfn(Param pm, char **x)
 {
     struct gsu_closure *c = (struct gsu_closure *)(pm->gsu.a);
     GsuArray gsu = (GsuArray)(c->g);
-    if (locallevel == pm->level)
+    if (locallevel == pm->level || locallevel > private_wraplevel)
 	gsu->setfn(pm, x);
     else
 	setfn_error(pm);
@@ -377,8 +434,11 @@ ppa_unsetfn(Param pm, int explicit)
     pm->gsu.a = gsu;
     if (locallevel <= pm->level)
 	gsu->unsetfn(pm, explicit);
-    if (explicit)
+    if (explicit) {
+	pm->node.flags |= PM_DECLARED;
 	pm->gsu.a = (GsuArray)c;
+    } else
+	zfree(c, sizeof(struct gsu_closure));
 }
 
 static HashTable emptytable;
@@ -401,7 +461,7 @@ pph_setfn(Param pm, HashTable x)
 {
     struct gsu_closure *c = (struct gsu_closure *)(pm->gsu.h);
     GsuHash gsu = (GsuHash)(c->g);
-    if (locallevel == pm->level)
+    if (locallevel == pm->level || locallevel > private_wraplevel)
 	gsu->setfn(pm, x);
     else
 	setfn_error(pm);
@@ -416,8 +476,11 @@ pph_unsetfn(Param pm, int explicit)
     pm->gsu.h = gsu;
     if (locallevel <= pm->level)
 	gsu->unsetfn(pm, explicit);
-    if (explicit)
+    if (explicit) {
+	pm->node.flags |= PM_DECLARED;
 	pm->gsu.h = (GsuHash)c;
+    } else
+	zfree(c, sizeof(struct gsu_closure));
 }
 
 /*
@@ -477,18 +540,19 @@ static struct funcwrap wrapper[] = {
 };
 
 /**/
+static int private_wraplevel = 0;
+
+/**/
 static int
 wrap_private(Eprog prog, FuncWrap w, char *name)
 {
-    static int wraplevel = 0;
-
-    if (wraplevel < locallevel /* && strcmp(name, "(anon)") != 0 */) {
-	int owl = wraplevel;
-	wraplevel = locallevel;
+    if (private_wraplevel < locallevel /* && strcmp(name, "(anon)") != 0 */) {
+	int owl = private_wraplevel;
+	private_wraplevel = locallevel;
 	scanhashtable(paramtab, 0, 0, 0, scopeprivate, PM_UNSET);
 	runshfunc(prog, w, name);
 	scanhashtable(paramtab, 0, 0, 0, scopeprivate, 0);
-	wraplevel = owl;
+	private_wraplevel = owl;
 	return 0;
     }
     return 1;
@@ -500,27 +564,50 @@ static GetNodeFunc getparamnode;
 static HashNode
 getprivatenode(HashTable ht, const char *nam)
 {
-    HashNode hn = getparamnode(ht, nam);
+    /* getparamnode() would follow namerefs, we must not do that here */
+    HashNode hn = gethashnode2(ht, nam);
     Param pm = (Param) hn;
 
-    while (!fakelevel && pm && locallevel > pm->level && is_private(pm)) {
+    /* autoload has precedence over nameref, so getparamnode() */
+    if (pm && (pm->node.flags & PM_AUTOLOAD)) {
+	hn = getparamnode(ht, nam);
+	pm = (Param) hn;
+	/* how would an autoloaded private behave?  return here? */
+    }
+    while (!fakelevel && pm && is_private(pm) && locallevel > pm->level) {
+	if (pm->level == private_wraplevel + 1) {
+	    /* Variable is in the current function scope */
+	    break;
+	}
+#if 0
 	if (!(pm->node.flags & PM_UNSET)) {
 	    /*
 	     * private parameters are always marked PM_UNSET before we
-	     * increment locallevel, so the only way we get here is
-	     * when createparam() wants a new parameter that is not at
-	     * the current locallevel and it has therefore cleared the
-	     * PM_UNSET flag.
+	     * increment locallevel, so there are three possible ways
+	     * to get here:
+	     *  1) createparam() wants a new parameter that is not at
+	     *  the current locallevel and it has therefore cleared the
+	     *  PM_UNSET flag
+	     *  2) locallevel has been incremented (startparamscope())
+	     *  outside the usual function call stack (private_wraplevel)
+	     *  3) dynamic scoping is fetching a value from a surrounding
+	     *  scope, we don't know if that's for assign or just expand
+	     * The first of those is now caught in createparam() when
+	     * testing PM_RO_BY_DESIGN and the second occurs only in
+	     * nofork substitution or handling of ZLE specials.  If the
+	     * third is an assignment, the GSU setfn rejects it.
 	     */
 	    DPUTS(pm->old, "BUG: PM_UNSET cleared in wrong scope");
-	    setfn_error(pm);
-	    /*
-	     * TODO: instead of throwing an error here, create a global
-	     * parameter, insert as pm->old, handle WARN_CREATE_GLOBAL.
-	     */
 	}
+#endif
 	pm = pm->old;
     }
+
+    /* resolve nameref after skipping private parameters */
+    if (pm && (pm->node.flags & PM_NAMEREF) &&
+	(pm->u.str || (pm->node.flags & PM_UNSET)))
+	pm = (Param) resolve_nameref(pm, NULL);
+
     return (HashNode)pm;
 }
 
@@ -559,7 +646,7 @@ printprivatenode(HashNode hn, int printflags)
 
 static struct builtin bintab[] = {
     /* Copied from BUILTIN("local"), "P" added */
-    BUILTIN("private", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_private, 0, -1, 0, "AE:%F:%HL:%PR:%TUZ:%ahi:%lprtux", "P")
+    BUILTIN("private", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_private, 0, -1, 0, "AE:%F:%HL:%PR:%TUZ:%ahi:%lnmrtux", "P")
 };
 
 static struct features module_features = {
diff --git a/Src/Modules/parameter.c b/Src/Modules/parameter.c
index ef9148d7b..7441c30b8 100644
--- a/Src/Modules/parameter.c
+++ b/Src/Modules/parameter.c
@@ -49,13 +49,15 @@ paramtypestr(Param pm)
 	if (pm->node.flags & PM_AUTOLOAD)
 	    return dupstring("undefined");
 
-	switch (PM_TYPE(f)) {
+	/* For simplicity we treat PM_NAMEREF as PM_TYPE(PM_SCALAR) */
+	switch (PM_TYPE(f)|(f & PM_NAMEREF)) {
 	case PM_SCALAR:  val = "scalar"; break;
 	case PM_ARRAY:   val = "array"; break;
 	case PM_INTEGER: val = "integer"; break;
 	case PM_EFLOAT:
 	case PM_FFLOAT:  val = "float"; break;
 	case PM_HASHED:  val = "association"; break;
+	case PM_NAMEREF: val = "nameref"; break;
 	}
 	DPUTS(!val, "BUG: type not handled in parameter");
 	val = dupstring(val);
@@ -103,10 +105,15 @@ getpmparameter(UNUSED(HashTable ht), const char *name)
     pm->node.nam = dupstring(name);
     pm->node.flags = PM_SCALAR | PM_READONLY;
     pm->gsu.s = &nullsetscalar_gsu;
-    if ((rpm = (Param) realparamtab->getnode(realparamtab, name)) &&
-	!(rpm->node.flags & PM_UNSET))
+    if ((rpm = (Param) realparamtab->getnode2(realparamtab, name)) &&
+	!(rpm->node.flags & PM_UNSET)) {
 	pm->u.str = paramtypestr(rpm);
-    else {
+	if ((rpm->node.flags & PM_NAMEREF) && rpm->u.str && *(rpm->u.str) &&
+	    (rpm = (Param) realparamtab->getnode(realparamtab, name)) &&
+	    !(rpm->node.flags & PM_UNSET)) {
+	    pm->u.str = zhtricat(pm->u.str, "-", paramtypestr(rpm));
+	}
+    } else {
 	pm->u.str = dupstring("");
 	pm->node.flags |= (PM_UNSET|PM_SPECIAL);
     }
@@ -302,7 +309,7 @@ setfunction(char *name, char *val, int dis)
     shfunc_set_sticky(shf);
 
     if (!strncmp(name, "TRAP", 4) &&
-	(sn = getsignum(name + 4)) != -1) {
+	(sn = getsigidx(name + 4)) != -1) {
 	if (settrap(sn, NULL, ZSIG_FUNC)) {
 	    freeeprog(shf->funcdef);
 	    zfree(shf, sizeof(*shf));
@@ -592,7 +599,7 @@ getpmfunction_source(HashTable ht, const char *name)
     return getfunction_source(ht, name, 0);
 }
 
-/* Param table entry for retrieving ds_functions_source element */
+/* Param table entry for retrieving dis_functions_source element */
 
 /**/
 static HashNode
@@ -1226,9 +1233,16 @@ histwgetfn(UNUSED(Param pm))
             pushnode(l, getdata(n));
 
     while (he) {
+	char *hstr = he->node.nam;
+	int len = strlen(hstr);
 	for (iw = he->nwords - 1; iw >= 0; iw--) {
-	    h = he->node.nam + he->words[iw * 2];
-	    e = he->node.nam + he->words[iw * 2 + 1];
+	    int wbegin = he->words[iw * 2];
+	    int wend = he->words[iw * 2 + 1];
+
+	    if (wbegin < 0 || wbegin >= len || wend < 0 || wend > len)
+		break;
+	    h = hstr + wbegin;
+	    e = hstr + wend;
 	    sav = *e;
 	    *e = '\0';
 	    addlinknode(l, dupstring(h));
@@ -1244,19 +1258,19 @@ histwgetfn(UNUSED(Param pm))
 
 /**/
 static char *
-pmjobtext(int job)
+pmjobtext(Job jtab, int job)
 {
     Process pn;
     int len = 1;
     char *ret;
 
-    for (pn = jobtab[job].procs; pn; pn = pn->next)
+    for (pn = jtab[job].procs; pn; pn = pn->next)
 	len += strlen(pn->text) + 3;
 
     ret = (char *) zhalloc(len);
     ret[0] = '\0';
 
-    for (pn = jobtab[job].procs; pn; pn = pn->next) {
+    for (pn = jtab[job].procs; pn; pn = pn->next) {
 	strcat(ret, pn->text);
 	if (pn->next)
 	    strcat(ret, " | ");
@@ -1269,22 +1283,25 @@ static HashNode
 getpmjobtext(UNUSED(HashTable ht), const char *name)
 {
     Param pm = NULL;
-    int job;
+    int job, jmax;
     char *pend;
+    Job jtab;
 
     pm = (Param) hcalloc(sizeof(struct param));
     pm->node.nam = dupstring(name);
     pm->node.flags = PM_SCALAR | PM_READONLY;
     pm->gsu.s = &nullsetscalar_gsu;
 
+    selectjobtab(&jtab, &jmax);
+
     job = strtod(name, &pend);
     /* Non-numeric keys are looked up by job name */
     if (*pend)
 	job = getjob(name, NULL);
-    if (job >= 1 && job <= maxjob &&
-	jobtab[job].stat && jobtab[job].procs &&
-	!(jobtab[job].stat & STAT_NOPRINT))
-	pm->u.str = pmjobtext(job);
+    if (job >= 1 && job <= jmax &&
+	jtab[job].stat && jtab[job].procs &&
+	!(jtab[job].stat & STAT_NOPRINT))
+	pm->u.str = pmjobtext(jtab, job);
     else {
 	pm->u.str = dupstring("");
 	pm->node.flags |= (PM_UNSET|PM_SPECIAL);
@@ -1297,22 +1314,25 @@ static void
 scanpmjobtexts(UNUSED(HashTable ht), ScanFunc func, int flags)
 {
     struct param pm;
-    int job;
+    int job, jmax;
     char buf[40];
+    Job jtab;
 
     memset((void *)&pm, 0, sizeof(struct param));
     pm.node.flags = PM_SCALAR | PM_READONLY;
     pm.gsu.s = &nullsetscalar_gsu;
 
-    for (job = 1; job <= maxjob; job++) {
-	if (jobtab[job].stat && jobtab[job].procs &&
-	    !(jobtab[job].stat & STAT_NOPRINT)) {
+    selectjobtab(&jtab, &jmax);
+
+    for (job = 1; job <= jmax; job++) {
+	if (jtab[job].stat && jtab[job].procs &&
+	    !(jtab[job].stat & STAT_NOPRINT)) {
 	    if (func != scancountparams) {
 		sprintf(buf, "%d", job);
 		pm.node.nam = dupstring(buf);
 		if ((flags & (SCANPM_WANTVALS|SCANPM_MATCHVAL)) ||
 		    !(flags & SCANPM_WANTKEYS))
-		    pm.u.str = pmjobtext(job);
+		    pm.u.str = pmjobtext(jtab, job);
 	    }
 	    func(&pm.node, flags);
 	}
@@ -1323,7 +1343,7 @@ scanpmjobtexts(UNUSED(HashTable ht), ScanFunc func, int flags)
 
 /**/
 static char *
-pmjobstate(int job)
+pmjobstate(Job jtab, int job)
 {
     Process pn;
     char buf[256], buf2[128], *ret, *state, *cp;
@@ -1335,14 +1355,14 @@ pmjobstate(int job)
     else
 	cp = ":";
 
-    if (jobtab[job].stat & STAT_DONE)
+    if (jtab[job].stat & STAT_DONE)
 	ret = dyncat("done", cp);
-    else if (jobtab[job].stat & STAT_STOPPED)
+    else if (jtab[job].stat & STAT_STOPPED)
 	ret = dyncat("suspended", cp);
     else
 	ret = dyncat("running", cp);
 
-    for (pn = jobtab[job].procs; pn; pn = pn->next) {
+    for (pn = jtab[job].procs; pn; pn = pn->next) {
 
 	if (pn->status == SP_RUNNING)
 	    state = "running";
@@ -1371,21 +1391,24 @@ static HashNode
 getpmjobstate(UNUSED(HashTable ht), const char *name)
 {
     Param pm = NULL;
-    int job;
+    int job, jmax;
     char *pend;
+    Job jtab;
 
     pm = (Param) hcalloc(sizeof(struct param));
     pm->node.nam = dupstring(name);
     pm->node.flags = PM_SCALAR | PM_READONLY;
     pm->gsu.s = &nullsetscalar_gsu;
 
+    selectjobtab(&jtab, &jmax);
+
     job = strtod(name, &pend);
     if (*pend)
 	job = getjob(name, NULL);
-    if (job >= 1 && job <= maxjob &&
-	jobtab[job].stat && jobtab[job].procs &&
-	!(jobtab[job].stat & STAT_NOPRINT))
-	pm->u.str = pmjobstate(job);
+    if (job >= 1 && job <= jmax &&
+	jtab[job].stat && jtab[job].procs &&
+	!(jtab[job].stat & STAT_NOPRINT))
+	pm->u.str = pmjobstate(jtab, job);
     else {
 	pm->u.str = dupstring("");
 	pm->node.flags |= (PM_UNSET|PM_SPECIAL);
@@ -1398,22 +1421,25 @@ static void
 scanpmjobstates(UNUSED(HashTable ht), ScanFunc func, int flags)
 {
     struct param pm;
-    int job;
+    int job, jmax;
+    Job jtab;
     char buf[40];
 
+    selectjobtab(&jtab, &jmax);
+
     memset((void *)&pm, 0, sizeof(struct param));
     pm.node.flags = PM_SCALAR | PM_READONLY;
     pm.gsu.s = &nullsetscalar_gsu;
 
-    for (job = 1; job <= maxjob; job++) {
-	if (jobtab[job].stat && jobtab[job].procs &&
-	    !(jobtab[job].stat & STAT_NOPRINT)) {
+    for (job = 1; job <= jmax; job++) {
+	if (jtab[job].stat && jtab[job].procs &&
+	    !(jtab[job].stat & STAT_NOPRINT)) {
 	    if (func != scancountparams) {
 		sprintf(buf, "%d", job);
 		pm.node.nam = dupstring(buf);
 		if ((flags & (SCANPM_WANTVALS|SCANPM_MATCHVAL)) ||
 		    !(flags & SCANPM_WANTKEYS))
-		    pm.u.str = pmjobstate(job);
+		    pm.u.str = pmjobstate(jtab, job);
 	    }
 	    func(&pm.node, flags);
 	}
@@ -1424,11 +1450,11 @@ scanpmjobstates(UNUSED(HashTable ht), ScanFunc func, int flags)
 
 /**/
 static char *
-pmjobdir(int job)
+pmjobdir(Job jtab, int job)
 {
     char *ret;
 
-    ret = dupstring(jobtab[job].pwd ? jobtab[job].pwd : pwd);
+    ret = dupstring(jtab[job].pwd ? jtab[job].pwd : pwd);
     return ret;
 }
 
@@ -1437,21 +1463,24 @@ static HashNode
 getpmjobdir(UNUSED(HashTable ht), const char *name)
 {
     Param pm = NULL;
-    int job;
+    int job, jmax;
     char *pend;
+    Job jtab;
 
     pm = (Param) hcalloc(sizeof(struct param));
     pm->node.nam = dupstring(name);
     pm->node.flags = PM_SCALAR | PM_READONLY;
     pm->gsu.s = &nullsetscalar_gsu;
 
+    selectjobtab(&jtab, &jmax);
+
     job = strtod(name, &pend);
     if (*pend)
 	job = getjob(name, NULL);
-    if (job >= 1 && job <= maxjob &&
-	jobtab[job].stat && jobtab[job].procs &&
-	!(jobtab[job].stat & STAT_NOPRINT))
-	pm->u.str = pmjobdir(job);
+    if (job >= 1 && job <= jmax &&
+	jtab[job].stat && jtab[job].procs &&
+	!(jtab[job].stat & STAT_NOPRINT))
+	pm->u.str = pmjobdir(jtab, job);
     else {
 	pm->u.str = dupstring("");
 	pm->node.flags |= (PM_UNSET|PM_SPECIAL);
@@ -1464,22 +1493,25 @@ static void
 scanpmjobdirs(UNUSED(HashTable ht), ScanFunc func, int flags)
 {
     struct param pm;
-    int job;
+    int job, jmax;
     char buf[40];
+    Job jtab;
 
     memset((void *)&pm, 0, sizeof(struct param));
     pm.node.flags = PM_SCALAR | PM_READONLY;
     pm.gsu.s = &nullsetscalar_gsu;
 
-    for (job = 1; job <= maxjob; job++) {
-       if (jobtab[job].stat && jobtab[job].procs &&
-           !(jobtab[job].stat & STAT_NOPRINT)) {
+    selectjobtab(&jtab, &jmax);
+
+    for (job = 1; job <= jmax; job++) {
+       if (jtab[job].stat && jtab[job].procs &&
+           !(jtab[job].stat & STAT_NOPRINT)) {
            if (func != scancountparams) {
 	       sprintf(buf, "%d", job);
 	       pm.node.nam = dupstring(buf);
                if ((flags & (SCANPM_WANTVALS|SCANPM_MATCHVAL)) ||
 		   !(flags & SCANPM_WANTKEYS))
-		   pm.u.str = pmjobdir(job);
+		   pm.u.str = pmjobdir(jtab, job);
 	   }
            func(&pm.node, flags);
        }
@@ -2011,6 +2043,9 @@ scanpmdissaliases(HashTable ht, ScanFunc func, int flags)
 /**/
 static Groupset get_all_groups(void)
 {
+#ifdef DISABLE_DYNAMIC_NSS
+    return NULL;
+#else
     Groupset gs = zhalloc(sizeof(*gs));
     Groupmap gaptr;
     gid_t *list, *lptr, egid;
@@ -2063,6 +2098,7 @@ static Groupset get_all_groups(void)
     }
 
     return gs;
+#endif /* DISABLE_DYNAMIC_NSS */
 }
 
 /* Standard hash element lookup. */
@@ -2081,7 +2117,11 @@ getpmusergroups(UNUSED(HashTable ht), const char *name)
     pm->gsu.s = &nullsetscalar_gsu;
 
     if (!gs) {
+#ifdef DISABLE_DYNAMIC_NSS
+	zerr("parameter 'usergroups' not available: NSS is disabled");
+#else
 	zerr("failed to retrieve groups for user: %e", errno);
+#endif
 	pm->u.str = dupstring("");
 	pm->node.flags |= (PM_UNSET|PM_SPECIAL);
 	return &pm->node;
@@ -2113,7 +2153,11 @@ scanpmusergroups(UNUSED(HashTable ht), ScanFunc func, int flags)
     Groupmap gaptr;
 
     if (!gs) {
+#ifdef DISABLE_DYNAMIC_NSS
+	zerr("parameter 'usergroups' not available: NSS is disabled");
+#else
 	zerr("failed to retrieve groups for user: %e", errno);
+#endif
 	return;
     }
 
diff --git a/Src/Modules/pcre.c b/Src/Modules/pcre.c
index 6289e003e..67157cc01 100644
--- a/Src/Modules/pcre.c
+++ b/Src/Modules/pcre.c
@@ -34,11 +34,11 @@
 #define CPCRE_PLAIN 0
 
 /**/
-#if defined(HAVE_PCRE_COMPILE) && defined(HAVE_PCRE_EXEC)
-#include <pcre.h>
+#if defined(HAVE_PCRE2_COMPILE_8) && defined(HAVE_PCRE2_H)
+#define PCRE2_CODE_UNIT_WIDTH 8
+#include <pcre2.h>
 
-static pcre *pcre_pattern;
-static pcre_extra *pcre_hints;
+static pcre2_code *pcre_pattern;
 
 /**/
 static int
@@ -47,8 +47,6 @@ zpcre_utf8_enabled(void)
 #if defined(MULTIBYTE_SUPPORT) && defined(HAVE_NL_LANGINFO) && defined(CODESET)
     static int have_utf8_pcre = -1;
 
-    /* value can toggle based on MULTIBYTE, so don't
-     * be too eager with caching */
     if (have_utf8_pcre < -1)
 	return 0;
 
@@ -56,15 +54,11 @@ zpcre_utf8_enabled(void)
 	return 0;
 
     if ((have_utf8_pcre == -1) &&
-        (!strcmp(nl_langinfo(CODESET), "UTF-8"))) {
-
-	if (pcre_config(PCRE_CONFIG_UTF8, &have_utf8_pcre))
-	    have_utf8_pcre = -2; /* erk, failed to ask */
+       (pcre2_config(PCRE2_CONFIG_UNICODE, &have_utf8_pcre))) {
+           have_utf8_pcre = -2; /* erk, failed to ask */
     }
 
-    if (have_utf8_pcre < 0)
-	return 0;
-    return have_utf8_pcre;
+    return (have_utf8_pcre == 1) && (!strcmp(nl_langinfo(CODESET), "UTF-8"));
 
 #else
     return 0;
@@ -75,47 +69,38 @@ zpcre_utf8_enabled(void)
 static int
 bin_pcre_compile(char *nam, char **args, Options ops, UNUSED(int func))
 {
-    int pcre_opts = 0, pcre_errptr, target_len;
-    const char *pcre_error;
+    uint32_t pcre_opts = 0;
+    int target_len;
+    int pcre_error;
+    PCRE2_SIZE pcre_offset;
     char *target;
     
-    if(OPT_ISSET(ops,'a')) pcre_opts |= PCRE_ANCHORED;
-    if(OPT_ISSET(ops,'i')) pcre_opts |= PCRE_CASELESS;
-    if(OPT_ISSET(ops,'m')) pcre_opts |= PCRE_MULTILINE;
-    if(OPT_ISSET(ops,'x')) pcre_opts |= PCRE_EXTENDED;
-    if(OPT_ISSET(ops,'s')) pcre_opts |= PCRE_DOTALL;
+    if (OPT_ISSET(ops, 'a')) pcre_opts |= PCRE2_ANCHORED;
+    if (OPT_ISSET(ops, 'i')) pcre_opts |= PCRE2_CASELESS;
+    if (OPT_ISSET(ops, 'm')) pcre_opts |= PCRE2_MULTILINE;
+    if (OPT_ISSET(ops, 'x')) pcre_opts |= PCRE2_EXTENDED;
+    if (OPT_ISSET(ops, 's')) pcre_opts |= PCRE2_DOTALL;
     
     if (zpcre_utf8_enabled())
-	pcre_opts |= PCRE_UTF8;
-
-#ifdef HAVE_PCRE_STUDY
-    if (pcre_hints)
-#ifdef PCRE_CONFIG_JIT
-	pcre_free_study(pcre_hints);
-#else
-	pcre_free(pcre_hints);
-#endif
-    pcre_hints = NULL;
-#endif
+	pcre_opts |= PCRE2_UTF;
 
     if (pcre_pattern)
-	pcre_free(pcre_pattern);
+	pcre2_code_free(pcre_pattern);
     pcre_pattern = NULL;
 
     target = ztrdup(*args);
     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);
+    pcre_pattern = pcre2_compile((PCRE2_SPTR) target, (PCRE2_SIZE) target_len,
+	    pcre_opts, &pcre_error, &pcre_offset, NULL);
 
     free(target);
 
     if (pcre_pattern == NULL)
     {
-	zwarnnam(nam, "error in regex: %s", pcre_error);
+	PCRE2_UCHAR buffer[256];
+	pcre2_get_error_message(pcre_error, buffer, sizeof(buffer));
+	zwarnnam(nam, "error in regex: %s", buffer);
 	return 1;
     }
     
@@ -123,67 +108,76 @@ bin_pcre_compile(char *nam, char **args, Options ops, UNUSED(int func))
 }
 
 /**/
-#ifdef HAVE_PCRE_STUDY
-
-/**/
 static int
 bin_pcre_study(char *nam, UNUSED(char **args), UNUSED(Options ops), UNUSED(int func))
 {
-    const char *pcre_error;
-
     if (pcre_pattern == NULL)
     {
 	zwarnnam(nam, "no pattern has been compiled for study");
 	return 1;
     }
-    
-    if (pcre_hints)
-#ifdef PCRE_CONFIG_JIT
-	pcre_free_study(pcre_hints);
-#else
-	pcre_free(pcre_hints);
-#endif
-    pcre_hints = NULL;
 
-    pcre_hints = pcre_study(pcre_pattern, 0, &pcre_error);
-    if (pcre_error != NULL)
-    {
-	zwarnnam(nam, "error while studying regex: %s", pcre_error);
-	return 1;
+    int jit = 0;
+    if (!pcre2_config(PCRE2_CONFIG_JIT, &jit) && jit) {
+	if (pcre2_jit_compile(pcre_pattern, PCRE2_JIT_COMPLETE) < 0) {
+	    zwarnnam(nam, "error while studying regex");
+	    return 1;
+	}
     }
     
     return 0;
 }
 
-/**/
-#else /* !HAVE_PCRE_STUDY */
+static int
+pcre_callout(pcre2_callout_block_8 *block, UNUSED(void *callout_data))
+{
+    Eprog prog;
+    int ret=0;
 
-# define bin_pcre_study bin_notavail
+    if (!block->callout_number &&
+	    ((prog = parse_string((char *) block->callout_string, 0))))
+    {
+	int ef = errflag, lv = lastval;
 
-/**/
-#endif /* !HAVE_PCRE_STUDY */
+	setsparam(".pcre.subject",
+		metafy((char *) block->subject, block->subject_length, META_DUP));
+	setiparam(".pcre.pos", block->current_position + 1);
+	execode(prog, 1, 0, "pcre");
+	ret = lastval | errflag;
+
+	/* Restore any user interrupt error status */
+	errflag = ef | (errflag & ERRFLAG_INT);
+	lastval = lv;
+    }
+
+    return ret;
+}
 
-/**/
 static int
-zpcre_get_substrings(char *arg, int *ovec, int captured_count, char *matchvar,
-		     char *substravar, int want_offset_pair, int matchedinarr,
-		     int want_begin_end)
+zpcre_get_substrings(pcre2_code *pat, char *arg, pcre2_match_data *mdata,
+	int captured_count, char *matchvar, char *substravar, char *namedassoc,
+	int want_offset_pair, int matchedinarr, int want_begin_end)
 {
-    char **captures, *match_all, **matches;
+    PCRE2_SIZE *ovec;
+    char *match_all, **matches;
     char offset_all[50];
     int capture_start = 1;
+    int vec_off;
+    PCRE2_SPTR ntable; /* table of named captures */
+    uint32_t ncount, nsize;
 
     if (matchedinarr) {
-	/* bash-style captures[0] entire-matched string in the array */
+	/* bash-style ovec[0] entire-matched string in the array */
 	capture_start = 0;
     }
 
-    /* captures[0] will be entire matched string, [1] first substring */
-    if (!pcre_get_substring_list(arg, ovec, captured_count, (const char ***)&captures)) {
-	int nelem = arrlen(captures)-1;
+    /* ovec[0] will be entire matched string, [1] first substring */
+    ovec = pcre2_get_ovector_pointer(mdata);
+    if (ovec) {
+	int nelem = captured_count - 1;
 	/* Set to the offsets of the complete match */
 	if (want_offset_pair) {
-	    sprintf(offset_all, "%d %d", ovec[0], ovec[1]);
+	    sprintf(offset_all, "%ld %ld", ovec[0], ovec[1]);
 	    setsparam("ZPCRE_OP", ztrdup(offset_all));
 	}
 	/*
@@ -192,7 +186,7 @@ zpcre_get_substrings(char *arg, int *ovec, int captured_count, char *matchvar,
 	 * ovec is length 2*(1+capture_list_length)
 	 */
 	if (matchvar) {
-	    match_all = metafy(captures[0], ovec[1] - ovec[0], META_DUP);
+	    match_all = metafy(arg + ovec[0], ovec[1] - ovec[0], META_DUP);
 	    setsparam(matchvar, match_all);
 	}
 	/*
@@ -207,21 +201,35 @@ zpcre_get_substrings(char *arg, int *ovec, int captured_count, char *matchvar,
 	 */
 	if (substravar &&
 	    (!want_begin_end || nelem)) {
-	    char **x, **y;
-	    int vec_off, i;
-	    y = &captures[capture_start];
+	    char **x;
+	    int i;
 	    matches = x = (char **) zalloc(sizeof(char *) * (captured_count+1-capture_start));
-	    for (i = capture_start; i < captured_count; i++, y++) {
+	    for (i = capture_start; i < captured_count; i++) {
 		vec_off = 2*i;
-		if (*y)
-		    *x++ = metafy(*y, ovec[vec_off+1]-ovec[vec_off], META_DUP);
-		else
-		    *x++ = NULL;
+		*x++ = metafy(arg + ovec[vec_off], ovec[vec_off+1]-ovec[vec_off], META_DUP);
 	    }
 	    *x = NULL;
 	    setaparam(substravar, matches);
 	}
 
+	if (namedassoc
+		&& !pcre2_pattern_info(pat, PCRE2_INFO_NAMECOUNT, &ncount) && ncount
+		&& !pcre2_pattern_info(pat, PCRE2_INFO_NAMEENTRYSIZE, &nsize)
+		&& !pcre2_pattern_info(pat, PCRE2_INFO_NAMETABLE, &ntable))
+	{
+	    char **hash, **hashptr;
+	    uint32_t nidx;
+	    hashptr = hash = (char **)zshcalloc((ncount+1)*2*sizeof(char *));
+	    for (nidx = 0; nidx < ncount; nidx++) {
+		vec_off = (ntable[nsize * nidx] << 9) + 2 * ntable[nsize * nidx + 1];
+		/* would metafy the key but pcre limits characters in the name */
+		*hashptr++ = ztrdup((char *) ntable + nsize * nidx + 2);
+		*hashptr++ = metafy(arg + ovec[vec_off],
+			ovec[vec_off+1]-ovec[vec_off], META_DUP);
+	    }
+	    sethparam(namedassoc, hash);
+	}
+
 	if (want_begin_end) {
 	    /*
 	     * cond-infix rather than builtin; also not bash; so we set a bunch
@@ -253,7 +261,8 @@ zpcre_get_substrings(char *arg, int *ovec, int captured_count, char *matchvar,
 	    setiparam("MEND", offs + !isset(KSHARRAYS) - 1);
 	    if (nelem) {
 		char **mbegin, **mend, **bptr, **eptr;
-		int i, *ipair;
+		int i;
+		size_t *ipair;
 
 		bptr = mbegin = zalloc(sizeof(char*)*(nelem+1));
 		eptr = mend = zalloc(sizeof(char*)*(nelem+1));
@@ -293,8 +302,6 @@ zpcre_get_substrings(char *arg, int *ovec, int captured_count, char *matchvar,
 		setaparam("mend", mend);
 	    }
 	}
-
-	pcre_free_substring_list((const char **)captures);
     }
 
     return 0;
@@ -320,29 +327,33 @@ getposint(char *instr, char *nam)
 static int
 bin_pcre_match(char *nam, char **args, Options ops, UNUSED(int func))
 {
-    int ret, capcount, *ovec, ovecsize, c;
+    int ret, c;
+    pcre2_match_data *pcre_mdata = NULL;
     char *matched_portion = NULL;
     char *plaintext = NULL;
-    char *receptacle = NULL;
+    char *receptacle;
+    char *named = NULL;
     int return_value = 1;
     /* The subject length and offset start are both int values in pcre_exec */
     int subject_len;
     int offset_start = 0;
     int want_offset_pair = 0;
+    int use_dfa = 0;
 
     if (pcre_pattern == NULL) {
 	zwarnnam(nam, "no pattern has been compiled");
 	return 1;
     }
 
-    matched_portion = "MATCH";
-    receptacle = "match";
-    if(OPT_HASARG(ops,c='a')) {
-	receptacle = OPT_ARG(ops,c);
-    }
-    if(OPT_HASARG(ops,c='v')) {
-	matched_portion = OPT_ARG(ops,c);
+    if (!(use_dfa = OPT_ISSET(ops, 'd'))) {
+	matched_portion = OPT_HASARG(ops, c='v') ? OPT_ARG(ops, c) : "MATCH";
+	named = OPT_HASARG(ops, c='A') ? OPT_ARG(ops, c) : ".pcre.match";
+    } else if (OPT_HASARG(ops, c='v') || OPT_HASARG(ops, c='A')) {
+	zwarnnam(nam, "-d cannot be combined with -%c", c);
+	return 1;
     }
+    receptacle = OPT_HASARG(ops, 'a') ? OPT_ARG(ops, 'a') : "match";
+
     if(OPT_HASARG(ops,c='n')) { /* The offset position to start the search, in bytes. */
 	if ((offset_start = getposint(OPT_ARG(ops,c), nam)) < 0)
 	    return 1;
@@ -350,36 +361,57 @@ bin_pcre_match(char *nam, char **args, Options ops, UNUSED(int func))
     /* For the entire match, 'Return' the offset byte positions instead of the matched string */
     if(OPT_ISSET(ops,'b')) want_offset_pair = 1;
 
-    if ((ret = pcre_fullinfo(pcre_pattern, pcre_hints, PCRE_INFO_CAPTURECOUNT, &capcount)))
-    {
-	zwarnnam(nam, "error %d in fullinfo", ret);
-	return 1;
-    }
-
-    ovecsize = (capcount+1)*3;
-    ovec = zalloc(ovecsize*sizeof(int));
-
     plaintext = ztrdup(*args);
     unmetafy(plaintext, &subject_len);
 
+    pcre2_match_context_8 *mcontext = pcre2_match_context_create(NULL);
+    pcre2_set_callout(mcontext, &pcre_callout, 0);
+
     if (offset_start > 0 && offset_start >= subject_len)
-	ret = PCRE_ERROR_NOMATCH;
-    else
-	ret = pcre_exec(pcre_pattern, pcre_hints, plaintext, subject_len, offset_start, 0, ovec, ovecsize);
+	ret = PCRE2_ERROR_NOMATCH;
+    else if (use_dfa) {
+	PCRE2_SIZE old, wscount = 128, capcount = 128;
+	void *workspace = zhalloc(sizeof(int) * wscount);
+	pcre_mdata = pcre2_match_data_create(capcount, NULL);
+	do {
+	    ret = pcre2_dfa_match(pcre_pattern, (PCRE2_SPTR) plaintext, subject_len,
+		offset_start, 0, pcre_mdata, mcontext, (int *) workspace, wscount);
+	    if (ret == PCRE2_ERROR_DFA_WSSIZE) {
+		old = wscount;
+		wscount += wscount / 2;
+		workspace = hrealloc(workspace, sizeof(int) * old, sizeof(int) * wscount);
+	    } else if (ret == 0) {
+		capcount += capcount / 2;
+		pcre2_match_data_free(pcre_mdata);
+		pcre_mdata = pcre2_match_data_create(capcount, NULL);
+	    } else
+		break;
+	} while(1);
+    } else {
+	pcre_mdata = pcre2_match_data_create_from_pattern(pcre_pattern, NULL);
+	ret = pcre2_match(pcre_pattern, (PCRE2_SPTR) plaintext, subject_len,
+		offset_start, 0, pcre_mdata, mcontext);
+	if (ret > 0)
+	    ret = pcre2_get_ovector_count(pcre_mdata);
+    }
 
     if (ret==0) return_value = 0;
-    else if (ret==PCRE_ERROR_NOMATCH) /* no match */;
+    else if (ret == PCRE2_ERROR_NOMATCH) /* no match */;
     else if (ret>0) {
-	zpcre_get_substrings(plaintext, ovec, ret, matched_portion, receptacle,
-			     want_offset_pair, 0, 0);
+	zpcre_get_substrings(pcre_pattern, plaintext, pcre_mdata, ret,
+		matched_portion, receptacle, named, want_offset_pair, use_dfa, 0);
 	return_value = 0;
     }
     else {
-	zwarnnam(nam, "error in pcre_exec [%d]", ret);
+	PCRE2_UCHAR buffer[256];
+	pcre2_get_error_message(ret, buffer, sizeof(buffer));
+	zwarnnam(nam, "error in pcre matching for %s: %s", *args, buffer);
     }
     
-    if (ovec)
-	zfree(ovec, ovecsize*sizeof(int));
+    if (pcre_mdata)
+	pcre2_match_data_free(pcre_mdata);
+    if (mcontext)
+	pcre2_match_context_free(mcontext);
     zsfree(plaintext);
 
     return return_value;
@@ -389,17 +421,19 @@ bin_pcre_match(char *nam, char **args, Options ops, UNUSED(int func))
 static int
 cond_pcre_match(char **a, int id)
 {
-    pcre *pcre_pat;
-    const char *pcre_err;
+    pcre2_code *pcre_pat = NULL;
+    int pcre_err;
+    PCRE2_SIZE pcre_erroff;
     char *lhstr, *rhre, *lhstr_plain, *rhre_plain, *avar, *svar;
-    int r = 0, pcre_opts = 0, pcre_errptr, capcnt, *ov, ovsize;
+    int r = 0, pcre_opts = 0;
+    pcre2_match_data *pcre_mdata = NULL;
     int lhstr_plain_len, rhre_plain_len;
     int return_value = 0;
 
     if (zpcre_utf8_enabled())
-	pcre_opts |= PCRE_UTF8;
+	pcre_opts |= PCRE2_UTF;
     if (isset(REMATCHPCRE) && !isset(CASEMATCH))
-	pcre_opts |= PCRE_CASELESS;
+	pcre_opts |= PCRE2_CASELESS;
 
     lhstr = cond_str(a,0,0);
     rhre = cond_str(a,1,0);
@@ -407,9 +441,6 @@ cond_pcre_match(char **a, int id)
     rhre_plain = ztrdup(rhre);
     unmetafy(lhstr_plain, &lhstr_plain_len);
     unmetafy(rhre_plain, &rhre_plain_len);
-    pcre_pat = NULL;
-    ov = NULL;
-    ovsize = 0;
 
     if (isset(BASHREMATCH)) {
 	svar = NULL;
@@ -421,27 +452,27 @@ 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);
+		if (!(pcre_pat = pcre2_compile((PCRE2_SPTR) rhre_plain,
+			(PCRE2_SIZE) rhre_plain_len, pcre_opts,
+			&pcre_err, &pcre_erroff, NULL)))
+		{
+		    PCRE2_UCHAR buffer[256];
+		    pcre2_get_error_message(pcre_err, buffer, sizeof(buffer));
+		    zwarn("failed to compile regexp /%s/: %s", rhre, buffer);
 		    break;
 		}
-                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, lhstr_plain_len, 0, 0, ov, ovsize);
-		/* r < 0 => error; r==0 match but not enough size in ov
+		pcre_mdata = pcre2_match_data_create_from_pattern(pcre_pat, NULL);
+		r = pcre2_match(pcre_pat, (PCRE2_SPTR8) lhstr_plain, lhstr_plain_len,
+			0, 0, pcre_mdata, NULL);
+		/* r < 0 => error; r==0 match but not enough size in match data
 		 * r > 0 => (r-1) substrings found; r==1 => no substrings
 		 */
     		if (r==0) {
-		    zwarn("reportable zsh problem: pcre_exec() returned 0");
+		    zwarn("reportable zsh problem: pcre2_match() returned 0");
 		    return_value = 1;
 		    break;
 		}
-	        else if (r==PCRE_ERROR_NOMATCH) {
+		else if (r == PCRE2_ERROR_NOMATCH) {
 		    return_value = 0; /* no match */
 		    break;
 		}
@@ -450,9 +481,9 @@ cond_pcre_match(char **a, int id)
 		    break;
 		}
                 else if (r>0) {
-		    zpcre_get_substrings(lhstr_plain, ov, r, svar, avar, 0,
-					 isset(BASHREMATCH),
-					 !isset(BASHREMATCH));
+		    uint32_t ovec_count = pcre2_get_ovector_count(pcre_mdata);
+		    zpcre_get_substrings(pcre_pat, lhstr_plain, pcre_mdata, ovec_count, svar, avar,
+			    ".pcre.match", 0, isset(BASHREMATCH), !isset(BASHREMATCH));
 		    return_value = 1;
 		    break;
 		}
@@ -463,10 +494,10 @@ cond_pcre_match(char **a, int id)
 	free(lhstr_plain);
     if(rhre_plain)
 	free(rhre_plain);
+    if (pcre_mdata)
+	pcre2_match_data_free(pcre_mdata);
     if (pcre_pat)
-	pcre_free(pcre_pat);
-    if (ov)
-	zfree(ov, ovsize*sizeof(int));
+	pcre2_code_free(pcre_pat);
 
     return return_value;
 }
@@ -488,18 +519,18 @@ static struct conddef cotab[] = {
 
 static struct builtin bintab[] = {
     BUILTIN("pcre_compile", 0, bin_pcre_compile, 1, 1, 0, "aimxs",  NULL),
-    BUILTIN("pcre_match",   0, bin_pcre_match,   1, 1, 0, "a:v:n:b",    NULL),
+    BUILTIN("pcre_match",   0, bin_pcre_match,   1, 1, 0, "A:a:v:n:bd",    NULL),
     BUILTIN("pcre_study",   0, bin_pcre_study,   0, 0, 0, NULL,    NULL)
 };
 
 
 static struct features module_features = {
     bintab, sizeof(bintab)/sizeof(*bintab),
-#if defined(HAVE_PCRE_COMPILE) && defined(HAVE_PCRE_EXEC)
+#if defined(HAVE_PCRE2_COMPILE_8) && defined(HAVE_PCRE2_H)
     cotab, sizeof(cotab)/sizeof(*cotab),
-#else /* !(HAVE_PCRE_COMPILE && HAVE_PCRE_EXEC) */
+#else /* !(HAVE_PCRE2_COMPILE_8 && HAVE_PCRE2_H) */
     NULL, 0,
-#endif /* !(HAVE_PCRE_COMPILE && HAVE_PCRE_EXEC) */
+#endif /* !(HAVE_PCRE2_COMPILE_8 && HAVE_PCRE2_H) */
     NULL, 0,
     NULL, 0,
     0
@@ -546,19 +577,9 @@ cleanup_(Module m)
 int
 finish_(UNUSED(Module m))
 {
-#if defined(HAVE_PCRE_COMPILE) && defined(HAVE_PCRE_EXEC)
-#ifdef HAVE_PCRE_STUDY
-    if (pcre_hints)
-#ifdef PCRE_CONFIG_JIT
-	pcre_free_study(pcre_hints);
-#else
-	pcre_free(pcre_hints);
-#endif
-    pcre_hints = NULL;
-#endif
-
+#if defined(HAVE_PCRE2_COMPILE_8) && defined(HAVE_PCRE2_H)
     if (pcre_pattern)
-	pcre_free(pcre_pattern);
+	pcre2_code_free(pcre_pattern);
     pcre_pattern = NULL;
 #endif
 
diff --git a/Src/Modules/pcre.mdd b/Src/Modules/pcre.mdd
index 6eb3c691b..3e1579117 100644
--- a/Src/Modules/pcre.mdd
+++ b/Src/Modules/pcre.mdd
@@ -1,5 +1,5 @@
 name=zsh/pcre
-link=`if test x$enable_pcre = xyes && (pcre-config --version >/dev/null 2>/dev/null); then echo dynamic; else echo no; fi`
+link=`if test x$enable_pcre = xyes; then echo dynamic; else echo no; fi`
 load=no
 
 autofeatures="b:pcre_compile b:pcre_study b:pcre_match"
diff --git a/Src/Modules/stat.c b/Src/Modules/stat.c
index 7c736072b..c9f851974 100644
--- a/Src/Modules/stat.c
+++ b/Src/Modules/stat.c
@@ -406,7 +406,7 @@ bin_stat(char *name, char **args, Options ops, UNUSED(int func))
 	} else {
 	    for (; *arg; arg++) {
 		if (strchr("glLnNorstT", *arg))
-		    ops->ind[STOUC(*arg)] = 1;
+		    ops->ind[(unsigned char) *arg] = 1;
 		else if (*arg == 'A') {
 		    if (arg[1]) {
 			arrnam = arg+1;
@@ -503,8 +503,10 @@ bin_stat(char *name, char **args, Options ops, UNUSED(int func))
     if (OPT_ISSET(ops,'f'))
 	nargs = 1;
     else
-	for (aptr = args; *aptr; aptr++)
+	for (aptr = args; *aptr; aptr++) {
+	    unmetafy(*aptr, NULL);
 	    nargs++;
+	}
 
     if (OPT_ISSET(ops,'g')) {
 	flags |= STF_GMT;
@@ -555,8 +557,8 @@ bin_stat(char *name, char **args, Options ops, UNUSED(int func))
     for (; OPT_ISSET(ops,'f') || *args; args++) {
 	char outbuf[PATH_MAX + 9]; /* "link   " + link name + NULL */
 	int rval = OPT_ISSET(ops,'f') ? fstat(fd, &statbuf) :
-	    OPT_ISSET(ops,'L') ? lstat(unmeta(*args), &statbuf) :
-	    stat(unmeta(*args), &statbuf);
+	    OPT_ISSET(ops,'L') ? lstat(*args, &statbuf) :
+	    stat(*args, &statbuf);
 	if (rval) {
 	    if (OPT_ISSET(ops,'f'))
 		sprintf(outbuf, "%d", fd);
@@ -571,10 +573,10 @@ bin_stat(char *name, char **args, Options ops, UNUSED(int func))
 
 	if (flags & STF_FILE) {
 	    if (arrnam)
-		*arrptr++ = ztrdup(*args);
+		*arrptr++ = ztrdup_metafy(*args);
 	    else if (hashnam) {
 	    	*hashptr++ = ztrdup(HNAMEKEY);
-		*hashptr++ = ztrdup(*args);
+		*hashptr++ = ztrdup_metafy(*args);
 	    } else
 		printf("%s%s", *args, (flags & STF_PICK) ? " " : ":\n");
 	}
diff --git a/Src/Modules/system.c b/Src/Modules/system.c
index ecd4e2546..929a8b002 100644
--- a/Src/Modules/system.c
+++ b/Src/Modules/system.c
@@ -74,6 +74,8 @@ bin_sysread(char *nam, char **args, Options ops, UNUSED(int func))
     int infd = 0, outfd = -1, bufsize = SYSREAD_BUFSIZE, count;
     char *outvar = NULL, *countvar = NULL, *inbuf;
 
+    errno = 0;	/* Distinguish non-system errors */
+
     /* -i: input file descriptor if not stdin */
     if (OPT_ISSET(ops, 'i')) {
 	infd = getposint(OPT_ARG(ops, 'i'), nam);
@@ -83,10 +85,6 @@ bin_sysread(char *nam, char **args, Options ops, UNUSED(int func))
 
     /* -o: output file descriptor, else store in REPLY */
     if (OPT_ISSET(ops, 'o')) {
-	if (*args) {
-	    zwarnnam(nam, "no argument allowed with -o");
-	    return 1;
-	}
 	outfd = getposint(OPT_ARG(ops, 'o'), nam);
 	if (outfd < 0)
 	    return 1;
@@ -242,6 +240,8 @@ bin_syswrite(char *nam, char **args, Options ops, UNUSED(int func))
     int outfd = 1, len, count, totcount;
     char *countvar = NULL;
 
+    errno = 0;	/* Distinguish non-system errors */
+
     /* -o: output file descriptor if not stdout */
     if (OPT_ISSET(ops, 'o')) {
 	outfd = getposint(OPT_ARG(ops, 'o'), nam);
@@ -280,7 +280,7 @@ bin_syswrite(char *nam, char **args, Options ops, UNUSED(int func))
 }
 
 
-static struct { char *name; int oflag; } openopts[] = {
+static struct { const char *name; int oflag; } openopts[] = {
 #ifdef O_CLOEXEC
     { "cloexec", O_CLOEXEC },
 #else
@@ -297,6 +297,9 @@ static struct { char *name; int oflag; } openopts[] = {
 #ifdef O_NOATIME
     { "noatime", O_NOATIME },
 #endif
+#ifdef O_NONBLOCK
+    { "nonblock", O_NONBLOCK},
+#endif
     { "excl", O_EXCL | O_CREAT },
     { "creat", O_CREAT },
     { "create", O_CREAT },
@@ -304,6 +307,13 @@ static struct { char *name; int oflag; } openopts[] = {
     { "trunc", O_TRUNC }
 };
 
+/*
+ * Return values of bin_sysopen:
+ *	0	Success
+ *	1	Error in parameters to command
+ *	2	Error on open, ERRNO set by system
+ */
+
 /**/
 static int
 bin_sysopen(char *nam, char **args, Options ops, UNUSED(int func))
@@ -320,6 +330,8 @@ bin_sysopen(char *nam, char **args, Options ops, UNUSED(int func))
     int fdflags = 0;
 #endif
 
+    errno = 0;	/* Distinguish non-system errors */
+
     if (!OPT_ISSET(ops, 'u')) {
 	zwarnnam(nam, "file descriptor not specified");
 	return 1;
@@ -375,12 +387,12 @@ bin_sysopen(char *nam, char **args, Options ops, UNUSED(int func))
 
     if (fd == -1) {
 	zwarnnam(nam, "can't open file %s: %e", *args, errno);
-	return 1;
+	return 2;
     }
     moved_fd = (explicit > -1) ? redup(fd, explicit) : movefd(fd);
     if (moved_fd == -1) {
 	zwarnnam(nam, "can't open file %s", *args);
-	return 1;
+	return 2;
     }
 
 #ifdef FD_CLOEXEC
@@ -424,6 +436,8 @@ bin_sysseek(char *nam, char **args, Options ops, UNUSED(int func))
     char *whence;
     off_t pos;
 
+    errno = 0;	/* Distinguish non-system errors */
+
     /* -u:  file descriptor if not stdin */
     if (OPT_ISSET(ops, 'u')) {
 	fd = getposint(OPT_ARG(ops, 'u'), nam);
diff --git a/Src/Modules/terminfo.c b/Src/Modules/terminfo.c
index 4596b41d2..f9ab64fb3 100644
--- a/Src/Modules/terminfo.c
+++ b/Src/Modules/terminfo.c
@@ -160,7 +160,7 @@ getterminfo(UNUSED(HashTable ht), const char *name)
 	pm->node.flags |= PM_SCALAR;
 	pm->gsu.s = &nullsetscalar_gsu;
     } else if ((tistr = (char *)tigetstr(nameu)) != NULL && tistr != (char *)-1) {
-	pm->u.str = dupstring(tistr);
+	pm->u.str = metafy(tistr, -1, META_HEAPDUP);
 	pm->node.flags |= PM_SCALAR;
 	pm->gsu.s = &nullsetscalar_gsu;
     } else {
@@ -280,7 +280,7 @@ scanterminfo(UNUSED(HashTable ht), ScanFunc func, int flags)
     for (capname = (char **)strnames; *capname; capname++) {
 	if ((tistr = (char *)tigetstr(*capname)) != NULL &&
 	    tistr != (char *)-1) {
-	    pm->u.str = dupstring(tistr);
+	    pm->u.str = metafy(tistr, -1, META_HEAPDUP);
 	    pm->node.nam = dupstring(*capname);
 	    func(&pm->node, flags);
 	}
diff --git a/Src/Modules/watch.c b/Src/Modules/watch.c
new file mode 100644
index 000000000..acc499518
--- /dev/null
+++ b/Src/Modules/watch.c
@@ -0,0 +1,778 @@
+/*
+ * watch.c - login/logout watching
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * 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
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "watch.mdh"
+
+/* Headers for utmp/utmpx structures */
+#ifdef HAVE_UTMP_H
+# include <utmp.h>
+#endif
+#ifdef HAVE_UTMPX_H
+# include <utmpx.h>
+#endif
+
+/* Find utmp file */
+#if !defined(REAL_UTMP_FILE) && defined(UTMP_FILE)
+# define REAL_UTMP_FILE UTMP_FILE
+#endif
+#if !defined(REAL_UTMP_FILE) && defined(_PATH_UTMP)
+# define REAL_UTMP_FILE _PATH_UTMP
+#endif
+#if !defined(REAL_UTMP_FILE) && defined(PATH_UTMP_FILE)
+# define REAL_UTMP_FILE PATH_UTMP_FILE
+#endif
+
+/* Find wtmp file */
+#if !defined(REAL_WTMP_FILE) && defined(WTMP_FILE)
+# define REAL_WTMP_FILE WTMP_FILE
+#endif
+#if !defined(REAL_WTMP_FILE) && defined(_PATH_WTMP)
+# define REAL_WTMP_FILE _PATH_WTMP
+#endif
+#if !defined(REAL_WTMP_FILE) && defined(PATH_WTMP_FILE)
+# define REAL_WTMP_FILE PATH_WTMP_FILE
+#endif
+
+/* Find utmpx file */
+#if !defined(REAL_UTMPX_FILE) && defined(UTMPX_FILE)
+# define REAL_UTMPX_FILE UTMPX_FILE
+#endif
+#if !defined(REAL_UTMPX_FILE) && defined(_PATH_UTMPX)
+# define REAL_UTMPX_FILE _PATH_UTMPX
+#endif
+#if !defined(REAL_UTMPX_FILE) && defined(PATH_UTMPX_FILE)
+# define REAL_UTMPX_FILE PATH_UTMPX_FILE
+#endif
+
+/* Find wtmpx file */
+#if !defined(REAL_WTMPX_FILE) && defined(WTMPX_FILE)
+# define REAL_WTMPX_FILE WTMPX_FILE
+#endif
+#if !defined(REAL_WTMPX_FILE) && defined(_PATH_WTMPX)
+# define REAL_WTMPX_FILE _PATH_WTMPX
+#endif
+#if !defined(REAL_WTMPX_FILE) && defined(PATH_WTMPX_FILE)
+# define REAL_WTMPX_FILE PATH_WTMPX_FILE
+#endif
+
+/* Decide which structure to use.  We use a structure that exists in *
+ * the headers, and require that its corresponding utmp file exist.  *
+ * (wtmp is less important.)                                         */
+
+#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.
+ * However, on some systems ut_name may already be defined this
+ * way for the purposes of utmp.
+ */
+# ifndef ut_name
+#  define ut_name ut_user
+# endif
+# ifdef HAVE_STRUCT_UTMPX_UT_XTIME
+#  undef ut_time
+#  define ut_time ut_xtime
+# else /* !HAVE_STRUCT_UTMPX_UT_XTIME */
+#  ifdef HAVE_STRUCT_UTMPX_UT_TV
+#   undef ut_time
+#   define ut_time ut_tv.tv_sec
+#  endif /* HAVE_STRUCT_UTMPX_UT_TV */
+# endif /* !HAVE_STRUCT_UTMPX_UT_XTIME */
+# define WATCH_UTMP_FILE REAL_UTMPX_FILE
+# ifdef REAL_WTMPX_FILE
+#  define WATCH_WTMP_FILE REAL_WTMPX_FILE
+# endif
+# ifdef HAVE_STRUCT_UTMPX_UT_HOST
+#  define WATCH_UTMP_UT_HOST 1
+# endif
+#endif
+
+#if !defined(WATCH_STRUCT_UTMP) && defined(HAVE_STRUCT_UTMP) && defined(REAL_UTMP_FILE)
+# define WATCH_STRUCT_UTMP struct utmp
+# define WATCH_UTMP_FILE REAL_UTMP_FILE
+# ifdef REAL_WTMP_FILE
+#  define WATCH_WTMP_FILE REAL_WTMP_FILE
+# endif
+# ifdef HAVE_STRUCT_UTMP_UT_HOST
+#  define WATCH_UTMP_UT_HOST 1
+# endif
+#endif
+
+#ifdef WATCH_UTMP_UT_HOST
+# define DEFAULT_WATCHFMT "%n has %a %l from %m."
+#else /* !WATCH_UTMP_UT_HOST */
+# define DEFAULT_WATCHFMT "%n has %a %l."
+#endif /* !WATCH_UTMP_UT_HOST */
+
+#ifdef WATCH_STRUCT_UTMP
+
+# include "watch.pro"
+
+# ifndef WATCH_WTMP_FILE
+#  define WATCH_WTMP_FILE "/dev/null"
+# endif
+
+static int wtabsz = 0;
+static WATCH_STRUCT_UTMP *wtab = NULL;
+
+/* the last time we checked the people in the WATCH variable */
+static time_t lastwatch;
+
+static time_t lastutmpcheck = 0;
+
+/* get the time of login/logout for WATCH */
+
+static time_t
+getlogtime(WATCH_STRUCT_UTMP *u, int inout)
+{
+    FILE *in;
+    WATCH_STRUCT_UTMP uu;
+    int first = 1;
+    int srchlimit = 50;		/* max number of wtmp records to search */
+
+    if (inout)
+	return u->ut_time;
+    if (!(in = fopen(WATCH_WTMP_FILE, "r")))
+	return time(NULL);
+    fseek(in, 0, SEEK_END);
+    do {
+	if (fseek(in, ((first) ? -1 : -2) * sizeof(WATCH_STRUCT_UTMP), SEEK_CUR)) {
+	    fclose(in);
+	    return time(NULL);
+	}
+	first = 0;
+	if (!fread(&uu, sizeof(WATCH_STRUCT_UTMP), 1, in)) {
+	    fclose(in);
+	    return time(NULL);
+	}
+	if (uu.ut_time < lastwatch || !srchlimit--) {
+	    fclose(in);
+	    return time(NULL);
+	}
+    }
+    while (memcmp(&uu, u, sizeof(uu)));
+
+    do
+	if (!fread(&uu, sizeof(WATCH_STRUCT_UTMP), 1, in)) {
+	    fclose(in);
+	    return time(NULL);
+	}
+    while (strncmp(uu.ut_line, u->ut_line, sizeof(u->ut_line)));
+    fclose(in);
+    return uu.ut_time;
+}
+
+/* Mutually recursive call to handle ternaries in $WATCHFMT */
+
+# define BEGIN3 '('
+# define END3 ')'
+
+static char *
+watch3ary(int inout, WATCH_STRUCT_UTMP *u, char *fmt, int prnt)
+{
+    int truth = 1, sep;
+
+    switch (*fmt++) {
+    case 'n':
+	truth = (u->ut_name[0] != 0);
+	break;
+    case 'a':
+	truth = inout;
+	break;
+    case 'l':
+	if (!strncmp(u->ut_line, "tty", 3))
+	    truth = (u->ut_line[3] != 0);
+	else
+	    truth = (u->ut_line[0] != 0);
+	break;
+# ifdef WATCH_UTMP_UT_HOST
+    case 'm':
+    case 'M':
+	truth = (u->ut_host[0] != 0);
+	break;
+# endif /* WATCH_UTMP_UT_HOST */
+    default:
+	prnt = 0;		/* Skip unknown conditionals entirely */
+	break;
+    }
+    sep = *fmt++;
+    fmt = watchlog2(inout, u, fmt, (truth && prnt), sep);
+    return watchlog2(inout, u, fmt, (!truth && prnt), END3);
+}
+
+/* print a login/logout event */
+
+/**/
+static char *
+watchlog2(int inout, WATCH_STRUCT_UTMP *u, char *fmt, int prnt, int fini)
+{
+    char buf[40], buf2[80];
+    time_t timet;
+    struct tm *tm;
+    char *fm2;
+    int len;
+    zattr atr;
+# ifdef WATCH_UTMP_UT_HOST
+    char *p;
+    int i;
+# endif /* WATCH_UTMP_UT_HOST */
+
+    while (*fmt)
+	if (*fmt == '\\') {
+	    if (*++fmt) {
+		if (prnt) {
+		    applytextattributes(TSC_RAW);
+		    putchar(*fmt);
+		}
+		++fmt;
+	    } else if (fini)
+		return fmt;
+	    else
+		break;
+	}
+	else if (*fmt == fini)
+	    return ++fmt;
+	else if (*fmt != '%') {
+	    if (prnt) {
+		applytextattributes(TSC_RAW);
+		putchar(*fmt);
+	    }
+	    ++fmt;
+	} else {
+	    if (*++fmt == BEGIN3)
+		fmt = watch3ary(inout, u, ++fmt, prnt);
+	    else if (!prnt)
+		++fmt;
+	    else
+		switch (*(fm2 = fmt++)) {
+		case 'n':
+		    applytextattributes(TSC_RAW);
+		    printf("%.*s", (int)sizeof(u->ut_name), u->ut_name);
+		    break;
+		case 'a':
+		    applytextattributes(TSC_RAW);
+		    printf("%s", (!inout) ? "logged off" : "logged on");
+		    break;
+		case 'l':
+		    applytextattributes(TSC_RAW);
+		    if (!strncmp(u->ut_line, "tty", 3))
+			printf("%.*s", (int)sizeof(u->ut_line) - 3, u->ut_line + 3);
+		    else
+			printf("%.*s", (int)sizeof(u->ut_line), u->ut_line);
+		    break;
+# ifdef WATCH_UTMP_UT_HOST
+		case 'm':
+		    applytextattributes(TSC_RAW);
+		    for (p = u->ut_host, i = sizeof(u->ut_host); i && *p; i--, p++) {
+			if (*p == '.' && !idigit(p[1]))
+			    break;
+			putchar(*p);
+		    }
+		    break;
+		case 'M':
+		    applytextattributes(TSC_RAW);
+		    printf("%.*s", (int)sizeof(u->ut_host), u->ut_host);
+		    break;
+# endif /* WATCH_UTMP_UT_HOST */
+		case 'T':
+		case 't':
+		case '@':
+		case 'W':
+		case 'w':
+		case 'D':
+		    switch (*fm2) {
+		    case '@':
+		    case 't':
+			fm2 = "%l:%M%p";
+			break;
+		    case 'T':
+			fm2 = "%K:%M";
+			break;
+		    case 'w':
+			fm2 = "%a %f";
+			break;
+		    case 'W':
+			fm2 = "%m/%d/%y";
+			break;
+		    case 'D':
+			if (fm2[1] == '{') {
+			    char *dd, *ss;
+			    int n = 79;
+
+			    for (ss = fm2 + 2, dd = buf2;
+				 n-- && *ss && *ss != '}'; ++ss, ++dd)
+				*dd = *((*ss == '\\' && ss[1]) ? ++ss : ss);
+			    if (*ss == '}') {
+				*dd = '\0';
+				fmt = ss + 1;
+				fm2 = buf2;
+			    }
+			    else fm2 = "%y-%m-%d";
+			}
+			else fm2 = "%y-%m-%d";
+			break;
+		    }
+		    timet = getlogtime(u, inout);
+		    tm = localtime(&timet);
+		    len = ztrftime(buf, 40, fm2, tm, 0L);
+		    if (len > 0)
+			metafy(buf, len, META_NOALLOC);
+		    applytextattributes(TSC_RAW);
+		    printf("%s", (*buf == ' ') ? buf + 1 : buf);
+		    break;
+		case '%':
+		    applytextattributes(TSC_RAW);
+		    putchar('%');
+		    break;
+		case 'F':
+		    if (*fmt == '{') {
+			fmt++;
+			atr = match_colour((const char**)&fmt, 1, 0);
+			if (*fmt == '}')
+			    fmt++;
+			if (atr && atr != TXT_ERROR) {
+			    tsetattrs(atr);
+			    break;
+			}
+		    } /* fall-through */
+		case 'f':
+		    tunsetattrs(TXTFGCOLOUR);
+		    break;
+		case 'H':
+		    if (*fmt == '{') {
+			fmt = parsehighlight(fmt + 1, '}', &atr);
+			if (atr && atr != TXT_ERROR)
+			    treplaceattrs(atr);
+		    }
+		    break;
+		case 'K':
+		    if (*fmt == '{') {
+			fmt++;
+			atr = match_colour((const char**)&fmt, 0, 0);
+			if (*fmt == '}')
+			    fmt++;
+			if (atr && atr != TXT_ERROR) {
+			    tsetattrs(atr);
+			    break;
+			}
+		    } /* fall-through */
+		case 'k':
+		    tunsetattrs(TXTBGCOLOUR);
+		    break;
+		case 'S':
+		    tsetattrs(TXTSTANDOUT);
+		    break;
+		case 's':
+		    tunsetattrs(TXTSTANDOUT);
+		    break;
+		case 'B':
+		    tsetattrs(TXTBOLDFACE);
+		    break;
+		case 'b':
+		    tunsetattrs(TXTBOLDFACE);
+		    break;
+		case 'U':
+		    tsetattrs(TXTUNDERLINE);
+		    break;
+		case 'u':
+		    tunsetattrs(TXTUNDERLINE);
+		    break;
+		default:
+		    applytextattributes(TSC_RAW);
+		    putchar('%');
+		    putchar(*fm2);
+		    break;
+		}
+	}
+    if (prnt) {
+	applytextattributes(TSC_RAW);
+	putchar('\n');
+    }
+
+    return fmt;
+}
+
+/* See if the watch entry matches */
+
+static int
+watchlog_match(char *teststr, char *actual, size_t buflen)
+{
+    int ret = 0;
+    Patprog pprog;
+    char *str = dupstring(teststr);
+    size_t len = strnlen(actual, buflen);
+    char *user = metafy(actual, len,
+	    len == buflen ? META_HEAPDUP : META_USEHEAP);
+
+    tokenize(str);
+
+    if ((pprog = patcompile(str, PAT_STATIC, 0))) {
+	queue_signals();
+	if (pattry(pprog, user))
+	    ret = 1;
+	unqueue_signals();
+    } else if (!strcmp(user, teststr))
+	ret = 1;
+    return ret;
+}
+
+/* check the List for login/logouts */
+
+static void
+watchlog(int inout, WATCH_STRUCT_UTMP *u, char **w, char *fmt)
+{
+    char *v, *vv, sav;
+    int bad;
+
+    if (!*u->ut_name)
+	return;
+
+    if (*w && !strcmp(*w, "all")) {
+	(void)watchlog2(inout, u, fmt, 1, 0);
+	return;
+    }
+    if (*w && !strcmp(*w, "notme")) {
+	int len = strnlen(u->ut_name, sizeof(u->ut_name));
+	char *username = metafy(u->ut_name, len,
+				(len == sizeof(u->ut_name) ?
+				 META_HEAPDUP /* allow for nul terminator */ :
+				 META_USEHEAP));
+	if (strcmp(username, get_username())) {
+	    (void)watchlog2(inout, u, fmt, 1, 0);
+	    return;
+	}
+	w++;
+    }
+    for (; *w; w++) {
+	bad = 0;
+	v = *w;
+	if (*v != '@' && *v != '%') {
+	    for (vv = v; *vv && *vv != '@' && *vv != '%'; vv++);
+	    sav = *vv;
+	    *vv = '\0';
+	    if (!watchlog_match(v, u->ut_name, sizeof(u->ut_name)))
+		bad = 1;
+	    *vv = sav;
+	    v = vv;
+	}
+	for (;;)
+	    if (*v == '%') {
+		for (vv = ++v; *vv && *vv != '@'; vv++);
+		sav = *vv;
+		*vv = '\0';
+		if (!watchlog_match(v, u->ut_line, sizeof(u->ut_line)))
+		    bad = 1;
+		*vv = sav;
+		v = vv;
+	    }
+# ifdef WATCH_UTMP_UT_HOST
+	    else if (*v == '@') {
+		for (vv = ++v; *vv && *vv != '%'; vv++);
+		sav = *vv;
+		*vv = '\0';
+		if (!watchlog_match(v, u->ut_host, sizeof(u->ut_host)))
+		    bad = 1;
+		*vv = sav;
+		v = vv;
+	    }
+# endif /* WATCH_UTMP_UT_HOST */
+	    else
+		break;
+	if (!bad) {
+	    (void)watchlog2(inout, u, fmt, 1, 0);
+	    return;
+	}
+    }
+}
+
+/* compare 2 utmp entries */
+
+static int
+ucmp(WATCH_STRUCT_UTMP *u, WATCH_STRUCT_UTMP *v)
+{
+    if (u->ut_time == v->ut_time)
+	return strncmp(u->ut_line, v->ut_line, sizeof(u->ut_line));
+    return u->ut_time - v->ut_time;
+}
+
+/* initialize the user List */
+
+static int
+readwtab(WATCH_STRUCT_UTMP **head, int initial_sz)
+{
+    WATCH_STRUCT_UTMP *uptr;
+    int wtabmax = initial_sz < 2 ? 32 : initial_sz;
+    int sz = 0;
+# ifdef HAVE_GETUTENT
+    WATCH_STRUCT_UTMP *tmp;
+# else
+    FILE *in;
+# endif
+
+    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 0;
+    while (fread(uptr, sizeof(WATCH_STRUCT_UTMP), 1, in)) {
+# endif
+# ifdef USER_PROCESS
+	if (uptr->ut_type == USER_PROCESS)
+# else /* !USER_PROCESS */
+	if (uptr->ut_name[0])
+# endif /* !USER_PROCESS */
+	{
+	    uptr++;
+	    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 (sz)
+	qsort((void *) *head, sz, sizeof(WATCH_STRUCT_UTMP),
+	           (int (*) (const void *, const void *))ucmp);
+    return sz;
+}
+
+/* Check for login/logout events; executed before *
+ * each prompt if WATCH is set                    */
+
+/**/
+void
+dowatch(void)
+{
+    WATCH_STRUCT_UTMP *utab, *uptr, *wptr;
+    struct stat st;
+    char **s;
+    char *fmt;
+    int utabsz, uct, wct;
+
+    s = watch;
+
+    holdintr();
+    if (!wtab)
+	wtabsz = readwtab(&wtab, 32);
+    if ((stat(WATCH_UTMP_FILE, &st) == -1) || (st.st_mtime <= lastutmpcheck)) {
+	noholdintr();
+	return;
+    }
+    lastutmpcheck = st.st_mtime;
+    utabsz = readwtab(&utab, wtabsz + 4);
+    noholdintr();
+    if (errflag) {
+	free(utab);
+	return;
+    }
+
+    wct = wtabsz;
+    uct = utabsz;
+    uptr = utab;
+    wptr = wtab;
+    if (errflag) {
+	free(utab);
+	return;
+    }
+    queue_signals();
+    if (!(fmt = getsparam_u("WATCHFMT")))
+	fmt = DEFAULT_WATCHFMT;
+    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;
+    wtabsz = utabsz;
+    fflush(stdout);
+    lastwatch = time(NULL);
+}
+
+static void
+checksched(void)
+{
+    /* Do nothing if WATCH is not set, or LOGCHECK has not elapsed */
+    if (watch && (int) difftime(time(NULL), lastwatch) > getiparam("LOGCHECK"))
+	dowatch();
+}
+
+/**/
+static int
+bin_log(UNUSED(char *nam), UNUSED(char **argv), UNUSED(Options ops), UNUSED(int func))
+{
+    if (!watch)
+	return 1;
+    if (wtab)
+	free(wtab);
+    wtab = (WATCH_STRUCT_UTMP *)zalloc(1);
+    wtabsz = 0;
+    lastutmpcheck = 0;
+    dowatch();
+    return 0;
+}
+
+#else /* !WATCH_STRUCT_UTMP */
+
+static void
+checksched(void)
+{
+}
+
+/**/
+static int
+bin_log(char *nam, char **argv, Options ops, int func)
+{
+    return bin_notavail(nam, argv, ops, func);
+}
+
+#endif /* !WATCH_STRUCT_UTMP */
+
+/**/
+static char **watch; /* $watch */
+
+/* module setup */
+
+static struct builtin bintab[] = {
+    BUILTIN("log", 0, bin_log, 0, 0, 0, NULL, NULL),
+};
+
+static struct paramdef partab[] = {
+    PARAMDEF("WATCH", PM_SCALAR|PM_SPECIAL, &watch, NULL),
+    PARAMDEF("watch", PM_ARRAY|PM_SPECIAL, &watch, NULL),
+};
+
+static struct features module_features = {
+    bintab, sizeof(bintab)/sizeof(*bintab),
+    NULL, 0,
+    NULL, 0,
+    partab, sizeof(partab)/sizeof(*partab),
+    0
+};
+
+/**/
+int
+setup_(UNUSED(Module m))
+{
+    /* On Cygwin, colonarr_gsu exists in libzsh.dll and we can't
+     * use &colonarr_gsu in the initialization of partab[] above */
+    partab[0].gsu = (void *)&colonarr_gsu;
+    partab[1].gsu = (void *)&vararray_gsu;
+    return 0;
+}
+
+/**/
+int
+features_(Module m, char ***features)
+{
+    *features = featuresarray(m, &module_features);
+    return 0;
+}
+
+/**/
+int
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m, &module_features, enables);
+}
+
+/**/
+int
+boot_(UNUSED(Module m))
+{
+    static char const * const default_watchfmt = DEFAULT_WATCHFMT;
+
+    Param pma = (Param) paramtab->getnode(paramtab, "watch");
+    Param pms = (Param) paramtab->getnode(paramtab, "WATCH");
+    if (pma && pms && pma->u.arr == watch && pms->u.arr == watch) {
+	/* only tie the two parameters if both were added */
+	pma->ename = "WATCH";
+	pms->ename = "watch";
+	pma->node.flags |= PM_TIED;
+	pms->node.flags |= PM_TIED;
+    }
+    watch = mkarray(NULL);
+
+    /* These two parameters are only set to defaults if not set.
+     * So setting them in .zshrc will not be enough to load the
+     * module. It's useless until the watch array is set anyway. */
+    if (!paramtab->getnode(paramtab, "WATCHFMT"))
+	setsparam("WATCHFMT", ztrdup_metafy(default_watchfmt));
+    if (!paramtab->getnode(paramtab, "LOGCHECK"))
+	setiparam("LOGCHECK", 60);
+
+    addprepromptfn(&checksched);
+
+    return 0;
+}
+
+/**/
+int
+cleanup_(Module m)
+{
+    delprepromptfn(&checksched);
+    return setfeatureenables(m, &module_features, NULL);
+}
+
+/**/
+int
+finish_(UNUSED(Module m))
+{
+    return 0;
+}
diff --git a/Src/Modules/watch.mdd b/Src/Modules/watch.mdd
new file mode 100644
index 000000000..7e8454ede
--- /dev/null
+++ b/Src/Modules/watch.mdd
@@ -0,0 +1,7 @@
+name=zsh/watch
+link=dynamic
+load=yes
+
+autofeatures="b:log p:WATCH p:watch"
+
+objects="watch.o"
diff --git a/Src/Modules/zftp.c b/Src/Modules/zftp.c
index e8e239e76..b60e5bf31 100644
--- a/Src/Modules/zftp.c
+++ b/Src/Modules/zftp.c
@@ -127,7 +127,7 @@ typedef int (*readwrite_t)(int, char *, off_t, int);
 
 struct zftpcmd {
     const char *nam;
-    int (*fun) _((char *, char **, int));
+    int (*fun) (char *, char **, int);
     int min, max, flags;
 };
 
@@ -944,9 +944,9 @@ zfopendata(char *name, union tcp_sockaddr *zdsockp, int *is_passivep)
 		return 1;
 	    }
 	    for (i = 0; i < 4; i++)
-		iaddr[i] = STOUC(nums[i]);
-	    iport[0] = STOUC(nums[4]);
-	    iport[1] = STOUC(nums[5]);
+		iaddr[i] = (unsigned char) nums[i];
+	    iport[0] = (unsigned char) nums[4];
+	    iport[1] = (unsigned char) nums[5];
 
 	    memcpy(&zdsockp->in.sin_addr, iaddr, sizeof(iaddr));
 	    memcpy(&zdsockp->in.sin_port, iport, sizeof(iport));
@@ -2438,7 +2438,7 @@ zftp_type(char *name, char **args, int flags)
 	fflush(stdout);
 	return 0;
     } else {
-	nt = toupper(STOUC(*str));
+	nt = toupper((unsigned char) *str);
 	/*
 	 * RFC959 specifies other types, but these are the only
 	 * ones we know what to do with.
@@ -2472,7 +2472,7 @@ zftp_mode(char *name, char **args, UNUSED(int flags))
 	fflush(stdout);
 	return 0;
     }
-    nt = str[0] = toupper(STOUC(*str));
+    nt = str[0] = toupper((unsigned char) *str);
     if (str[1] || (nt != 'S' && nt != 'B')) {
 	zwarnnam(name, "transfer mode %s not recognised", str);
 	return 1;
@@ -3075,7 +3075,7 @@ bin_zftp(char *name, char **args, UNUSED(Options ops), UNUSED(int func))
     if ((prefs = getsparam_u("ZFTP_PREFS"))) {
 	zfprefs = 0;
 	for (ptr = prefs; *ptr; ptr++) {
-	    switch (toupper(STOUC(*ptr))) {
+	    switch (toupper((unsigned char) *ptr)) {
 	    case 'S':
 		/* sendport */
 		zfprefs |= ZFPF_SNDP;
@@ -3147,6 +3147,7 @@ zftp_cleanup(void)
     lastmsg = NULL;
     zfunsetparam("ZFTP_SESSION");
     freelinklist(zfsessions, (FreeFunc) freesession);
+    zfsessions = NULL;
     zfree(zfstatusp, sizeof(int)*zfsesscnt);
     zfstatusp = NULL;
 }
@@ -3172,7 +3173,7 @@ static struct features module_features = {
 int
 setup_(UNUSED(Module m))
 {
-    return (require_module("zsh/net/tcp", NULL, 0) == 1);
+    return 0;
 }
 
 /**/
diff --git a/Src/Modules/zprof.c b/Src/Modules/zprof.c
index 56cdab888..171a15b90 100644
--- a/Src/Modules/zprof.c
+++ b/Src/Modules/zprof.c
@@ -163,9 +163,9 @@ bin_zprof(UNUSED(char *nam), UNUSED(char **args), Options ops, UNUSED(int func))
 	*ap = NULL;
 
 	qsort(fs, ncalls, sizeof(f),
-	      (int (*) _((const void *, const void *))) cmpsfuncs);
+	      (int (*) (const void *, const void *)) cmpsfuncs);
 	qsort(as, narcs, sizeof(a),
-	      (int (*) _((const void *, const void *))) cmpparcs);
+	      (int (*) (const void *, const void *)) cmpparcs);
 
 	printf("num  calls                time                       self            name\n-----------------------------------------------------------------------------------\n");
 	for (fp = fs, i = 1; *fp; fp++, i++) {
@@ -179,7 +179,7 @@ bin_zprof(UNUSED(char *nam), UNUSED(char **args), Options ops, UNUSED(int func))
 		   (*fp)->name);
 	}
 	qsort(fs, ncalls, sizeof(f),
-	      (int (*) _((const void *, const void *))) cmptfuncs);
+	      (int (*) (const void *, const void *)) cmptfuncs);
 
 	for (fp = fs; *fp; fp++) {
 	    printf("\n-----------------------------------------------------------------------------------\n\n");
diff --git a/Src/Modules/zpty.c b/Src/Modules/zpty.c
index dfd2a2a7a..c2656698c 100644
--- a/Src/Modules/zpty.c
+++ b/Src/Modules/zpty.c
@@ -638,7 +638,7 @@ ptyread(char *nam, Ptycmd cmd, char **args, int noblock, int mustmatch)
 		readchar = cmd->read;
 		cmd->read = -1;
 	    } else
-		readchar = STOUC(buf[used]);
+		readchar = (unsigned char) buf[used];
 	    if (imeta(readchar)) {
 		buf[used++] = Meta;
 		buf[used++] = (char) (readchar ^ 32);
diff --git a/Src/Modules/zutil.c b/Src/Modules/zutil.c
index 691ba6c2f..5eccea7a9 100644
--- a/Src/Modules/zutil.c
+++ b/Src/Modules/zutil.c
@@ -462,6 +462,28 @@ lookupstyle(char *ctxt, char *style)
 }
 
 static int
+testforstyle(char *ctxt, char *style)
+{
+    Style s;
+    Stypat p;
+    int found = 0;
+
+    s = (Style)zstyletab->getnode2(zstyletab, style);
+    if (s) {
+	MatchData match;
+	savematch(&match);
+	for (p = s->pats; p; p = p->next)
+	    if (pattry(p->prog, ctxt)) {
+		found = 1;
+		break;
+	    }
+	restorematch(&match);
+    }
+
+    return !found;	/* 0 == success */
+}
+
+static int
 bin_zstyle(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 {
     int min, max, n, add = 0, list = ZSLIST_NONE, eval = 0;
@@ -570,6 +592,7 @@ bin_zstyle(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
     case 't': min = 2; max = -1; break;
     case 'T': min = 2; max = -1; break;
     case 'm': min = 3; max =  3; break;
+    case 'q': min = 2; max =  2; break;
     case 'g': min = 1; max =  3; break;
     default:
 	zwarnnam(nam, "invalid option: %s", args[0]);
@@ -723,6 +746,15 @@ bin_zstyle(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 	    return 1;
 	}
 	break;
+    case 'q':
+	{
+	    int success;
+	    queue_signals();	/* Protect PAT_STATIC */
+	    success = testforstyle(args[1], args[2]);
+	    unqueue_signals();
+	    return success;
+	}
+	break;
     case 'g':
 	{
 	    int ret = 1;
@@ -776,10 +808,12 @@ bin_zstyle(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
  *   ousedp	(*outp)[*ousedp] is where to write next
  *   olenp	*olenp is the size allocated for *outp
  *   endchar    Terminator character in addition to `\0' (may be '\0')
+ *   presence   -F: Ternary expressions test emptyness instead
  *   skip	If 1, don't output, just parse.
  */
 static char *zformat_substring(char* instr, char **specs, char **outp,
-			       int *ousedp, int *olenp, int endchar, int skip)
+			       int *ousedp, int *olenp, int endchar,
+			       int presence, int skip)
 {
     char *s;
 
@@ -793,11 +827,11 @@ static char *zformat_substring(char* instr, char **specs, char **outp,
 
 	    if (idigit(*s)) {
 		for (min = 0; idigit(*s); s++)
-		    min = (min * 10) + (int) STOUC(*s) - '0';
+		    min = (min * 10) + (int) (unsigned char) *s - '0';
 	    }
 
 	    /* Ternary expressions */
-	    testit = (STOUC(*s) == '(');
+	    testit = ((unsigned char) *s == '(');
 	    if (testit && s[1] == '-')
 	    {
 		/* Allow %(-1... etc. */
@@ -806,27 +840,36 @@ static char *zformat_substring(char* instr, char **specs, char **outp,
 	    }
 	    if ((*s == '.' || testit) && idigit(s[1])) {
 		for (max = 0, s++; idigit(*s); s++)
-		    max = (max * 10) + (int) STOUC(*s) - '0';
+		    max = (max * 10) + (int) (unsigned char) *s - '0';
 	    } else if (*s == '.' || testit)
 		s++;
 
-	    if (testit && STOUC(*s)) {
+	    if (testit && (unsigned char) *s) {
 		int actval, testval, endcharl;
 
-		/*
-		 * One one number is useful for ternary expressions.
-		 * Remember to put the sign back.
-		 */
+		/* Only one number is useful for ternary expressions. */
 		testval = (min >= 0) ? min : (max >= 0) ? max : 0;
-		if (right)
-		    testval *= -1;
 
-		if (specs[STOUC(*s)])
-		    actval = (int)mathevali(specs[STOUC(*s)]);
-		else
-		    actval = 0;
-		/* zero means values are equal, i.e. true */
-		actval -= testval;
+		if (specs[(unsigned char) *s] && *specs[(unsigned char) *s]) {
+		    if (presence) {
+			if (testval)
+#ifdef MULTIBYTE_SUPPORT
+			    if (isset(MULTIBYTE))
+				actval = MB_METASTRWIDTH(specs[(unsigned char) *s]);
+			    else
+#endif
+				actval = strlen(specs[(unsigned char) *s]);
+		        else
+			    actval = 1;
+			actval = right ? (testval < actval) : (testval >= actval);
+		    } else {
+			if (right) /* put the sign back */
+			    testval *= -1;
+			/* zero means values are equal, i.e. true */
+			actval = (int) mathevali(specs[(unsigned char) *s]) - testval;
+		    }
+		} else
+		    actval = presence ? !right : testval;
 
 		/* careful about premature end of string */
 		if (!(endcharl = *++s))
@@ -837,14 +880,14 @@ static char *zformat_substring(char* instr, char **specs, char **outp,
 		 * vice versa... unless we are already skipping.
 		 */
 		if (!(s = zformat_substring(s+1, specs, outp, ousedp,
-					    olenp, endcharl, skip || actval)))
+			    olenp, endcharl, presence, skip || actval)))
 		    return NULL;
 		if (!(s = zformat_substring(s+1, specs, outp, ousedp,
-					    olenp, ')', skip || !actval)))
+			    olenp, ')', presence, skip || !actval)))
 		    return NULL;
 	    } else if (skip) {
 		continue;
-	    } else if ((spec = specs[STOUC(*s)])) {
+	    } else if ((spec = specs[(unsigned char) *s])) {
 		int len;
 
 		if ((len = strlen(spec)) > max && max >= 0)
@@ -912,6 +955,7 @@ static int
 bin_zformat(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 {
     char opt;
+    int presence = 0;
 
     if (args[0][0] != '-' || !(opt = args[0][1]) || args[0][2]) {
 	zwarnnam(nam, "invalid argument: %s", args[0]);
@@ -920,6 +964,9 @@ bin_zformat(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
     args++;
 
     switch (opt) {
+    case 'F':
+	presence = 1;
+	/* fall-through */
     case 'f':
 	{
 	    char **ap, *specs[256] = {0}, *out;
@@ -935,11 +982,12 @@ bin_zformat(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 		    zwarnnam(nam, "invalid argument: %s", *ap);
 		    return 1;
 		}
-		specs[STOUC(ap[0][0])] = ap[0] + 2;
+		specs[(unsigned char) ap[0][0]] = ap[0] + 2;
 	    }
 	    out = (char *) zhalloc(olen = 128);
 
-	    zformat_substring(args[1], specs, &out, &oused, &olen, '\0', 0);
+	    zformat_substring(args[1], specs, &out, &oused, &olen, '\0',
+		    presence, 0);
 	    out[oused] = '\0';
 
 	    setsparam(args[0], ztrdup(out));
@@ -949,7 +997,7 @@ bin_zformat(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
     case 'a':
 	{
 	    char **ap, *cp;
-	    int nbc = 0, colon = 0, pre = 0, suf = 0;
+	    int nbc = 0, pre = 0, suf = 0;
 #ifdef MULTIBYTE_SUPPORT
 	    int prechars = 0;
 #endif /* MULTIBYTE_SUPPORT */
@@ -964,7 +1012,6 @@ bin_zformat(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 		    int dchars = 0;
 #endif /* MULTIBYTE_SUPPORT */
 
-		    colon++;
 		    if ((d = cp - *ap - nbc) > pre)
 			pre = d;
 #ifdef MULTIBYTE_SUPPORT
@@ -1362,11 +1409,11 @@ rmatch(RParseResult *sm, char *subj, char *var1, char *var2, int comp)
 					     "zregexparse-guard"), !lastval))) {
 		LinkNode aln;
 		char **mend;
-		int len;
+		int len = 0;
 
 		queue_signals();
-		mend = getaparam("mend");
-		len = atoi(mend[0]);
+		if ((mend = getaparam("mend")))
+		    len = atoi(mend[0]);
 		unqueue_signals();
 
 		for (i = len; i; i--)
@@ -1848,7 +1895,7 @@ bin_zparseopts(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 	d->vals = d->last = NULL;
 	opt_descs = d;
 	if (!o[1])
-	    sopts[STOUC(*o)] = d;
+	    sopts[(unsigned char) *o] = d;
 	if ((flags & ZOF_MAP) && !map_opt_desc(d)) {
 	    zwarnnam(nam, "cyclic option mapping: %s", args[-1]);
 	    return 1;
@@ -1872,7 +1919,7 @@ bin_zparseopts(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 	}
 	if (!(d = lookup_opt(o + 1))) {
 	    while (*++o) {
-		if (!(d = sopts[STOUC(*o)])) {
+		if (!(d = sopts[(unsigned char) *o])) {
 		    if (fail) {
 			if (*o != '-')
 			    zwarnnam(nam, "bad option: -%c", *o);