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.c4
-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.c149
-rw-r--r--Src/Modules/parameter.c28
-rw-r--r--Src/Modules/pcre.c319
-rw-r--r--Src/Modules/pcre.mdd2
-rw-r--r--Src/Modules/terminfo.c4
-rw-r--r--Src/Modules/watch.c35
-rw-r--r--Src/Modules/zftp.c5
-rw-r--r--Src/Modules/zprof.c6
-rw-r--r--Src/Modules/zutil.c41
16 files changed, 905 insertions, 222 deletions
diff --git a/Src/Modules/curses.c b/Src/Modules/curses.c
index ad17ed65f..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;
 	    }
 	}
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 065fa63d2..044617190 100644
--- a/Src/Modules/param_private.c
+++ b/Src/Modules/param_private.c
@@ -87,9 +87,52 @@ 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 = zalloc(sizeof(struct gsu_closure));
@@ -122,6 +165,7 @@ 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 */
@@ -229,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.
  *
  */
 
@@ -252,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);
@@ -267,9 +313,10 @@ 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
+    } else
 	zfree(c, sizeof(struct gsu_closure));
 }
 
@@ -291,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);
@@ -306,9 +353,10 @@ 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
+    } else
 	zfree(c, sizeof(struct gsu_closure));
 }
 
@@ -330,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);
@@ -345,9 +393,10 @@ 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
+    } else
 	zfree(c, sizeof(struct gsu_closure));
 }
 
@@ -370,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);
@@ -385,9 +434,10 @@ 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
+    } else
 	zfree(c, sizeof(struct gsu_closure));
 }
 
@@ -411,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);
@@ -426,9 +476,10 @@ 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
+    } else
 	zfree(c, sizeof(struct gsu_closure));
 }
 
@@ -489,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;
@@ -512,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;
 }
 
@@ -571,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:%lmprtux", "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 dbb61e474..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));
@@ -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));
diff --git a/Src/Modules/pcre.c b/Src/Modules/pcre.c
index 46875a59b..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
@@ -54,8 +54,8 @@ zpcre_utf8_enabled(void)
 	return 0;
 
     if ((have_utf8_pcre == -1) &&
-	(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 */
     }
 
     return (have_utf8_pcre == 1) && (!strcmp(nl_langinfo(CODESET), "UTF-8"));
@@ -69,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;
     }
     
@@ -117,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));
 	}
 	/*
@@ -186,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);
 	}
 	/*
@@ -201,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
@@ -247,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));
@@ -287,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;
@@ -314,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;
@@ -344,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;
@@ -383,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);
@@ -401,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;
@@ -415,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;
 		}
@@ -444,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;
 		}
@@ -457,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;
 }
@@ -482,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
@@ -540,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/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
index 0de8cbf9a..acc499518 100644
--- a/Src/Modules/watch.c
+++ b/Src/Modules/watch.c
@@ -373,6 +373,13 @@ watchlog2(int inout, WATCH_STRUCT_UTMP *u, char *fmt, int prnt, int fini)
 		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++;
@@ -423,20 +430,23 @@ watchlog2(int inout, WATCH_STRUCT_UTMP *u, char *fmt, int prnt, int fini)
 /* See if the watch entry matches */
 
 static int
-watchlog_match(char *teststr, char *actual, int len)
+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, actual))
+	if (pattry(pprog, user))
 	    ret = 1;
 	unqueue_signals();
-    } else if (!strncmp(actual, teststr, len))
+    } else if (!strcmp(user, teststr))
 	ret = 1;
     return ret;
 }
@@ -456,10 +466,17 @@ watchlog(int inout, WATCH_STRUCT_UTMP *u, char **w, char *fmt)
 	(void)watchlog2(inout, u, fmt, 1, 0);
 	return;
     }
-    if (*w && !strcmp(*w, "notme") &&
-	strncmp(u->ut_name, get_username(), sizeof(u->ut_name))) {
-	(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;
@@ -488,7 +505,7 @@ watchlog(int inout, WATCH_STRUCT_UTMP *u, char **w, char *fmt)
 		for (vv = ++v; *vv && *vv != '%'; vv++);
 		sav = *vv;
 		*vv = '\0';
-		if (!watchlog_match(v, u->ut_host, strlen(v)))
+		if (!watchlog_match(v, u->ut_host, sizeof(u->ut_host)))
 		    bad = 1;
 		*vv = sav;
 		v = vv;
@@ -567,7 +584,7 @@ readwtab(WATCH_STRUCT_UTMP **head, int initial_sz)
 
     if (sz)
 	qsort((void *) *head, sz, sizeof(WATCH_STRUCT_UTMP),
-	           (int (*) _((const void *, const void *)))ucmp);
+	           (int (*) (const void *, const void *))ucmp);
     return sz;
 }
 
diff --git a/Src/Modules/zftp.c b/Src/Modules/zftp.c
index 49b3ffa89..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;
 };
 
@@ -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/zutil.c b/Src/Modules/zutil.c
index 8a7d0a4c5..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;
@@ -965,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 */
@@ -980,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
@@ -1378,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--)