Snap for 7550844 from 2b879bdf812f2dd0ba95be7234ea064a61308ebc to mainline-os-statsd-release

Change-Id: Ib69d30be15f44a489f1f8e04eef47496b89ce36a
diff --git a/Android.bp b/Android.bp
index 71a0d67..dcb534e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -2,6 +2,24 @@
 //    Thorsten Glaser <t.glaser@tarent.de>
 // This file is provided under the same terms as mksh.
 
+package {
+    default_applicable_licenses: ["external_mksh_license"],
+}
+
+license {
+    name: "external_mksh_license",
+    visibility: [":__subpackages__"],
+    license_kinds: [
+        "SPDX-license-identifier-BSD",
+        "SPDX-license-identifier-MIT",
+        "SPDX-license-identifier-Unicode-DFS",
+        "legacy_notice",
+    ],
+    license_text: [
+        "NOTICE",
+    ],
+}
+
 cc_defaults {
     name: "sh-defaults",
 
@@ -107,7 +125,7 @@
         "-DHAVE_SYS_ERRLIST_DECL=0",
         "-DHAVE_SYS_SIGLIST_DECL=1",
         "-DHAVE_PERSISTENT_HISTORY=0",
-        "-DMKSH_BUILD_R=571",
+        "-DMKSH_BUILD_R=592",
 
         // Additional flags
         "-DMKSH_DEFAULT_PROFILEDIR=\"/system/etc\"",
@@ -124,6 +142,7 @@
     name: "sh",
     defaults: ["sh-defaults"],
     recovery_available: true,
+    vendor_ramdisk_available: true,
 }
 
 cc_binary {
diff --git a/Android.patch.txt b/Android.patch.txt
index 15118e3..eefcd5c 100644
--- a/Android.patch.txt
+++ b/Android.patch.txt
@@ -1,7 +1,8 @@
-diff -ru mksh-R57/funcs.c src/funcs.c
---- mksh-R57/funcs.c	2018-10-20 14:04:55.000000000 -0700
-+++ src/funcs.c	2019-03-26 12:05:23.976821773 -0700
-@@ -103,7 +103,9 @@
+Only in src: FAQ.htm
+diff -u mksh-R59b/funcs.c src/funcs.c
+--- mksh-R59b/funcs.c	2020-05-16 15:38:48.000000000 -0700
++++ src/funcs.c	2020-05-20 17:14:19.588510856 -0700
+@@ -104,7 +104,9 @@
  	{Tsgbreak, c_brkcont},
  	{T__builtin, c_builtin},
  	{Tbuiltin, c_builtin},
@@ -11,7 +12,7 @@
  	{Tcd, c_cd},
  	/* dash compatibility hack */
  	{"chdir", c_cd},
-@@ -126,7 +128,9 @@
+@@ -125,7 +127,9 @@
  	{"pwd", c_pwd},
  	{Tread, c_read},
  	{Tdsgreadonly, c_typeset},
@@ -20,8 +21,8 @@
 +#endif
  	{"~rename", c_rename},
  	{"*=return", c_exitreturn},
- 	{Tsgset, c_set},
-@@ -160,8 +164,10 @@
+ 	{Tsghset, c_set},
+@@ -159,8 +163,10 @@
  	{"~printf", c_printf},
  #endif
  #if HAVE_SELECT
@@ -32,10 +33,10 @@
  #ifdef __MirBSD__
  	/* alias to "true" for historical reasons */
  	{"domainname", c_true},
-diff -ru mksh-R57/main.c src/main.c
---- mksh-R57/main.c	2019-01-05 05:24:45.000000000 -0800
-+++ src/main.c	2019-03-26 12:05:23.980821764 -0700
-@@ -399,6 +399,12 @@
+diff -u mksh-R59b/main.c src/main.c
+--- mksh-R59b/main.c	2020-05-16 15:51:51.000000000 -0700
++++ src/main.c	2020-05-20 17:14:19.588510856 -0700
+@@ -414,6 +414,12 @@
  	/* import environment */
  	init_environ();
  
@@ -48,3 +49,9 @@
  	/* for security */
  	typeset(TinitIFS, 0, 0, 0, 0);
  
+Only in src: main.c.orig
+Only in src: Rebuild.sh
+Only in src: rlimits.gen
+Only in src: sh_flags.gen
+Only in src: signames.inc
+Only in src: test.sh
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..d97975c
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,3 @@
+third_party {
+  license_type: NOTICE
+}
diff --git a/NOTICE b/NOTICE
index 2c542fc..2d2ae4e 100644
--- a/NOTICE
+++ b/NOTICE
@@ -1,22 +1,67 @@
-mksh is covered by The MirOS Licence:
+The MirBSD Korn Shell (mksh) is
+Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+        2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
+        2020
+  mirabilos <m@mirbsd.org>
+Copyright (c) 2015, 2017, 2020
+  KO Myung-Hun <komh@chollian.net>
+Copyright (c) 2015
+  Daniel Richard G. <skunk@iSKUNK.ORG>
+Copyright (c) 2017
+  Giacomo Tesio <giacomo@tesio.it>
+All rights reserved.
 
-/*-
- * Copyright © 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
- *	       2011, 2012, 2013
- *	Thorsten Glaser <tg@mirbsd.org>
- *
- * Provided that these terms and disclaimer and all copyright notices
- * are retained or reproduced in an accompanying document, permission
- * is granted to deal in this work without restriction, including un‐
- * limited rights to use, publicly perform, distribute, sell, modify,
- * merge, give away, or sublicence.
- *
- * This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
- * the utmost extent permitted by applicable law, neither express nor
- * implied; without malicious intent or gross negligence. In no event
- * may a licensor, author or contributor be held liable for indirect,
- * direct, other damage, loss, or other issues arising in any way out
- * of dealing in the work, even if advised of the possibility of such
- * damage or existence of a defect, except proven that it results out
- * of said person’s immediate fault when using the work as intended.
- */
+The mksh logo is
+Copyright (c) 2008, 2009
+  Lukas U. <smultron@midnightbsd.org>
+Copyright (c) 2008, 2009
+  mirabilos <m@mirbsd.org>
+
+
+Provided that these terms and disclaimer and all copyright notices
+are retained or reproduced in an accompanying document, permission
+is granted to deal in this work without restriction, including un-
+limited rights to use, publicly perform, distribute, sell, modify,
+merge, give away, or sublicence.
+
+This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+the utmost extent permitted by applicable law, neither express nor
+implied; without malicious intent or gross negligence. In no event
+may a licensor, author or contributor be held liable for indirect,
+direct, other damage, loss, or other issues arising in any way out
+of dealing in the work, even if advised of the possibility of such
+damage or existence of a defect, except proven that it results out
+of said person's immediate fault when using the work as intended.
+
+
+Copyright © 1991–2020 Unicode, Inc. All rights reserved.
+Distributed under the Terms of Use in https://www.unicode.org/copyright.html.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Unicode data files and any associated documentation
+(the "Data Files") or Unicode software and any associated documentation
+(the "Software") to deal in the Data Files or Software
+without restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, and/or sell copies of
+the Data Files or Software, and to permit persons to whom the Data Files
+or Software are furnished to do so, provided that either
+(a) this copyright and permission notice appear with all copies
+of the Data Files or Software, or
+(b) this copyright and permission notice appear in associated
+Documentation.
+
+THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS
+NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL
+DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THE DATA FILES OR SOFTWARE.
+
+Except as contained in this notice, the name of a copyright holder
+shall not be used in advertising or otherwise to promote the sale,
+use or other dealings in these Data Files or Software without prior
+written authorization of the copyright holder.
diff --git a/src/Build.sh b/src/Build.sh
index be3f711..1d30045 100644
--- a/src/Build.sh
+++ b/src/Build.sh
@@ -1,8 +1,9 @@
 #!/bin/sh
-srcversion='$MirOS: src/bin/mksh/Build.sh,v 1.734 2019/03/01 16:18:13 tg Exp $'
+srcversion='$MirOS: src/bin/mksh/Build.sh,v 1.756 2020/05/16 22:53:03 tg Exp $'
 #-
 # Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
-#		2011, 2012, 2013, 2014, 2015, 2016, 2017
+#		2011, 2012, 2013, 2014, 2015, 2016, 2017, 2019,
+#		2020
 #	mirabilos <m@mirbsd.org>
 #
 # Provided that these terms and disclaimer and all copyright notices
@@ -25,8 +26,8 @@
 #
 # Used environment documentation is at the end of this file.
 
-LC_ALL=C
-export LC_ALL
+LC_ALL=C; LANGUAGE=C
+export LC_ALL; unset LANGUAGE
 
 case $ZSH_VERSION:$VERSION in
 :zsh*) ZSH_VERSION=2 ;;
@@ -53,6 +54,15 @@
 alln=0123456789
 alls=______________________________________________________________
 
+test_n() {
+	test x"$1" = x"" || return 0
+	return 1
+}
+
+test_z() {
+	test x"$1" = x""
+}
+
 case `echo a | tr '\201' X` in
 X)
 	# EBCDIC build system
@@ -64,11 +74,11 @@
 esac
 
 genopt_die() {
-	if test -n "$1"; then
-		echo >&2 "E: $*"
-		echo >&2 "E: in '$srcfile': '$line'"
-	else
+	if test_z "$1"; then
 		echo >&2 "E: invalid input in '$srcfile': '$line'"
+	else
+		echo >&2 "E: $*"
+		echo >&2 "N: in '$srcfile': '$line'"
 	fi
 	rm -f "$bn.gen"
 	exit 1
@@ -172,9 +182,9 @@
 			esac
 			IFS= read line || genopt_die Unexpected EOF
 			IFS=$safeIFS
-			test -n "$cond" && o_gen=$o_gen$nl"$cond"
+			test_z "$cond" || o_gen=$o_gen$nl"$cond"
 			o_gen=$o_gen$nl"$line, $optc)"
-			test -n "$cond" && o_gen=$o_gen$nl"#endif"
+			test_z "$cond" || o_gen=$o_gen$nl"#endif"
 			;;
 		esac
 	done
@@ -185,11 +195,11 @@
 	esac
 	echo "$o_str" | sort | while IFS='|' read x opts cond; do
 		IFS=$safeIFS
-		test -n "$x" || continue
+		test_n "$x" || continue
 		genopt_scond
-		test -n "$cond" && echo "$cond"
+		test_z "$cond" || echo "$cond"
 		echo "\"$opts\""
-		test -n "$cond" && echo "#endif"
+		test_z "$cond" || echo "#endif"
 	done | {
 		echo "$o_hdr"
 		echo "#ifndef $o_sym$o_gen"
@@ -234,7 +244,7 @@
 rmf() {
 	for _f in "$@"; do
 		case $_f in
-		Build.sh|check.pl|check.t|dot.mkshrc|*.1|*.c|*.h|*.ico|*.opt) ;;
+		*.1|*.faq|*.ico) ;;
 		*) rm -f "$_f" ;;
 		esac
 	done
@@ -343,7 +353,7 @@
 		test $ct = pcc && vscan='unsupported'
 		test $ct = sunpro && vscan='-e ignored -e turned.off'
 	fi
-	test -n "$vscan" && grep $vscan vv.out >/dev/null 2>&1 && fv=$fr
+	test_n "$vscan" && grep $vscan vv.out >/dev/null 2>&1 && fv=$fr
 	return 0
 }
 ac_testn() {
@@ -406,10 +416,8 @@
 	test x"$ft" = x"" && ft="if $f can be used"
 	save_CFLAGS=$CFLAGS
 	CFLAGS="$CFLAGS $f"
-	if test -n "$fl"; then
-		save_LDFLAGS=$LDFLAGS
-		LDFLAGS="$LDFLAGS $fl"
-	fi
+	save_LDFLAGS=$LDFLAGS
+	test_z "$fl" || LDFLAGS="$LDFLAGS $fl"
 	if test 1 = $hf; then
 		ac_testn can_$vn '' "$ft"
 	else
@@ -421,9 +429,7 @@
 		#'
 	fi
 	eval fv=\$HAVE_CAN_`upper $vn`
-	if test -n "$fl"; then
-		test 11 = $fa$fv || LDFLAGS=$save_LDFLAGS
-	fi
+	test_z "$fl" || test 11 = $fa$fv || LDFLAGS=$save_LDFLAGS
 	test 11 = $fa$fv || CFLAGS=$save_CFLAGS
 }
 
@@ -512,7 +518,7 @@
 for i
 do
 	case $last:$i in
-	c:combine|c:dragonegg|c:llvm|c:lto)
+	c:dragonegg|c:llvm)
 		cm=$i
 		last=
 		;;
@@ -524,10 +530,6 @@
 		optflags=$i
 		last=
 		;;
-	t:*)
-		tfn=$i
-		last=
-		;;
 	:-c)
 		last=c
 		;;
@@ -573,9 +575,6 @@
 	:+T)
 		textmode=0
 		;;
-	:-t)
-		last=t
-		;;
 	:-v)
 		echo "Build.sh $srcversion"
 		echo "for mksh $dstversion"
@@ -591,12 +590,12 @@
 		;;
 	esac
 done
-if test -n "$last"; then
+if test_n "$last"; then
 	echo "$me: Option -'$last' not followed by argument!" >&2
 	exit 1
 fi
 
-test -z "$tfn" && if test $legacy = 0; then
+test_n "$tfn" || if test $legacy = 0; then
 	tfn=mksh
 else
 	tfn=lksh
@@ -606,7 +605,7 @@
 	exit 1
 fi
 rmf a.exe* a.out* conftest.c conftest.exe* *core core.* ${tfn}* *.bc *.dbg \
-    *.ll *.o *.gen *.cat1 Rebuild.sh lft no signames.inc test.sh x vv.out
+    *.ll *.o *.gen *.cat1 Rebuild.sh lft no signames.inc test.sh x vv.out *.htm
 
 SRCS="lalloc.c edit.c eval.c exec.c expr.c funcs.c histrap.c jobs.c"
 SRCS="$SRCS lex.c main.c misc.c shf.c syn.c tree.c var.c"
@@ -634,17 +633,17 @@
 else
 	CPPFLAGS="-I. -I'$srcdir' $CPPFLAGS"
 fi
-test -n "$LDSTATIC" && if test -n "$LDFLAGS"; then
-	LDFLAGS="$LDFLAGS $LDSTATIC"
-else
+test_z "$LDSTATIC" || if test_z "$LDFLAGS"; then
 	LDFLAGS=$LDSTATIC
+else
+	LDFLAGS="$LDFLAGS $LDSTATIC"
 fi
 
-if test -z "$TARGET_OS"; then
+if test_z "$TARGET_OS"; then
 	x=`uname -s 2>/dev/null || uname`
 	test x"$x" = x"`uname -n 2>/dev/null`" || TARGET_OS=$x
 fi
-if test -z "$TARGET_OS"; then
+if test_z "$TARGET_OS"; then
 	echo "$me: Set TARGET_OS, your uname is broken!" >&2
 	exit 1
 fi
@@ -652,7 +651,7 @@
 ccpc=-Wc,
 ccpl=-Wl,
 tsts=
-ccpr='|| for _f in ${tcfn}*; do case $_f in Build.sh|check.pl|check.t|dot.mkshrc|*.1|*.c|*.h|*.ico|*.opt) ;; *) rm -f "$_f" ;; esac; done'
+ccpr='|| for _f in ${tcfn}*; do case $_f in *.1|*.faq|*.ico) ;; *) rm -f "$_f" ;; esac; done'
 
 # Evil hack
 if test x"$TARGET_OS" = x"Android"; then
@@ -706,12 +705,12 @@
 # Configuration depending on OS revision, on OSes that need them
 case $TARGET_OS in
 NEXTSTEP)
-	test x"$TARGET_OSREV" = x"" && TARGET_OSREV=`hostinfo 2>&1 | \
+	test_n "$TARGET_OSREV" || TARGET_OSREV=`hostinfo 2>&1 | \
 	    grep 'NeXT Mach [0-9][0-9.]*:' | \
 	    sed 's/^.*NeXT Mach \([0-9][0-9.]*\):.*$/\1/'`
 	;;
 QNX|SCO_SV)
-	test x"$TARGET_OSREV" = x"" && TARGET_OSREV=`uname -r`
+	test_n "$TARGET_OSREV" || TARGET_OSREV=`uname -r`
 	;;
 esac
 
@@ -847,6 +846,11 @@
 LynxOS)
 	oswarn="; it has minor issues"
 	;;
+midipix)
+	add_cppflags -D_GNU_SOURCE
+	# their Perl (currently…) identifies as os:linux ☹
+	check_categories="$check_categories os:midipix"
+	;;
 MidnightBSD)
 	;;
 Minix-vmd)
@@ -1037,11 +1041,11 @@
 	# generic target for SVR4 Unix with uname -s = uname -n
 	# this duplicates the * target below
 	oswarn='; it may or may not work'
-	test x"$TARGET_OSREV" = x"" && TARGET_OSREV=`uname -r`
+	test_n "$TARGET_OSREV" || TARGET_OSREV=`uname -r`
 	;;
 *)
 	oswarn='; it may or may not work'
-	test x"$TARGET_OSREV" = x"" && TARGET_OSREV=`uname -r`
+	test_n "$TARGET_OSREV" || TARGET_OSREV=`uname -r`
 	;;
 esac
 
@@ -1084,11 +1088,12 @@
 	vv '|' "uname -a >&2"
 	;;
 esac
-test -z "$oswarn" || echo >&2 "
+test_z "$oswarn" || echo >&2 "
 Warning: mksh has not yet been ported to or tested on your
 operating system '$TARGET_OS'$oswarn. If you can provide
 a shell account to the developer, this may improve; please
-drop us a success or failure notice or even send in diffs.
+drop us a success or failure notice or even send in diffs,
+at the very least, complete logs (Build.sh + test.sh) will help.
 "
 $e "$bi$me: Building the MirBSD Korn Shell$ao $ui$dstversion$ao on $TARGET_OS ${TARGET_OSREV}..."
 
@@ -1115,6 +1120,10 @@
 ct="xlc"
 #elif defined(__SUNPRO_C)
 ct="sunpro"
+#elif defined(__neatcc__)
+ct="neatcc"
+#elif defined(__lacc__)
+ct="lacc"
 #elif defined(__ACK__)
 ct="ack"
 #elif defined(__BORLANDC__)
@@ -1230,9 +1239,8 @@
 	;;
 gcc)
 	vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN -v conftest.c $LIBS"
-	vv '|' 'echo `$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS \
-	    -dumpmachine` gcc`$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN \
-	    $LIBS -dumpversion`'
+	vv '|' 'eval echo "\`$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -dumpmachine\`" \
+		 "gcc\`$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -dumpversion\`"'
 	: "${HAVE_STRING_POOLING=i2}"
 	;;
 hpcc)
@@ -1250,6 +1258,9 @@
 kencc)
 	vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN -v conftest.c $LIBS"
 	;;
+lacc)
+	# no version information
+	;;
 lcc)
 	vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN -v conftest.c $LIBS"
 	add_cppflags -D__inline__=__inline
@@ -1266,21 +1277,27 @@
 	ccpr=		# errorlevels are not reliable
 	case $TARGET_OS in
 	Interix)
-		if [[ -n $C89_COMPILER ]]; then
-			C89_COMPILER=`ntpath2posix -c "$C89_COMPILER"`
-		else
+		if test_z "$C89_COMPILER"; then
 			C89_COMPILER=CL.EXE
-		fi
-		if [[ -n $C89_LINKER ]]; then
-			C89_LINKER=`ntpath2posix -c "$C89_LINKER"`
 		else
+			C89_COMPILER=`ntpath2posix -c "$C89_COMPILER"`
+		fi
+		if test_z "$C89_LINKER"; then
 			C89_LINKER=LINK.EXE
+		else
+			C89_LINKER=`ntpath2posix -c "$C89_LINKER"`
 		fi
 		vv '|' "$C89_COMPILER /HELP >&2"
 		vv '|' "$C89_LINKER /LINK >&2"
 		;;
 	esac
 	;;
+neatcc)
+	add_cppflags -DMKSH_DONT_EMIT_IDSTRING
+	add_cppflags -DMKSH_NO_SIGSETJMP
+	add_cppflags -Dsig_atomic_t=int
+	vv '|' "$CC"
+	;;
 nwcc)
 	vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -version"
 	;;
@@ -1333,7 +1350,7 @@
 *)
 	test x"$ct" = x"untested" && $e "!!! detecting preprocessor failed"
 	ct=unknown
-	vv "$CC --version"
+	vv '|' "$CC --version"
 	vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN -v conftest.c $LIBS"
 	vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN -V conftest.c $LIBS"
 	;;
@@ -1376,14 +1393,14 @@
 	case $ct in
 	dec)
 		CFLAGS="$CFLAGS ${ccpl}-non_shared"
-		ac_testn can_delexe compiler_fails 0 'for the -non_shared linker option' <<-EOF
+		ac_testn can_delexe compiler_fails 0 'for the -non_shared linker option' <<-'EOF'
 			#include <unistd.h>
 			int main(void) { return (isatty(0)); }
 		EOF
 		;;
 	dmc)
 		CFLAGS="$CFLAGS ${ccpl}/DELEXECUTABLE"
-		ac_testn can_delexe compiler_fails 0 'for the /DELEXECUTABLE linker option' <<-EOF
+		ac_testn can_delexe compiler_fails 0 'for the /DELEXECUTABLE linker option' <<-'EOF'
 			#include <unistd.h>
 			int main(void) { return (isatty(0)); }
 		EOF
@@ -1393,9 +1410,8 @@
 		;;
 	esac
 	test 1 = $HAVE_CAN_DELEXE || CFLAGS=$save_CFLAGS
-	ac_testn compiler_still_fails '' 'if the compiler still does not fail correctly' <<-EOF
-	EOF
-	test 1 = $HAVE_COMPILER_STILL_FAILS && exit 1
+	ac_ifcpp 'if 0' compiler_still_fails \
+	    'if the compiler still does not fail correctly' && exit 1
 fi
 if ac_ifcpp 'ifdef __TINYC__' couldbe_tcc '!' compiler_known 0 \
     'if this could be tcc'; then
@@ -1478,7 +1494,7 @@
 #
 i=`echo :"$orig_CFLAGS" | sed 's/^://' | tr -c -d $alll$allu$alln`
 # optimisation: only if orig_CFLAGS is empty
-test x"$i" = x"" && case $ct in
+test_n "$i" || case $ct in
 hpcc)
 	phase=u
 	ac_flags 1 otwo +O2
@@ -1526,6 +1542,7 @@
 	ac_flags 1 schk "${ccpc}-s" 'for stack overflow checking'
 	;;
 gcc)
+	ac_flags 1 fnolto -fno-lto 'whether we can explicitly disable buggy GCC LTO' -fno-lto
 	# The following tests run with -Werror (gcc only) if possible
 	NOWARN=$DOWARN; phase=u
 	ac_flags 1 wnodeprecateddecls -Wno-deprecated-declarations
@@ -1708,7 +1725,7 @@
 	#undef fprintf
 	extern int fprintf(FILE *, const char *format, ...)
 	    __attribute__((__format__(__printf__, 2, 3)));
-	int main(int ac, char **av) { return (fprintf(stderr, "%s%d", *av, ac)); }
+	int main(int ac, char *av[]) { return (fprintf(stderr, "%s%d", *av, ac)); }
 	#endif
 EOF
 ac_test attribute_noreturn '' 'for __attribute__((__noreturn__))' <<-'EOF'
@@ -1733,7 +1750,7 @@
 	#include <unistd.h>
 	#undef __attribute__
 	int foo(const char *) __attribute__((__pure__));
-	int main(int ac, char **av) { return (foo(av[ac - 1]) + isatty(0)); }
+	int main(int ac, char *av[]) { return (foo(av[ac - 1]) + isatty(0)); }
 	int foo(const char *s) { return ((int)s[0]); }
 	#endif
 EOF
@@ -1745,7 +1762,7 @@
 	#else
 	#include <unistd.h>
 	#undef __attribute__
-	int main(int ac __attribute__((__unused__)), char **av
+	int main(int ac __attribute__((__unused__)), char *av[]
 	    __attribute__((__unused__))) { return (isatty(0)); }
 	#endif
 EOF
@@ -1863,22 +1880,22 @@
 ac_test can_inttypes '!' stdint_h 1 "for standard 32-bit integer types" <<-'EOF'
 	#include <sys/types.h>
 	#include <stddef.h>
-	int main(int ac, char **av) { return ((uint32_t)(size_t)*av + (int32_t)ac); }
+	int main(int ac, char *av[]) { return ((uint32_t)(size_t)*av + (int32_t)ac); }
 EOF
 ac_test can_ucbints '!' can_inttypes 1 "for UCB 32-bit integer types" <<-'EOF'
 	#include <sys/types.h>
 	#include <stddef.h>
-	int main(int ac, char **av) { return ((u_int32_t)(size_t)*av + (int32_t)ac); }
+	int main(int ac, char *av[]) { return ((u_int32_t)(size_t)*av + (int32_t)ac); }
 EOF
 ac_test can_int8type '!' stdint_h 1 "for standard 8-bit integer type" <<-'EOF'
 	#include <sys/types.h>
 	#include <stddef.h>
-	int main(int ac, char **av) { return ((uint8_t)(size_t)av[ac]); }
+	int main(int ac, char *av[]) { return ((uint8_t)(size_t)av[ac]); }
 EOF
 ac_test can_ucbint8 '!' can_int8type 1 "for UCB 8-bit integer type" <<-'EOF'
 	#include <sys/types.h>
 	#include <stddef.h>
-	int main(int ac, char **av) { return ((u_int8_t)(size_t)av[ac]); }
+	int main(int ac, char *av[]) { return ((u_int8_t)(size_t)av[ac]); }
 EOF
 
 ac_test rlim_t <<-'EOF'
@@ -1948,7 +1965,11 @@
 		#define MKSH_INCLUDES_ONLY
 		#include "sh.h"
 		__RCSID("$srcversion");
-		int main(void) { printf("Hello, World!\\n"); return (isatty(0)); }
+		int main(void) {
+			struct timeval tv;
+			printf("Hello, World!\\n");
+			return (time(&tv.tv_sec));
+		}
 EOF
 	case $cm in
 	llvm)
@@ -2005,15 +2026,15 @@
 
 for what in name list; do
 	uwhat=`upper $what`
-	ac_testn sys_sig$what '' "the sys_sig${what}[] array" <<-EOF
-		extern const char * const sys_sig${what}[];
+	ac_testn sys_sig$what '' "the sys_sig$what[] array" <<-EOF
+		extern const char * const sys_sig$what[];
 		extern int isatty(int);
-		int main(void) { return (sys_sig${what}[0][0] + isatty(0)); }
+		int main(void) { return (sys_sig$what[0][0] + isatty(0)); }
 	EOF
-	ac_testn _sys_sig$what '!' sys_sig$what 0 "the _sys_sig${what}[] array" <<-EOF
-		extern const char * const _sys_sig${what}[];
+	ac_testn _sys_sig$what '!' sys_sig$what 0 "the _sys_sig$what[] array" <<-EOF
+		extern const char * const _sys_sig$what[];
 		extern int isatty(int);
-		int main(void) { return (_sys_sig${what}[0][0] + isatty(0)); }
+		int main(void) { return (_sys_sig$what[0][0] + isatty(0)); }
 	EOF
 	eval uwhat_v=\$HAVE__SYS_SIG$uwhat
 	if test 1 = "$uwhat_v"; then
@@ -2265,6 +2286,17 @@
 	int main(void) { return (sys_siglist[0][0] + isatty(0)); }
 EOF
 
+ac_test st_mtim '' 'for struct stat.st_mtim.tv_nsec' <<-'EOF'
+	#define MKSH_INCLUDES_ONLY
+	#include "sh.h"
+	int main(void) { struct stat sb; return (sizeof(sb.st_mtim.tv_nsec)); }
+EOF
+ac_test st_mtimensec '!' st_mtim 0 'for struct stat.st_mtimensec' <<-'EOF'
+	#define MKSH_INCLUDES_ONLY
+	#include "sh.h"
+	int main(void) { struct stat sb; return (sizeof(sb.st_mtimensec)); }
+EOF
+
 #
 # other checks
 #
@@ -2287,7 +2319,7 @@
 		#define CHAR_BIT 0
 		#endif
 		struct ctasserts {
-		#define cta(name, assertion) char name[(assertion) ? 1 : -1]
+		#define cta(name,assertion) char name[(assertion) ? 1 : -1]
 			cta(char_is_8_bits, (CHAR_BIT) == 8);
 			cta(long_is_32_bits, sizeof(long) == 4);
 		};
@@ -2301,7 +2333,7 @@
 		#define CHAR_BIT 0
 		#endif
 		struct ctasserts {
-		#define cta(name, assertion) char name[(assertion) ? 1 : -1]
+		#define cta(name,assertion) char name[(assertion) ? 1 : -1]
 			cta(char_is_8_bits, (CHAR_BIT) == 8);
 			cta(long_is_64_bits, sizeof(long) == 8);
 		};
@@ -2429,7 +2461,7 @@
 addsrcs USE_PRINTF_BUILTIN printf.c
 test 1 = "$USE_PRINTF_BUILTIN" && add_cppflags -DMKSH_PRINTF_BUILTIN
 test 1 = "$HAVE_CAN_VERB" && CFLAGS="$CFLAGS -verbose"
-add_cppflags -DMKSH_BUILD_R=571
+add_cppflags -DMKSH_BUILD_R=592
 
 $e $bi$me: Finished configuration testing, now producing output.$ao
 
@@ -2452,7 +2484,10 @@
 cat >test.sh <<-EOF
 	$mkshshebang
 	LC_ALL=C PATH='$PATH'; export LC_ALL PATH
-	test -n "\$KSH_VERSION" || exit 1
+	case \$KSH_VERSION in
+	*MIRBSD*|*LEGACY*) ;;
+	*) exit 1 ;;
+	esac
 	set -A check_categories -- $check_categories
 	pflag='$curdir/$mkshexe'
 	sflag='$srcdir/check.t'
@@ -2614,8 +2649,8 @@
 NONSRCS_INST=	dot.mkshrc \$(MAN)
 NONSRCS_NOINST=	Build.sh Makefile Rebuild.sh check.pl check.t test.sh
 CC=		$CC
-CFLAGS=		$CFLAGS
 CPPFLAGS=	$CPPFLAGS
+CFLAGS=		$CFLAGS
 LDFLAGS=	$LDFLAGS
 LIBS=		$LIBS
 
@@ -2685,6 +2720,7 @@
 test -f $tcfn || exit 1
 test 1 = $r || v "$NROFF -mdoc <'$srcdir/lksh.1' >lksh.cat1" || rmf lksh.cat1
 test 1 = $r || v "$NROFF -mdoc <'$srcdir/mksh.1' >mksh.cat1" || rmf mksh.cat1
+test 1 = $r || v "(set -- ''; . '$srcdir/FAQ2HTML.sh')" || rmf FAQ.htm
 test 0 = $eq && v $SIZE $tcfn
 i=install
 test -f /usr/ucb/$i && i=/usr/ucb/$i
@@ -2698,7 +2734,13 @@
 fi
 $e
 $e Installing the manual:
+if test -e FAQ.htm; then
+	$e "# $i -c -o root -g bin -m 444 FAQ.htm /usr/share/doc/mksh/"
+fi
 if test -f mksh.cat1; then
+	if test -e FAQ.htm; then
+		$e plus either
+	fi
 	$e "# $i -c -o root -g bin -m 444 lksh.cat1" \
 	    "/usr/share/man/cat1/lksh.0"
 	$e "# $i -c -o root -g bin -m 444 mksh.cat1" \
@@ -2709,6 +2751,8 @@
 $e
 $e Run the regression test suite: ./test.sh
 $e Please also read the sample file dot.mkshrc and the fine manual.
+test -e FAQ.htm || \
+    $e Run FAQ2HTML.sh and place FAQ.htm into a suitable location as well.
 exit 0
 
 : <<'EOD'
@@ -2740,6 +2784,7 @@
 ==== cpp definitions ====
 DEBUG				dont use in production, wants gcc, implies:
 DEBUG_LEAKS			enable freeing resources before exiting
+KSH_VERSIONNAME_VENDOR_EXT	when patching; space+plus+word (e.g. " +SuSE")
 MKSHRC_PATH			"~/.mkshrc" (do not change)
 MKSH_A4PB			force use of arc4random_pushb
 MKSH_ASSUME_UTF8		(0=disabled, 1=enabled; default: unset)
@@ -2768,6 +2813,7 @@
 MKSH_TYPEDEF_SIG_ATOMIC_T	define to e.g. 'int' if sig_atomic_t is missing
 MKSH_TYPEDEF_SSIZE_T		define to e.g. 'long' if your OS has no ssize_t
 MKSH_UNEMPLOYED			disable job control (but not jobs/co-processes)
+USE_REALLOC_MALLOC		define as 0 to not use realloc as malloc
 
 === generic installation instructions ===
 
@@ -2777,15 +2823,15 @@
 MKSH_SMALL but with Vi mode, add -DMKSH_S_NOVI=0 to CPPFLAGS as well.
 
 Normally, the following command is what you want to run, then:
-$ (sh Build.sh -r -c lto && ./test.sh -f) 2>&1 | tee log
+$ (sh Build.sh -r && ./test.sh -f) 2>&1 | tee log
 
 Copy dot.mkshrc to /etc/skel/.mkshrc; install mksh into $prefix/bin; or
 /bin; install the manpage, if omitting the -r flag a catmanpage is made
 using $NROFF. Consider using a forward script as /etc/skel/.mkshrc like
-http://anonscm.debian.org/cgit/collab-maint/mksh.git/plain/debian/.mkshrc
+https://evolvis.org/plugins/scmgit/cgi-bin/gitweb.cgi?p=alioth/mksh.git;a=blob;f=debian/.mkshrc
 and put dot.mkshrc as /etc/mkshrc so users need not keep up their HOME.
 
 You may also want to install the lksh binary (also as /bin/sh) built by:
-$ CPPFLAGS="$CPPFLAGS -DMKSH_BINSHPOSIX" sh Build.sh -L -r -c lto
+$ CPPFLAGS="$CPPFLAGS -DMKSH_BINSHPOSIX" sh Build.sh -L -r
 
 EOD
diff --git a/src/FAQ2HTML.sh b/src/FAQ2HTML.sh
new file mode 100644
index 0000000..180ed98
--- /dev/null
+++ b/src/FAQ2HTML.sh
@@ -0,0 +1,136 @@
+#!/bin/mksh
+rcsid='$MirOS: src/bin/mksh/FAQ2HTML.sh,v 1.1 2020/02/03 22:23:33 tg Exp $'
+#-
+# Copyright © 2020
+#	mirabilos <m@mirbsd.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+#-
+
+set -e
+LC_ALL=C; LANGUAGE=C
+export LC_ALL; unset LANGUAGE
+nl='
+'
+srcdir=$(dirname "$0")
+
+p=--posix
+sed $p -e q </dev/null >/dev/null 2>&1 || p=
+
+v=$1
+if test -z "$v"; then
+	v=$(sed $p -n '/^#define MKSH_VERSION "\(.*\)"$/s//\1/p' "$srcdir"/sh.h)
+fi
+src_id=$(sed $p -n '/^RCSID: /s///p' "$srcdir"/mksh.faq)
+# sanity check
+case $src_id in
+(*"$nl"*)
+	echo >&2 "E: more than one RCSID in mksh.faq?"
+	exit 1 ;;
+esac
+
+sed $p \
+    -e '/^RCSID: \$/s/^.*$/----/' \
+    -e 's!@@RELPATH@@!http://www.mirbsd.org/!g' \
+    -e 's^	<span style="display:none;">	</span>' \
+    "$srcdir"/mksh.faq | tr '\n' '' | sed $p \
+    -e 'sg' \
+    -e 's----g' \
+    -e 's\([^]*\)\1g' \
+    -e 's\([^]*\)\1g' \
+    -e 's\([^]*\)*ToC: \([^]*\)Title: \([^]*\)\([^]*\)\{0,1\}</div><h2 id="\2"><a href="#\2">\3</a></h2><div>g' \
+    -e 's[^]*</div><div>g' \
+    -e 's^</div>*' \
+    -e 's$</div>' \
+    -e 's<><error><>g' \
+    -e 'sg' | tr '' '\n' >FAQ.tmp
+
+exec >FAQ.htm~
+cat <<EOF
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+ "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"><head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+ <title>mksh $v FAQ (local copy)</title>
+ <meta name="source" content="$src_id" />
+ <meta name="generator" content="$rcsid" />
+ <style type="text/css"><!--/*--><![CDATA[/*><!--*/
+ .boxhead {
+	margin-bottom:0px;
+ }
+
+ .boxtext {
+	border:4px ridge green;
+	margin:0px 24px 0px 18px;
+	padding:2px 3px 2px 3px;
+ }
+
+ .boxfoot {
+	margin-top:0px;
+ }
+
+ h2:before {
+	content:"🔗 ";
+ }
+
+ a[href^="ftp://"]:after,
+ a[href^="http://"]:after,
+ a[href^="https://"]:after,
+ a[href^="irc://"]:after,
+ a[href^="mailto:"]:after,
+ a[href^="news:"]:after,
+ a[href^="nntp://"]:after {
+	content:"⏍";
+	color:#FF0000;
+	vertical-align:super;
+	margin:0 0 0 1px;
+ }
+
+ pre {
+	/*      ↑   →   ↓    ←   */
+	margin:0px 9px 0px 15px;
+ }
+
+ tt {
+	white-space:nowrap;
+ }
+ /*]]>*/--></style>
+</head><body>
+<p>Note: Links marked like <a href="irc://chat.freenode.net/!/bin/mksh">this
+ one to the mksh IRC channel</a> connect to external resources.</p>
+<p>⚠ <b>Notice:</b> the website will have <a
+ href="http://www.mirbsd.org/mksh-faq.htm">the latest version of the
+ mksh FAQ</a> online.</p>
+<h1>Table of Contents</h1>
+<ul>
+EOF
+sed $p -n \
+    '/^<h2 id="\([^"]*"\)><a[^>]*\(>.*<\/a><\/\)h2>$/s//<li><a href="#\1\2li>/p' \
+    <FAQ.tmp
+cat <<EOF
+</ul>
+
+<h1>Frequently Asked Questions</h1>
+EOF
+cat FAQ.tmp - <<EOF
+<h1>Imprint</h1>
+<p>This offline HTML page for mksh $v was automatically generated
+ from the sources.</p>
+</body></html>
+EOF
+exec >/dev/null
+rm FAQ.tmp
+mv FAQ.htm~ FAQ.htm
diff --git a/src/check.pl b/src/check.pl
index e9c2437..c08e287 100644
--- a/src/check.pl
+++ b/src/check.pl
@@ -1,4 +1,4 @@
-# $MirOS: src/bin/mksh/check.pl,v 1.49 2017/05/05 21:17:31 tg Exp $
+# $MirOS: src/bin/mksh/check.pl,v 1.50 2019/08/01 20:05:55 tg Exp $
 # $OpenBSD: th,v 1.1 2013/12/02 20:39:44 millert Exp $
 #-
 # Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2011,
@@ -292,7 +292,7 @@
 # Set up a very minimal environment
 %new_env = ();
 foreach $env (('HOME', 'LD_LIBRARY_PATH', 'LOCPATH', 'LOGNAME',
-  'PATH', 'SHELL', 'UNIXMODE', 'UNIXROOT', 'USER')) {
+  'PATH', 'PERLIO', 'SHELL', 'UNIXMODE', 'UNIXROOT', 'USER')) {
     $new_env{$env} = $ENV{$env} if defined $ENV{$env};
 }
 $new_env{'CYGWIN'} = 'nodosfilewarning';
diff --git a/src/check.t b/src/check.t
index d5df4cb..8818822 100644
--- a/src/check.t
+++ b/src/check.t
Binary files differ
diff --git a/src/dot.mkshrc b/src/dot.mkshrc
index 4a3dfea..f06dbc6 100644
--- a/src/dot.mkshrc
+++ b/src/dot.mkshrc
@@ -1,8 +1,9 @@
 # $Id$
-# $MirOS: src/bin/mksh/dot.mkshrc,v 1.121 2017/08/08 21:10:21 tg Exp $
+# $MirOS: src/bin/mksh/dot.mkshrc,v 1.128 2020/04/13 18:39:03 tg Exp $
 #-
 # Copyright (c) 2002, 2003, 2004, 2006, 2007, 2008, 2009, 2010,
-#		2011, 2012, 2013, 2014, 2015, 2016, 2017
+#		2011, 2012, 2013, 2014, 2015, 2016, 2017, 2019,
+#		2020
 #	mirabilos <m@mirbsd.org>
 #
 # Provided that these terms and disclaimer and all copyright notices
@@ -64,11 +65,10 @@
 done
 
 \\builtin alias ls=ls l='ls -F' la='l -a' ll='l -l' lo='l -alo'
-\: "${HOSTNAME:=$(\\builtin ulimit -c 0; \\builtin print -r -- $(hostname \
-    2>/dev/null))}${EDITOR:=/bin/ed}${TERM:=vt100}${USER:=$(\\builtin ulimit \
-    -c 0; id -un 2>/dev/null)}${USER:=?}"
+\: "${EDITOR:=/bin/ed}${TERM:=vt100}${USER:=$(\\builtin ulimit -c 0; id -un \
+    2>/dev/null)}${HOSTNAME:=$(\\builtin ulimit -c 0; hostname 2>/dev/null)}"
 [[ $HOSTNAME = ?(?(ip6-)localhost?(6)) ]] && HOSTNAME=nil; \\builtin unalias ls
-\\builtin export EDITOR HOSTNAME TERM USER
+\\builtin export EDITOR HOSTNAME TERM USER="${USER:-?}"
 
 # minimal support for lksh users
 if [[ $KSH_VERSION = *LEGACY\ KSH* ]]; then
@@ -109,7 +109,7 @@
 	}
 else
 	function hd {
-		\\builtin cat "$@" | hd_mksh "$@"
+		\\builtin cat "$@" | hd_mksh
 	}
 fi
 
@@ -150,6 +150,48 @@
 	(( hv == 2147483647 )) || \\builtin print -r -- "$dasc|"
 }
 
+function which {
+	\\builtin typeset p x c
+	\\builtin typeset -i a=0 rv=2 e
+	\\builtin set +e
+	\\builtin set -o noglob
+
+	while \\builtin getopts "a" x; do
+		case $x {
+		(a)	a=1 ;;
+		(+a)	a=0 ;;
+		(*)	\\builtin print -ru2 'Usage: which [-a] name [...]'
+			\\builtin return 255 ;;
+		}
+	done
+	\\builtin shift $((OPTIND - 1))
+
+	#        vvvvvvvvvvvvvvvvvvvv should be def_path
+	p=${PATH-/usr/bin$PATHSEP/bin}
+	#       ^ no colon!
+
+	# trailing PATHSEP vs field splitting
+	[[ $p = *"$PATHSEP" ]] && p+=.
+
+	IFS=$PATHSEP
+	\\builtin set -A p -- ${p:-.}
+	IFS=$' \t\n'
+
+	for x in "$@"; do
+		if (( !a )) || [[ $x = */* ]]; then
+			\\builtin whence -p -- "$x"
+			e=$?
+		else
+			e=1
+			for c in "${p[@]}"; do
+				PATH=${c:-.} \\builtin whence -p -- "$x" && e=0
+			done
+		fi
+		(( rv = (e == 0) ? (rv & ~2) : (rv == 2 ? 2 : 1) ))
+	done
+	\\builtin return $rv
+}
+
 # Berkeley C shell compatible dirs, popd, and pushd functions
 # Z shell compatible chpwd() hook, used to update DIRSTACK[0]
 DIRSTACKBASE=$(\\builtin realpath ~/. 2>/dev/null || \
@@ -450,7 +492,6 @@
 	i_func[nfunc++]=false
 	i_func[nfunc++]=fc
 	i_func[nfunc++]=getopts
-	i_func[nfunc++]=global
 	i_func[nfunc++]=jobs
 	i_func[nfunc++]=kill
 	i_func[nfunc++]=let
@@ -499,6 +540,7 @@
 	i_func[nfunc++]=smores
 	i_func[nfunc++]=hd
 	i_func[nfunc++]=hd_mksh
+	i_func[nfunc++]=which
 	i_func[nfunc++]=chpwd
 	i_func[nfunc++]=cd
 	i_func[nfunc++]=cd_csh
@@ -604,7 +646,7 @@
 
 \: place customisations below this line
 
-# some defaults follow — you are supposed to adjust these to your
+# some defaults / samples which you are supposed to adjust to your
 # liking; by default we add ~/.etc/bin and ~/bin (whichever exist)
 # to $PATH, set $SHELL to mksh, set some defaults for man and less
 # and show a few more possible things for users to begin moving in
@@ -618,11 +660,15 @@
 \\builtin export SHELL=$MKSH MANWIDTH=80 LESSHISTFILE=-
 \\builtin alias cls='\\builtin print -n \\ec'
 
-#\\builtin unset LANGUAGE LC_ADDRESS LC_ALL LC_COLLATE LC_IDENTIFICATION LC_MONETARY \
-#    LC_NAME LC_NUMERIC LC_TELEPHONE LC_TIME
+#\\builtin unset LC_ADDRESS LC_COLLATE LC_CTYPE LC_IDENTIFICATION \
+#    LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER \
+#    LC_TELEPHONE LC_TIME LANGUAGE LANG LC_ALL
 #p=en_GB.UTF-8
 #\\builtin export LANG=C LC_CTYPE=$p LC_MEASUREMENT=$p LC_MESSAGES=$p LC_PAPER=$p
+#\\builtin export LANG=C.UTF-8 LC_CTYPE=C.UTF-8
+#\\builtin export LC_ALL=C.UTF-8
 #\\builtin set -U
+#[[ ${LC_ALL:-${LC_CTYPE:-${LANG:-}}} = *[Uu][Tt][Ff]?(-)8* ]] || \\builtin set +U
 
 \\builtin unset p
 
diff --git a/src/edit.c b/src/edit.c
index c231af1..8c1c2c8 100644
--- a/src/edit.c
+++ b/src/edit.c
@@ -5,7 +5,8 @@
 
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
- *		 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018
+ *		 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018,
+ *		 2019, 2020
  *	mirabilos <m@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -28,7 +29,7 @@
 
 #ifndef MKSH_NO_CMDLINE_EDITING
 
-__RCSID("$MirOS: src/bin/mksh/edit.c,v 1.343 2018/07/15 16:16:38 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/edit.c,v 1.351 2020/04/15 20:16:19 tg Exp $");
 
 /*
  * in later versions we might use libtermcap for this, but since external
@@ -176,7 +177,7 @@
 static void
 x_putcf(int c)
 {
-	shf_putc(c, shl_out);
+	shf_putc_i(c, shl_out);
 }
 
 /*********************************
@@ -353,7 +354,7 @@
 			*--cp = '/';
 		} else {
 			/* ok, expand and replace */
-			cp = shf_smprintf(Tf_sSs, dp, cp);
+			strpathx(cp, dp, cp, 1);
 			if (magic_flag)
 				afree(s, ATEMP);
 			s = cp;
@@ -995,10 +996,7 @@
 static int x_match(char *, char *);
 static void x_redraw(int);
 static void x_push(size_t);
-static char *x_mapin(const char *, Area *);
-static char *x_mapout(int);
-static void x_mapout2(int, char **);
-static void x_print(int, int);
+static void x_bind_showone(int, int);
 static void x_e_ungetc(int);
 static int x_e_getc(void);
 static void x_e_putc2(int);
@@ -1145,6 +1143,7 @@
 #ifndef MKSH_SMALL
 	/* more non-standard ones */
 	{ XFUNC_eval_region,		1,  CTRL_E	},
+	{ XFUNC_quote_region,		1,	'Q'	},
 	{ XFUNC_edit_line,		2,	'e'	}
 #endif
 };
@@ -2389,190 +2388,223 @@
 }
 #endif
 
-static char *
-x_mapin(const char *cp, Area *ap)
+int
+x_bind_check(void)
 {
-	char *news, *op;
+	return (x_tab == NULL);
+}
 
-	strdupx(news, cp, ap);
-	op = news;
-	while (*cp) {
-		switch (*cp) {
-		case '^':
-			cp++;
-			*op++ = ksh_toctrl(*cp);
-			break;
-		case '\\':
-			if (cp[1] == '\\' || cp[1] == '^')
-				++cp;
-			/* FALLTHROUGH */
-		default:
-			*op++ = *cp;
-		}
-		cp++;
+static XString x_bind_show_xs;
+static char *x_bind_show_xp;
+
+static void
+x_bind_show_ch(unsigned char ch)
+{
+	Xcheck(x_bind_show_xs, x_bind_show_xp);
+	switch (ch) {
+	case ORD('^'):
+	case ORD('\\'):
+	case ORD('='):
+		*x_bind_show_xp++ = '\\';
+		*x_bind_show_xp++ = ch;
+		break;
+	default:
+		if (ksh_isctrl(ch)) {
+			*x_bind_show_xp++ = '^';
+			*x_bind_show_xp++ = ksh_unctrl(ch);
+		} else
+			*x_bind_show_xp++ = ch;
+		break;
 	}
-	*op = '\0';
-
-	return (news);
 }
 
 static void
-x_mapout2(int c, char **buf)
+x_bind_showone(int prefix, int key)
 {
-	char *p = *buf;
+	unsigned char f = XFUNC_VALUE(x_tab[prefix][key]);
 
-	if (ksh_isctrl(c)) {
-		*p++ = '^';
-		*p++ = ksh_unctrl(c);
-	} else
-		*p++ = c;
-	*p = 0;
-	*buf = p;
-}
+	if (!x_bind_show_xs.areap)
+		XinitN(x_bind_show_xs, 16, AEDIT);
 
-static char *
-x_mapout(int c)
-{
-	static char buf[8];
-	char *bp = buf;
-
-	x_mapout2(c, &bp);
-	return (buf);
-}
-
-static void
-x_print(int prefix, int key)
-{
-	int f = x_tab[prefix][key];
-
-	if (prefix)
-		/* prefix == 1 || prefix == 2 || prefix == 3 */
-		shf_puts(x_mapout(prefix == 1 ? CTRL_BO :
-		    prefix == 2 ? CTRL_X : 0), shl_stdout);
-#ifdef MKSH_SMALL
-	shprintf("%s = ", x_mapout(key));
-#else
-	shprintf("%s%s = ", x_mapout(key), (f & 0x80) ? "~" : "");
-	if (XFUNC_VALUE(f) != XFUNC_ins_string)
-#endif
-		shprintf(Tf_sN, x_ftab[XFUNC_VALUE(f)].xf_name);
+	x_bind_show_xp = Xstring(x_bind_show_xs, x_bind_show_xp);
+	shf_puts("bind ", shl_stdout);
 #ifndef MKSH_SMALL
-	else
-		shprintf("'%s'\n", x_atab[prefix][key]);
+	if (f == XFUNC_ins_string)
+		shf_puts("-m ", shl_stdout);
 #endif
+	switch (prefix) {
+	case 1:
+		x_bind_show_ch(CTRL_BO);
+		break;
+	case 2:
+		x_bind_show_ch(CTRL_X);
+		break;
+	case 3:
+		x_bind_show_ch(0);
+		break;
+	}
+	x_bind_show_ch(key);
+#ifndef MKSH_SMALL
+	if (x_tab[prefix][key] & 0x80)
+		*x_bind_show_xp++ = '~';
+#endif
+	*x_bind_show_xp = '\0';
+	x_bind_show_xp = Xstring(x_bind_show_xs, x_bind_show_xp);
+	print_value_quoted(shl_stdout, x_bind_show_xp);
+	shf_putc('=', shl_stdout);
+#ifndef MKSH_SMALL
+	if (f == XFUNC_ins_string) {
+		const unsigned char *cp = (const void *)x_atab[prefix][key];
+		unsigned char c;
+
+		while ((c = *cp++))
+			x_bind_show_ch(c);
+		*x_bind_show_xp = '\0';
+		x_bind_show_xp = Xstring(x_bind_show_xs, x_bind_show_xp);
+		print_value_quoted(shl_stdout, x_bind_show_xp);
+	} else
+#endif
+	  shf_puts(x_ftab[f].xf_name, shl_stdout);
+	shf_putc('\n', shl_stdout);
 }
 
 int
-x_bind(const char *a1, const char *a2,
-#ifndef MKSH_SMALL
-    /* bind -m */
-    bool macro,
-#endif
-    /* bind -l */
-    bool list)
+x_bind_list(void)
 {
-	unsigned char f;
+	size_t f;
+
+	for (f = 0; f < NELEM(x_ftab); f++)
+		if (!(x_ftab[f].xf_flags & XF_NOBIND))
+			shprintf(Tf_sN, x_ftab[f].xf_name);
+	return (0);
+}
+
+int
+x_bind_showall(void)
+{
 	int prefix, key;
-	char *m1, *m2;
-#ifndef MKSH_SMALL
-	char *sp = NULL;
-	bool hastilde;
-#endif
 
-	if (x_tab == NULL) {
-		bi_errorf("can't bind, not a tty");
-		return (1);
-	}
-	/* List function names */
-	if (list) {
-		for (f = 0; f < NELEM(x_ftab); f++)
-			if (!(x_ftab[f].xf_flags & XF_NOBIND))
-				shprintf(Tf_sN, x_ftab[f].xf_name);
-		return (0);
-	}
-	if (a1 == NULL) {
-		for (prefix = 0; prefix < X_NTABS; prefix++)
-			for (key = 0; key < X_TABSZ; key++) {
-				f = XFUNC_VALUE(x_tab[prefix][key]);
-				if (f == XFUNC_insert || f == XFUNC_error
-#ifndef MKSH_SMALL
-				    || (macro && f != XFUNC_ins_string)
-#endif
-				    )
-					continue;
-				x_print(prefix, key);
-			}
-		return (0);
-	}
-	m2 = m1 = x_mapin(a1, ATEMP);
-	prefix = 0;
-	for (;; m1++) {
-		key = (unsigned char)*m1;
-		f = XFUNC_VALUE(x_tab[prefix][key]);
-		if (f == XFUNC_meta1)
-			prefix = 1;
-		else if (f == XFUNC_meta2)
-			prefix = 2;
-		else if (f == XFUNC_meta3)
-			prefix = 3;
-		else
-			break;
-	}
-	if (*++m1
-#ifndef MKSH_SMALL
-	    && ((*m1 != '~') || *(m1 + 1))
-#endif
-	    ) {
-		char msg[256];
-		const char *c = a1;
-		m1 = msg;
-		while (*c && (size_t)(m1 - msg) < sizeof(msg) - 3)
-			x_mapout2(*c++, &m1);
-		bi_errorf("too long key sequence: %s", msg);
-		return (1);
-	}
-#ifndef MKSH_SMALL
-	hastilde = tobool(*m1);
-#endif
-	afree(m2, ATEMP);
-
-	if (a2 == NULL) {
-		x_print(prefix, key);
-		return (0);
-	}
-	if (*a2 == 0) {
-		f = XFUNC_insert;
-#ifndef MKSH_SMALL
-	} else if (macro) {
-		f = XFUNC_ins_string;
-		sp = x_mapin(a2, AEDIT);
-#endif
-	} else {
-		for (f = 0; f < NELEM(x_ftab); f++)
-			if (!strcmp(x_ftab[f].xf_name, a2))
+	for (prefix = 0; prefix < X_NTABS; prefix++)
+		for (key = 0; key < X_TABSZ; key++)
+			switch (XFUNC_VALUE(x_tab[prefix][key])) {
+			case XFUNC_error:	/* unset */
+			case XFUNC_insert:	/* auto-insert */
 				break;
-		if (f == NELEM(x_ftab) || x_ftab[f].xf_flags & XF_NOBIND) {
-			bi_errorf("%s: no such function", a2);
+			default:
+				x_bind_showone(prefix, key);
+				break;
+			}
+	return (0);
+}
+
+static unsigned int
+x_bind_getc(const char **ccpp)
+{
+	unsigned int ch, ec;
+
+	if ((ch = ord(**ccpp)))
+		++(*ccpp);
+	switch (ch) {
+	case ORD('^'):
+		ch = ksh_toctrl(**ccpp) | 0x100U;
+		if (**ccpp)
+			++(*ccpp);
+		break;
+	case ORD('\\'):
+		switch ((ec = ord(**ccpp))) {
+		case ORD('^'):
+		case ORD('\\'):
+		case ORD('='):
+			ch = ec | 0x100U;
+			++(*ccpp);
+			break;
+		}
+		break;
+	}
+	return (ch);
+}
+
+int
+x_bind(const char *s SMALLP(bool macro))
+{
+	const char *ccp = s;
+	int prefix, key;
+	unsigned int c;
+#ifndef MKSH_SMALL
+	bool hastilde = false;
+	char *ms = NULL;
+#endif
+
+	prefix = 0;
+	c = x_bind_getc(&ccp);
+	if (!c || c == ORD('=')) {
+		bi_errorf("no key to bind");
+		return (1);
+	}
+	key = c & 0xFF;
+	while ((c = x_bind_getc(&ccp)) != ORD('=')) {
+		if (!c) {
+			x_bind_showone(prefix, key);
+			return (0);
+		}
+		switch (XFUNC_VALUE(x_tab[prefix][key])) {
+		case XFUNC_meta1:
+			prefix = 1;
+			if (0)
+				/* FALLTHROUGH */
+		case XFUNC_meta2:
+			  prefix = 2;
+			if (0)
+				/* FALLTHROUGH */
+		case XFUNC_meta3:
+			  prefix = 3;
+			key = c & 0xFF;
+			continue;
+		}
+#ifndef MKSH_SMALL
+		if (c == ORD('~')) {
+			hastilde = true;
+			continue;
+		}
+#endif
+		bi_errorf("too long key sequence: %s", s);
+		return (-1);
+	}
+
+#ifndef MKSH_SMALL
+	if (macro) {
+		char *cp;
+
+		cp = ms = alloc(strlen(ccp) + 1, AEDIT);
+		while ((c = x_bind_getc(&ccp)))
+			*cp++ = c;
+		*cp = '\0';
+		c = XFUNC_ins_string;
+	} else
+#endif
+	  if (!*ccp) {
+		c = XFUNC_insert;
+	} else {
+		for (c = 0; c < NELEM(x_ftab); ++c)
+			if (!strcmp(x_ftab[c].xf_name, ccp))
+				break;
+		if (c == NELEM(x_ftab) || x_ftab[c].xf_flags & XF_NOBIND) {
+			bi_errorf("%s: no such editing command", ccp);
 			return (1);
 		}
 	}
 
 #ifndef MKSH_SMALL
-	if (XFUNC_VALUE(x_tab[prefix][key]) == XFUNC_ins_string &&
-	    x_atab[prefix][key])
+	if (XFUNC_VALUE(x_tab[prefix][key]) == XFUNC_ins_string)
 		afree(x_atab[prefix][key], AEDIT);
+	x_atab[prefix][key] = ms;
+	if (hastilde)
+		c |= 0x80U;
 #endif
-	x_tab[prefix][key] = f
-#ifndef MKSH_SMALL
-	    | (hastilde ? 0x80 : 0)
-#endif
-	    ;
-#ifndef MKSH_SMALL
-	x_atab[prefix][key] = sp;
-#endif
+	x_tab[prefix][key] = c;
 
-	/* Track what the user has bound so x_mode(true) won't toast things */
-	if (f == XFUNC_insert)
+	/* track what the user has bound, so x_mode(true) won't toast things */
+	if (c == XFUNC_insert)
 		x_bound[(prefix * X_TABSZ + key) / 8] &=
 		    ~(1 << ((prefix * X_TABSZ + key) % 8));
 	else
@@ -3507,11 +3539,11 @@
 static int srchlen;			/* length of current search pattern */
 static char *ybuf;			/* yank buffer */
 static int yanklen;			/* length of yank buffer */
-static int fsavecmd = ' ';		/* last find command */
+static uint8_t fsavecmd = ORD(' ');	/* last find command */
 static int fsavech;			/* character to find */
 static char lastcmd[MAXVICMD];		/* last non-move command */
 static int lastac;			/* argcnt for lastcmd */
-static int lastsearch = ' ';		/* last search command */
+static uint8_t lastsearch = ORD(' ');	/* last search command */
 static char srchpat[SRCHLEN];		/* last search pattern */
 static int insert;			/* <>0 in insert mode */
 static int hnum;			/* position in history */
@@ -3651,21 +3683,25 @@
 
 	case VNORMAL:
 		/* PC scancodes */
-		if (!ch) switch (cmdlen = 0, (ch = x_getc())) {
-		case 71: ch = '0'; goto pseudo_vi_command;
-		case 72: ch = 'k'; goto pseudo_vi_command;
-		case 73: ch = 'A'; goto vi_xfunc_search_up;
-		case 75: ch = 'h'; goto pseudo_vi_command;
-		case 77: ch = 'l'; goto pseudo_vi_command;
-		case 79: ch = '$'; goto pseudo_vi_command;
-		case 80: ch = 'j'; goto pseudo_vi_command;
-		case 83: ch = 'x'; goto pseudo_vi_command;
-		default: ch = 0; goto vi_insert_failed;
+		if (!ch) {
+			cmdlen = 0;
+			switch (ch = x_getc()) {
+			case 71: ch = ORD('0'); goto pseudo_vi_command;
+			case 72: ch = ORD('k'); goto pseudo_vi_command;
+			case 73: ch = ORD('A'); goto vi_xfunc_search;
+			case 75: ch = ORD('h'); goto pseudo_vi_command;
+			case 77: ch = ORD('l'); goto pseudo_vi_command;
+			case 79: ch = ORD('$'); goto pseudo_vi_command;
+			case 80: ch = ORD('j'); goto pseudo_vi_command;
+			case 81: ch = ORD('B'); goto vi_xfunc_search;
+			case 83: ch = ORD('x'); goto pseudo_vi_command;
+			default: ch = 0; goto vi_insert_failed;
+			}
 		}
 		if (insert != 0) {
 			if (ch == CTRL_V) {
 				state = VLIT;
-				ch = '^';
+				ch = ORD('^');
 			}
 			switch (vi_insert(ch)) {
 			case -1:
@@ -3699,8 +3735,8 @@
 					save_cbuf();
 					vs->cursor = 0;
 					vs->linelen = 0;
-					if (putbuf(ch == '/' ? "/" : "?", 1,
-					    false) != 0)
+					if (putbuf(ord(ch) == ORD('/') ?
+					    "/" : "?", 1, false) != 0)
 						return (-1);
 					refresh(0);
 				}
@@ -3861,10 +3897,11 @@
 		break;
 
 	case VPREFIX2:
- vi_xfunc_search_up:
+ vi_xfunc_search:
 		state = VFAIL;
 		switch (ch) {
-		case 'A':
+		case ORD('A'):
+		case ORD('B'):
 			/* the cursor may not be at the BOL */
 			if (!vs->cursor)
 				break;
@@ -3873,13 +3910,14 @@
 				vs->cursor = sizeof(srchpat) - 2;
 			/* anchor the search pattern */
 			srchpat[0] = '^';
-			/* take the current line up to the cursor */
-			memmove(srchpat + 1, vs->cbuf, vs->cursor);
+			/* take current line up to the cursor */
+			memcpy(srchpat + 1, vs->cbuf, vs->cursor);
 			srchpat[vs->cursor + 1] = '\0';
 			/* set a magic flag */
 			argc1 = 2 + (int)vs->cursor;
-			/* and emulate a backwards history search */
-			lastsearch = '/';
+			/* and emulate a history search */
+			/* search backwards if PgUp, forwards for PgDn */
+			lastsearch = ch == ORD('A') ? '/' : '?';
 			*curcmd = 'n';
 			goto pseudo_VCMD;
 		}
@@ -4442,9 +4480,9 @@
 			/* FALLTHROUGH */
 		case ORD('n'):
 		case ORD('N'):
-			if (lastsearch == ' ')
+			if (lastsearch == ORD(' '))
 				return (-1);
-			if (lastsearch == '?')
+			if (lastsearch == ORD('?'))
 				c1 = 1;
 			else
 				c1 = 0;
@@ -4648,10 +4686,10 @@
 		/* FALLTHROUGH */
 	case ORD(','):
 	case ORD(';'):
-		if (fsavecmd == ' ')
+		if (fsavecmd == ORD(' '))
 			return (-1);
 		i = ksh_eq(fsavecmd, 'F', 'f');
-		t = fsavecmd > 'a';
+		t = rtt2asc(fsavecmd) > rtt2asc('a');
 		if (*cmd == ',')
 			t = !t;
 		if ((ncursor = findch(fsavech, argcnt, tobool(t),
@@ -5565,9 +5603,19 @@
 {
 	/* default must be 0 (bss) */
 	x_term_mode = 0;
-	/* this is what tmux uses, don't ask me about it */
-	if (!strcmp(termtype, "screen") || !strncmp(termtype, "screen-", 7))
-		x_term_mode = 1;
+	/* catch any of the TERM types tmux uses, don’t ask m̲e̲ about it… */
+	switch (*termtype) {
+	case 's':
+		if (!strncmp(termtype, "screen", 6) &&
+		    (termtype[6] == '\0' || termtype[6] == '-'))
+			x_term_mode = 1;
+		break;
+	case 't':
+		if (!strncmp(termtype, "tmux", 4) &&
+		    (termtype[4] == '\0' || termtype[4] == '-'))
+			x_term_mode = 1;
+		break;
+	}
 }
 
 #ifndef MKSH_SMALL
@@ -5589,39 +5637,40 @@
 		afree(wds, ATEMP);
 		strdupx(cp, cp, AEDIT);
 	} else
+		/* command cannot be parsed */
 		cp = NULL;
 	quitenv(NULL);
 	return (cp);
 }
 
 static int
-x_eval_region(int c MKSH_A_UNUSED)
+x_operate_region(char *(*helper)(const char *, size_t))
 {
-	char *evbeg, *evend, *cp;
+	char *rgbeg, *rgend, *cp;
 	size_t newlen;
 	/* only for LINE overflow checking */
 	size_t restlen;
 
 	if (xmp == NULL) {
-		evbeg = xbuf;
-		evend = xep;
+		rgbeg = xbuf;
+		rgend = xep;
 	} else if (xmp < xcp) {
-		evbeg = xmp;
-		evend = xcp;
+		rgbeg = xmp;
+		rgend = xcp;
 	} else {
-		evbeg = xcp;
-		evend = xmp;
+		rgbeg = xcp;
+		rgend = xmp;
 	}
 
 	x_e_putc2('\r');
 	x_clrtoeol(' ', false);
 	x_flush();
 	x_mode(false);
-	cp = x_eval_region_helper(evbeg, evend - evbeg);
+	cp = helper(rgbeg, rgend - rgbeg);
 	x_mode(true);
 
 	if (cp == NULL) {
-		/* command cannot be parsed */
+		/* error return from helper */
  x_eval_region_err:
 		x_e_putc2(KSH_BEL);
 		x_redraw('\r');
@@ -5629,20 +5678,49 @@
 	}
 
 	newlen = strlen(cp);
-	restlen = xep - evend;
+	restlen = xep - rgend;
 	/* check for LINE overflow, until this is dynamically allocated */
-	if (evbeg + newlen + restlen >= xend)
+	if (rgbeg + newlen + restlen >= xend)
 		goto x_eval_region_err;
 
-	xmp = evbeg;
-	xcp = evbeg + newlen;
+	xmp = rgbeg;
+	xcp = rgbeg + newlen;
 	xep = xcp + restlen;
-	memmove(xcp, evend, restlen + /* NUL */ 1);
+	memmove(xcp, rgend, restlen + /* NUL */ 1);
 	memcpy(xmp, cp, newlen);
 	afree(cp, AEDIT);
 	x_adjust();
 	x_modified();
 	return (KSTD);
 }
+
+static int
+x_eval_region(int c MKSH_A_UNUSED)
+{
+	return (x_operate_region(x_eval_region_helper));
+}
+
+static char *
+x_quote_region_helper(const char *cmd, size_t len)
+{
+	char *s;
+	size_t newlen;
+	struct shf shf;
+
+	strndupx(s, cmd, len, ATEMP);
+	newlen = len < 256 ? 256 : 4096;
+	shf_sopen(alloc(newlen, AEDIT), newlen, SHF_WR | SHF_DYNAMIC, &shf);
+	shf.areap = AEDIT;
+	shf.flags |= SHF_ALLOCB;
+	print_value_quoted(&shf, s);
+	afree(s, ATEMP);
+	return (shf_sclose(&shf));
+}
+
+static int
+x_quote_region(int c MKSH_A_UNUSED)
+{
+	return (x_operate_region(x_quote_region_helper));
+}
 #endif /* !MKSH_SMALL */
 #endif /* !MKSH_NO_CMDLINE_EDITING */
diff --git a/src/emacsfn.h b/src/emacsfn.h
index a1e8e82..6162987 100644
--- a/src/emacsfn.h
+++ b/src/emacsfn.h
@@ -1,5 +1,5 @@
 /*-
- * Copyright (c) 2009, 2010, 2015, 2016
+ * Copyright (c) 2009, 2010, 2015, 2016, 2020
  *	mirabilos <m@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -19,7 +19,7 @@
  */
 
 #if defined(EMACSFN_DEFNS)
-__RCSID("$MirOS: src/bin/mksh/emacsfn.h,v 1.10 2016/09/01 12:59:09 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/emacsfn.h,v 1.11 2020/04/13 20:46:39 tg Exp $");
 #define FN(cname,sname,flags)	static int x_##cname(int);
 #elif defined(EMACSFN_ENUMS)
 #define FN(cname,sname,flags)	XFUNC_##cname,
@@ -89,6 +89,9 @@
 FN(noop, "no-op", 0)
 FN(prev_com, "up-history", XF_ARG)
 FN(prev_histword, "prev-hist-word", XF_ARG)
+#ifndef MKSH_SMALL
+FN(quote_region, "quote-region", 0)
+#endif
 FN(search_char_back, "search-character-backward", XF_ARG)
 FN(search_char_forw, "search-character-forward", XF_ARG)
 FN(search_hist, "search-history", 0)
diff --git a/src/eval.c b/src/eval.c
index 6aa1844..33a8ea1 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -2,7 +2,8 @@
 
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
- *		 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018
+ *		 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018,
+ *		 2019, 2020
  *	mirabilos <m@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -23,7 +24,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/eval.c,v 1.219 2018/01/14 01:29:47 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/eval.c,v 1.231 2020/05/05 21:34:27 tg Exp $");
 
 /*
  * string expansion
@@ -50,13 +51,17 @@
 	bool split;
 } Expand;
 
-#define	XBASE		0	/* scanning original */
-#define	XSUB		1	/* expanding ${} string */
-#define	XARGSEP		2	/* ifs0 between "$*" */
-#define	XARG		3	/* expanding $*, $@ */
-#define	XCOM		4	/* expanding $() */
-#define XNULLSUB	5	/* "$@" when $# is 0 (don't generate word) */
-#define XSUBMID		6	/* middle of expanding ${} */
+#define XBASE		0	/* scanning original string */
+#define XARGSEP		1	/* ifs0 between "$*" */
+#define XARG		2	/* expanding $*, $@ */
+#define XCOM		3	/* expanding $() */
+#define XNULLSUB	4	/* "$@" when $# is 0, so don't generate word */
+#define XSUB		5	/* expanding ${} string */
+#define XSUBMID		6	/* middle of expanding ${}; must be XSUB+1 */
+#define XSUBPAT		7	/* expanding [[ x = ${} ]] string */
+#define XSUBPATMID	8	/* middle, must be XSUBPAT+1 */
+
+#define isXSUB(t)	((t) == XSUB || (t) == XSUBPAT)
 
 /* States used for field splitting */
 #define IFS_WORD	0	/* word has chars (or quotes except "$@") */
@@ -71,7 +76,7 @@
 #define STYPE_SINGLE	0x2FF
 #define STYPE_MASK	0x300
 
-static int varsub(Expand *, const char *, const char *, int *, int *);
+static int varsub(Expand *, const char *, const char *, unsigned int *, int *);
 static int comsub(Expand *, const char *, int);
 static char *valsub(struct op *, Area *);
 static char *trimsub(char *, char *, int);
@@ -205,7 +210,7 @@
 	struct SubType *prev;	/* old type */
 	struct SubType *next;	/* poped type (to avoid re-allocating) */
 	size_t	base;		/* start position of expanded word */
-	short	stype;		/* [=+-?%#] action after expanded word */
+	unsigned short stype;	/* [=+-?%#] action after expanded word */
 	short	f;		/* saved value of f (DOPAT, etc) */
 	uint8_t	quotep;		/* saved value of quote (for ${..[%#]..}) */
 	uint8_t	quotew;		/* saved value of quote (for ${..[+-=]..}) */
@@ -334,7 +339,7 @@
 						Xcheck(ds, dp);
 						*dp++ = *sp++;
 					}
-					if ((unsigned int)c == ORD('}'))
+					if ((unsigned int)c == ORD(/*{*/'}'))
 						*dp++ = ';';
 					*dp++ = c;
 				} else {
@@ -381,7 +386,7 @@
 			 */
 				/* skip the { or x (}) */
 				const char *varname = ++sp;
-				int stype;
+				unsigned int stype;
 				int slen = 0;
 
 				/* skip variable */
@@ -437,17 +442,8 @@
 						sp += slen;
 					switch (stype & STYPE_SINGLE) {
 					case ORD('#') | STYPE_AT:
-						x.str = shf_smprintf("%08X",
-						    (unsigned int)hash(str_val(st->var)));
+					case ORD('Q') | STYPE_AT:
 						break;
-					case ORD('Q') | STYPE_AT: {
-						struct shf shf;
-
-						shf_sopen(NULL, 0, SHF_WR|SHF_DYNAMIC, &shf);
-						print_value_quoted(&shf, str_val(st->var));
-						x.str = shf_sclose(&shf);
-						break;
-					    }
 					case ORD('0'): {
 						char *beg, *mid, *end, *stg;
 						mksh_ari_t from = 0, num = -1, flen, finc = 0;
@@ -497,10 +493,11 @@
 					    }
 					case ORD('/') | STYPE_AT:
 					case ORD('/'): {
-						char *s, *p, *d, *sbeg, *end;
-						char *pat = NULL, *rrep = null;
+						char *s, *p, *d, *sbeg;
+						char *pat = NULL, *rrep;
 						char fpat = 0, *tpat1, *tpat2;
-						char *ws, *wpat, *wrep;
+						char *ws, *wpat, *wrep, tch;
+						size_t rreplen;
 
 						s = ws = wdcopy(sp, ATEMP);
 						p = s + (wdscan(sp, ADELIM) - sp);
@@ -516,11 +513,17 @@
 						    ctype(s[1], C_SUB2))
 							fpat = s[1];
 						wpat = s + (fpat ? 2 : 0);
-						wrep = d ? p : NULL;
-						if (!(stype & STYPE_AT)) {
-							rrep = wrep ? evalstr(wrep,
-							    DOTILDE | DOSCALAR) :
-							    null;
+						if (!(wrep = d ? p : NULL)) {
+							rrep = null;
+							rreplen = 0;
+						} else if (!(stype & STYPE_AT)) {
+							rrep = evalstr(wrep,
+							    DOTILDE | DOSCALAR);
+							rreplen = strlen(rrep);
+						} else {
+							rrep = NULL;
+							/* shut up GCC */
+							rreplen = 0;
 						}
 
 						/* prepare string on which to work */
@@ -567,17 +570,17 @@
 						 */
 						if (!gmatchx(sbeg, tpat1, false))
 							goto end_repl;
-						end = strnul(s);
+						d = strnul(s);
 						/* now anchor the beginning of the match */
 						if (ord(fpat) != ORD('#'))
-							while (sbeg <= end) {
+							while (sbeg <= d) {
 								if (gmatchx(sbeg, tpat2, false))
 									break;
 								else
 									sbeg++;
 							}
 						/* now anchor the end of the match */
-						p = end;
+						p = d;
 						if (ord(fpat) != ORD('%'))
 							while (p >= sbeg) {
 								bool gotmatch;
@@ -590,22 +593,56 @@
 									break;
 								p--;
 							}
-						strndupx(end, sbeg, p - sbeg, ATEMP);
-						record_match(end);
-						afree(end, ATEMP);
-						if (stype & STYPE_AT) {
-							if (rrep != null)
-								afree(rrep, ATEMP);
-							rrep = wrep ? evalstr(wrep,
-							    DOTILDE | DOSCALAR) :
-							    null;
+
+						/* record partial string as match */
+						tch = *p;
+						*p = '\0';
+						record_match(sbeg);
+						*p = tch;
+						/* get replacement string, if necessary */
+						if ((stype & STYPE_AT) &&
+						    rrep != null) {
+							afree(rrep, ATEMP);
+							/* might access match! */
+							rrep = evalstr(wrep,
+							    DOTILDE | DOSCALAR);
+							rreplen = strlen(rrep);
 						}
-						strndupx(end, s, sbeg - s, ATEMP);
-						d = shf_smprintf(Tf_sss, end, rrep, p);
-						afree(end, ATEMP);
-						sbeg = d + (sbeg - s) + strlen(rrep);
-						afree(s, ATEMP);
-						s = d;
+
+						/*
+						 * string:
+						 * |--------|---------|-------\0
+						 * s  n1    sbeg  n2  p  n3   d
+						 *
+						 * replacement:
+						 *          |------------|
+						 *          rrep  rreplen
+						 */
+
+						/* move strings around and replace */
+						{
+							size_t n1 = sbeg - s;
+							size_t n2 = p - sbeg;
+							size_t n3 = d - p;
+							/* move part3 to the front, OR… */
+							if (rreplen < n2)
+								memmove(sbeg + rreplen,
+								    p, n3 + 1);
+							/* … adjust size, move to back */
+							if (rreplen > n2) {
+								s = aresize(s,
+								    n1 + rreplen + n3 + 1,
+								    ATEMP);
+								memmove(s + n1 + rreplen,
+								    s + n1 + n2,
+								    n3 + 1);
+							}
+							/* insert replacement */
+							if (rreplen)
+								memcpy(s + n1, rrep, rreplen);
+							/* continue after the place */
+							sbeg = s + n1 + rreplen;
+						}
 						if (stype & STYPE_AT) {
 							afree(tpat1, ATEMP);
 							afree(pat, ATEMP);
@@ -769,11 +806,22 @@
 					errorf(Tf_sD_s, st->var->name,
 					    debunk(dp, dp, strlen(dp) + 1));
 					break;
+				case ORD('#') | STYPE_AT:
+					x.str = shf_smprintf("%08X",
+					    (unsigned int)hash(str_val(st->var)));
+					goto common_CSUBST;
+				case ORD('Q') | STYPE_AT: {
+					struct shf shf;
+
+					shf_sopen(NULL, 0, SHF_WR|SHF_DYNAMIC, &shf);
+					print_value_quoted(&shf, str_val(st->var));
+					x.str = shf_sclose(&shf);
+					goto common_CSUBST;
+				    }
 				case ORD('0'):
 				case ORD('/') | STYPE_AT:
 				case ORD('/'):
-				case ORD('#') | STYPE_AT:
-				case ORD('Q') | STYPE_AT:
+ common_CSUBST:
 					dp = Xrestpos(ds, dp, st->base);
 					type = XSUB;
 					word = quote || (!*x.str && (f & DOSCALAR)) ? IFS_WORD : IFS_IWS;
@@ -822,9 +870,70 @@
 			}
 			continue;
 
+		case XSUBPAT:
+		case XSUBPATMID:
+ XSUBPAT_beg:
+			switch ((c = ord(*x.str++))) {
+			case 0:
+				goto XSUB_end;
+			case ORD('\\'):
+				if ((c = ord(*x.str)) == 0)
+					/* keep backslash at EOS */
+					c = ORD('\\');
+				else
+					++x.str;
+				quote |= 2;
+				break;
+			/* ctype(c, C_PATMO) */
+			case ORD('!'):
+			case ORD('*'):
+			case ORD('+'):
+			case ORD('?'):
+			case ORD('@'):
+				if (ord(*x.str) == ORD('('/*)*/)) {
+					++x.str;
+					c |= 0x80U;
+					make_magic = true;
+				}
+				break;
+			case ORD('('):
+				c = ORD(' ') | 0x80U;
+				/* FALLTHROUGH */
+			case ORD('|'):
+			case ORD(')'):
+				make_magic = true;
+				break;
+			}
+			break;
+
 		case XSUB:
+			if (!quote && (f & DODBMAGIC)) {
+				const char *cs = x.str;
+				int level = 0;
+
+				while ((c = *cs++))
+					switch (c) {
+					case '\\':
+						if ((c = *cs))
+							++cs;
+						break;
+					case ORD('('):
+						++level;
+						break;
+					case ORD(')'):
+						--level;
+						break;
+					}
+				/* balanced parentheses? */
+				if (!level) {
+					type = XSUBPAT;
+					goto XSUBPAT_beg;
+				}
+			}
+			/* FALLTHROUGH */
 		case XSUBMID:
 			if ((c = ord(*x.str++)) == 0) {
+ XSUB_end:
 				type = XBASE;
 				if (f & DOBLANK)
 					doblank--;
@@ -980,23 +1089,17 @@
 				Xinit(ds, dp, 128, ATEMP);
 			} else if (c == 0) {
 				return;
-			} else if (type == XSUB && ctype(c, C_IFS) &&
+			} else if (isXSUB(type) && ctype(c, C_IFS) &&
 			    !ctype(c, C_IFSWS) && Xlength(ds, dp) == 0) {
 				*(cp = alloc(1, ATEMP)) = '\0';
 				XPput(*wp, cp);
-				type = XSUBMID;
+				++type;
 			}
 			if (word != IFS_NWS)
 				word = ctype(c, C_IFSWS) ? IFS_WS : IFS_NWS;
 		} else {
-			if (type == XSUB) {
-				if (word == IFS_NWS &&
-				    Xlength(ds, dp) == 0) {
-					*(cp = alloc(1, ATEMP)) = '\0';
-					XPput(*wp, cp);
-				}
-				type = XSUBMID;
-			}
+			if (isXSUB(type))
+				++type;
 
 			/* age tilde_ok info - ~ code tests second bit */
 			tilde_ok <<= 1;
@@ -1111,16 +1214,20 @@
  */
 static int
 varsub(Expand *xp, const char *sp, const char *word,
-    int *stypep,	/* becomes qualifier type */
-    int *slenp)		/* " " len (=, :=, etc.) valid iff *stypep != 0 */
+    /* becomes qualifier type */
+    unsigned int *stypep,
+    /* becomes qualifier type len (=, :=, etc.) valid iff *stypep != 0 */
+    int *slenp)
 {
-	int c;
-	int state;	/* next state: XBASE, XARG, XSUB, XNULLSUB */
-	int stype;	/* substitution type */
+	unsigned int c;
+	int state;		/* next state: XBASE, XARG, XSUB, XNULLSUB */
+	unsigned int stype;	/* substitution type */
 	int slen = 0;
 	const char *p;
 	struct tbl *vp;
 	bool zero_ok = false;
+	int sc;
+	XPtrV wv;
 
 	if ((stype = ord(sp[0])) == '\0')
 		/* Bad variable name */
@@ -1128,108 +1235,166 @@
 
 	xp->var = NULL;
 
+	/* entirety of named array? */
+	if ((p = cstrchr(sp, '[')) && (sc = ord(p[1])) &&
+	    ord(p[2]) == ORD(']'))
+		/* keep p (for ${!foo[1]} below)! */
+		switch (sc) {
+		case ORD('*'):
+			sc = 3;
+			break;
+		case ORD('@'):
+			sc = 7;
+			break;
+		default:
+			/* bit2 = @, bit1 = array, bit0 = enabled */
+			sc = 0;
+		}
+	else
+		/* $* and $@ checked below */
+		sc = 0;
+
 	/*-
-	 * ${#var}, string length (-U: characters, +U: octets) or array size
 	 * ${%var}, string width (-U: screen columns, +U: octets)
+	 * ${#var}, string length (-U: characters, +U: octets) or array size
+	 * ${!var}, variable name
+	 * ${*…} -> set flag for argv
+	 * ${@…} -> set flag for argv
 	 */
-	c = ord(sp[1]);
-	if ((unsigned int)stype == ORD('%') && c == '\0')
-		return (-1);
-	if (ctype(stype, C_SUB2) && c != '\0') {
-		/* Can't have any modifiers for ${#...} or ${%...} */
+	if (ctype(stype, C_SUB2 | CiVAR1)) {
+		switch (stype) {
+		case ORD('*'):
+			if (!sc)
+				sc = 1;
+			goto nopfx;
+		case ORD('@'):
+			if (!sc)
+				sc = 5;
+			goto nopfx;
+		}
+		/* varname required */
+		if ((c = ord(sp[1])) == '\0') {
+			if (stype == ORD('%'))
+				/* $% */
+				return (-1);
+			/* $# or $! */
+			goto nopfx;
+		}
+		/* can’t have any modifiers for ${#…} or ${%…} or ${!…} */
 		if (*word != CSUBST)
 			return (-1);
-		sp++;
-		/* Check for size of array */
-		if ((p = cstrchr(sp, '[')) && (ord(p[1]) == ORD('*') ||
-		    ord(p[1]) == ORD('@')) && ord(p[2]) == ORD(']')) {
-			int n = 0;
-
-			if ((unsigned int)stype != ORD('#'))
+		/* check for argv past prefix */
+		if (!sc) switch (c) {
+		case ORD('*'):
+			sc = 1;
+			break;
+		case ORD('@'):
+			sc = 5;
+			break;
+		}
+		/* skip past prefix */
+		++sp;
+		/* determine result */
+		switch (stype) {
+		case ORD('!'):
+			if (sc & 2) {
+				stype = 0;
+				XPinit(wv, 32);
+				vp = global(arrayname(sp));
+				do {
+					if (vp->flag & ISSET)
+						XPput(wv, shf_smprintf(Tf_lu,
+						    arrayindex(vp)));
+				} while ((vp = vp->u.array));
+				goto arraynames;
+			}
+			xp->var = global(sp);
+			/* use saved p from above */
+			xp->str = p ? shf_smprintf("%s[%lu]", xp->var->name,
+			    arrayindex(xp->var)) : xp->var->name;
+			break;
+#ifdef DEBUG
+		default:
+			internal_errorf("stype mismatch");
+			/* NOTREACHED */
+#endif
+		case ORD('%'):
+			/* cannot do this on an array */
+			if (sc)
 				return (-1);
-			vp = global(arrayname(sp));
-			if (vp->flag & (ISSET|ARRAY))
-				zero_ok = true;
-			for (; vp; vp = vp->u.array)
-				if (vp->flag & ISSET)
-					n++;
-			c = n;
-		} else if ((unsigned int)c == ORD('*') ||
-		    (unsigned int)c == ORD('@')) {
-			if ((unsigned int)stype != ORD('#'))
-				return (-1);
-			c = e->loc->argc;
-		} else {
 			p = str_val(global(sp));
 			zero_ok = p != null;
-			if ((unsigned int)stype == ORD('#'))
-				c = utflen(p);
-			else {
-				/* partial utf_mbswidth reimplementation */
-				const char *s = p;
-				unsigned int wc;
-				size_t len;
-				int cw;
-
-				c = 0;
-				while (*s) {
-					if (!UTFMODE || (len = utf_mbtowc(&wc,
-					    s)) == (size_t)-1)
-						/* not UTFMODE or not UTF-8 */
-						wc = rtt2asc(*s++);
-					else
-						/* UTFMODE and UTF-8 */
-						s += len;
-					/* wc == char or wchar at s++ */
-					if ((cw = utf_wcwidth(wc)) == -1) {
-						/* 646, 8859-1, 10646 C0/C1 */
-						c = -1;
-						break;
-					}
-					c += cw;
+			/* partial utf_mbswidth reimplementation */
+			sc = 0;
+			while (*p) {
+				if (!UTFMODE ||
+				    (wv.len = utf_mbtowc(&c, p)) == (size_t)-1)
+					/* not UTFMODE or not UTF-8 */
+					c = rtt2asc(*p++);
+				else
+					/* UTFMODE and UTF-8 */
+					p += wv.len;
+				/* c == char or wchar at p++ */
+				if ((slen = utf_wcwidth(c)) == -1) {
+					/* 646, 8859-1, 10646 C0/C1 */
+					sc = -1;
+					break;
 				}
+				sc += slen;
 			}
+			if (0)
+				/* FALLTHROUGH */
+		case ORD('#'):
+			  switch (sc & 3) {
+			case 3:
+				vp = global(arrayname(sp));
+				if (vp->flag & (ISSET|ARRAY))
+					zero_ok = true;
+				sc = 0;
+				do {
+					if (vp->flag & ISSET)
+						sc++;
+				} while ((vp = vp->u.array));
+				break;
+			case 1:
+				sc = e->loc->argc;
+				break;
+			default:
+				p = str_val(global(sp));
+				zero_ok = p != null;
+				sc = utflen(p);
+				break;
+			}
+			/* ${%var} also here */
+			if (Flag(FNOUNSET) && sc == 0 && !zero_ok)
+				errorf(Tf_parm, sp);
+			xp->str = shf_smprintf(Tf_d, sc);
+			break;
 		}
-		if (Flag(FNOUNSET) && c == 0 && !zero_ok)
-			errorf(Tf_parm, sp);
 		/* unqualified variable/string substitution */
 		*stypep = 0;
-		xp->str = shf_smprintf(Tf_d, c);
 		return (XSUB);
 	}
-	if ((unsigned int)stype == ORD('!') && c != '\0' && *word == CSUBST) {
-		sp++;
-		if ((p = cstrchr(sp, '[')) && (ord(p[1]) == ORD('*') ||
-		    ord(p[1]) == ORD('@')) && ord(p[2]) == ORD(']')) {
-			c = ORD('!');
-			stype = 0;
-			goto arraynames;
-		}
-		xp->var = global(sp);
-		xp->str = p ? shf_smprintf("%s[%lu]",
-		    xp->var->name, arrayindex(xp->var)) : xp->var->name;
-		*stypep = 0;
-		return (XSUB);
-	}
+ nopfx:
 
-	/* Check for qualifiers in word part */
+	/* check for qualifiers in word part */
 	stype = 0;
-	c = word[slen + 0] == CHAR ? ord(word[slen + 1]) : 0;
-	if ((unsigned int)c == ORD(':')) {
+	/*slen = 0;*/
+	c = word[/*slen +*/ 0] == CHAR ? ord(word[/*slen +*/ 1]) : 0;
+	if (c == ORD(':')) {
 		slen += 2;
 		stype = STYPE_DBL;
 		c = word[slen + 0] == CHAR ? ord(word[slen + 1]) : 0;
 	}
-	if (!stype && (unsigned int)c == ORD('/')) {
+	if (!stype && c == ORD('/')) {
 		slen += 2;
 		stype = c;
 		if (word[slen] == ADELIM &&
-		    ord(word[slen + 1]) == (unsigned int)c) {
+		    ord(word[slen + 1]) == c) {
 			slen += 2;
 			stype |= STYPE_DBL;
 		}
-	} else if (stype == STYPE_DBL && ((unsigned int)c == ORD(' ') ||
-	    (unsigned int)c == ORD('0'))) {
+	} else if (stype == STYPE_DBL && (c == ORD(' ') || c == ORD('0'))) {
 		stype |= ORD('0');
 	} else if (ctype(c, C_SUB1)) {
 		slen += 2;
@@ -1238,12 +1403,11 @@
 		/* Note: ksh88 allows :%, :%%, etc */
 		slen += 2;
 		stype = c;
-		if (word[slen + 0] == CHAR &&
-		    ord(word[slen + 1]) == (unsigned int)c) {
+		if (word[slen + 0] == CHAR && ord(word[slen + 1]) == c) {
 			stype |= STYPE_DBL;
 			slen += 2;
 		}
-	} else if ((unsigned int)c == ORD('@')) {
+	} else if (c == ORD('@')) {
 		/* @x where x is command char */
 		switch (c = ord(word[slen + 2]) == CHAR ?
 		    ord(word[slen + 3]) : 0) {
@@ -1262,84 +1426,70 @@
 	if (!stype && *word != CSUBST)
 		return (-1);
 
-	c = ord(sp[0]);
-	if ((unsigned int)c == ORD('*') || (unsigned int)c == ORD('@')) {
-		switch (stype & STYPE_SINGLE) {
-		/* can't assign to a vector */
-		case ORD('='):
-		/* can't trim a vector (yet) */
-		case ORD('%'):
-		case ORD('#'):
-		case ORD('?'):
-		case ORD('0'):
-		case ORD('/') | STYPE_AT:
-		case ORD('/'):
-		case ORD('#') | STYPE_AT:
-		case ORD('Q') | STYPE_AT:
-			return (-1);
-		}
-		if (e->loc->argc == 0) {
-			xp->str = null;
-			xp->var = global(sp);
-			state = (unsigned int)c == ORD('@') ? XNULLSUB : XSUB;
-		} else {
-			xp->u.strv = (const char **)e->loc->argv + 1;
-			xp->str = *xp->u.strv++;
-			/* $@ */
-			xp->split = tobool((unsigned int)c == ORD('@'));
-			state = XARG;
-		}
-		/* POSIX 2009? */
-		zero_ok = true;
-	} else if ((p = cstrchr(sp, '[')) && (ord(p[1]) == ORD('*') ||
-	    ord(p[1]) == ORD('@')) && ord(p[2]) == ORD(']')) {
-		XPtrV wv;
-
-		switch (stype & STYPE_SINGLE) {
-		/* can't assign to a vector */
-		case ORD('='):
-		/* can't trim a vector (yet) */
-		case ORD('%'):
-		case ORD('#'):
-		case ORD('?'):
-		case ORD('0'):
-		case ORD('/') | STYPE_AT:
-		case ORD('/'):
-		case ORD('#') | STYPE_AT:
-		case ORD('Q') | STYPE_AT:
-			return (-1);
-		}
-		c = 0;
- arraynames:
-		XPinit(wv, 32);
-		vp = global(arrayname(sp));
-		for (; vp; vp = vp->u.array) {
-			if (!(vp->flag&ISSET))
-				continue;
-			XPput(wv, (unsigned int)c == ORD('!') ?
-			    shf_smprintf(Tf_lu, arrayindex(vp)) :
-			    str_val(vp));
-		}
-		if (XPsize(wv) == 0) {
-			xp->str = null;
-			state = ord(p[1]) == ORD('@') ? XNULLSUB : XSUB;
-			XPfree(wv);
-		} else {
-			XPput(wv, 0);
-			xp->u.strv = (const char **)XPptrv(wv);
-			xp->str = *xp->u.strv++;
-			/* ${foo[@]} */
-			xp->split = tobool(ord(p[1]) == ORD('@'));
-			state = XARG;
-		}
-	} else {
+	if (!sc) {
 		xp->var = global(sp);
 		xp->str = str_val(xp->var);
 		/* can't assign things like $! or $1 */
-		if ((unsigned int)(stype & STYPE_SINGLE) == ORD('=') &&
+		if ((stype & STYPE_SINGLE) == ORD('=') &&
 		    !*xp->str && ctype(*sp, C_VAR1 | C_DIGIT))
 			return (-1);
 		state = XSUB;
+	} else {
+		/* can’t assign/trim a vector (yet) */
+		switch (stype & STYPE_SINGLE) {
+		case ORD('-'):
+		case ORD('+'):
+			/* allowed ops */
+		case 0:
+			/* or no ops */
+			break;
+	/*	case ORD('='):
+		case ORD('?'):
+		case ORD('#'):
+		case ORD('%'):
+		case ORD('/'):
+		case ORD('/') | STYPE_AT:
+		case ORD('0'):
+		case ORD('#') | STYPE_AT:
+		case ORD('Q') | STYPE_AT:
+	*/	default:
+			return (-1);
+		}
+		/* do what we can */
+		if (sc & 2) {
+			XPinit(wv, 32);
+			vp = global(arrayname(sp));
+			do {
+				if (vp->flag & ISSET)
+					XPput(wv, str_val(vp));
+			} while ((vp = vp->u.array));
+ arraynames:
+			if ((c = (XPsize(wv) == 0)))
+				XPfree(wv);
+			else {
+				XPput(wv, NULL);
+				xp->u.strv = (const char **)XPptrv(wv);
+			}
+		} else {
+			if ((c = (e->loc->argc == 0)))
+				xp->var = global(sp);
+			else
+				xp->u.strv = (const char **)e->loc->argv + 1;
+			/* POSIX 2009? */
+			zero_ok = true;
+		}
+		/* have we got any elements? */
+		if (c) {
+			/* no */
+			xp->str = null;
+			state = sc & 4 ? XNULLSUB : XSUB;
+		} else {
+			/* yes → load first */
+			xp->str = *xp->u.strv++;
+			/* $@ or ${foo[@]} */
+			xp->split = tobool(sc & 4);
+			state = XARG;
+		}
 	}
 
 	c = stype & STYPE_CHAR;
@@ -1348,15 +1498,15 @@
 	    (((stype & STYPE_DBL) ? *xp->str == '\0' : xp->str == null) &&
 	    (state != XARG || (ifs0 || xp->split ?
 	    (xp->u.strv[0] == NULL) : !hasnonempty(xp->u.strv))) ?
-	    ctype(c, C_EQUAL | C_MINUS | C_QUEST) : (unsigned int)c == ORD('+')))) ||
-	    (unsigned int)stype == (ORD('0') | STYPE_DBL) ||
-	    (unsigned int)stype == (ORD('#') | STYPE_AT) ||
-	    (unsigned int)stype == (ORD('Q') | STYPE_AT) ||
-	    (unsigned int)(stype & STYPE_CHAR) == ORD('/'))
+	    ctype(c, C_EQUAL | C_MINUS | C_QUEST) : c == ORD('+')))) ||
+	    stype == (ORD('0') | STYPE_DBL) ||
+	    stype == (ORD('#') | STYPE_AT) ||
+	    stype == (ORD('Q') | STYPE_AT) ||
+	    (stype & STYPE_CHAR) == ORD('/'))
 		/* expand word instead of variable value */
 		state = XBASE;
 	if (Flag(FNOUNSET) && xp->str == null && !zero_ok &&
-	    (ctype(c, C_SUB2) || (state != XBASE && (unsigned int)c != ORD('+'))))
+	    (ctype(c, C_SUB2) || (state != XBASE && c != ORD('+'))))
 		errorf(Tf_parm, sp);
 	*stypep = stype;
 	*slenp = slen;
@@ -1950,7 +2100,7 @@
 	newenv(E_FUNC);
 	newblock();
 	if (ap)
-		vp = local("REPLY", false);
+		vp = local(TREPLY, false);
 	if (!kshsetjmp(e->jbuf))
 		execute(t, XXCOM | XERROK, NULL);
 	if (vp)
diff --git a/src/exec.c b/src/exec.c
index 8231b54..9d1b69b 100644
--- a/src/exec.c
+++ b/src/exec.c
@@ -3,7 +3,7 @@
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
  *		 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018,
- *		 2019
+ *		 2019, 2020
  *	mirabilos <m@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -24,7 +24,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/exec.c,v 1.206 2019/03/01 16:17:53 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/exec.c,v 1.223 2020/04/07 23:14:41 tg Exp $");
 
 #ifndef MKSH_DEFAULT_EXECSHELL
 #define MKSH_DEFAULT_EXECSHELL	MKSH_UNIXROOT "/bin/sh"
@@ -108,8 +108,8 @@
 
 			if ((rv = herein(t->ioact[0], &cp) /*? 1 : 0*/))
 				cp = NULL;
-			dp = shf_smprintf(Tf_ss, evalstr(t->vars[0],
-			    DOASNTILDE | DOSCALAR), rv ? null : cp);
+			strdup2x(dp, evalstr(t->vars[0], DOASNTILDE | DOSCALAR),
+			    rv ? null : cp);
 			typeset(dp, Flag(FEXPORT) ? EXPORT : 0, 0, 0, 0);
 			/* free the expanded value */
 			afree(cp, APERM);
@@ -127,7 +127,7 @@
 			timex_hook(t, &up);
 		ap = (const char **)up;
 		if (ap[0])
-			tp = findcom(ap[0], FC_BI|FC_FUNC);
+			tp = findcom(ap[0], FC_BI | FC_FUNC);
 	}
 	flags &= ~XTIME;
 
@@ -446,7 +446,7 @@
 		if (rv == ENOEXEC)
 			scriptexec(t, (const char **)up);
 		else
-			errorf(Tf_sD_s, t->str, cstrerror(rv));
+			errorfx(126, Tf_sD_s, t->str, cstrerror(rv));
 	}
  Break:
 	exstat = rv & 0xFF;
@@ -464,14 +464,9 @@
 		unwind(LEXIT);
 	if (rv != 0 && !(flags & XERROK) &&
 	    (xerrok == NULL || !*xerrok)) {
-		if (Flag(FERREXIT) & 0x80) {
-			/* inside eval */
-			Flag(FERREXIT) = 0;
-		} else {
-			trapsig(ksh_SIGERR);
-			if (Flag(FERREXIT))
-				unwind(LERROR);
-		}
+		trapsig(ksh_SIGERR);
+		if (Flag(FERREXIT))
+			unwind(LERREXT);
 	}
 	return (rv);
 }
@@ -492,7 +487,7 @@
 	static struct op texec;
 	int type_flags;
 	bool resetspec;
-	int fcflags = FC_BI|FC_FUNC|FC_PATH;
+	int fcflags = FC_BI | FC_FUNC | FC_PATH;
 	struct block *l_expand, *l_assign;
 	int optc;
 	const char *exec_argv0 = NULL;
@@ -529,7 +524,7 @@
 	resetspec = false;
 	while (tp && tp->type == CSHELL) {
 		/* undo effects of command */
-		fcflags = FC_BI|FC_FUNC|FC_PATH;
+		fcflags = FC_BI | FC_FUNC | FC_PATH;
 		if (tp->val.f == c_builtin) {
 			if ((cp = *++ap) == NULL ||
 			    (!strcmp(cp, "--") && (cp = *++ap) == NULL)) {
@@ -578,7 +573,7 @@
 				/* command -vV or something */
 				break;
 			/* don't look for functions */
-			fcflags = FC_BI|FC_PATH;
+			fcflags = FC_BI | FC_PATH;
 			if (saw_p) {
 				if (Flag(FRESTRICTED)) {
 					warningf(true, Tf_sD_s,
@@ -608,8 +603,8 @@
 			    (tp->flag & LOWER_BI)) {
 				struct tbl *ext_cmd;
 
-				ext_cmd = findcom(tp->name, FC_PATH | FC_FUNC);
-				if (ext_cmd && (ext_cmd->type != CTALIAS ||
+				ext_cmd = findcom(tp->name, FC_FUNC | FC_PATH);
+				if (ext_cmd && (ext_cmd->type == CFUNC ||
 				    (ext_cmd->flag & ISSET)))
 					tp = ext_cmd;
 			}
@@ -619,7 +614,7 @@
 			break;
 		} else
 			break;
-		tp = findcom(ap[0], fcflags & (FC_BI|FC_FUNC));
+		tp = findcom(ap[0], fcflags & (FC_BI | FC_FUNC));
 	}
 	if (t->u.evalflags & DOTCOMEXEC)
 		flags |= XEXEC;
@@ -629,12 +624,8 @@
 	else {
 		/* create new variable/function block */
 		newblock();
-		/* ksh functions don't keep assignments, POSIX functions do. */
-		if (!resetspec && tp && tp->type == CFUNC &&
-		    !(tp->flag & FKSH))
-			type_flags = EXPORT;
-		else
-			type_flags = LOCAL|LOCAL_COPY|EXPORT;
+		/* all functions keep assignments */
+		type_flags = LOCAL | LOCAL_COPY | EXPORT;
 	}
 	l_assign = e->loc;
 	if (exec_clrenv)
@@ -694,11 +685,9 @@
 	/* shell built-in */
 	case CSHELL:
  do_call_builtin:
+		if (l_expand != l_assign)
+			l_assign->flags |= (tp->flag & NEXTLOC_BI);
 		rv = call_builtin(tp, (const char **)ap, null, resetspec);
-		if (resetspec && tp->val.f == c_shift) {
-			l_expand->argc = l_assign->argc;
-			l_expand->argv = l_assign->argv;
-		}
 		break;
 
 	/* function call */
@@ -804,6 +793,7 @@
 		switch (i) {
 		case LRETURN:
 		case LERROR:
+		case LERREXT:
 			rv = exstat & 0xFF;
 			break;
 		case LINTR:
@@ -1147,6 +1137,10 @@
 		/* is declaration utility (POSIX: export, readonly) */
 		flag |= DECL_UTIL;
 		break;
+	case '#':
+		/* is set or shift */
+		flag |= NEXTLOC_BI;
+		break;
 	default:
 		goto flags_seen;
 	}
@@ -1309,10 +1303,10 @@
 }
 
 #ifdef __OS2__
-/* check if path is something we want to find, adding executable extensions */
-#define search_access(fn, mode)	access_ex((search_access), (fn), (mode))
+/* check if path is something we want to find adding executable extensions */
+#define search_access(fn,mode)	access_ex((search_access), (fn), (mode))
 #else
-#define search_access(fn, mode)	(search_access)((fn), (mode))
+#define search_access(fn,mode)	(search_access)((fn), (mode))
 #endif
 
 /*
@@ -1605,7 +1599,7 @@
 		return (-2);
 	}
 	if (iop->ioflag & IOHERESTR) {
-		ccp = evalstr(iop->delim, DOHERESTR | DOSCALAR | DOHEREDOC);
+		ccp = evalstr(iop->delim, DOHERESTR | DOSCALAR);
 	} else if (sub) {
 		/* do substitutions on the content of heredoc */
 		s = pushs(SSTRING, ATEMP);
@@ -1682,7 +1676,7 @@
 do_selectargs(const char **ap, bool print_menu)
 {
 	static const char *read_args[] = {
-		Tread, "-r", "REPLY", NULL
+		Tread, Tdr, TREPLY, NULL
 	};
 	char *s;
 	int i, argct;
@@ -1696,13 +1690,13 @@
 		 *	- the user enters a blank line
 		 *	- the REPLY parameter is empty
 		 */
-		if (print_menu || !*str_val(global("REPLY")))
+		if (print_menu || !*str_val(global(TREPLY)))
 			pr_menu(ap);
 		shellf(Tf_s, str_val(global("PS3")));
 		if (call_builtin(findcom(Tread, FC_BI), read_args, Tselect,
 		    false))
 			return (NULL);
-		if (*(s = str_val(global("REPLY"))))
+		if (*(s = str_val(global(TREPLY))))
 			return ((getn(s, &i) && i >= 1 && i <= argct) ?
 			    ap[i - 1] : null);
 		print_menu = true;
@@ -1861,8 +1855,11 @@
 	if (!do_eval)
 		return (null);
 
-	if (op == TO_STEQL || op == TO_STNEQ)
+	if (op == TO_STEQL || op == TO_STNEQ) {
 		flags |= DOPAT;
+		if (!Flag(FSH))
+			flags |= DODBMAGIC;
+	}
 
 	return (evalstr(s, flags));
 }
diff --git a/src/expr.c b/src/expr.c
index 5d2c869..6764279 100644
--- a/src/expr.c
+++ b/src/expr.c
@@ -2,7 +2,7 @@
 
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
- *		 2011, 2012, 2013, 2014, 2016, 2017, 2018
+ *		 2011, 2012, 2013, 2014, 2016, 2017, 2018, 2019
  *	mirabilos <m@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -23,7 +23,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/expr.c,v 1.105 2018/08/10 02:53:33 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/expr.c,v 1.107 2020/03/27 02:49:40 tg Exp $");
 
 #define EXPRTOK_DEFNS
 #include "exprtok.h"
@@ -864,7 +864,8 @@
 	int rv;
 	struct stat sb;
 
-	if ((rv = access(fn, mode)) == 0 && kshuid == 0 && (mode & X_OK) &&
+	if ((rv = access(fn, mode)) == 0 && (mode & X_OK) &&
+	    (kshuid == 0 || ksheuid == 0) &&
 	    (rv = stat(fn, &sb)) == 0 && !S_ISDIR(sb.st_mode) &&
 	    (sb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) == 0)
 		rv = -1;
@@ -885,8 +886,43 @@
     unsigned int val) MKSH_A_PURE;
 
 /*
- * Generated from the UCD 11.0.0 by
- * MirOS: contrib/code/Snippets/eawparse,v 1.12 2017/09/06 16:05:45 tg Exp $
+ * Generated from the UCD 13.0.0 by
+ * MirOS: contrib/code/Snippets/eawparse,v 1.14 2020/03/27 01:33:21 tg Exp $
+ */
+
+/*-
+ * Parts Copyright © 1991–2020 Unicode, Inc. All rights reserved.
+ * Distributed under the Terms of Use in
+ *     https://www.unicode.org/copyright.html.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of the Unicode data files and any associated documentation
+ * (the "Data Files") or Unicode software and any associated documentation
+ * (the "Software") to deal in the Data Files or Software
+ * without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, and/or sell copies of
+ * the Data Files or Software, and to permit persons to whom the Data Files
+ * or Software are furnished to do so, provided that either
+ * (a) this copyright and permission notice appear with all copies
+ * of the Data Files or Software, or
+ * (b) this copyright and permission notice appear in associated
+ * Documentation.
+ *
+ * THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF
+ * ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS
+ * NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL
+ * DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THE DATA FILES OR SOFTWARE.
+ *
+ * Except as contained in this notice, the name of a copyright holder
+ * shall not be used in advertising or otherwise to promote the sale,
+ * use or other dealings in these Data Files or Software without prior
+ * written authorization of the copyright holder.
  */
 
 static const struct mb_ucsrange mb_ucs_combining[] = {
@@ -949,7 +985,7 @@
 	{ 0x0B3F, 0x0B3F },
 	{ 0x0B41, 0x0B44 },
 	{ 0x0B4D, 0x0B4D },
-	{ 0x0B56, 0x0B56 },
+	{ 0x0B55, 0x0B56 },
 	{ 0x0B62, 0x0B63 },
 	{ 0x0B82, 0x0B82 },
 	{ 0x0BC0, 0x0BC0 },
@@ -972,6 +1008,7 @@
 	{ 0x0D41, 0x0D44 },
 	{ 0x0D4D, 0x0D4D },
 	{ 0x0D62, 0x0D63 },
+	{ 0x0D81, 0x0D81 },
 	{ 0x0DCA, 0x0DCA },
 	{ 0x0DD2, 0x0DD4 },
 	{ 0x0DD6, 0x0DD6 },
@@ -979,8 +1016,7 @@
 	{ 0x0E34, 0x0E3A },
 	{ 0x0E47, 0x0E4E },
 	{ 0x0EB1, 0x0EB1 },
-	{ 0x0EB4, 0x0EB9 },
-	{ 0x0EBB, 0x0EBC },
+	{ 0x0EB4, 0x0EBC },
 	{ 0x0EC8, 0x0ECD },
 	{ 0x0F18, 0x0F19 },
 	{ 0x0F35, 0x0F35 },
@@ -1030,7 +1066,7 @@
 	{ 0x1A65, 0x1A6C },
 	{ 0x1A73, 0x1A7C },
 	{ 0x1A7F, 0x1A7F },
-	{ 0x1AB0, 0x1ABE },
+	{ 0x1AB0, 0x1AC0 },
 	{ 0x1B00, 0x1B03 },
 	{ 0x1B34, 0x1B34 },
 	{ 0x1B36, 0x1B3A },
@@ -1073,6 +1109,7 @@
 	{ 0xA806, 0xA806 },
 	{ 0xA80B, 0xA80B },
 	{ 0xA825, 0xA826 },
+	{ 0xA82C, 0xA82C },
 	{ 0xA8C4, 0xA8C5 },
 	{ 0xA8E0, 0xA8F1 },
 	{ 0xA8FF, 0xA8FF },
@@ -1081,7 +1118,7 @@
 	{ 0xA980, 0xA982 },
 	{ 0xA9B3, 0xA9B3 },
 	{ 0xA9B6, 0xA9B9 },
-	{ 0xA9BC, 0xA9BC },
+	{ 0xA9BC, 0xA9BD },
 	{ 0xA9E5, 0xA9E5 },
 	{ 0xAA29, 0xAA2E },
 	{ 0xAA31, 0xAA32 },
diff --git a/src/exprtok.h b/src/exprtok.h
index e4329da..e98e51f 100644
--- a/src/exprtok.h
+++ b/src/exprtok.h
@@ -1,5 +1,5 @@
 /*-
- * Copyright (c) 2016
+ * Copyright (c) 2016, 2020
  *	mirabilos <m@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -19,25 +19,25 @@
  */
 
 #if defined(EXPRTOK_DEFNS)
-__RCSID("$MirOS: src/bin/mksh/exprtok.h,v 1.2 2016/08/12 16:48:05 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/exprtok.h,v 1.4 2020/04/07 11:56:46 tg Exp $");
 /* see range comment below */
 #define IS_ASSIGNOP(op) ((int)(op) >= (int)O_ASN && (int)(op) <= (int)O_BORASN)
-#define FN(name, len, prec, enum)	/* nothing */
+#define FN(name,len,prec,enum)		/* nothing */
 #define F1(enum)			/* nothing */
 #elif defined(EXPRTOK_ENUM)
-#define F0(name, len, prec, enum)	enum = 0,
-#define FN(name, len, prec, enum)	enum,
+#define F0(name,len,prec,enum)		enum = 0,
+#define FN(name,len,prec,enum)		enum,
 #define F1(enum)			enum,
 #define F2(enum)			enum,
 #define F9(enum)			enum
 #elif defined(EXPRTOK_NAME)
-#define FN(name, len, prec, enum)	name,
+#define FN(name,len,prec,enum)		name,
 #define F1(enum)			""
 #elif defined(EXPRTOK_LEN)
-#define FN(name, len, prec, enum)	len,
+#define FN(name,len,prec,enum)		len,
 #define F1(enum)			0
 #elif defined(EXPRTOK_PREC)
-#define FN(name, len, prec, enum)	prec,
+#define FN(name,len,prec,enum)		prec,
 #define F1(enum)			P_PRIMARY
 #endif
 
@@ -53,7 +53,7 @@
 /* tokens must be ordered so the longest are first (e.g. += before +) */
 
 /* some (long) unary operators */
-FN("++", 2, P_PRIMARY, O_PLUSPLUS = 0)	/* before + */
+F0("++", 2, P_PRIMARY, O_PLUSPLUS)	/* before + */
 FN("--", 2, P_PRIMARY, O_MINUSMINUS)	/* before - */
 /* binary operators */
 FN("==", 2, P_EQUALITY, O_EQ)		/* before = */
diff --git a/src/funcs.c b/src/funcs.c
index ade00d6..d3427d0 100644
--- a/src/funcs.c
+++ b/src/funcs.c
@@ -5,7 +5,8 @@
 
 /*-
  * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
- *		 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017
+ *		 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017,
+ *		 2019, 2020
  *	mirabilos <m@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -38,7 +39,7 @@
 #endif
 #endif
 
-__RCSID("$MirOS: src/bin/mksh/funcs.c,v 1.355 2018/10/20 21:04:28 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/funcs.c,v 1.373 2020/05/16 22:38:21 tg Exp $");
 
 #if HAVE_KILLPG
 /*
@@ -119,8 +120,6 @@
 	{Tfalse, c_false},
 	{"fc", c_fc},
 	{Tgetopts, c_getopts},
-	/* deprecated, replaced by typeset -g */
-	{"^=global", c_typeset},
 	{Tjobs, c_jobs},
 	{"kill", c_kill},
 	{"let", c_let},
@@ -133,8 +132,8 @@
 #endif
 	{"~rename", c_rename},
 	{"*=return", c_exitreturn},
-	{Tsgset, c_set},
-	{"*=shift", c_shift},
+	{Tsghset, c_set},
+	{"*=#shift", c_shift},
 	{Tgsource, c_dot},
 #if !defined(MKSH_UNEMPLOYED) && HAVE_GETSID
 	{Tsuspend, c_suspend},
@@ -183,11 +182,8 @@
 	int name_width;
 };
 
-static const struct t_op {
-	char op_text[4];
-	Test_op op_num;
-} u_ops[] = {
-	{"-a",	TO_FILAXST },
+const struct t_op u_ops[] = {
+/* 0*/	{"-a",	TO_FILAXST },
 	{"-b",	TO_FILBDEV },
 	{"-c",	TO_FILCDEV },
 	{"-d",	TO_FILID },
@@ -199,22 +195,23 @@
 	{"-h",	TO_FILSYM },
 	{"-k",	TO_FILSTCK },
 	{"-L",	TO_FILSYM },
-	{"-n",	TO_STNZE },
+/*12*/	{"-n",	TO_STNZE },
 	{"-O",	TO_FILUID },
-	{"-o",	TO_OPTION },
+/*14*/	{"-o",	TO_OPTION },
 	{"-p",	TO_FILFIFO },
-	{"-r",	TO_FILRD },
+/*16*/	{"-r",	TO_FILRD },
 	{"-S",	TO_FILSOCK },
 	{"-s",	TO_FILGZ },
 	{"-t",	TO_FILTT },
-	{"-u",	TO_FILSETU },
+/*20*/	{"-u",	TO_FILSETU },
 	{"-v",	TO_ISSET },
 	{"-w",	TO_FILWR },
-	{"-x",	TO_FILEX },
+/*23*/	{"-x",	TO_FILEX },
 	{"-z",	TO_STZER },
 	{"",	TO_NONOP }
 };
-static const struct t_op b_ops[] = {
+cta(u_ops_size, NELEM(u_ops) == 26);
+const struct t_op b_ops[] = {
 	{"=",	TO_STEQL },
 	{"==",	TO_STEQL },
 	{"!=",	TO_STNEQ },
@@ -333,11 +330,11 @@
 #ifndef MKSH_MIDNIGHTBSD01ASH_COMPAT
 		    Flag(FSH) ||
 #endif
-		    Flag(FAS_BUILTIN)) {
+		    as_builtin) {
 			/* BSD "echo" cmd, Debian Policy 10.4 compliant */
 			++wp;
  bsd_echo:
-			if (*wp && !strcmp(*wp, "-n")) {
+			if (*wp && !strcmp(*wp, Tdn)) {
 				po.nl = false;
 				++wp;
 			}
@@ -724,12 +721,16 @@
 			}
 			break;
 		case CALIAS:
-			if (vflag) {
-				shprintf("%s is an %s%s for ", id,
+			if (!vflag && iscommand)
+				shprintf(Tf_s_, Talias);
+			if (vflag || iscommand)
+				print_value_quoted(shl_stdout, id);
+			if (vflag)
+				shprintf(" is an %s%s for ",
 				    (tp->flag & EXPORT) ? "exported " : "",
 				    Talias);
-			} else if (iscommand)
-				shprintf("%s %s=", Talias, id);
+			else if (iscommand)
+				shf_putc('=', shl_stdout);
 			print_value_quoted(shl_stdout, tp->val.s);
 			break;
 		case CKEYWD:
@@ -751,10 +752,15 @@
 bool
 valid_alias_name(const char *cp)
 {
-	if (ord(*cp) == ORD('-'))
+	switch (ord(*cp)) {
+	case ORD('+'):
+	case ORD('-'):
 		return (false);
-	if (ord(cp[0]) == ORD('[') && ord(cp[1]) == ORD('[') && !cp[2])
-		return (false);
+	case ORD('['):
+		if (ord(cp[1]) == ORD('[') && !cp[2])
+			return (false);
+		break;
+	}
 	while (*cp)
 		if (ctype(*cp, C_ALIAS))
 			++cp;
@@ -843,7 +849,7 @@
 			if ((ap->flag & (ISSET|xflag)) == (ISSET|xflag)) {
 				if (pflag)
 					shprintf(Tf_s_, Talias);
-				shf_puts(ap->name, shl_stdout);
+				print_value_quoted(shl_stdout, ap->name);
 				if (prefix != '+') {
 					shf_putc('=', shl_stdout);
 					print_value_quoted(shl_stdout, ap->val.s);
@@ -873,7 +879,7 @@
 			if (ap != NULL && (ap->flag&ISSET)) {
 				if (pflag)
 					shprintf(Tf_s_, Talias);
-				shf_puts(ap->name, shl_stdout);
+				print_value_quoted(shl_stdout, ap->name);
 				if (prefix != '+') {
 					shf_putc('=', shl_stdout);
 					print_value_quoted(shl_stdout, ap->val.s);
@@ -1297,54 +1303,32 @@
 #ifndef MKSH_SMALL
 	bool macro = false;
 #endif
-	bool list = false;
-	const char *cp;
-	char *up;
 
-	while ((optc = ksh_getopt(wp, &builtin_opt,
-#ifndef MKSH_SMALL
-	    "lm"
-#else
-	    "l"
-#endif
-	    )) != -1)
+	if (x_bind_check()) {
+		bi_errorf("can't bind, not a tty");
+		return (1);
+	}
+
+	while ((optc = ksh_getopt(wp, &builtin_opt, "lm")) != -1)
 		switch (optc) {
 		case 'l':
-			list = true;
-			break;
+			return (x_bind_list());
 #ifndef MKSH_SMALL
 		case 'm':
 			macro = true;
 			break;
 #endif
-		case '?':
+		default:
 			return (1);
 		}
 	wp += builtin_opt.optind;
 
 	if (*wp == NULL)
-		/* list all */
-		rv = x_bind(NULL, NULL,
-#ifndef MKSH_SMALL
-		    false,
-#endif
-		    list);
+		return (x_bind_showall());
 
-	for (; *wp != NULL; wp++) {
-		if ((cp = cstrchr(*wp, '=')) == NULL)
-			up = NULL;
-		else {
-			strdupx(up, *wp, ATEMP);
-			up[cp++ - *wp] = '\0';
-		}
-		if (x_bind(up ? up : *wp, cp,
-#ifndef MKSH_SMALL
-		    macro,
-#endif
-		    false))
-			rv = 1;
-		afree(up, ATEMP);
-	}
+	do {
+		rv |= x_bind(*wp SMALLP(macro));
+	} while (*++wp);
 
 	return (rv);
 }
@@ -1353,10 +1337,17 @@
 int
 c_shift(const char **wp)
 {
-	struct block *l = e->loc;
 	int n;
 	mksh_ari_t val;
 	const char *arg;
+	struct block *l = e->loc;
+
+	if ((l->flags & BF_RESETSPEC)) {
+		/* prevent pollution */
+		l->flags &= ~BF_RESETSPEC;
+		/* operate on parent environment */
+		l = l->next;
+	}
 
 	if (ksh_getopt(wp, &builtin_opt, null) == '?')
 		return (1);
@@ -1375,6 +1366,7 @@
 		bi_errorf(Tf_sD_s, Tbadnum, arg);
 		return (1);
 	}
+
 	if (l->argc < n) {
 		bi_errorf("nothing to shift");
 		return (1);
@@ -1589,7 +1581,6 @@
 	return (rv);
 }
 
-static const char REPLY[] = "REPLY";
 int
 c_read(const char **wp)
 {
@@ -1670,7 +1661,7 @@
 		if (!builtin_opt.optarg[0])
 			fd = 0;
 		else if ((fd = check_fd(builtin_opt.optarg, R_OK, &ccp)) < 0) {
-			bi_errorf(Tf_sD_sD_s, "-u", builtin_opt.optarg, ccp);
+			bi_errorf(Tf_sD_sD_s, Tdu, builtin_opt.optarg, ccp);
 			return (2);
 		}
 		break;
@@ -1679,7 +1670,7 @@
 	}
 	wp += builtin_opt.optind;
 	if (*wp == NULL)
-		*--wp = REPLY;
+		*--wp = TREPLY;
 
 	if (intoarray && wp[1] != NULL) {
 		bi_errorf(Ttoo_many_args);
@@ -2029,7 +2020,6 @@
 c_eval(const char **wp)
 {
 	struct source *s, *saves = source;
-	unsigned char savef;
 	int rv;
 
 	if (ksh_getopt(wp, &builtin_opt, null) == '?')
@@ -2072,10 +2062,7 @@
 	/* SUSv4: OR with a high value never written otherwise */
 	exstat |= 0x4000;
 
-	savef = Flag(FERREXIT);
-	Flag(FERREXIT) |= 0x80;
 	rv = shell(s, 2);
-	Flag(FERREXIT) = savef;
 	source = saves;
 	afree(s, ATEMP);
 	if (exstat & 0x4000)
@@ -2237,7 +2224,13 @@
 	int argi;
 	bool setargs;
 	struct block *l = e->loc;
-	const char **owp;
+
+	if ((l->flags & BF_RESETSPEC)) {
+		/* prevent pollution */
+		l->flags &= ~BF_RESETSPEC;
+		/* operate on parent environment */
+		l = l->next;
+	}
 
 	if (wp[1] == NULL) {
 		static const char *args[] = { Tset, "-", NULL };
@@ -2248,6 +2241,8 @@
 		return (2);
 	/* set $# and $* */
 	if (setargs) {
+		const char **owp;
+
 		wp += argi - 1;
 		owp = wp;
 		/* save $0 */
@@ -2579,25 +2574,25 @@
 #endif
 
 /*-
-   test(1) roughly accepts the following grammar:
-	oexpr	::= aexpr | aexpr "-o" oexpr ;
-	aexpr	::= nexpr | nexpr "-a" aexpr ;
-	nexpr	::= primary | "!" nexpr ;
-	primary	::= unary-operator operand
-		| operand binary-operator operand
-		| operand
-		| "(" oexpr ")"
-		;
-
-	unary-operator ::= "-a"|"-b"|"-c"|"-d"|"-e"|"-f"|"-G"|"-g"|"-H"|"-h"|
-			   "-k"|"-L"|"-n"|"-O"|"-o"|"-p"|"-r"|"-S"|"-s"|"-t"|
-			   "-u"|"-v"|"-w"|"-x"|"-z";
-
-	binary-operator ::= "="|"=="|"!="|"<"|">"|"-eq"|"-ne"|"-gt"|"-ge"|
-			    "-lt"|"-le"|"-ef"|"-nt"|"-ot";
-
-	operand ::= <anything>
-*/
+ * test(1) roughly accepts the following grammar:
+ *	oexpr	::= aexpr | aexpr "-o" oexpr ;
+ *	aexpr	::= nexpr | nexpr "-a" aexpr ;
+ *	nexpr	::= primary | "!" nexpr ;
+ *	primary	::= unary-operator operand
+ *		| operand binary-operator operand
+ *		| operand
+ *		| "(" oexpr ")"
+ *		;
+ *
+ *	unary-operator ::= "-a"|"-b"|"-c"|"-d"|"-e"|"-f"|"-G"|"-g"|"-H"|"-h"|
+ *			   "-k"|"-L"|"-n"|"-O"|"-o"|"-p"|"-r"|"-S"|"-s"|"-t"|
+ *			   "-u"|"-v"|"-w"|"-x"|"-z";
+ *
+ *	binary-operator ::= "="|"=="|"!="|"<"|">"|"-eq"|"-ne"|"-gt"|"-ge"|
+ *			    "-lt"|"-le"|"-ef"|"-nt"|"-ot";
+ *
+ *	operand ::= <anything>
+ */
 
 /* POSIX says > 1 for errors */
 #define T_ERR_EXIT 2
@@ -2749,13 +2744,36 @@
 }
 
 #ifdef __OS2__
-#define test_access(name, mode) access_ex(access, (name), (mode))
-#define test_stat(name, buffer) stat_ex((name), (buffer))
+#define test_access(name,mode)	access_ex(access, (name), (mode))
+#define test_stat(name,buffer)	stat_ex(stat, (name), (buffer))
+#define test_lstat(name,buffer)	stat_ex(lstat, (name), (buffer))
 #else
-#define test_access(name, mode) access((name), (mode))
-#define test_stat(name, buffer) stat((name), (buffer))
+#define test_access(name,mode)	access((name), (mode))
+#define test_stat(name,buffer)	stat((name), (buffer))
+#define test_lstat(name,buffer)	lstat((name), (buffer))
 #endif
 
+#if HAVE_ST_MTIM
+#undef st_mtimensec
+#define st_mtimensec st_mtim.tv_nsec
+#endif
+
+static int
+mtimecmp(const struct stat *sb1, const struct stat *sb2)
+{
+	if (sb1->st_mtime < sb2->st_mtime)
+		return (-1);
+	if (sb1->st_mtime > sb2->st_mtime)
+		return (1);
+#if (HAVE_ST_MTIMENSEC || HAVE_ST_MTIM)
+	if (sb1->st_mtimensec < sb2->st_mtimensec)
+		return (-1);
+	if (sb1->st_mtimensec > sb2->st_mtimensec)
+		return (1);
+#endif
+	return (0);
+}
+
 int
 test_eval(Test_env *te, Test_op op, const char *opnd1, const char *opnd2,
     bool do_eval)
@@ -2849,31 +2867,31 @@
 
 	/* -d */
 	case TO_FILID:
-		return (stat(opnd1, &b1) == 0 && S_ISDIR(b1.st_mode));
+		return (test_stat(opnd1, &b1) == 0 && S_ISDIR(b1.st_mode));
 
 	/* -c */
 	case TO_FILCDEV:
-		return (stat(opnd1, &b1) == 0 && S_ISCHR(b1.st_mode));
+		return (test_stat(opnd1, &b1) == 0 && S_ISCHR(b1.st_mode));
 
 	/* -b */
 	case TO_FILBDEV:
-		return (stat(opnd1, &b1) == 0 && S_ISBLK(b1.st_mode));
+		return (test_stat(opnd1, &b1) == 0 && S_ISBLK(b1.st_mode));
 
 	/* -p */
 	case TO_FILFIFO:
-		return (stat(opnd1, &b1) == 0 && S_ISFIFO(b1.st_mode));
+		return (test_stat(opnd1, &b1) == 0 && S_ISFIFO(b1.st_mode));
 
 	/* -h or -L */
 	case TO_FILSYM:
 #ifdef MKSH__NO_SYMLINK
 		return (0);
 #else
-		return (lstat(opnd1, &b1) == 0 && S_ISLNK(b1.st_mode));
+		return (test_lstat(opnd1, &b1) == 0 && S_ISLNK(b1.st_mode));
 #endif
 
 	/* -S */
 	case TO_FILSOCK:
-		return (stat(opnd1, &b1) == 0 && S_ISSOCK(b1.st_mode));
+		return (test_stat(opnd1, &b1) == 0 && S_ISSOCK(b1.st_mode));
 
 	/* -H => HP context dependent files (directories) */
 	case TO_FILCDF:
@@ -2892,7 +2910,7 @@
 		 */
 
 		nv = shf_smprintf("%s+", opnd1);
-		i = (stat(nv, &b1) == 0 && S_ISCDF(b1.st_mode));
+		i = (test_stat(nv, &b1) == 0 && S_ISCDF(b1.st_mode));
 		afree(nv, ATEMP);
 		return (i);
 	}
@@ -2902,18 +2920,18 @@
 
 	/* -u */
 	case TO_FILSETU:
-		return (stat(opnd1, &b1) == 0 &&
+		return (test_stat(opnd1, &b1) == 0 &&
 		    (b1.st_mode & S_ISUID) == S_ISUID);
 
 	/* -g */
 	case TO_FILSETG:
-		return (stat(opnd1, &b1) == 0 &&
+		return (test_stat(opnd1, &b1) == 0 &&
 		    (b1.st_mode & S_ISGID) == S_ISGID);
 
 	/* -k */
 	case TO_FILSTCK:
 #ifdef S_ISVTX
-		return (stat(opnd1, &b1) == 0 &&
+		return (test_stat(opnd1, &b1) == 0 &&
 		    (b1.st_mode & S_ISVTX) == S_ISVTX);
 #else
 		return (0);
@@ -2921,7 +2939,8 @@
 
 	/* -s */
 	case TO_FILGZ:
-		return (stat(opnd1, &b1) == 0 && (off_t)b1.st_size > (off_t)0);
+		return (test_stat(opnd1, &b1) == 0 &&
+		    (off_t)b1.st_size > (off_t)0);
 
 	/* -t */
 	case TO_FILTT:
@@ -2934,11 +2953,13 @@
 
 	/* -O */
 	case TO_FILUID:
-		return (stat(opnd1, &b1) == 0 && (uid_t)b1.st_uid == ksheuid);
+		return (test_stat(opnd1, &b1) == 0 &&
+		    (uid_t)b1.st_uid == ksheuid);
 
 	/* -G */
 	case TO_FILGID:
-		return (stat(opnd1, &b1) == 0 && (gid_t)b1.st_gid == getegid());
+		return (test_stat(opnd1, &b1) == 0 &&
+		    (gid_t)b1.st_gid == kshegid);
 
 	/*
 	 * Binary Operators
@@ -2976,9 +2997,9 @@
 		 * ksh88/ksh93 succeed if file2 can't be stated
 		 * (subtly different from 'does not exist').
 		 */
-		return (stat(opnd1, &b1) == 0 &&
-		    (((s = stat(opnd2, &b2)) == 0 &&
-		    b1.st_mtime > b2.st_mtime) || s < 0));
+		return (test_stat(opnd1, &b1) == 0 &&
+		    (((s = test_stat(opnd2, &b2)) == 0 &&
+		    mtimecmp(&b1, &b2) > 0) || s < 0));
 
 	/* -ot */
 	case TO_FILOT:
@@ -2986,13 +3007,14 @@
 		 * ksh88/ksh93 succeed if file1 can't be stated
 		 * (subtly different from 'does not exist').
 		 */
-		return (stat(opnd2, &b2) == 0 &&
-		    (((s = stat(opnd1, &b1)) == 0 &&
-		    b1.st_mtime < b2.st_mtime) || s < 0));
+		return (test_stat(opnd2, &b2) == 0 &&
+		    (((s = test_stat(opnd1, &b1)) == 0 &&
+		    mtimecmp(&b1, &b2) < 0) || s < 0));
 
 	/* -ef */
 	case TO_FILEQ:
-		return (stat (opnd1, &b1) == 0 && stat (opnd2, &b2) == 0 &&
+		return (test_stat(opnd1, &b1) == 0 &&
+		    test_stat(opnd2, &b2) == 0 &&
 		    b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino);
 
 	/* all other cases */
@@ -3155,7 +3177,7 @@
 {
 	/* Order important - indexed by Test_meta values */
 	static const char * const tokens[] = {
-		"-o", "-a", "!", "(", ")"
+		Tdo, Tda, "!", "(", ")"
 	};
 	Test_op rv;
 
@@ -3469,7 +3491,7 @@
 #define MKSH_CAT_BUFSIZ 4096
 
 	/* parse options: POSIX demands we support "-u" as no-op */
-	while ((rv = ksh_getopt(wp, &builtin_opt, "u")) != -1) {
+	while ((rv = ksh_getopt(wp, &builtin_opt, Tu)) != -1) {
 		switch (rv) {
 		case 'u':
 			/* we already operate unbuffered */
diff --git a/src/histrap.c b/src/histrap.c
index 985b2a3..84211c6 100644
--- a/src/histrap.c
+++ b/src/histrap.c
@@ -3,7 +3,7 @@
 
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
- *		 2011, 2012, 2014, 2015, 2016, 2017, 2018
+ *		 2011, 2012, 2014, 2015, 2016, 2017, 2018, 2019
  *	mirabilos <m@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -27,7 +27,7 @@
 #include <sys/file.h>
 #endif
 
-__RCSID("$MirOS: src/bin/mksh/histrap.c,v 1.167 2018/04/28 17:16:54 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/histrap.c,v 1.169 2019/09/16 21:10:33 tg Exp $");
 
 Trap sigtraps[ksh_NSIG + 1];
 static struct sigaction Sigact_ign;
@@ -504,6 +504,8 @@
 void
 sethistsize(mksh_ari_t n)
 {
+	if (n > 65535)
+		n = 65535;
 	if (n > 0 && n != histsize) {
 		int cursize = histptr - history;
 
diff --git a/src/jobs.c b/src/jobs.c
index 66e31aa..8663264 100644
--- a/src/jobs.c
+++ b/src/jobs.c
@@ -2,7 +2,7 @@
 
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2011,
- *		 2012, 2013, 2014, 2015, 2016, 2018
+ *		 2012, 2013, 2014, 2015, 2016, 2018, 2019
  *	mirabilos <m@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -23,7 +23,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/jobs.c,v 1.127 2018/07/15 16:23:10 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/jobs.c,v 1.128 2019/12/11 19:46:20 tg Exp $");
 
 #if HAVE_KILLPG
 #define mksh_killpg		killpg
@@ -88,6 +88,7 @@
 
 typedef struct job Job;
 struct job {
+	ALLOC_ITEM alloc_INT;	/* internal, do not touch */
 	Job *next;		/* next job in list */
 	Proc *proc_list;	/* process list */
 	Proc *last_proc;	/* last process in list */
@@ -1778,16 +1779,26 @@
 	if (free_jobs != NULL) {
 		newj = free_jobs;
 		free_jobs = free_jobs->next;
-	} else
-		newj = alloc(sizeof(Job), APERM);
+	} else {
+		char *cp;
+
+		/*
+		 * struct job includes ALLOC_ITEM for alignment constraints
+		 * so first get the actually used memory, then assign it
+		 */
+		cp = alloc(sizeof(Job) - sizeof(ALLOC_ITEM), APERM);
+		/* undo what alloc() did to the malloc result address */
+		newj = (void *)(cp - sizeof(ALLOC_ITEM));
+	}
 
 	/* brute force method */
-	for (i = 1; ; i++) {
-		for (j = job_list; j && j->job != i; j = j->next)
-			;
-		if (j == NULL)
-			break;
-	}
+	i = 0;
+	do {
+		++i;
+		j = job_list;
+		while (j && j->job != i)
+			j = j->next;
+	} while (j);
 	newj->job = i;
 
 	return (newj);
diff --git a/src/lex.c b/src/lex.c
index 8c75a98..21c2f35 100644
--- a/src/lex.c
+++ b/src/lex.c
@@ -23,7 +23,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/lex.c,v 1.250 2018/10/20 18:34:14 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/lex.c,v 1.251 2020/03/10 23:48:40 tg Exp $");
 
 /*
  * states while lexing word
@@ -451,8 +451,7 @@
 							statep->ls_adelim.num = 1;
 							statep->nparen = 0;
 							break;
-						} else if (ctype(c, C_DIGIT | C_DOLAR | C_SPC) ||
-						    /*XXX what else? */
+						} else if (ctype(c, C_ALNUX | C_DOLAR | C_SPC) ||
 						    c == '(' /*)*/) {
 							/* substring subst. */
 							if (c != ' ') {
diff --git a/src/main.c b/src/main.c
index 41dd6da..1328045 100644
--- a/src/main.c
+++ b/src/main.c
@@ -6,7 +6,7 @@
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
  *		 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018,
- *		 2019
+ *		 2019, 2020
  *	mirabilos <m@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -35,7 +35,7 @@
 #include <locale.h>
 #endif
 
-__RCSID("$MirOS: src/bin/mksh/main.c,v 1.351 2019/01/05 13:24:18 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/main.c,v 1.372 2020/05/16 22:51:24 tg Exp $");
 
 #ifndef MKSHRC_PATH
 #define MKSHRC_PATH	"~/.mkshrc"
@@ -65,8 +65,8 @@
     "${EPOCHREALTIME=}";
 
 static const char *initcoms[] = {
-	Ttypeset, "-r", initvsn, NULL,
-	Ttypeset, "-x", "HOME", TPATH, TSHELL, NULL,
+	Ttypeset, Tdr, initvsn, NULL,
+	Ttypeset, Tdx, "HOME", TPATH, TSHELL, NULL,
 	Ttypeset, "-i10", "COLUMNS", "LINES", "SECONDS", "TMOUT", NULL,
 	Talias,
 	"integer=\\\\builtin typeset -i",
@@ -90,7 +90,7 @@
 };
 
 static const char *restr_com[] = {
-	Ttypeset, "-r", TPATH, "ENV", TSHELL, NULL
+	Ttypeset, Tdr, TPATH, TENV, TSHELL, NULL
 };
 
 static bool initio_done;
@@ -100,7 +100,6 @@
 struct env *e = &env;
 
 /* compile-time assertions */
-#define cta(name, expr) struct cta_ ## name { char t[(expr) ? 1 : -1]; }
 
 /* this one should be defined by the standard */
 cta(char_is_1_char, (sizeof(char) == 1) && (sizeof(signed char) == 1) &&
@@ -227,7 +226,7 @@
 	int argi, i;
 	Source *s = NULL;
 	struct block *l;
-	unsigned char restricted_shell, errexit, utf_flag;
+	unsigned char restricted_shell = 0, errexit, utf_flag;
 	char *cp;
 	const char *ccp, **wp;
 	struct tbl *vp;
@@ -243,6 +242,9 @@
 
 #ifdef __OS2__
 	os2_init(&argc, &argv);
+#define builtin_name_cmp stricmp
+#else
+#define builtin_name_cmp strcmp
 #endif
 
 	/* do things like getpgrp() et al. */
@@ -277,10 +279,11 @@
 			ccp += argi;
  begin_parsing_kshname:
 			argi = 0;
-			if (*ccp == '-')
-				++ccp;
 		}
 	}
+	Flag(FLOGIN) = (ord(*ccp) == ORD('-')) || (ord(*kshname) == ORD('-'));
+	if (ord(*ccp) == ORD('-'))
+		++ccp;
 	if (!*ccp)
 		ccp = empty_argv[0];
 
@@ -303,22 +306,34 @@
 
 	/* define built-in commands and see if we were called as one */
 	ktinit(APERM, &builtins,
-	    /* currently up to 54 builtins: 75% of 128 = 2^7 */
+	    /* currently up to 52 builtins: 75% of 128 = 2^7 */
 	    7);
-	for (i = 0; mkshbuiltins[i].name != NULL; i++)
-		if (!strcmp(ccp, builtin(mkshbuiltins[i].name,
-		    mkshbuiltins[i].func)))
-			Flag(FAS_BUILTIN) = 1;
+	for (i = 0; mkshbuiltins[i].name != NULL; ++i) {
+		const char *builtin_name;
 
-	if (!Flag(FAS_BUILTIN)) {
+		builtin_name = builtin(mkshbuiltins[i].name,
+		    mkshbuiltins[i].func);
+		if (!builtin_name_cmp(ccp, builtin_name)) {
+			/* canonicalise argv[0] */
+			ccp = builtin_name;
+			as_builtin = true;
+		}
+	}
+
+	if (!as_builtin) {
 		/* check for -T option early */
 		argi = parse_args(argv, OF_FIRSTTIME, NULL);
 		if (argi < 0)
 			return (1);
-
+		/* called as rsh, rmksh, -rsh, RKSH.EXE, etc.? */
+		if (ksh_eq(*ccp, 'R', 'r')) {
+			++ccp;
+			++restricted_shell;
+		}
 #if defined(MKSH_BINSHPOSIX) || defined(MKSH_BINSHREDUCED)
-		/* are we called as -sh or /bin/sh or so? */
-		if (!strcmp(ccp, "sh" MKSH_EXE_EXT)) {
+		/* are we called as -rsh or /bin/sh or SH.EXE or so? */
+		if (ksh_eq(ccp[0], 'S', 's') &&
+		    ksh_eq(ccp[1], 'H', 'h')) {
 			/* either also turns off braceexpand */
 #ifdef MKSH_BINSHPOSIX
 			/* enable better POSIX conformance */
@@ -471,7 +486,7 @@
 	/* this to note if utf-8 mode is set on command line (see below) */
 	UTFMODE = 2;
 
-	if (!Flag(FAS_BUILTIN)) {
+	if (!as_builtin) {
 		argi = parse_args(argv, OF_CMDLINE, NULL);
 		if (argi < 0)
 			return (1);
@@ -481,7 +496,7 @@
 	utf_flag = UTFMODE;
 	UTFMODE = 0;
 
-	if (Flag(FAS_BUILTIN)) {
+	if (as_builtin) {
 		/* auto-detect from environment variables, always */
 		utf_flag = 3;
 	} else if (Flag(FCOMMAND)) {
@@ -546,8 +561,8 @@
 	}
 
 	/* this bizarreness is mandated by POSIX */
-	if (fstat(0, &s_stdin) >= 0 && S_ISCHR(s_stdin.st_mode) &&
-	    Flag(FTALKING))
+	if (Flag(FTALKING) && fstat(0, &s_stdin) >= 0 &&
+	    S_ISCHR(s_stdin.st_mode))
 		reset_nonblock(0);
 
 	/* initialise job control */
@@ -579,7 +594,7 @@
 #endif
 
 	l = e->loc;
-	if (Flag(FAS_BUILTIN)) {
+	if (as_builtin) {
 		l->argc = argc;
 		l->argv = argv;
 		l->argv[0] = ccp;
@@ -642,11 +657,26 @@
 	}
 
 	/* Disable during .profile/ENV reading */
-	restricted_shell = Flag(FRESTRICTED);
+	restricted_shell |= Flag(FRESTRICTED);
 	Flag(FRESTRICTED) = 0;
 	errexit = Flag(FERREXIT);
 	Flag(FERREXIT) = 0;
 
+	/* save flags for "set +o" handling */
+	memcpy(baseline_flags, shell_flags, sizeof(shell_flags));
+	/* disable these because they have special handling */
+	baseline_flags[(int)FPOSIX] = 0;
+	baseline_flags[(int)FSH] = 0;
+	/* ensure these always show up setting, for FPOSIX/FSH */
+	baseline_flags[(int)FBRACEEXPAND] = 0;
+	baseline_flags[(int)FUNNYCODE] = 0;
+#if !defined(MKSH_SMALL) || defined(DEBUG)
+	/* mark as initialised */
+	baseline_flags[(int)FNFLAGS] = 1;
+#endif
+	if (as_builtin)
+		goto skip_startup_files;
+
 	/*
 	 * Do this before profile/$ENV so that if it causes problems in them,
 	 * user will know why things broke.
@@ -656,7 +686,18 @@
 
 	if (Flag(FLOGIN))
 		include(MKSH_SYSTEM_PROFILE, 0, NULL, true);
-	if (!Flag(FPRIVILEGED)) {
+	if (Flag(FPRIVILEGED)) {
+		include(MKSH_SUID_PROFILE, 0, NULL, true);
+		/* note whether -p was enabled during startup */
+		if (Flag(FPRIVILEGED) == 1)
+			/* allow set -p to setuid() later */
+			Flag(FPRIVILEGED) = 3;
+		else
+			/* turn off -p if not set explicitly */
+			change_flag(FPRIVILEGED, OF_INTERNAL, false);
+		/* track shell-imposed changes */
+		baseline_flags[(int)FPRIVILEGED] = Flag(FPRIVILEGED);
+	} else {
 		if (Flag(FLOGIN))
 			include(substitute("$HOME/.profile", 0), 0, NULL, true);
 		if (Flag(FTALKING)) {
@@ -664,25 +705,25 @@
 			if (cp[0] != '\0')
 				include(cp, 0, NULL, true);
 		}
-	} else {
-		include(MKSH_SUID_PROFILE, 0, NULL, true);
-		/* turn off -p if not set explicitly */
-		if (Flag(FPRIVILEGED) != 1)
-			change_flag(FPRIVILEGED, OF_INTERNAL, false);
 	}
-
 	if (restricted_shell) {
 		c_builtin(restr_com);
 		/* After typeset command... */
 		Flag(FRESTRICTED) = 1;
+		/* track shell-imposed changes */
+		baseline_flags[(int)FRESTRICTED] = 1;
 	}
 	Flag(FERREXIT) = errexit;
 
 	if (Flag(FTALKING) && s)
 		hist_init(s);
-	else
+	else {
 		/* set after ENV */
+ skip_startup_files:
 		Flag(FTRACKALL) = 1;
+		/* track shell-imposed change (might lower surprise) */
+		baseline_flags[(int)FTRACKALL] = 1;
+	}
 
 	alarm_init();
 
@@ -701,7 +742,7 @@
 	struct block *l;
 
 	if ((rv = main_init(argc, argv, &s, &l)) == 0) {
-		if (Flag(FAS_BUILTIN)) {
+		if (as_builtin) {
 			rv = c_builtin(l->argv);
 		} else {
 			shell(s, 0);
@@ -741,6 +782,7 @@
 		switch (i) {
 		case LRETURN:
 		case LERROR:
+		case LERREXT:
 			/* see below */
 			return (exstat & 0xFF);
 		case LINTR:
@@ -808,6 +850,8 @@
 	int i;
 
 	newenv(level == 2 ? E_EVAL : E_PARSE);
+	if (level == 2)
+		e->flags |= EF_IN_EVAL;
 	if (interactive)
 		really_exit = false;
 	switch ((i = kshsetjmp(e->jbuf))) {
@@ -815,18 +859,21 @@
 		break;
 	case LBREAK:
 	case LCONTIN:
-		if (level != 2) {
-			source = old_source;
-			quitenv(NULL);
-			internal_errorf(Tf_cant_s, Tshell,
-			    i == LBREAK ? Tbreak : Tcontinue);
+		/* assert: interactive == false */
+		source = old_source;
+		quitenv(NULL);
+		if (level == 2) {
+			/* keep on going */
+			unwind(i);
 			/* NOTREACHED */
 		}
-		/* assert: interactive == false */
-		/* FALLTHROUGH */
+		internal_errorf(Tf_cant_s, Tshell,
+		    i == LBREAK ? Tbreak : Tcontinue);
+		/* NOTREACHED */
 	case LINTR:
 		/* we get here if SIGINT not caught or ignored */
 	case LERROR:
+	case LERREXT:
 	case LSHELL:
 		if (interactive) {
 			if (i == LINTR)
@@ -857,6 +904,8 @@
 	case LRETURN:
 		source = old_source;
 		quitenv(NULL);
+		if (i == LERREXT && level == 2)
+			return (exstat & 0xFF);
 		/* keep on going */
 		unwind(i);
 		/* NOTREACHED */
@@ -916,8 +965,8 @@
  source_no_tree:
 		reclaim();
 	}
-	quitenv(NULL);
 	source = old_source;
+	quitenv(NULL);
 	return (exstat & 0xFF);
 }
 
@@ -926,36 +975,25 @@
 void
 unwind(int i)
 {
-	/*
-	 * This is a kludge. We need to restore everything that was
-	 * changed in the new environment, see cid 1005090337C7A669439
-	 * and 10050903386452ACBF1, but fail to even save things most of
-	 * the time. funcs.c:c_eval() changes FERREXIT temporarily to 0,
-	 * which needs to be restored thus (related to Debian #696823).
-	 * We did not save the shell flags, so we use a special or'd
-	 * value here... this is mostly to clean up behind *other*
-	 * callers of unwind(LERROR) here; exec.c has the regular case.
-	 */
-	if (Flag(FERREXIT) & 0x80) {
-		/* GNU bash does not run this trapsig */
-		trapsig(ksh_SIGERR);
-		Flag(FERREXIT) &= ~0x80;
-	}
+	/* during eval, skip FERREXIT trap */
+	if (i == LERREXT && (e->flags & EF_IN_EVAL))
+		goto defer_traps;
 
 	/* ordering for EXIT vs ERR is a bit odd (this is what AT&T ksh does) */
-	if (i == LEXIT || ((i == LERROR || i == LINTR) &&
+	if (i == LEXIT || ((i == LERROR || i == LERREXT || i == LINTR) &&
 	    sigtraps[ksh_SIGEXIT].trap &&
 	    (!Flag(FTALKING) || Flag(FERREXIT)))) {
 		++trap_nested;
 		runtrap(&sigtraps[ksh_SIGEXIT], trap_nested == 1);
 		--trap_nested;
 		i = LLEAVE;
-	} else if (Flag(FERREXIT) == 1 && (i == LERROR || i == LINTR)) {
+	} else if (Flag(FERREXIT) && (i == LERROR || i == LERREXT || i == LINTR)) {
 		++trap_nested;
 		runtrap(&sigtraps[ksh_SIGERR], trap_nested == 1);
 		--trap_nested;
 		i = LLEAVE;
 	}
+ defer_traps:
 
 	while (/* CONSTCOND */ 1) {
 		switch (e->type) {
@@ -998,8 +1036,7 @@
 	ep->temps = NULL;
 	ep->yyrecursive_statep = NULL;
 	ep->type = type;
-	ep->flags = 0;
-	/* jump buffer is invalid because flags == 0 */
+	ep->flags = e->flags & EF_IN_EVAL;
 	e = ep;
 }
 
@@ -1336,6 +1373,39 @@
 	}
 }
 
+/*
+ * Used by functions called by builtins and not:
+ * identical to errorfx if first argument is nil,
+ * like bi_errorf storing the errorlevel into it otherwise
+ */
+void
+maybe_errorf(int *ep, int rc, const char *fmt, ...)
+{
+	va_list va;
+
+	/* debugging: note that stdout not valid */
+	shl_stdout_ok = false;
+
+	exstat = rc;
+
+	va_start(va, fmt);
+	vwarningf(VWARNINGF_ERRORPREFIX | VWARNINGF_FILELINE |
+	    (ep ? VWARNINGF_BUILTIN : 0), fmt, va);
+	va_end(va);
+
+	if (!ep)
+		goto and_out;
+	*ep = rc;
+
+	/* POSIX special builtins cause non-interactive shells to exit */
+	if (builtin_spec) {
+		builtin_argv0 = NULL;
+		/* may not want to use LERROR here */
+ and_out:
+		unwind(LERROR);
+	}
+}
+
 /* Called when something that shouldn't happen does */
 void
 internal_errorf(const char *fmt, ...)
@@ -1435,7 +1505,7 @@
 	if ((lfp = getenv("SDMKSH_PATH")) == NULL) {
 		if ((lfp = getenv("HOME")) == NULL || !mksh_abspath(lfp))
 			errorf("can't get home directory");
-		lfp = shf_smprintf(Tf_sSs, lfp, "mksh-dbg.txt");
+		strpathx(lfp, lfp, "mksh-dbg.txt", 1);
 	}
 
 	if ((shl_dbg_fd = open(lfp, O_WRONLY | O_APPEND | O_CREAT, 0600)) < 0)
@@ -1997,7 +2067,7 @@
 	errno = 0;
 	if ((dent = readdir(dirp)) != NULL) {
 		if (skip_varname(dent->d_name, true)[0] == '\0') {
-			xp = shf_smprintf(Tf_sSs, MKSH_ENVDIR, dent->d_name);
+			strpathx(xp, MKSH_ENVDIR, dent->d_name, 1);
 			if (!(shf = shf_open(xp, O_RDONLY, 0, 0))) {
 				warningf(false,
 				    "cannot read environment %s from %s: %s",
diff --git a/src/misc.c b/src/misc.c
index cddc516..b19a253 100644
--- a/src/misc.c
+++ b/src/misc.c
@@ -3,7 +3,8 @@
 
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
- *		 2011, 2012, 2013, 2014, 2015, 2016, 2017
+ *		 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2019,
+ *		 2020
  *	mirabilos <m@mirbsd.org>
  * Copyright (c) 2015
  *	Daniel Richard G. <skunk@iSKUNK.ORG>
@@ -32,7 +33,7 @@
 #include <grp.h>
 #endif
 
-__RCSID("$MirOS: src/bin/mksh/misc.c,v 1.293 2018/08/10 02:53:35 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/misc.c,v 1.299 2020/05/16 22:19:58 tg Exp $");
 
 #define KSH_CHVT_FLAG
 #ifdef MKSH_SMALL
@@ -62,13 +63,13 @@
 
 #ifdef SETUID_CAN_FAIL_WITH_EAGAIN
 /* we don't need to check for other codes, EPERM won't happen */
-#define DO_SETUID(func, argvec) do {					\
+#define DO_SETUID(func,argvec) do {					\
 	if ((func argvec) && errno == EAGAIN)				\
 		errorf("%s failed with EAGAIN, probably due to a"	\
 		    " too low process limit; aborting", #func);		\
 } while (/* CONSTCOND */ 0)
 #else
-#define DO_SETUID(func, argvec) func argvec
+#define DO_SETUID(func,argvec) func argvec
 #endif
 
 
@@ -141,7 +142,8 @@
 };
 
 static void options_fmt_entry(char *, size_t, unsigned int, const void *);
-static void printoptions(bool);
+static int printoptions(bool);
+static int printoption(size_t);
 
 /* format a single select menu item */
 static void
@@ -154,10 +156,32 @@
 	    Flag(oi->opts[i]) ? "on" : "off");
 }
 
-static void
+static int
+printoption(size_t i)
+{
+	if (Flag(i) == baseline_flags[i])
+		return (0);
+	if (!OFN(i)[0]) {
+#if !defined(MKSH_SMALL) || defined(DEBUG)
+		bi_errorf(Tf_sd, "change in unnamed option", (int)i);
+#endif
+		return (1);
+	}
+	if (Flag(i) != 0 && Flag(i) != 1) {
+#if !defined(MKSH_SMALL) || defined(DEBUG)
+		bi_errorf(Tf_s_sD_s, Tdo, OFN(i), "not 0 or 1");
+#endif
+		return (1);
+	}
+	shprintf(Tf__s_s, Flag(i) ? Tdo : Tpo, OFN(i));
+	return (0);
+}
+
+static int
 printoptions(bool verbose)
 {
 	size_t i = 0;
+	int rv = 0;
 
 	if (verbose) {
 		size_t n = 0, len, octs = 0;
@@ -187,13 +211,17 @@
 	} else {
 		/* short version like AT&T ksh93 */
 		shf_puts(Tset, shl_stdout);
-		while (i < NELEM(options)) {
-			if (Flag(i) && OFN(i)[0])
-				shprintf(" -o %s", OFN(i));
+		shf_puts(To_o_reset, shl_stdout);
+		printoption(FSH);
+		printoption(FPOSIX);
+		while (i < FNFLAGS) {
+			if (i != FSH && i != FPOSIX)
+				rv |= printoption(i);
 			++i;
 		}
 		shf_putc('\n', shl_stdout);
 	}
+	return (rv);
 }
 
 char *
@@ -216,70 +244,85 @@
 void
 change_flag(enum sh_flag f, int what, bool newset)
 {
-	unsigned char oldval;
+	unsigned char oldval = Flag(f);
 	unsigned char newval = (newset ? 1 : 0);
 
 	if (f == FXTRACE) {
 		change_xtrace(newval, true);
 		return;
-	}
-	oldval = Flag(f);
-	Flag(f) = newval = (newset ? 1 : 0);
-#ifndef MKSH_UNEMPLOYED
-	if (f == FMONITOR) {
-		if (what != OF_CMDLINE && newval != oldval)
-			j_change();
-	} else
-#endif
-#ifndef MKSH_NO_CMDLINE_EDITING
-	  if ((
-#if !MKSH_S_NOVI
-	    f == FVI ||
-#endif
-	    f == FEMACS || f == FGMACS) && newval) {
-#if !MKSH_S_NOVI
-		Flag(FVI) =
-#endif
-		    Flag(FEMACS) = Flag(FGMACS) = 0;
-		Flag(f) = newval;
-	} else
-#endif
-	  if (f == FPRIVILEGED && oldval && !newval) {
-		/* Turning off -p? */
+	} else if (f == FPRIVILEGED) {
+		if (!oldval)
+			/* no getting back dropped privs */
+			return;
+		else if (!newval) {
+			/* turning off -p */
+			kshegid = kshgid;
+			ksheuid = kshuid;
+		} else if (oldval != 3)
+			/* nor going full sugid */
+			goto change_flag;
 
-		/*XXX this can probably be optimised */
-		kshegid = kshgid = getgid();
-		ksheuid = kshuid = getuid();
+		/* +++ set group IDs +++ */
 #if HAVE_SETRESUGID
-		DO_SETUID(setresgid, (kshegid, kshegid, kshegid));
-#if HAVE_SETGROUPS
-		/* setgroups doesn't EAGAIN on Linux */
-		setgroups(1, &kshegid);
-#endif
-		DO_SETUID(setresuid, (ksheuid, ksheuid, ksheuid));
+		DO_SETUID(setresgid, (kshegid, kshegid, kshgid));
 #else /* !HAVE_SETRESUGID */
-		/* setgid, setegid, seteuid don't EAGAIN on Linux */
+		/* setgid, setegid don't EAGAIN on Linux */
 		setgid(kshegid);
 #ifndef MKSH__NO_SETEUGID
 		setegid(kshegid);
-#endif
+#endif /* !MKSH__NO_SETEUGID */
+#endif /* !HAVE_SETRESUGID */
+
+		/* +++ wipe groups vector +++ */
+#if HAVE_SETGROUPS
+		/* setgroups doesn't EAGAIN on Linux */
+		setgroups(0, NULL);
+#endif /* HAVE_SETGROUPS */
+
+		/* +++ set user IDs +++ */
+#if HAVE_SETRESUGID
+		DO_SETUID(setresuid, (ksheuid, ksheuid, kshuid));
+#else /* !HAVE_SETRESUGID */
+		/* seteuid doesn't EAGAIN on Linux */
 		DO_SETUID(setuid, (ksheuid));
 #ifndef MKSH__NO_SETEUGID
 		seteuid(ksheuid);
-#endif
+#endif /* !MKSH__NO_SETEUGID */
 #endif /* !HAVE_SETRESUGID */
+
+		/* +++ privs changed +++ */
 	} else if ((f == FPOSIX || f == FSH) && newval) {
-		/* Turning on -o posix or -o sh? */
-		Flag(FBRACEEXPAND) = 0;
 		/* Turning on -o posix? */
-		if (f == FPOSIX) {
+		if (f == FPOSIX)
 			/* C locale required for compliance */
 			UTFMODE = 0;
-		}
-	} else if (f == FTALKING) {
+		/* Turning on -o posix or -o sh? */
+		Flag(FBRACEEXPAND) = 0;
+#ifndef MKSH_NO_CMDLINE_EDITING
+	} else if ((f == FEMACS ||
+#if !MKSH_S_NOVI
+	    f == FVI ||
+#endif
+	    f == FGMACS) && newval) {
+#if !MKSH_S_NOVI
+		Flag(FVI) = 0;
+#endif
+		Flag(FEMACS) = Flag(FGMACS) = 0;
+#endif
+	}
+
+ change_flag:
+	Flag(f) = newval;
+
+	if (f == FTALKING) {
 		/* Changing interactive flag? */
 		if ((what == OF_CMDLINE || what == OF_SET) && procpid == kshpid)
 			Flag(FTALKING_I) = newval;
+#ifndef MKSH_UNEMPLOYED
+	} else if (f == FMONITOR) {
+		if (what != OF_CMDLINE && newval != oldval)
+			j_change();
+#endif
 	}
 }
 
@@ -347,7 +390,8 @@
 #undef SHFLAGS_NOT_CMD
 	    ;
 	bool set;
-	const char *opts;
+	const char *opts = what == OF_CMDLINE || what == OF_FIRSTTIME ?
+	    cmd_opts : set_opts;
 	const char *array = NULL;
 	Getopt go;
 	size_t i;
@@ -355,22 +399,6 @@
 	bool sortargs = false;
 	bool fcompatseen = false;
 
-	if (what == OF_CMDLINE) {
-		const char *p = argv[0], *q;
-		/*
-		 * Set FLOGIN before parsing options so user can clear
-		 * flag using +l.
-		 */
-		if (*p != '-')
-			for (q = p; *q; )
-				if (mksh_cdirsep(*q++))
-					p = q;
-		Flag(FLOGIN) = (*p == '-');
-		opts = cmd_opts;
-	} else if (what == OF_FIRSTTIME) {
-		opts = cmd_opts;
-	} else
-		opts = set_opts;
 	ksh_getopt_reset(&go, GF_ERROR|GF_PLUSOPT);
 	while ((optc = ksh_getopt(argv, &go, opts)) != -1) {
 		set = tobool(!(go.info & GI_PLUS));
@@ -393,7 +421,15 @@
 				 * an option (ie, can't get here if what is
 				 * OF_CMDLINE).
 				 */
-				printoptions(set);
+#if !defined(MKSH_SMALL) || defined(DEBUG)
+				if (!set && !baseline_flags[(int)FNFLAGS]) {
+					bi_errorf(Tf_s_s, "too early",
+					    Tset_po);
+					return (-1);
+				}
+#endif
+				if (printoptions(set))
+					return (-1);
 				break;
 			}
 			i = option(go.optarg);
@@ -418,7 +454,23 @@
 				;
 			else if ((i != (size_t)-1) && (OFF(i) & what))
 				change_flag((enum sh_flag)i, what, set);
-			else {
+			else if (!strcmp(go.optarg, To_reset)) {
+#if !defined(MKSH_SMALL) || defined(DEBUG)
+				if (!baseline_flags[(int)FNFLAGS]) {
+					bi_errorf(Tf_ss, "too early",
+					    To_o_reset);
+					return (-1);
+				}
+#endif
+				/*
+				 * ordering, with respect to side effects,
+				 * was ensured above by printoptions
+				 */
+				for (i = 0; i < FNFLAGS; ++i)
+					if (Flag(i) != baseline_flags[i])
+						change_flag((enum sh_flag)i,
+						    what, baseline_flags[i]);
+			} else {
 				bi_errorf(Tf_sD_s, go.optarg,
 				    Tunknown_option);
 				return (-1);
@@ -1674,14 +1726,13 @@
 		if (getdrvwd(&ldest, ord(*upath)))
 			return (NULL);
 		/* A:foo -> A:/cwd/foo; A: -> A:/cwd */
-		ipath = shf_smprintf(Tf_sss, ldest,
-		    upath[2] ? "/" : "", upath + 2);
+		strpathx(ipath, ldest, upath + 2, 0);
 #endif
 	} else {
 		/* upath is a relative pathname, prepend cwd */
 		if ((tp = ksh_get_wd()) == NULL || !mksh_abspath(tp))
 			return (NULL);
-		ipath = shf_smprintf(Tf_sss, tp, "/", upath);
+		strpathx(ipath, tp, upath, 1);
 		afree(tp, ATEMP);
 	}
 
@@ -1783,7 +1834,7 @@
  assemble_symlink:
 #endif
 			/* append rest of current input path to link target */
-			tp = shf_smprintf(Tf_sss, ldest, *ip ? "/" : "", ip);
+			strpathx(tp, ldest, ip, 0);
 			afree(ipath, ATEMP);
 			ip = ipath = tp;
 			if (!mksh_abspath(ipath)) {
@@ -2199,8 +2250,7 @@
 	tryp = NULL;
 	if (mksh_drvltr(dir) && !mksh_cdirsep(dir[2]) &&
 	    !getdrvwd(&tryp, ord(*dir))) {
-		dir = shf_smprintf(Tf_sss, tryp,
-		    dir[2] ? "/" : "", dir + 2);
+		strpathx(dir, tryp, dir + 2, 0);
 		afree(tryp, ATEMP);
 		afree(allocd, ATEMP);
 		allocd = dir;
diff --git a/src/mksh.1 b/src/mksh.1
index 2e83428..1f1a121 100644
--- a/src/mksh.1
+++ b/src/mksh.1
@@ -1,9 +1,9 @@
-.\" $MirOS: src/bin/mksh/mksh.1,v 1.463 2019/03/01 16:17:31 tg Exp $
+.\" $MirOS: src/bin/mksh/mksh.1,v 1.491 2020/05/16 22:12:36 tg Exp $
 .\" $OpenBSD: ksh.1,v 1.160 2015/07/04 13:27:04 feinerer Exp $
 .\"-
 .\" Copyright © 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
 .\"		2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017,
-.\"		2018, 2019
+.\"		2018, 2019, 2020
 .\"	mirabilos <m@mirbsd.org>
 .\"
 .\" Provided that these terms and disclaimer and all copyright notices
@@ -28,7 +28,8 @@
 .\"   thus use - for hyphens and \- for minus signs and option dashes
 .\" * ~ is size-reduced and placed atop in groff, so use \*(TI
 .\" * ^ is size-reduced and placed atop in groff, so use \*(ha
-.\" * \(en does not work in nroff, so use \*(en
+.\" * \(en does not work in nroff, so use \*(en for a solo en dash
+.\" *   and \*(EM for a correctly spaced em dash
 .\" * <>| are problematic, so redefine and use \*(Lt\*(Gt\*(Ba
 .\" Also make sure to use \& *before* a punctuation char that is to not
 .\" be interpreted as punctuation, and especially with two-letter words
@@ -62,6 +63,12 @@
 .	ds ha ^
 .	ds en \(em
 .\}
+.ie n \{\
+.	ds EM \ \*(en\ \&
+.\}
+.el \{\
+.	ds EM \f(TR\^\(em\^\fP
+.\}
 .\"
 .\" Implement .Dd with the Mdocdate RCS keyword
 .\"
@@ -77,7 +84,7 @@
 .\" with -mandoc, it might implement .Mx itself, but we want to
 .\" use our own definition. And .Dd must come *first*, always.
 .\"
-.Dd $Mdocdate: March 1 2019 $
+.Dd $Mdocdate: May 16 2020 $
 .\"
 .\" Check which macro package we use, and do other -mdoc setup.
 .\"
@@ -221,7 +228,8 @@
 .Nm
 in mind and should be taken as such.
 .Ss I use Android, OS/2, etc. so what...?
-Please see the FAQ at the end of this document.
+Please refer to:
+.Pa http://www.mirbsd.org/mksh\-faq.htm#sowhatismksh
 .Ss Invocation
 Most builtins can be called directly, for example if a link points from its
 name to the shell; not all make sense, have been tested or work at all though.
@@ -262,7 +270,7 @@
 command below).
 .It Fl l
 Login shell.
-If the basename the shell is called with (i.e. argv[0])
+If the name or basename the shell is called with (i.e. argv[0])
 starts with
 .Ql \-
 or if this option is used,
@@ -279,7 +287,7 @@
 and
 .Xr getgid 2 ) .
 Clearing the privileged option causes the shell to set
-its effective user ID (group ID) to its real user ID (group ID).
+its effective user ID (group ID) to its initial real user ID (group ID).
 For further implications, see
 .Sx Startup files .
 If the shell is privileged and this flag is not explicitly set, the
@@ -289,8 +297,11 @@
 Restricted shell.
 A shell is
 .Dq restricted
-if this
-option is used.
+if the basename the shell is called with, after
+.Ql \-
+processing, starts with
+.Ql r
+or if this option is used.
 The following restrictions come into effect after the shell processes any
 profile and
 .Ev ENV
@@ -373,7 +384,7 @@
 .Fl c
 option is used and there is a non-option argument, it is used as the name;
 if commands are being read from a file, the file is used as the name;
-otherwise, the basename the shell was called with (i.e. argv[0]) is used.
+otherwise, the name the shell was called with (i.e. argv[0]) is used.
 .Pp
 The exit status of the shell is 127 if the command file specified on the
 command line could not be opened, or non-zero if a fatal syntax error
@@ -481,8 +492,8 @@
 .Sx Quoting
 below);
 .Ql # ,
-if used at the beginning of a word, introduces a comment \*(en everything after
-the
+if used at the beginning of a word,
+introduces a comment\*(EMeverything after the
 .Ql #
 up to the nearest newline is ignored;
 .Ql $
@@ -594,8 +605,7 @@
 .Ar cmd1
 is zero;
 .Dq Li \*(Ba\*(Ba
-is the opposite \*(en
-.Ar cmd2
+.No is the opposite\*(EM Ns Ar cmd2
 is executed only if the exit status of
 .Ar cmd1
 is non-zero.
@@ -681,29 +691,14 @@
 .Pp
 .Dl $ { echo foo; echo bar }
 .Bl -tag -width 4n
-.It Pq Ar list
-Execute
-.Ar list
-in a subshell.
-There is no implicit way to pass environment changes from a
-subshell back to its parent.
-.It { Ar list ; No }
-Compound construct;
-.Ar list
-is executed, but not in a subshell.
-Note that
-.Dq Li {
-and
-.Dq Li }
-are reserved words, not meta-characters.
-.It Xo case Ar word No in
+.It Xo Ic case Ar word Ic in
 .Oo Op \&(
 .Ar pattern
 .Op \*(Ba Ar pattern
 .No ... Ns )
 .Ar list
-.Ic terminator
-.Oc No ... esac
+.Aq terminator
+.Oc No ... Ic esac
 .Xc
 The
 .Ic case
@@ -731,12 +726,12 @@
 For historical reasons, open and close braces may be used instead of
 .Ic in
 and
-.Ic esac
-e.g.\&
-.Ic case $foo { *) echo bar ;; } .
+.Ic esac ,
+for example:
+.Dq Li case $foo { (ba[rz]\*(Bablah) date ;; }
 .Pp
 The list
-.Ic terminator Ns s
+.Ao terminator Ac Ns s
 are:
 .Bl -tag -width 4n
 .It Dq Li ;;
@@ -754,9 +749,9 @@
 if no
 .Ar list
 is executed, the exit status is zero.
-.It Xo for Ar name
-.Oo in Ar word No ... Oc ;
-.No do Ar list ; No done
+.It Xo Ic for Ar name
+.Oo Ic in Ar word ... Oc Ic ;
+.Ic do Ar list ; Ic done
 .Xc
 For each
 .Ar word
@@ -765,16 +760,6 @@
 is set to the word and
 .Ar list
 is executed.
-If
-.Ic in
-is not used to specify a word list, the positional parameters ($1, $2,
-etc.) are used instead.
-For historical reasons, open and close braces may be used instead of
-.Ic do
-and
-.Ic done
-e.g.\&
-.Ic for i; { echo $i; } .
 The exit status of a
 .Ic for
 statement is the last exit status of
@@ -782,13 +767,52 @@
 if
 .Ar list
 is never executed, the exit status is zero.
-.It Xo if Ar list ;
-.No then Ar list ;
-.Oo elif Ar list ;
-.No then Ar list ; Oc
+If
+.Ic in
+is not used to specify a word list, the positional parameters ($1, $2,
+etc.) are used instead; in this case, use a newline instead of the semicolon
+.Pq Sq Ic ;\&
+for portability.
+For historical reasons, open and close braces may be used instead of
+.Ic do
+and
+.Ic done ,
+as in
+.Dq Li for i; { echo $i; }
+.Pq not portable .
+.It Xo Ic function Ar name
+.No { Ar list ; No }
+.Xc
+Defines the function
+.Ar name
+(see
+.Sx Functions
+below).
+All redirections specified after a function definition are performed whenever
+the function is executed, not when the function definition is executed.
+.It Ar name Ns \&() Ar command
+Mostly the same as
+.Ic function
+(see above and
+.Sx Functions
+below).
+Most amounts of space and tab after
+.Ar name
+will be ignored.
+.It Xo Ic function Ar name Ns \&()
+.No { Ar list ; No }
+.Xc
+.Nm bash Ns ism for
+.Ar name Ns Li ()\ {
+.Ar list Ns Li ;\ }
+.Pq the Ic function No keyword is ignored .
+.It Xo Ic if Ar list ;
+.Ic then Ar list ;
+.Oo Ic elif Ar list ;
+.Ic then Ar list ; Oc
 .No ...
-.Oo else Ar list ; Oc
-.No fi
+.Oo Ic else Ar list ; Oc
+.Ic fi
 .Xc
 If the exit status of the first
 .Ar list
@@ -810,21 +834,22 @@
 is executed.
 The exit status of an
 .Ic if
-statement is that of non-conditional
+statement is that of whatever non-conditional
+.Pq not the first
 .Ar list
 that is executed; if no non-conditional
 .Ar list
 is executed, the exit status is zero.
-.It Xo select Ar name
-.Oo in Ar word No ... Oc ;
-.No do Ar list ; No done
+.It Xo Ic select Ar name
+.Oo Ic in Ar word No ... Oc ;
+.Ic do Ar list ; Ic done
 .Xc
 The
 .Ic select
 statement provides an automatic method of presenting the user with a menu and
 selecting from it.
 An enumerated list of the specified
-.Ar word Ns (s)
+.Ar word Ns s
 is printed on standard error, followed by a prompt
 .Po
 .Ev PS3 :
@@ -836,7 +861,7 @@
 .Ar name
 is set to the selected word (or unset if the selection is not valid),
 .Ev REPLY
-is set to what was read (leading/trailing space is stripped), and
+is set to what was read (leading and trailing space is stripped), and
 .Ar list
 is executed.
 If a blank line (i.e. zero or more
@@ -853,74 +878,20 @@
 is read, an interrupt is received, or a
 .Ic break
 statement is executed inside the loop.
-If
-.Dq in Ar word ...
-is omitted, the positional parameters are used
-(i.e. $1, $2, etc.).
-For historical reasons, open and close braces may be used instead of
-.Ic do
-and
-.Ic done
-e.g.\&
-.Ic select i; { echo $i; } .
 The exit status of a
 .Ic select
 statement is zero if a
 .Ic break
 statement is used to exit the loop, non-zero otherwise.
-.It Xo until Ar list ;
-.No do Ar list ;
-.No done
-.Xc
-This works like
-.Ic while ,
-except that the body is executed only while the exit status of the first
-.Ar list
-is non-zero.
-.It Xo while Ar list ;
-.No do Ar list ;
-.No done
-.Xc
-A
-.Ic while
-is a pre-checked loop.
-Its body is executed as often as the exit status of the first
-.Ar list
-is zero.
-The exit status of a
-.Ic while
-statement is the last exit status of the
-.Ar list
-in the body of the loop; if the body is not executed, the exit status is zero.
-.It Xo function Ar name
-.No { Ar list ; No }
-.Xc
-Defines the function
-.Ar name
-(see
-.Sx Functions
-below).
-Note that redirections specified after a function definition are
-performed whenever the function is executed, not when the function definition
-is executed.
-.It Ar name Ns \&() Ar command
-Mostly the same as
-.Ic function
-(see
-.Sx Functions
-below).
-Whitespace (space or tab) after
-.Ar name
-will be ignored most of the time.
-.It Xo function Ar name Ns \&()
-.No { Ar list ; No }
-.Xc
-The same as
-.Ar name Ns \&()
-.Pq Nm bash Ns ism .
-The
-.Ic function
-keyword is ignored.
+If
+.Dq Ic in Ar word ...
+is omitted, the positional parameters are used.
+For historical reasons, open and close braces may be used instead of
+.Ic do
+and
+.Ic done ,
+as in:
+.Dq Li select i; { echo $i; }
 .It Xo Ic time Op Fl p
 .Op Ar pipeline
 .Xc
@@ -929,16 +900,32 @@
 section describes the
 .Ic time
 reserved word.
-.It \&(( Ar expression No ))
-The arithmetic expression
-.Ar expression
-is evaluated; equivalent to
-.Dq Li let \&" Ns Ar expression Ns \&"
-(see
-.Sx Arithmetic expressions
-and the
-.Ic let
-command, below) in a compound construct.
+.It Xo Ic until Ar list ;
+.Ic do Ar list ; Ic done
+.Xc
+This works like
+.Ic while Pq see below ,
+except that the body
+.Ar list
+is executed only while the exit status of the first
+.Ar list
+is non-zero.
+.It Xo Ic while Ar list ;
+.Ic do Ar list ; Ic done
+.Xc
+A
+.Ic while
+is a pre-checked loop.
+Its body
+.Ar list
+is executed as often as the exit status of the first
+.Ar list
+is zero.
+The exit status of a
+.Ic while
+statement is the last exit status of the
+.Ar list
+in the body of the loop; if the body is not executed, the exit status is zero.
 .It Bq Bq Ar \ \&expression\ \&
 Similar to the
 .Ic test
@@ -947,7 +934,7 @@
 commands (described later), with the following exceptions:
 .Bl -bullet
 .It
-Field splitting and file name generation are not performed on arguments.
+Field splitting and globbing are not performed on arguments.
 .It
 The
 .Fl a
@@ -955,23 +942,22 @@
 and
 .Fl o
 .Pq OR
-operators are replaced with
-.Dq Li &&
+operators are replaced, respectively, with
+.Dq Ic &&
 and
-.Dq Li \*(Ba\*(Ba ,
-respectively.
+.Dq Ic \*(Ba\*(Ba .
 .It
 Operators (e.g.\&
-.Dq Li \-f ,
-.Dq Li = ,
-.Dq Li \&! )
+.Dq Ic \-f ,
+.Dq Ic = ,
+.Dq Ic \&! )
 must be unquoted.
 .It
 Parameter, command and arithmetic substitutions are performed as expressions
 are evaluated and lazy expression evaluation is used for the
-.Dq Li &&
+.Dq Ic &&
 and
-.Dq Li \*(Ba\*(Ba
+.Dq Ic \*(Ba\*(Ba
 operators.
 This means that in the following statement,
 .Ic $(\*(Ltfoo)
@@ -983,23 +969,47 @@
 .Ed
 .It
 The second operand of the
-.Dq Li !=
+.Dq Ic =
 and
-.Dq Li =
-expressions are a subset of patterns (e.g. the comparison
+.Dq Ic !=
+expressions is a pattern (e.g. the comparison
 .Ic \&[[ foobar = f*r ]]
 succeeds).
-This even works indirectly:
+This even works indirectly, while quoting forces literal interpretation:
 .Bd -literal -offset indent
-$ bar=foobar; baz=\*(aqf*r\*(aq
-$ [[ $bar = $baz ]]; echo $?
-$ [[ $bar = \&"$baz" ]]; echo $?
+$ bar=foobar; baz=\*(aqf*r\*(aq         # or: baz=\*(aqf+(o)b?r\*(aq
+$ [[ $bar = $baz ]]; echo $?    # 0
+$ [[ $bar = \&"$baz" ]]; echo $?  # 1
 .Ed
-.Pp
-Perhaps surprisingly, the first comparison succeeds,
-whereas the second doesn't.
-This does not apply to all extglob metacharacters, currently.
 .El
+.It { Ar list ; No }
+Compound construct;
+.Ar list
+is executed, but not in a subshell.
+.br
+Note that
+.Dq Li {
+and
+.Dq Li }
+are reserved words, not meta-characters.
+.It Pq Ar list
+Execute
+.Ar list
+in a subshell, forking.
+There is no implicit way to pass environment changes from a
+subshell back to its parent.
+.It \&(( Ar expression No ))
+The arithmetic expression
+.Ar expression
+is evaluated; equivalent to
+.Sq Li let \&" Ns Ar expression Ns \&"
+in a compound construct.
+.br
+See the
+.Ic let
+command and
+.Sx Arithmetic expressions
+below.
 .El
 .Ss Quoting
 Quoting is used to prevent the shell from treating characters or words
@@ -1078,9 +1088,11 @@
 .Dq Li \eU########
 and
 .Dq Li \eu#### ,
-.Dq #
-means a hexadecimal digit, of which there may be none up to four or eight;
-these escapes translate a Universal Coded Character Set codepoint to UTF-8.
+.Sq Li #
+means a hexadecimal digit (up to 4 or 8); these translate a
+Universal Coded Character Set codepoint to UTF-8 (see
+.Sx CAVEATS
+on UCS limitations).
 Furthermore,
 .Dq Li \eE
 and
@@ -1090,44 +1102,48 @@
 In the
 .Ic print
 builtin mode,
-.Dq Li \e" ,
-.Dq Li \e\*(aq
-and
-.Dq Li \e?
-are explicitly excluded;
-octal sequences must have the none up to three octal digits
-.Dq #
+octal sequences must have the optional up to three octal digits
+.Sq Li #
 prefixed with the digit zero
 .Pq Dq Li \e0### ;
 hexadecimal sequences
 .Dq Li \ex##
-are limited to none up to two hexadecimal digits
-.Dq # ;
+are limited to up to two hexadecimal digits
+.Sq Li # ;
 both octal and hexadecimal sequences convert to raw octets;
-.Dq Li \e# ,
-where # is none of the above, translates to \e# (backslashes are retained).
-.Pp
-Backslash expansion in the C style mode slightly differs: octal sequences
-.Dq Li \e###
-must have no digit zero prefixing the one up to three octal digits
-.Dq #
-and yield raw octets; hexadecimal sequences
-.Dq Li \ex#*
-greedily eat up as many hexadecimal digits
-.Dq #
-as they can and terminate with the first non-hexadecimal digit;
-these translate a Universal Coded Character Set codepoint to UTF-8.
-The sequence
-.Dq Li \ec# ,
+.Dq Li \e% ,
 where
-.Dq #
-is any octet, translates to Ctrl-# (which basically means,
+.Sq Li %
+is none of the above, translates to
+.Li \e%
+.Pq backslashes are retained .
+.Pp
+In C style mode, raw octet-yielding octal sequences
+.Dq Li \e###
+must not have the one up to three octal digits prefixed with the
+digit zero; hexadecimal sequences
+.Dq Li \ex##
+greedily eat up as many hexadecimal digits
+.Sq Li #
+as they can and terminate with the first non-xdigit; below
+.Li \ex100
+these produce raw octets; above, they are equivalent to
+.Dq Li \eU# .
+The sequence
+.Dq Li \ec% ,
+where
+.Sq Li %
+is any octet, translates to
+.Ic Ctrl- Ns Li % ,
+that is,
 .Dq Li \ec?
-becomes DEL, everything else is bitwise ANDed with 0x1F).
-Finally,
-.Dq Li \e# ,
-where # is none of the above, translates to # (has the backslash trimmed),
-even if it is a newline.
+becomes DEL, everything else is bitwise ANDed with 0x9F.
+.Dq Li \e% ,
+where
+.Sq Li %
+is none of the above, translates to
+.Li % :
+backslashes are trimmed even before newlines.
 .Ss Aliases
 There are two types of aliases: normal command aliases and tracked aliases.
 Command aliases are normally used as a short hand for a long or often used
@@ -1438,10 +1454,10 @@
 must be unquoted for the shell to recognise a parameter assignment.
 The construct
 .Ic FOO+=baz
-is also recognised; the old and new values are immediately concatenated.
+is also recognised;
+the old and new values are string-concatenated with no separator.
 The fourth way of setting a parameter is with the
 .Ic export ,
-.Ic global ,
 .Ic readonly
 and
 .Ic typeset
@@ -1623,8 +1639,6 @@
 .Ql #
 results in the shortest match, and two
 of them result in the longest match.
-Cannot be applied to a vector
-.Pq ${*} or ${@} or ${array[*]} or ${array[@]} .
 .Pp
 .Sm off
 .It Xo
@@ -1636,8 +1650,7 @@
 .Pf %% Ar pattern No }
 .Xc
 .Sm on
-Like ${...#...} substitution, but it deletes from the end of the value.
-Cannot be applied to a vector.
+Like ${...#...} but deletes from the end of the value.
 .Pp
 .Sm off
 .It Xo
@@ -1680,8 +1693,7 @@
 that matches the empty string causes the replacement to
 happen only once; two leading slashes cause all occurrences
 of matches in the value to be replaced.
-Cannot be applied to a vector.
-Inefficiently implemented, may be slow.
+May be slow on long strings.
 .Pp
 .Sm off
 .It Xo
@@ -1701,6 +1713,8 @@
 and
 .Ar string
 are expanded anew for each iteration.
+Use with
+.Ev KSH_MATCH .
 .Pp
 .Sm off
 .It Xo
@@ -1733,10 +1747,6 @@
 and
 .Ar len
 are evaluated as arithmetic expressions.
-Currently,
-.Ar pos
-must start with a space, opening parenthesis or digit to be recognised.
-Cannot be applied to a vector.
 .Pp
 .It Pf ${ Ns Ar name Ns @#}
 The hash (using the BAFH algorithm) of the expansion of
@@ -1793,12 +1803,11 @@
 .Fl c
 option and arguments were given; otherwise the
 .Ar file
-argument, if it was supplied;
-or else the basename the shell was invoked with (i.e.\&
-.Li argv[0] ) .
+argument, if it was supplied; or else the name the shell was invoked with
+.Pq i.e.\& Li argv[0] .
 .Ev $0
-is also set to the name of the current script or
-the name of the current function, if it was defined with the
+is also set to the name of the current script,
+or to the name of the current function if it was defined with the
 .Ic function
 keyword (i.e. a Korn shell style function).
 .It Ev 1 No .. Ev 9
@@ -1924,9 +1933,7 @@
 .It Ev HISTSIZE
 The number of commands normally stored for history.
 The default is 2047.
-Do not set this value to insanely high values such as 1000000000 because
-.Nm
-can then not allocate enough memory for the history and will not start.
+The maximum is 65535.
 .It Ev HOME
 The default directory for the
 .Ic cd
@@ -1948,16 +1955,16 @@
 This parameter is not imported from the environment when the shell is
 started.
 .It Ev KSHEGID
-The effective group id of the shell.
+The effective group id of the shell at startup.
 .It Ev KSHGID
-The real group id of the shell.
+The real group id of the shell at startup.
 .It Ev KSHUID
-The real user id of the shell.
+The real user id of the shell at startup.
 .It Ev KSH_MATCH
 The last matched string.
 In a future version, this will be an indexed array,
 with indexes 1 and up capturing matching groups.
-Set by string comparisons (== and !=) in double-bracket test
+Set by string comparisons (= and !=) in double-bracket test
 expressions when a match is found (when != returns false), by
 .Ic case
 when a match is encountered, and by the substitution operations
@@ -2018,7 +2025,7 @@
 .Xc
 See the end of the Emacs editing mode documentation for an example.
 .It Ev KSH_VERSION
-The name and version of the shell (read-only).
+The name (self-identification) and version of the shell (read-only).
 See also the version commands in
 .Sx Emacs editing mode
 and
@@ -2203,7 +2210,7 @@
 files are created in
 .Pa /tmp .
 .It Ev USER_ID
-The effective user id of the shell.
+The effective user id of the shell at startup.
 .El
 .Ss Tilde expansion
 Tilde expansion, which is done in parallel with parameter substitution,
@@ -2699,16 +2706,16 @@
 is also supported.
 Note that NUL bytes (integral value of zero) cannot be used.
 An unset or empty parameter evaluates to 0 in integer context.
-In UTF-8 mode, raw octets are mapped into the range EF80..EFFF as in
-OPTU-8, which is in the PUA and has been assigned by CSUR for this use.
-If more than one octet in ASCII mode, or a sequence of more than one
-octet not forming a valid and minimal CESU-8 sequence is passed, the
-behaviour is undefined (usually, the shell aborts with a parse error,
-but rarely, it succeeds, e.g. on the sequence C2 20).
-That's why you should always use ASCII mode unless you know that the
-input is well-formed UTF-8 in the range of 0000..FFFD if you use this
-feature, as opposed to
-.Ic read Fl a .
+If
+.Sq Li x
+isn't comprised of exactly one valid character, the behaviour is undefined
+(usually, the shell aborts with a parse error, but rarely, it succeeds,
+e.g. on the sequence C2 20); users of this feature (as opposed to
+.Ic read Fl a )
+must validate the input first.
+See
+.Sx CAVEATS
+for UTF-8 mode handling.
 .Pp
 The operators are evaluated as follows:
 .Bl -tag -width Ds -offset indent
@@ -2965,7 +2972,7 @@
 will only exit that subshell and will not cause the original shell to exit
 a running function (see the
 .Ic while Ns Li \&... Ns Ic read
-loop FAQ below).
+loop FAQ).
 .Pp
 Functions defined with the
 .Ic function
@@ -2978,9 +2985,6 @@
 The $0 parameter is set to the name of the function
 (Bourne-style functions leave $0 untouched).
 .It
-Parameter assignments preceding function calls are not kept in the shell
-environment (executing Bourne-style functions will keep assignments).
-.It
 .Ev OPTIND
 is saved/reset and restored on entry and exit from the function so
 .Ic getopts
@@ -3026,11 +3030,6 @@
 .Ev PATH
 parameter is not used to find them.
 .Pp
-The original
-.Nm ksh
-and POSIX differ somewhat in which commands are considered
-special or regular.
-.Pp
 POSIX special built-in utilities:
 .Pp
 .Ic \&. , \&: , break , continue ,
@@ -3042,11 +3041,11 @@
 .Nm
 commands keeping assignments:
 .Pp
-.Ic global , source , typeset
+.Ic source , typeset
 .Pp
-Builtins that are not special:
+All other builtins are not special; these are at least:
 .Pp
-.Ic [ , alias , bg , bind ,
+.Ic [\& , alias , bg , bind ,
 .Ic builtin , cat , cd , command ,
 .Ic echo , false , fc , fg ,
 .Ic getopts , jobs , kill , let ,
@@ -3059,10 +3058,11 @@
 assignments are performed and exported for the duration of the command.
 .Pp
 The following describes the special and regular built-in commands and
-builtin-like reserved words:
+builtin-like reserved words, as well as some optional utilities:
 .Pp
 .Bl -tag -width false -compact
 .It Ic \&. Ar file Op Ar arg ...
+.Pq keeps assignments , special
 This is called the
 .Dq dot
 command.
@@ -3079,22 +3079,62 @@
 those of the environment the command is used in.
 .Pp
 .It Ic \&: Op Ar ...
+.Pq keeps assignments , special
 The null command.
+.br
 Exit status is set to zero.
 .Pp
+.It Ic Lb64decode Op Ar string
+.Pq Li dot.mkshrc No function
+Decode
+.Ar string
+or standard input to binary.
+.Pp
+.It Ic Lb64encode Op Ar string
+.Pq Li dot.mkshrc No function
+Encode
+.Ar string
+or standard input as base64.
+.Pp
+.It Ic Lbafh_init
+.It Ic Lbafh_add Op Ar string
+.It Ic Lbafh_finish
+.Pq Li dot.mkshrc No functions
+Implement the Better Avalance for the Jenkins Hash.
+This is the same hash
+.Nm
+currently uses internally.
+.No "After calling" Ic Lbafh_init , No call Ic Lbafh_add
+multiple times until all input is read, then call
+.Ic Lbafh_finish ,
+which writes the result to the unsigned integer
+.Va Lbafh_v
+variable for your consumption.
+.Pp
+.It Ic Lstripcom Op Ar
+.Pq Li dot.mkshrc No function
+Same as
+.Ic cat
+but strips any empty lines and comments (from any
+.Sq #
+character onwards, no escapes)
+and reduces any amount of whitespace to one space character.
+.Pp
 .It Ic \&[ Ar expression Ic \&]
+.Pq regular
 See
 .Ic test .
 .Pp
 .It Xo Ic alias
 .Oo Fl d \*(Ba t Oo Fl r Oc \*(Ba
-.Cm +\-x Oc
+.Fl +x Oc
 .Op Fl p
 .Op Cm +
 .Oo Ar name
 .Op Ns = Ns Ar value
 .Ar ... Oc
 .Xc
+.Pq regular
 Without arguments,
 .Ic alias
 lists all aliases.
@@ -3102,8 +3142,8 @@
 Any name with a value defines an alias; see
 .Sx Aliases
 above.
-.Li \&[][A\-Za\-z0\-9_!%,.@:\-]
-are valid in names, except they may not begin with a hyphen-minus, and
+.Li \&[][A\-Za\-z0\-9_!%+,.@:\-]
+are valid in names, except they may not begin with a plus or hyphen-minus, and
 .Ic \&[[
 is not a valid alias name.
 .Pp
@@ -3112,7 +3152,7 @@
 .Ar name Ns = Ns Ar value ,
 where
 .Ar value
-is quoted.
+is quoted as necessary.
 If options were preceded with
 .Ql + ,
 or a lone
@@ -3128,15 +3168,17 @@
 .Sx Tilde expansion
 above).
 .Pp
-If the
-.Fl p
-option is used, each alias is prefixed with the string
-.Dq Li alias\ \& .
+With
+.Fl p ,
+each alias is listed with the string
+.Dq Li alias\ \&
+prefixed.
 .Pp
 The
 .Fl t
-option indicates that tracked aliases are to be listed/set (values specified on
-the command line are ignored for tracked aliases).
+option indicates that tracked aliases are to be listed/set (values given
+with the command are ignored for tracked aliases).
+.Pp
 The
 .Fl r
 option indicates that all tracked aliases are to be reset.
@@ -3148,7 +3190,14 @@
 the export attribute of an alias, or, if no names are given, lists the aliases
 with the export attribute (exporting an alias has no effect).
 .Pp
+.It Ic autoload
+.Pq built-in alias
+See
+.Sx Functions
+above.
+.Pp
 .It Ic bg Op Ar job ...
+.Pq regular , needs job control
 Resume the specified stopped job(s) in the background.
 If no jobs are specified,
 .Ic %+
@@ -3157,70 +3206,62 @@
 .Sx Job control
 below for more information.
 .Pp
-.It Ic bind Op Fl l
-The current bindings are listed.
-If the
-.Fl l
-flag is given,
-.Ic bind
-instead lists the names of the functions to which keys may be bound.
+.It Ic bind Fl l
+.Pq regular
+The names of editing commands strings can be bound to are listed.
 See
 .Sx Emacs editing mode
 for more information.
 .Pp
-.It Xo Ic bind Op Fl m
-.Ar string Ns = Ns Op Ar substitute
-.Ar ...
-.Xc
+.It Ic bind Op Ar string ...
+The current bindings, for
+.Ar string ,
+if given, else all, are listed.
+.Em Note:
+Default prefix bindings
+.Pq 1=Esc , 2=\*(haX , 3=NUL
+assumed.
+.Pp
 .It Xo Ic bind
 .Ar string Ns = Ns Op Ar editing-command
-.Ar ...
+.Op Ar ...
 .Xc
-The specified editing command is bound to the given
+.It Xo Ic bind Fl m
+.Ar string Ns = Ns Ar substitute
+.Op Ar ...
+.Xc
+To
 .Ar string ,
 which should consist of a control character
-optionally preceded by one of the two prefix characters
-and optionally succeeded by a tilde character.
-Future input of the
+optionally preceded by one of the three prefix characters
+and optionally succeeded by a tilde character, the
+.Ar editing-command
+is bound so that future input of the
 .Ar string
-will cause the editing command to be immediately invoked.
-If the
+will immediately invoke that editing command.
+If a tilde postfix is given, a tilde trailing the control character is ignored.
+If
 .Fl m
-flag is given, the specified input
+.Pq macro
+is given, future input of the
 .Ar string
-will afterwards be immediately replaced by the given
+will be replaced by the given NUL-terminated
 .Ar substitute
-string which may contain editing commands but not other macros.
-If a tilde postfix is given, a tilde trailing the one or
-two prefices and the control character is ignored, any
-other trailing character will be processed afterwards.
+string, wherein prefix/control/tilde characters mapped to editing commands
+.Pq but not those mapped to other macros
+will be processed.
 .Pp
-Control characters may be written using caret notation
-i.e. \*(haX represents Ctrl-X.
-The caret itself can be escaped by a backslash, which also escapes itself.
-Note that although only three prefix characters (usually ESC, \*(haX and NUL)
+Prefix and control characters may be written using caret notation, i.e.\&
+.No \*(ha Ns Li Z
+represents
+.No Ctrl- Ns Li Z .
+Use a backslash to escape the caret, an equals sign or another backslash.
+Note that, although only three prefix characters
+.Pq usually Esc, \*(haX and NUL
 are supported, some multi-character sequences can be supported.
 .Pp
-The following default bindings show how the arrow keys, the home, end and
-delete key on a BSD wsvt25, xterm\-xfree86 or GNU screen terminal are bound
-(of course some escape sequences won't work out quite this nicely):
-.Bd -literal -offset indent
-bind \*(aq\*(haX\*(aq=prefix\-2
-bind \*(aq\*(ha[[\*(aq=prefix\-2
-bind \*(aq\*(haXA\*(aq=up\-history
-bind \*(aq\*(haXB\*(aq=down\-history
-bind \*(aq\*(haXC\*(aq=forward\-char
-bind \*(aq\*(haXD\*(aq=backward\-char
-bind \*(aq\*(haX1\*(TI\*(aq=beginning\-of\-line
-bind \*(aq\*(haX7\*(TI\*(aq=beginning\-of\-line
-bind \*(aq\*(haXH\*(aq=beginning\-of\-line
-bind \*(aq\*(haX4\*(TI\*(aq=end\-of\-line
-bind \*(aq\*(haX8\*(TI\*(aq=end\-of\-line
-bind \*(aq\*(haXF\*(aq=end\-of\-line
-bind \*(aq\*(haX3\*(TI\*(aq=delete\-char\-forward
-.Ed
-.Pp
 .It Ic break Op Ar level
+.Pq keeps assignments , special
 Exit the
 .Ar level Ns th
 inner-most
@@ -3238,6 +3279,7 @@
 .Op Fl \-
 .Ar command Op Ar arg ...
 .Xc
+.Pq regular
 Execute the built-in command
 .Ar command .
 .Pp
@@ -3245,6 +3287,7 @@
 .Ic \ebuiltin
 .Ar command Op Ar arg ...
 .Xc
+.Pq regular , decl-forwarder
 Same as
 .Ic builtin .
 Additionally acts as declaration utility forwarder, i.e. this is a
@@ -3258,8 +3301,8 @@
 .Op Fl u
 .Op Ar
 .Xc
-Read files sequentially, in command line order, and write them to
-standard output.
+.Pq defer with flags
+Copy files in command line order to standard output.
 If a
 .Ar file
 is a single dash
@@ -3288,6 +3331,7 @@
 .Op Fl eLP
 .Op Ar dir
 .Xc
+.Pq regular
 Set the working directory to
 .Ar dir .
 If the parameter
@@ -3355,6 +3399,7 @@
 .Op Fl eLP
 .Ar old new
 .Xc
+.Pq regular
 The string
 .Ar new
 is substituted for
@@ -3362,12 +3407,17 @@
 in the current directory, and the shell attempts to change to the new
 directory.
 .Pp
+.It Ic cls
+.Pq Li dot.mkshrc No alias
+Reinitialise the display (hard reset).
+.Pp
 .It Xo
 .Ic command
 .Op Fl pVv
 .Ar cmd
 .Op Ar arg ...
 .Xc
+.Pq regular , decl-forwarder
 If neither the
 .Fl v
 nor
@@ -3383,20 +3433,18 @@
 and secondly, special built-in commands lose their specialness
 (i.e. redirection and utility errors do not cause the shell to
 exit, and command assignments are not permanent).
-The declaration utility property is not reset.
 .Pp
 If the
 .Fl p
-option is given, a default search path is used instead of the current value of
-.Ev PATH ,
-the actual value of which is system dependent.
+option is given, a default search path, whose actual value is
+system-dependent, is used instead of the current
+.Ev PATH .
 .Pp
 If the
 .Fl v
 option is given, instead of executing
 .Ar cmd ,
-information about what would be executed is given (and the same is done for
-.Ar arg ... ) .
+information about what would be executed is given for each argument.
 For builtins, functions and keywords, their names are simply printed;
 for aliases, a command that defines them is printed;
 for utilities found by searching the
@@ -3410,9 +3458,10 @@
 .Fl V
 option is like the
 .Fl v
-option, except it is more verbose.
+option, but more verbose.
 .Pp
 .It Ic continue Op Ar level
+.Pq keeps assignments , special
 Jumps to the beginning of the
 .Ar level Ns th
 inner-most
@@ -3425,17 +3474,33 @@
 .Ar level
 defaults to 1.
 .Pp
+.It Ic dirs Op Fl lnv
+.Pq Li dot.mkshrc No function
+Print the directory stack.
+.Fl l
+causes tilde expansion to occur in the output.
+.Fl n
+causes line wrapping before 80 columns, whereas
+.Fl v
+causes numbered vertical output.
+.Pp
+.It Ic doch
+.Pq Li dot.mkshrc No alias
+Execute the last command with
+.Xr sudo 8 .
+.Pp
 .It Xo
 .Ic echo
 .Op Fl Een
 .Op Ar arg ...
 .Xc
+.Pq regular
 .Em Warning:
-this utility is not portable; use the Korn shell builtin
+this utility is not portable; use the standard Korn shell built-in utility
 .Ic print
-instead.
+in new code instead.
 .Pp
-Prints its arguments (separated by spaces) followed by a newline, to the
+Print arguments, separated by spaces, followed by a newline, to
 standard output.
 The newline is suppressed if any of the arguments contain the
 backslash sequence
@@ -3448,12 +3513,13 @@
 .Bx
 shell scripts.
 The
-.Fl n
-option suppresses the trailing newline,
-.Fl e
-enables backslash interpretation (a no-op, since this is normally done), and
 .Fl E
-suppresses backslash interpretation.
+option suppresses backslash interpretation,
+.Fl e
+enables it (normally default),
+.Fl n
+suppresses the trailing newline,
+and anything else causes the word to be printed as argument instead.
 .Pp
 If the
 .Ic posix
@@ -3466,9 +3532,40 @@
 .Dq Li \-n .
 Backslash interpretation is disabled.
 .Pp
+.It Xo
+.Ic enable
+.Op Fl anps
+.Op Ar name ...
+.Xc
+.Pq Li dot.mkshrc No function
+Hide and unhide built-in utilities, aliases and functions and those defined in
+.Li dot.mkshrc .
+.Pp
+If no
+.Ar name
+is given or the
+.Fl p
+option is used, builtins are printed (behind the string
+.Dq Li enable\ \& ,
+followed by
+.Dq Li \-n\ \&
+if the builtin is currently disabled), otherwise, they are disabled (if
+.Fl n
+is given) or re-enabled.
+.Pp
+When printing, only enabled builtins are printed by default; the
+.Fl a
+options prints all builtins, while
+.Fl n
+prints only disabled builtins instead;
+.Fl s
+limits the list to POSIX special builtins.
+.Pp
 .It Ic eval Ar command ...
-The arguments are concatenated (with spaces between them) to form a single
-string which the shell then parses and executes in the current environment.
+.Pq keeps assignments , special
+The arguments are concatenated, with a space between each,
+to form a single string which the shell then parses
+and executes in the current execution environment.
 .Pp
 .It Xo
 .Ic exec
@@ -3476,8 +3573,9 @@
 .Op Fl c
 .Op Ar command Op Ar arg ...
 .Xc
-The command is executed without forking, replacing the shell process.
-This is currently absolute, i.e.\&
+.Pq keeps assignments , special
+The command (with arguments) is executed without forking,
+fully replacing the shell process; this is absolute, i.e.\&
 .Ic exec
 never returns, even if the
 .Ar command
@@ -3490,7 +3588,7 @@
 .Fl c
 clears the environment before executing the child process, except for the
 .Ev _
-variable and direct assignments.
+parameter and direct assignments.
 .Pp
 If no command is given except for I/O redirection, the I/O redirection is
 permanent and the shell is
@@ -3503,32 +3601,38 @@
 it does pass these file descriptors on.
 .Pp
 .It Ic exit Op Ar status
-The shell or subshell exits with the specified exit status.
-If
-.Ar status
-is not specified, the exit status is the current value of the
-.Ic \&$?
-parameter.
+.Pq keeps assignments , special
+The shell or subshell exits with the specified errorlevel
+(or the current value of the
+.Va $?\&
+parameter).
 .Pp
 .It Xo
 .Ic export
 .Op Fl p
 .Op Ar parameter Ns Op = Ns Ar value
 .Xc
+.Pq keeps assignments , special, decl-util
 Sets the export attribute of the named parameters.
 Exported parameters are passed in the environment to executed commands.
 If values are specified, the named parameters are also assigned.
 This is a declaration utility.
 .Pp
 If no parameters are specified, all parameters with the export attribute
-set are printed one per line; either their names, or, if a
+set are printed one per line: either their names, or, if a
 .Dq Li \-
-with no option letter is specified, name=value pairs, or, with
-.Fl p ,
+with no option letter is specified, name=value pairs, or, with the
+.Fl p
+option,
 .Ic export
 commands suitable for re-entry.
 .Pp
+.It Ic extproc
+.Pq OS/2
+Null command required for shebang-like functionality.
+.Pp
 .It Ic false
+.Pq regular
 A command that exits with a non-zero status.
 .Pp
 .It Xo
@@ -3538,6 +3642,7 @@
 .Op Fl r
 .Op Ar first Op Ar last
 .Xc
+.Pq regular
 .Ar first
 and
 .Ar last
@@ -3563,7 +3668,7 @@
 .Ev FCEDIT
 parameter (if this parameter is not set,
 .Pa /bin/ed
-is used), and then executed by the shell.
+is used), and the result is executed by the shell.
 .Pp
 .It Xo
 .Ic fc
@@ -3572,6 +3677,7 @@
 .Op Ar old Ns = Ns Ar new
 .Op Ar prefix
 .Xc
+.Pq regular
 Re-execute the selected command (the previous command by default) after
 performing the optional substitution of
 .Ar old
@@ -3592,71 +3698,73 @@
 .Ic alias r=\*(aqfc \-e \-\*(aq
 .Pp
 .It Ic fg Op Ar job ...
+.Pq regular , needs job control
 Resume the specified job(s) in the foreground.
 If no jobs are specified,
 .Ic %+
 is assumed.
+.br
 See
 .Sx Job control
 below for more information.
 .Pp
+.It Ic functions Op Ar name ...
+.Pq built-in alias
+Display the function definition commands corresponding to the listed,
+or all defined, functions.
+.Pp
 .It Xo
 .Ic getopts
 .Ar optstring name
 .Op Ar arg ...
 .Xc
+.Pq regular
 Used by shell procedures to parse the specified arguments (or positional
 parameters, if no arguments are given) and to check for legal options.
-.Ar optstring
-contains the option letters that
-.Ic getopts
-is to recognise.
-If a letter is followed by a colon, the option is expected to
-have an argument.
 Options that do not take arguments may be grouped in a single argument.
-If an option takes an argument and the option character is not the
-last character of the argument it is found in, the remainder of the argument is
-taken to be the option's argument; otherwise, the next argument is the option's
-argument.
+If an option takes an argument and the option character is not the last
+character of the word it is found in, the remainder of the word is taken to
+be the option's argument; otherwise, the next word is the option's argument.
+.Pp
+.Ar optstring
+contains the option letters to be recognised.
+If a letter is followed by a colon, the option takes an argument.
 .Pp
 Each time
 .Ic getopts
 is invoked, it places the next option in the shell parameter
-.Ar name
-and the index of the argument to be processed by the next call to
-.Ic getopts
-in the shell parameter
-.Ev OPTIND .
+.Ar name .
 If the option was introduced with a
 .Ql + ,
-the option placed in
+the character placed in
 .Ar name
 is prefixed with a
 .Ql + .
-When an option requires an argument,
-.Ic getopts
-places it in the shell parameter
-.Ev OPTARG .
+If the option takes an argument, it is placed in the shell parameter
+.Ev OPTARG.
 .Pp
 When an illegal option or a missing option argument is encountered, a question
 mark or a colon is placed in
 .Ar name
 (indicating an illegal option or missing argument, respectively) and
 .Ev OPTARG
-is set to the option character that caused the problem.
-Furthermore, if
+is set to the option letter that caused the problem.
+Furthermore, unless
 .Ar optstring
-does not begin with a colon, a question mark is placed in
+begins with a colon, a question mark is placed in
 .Ar name ,
 .Ev OPTARG
-is unset, and an error message is printed to standard error.
+is unset and a diagnostic is shown on standard error.
 .Pp
+.Ic getopts
+records the index of the argument to be processed by the next call in
+.Ev OPTIND .
 When the end of the options is encountered,
 .Ic getopts
-exits with a non-zero exit status.
-Options end at the first (non-option
-argument) argument that does not start with a
-.Ql \- ,
+returns a non-zero exit status.
+Options end at the first argument that does not start with a
+.Ql \-
+.Pq non-option argument
 or when a
 .Dq Li \-\-
 argument is encountered.
@@ -3666,7 +3774,8 @@
 to 1 (this is done automatically whenever the shell or a shell procedure is
 invoked).
 .Pp
-Warning: Changing the value of the shell parameter
+.Em Warning:
+Changing the value of the shell parameter
 .Ev OPTIND
 to a value other than 1 or parsing different sets of arguments without
 resetting
@@ -3674,41 +3783,50 @@
 may lead to unexpected results.
 .Pp
 .It Xo
-.Ic global
-.Op Ic +\-aglpnrtUux
-.Oo Fl L Ns Op Ar n
-.No \*(Ba Fl R Ns Op Ar n
-.No \*(Ba Fl Z Ns Op Ar n Oc
-.Op Fl i Ns Op Ar n
-.Oo Ar name
-.Op Ns = Ns Ar value
-.Ar ... Oc
-.Xc
-See
-.Ic typeset Fl g .
-.No Deprecated , Em will
-be removed from a future version of
-.Nm .
-.Pp
-.It Xo
 .Ic hash
 .Op Fl r
 .Op Ar name ...
 .Xc
-Without arguments, any hashed executable command pathnames are listed.
+.Pq built-in alias
+Without arguments, any hashed executable command paths are listed.
 The
 .Fl r
-option causes all hashed commands to be removed from the hash table.
+option causes all hashed commands to be removed from the cache.
 Each
 .Ar name
-is searched as if it were a command name and added to the hash table if it is
-an executable command.
+is searched as if it were a command name
+and added to the cache if it is an executable command.
+.Pp
+.It Ic hd Op Ar
+.Pq Li dot.mkshrc No alias or function
+Hexdump stdin or arguments legibly.
+.Pp
+.It Xo
+.Ic history
+.Op Fl nr
+.Op Ar first Op Ar last
+.Xc
+.Pq built-in alias
+Same as
+.Ic fc Fl l Pq see above .
+.Pp
+.It Xo
+.Ic integer
+.Op flags
+.Oo Ar name
+.Op Ns = Ns Ar value
+.Ar ... Oc
+.Xc
+.Pq built-in alias
+Same as
+.Ic typeset Fl i Pq see below .
 .Pp
 .It Xo
 .Ic jobs
 .Op Fl lnp
 .Op Ar job ...
 .Xc
+.Pq regular
 Display information about the specified job(s); if no jobs are specified, all
 jobs are displayed.
 The
@@ -3735,6 +3853,7 @@
 .No { Ar job \*(Ba pid \*(Ba pgrp No }
 .Ar ...
 .Xc
+.Pq regular
 Send the specified signal to the specified jobs, process IDs or process
 groups.
 If no signal is specified, the
@@ -3751,23 +3870,36 @@
 .Fl l
 .Op Ar exit-status ...
 .Xc
+.Pq regular
 Print the signal name corresponding to
 .Ar exit-status .
 If no arguments are specified, a list of all the signals with their numbers
 and a short description of each are printed.
 .Pp
 .It Ic let Op Ar expression ...
+.Pq regular
 Each expression is evaluated (see
 .Sx Arithmetic expressions
 above).
-If all expressions are successfully evaluated, the exit status is 0 (1)
-if the last expression evaluated to non-zero (zero).
+If all expressions evaluate successfully, the exit status is
+0 (1) if the last expression evaluated to non-zero (zero).
 If an error occurs during
 the parsing or evaluation of an expression, the exit status is greater than 1.
 Since expressions may need to be quoted,
-.No \&(( Ar expr No ))
+.Li \&(( Ar expr Li ))
 is syntactic sugar for:
-.Dl "{ \e\ebuiltin let \*(aq" Ns Ar expr Ns "\*(aq; }"
+.Dl "{ \e\ebuiltin let \*(aq" Ns Ar expr Ns Li "\*(aq; }"
+.Pp
+.It Xo
+.Ic local
+.Op flags
+.Oo Ar name
+.Op Ns = Ns Ar value
+.Ar ... Oc
+.Xc
+.Pq built-in alias
+Same as
+.Ic typeset Pq see below .
 .Pp
 .It Xo
 .Ic mknod
@@ -3782,8 +3914,9 @@
 .Ar name
 .Cm p
 .Xc
+.Pq optional
 Create a device special file.
-The file type may be
+The file type may be one of
 .Cm b
 (block type device),
 .Cm c
@@ -3806,11 +3939,36 @@
 however, distributors may have added this as builtin as a speed hack.
 .Pp
 .It Xo
+.Ic nameref
+.Op flags
+.Oo Ar name
+.Op Ns = Ns Ar value
+.Ar ... Oc
+.Xc
+.Pq built-in alias
+Same as
+.Ic typeset Fl n Pq see below .
+.Pp
+.It Xo
+.Ic popd
+.Op Fl lnv
+.Op Ic + Ns Ar n
+.Xc
+.Pq Li dot.mkshrc No function
+Pops the directory stack and returns to the new top directory.
+The flags are as in
+.Ic dirs Pq see above .
+A numeric argument
+.Ic + Ns Ar n
+selects the entry in the stack to discard.
+.Pp
+.It Xo
 .Ic print
 .Oo Fl AcelNnprsu Ns Oo Ar n Oc \*(Ba
 .Fl R Op Fl n Oc
 .Op Ar argument ...
 .Xc
+.Pq regular
 Print the specified argument(s) on the standard output,
 separated by spaces, terminated with a newline.
 The escapes mentioned in
@@ -3822,7 +3980,7 @@
 option, are interpreted.
 .Pp
 The options are as follows:
-.Bl -tag -width Ds
+.Bl -tag -width XuXnX
 .It Fl A
 Each
 .Ar argument
@@ -3872,24 +4030,73 @@
 .Dq Li \-n
 .Pq to suppress the trailing newline .
 .Pp
+.It Ic printf Ar format Op Ar arguments ...
+.Pq optional , defer always
+If compiled in, format and print the arguments, supporting the bare
+.Tn POSIX Ns -mandated
+minimum.
+If an external utility of the same name is found, it is deferred to,
+unless run as direct builtin call or from the
+.Ic builtin
+utility.
+.Pp
+.It Ic pushd Op Fl lnv
+.Pq Li dot.mkshrc No function
+Rotate the top two elements of the directory stack.
+The options are the same as for
+.Ic dirs Pq see above ,
+and
+.Ic pushd
+changes to the topmost directory stack entry after acting.
+.Pp
+.It Xo
+.Ic pushd
+.Op Fl lnv
+.Ic + Ns Ar n
+.Xc
+.Pq Li dot.mkshrc No function
+Rotate the element number
+.Ar n
+to the top.
+.Pp
+.It Xo
+.Ic pushd
+.Op Fl lnv
+.Ar name
+.Xc
+.Pq Li dot.mkshrc No function
+Push
+.Ar name
+on top of the stack.
+.Pp
 .It Ic pwd Op Fl LP
+.Pq regular
 Print the present working directory.
-If the
-.Fl L
-option is used or if the
-.Ic physical
-option isn't set (see the
-.Ic set
-command below), the logical path is printed (i.e. the path used to
-.Ic cd
-to the current directory).
-If the
+If no options are given,
+.Ic pwd
+behaves as if the
 .Fl P
-option (physical path) is used or if the
+option (print physical path) was used if the
 .Ic physical
-option is set, the path determined from the filesystem (by following
-.Dq Li ..
-directories to the root directory) is printed.
+shell option is set, the
+.Fl L
+option (print logical path) otherwise.
+The logical path is the path used to
+.Ic cd
+to the current directory;
+the physical path is determined from the filesystem (by following
+.Dq Li ..\&
+directories to the root directory).
+.Pp
+.It Xo
+.Ic r
+.Op Fl g
+.Op Ar old Ns = Ns Ar new
+.Op Ar prefix
+.Xc
+.Pq built-in alias
+Same as
+.Ic fc Fl e \& Pq see above .
 .Pp
 .It Xo
 .Ic read
@@ -3903,20 +4110,17 @@
 .Op Fl rs
 .Op Ar p ...
 .Xc
+.Pq regular
 Reads a line of input, separates the input into fields using the
 .Ev IFS
 parameter (see
 .Sx Substitution
-above), and assigns each field to the specified parameters
+above) or other specified means,
+and assigns each field to the specified parameters
 .Ar p .
 If no parameters are specified, the
 .Ev REPLY
 parameter is used to store the result.
-With the
-.Fl A
-and
-.Fl a
-options, only no or one parameter is accepted.
 If there are more parameters than fields, the extra parameters are set to
 the empty string or 0; if there are more fields than parameters, the last
 parameter is assigned the remaining fields (including the word separators).
@@ -3929,8 +4133,9 @@
 (or
 .Ev REPLY )
 as array of words.
+Only no or one parameter is accepted.
 .It Fl a
-Store the result without word splitting into the parameter
+Store the result, without applying IFS word splitting, into the parameter
 .Ar p
 (or
 .Ev REPLY )
@@ -3938,11 +4143,12 @@
 .Ic utf8\-mode
 option is enacted, octets otherwise); the codepoints are
 encoded as decimal numbers by default.
+Only no or one parameter is accepted.
 .It Fl d Ar x
 Use the first byte of
 .Ar x ,
 .Dv NUL
-if empty, instead of the ASCII newline character as input line delimiter.
+if empty, instead of the ASCII newline character to delimit input lines.
 .It Fl N Ar z
 Instead of reading till end-of-line, read exactly
 .Ar z
@@ -3957,13 +4163,14 @@
 bytes but return as soon as any bytes are read, e.g.\& from a
 slow terminal device, or if EOF or a timeout occurs.
 .It Fl p
-Read from the currently active co-process, see
+Read from the currently active co-process (see
 .Sx Co-processes
-above for details on this.
+above for details) instead of from a file descriptor.
 .It Fl u Ns Op Ar n
-Read from the file descriptor
+Read from the file descriptor number
 .Ar n
 (defaults to 0, i.e.\& standard input).
+.br
 The argument must immediately follow the option character.
 .It Fl t Ar n
 Interrupt reading after
@@ -3975,12 +4182,10 @@
 .Dv SIGALRM
 were caught if the timeout occurred, but partial reads may still be returned.
 .It Fl r
-Normally, the ASCII backslash character escapes the special
-meaning of the following character and is stripped from the input;
+Normally,
 .Ic read
-does not stop when encountering a backslash-newline sequence and
-does not store that newline in the result.
-This option enables raw mode, in which backslashes are not processed.
+strips backslash-newline sequences and any remaining backslashes from input.
+This option enables raw mode, in which backslashes are retained and ignored.
 .It Fl s
 The input line is saved to the history.
 .El
@@ -4012,12 +4217,12 @@
 .Op Ns = Ns Ar value
 .Ar ... Oc
 .Xc
+.Pq keeps assignments , special, decl-util
 Sets the read-only attribute of the named parameters.
-This is a declaration utility.
 If values are given,
-parameters are set to them before setting the attribute.
-Once a parameter is
-made read-only, it cannot be unset and its value cannot be changed.
+parameters are assigned these before disallowing writes.
+Once a parameter is made read-only,
+it cannot be unset and its value cannot be changed.
 .Pp
 If no parameters are specified, the names of all parameters with the read-only
 attribute are printed one per line, unless the
@@ -4032,40 +4237,40 @@
 .Op Fl \-
 .Ar name
 .Xc
-Prints the resolved absolute pathname corresponding to
+.Pq defer with flags
+Resolves an absolute pathname corresponding to
 .Ar name .
+If the resolved pathname either exists or can be created immediately,
+.Ic realpath
+returns 0 and prints the resolved pathname,
+otherwise or if an error occurs, it issues a diagnostic and returns nonzero.
 If
 .Ar name
 ends with a slash
 .Pq Ql / ,
-it's also checked for existence and whether it is a directory; otherwise,
-.Ic realpath
-returns 0 if the pathname either exists or can be created immediately,
-i.e. all but the last component exist and are directories.
-For calls from the shell, if any options are given, an external
-.Xr realpath 1
-utility is preferred over the builtin.
+resolving to an extant non-directory is also treated as error.
 .Pp
 .It Xo
 .Ic rename
 .Op Fl \-
 .Ar from to
 .Xc
+.Pq defer always
 Renames the file
 .Ar from
 to
 .Ar to .
 Both must be complete pathnames and on the same device.
-An external utility is preferred over this builtin,
-which is intended for emergency situations
-.Pq where Pa /bin/mv No becomes unusable
-and directly calls
+Intended for emergency situations
+.Pq where Pa /bin/mv No becomes unusable ;
+directly calls
 .Xr rename 2 .
 .Pp
 .It Ic return Op Ar status
+.Pq keeps assignments , special
 Returns from a function or
 .Ic \&.
-script, with exit status
+script with errorlevel
 .Ar status .
 If no
 .Ar status
@@ -4080,50 +4285,65 @@
 .Ev ENV
 files as
 .Ic \&.
-scripts, while the original Korn shell only treats profiles as
+scripts, while the original Korn shell only treated profiles as
 .Ic \&.
 scripts.
 .Pp
+.It Ic rot13
+.Pq Li dot.mkshrc No alias
+ROT13-encrypts/-decrypts stdin to stdout.
+.Pp
 .It Xo
-.Ic set Op Ic +\-abCefhiklmnprsUuvXx
-.Op Ic +\-o Ar option
-.Op Ic +\-A Ar name
+.Ic set Op Fl +abCefhiklmnprsUuvXx
+.Op Fl +o Ar option
+.Op Fl +A Ar name
 .Op Fl \-
 .Op Ar arg ...
 .Xc
+.Pq keeps assignments , special
 The
 .Ic set
-command can be used to set
+command can be used to show all shell parameters
+.Pq like Ic typeset \- ,
+set
 .Pq Ic \-
 or clear
 .Pq Ic +
-shell options, set the positional parameters, or set an array parameter.
+shell options, set an array parameter or the positional parameters.
+.Pp
 Options can be changed using the
-.Cm +\-o Ar option
+.Fl +o Ar option
 syntax, where
 .Ar option
 is the long name of an option, or using the
-.Cm +\- Ns Ar letter
+.Fl + Ns Ar letter
 syntax, where
 .Ar letter
 is the option's single letter name (not all options have a single letter name).
-The following table lists both option letters (if they exist) and long names
-along with a description of what the option does:
+The following table lists short (if extant) and long names
+along with a description of what each option does:
 .Bl -tag -width 3n
 .It Fl A Ar name
 Sets the elements of the array parameter
 .Ar name
 to
 .Ar arg ...
+.Pp
 If
 .Fl A
 is used, the array is reset (i.e. emptied) first; if
 .Ic +A
 is used, the first N elements are set (where N is the number of arguments);
 the rest are left untouched.
+If
+.Ar name
+ends with a
+.Sq + ,
+the array is appended to instead.
 .Pp
 An alternative syntax for the command
-.Ic set \-A foo \-\- a b c
+.Ic set \-A foo \-\- a b c;
+.Ic set \-A foo+ \-\- d e
 which is compatible to
 .Tn GNU
 .Nm bash
@@ -4135,14 +4355,15 @@
 .It Fl a \*(Ba Fl o Ic allexport
 All new parameters are created with the export attribute.
 .It Fl b \*(Ba Fl o Ic notify
-Print job notification messages asynchronously, instead of just before the
+Print job notification messages asynchronously instead of just before the
 prompt.
-Only used if job control is enabled
+Only used with job control
 .Pq Fl m .
 .It Fl C \*(Ba Fl o Ic noclobber
 Prevent \*(Gt redirection from overwriting existing files.
 Instead, \*(Gt\*(Ba must be used to force an overwrite.
-Note that this is not safe to use for creation of temporary files or
+.Em Note:
+This is not safe to use for creation of temporary files or
 lockfiles due to a TOCTOU in a check allowing one to redirect output to
 .Pa /dev/null
 or other device files even in
@@ -4155,16 +4376,17 @@
 non-zero status).
 This does not apply to commands whose exit status is
 explicitly tested by a shell construct such as
+.Ic !\& ,
 .Ic if ,
-.Ic until ,
-.Ic while
+.Ic until
 or
-.Ic \&!
+.Ic while
 statements.
 For
-.Ic &&
-or
-.Ic \*(Ba\*(Ba ,
+.Ic && ,
+.Ic \*(Ba\*(Ba
+and pipelines (but mind
+.Fl o Ic pipefail ) ,
 only the status of the last command is tested.
 .It Fl f \*(Ba Fl o Ic noglob
 Do not expand file name patterns.
@@ -4176,29 +4398,34 @@
 .It Fl i \*(Ba Fl o Ic interactive
 The shell is an interactive shell.
 This option can only be used when the shell is invoked.
-See above for a description of what this means.
+See above for details.
 .It Fl k \*(Ba Fl o Ic keyword
 Parameter assignments are recognised anywhere in a command.
 .It Fl l \*(Ba Fl o Ic login
 The shell is a login shell.
 This option can only be used when the shell is invoked.
-See above for a description of what this means.
+See above for what this means.
 .It Fl m \*(Ba Fl o Ic monitor
 Enable job control (default for interactive shells).
 .It Fl n \*(Ba Fl o Ic noexec
 Do not execute any commands.
-Useful for checking the syntax of scripts
-(ignored if interactive).
+Useful for checking the syntax of scripts.
+Ignored if reading commands from a tty.
 .It Fl p \*(Ba Fl o Ic privileged
 The shell is a privileged shell.
 It is set automatically if, when the shell starts,
 the real UID or GID does not match
 the effective UID (EUID) or GID (EGID), respectively.
 See above for a description of what this means.
+.Pp
+If the shell is privileged, setting this flag after startup files
+have been processed let it go full setuid and/or setgid.
+Clearing this flag makes the shell drop privileges.
+Changing this flag resets the groups vector.
 .It Fl r \*(Ba Fl o Ic restricted
 The shell is a restricted shell.
 This option can only be used when the shell is invoked.
-See above for a description of what this means.
+See above for what this means.
 .It Fl s \*(Ba Fl o Ic stdin
 If used when the shell is invoked, commands are read from standard input.
 Set automatically if the shell is invoked with no arguments.
@@ -4207,12 +4434,11 @@
 .Fl s
 is used with the
 .Ic set
-command it causes the specified arguments to be sorted before assigning them to
-the positional parameters (or to array
+command it causes the specified arguments to be sorted ASCIIbetically
+before assigning them to the positional parameters (or to array
 .Ar name ,
-if
-.Fl A
-is used).
+with
+.Fl A ) .
 .It Fl U \*(Ba Fl o Ic utf8\-mode
 Enable UTF-8 support in the
 .Sx Emacs editing mode
@@ -4258,23 +4484,24 @@
 .It Fl X \*(Ba Fl o Ic markdirs
 Mark directories with a trailing
 .Ql /
-during file name generation.
+during globbing.
 .It Fl x \*(Ba Fl o Ic xtrace
-Print command trees when they are executed, preceded by
-the value of
+Print commands when they are executed, preceded by
 .Ev PS4 .
 .It Fl o Ic bgnice
 Background jobs are run with lower priority.
 .It Fl o Ic braceexpand
-Enable brace expansion (a.k.a. alternation).
+Enable brace expansion.
 This is enabled by default.
 .It Fl o Ic emacs
 Enable BRL emacs-like command-line editing (interactive shells only); see
 .Sx Emacs editing mode .
+Enabled by default.
 .It Fl o Ic gmacs
 Enable gmacs-like command-line editing (interactive shells only).
-Currently identical to emacs editing except that transpose\-chars (\*(haT) acts
-slightly differently.
+Currently identical to emacs editing except that
+.Li transpose\-chars Pq \*(haT
+acts slightly differently.
 .It Fl o Ic ignoreeof
 The shell will not (easily) exit when end-of-file is read;
 .Ic exit
@@ -4285,8 +4512,7 @@
 .It Fl o Ic inherit\-xtrace
 Do not reset
 .Fl o Ic xtrace
-upon entering functions.
-This is enabled by default.
+upon entering functions (default).
 .It Fl o Ic nohup
 Do not kill running jobs with a
 .Dv SIGHUP
@@ -4301,7 +4527,7 @@
 signal.
 .It Fl o Ic nolog
 No effect.
-In the original Korn shell, this prevents function definitions from
+In the original Korn shell, this prevented function definitions from
 being stored in the history file.
 .It Fl o Ic physical
 Causes the
@@ -4324,45 +4550,55 @@
 .Ic cd
 command changes
 .Ev PWD .
-See the
+See
 .Ic cd
 and
 .Ic pwd
-commands above for more details.
+above for more details.
 .It Fl o Ic pipefail
-Make the exit status of a pipeline (before logically complementing) the
-rightmost non-zero errorlevel, or zero if all commands exited with zero.
+Make the exit status of a pipeline the rightmost non-zero errorlevel,
+or zero if all commands exited with zero.
 .It Fl o Ic posix
 Behave closer to the standards
 (see
 .Sx POSIX mode
 for details).
-Automatically enabled if the basename of the shell invocation begins with
+Automatically enabled if the shell invocation basename, after
+.Sq \-
+and
+.Sq r
+processing, begins with
 .Dq sh
-and this autodetection feature is compiled in
-.Pq not in MirBSD .
+and
+.Pq often used for the Nm lksh No binary
+this autodetection feature is compiled in.
 As a side effect, setting this flag turns off the
 .Ic braceexpand
 and
 .Ic utf8\-mode
 flags, which can be turned back on manually, and
+.Pq unless both are set in the same command
 .Ic sh
-mode (unless both are enabled at the same time).
+mode.
 .It Fl o Ic sh
-Enable
+Enable kludge
 .Pa /bin/sh
-.Pq kludge
-mode (see
-.Sx SH mode ) .
-Automatically enabled if the basename of the shell invocation begins with
+compatibility mode (see
+.Sx SH mode
+below for details).
+Automatically enabled if the basename of the shell invocation, after
+.Sq \-
+and
+.Sq r
+processing, begins with
 .Dq sh
 and this autodetection feature is compiled in
-.Pq not in MirBSD .
-As a side effect, setting this flag turns off
+.Pq rather uncommon .
+As a side effect, setting this flag turns off the
 .Ic braceexpand
-mode, which can be turned back on manually, and
+flag, which can be turned back on manually, and
 .Ic posix
-mode (unless both are enabled at the same time).
+mode (unless both are set in the same command).
 .It Fl o Ic vi
 Enable
 .Xr vi 1 Ns -like
@@ -4371,19 +4607,20 @@
 .Sx Vi editing mode
 for documentation and limitations.
 .It Fl o Ic vi\-esccomplete
-In vi command-line editing, do command and file name completion when escape
-(\*(ha[) is entered in command mode.
+In vi command-line editing, do command and file name completion when Esc
+.Pq \*(ha[
+is entered in command mode.
 .It Fl o Ic vi\-tabcomplete
-In vi command-line editing, do command and file name completion when tab (\*(haI)
-is entered in insert mode.
-This is the default.
+In vi command-line editing, do command and file name completion when Tab
+.Pq \*(haI
+is entered in insert mode (default).
 .It Fl o Ic viraw
 No effect.
 In the original Korn shell, unless
 .Ic viraw
 was set, the vi command-line mode would let the
 .Xr tty 4
-driver do the work until ESC (\*(ha[) was entered.
+driver do the work until Esc was entered.
 .Nm
 is always in viraw mode.
 .El
@@ -4395,52 +4632,75 @@
 .Ic set Fl o
 with no option name will list all the options and whether each is on or off;
 .Ic set +o
-will print the long names of all options that are currently on.
-In a future version,
-.Ic set +o
-will behave
-.Tn POSIX
-compliant and print commands to restore the current options instead.
+prints a command to restore the current option set, using the internal
+.Ic set Fl o Ic .reset
+construct, which is an implementation detail; these commands are transient
+.Pq only valid within the current shell session .
 .Pp
 Remaining arguments, if any, are positional parameters and are assigned, in
 order, to the positional parameters (i.e. $1, $2, etc.).
 If options end with
 .Dq Li \-\-
 and there are no remaining arguments, all positional parameters are cleared.
-If no options or arguments are given, the values of all names are printed.
 For unknown historical reasons, a lone
 .Dq Li \-
-option is treated specially \*(en it clears both the
+option is treated specially\*(EMit clears both the
 .Fl v
 and
 .Fl x
 options.
+If no options or arguments are given, the values of all parameters are printed
+.Pq suitably quoted .
+.Pp
+.It Xo
+.Ic setenv
+.Op Ar name Op Ar value
+.Xc
+.Pq Li dot.mkshrc No function
+Without arguments, display the names and values of all exported parameters.
+Otherwise, set
+.Ar name Ns 's
+export attribute, and its value to
+.Ar value Pq empty string if none given .
 .Pp
 .It Ic shift Op Ar number
+.Pq keeps assignments , special
 The positional parameters
 .Ar number Ns +1 ,
 .Ar number Ns +2 ,
-etc. are renamed to 1, 2, etc.
-.Ar number
-defaults to 1.
+etc.
+.Pq Ar number No defaults to 1
+are renamed to 1, 2, etc.
 .Pp
 .It Ic sleep Ar seconds
+.Pq regular , needs Xr select 2
 Suspends execution for a minimum of the
 .Ar seconds
-specified as positive decimal value with an optional fractional part.
+(specified as positive decimal value with an optional fractional part).
 Signal delivery may continue execution earlier.
 .Pp
+.It Ic smores Op Ar
+.Pq Li dot.mkshrc No function
+Simple pager:
+.Aq Enter
+next;
+.So q Sc Ns + Ns Aq Enter
+quit
+.Pp
 .It Ic source Ar file Op Ar arg ...
+.Pq keeps assignments
 Like
-.Ic \&. Po Do dot Dc Pc ,
+.Ic \&.
+.Pq Dq dot ,
 except that the current working directory is appended to the
-search path (GNU
-.Nm bash
-extension).
+search path.
+.Pq GNU Nm bash No extension
 .Pp
 .It Ic suspend
+.Pq needs job control and Xr getsid 2
 Stops the shell as if it had received the suspend character from
 the terminal.
+.Pp
 It is not possible to suspend a login shell unless the parent process
 is a member of the same terminal session but is a member of a different
 process group.
@@ -4450,22 +4710,24 @@
 .Pp
 .It Ic test Ar expression
 .It Ic \&[ Ar expression Ic \&]
+.Pq regular
 .Ic test
 evaluates the
 .Ar expression
-and returns zero status if true, 1 if false, or greater than 1 if there
-was an error.
-It is normally used as the condition command of
+and exits with status code 0 if true, 1 if false,
+or greater than 1 if there was an error.
+It is often used as the condition command of
 .Ic if
 and
 .Ic while
 statements.
-Symbolic links are followed for all
+All
 .Ar file
-expressions except
+expressions, except
 .Fl h
 and
-.Fl L .
+.Fl L ,
+follow symbolic links.
 .Pp
 The following basic expressions are available:
 .Bl -tag -width 17n
@@ -4502,7 +4764,7 @@
 .It Fl k Ar file
 .Ar file Ns 's
 mode has the
-.Xr sticky 8
+.Xr sticky 7
 bit set.
 .It Fl L Ar file
 .Ar file
@@ -4592,7 +4854,7 @@
 .At
 .Nm ksh93 .
 .Ar option
-can also be the short flag led by either
+can also be the short flag prefixed with either
 .Ql \-
 or
 .Ql +
@@ -4605,14 +4867,22 @@
 .Dq Li xtrace .
 .It Ar string No = Ar string
 Strings are equal.
+In double brackets, pattern matching
+.Pq R59+ using extglobs
+occurs if the right-hand string isn't quoted.
 .It Ar string No == Ar string
-Strings are equal.
+Same as
+.Sq =
+.Pq deprecated .
+.It Ar string No != Ar string
+Strings are not equal.
+See
+.Sq =
+regarding pattern matching.
 .It Ar string No \*(Gt Ar string
 First string operand is greater than second string operand.
 .It Ar string No \*(Lt Ar string
 First string operand is less than second string operand.
-.It Ar string No != Ar string
-Strings are not equal.
 .It Ar number Fl eq Ar number
 Numbers compare equal.
 .It Ar number Fl ne Ar number
@@ -4659,6 +4929,7 @@
 followed by negation and parenthesis lowering; two- and four-argument forms
 prefer negation followed by parenthesis; the one-argument form always implies
 .Fl n .
+To assume this is not necessarily portable.
 .Pp
 .Sy Note :
 A common mistake is to use
@@ -4673,14 +4944,13 @@
 .Dq Li \-n .
 Use tests like
 .Dq Li if \&[ x\&"$foo\&" = x"bar" \&]
-instead, or the double-bracket operator
-.Dq Li if \&[[ $foo = bar \&]]
-or, to avoid pattern matching (see
+instead, or the double-bracket operator (see
 .Ic \&[[
 above):
-.Dq Li if \&[[ $foo = \&"$bar" \&]]
-.Pp
-The
+.Dq Li if \&[[ $foo = bar \&]]
+or, to avoid pattern matching,
+.Dq Li if \&[[ $foo = \&"$bar" \&]] ;
+the
 .Ic \&[[ ... \&]]
 construct is not only more secure to use but also often faster.
 .Pp
@@ -4689,37 +4959,35 @@
 .Op Fl p
 .Op Ar pipeline
 .Xc
+.Pq reserved word
 If a
 .Ar pipeline
 is given, the times used to execute the pipeline are reported.
 If no pipeline
 is given, then the user and system time used by the shell itself, and all the
 commands it has run since it was started, are reported.
+.Pp
 The times reported are the real time (elapsed time from start to finish),
 the user CPU time (time spent running in user mode), and the system CPU time
 (time spent running in kernel mode).
+.Pp
 Times are reported to standard error; the format of the output is:
 .Pp
 .Dl "0m0.03s real     0m0.02s user     0m0.01s system"
 .Pp
 If the
 .Fl p
-option is given the output is slightly longer:
+option is given (which is only permitted if
+.Ar pipeline
+is a simple command), the output is slightly longer:
 .Bd -literal -offset indent
 real     0.03
 user     0.02
 sys      0.01
 .Ed
 .Pp
-It is an error to specify the
-.Fl p
-option unless
-.Ar pipeline
-is a simple command.
-.Pp
-Simple redirections of standard error do not affect the output of the
-.Ic time
-command:
+Simple redirections of standard error do not affect
+.Ic time Ns 's output :
 .Pp
 .Dl $ time sleep 1 2\*(Gtafile
 .Dl $ { time sleep 1; } 2\*(Gtafile
@@ -4729,8 +4997,9 @@
 but those of the second command do.
 .Pp
 .It Ic times
-Print the accumulated user and system times used both by the shell
-and by processes that the shell started which have exited.
+.Pq keeps assignments , special
+Print the accumulated user and system times (see above) used both
+by the shell and by processes that the shell started which have exited.
 The format of the output is:
 .Bd -literal -offset indent
 0m0.01s 0m0.00s
@@ -4738,6 +5007,7 @@
 .Ed
 .Pp
 .It Ic trap Ar n Op Ar signal ...
+.Pq keeps assignments , special
 If the first operand is a decimal unsigned integer, this resets all
 specified signals to the default action, i.e. is the same as calling
 .Ic trap
@@ -4745,11 +5015,10 @@
 .Pq Dq Li \-
 as
 .Ar handler ,
-followed by the arguments
-.Pq Ar n Op Ar signal ... ,
-all of which are treated as signals.
+followed by the arguments (interpreted as signals).
 .Pp
 .It Ic trap Op Ar handler signal ...
+.Pq keeps assignments , special
 Sets a trap handler that is to be executed when any of the specified
 .Ar signal Ns s
 are received.
@@ -4758,13 +5027,21 @@
 .Pq Dq Li \- ,
 indicating that the default action is to be taken for the signals
 .Pq see Xr signal 3 ,
-or a string containing shell commands to be executed at the first opportunity
+or a string comprised of shell commands to be executed at the first opportunity
 (i.e. when the current command completes or before printing the next
 .Ev PS1
 prompt) after receipt of one of the signals.
 .Ar signal
-is the name of a signal
-.Pq e.g.\& Dv PIPE or Dv ALRM
+is the name, possibly prefixed with
+.Dq Li SIG ,
+of a signal
+.Po
+e.g.\&
+.Dv PIPE ,
+.Dv ALRM
+or
+.Dv SIGINT
+.Pc
 or the number of the signal (see the
 .Ic kill Fl l
 command above).
@@ -4782,6 +5059,13 @@
 option were set.
 .Dv EXIT
 handlers are executed in the environment of the last executed command.
+The original Korn shell's
+.Dv DEBUG
+trap and handling of
+.Dv ERR
+and
+.Dv EXIT
+in functions are not yet implemented.
 .Pp
 Note that, for non-interactive shells, the trap handler cannot be changed
 for signals that were ignored when the shell started.
@@ -4792,23 +5076,22 @@
 commands.
 Note that the output of
 .Ic trap
-cannot be usefully piped to another process (an artifact of the fact that
-traps are cleared when subprocesses are created).
-.Pp
-The original Korn shell's
-.Dv DEBUG
-trap and the handling of
-.Dv ERR
-and
-.Dv EXIT
-traps in functions are not yet implemented.
+cannot be usefully captured or piped to another process (an artifact
+of the fact that traps are cleared when subprocesses are created).
 .Pp
 .It Ic true
-A command that exits with a zero value.
+.Pq regular
+A command that exits with a zero status.
+.Pp
+.It Ic type Ar name ...
+.Pq built-in alias
+Reveal how
+.Ar name
+would be interpreted as command.
 .Pp
 .It Xo
 .Ic typeset
-.Op Ic +\-aglpnrtUux
+.Op Fl +aglpnrtUux
 .Oo Fl L Ns Op Ar n
 .No \*(Ba Fl R Ns Op Ar n
 .No \*(Ba Fl Z Ns Op Ar n Oc
@@ -4822,29 +5105,35 @@
 .Fl f Op Fl tux
 .Op Ar name ...
 .Xc
-Display or set parameter attributes.
-This is a declaration utility.
+.Pq keeps assignments , decl-util
+Display or set attributes of shell parameters or functions.
 With no
 .Ar name
-arguments, parameter attributes are displayed; if no options are used, the
-current attributes of all parameters are printed as
+arguments, parameter attributes are shown;
+if no options are used, the current attributes of all parameters are printed as
 .Ic typeset
 commands; if an option is given (or
 .Dq Li \-
 with no option letter), all parameters and their values with the specified
 attributes are printed; if options are introduced with
-.Ql + ,
-parameter values are not printed.
+.Ql +
+.Po
+or
+.Dq Li +
+alone
+.Pc ,
+only names are printed.
 .Pp
-If
+If any
 .Ar name
-arguments are given, the attributes of the named parameters are set
+arguments are given, the attributes of the so named parameters are set
 .Pq Ic \&\-
 or cleared
 .Pq Ic \&+ ;
-inside a function, this will cause the parameters to be created
-(with no value) in the local scope (but see
-.Fl g ) .
+inside a function, this will cause the parameters to be created (and set to
+.Dq
+if no value is given) in the local scope
+.Pq except if Fl g No is used .
 Values for parameters may optionally be specified.
 For
 .Ar name Ns \&[*] ,
@@ -4861,36 +5150,39 @@
 functions are listed with their values (i.e. definitions) unless
 options are introduced with
 .Ql + ,
-in which case only the function names are reported.
+in which case only the names are displayed.
 .Bl -tag -width Ds
 .It Fl a
 Indexed array attribute.
 .It Fl f
 Function mode.
-Display or set functions and their attributes, instead of parameters.
+Display or set shell functions and their attributes,
+instead of shell parameters.
 .It Fl g
+.Dq global
+mode.
 Do not cause named parameters to be created in
 the local scope when called inside a function.
 .It Fl i Ns Op Ar n
 Integer attribute.
 .Ar n
-specifies the base to use when displaying the integer (if not specified, the
-base given in the first assignment is used).
-Parameters with this attribute may
-be assigned values containing arithmetic expressions.
+specifies the base to use when stringifying the integer
+(if not specified, the base given in the first assignment is used).
+Parameters with this attribute
+may be assigned arithmetic expressions for values.
 .It Fl L Ns Op Ar n
 Left justify attribute.
 .Ar n
 specifies the field width.
 If
 .Ar n
-is not specified, the current width of a parameter (or the width of its first
-assigned value) is used.
-Leading whitespace (and zeros, if used with the
+is not specified, the current width of the parameter
+(or the width of its first assigned value) is used.
+Leading whitespace (and digit zeros, if used with the
 .Fl Z
 option) is stripped.
-If necessary, values are either truncated or space padded
-to fit the field width.
+If necessary, values are either truncated
+or padded with space to fit the field width.
 .It Fl l
 Lower case attribute.
 All upper case ASCII characters in values are converted to lower case.
@@ -4917,7 +5209,7 @@
 .Ar name
 is accessed.
 This can be used by functions to access variables whose names are
-passed as parameters, instead of using
+passed as parameters, instead of resorting to
 .Ic eval .
 .It Fl p
 Print complete
@@ -4930,32 +5222,32 @@
 specifies the field width.
 If
 .Ar n
-is not specified, the current width of a parameter (or the width of its first
-assigned value) is used.
+is not specified, the current width of the parameter
+(or the width of its first assigned value) is used.
 Trailing whitespace is stripped.
-If necessary, values are either stripped of leading characters or space
-padded to make them fit the field width.
+If necessary, values are either stripped of leading characters
+or padded with space to fit the field width.
 .It Fl r
 Read-only attribute.
 Parameters with this attribute may not be assigned to or unset.
 Once this attribute is set, it cannot be turned off.
 .It Fl t
 Tag attribute.
-Has no meaning to the shell; provided for application use.
+This attribute has no meaning to the shell for parameters
+and is provided for application use.
 .Pp
 For functions,
 .Fl t
 is the trace attribute.
 When functions with the trace attribute are executed, the
-.Ic xtrace
+.Fl o Ic xtrace
 .Pq Fl x
 shell option is temporarily turned on.
 .It Fl U
 Unsigned integer attribute.
-Integers are printed as unsigned values (combine with the
+Integers are printed as unsigned values (combined with the
 .Fl i
 option).
-This option is not in the original Korn shell.
 .It Fl u
 Upper case attribute.
 All lower case ASCII characters in values are converted to upper case.
@@ -4971,7 +5263,8 @@
 .Pp
 For functions,
 .Fl u
-is the undefined attribute.
+is the undefined attribute, used with
+.Ev FPATH .
 See
 .Sx Functions
 above for the implications of this.
@@ -5005,29 +5298,30 @@
 .Pp
 .It Xo
 .Ic ulimit
-.Op Fl aBCcdefHilMmnOPpqrSsTtVvw
+.Op Fl aBCcdefHilMmnOPpqrSsTtVvwx
 .Op Ar value
 .Xc
+.Pq regular
 Display or set process limits.
 If no options are used, the file size limit
 .Pq Fl f
 is assumed.
 .Ar value ,
 if specified, may be either an arithmetic expression or the word
-.Dq unlimited .
+.Dq Li unlimited .
 The limits affect the shell and any processes created by the shell after a
 limit is imposed.
-Note that some systems may not allow limits to be increased
+Note that systems may not allow some limits to be increased
 once they are set.
 Also note that the types of limits available are system
-dependent \*(en some systems have only the
+dependent\*(EMsome systems have only the
 .Fl f
-limit, or not even that, or can set only the soft limits
+limit, or not even that, or can set only the soft limits, etc.
 .Bl -tag -width 5n
 .It Fl a
-Display all limits; unless
+Display all limits (soft limits unless
 .Fl H
-is used, soft limits are displayed.
+is used).
 .It Fl B Ar n
 Set the socket buffer size to
 .Ar n
@@ -5040,19 +5334,22 @@
 .Ar n
 blocks on the size of core dumps.
 .It Fl d Ar n
-Impose a size limit of
+Limit the size of the data area to
 .Ar n
-kibibytes on the size of the data area.
+kibibytes.
 .It Fl e Ar n
 Set the maximum niceness to
 .Ar n .
 .It Fl f Ar n
 Impose a size limit of
 .Ar n
-blocks on files written by the shell and its child processes (files of any
-size may be read).
+blocks on files written by the shell and its child processes
+.Pq any size may be read .
 .It Fl H
 Set the hard limit only (the default is to set both hard and soft limits).
+With
+.Fl a ,
+display all hard limits.
 .It Fl i Ar n
 Set the number of pending signals to
 .Ar n .
@@ -5078,10 +5375,23 @@
 .It Fl P Ar n
 Limit the number of threads per process to
 .Ar n .
+.Pp
+This option mostly matches
+.At
+.Nm ksh93 Ns 's
+.Fl T ;
+.br
+on
+.Tn AIX ,
+see
+.Fl r
+as used by its
+.Nm ksh
+though.
 .It Fl p Ar n
 Impose a limit of
 .Ar n
-processes that can be run by the user at any one time.
+processes that can be run by the user (uid) at any one time.
 .It Fl q Ar n
 Limit the size of
 .Tn POSIX
@@ -5089,18 +5399,28 @@
 .Ar n
 bytes.
 .It Fl r Ar n
+.Pq Cm AIX
+Limit the number of threads per process to
+.Ar n .
+.br
+.Pq Cm Linux
 Set the maximum real-time priority to
 .Ar n .
 .It Fl S
 Set the soft limit only (the default is to set both hard and soft limits).
+With
+.Fl a ,
+display soft limits (default).
 .It Fl s Ar n
-Impose a size limit of
+Limit the size of the stack area to
 .Ar n
-kibibytes on the size of the stack area.
+kibibytes.
 .It Fl T Ar n
 Impose a time limit of
 .Ar n
-real seconds to be used by each process.
+real seconds
+.Pq Dq humantime
+to be used by each process.
 .It Fl t Ar n
 Impose a time limit of
 .Ar n
@@ -5113,9 +5433,12 @@
 .Ar n
 kibibytes on the amount of virtual memory (address space) used.
 .It Fl w Ar n
-Impose a limit of
+Limit the amount of swap space used to at most
 .Ar n
-kibibytes on the amount of swap space used.
+kibibytes.
+.It Fl x Ar n
+Set the maximum number of file locks to
+.Ar n .
 .El
 .Pp
 As far as
@@ -5127,6 +5450,7 @@
 .Op Fl S
 .Op Ar mask
 .Xc
+.Pq regular
 Display or set the file permission creation mask or umask (see
 .Xr umask 2 ) .
 If the
@@ -5150,6 +5474,7 @@
 .Op Fl adt
 .Op Ar name ...
 .Xc
+.Pq regular
 The aliases for the given names are removed.
 If the
 .Fl a
@@ -5166,31 +5491,27 @@
 .Op Fl fv
 .Ar parameter ...
 .Xc
+.Pq keeps assignments , special
 Unset the named parameters
-.Po
-.Fl v ,
-the default
-.Pc
+.Pq Fl v , No the default
 or functions
 .Pq Fl f .
 With
 .Ar parameter Ns \&[*] ,
-attributes are kept, only values are unset.
-.Pp
-The exit status is non-zero if any of the parameters have the read-only
-attribute set, zero otherwise.
+attributes are retained, only values are unset.
+The exit status is non-zero if any of the parameters are read-only,
+zero otherwise (not portable).
 .Pp
 .It Ic wait Op Ar job ...
+.Pq regular
 Wait for the specified job(s) to finish.
 The exit status of
 .Ic wait
 is that of the last specified job; if the last job is killed by a signal, the
-exit status is 128 + the number of the signal (see
+exit status is 128 + the signal number (see
 .Ic kill Fl l Ar exit-status
-above); if the last specified job can't be found (because it never existed or
-had already finished), the exit status of
-.Ic wait
-is 127.
+above); if the last specified job cannot be found (because it never existed
+or had already finished), the exit status is 127.
 See
 .Sx Job control
 below for the format of
@@ -5215,20 +5536,44 @@
 .Op Fl pv
 .Op Ar name ...
 .Xc
+.Pq regular
 Without the
 .Fl v
 option, it is the same as
 .Ic command Fl v ,
-except aliases are not printed as alias command.
+except aliases are printed as their definition only.
 With the
 .Fl v
-option, it is exactly the same as
+option, it is exactly identical to
 .Ic command Fl V .
-In either case, the
+In either case, with the
 .Fl p
-option differs: the search path is not affected in
-.Ic whence ,
-but the search is restricted to the path.
+option the search is restricted to the (current)
+.Ev PATH .
+.Pp
+.It Xo Ic which
+.Op Fl a
+.Op Ar name ...
+.Xc
+.Pq Li dot.mkshrc No function
+Without
+.Fl a ,
+behaves like
+.Ic whence Fl p
+(does a
+.Ev PATH
+search for each
+.Ar name
+printing the resulting pathname if found); with
+.Fl a ,
+matches in all
+.Ev PATH
+components are printed, i.e. the search is not stopped after a match.
+If no
+.Ar name
+was matched, the exit status is 2; if every name was matched, it is zero,
+otherwise it is 1.
+No diagnostics are produced on failure to match.
 .El
 .Ss Job control
 Job control refers to the shell's ability to monitor and control jobs which
@@ -5323,9 +5668,9 @@
 .Ar number
 is the exit status of the job which is omitted if the status is zero.
 .It Running
-The job has neither stopped nor exited (note that running does not necessarily
-mean consuming CPU time \*(en
-the process could be blocked waiting for some event).
+The job has neither stopped nor exited (note that running does not
+necessarily mean consuming CPU time\*(EMthe process could be blocked
+waiting for some event).
 .It Stopped Op Ar signal
 The job was stopped by the indicated
 .Ar signal
@@ -5560,7 +5905,7 @@
 .Op Ar n
 (if the command can be prefixed with a count); and any keys the command is
 bound to by default, written using caret notation
-e.g. the ASCII ESC character is written as \*(ha[.
+e.g. the ASCII Esc character is written as \*(ha[.
 These control sequences are not case sensitive.
 A count prefix for a command is entered using the sequence
 .Pf \*(ha[ Ns Ar n ,
@@ -5660,7 +6005,7 @@
 match as in the
 .Ic complete
 command above.
-Note that \*(haI is usually generated by the TAB (tabulator) key.
+Note that \*(haI is usually generated by the Tab (tabulator) key.
 .It Xo delete\-char\-backward:
 .Op Ar n
 .No ERASE Pq \*(haH ,
@@ -5833,6 +6178,10 @@
 Use of this editing command trashes the mark.
 .It quote: \*(ha\*(ha , \*(haV
 The following character is taken literally rather than as an editing command.
+.It quote\-region: \*(ha[Q
+Escapes the text between the mark and the cursor position
+.Pq the entire line if no mark is set
+into a shell command argument.
 .It redraw: \*(haL
 Reprints the last line of the prompt string and the current input line
 on a new line.
@@ -5926,11 +6275,12 @@
 .Pp
 The tab completion escapes characters the same way as the following code:
 .Bd -literal
-print \-nr \-\- "${x@/[\e"\-\e$\e&\-*:\-?[\e\e\e\`{\-\e}${IFS\-$\*(aq \et\en\*(aq}]/\e\e$KSH_MATCH}"
+print \-nr \-\- "${x@/[\e"\-\e$\e&\-*:\-?[\e\e\e`\e{\-\e}${IFS\-$\*(aq \et\en\*(aq}]/\e\e$KSH_MATCH}"
 .Ed
 .Ss Vi editing mode
 .Em Note:
-The vi command-line editing mode is orphaned, yet still functional.
+The vi command-line editing mode has not yet been brought up to the
+same quality and feature set as the emacs mode.
 It is 8-bit clean but specifically does not support UTF-8 or MBCS.
 .Pp
 The vi command-line editor in
@@ -5944,9 +6294,9 @@
 .It
 There are file name and command completion commands:
 =, \e, *, \*(haX, \*(haE, \*(haF and, optionally,
-.Aq tab
+.Aq Tab
 and
-.Aq esc .
+.Aq Esc .
 .It
 The
 .Ic _
@@ -6010,9 +6360,9 @@
 to insert the characters being described here).
 .It \*(haX
 Command and file name expansion (see below).
-.It Aq esc
+.It Aq Esc
 Puts the editor in command mode (see below).
-.It Aq tab
+.It Aq Tab
 Optional file name and command completion (see
 .Ic \*(haF
 above), enabled with
@@ -6089,7 +6439,7 @@
 Command or file name expansion is applied to the current big-word (with an
 appended
 .Ql *
-if the word contains no file globbing characters) \*(en the big-word is replaced
+if the word contains no file globbing characters)\*(EMthe big-word is replaced
 with the resulting words.
 If the current big-word is the first on the line
 or follows one of the characters
@@ -6114,18 +6464,18 @@
 .It Xo
 .Oo Ar n Oc Ns \e ,
 .Oo Ar n Oc Ns \*(haF ,
-.Oo Ar n Oc Ns Aq tab ,
+.Oo Ar n Oc Ns Aq Tab ,
 .No and
-.Oo Ar n Oc Ns Aq esc
+.Oo Ar n Oc Ns Aq Esc
 .Xc
 Command/file name completion.
 Replace the current big-word with the
 longest unique match obtained after performing command and file name expansion.
-.Aq tab
+.Aq Tab
 is only recognised if the
 .Ic vi\-tabcomplete
 option is set, while
-.Aq esc
+.Aq Esc
 is only recognised if the
 .Ic vi\-esccomplete
 option is set (see
@@ -6147,7 +6497,7 @@
 .It @ Ns Ar c
 Macro expansion.
 Execute the commands found in the alias
-.Ar c .
+.Li _ Ns Ar c .
 .El
 .Pp
 Intra-line movement commands:
@@ -6334,7 +6684,12 @@
 the direction of the search is the opposite of the last search.
 .It Ar ANSI-CurUp , PC-PgUp
 Take the characters from the beginning of the line to the current
-cursor position as search string and do a backwards history search
+cursor position as search string and do a history search, backwards,
+for lines beginning with this string; keep the cursor position.
+This works only in insert mode and keeps it enabled.
+.It Ar ANSI-CurDown , PC-PgDn
+Take the characters from the beginning of the line to the current
+cursor position as search string and do a history search, forwards,
 for lines beginning with this string; keep the cursor position.
 This works only in insert mode and keeps it enabled.
 .El
@@ -6349,7 +6704,7 @@
 times; goes into insert mode just after the current position.
 The append is
 only replicated if command mode is re-entered i.e.\&
-.Aq esc
+.Aq Esc
 is used.
 .It Xo
 .Oo Ar n Oc Ns A
@@ -6365,7 +6720,7 @@
 times; goes into insert mode at the current position.
 The insertion is only
 replicated if command mode is re-entered i.e.\&
-.Aq esc
+.Aq Esc
 is used.
 .It Xo
 .Oo Ar n Oc Ns I
@@ -6494,7 +6849,7 @@
 .It Pa \*(TI/.mkshrc
 User mkshrc profile (non-privileged interactive shells); see
 .Sx Startup files.
-The location can be changed at compile time (for embedded systems);
+The location can be changed at compile time (e.g. for embedded systems);
 AOSP Android builds use
 .Pa /system/etc/mkshrc .
 .It Pa \*(TI/.profile
@@ -6507,7 +6862,7 @@
 .It Pa /etc/shells
 Shell database.
 .It Pa /etc/suid_profile
-Suid profile (privileged shells); see
+Privileged shells' profile (sugid); see
 .Sx Startup files.
 .El
 .Pp
@@ -6545,6 +6900,12 @@
 .Xr utf\-8 7 ,
 .Xr mknod 8
 .Pp
+The FAQ at
+.Pa http://www.mirbsd.org/mksh\-faq.htm
+or in the
+.Pa mksh.faq
+file.
+.Pp
 .Pa http://www.mirbsd.org/ksh\-chan.htm
 .Rs
 .%A Morris Bolsky
@@ -6575,7 +6936,7 @@
 .Re
 .Rs
 .%A "IEEE Inc."
-.%T "\\*(tNIEEE\\*(sP Standard for Information Technology \*(en Portable Operating System Interface (POSIX)"
+.%T "\\*(tNIEEE\\*(sP Standard for Information Technology\*(EMPortable Operating System Interface (POSIX)"
 .%V "Part 2: Shell and Utilities"
 .%D 1993
 .%I "IEEE Press"
@@ -6669,70 +7030,25 @@
 .\"
 .Sh CAVEATS
 .Nm mksh
-provides a consistent 32-bit integer arithmetic implementation, both
-signed and unsigned, with sign of the result of a remainder operation
-and wraparound defined, even (defying POSIX) on 36-bit and 64-bit systems.
-.Pp
-.Nm mksh
 provides a consistent, clear interface normally.
 This may deviate from POSIX in historic or opinionated places.
 .Ic set Fl o Ic posix
 (see
 .Sx POSIX mode
 for details)
-will cause the shell to behave more conformant.
-.Pp
-For the purpose of
-.Tn POSIX ,
+will make the shell more conformant, but mind the FAQ (see
+.Sx SEE ALSO ) ,
+especially regarding locales.
 .Nm mksh
-supports only the
-.Dq C
-locale.
-.Nm mksh Ns 's
-.Ic utf8\-mode
-.Em must
-be disabled in POSIX mode, and it
-only supports the BMP (Basic Multilingual Plane) of UCS and maps
-raw octets into the U+EF80..U+EFFF wide character range; compare
-.Sx Arithmetic expressions .
-The following
-.Tn POSIX
-.Nm sh Ns -compatible
-code toggles the
-.Ic utf8\-mode
-option dependent on the current
-.Tn POSIX
-locale for mksh to allow using the UTF-8 mode, within the constraints
-outlined above, in code portable across various shell implementations:
-.Bd -literal -offset indent
-case ${KSH_VERSION:\-} in
-*MIRBSD\ KSH*\*(Ba*LEGACY\ KSH*)
-	case ${LC_ALL:\-${LC_CTYPE:\-${LANG:\-}}} in
-	*[Uu][Tt][Ff]8*\*(Ba*[Uu][Tt][Ff]\-8*) set \-U ;;
-	*) set +U ;;
-	esac ;;
-esac
-.Ed
-In near future, (UTF-8) locale tracking will be implemented though.
+.Pq but not Nm lksh
+provides a consistent 32-bit integer arithmetic implementation, both
+signed and unsigned, with sign of the result of a remainder operation
+and wraparound defined, even (defying POSIX) on 36-bit and 64-bit systems.
 .Pp
-Using
-.Ic set Fl o Ic pipefail
-makes the following construct error out:
-.Bd -literal -offset indent
-set -e
-for x in 1 2; do
-	false && echo $x
-done \*(Ba cat
-.Ed
-This is because, while the
-.Dq Li &&\&
-ensures that the inner command's failure is not taken, it sets
-the entire for..done loop's errorlevel, which is passed on by
-.Fl o Ic pipefail .
-Invert the inner command:
-.Li true \*(Ba\*(Ba echo $x
-.Pp
-See also the FAQ below.
+.Nm mksh
+currently uses OPTU-16 internally, which is the same as UTF-8 and CESU-8
+with 0000..FFFD being valid codepoints; raw octets are mapped into the
+PUA range EF80..EFFF, which is assigned by CSUR for this purpose.
 .Sh BUGS
 Suspending (using \*(haZ) pipelines like the one below will only suspend
 the currently running part of the pipeline; in this example,
@@ -6756,7 +7072,7 @@
 .Xr memmove 3 .
 .Pp
 This document attempts to describe
-.Nm mksh\ R57
+.Nm mksh\ R59b
 and up,
 .\" with vendor patches from insert-your-name-here,
 compiled without any options impacting functionality, such as
@@ -6784,187 +7100,3 @@
 .Pq Port 6697 SSL, 6667 unencrypted ,
 or at:
 .Pa https://launchpad.net/mksh
-.Sh FREQUENTLY ASKED QUESTIONS
-This FAQ attempts to document some of the questions users of
-.Nm
-or readers of this manual page may encounter.
-.Ss I'm an Android user, so what's mksh?
-.Nm mksh
-is a
-.Ux
-shell / command interpreter, similar to
-.Nm COMMAND.COM
-or
-.Nm CMD.EXE ,
-which has been included with
-.Tn Android Open Source Project
-for a while now.
-Basically, it's a program that runs in a terminal (console window),
-takes user input and runs commands or scripts, which it can also
-be asked to do by other programs, even in the background.
-Any privilege pop-ups you might be encountering are thus not
-.Nm mksh
-issues but questions by some other program utilising it.
-.Ss "I'm an OS/2 user, what do I need to know?"
-Unlike the native command prompt, the current working directory is,
-for security reasons common on Unix systems which the shell is designed for,
-not in the search path at all; if you really need this, run the command
-.Li PATH=.$PATHSEP$PATH
-or add that to a suitable initialisation file.
-.Pp
-There are two different newline modes for mksh-os2: standard (Unix) mode,
-in which only LF (0A hex) is supported as line separator, and "textmode",
-which also accepts ASCII newlines (CR+LF), like most other tools on OS/2,
-but creating an incompatibility with standard
-.Nm .
-If you compiled mksh from source, you will get the standard Unix mode unless
-.Fl T
-is added during compilation; you will most likely have gotten this shell
-through komh's port on Hobbes, or from his OS/2 Factory on eComStation
-Korea, which uses "textmode", though.
-Most OS/2 users will want to use "textmode" unless they need absolute
-compatibility with Unix
-.Nm .
-.Ss "How do I start mksh on a specific terminal?"
-Normally:
-.Dl mksh \-T/dev/tty2
-.Pp
-However, if you want for it to return (e.g. for an embedded
-system rescue shell), use this on your real console device instead:
-.Dl mksh \-T!/dev/ttyACM0
-.Pp
-.Nm
-can also daemonise (send to the background):
-.Dl mksh \-T\- \-c \*(aqexec cdio lock\*(aq
-.Ss "POSIX says..."
-Run the shell in POSIX mode (and possibly
-.Nm lksh
-instead of
-.Nm mksh ) :
-.Dl set \-o posix
-.Ss "I forbid stat(2) in my SELinux policy, and some things do not work!"
-Don't break Unix.
-Read up on the GIGO principle.
-Duh.
-.Ss "My prompt from <some other shell> does not work!"
-Contact us on the mailing list or on IRC, we'll convert it for you.
-.Ss "Something is going wrong with my while...read loop"
-Most likely, you've encountered the problem in which the shell runs
-all parts of a pipeline as subshell.
-The inner loop will be executed in a subshell and variable changes
-cannot be propagated if run in a pipeline:
-.Bd -literal -offset indent
-bar \*(Ba baz \*(Ba while read foo; do ...; done
-.Ed
-.Pp
-Note that
-.Ic exit
-in the inner loop will only exit the subshell and not the original shell.
-Likewise, if the code is inside a function,
-.Ic return
-in the inner loop will only exit the subshell and won't terminate the function.
-.Pp
-Use co-processes instead:
-.Bd -literal -offset indent
-bar \*(Ba baz \*(Ba&
-while read \-p foo; do ...; done
-exec 3\*(Gt&p; exec 3\*(Gt&\-
-.Ed
-.Pp
-If
-.Ic read
-is run in a loop such as
-.Ic while read foo; do ...; done
-then leading whitespace will be removed (IFS) and backslashes processed.
-You might want to use
-.Ic while IFS= read \-r foo; do ...; done
-for pristine I/O.
-Similarly, when using the
-.Fl a
-option, use of the
-.Fl r
-option might be prudent
-.Pq Dq Li read \-raN\-1 arr \*(Ltfile ;
-the same applies for NUL-terminated lines:
-.Bd -literal -offset indent
-find . \-type f \-print0 \*(Ba& \e
-    while IFS= read \-d \*(aq\*(aq \-pr filename; do
-	print \-r \-\- "found \*(Lt${filename#./}\*(Gt"
-done
-.Ed
-.Pp
-.Ss "What differences in function-local scopes are there?"
-.Nm
-has a different scope model from
-.At
-.Nm ksh ,
-which leads to subtle differences in semantics for identical builtins.
-This can cause issues with a
-.Ic nameref
-to suddenly point to a local variable by accident.
-.Pp
-.Tn GNU
-.Nm bash
-allows unsetting local variables; in
-.Nm ,
-doing so in a function allows back access to the global variable
-(actually the one in the next scope up) with the same name.
-The following code, when run before the function definitions, changes
-the behaviour of
-.Ic unset
-to behave like other shells (the alias can be removed after the definitions):
-.Bd -literal -offset indent
-case ${KSH_VERSION:\-} in
-*MIRBSD\ KSH*\*(Ba*LEGACY\ KSH*)
-	function unset_compat {
-		\e\ebuiltin typeset unset_compat_x
-
-		for unset_compat_x in "$@"; do
-			eval "\e\e\e\ebuiltin unset $unset_compat_x[*]"
-		done
-	}
-	\e\ebuiltin alias unset=unset_compat
-	;;
-esac
-.Ed
-.Pp
-When a local variable is created (e.g. using
-.Ic local ,
-.Ic typeset ,
-.Ic integer ,
-.Ic \e\ebuiltin typeset )
-it does not, like in other shells, inherit the value from the global
-(next scope up) variable with the same name; it is rather created
-without any value (unset but defined).
-.Ss "I get an error in this regex comparison"
-Use extglobs instead of regexes:
-.Dl "[[ foo =~ (foo\*(Babar).*baz ]]	# becomes"
-.Dl "[[ foo = *@(foo\*(Babar)*baz* ]]	# instead"
-.Ss "Are there any extensions to avoid?"
-.Tn GNU
-.Nm bash
-supports
-.Dq Li &\*(Gt
-.Pq and Dq Li \*(Ba&
-to redirect both stdout and stderr in one go, but this breaks POSIX
-and Korn Shell syntax; use POSIX redirections instead:
-.Dl "foo \*(Ba& bar \*(Ba& baz &\*(Gtlog			     # GNU bash"
-.Dl "foo 2\*(Gt&1 \*(Ba bar 2\*(Gt&1 \*(Ba baz \*(Gtlog 2\*(Gt&1	# POSIX"
-.Ss "\*(haL (Ctrl-L) does not clear the screen"
-Use \*(ha[\*(haL (Escape+Ctrl-L) or rebind it:
-.Dl bind \*(aq\*(haL=clear-screen\*(aq
-.Ss "\*(haU (Ctrl-U) clears the entire line"
-If it should only delete the line up to the cursor, use:
-.Dl bind \-m \*(haU=\*(aq\*(ha[0\*(haK\*(aq
-.Ss "Cursor Up behaves differently from zsh"
-Some shells make Cursor Up search in the history only for
-commands starting with what was already entered.
-.Nm
-separates the shortcuts: Cursor Up goes up one command
-and PgUp searches the history as described above.
-.Ss "My question is not answered here!"
-Check
-.Pa http://www.mirbsd.org/mksh\-faq.htm
-which contains a collection of frequently asked questions about
-.Nm
-in general, for packagers, etc. while these above are in user scope.
diff --git a/src/mksh.faq b/src/mksh.faq
new file mode 100644
index 0000000..83e0191
--- /dev/null
+++ b/src/mksh.faq
@@ -0,0 +1,620 @@
+RCSID: $MirOS: src/bin/mksh/mksh.faq,v 1.7 2020/04/25 12:09:55 tg Exp $
+ToC: spelling
+Title: How do you spell <tt>mksh</tt>? How do you pronounce it?
+
+<p>This <a href="@@RELPATH@@mksh.htm">shell</a> is spelt either
+ “<tt>mksh</tt>” (with, even at the beginning of a sentence, <a
+ href="https://en.wikipedia.org/wiki/Wikipedia:Manual_of_Style/Capital_letters#Items_that_require_initial_lower_case">an
+ initial lowercase letter</a>; this is important) or “MirBSD Korn Shell”,
+ possibly with “the”.</p>
+<p>I usually pronounce it as “<span xml:lang="de-DE-1901">em-ka-es-ha</span>”,
+ that is, the letters individually in my native German, or say “MirBSD Korn
+ Shell”, although it is manageable, mostly for Slavic speakers, to actually
+ say “mksh” as if it were a word ☺</p>
+<p>Oh… I’ve run into this one, didn’t I? “MirBSD” is pronounced “<span
+ xml:lang="de-DE-1901">Mir-Be-Es-De</span>” germanically, for anglophones
+ “Mir-beas’tie” is fine.</p>
+----
+ToC: sowhatismksh
+Title: I’m a $OS (<i>Android, OS/2, …</i>) user, so what’s mksh?
+
+<p>mksh is a so-called (Unix) “shell” or “command interpreter”, similar to
+ <tt>COMMAND.COM</tt>, <tt>CMD.EXE</tt> or PowerShell on other operating
+ systems you might know. Basically, it runs in a terminal (“console” or
+ “DOS box”) window, taking user input and running that as commands. It’s
+ also used to write so-called (shell) “script”s, short programs made by
+ putting several of those commands into a “batch file”.</p>
+<p>On Android, mksh is used as the system shell — basically, the one
+ running commands at system startup, in the background, and on user
+ behalf (but never of its own). Any privilege pop-ups you might <a
+ href="https://forum.xda-developers.com/showthread.php?t=1963976">be
+ encountering</a> are therefore <a
+ href="https://forum.xda-developers.com/showpost.php?p=33550523&amp;postcount=1553">not
+ caused by mksh</a> but by some other code invoking mksh to do something
+ on its behalf.</p>
+----
+ToC: os2
+Title: I’m an OS/2 user, what else do I need to know?
+
+<p>Unlike the native command prompt, the current working directory is,
+ for security reasons common on Unix systems which the shell is designed
+ for, not in the search path at all; if you really need this, run the
+ command <tt>PATH=.$PATHSEP$PATH</tt> or add that to a suitable
+ initialisation file (<tt>~/.mkshrc</tt>).</p>
+<p>There are two different newline modes for mksh-os2: standard (Unix)
+ mode, in which only LF (0A hex) is supported as line separator, and
+ “textmode”, which also accepts ASCII newlines (CR+LF), like most other
+ tools on OS/2, but creating an incompatibility with standard mksh. If
+ you compiled mksh from source, you will get the standard Unix mode unless
+ <tt>-T</tt> is added during compilation; however, you will most likely
+ have gotten this shell through komh’s port on Hobbes, or from his OS/2
+ Factory on eComStation Korea, which uses “textmode”, though. Most OS/2
+ users will want to use “textmode” unless they need absolute compatibility
+ with Unix mksh and other Unix shells and tools.</p>
+----
+ToC: kornshell
+Title: How does this relate to ksh or the Korn Shell?
+
+<p>The Korn Shell (AT&amp;T ksh) was authored by David Korn; two major
+ flavours exist (ksh88 and ksh93), the latter having been maintained
+ until 2012 (last formal release) and 2014 (last beta snapshot, buggy).
+ A ksh86 did exist.</p>
+<p>There’s now <tt>ksh2020</tt>, a project having restarted development
+ around November 2017 forking the last <tt>ksh93 v-</tt> (beta) snapshot
+ and continuing to develop it, presented at FOSDEM.</p>
+<p>AT&amp;T ksh88 is “the (original) Korn Shell”. Other implementations,
+ of varying quality (MKS Toolkit’s MKS ksh being named as an example of
+ the lower end, MirBSD’s mksh at the upper end). They are all <em>not</em>
+ “Korn Shell” or “ksh”. However, mksh got blessed by David Korn, as long
+ as it cannot be confused with the original Korn Shell.</p>
+<p>The POSIX shell standard, while lacking most Korn Shell features, was
+ largely based on AT&amp;T ksh88, with some from the Bourne shell.</p>
+<p>mksh is the currently active development of what started as the Public
+ Domain Bourne Shell in the mid-1980s with ksh88-compatibl-ish extensions
+ having been added later, making the Public Domain Korn Shell (pdksh),
+ which, while never officially blessed, was the only way for most to get
+ a Korn Shell-like command interpreter for AT&amp;T’s was proprietary,
+ closed-source code for a very long time. pdksh’s development ended in
+ 1999, with some projects like Debian and NetBSD® creating small bug fixes
+ (which often introduced new bugs) as part of maintenance. Around 2003,
+ OpenBSD started cleaning up their shipped version of pdksh, removing old
+ and compatibility code and modernising it. In 2002, development of what
+ is now mksh started as the system shell of MirBSD, which took over almost
+ all of OpenBSD’s cleanup, adding compatibility to other operating systems
+ back on top of it, and after 2004, independent, massive development of
+ bugfixes including a complete reorganisation of the way the parser works,
+ and of new features both independent and compatible with other shells
+ (ksh93, GNU bash, zsh, BSD csh) started and was followed by working with
+ the group behind POSIX to fix issues both in the standard and in mksh.
+ mksh became the system shell in several other operating systems and Linux
+ distributions and Android and thus is likely the Korn shell, if not Unix
+ shell, flavour with the largest user base. It has replaced pdksh in all
+ contemporary systems except QNX, NetBSD® and OpenBSD (who continue to
+ maintain their variant on “low flame”).</p>
+<p>dtksh is the “Desktop Korn Shell”, a build of AT&amp;T ksh93 with some
+ additional built-in utilities for graphics programming (windows, menu
+ bars, dialogue boxes, etc.) utilising Motif bindings.</p>
+<p>MKS ksh is a proprietary reimplemention aiming for, but not quite
+ getting close to, ksh88 compatibility.</p>
+<p>SKsh is an AmigaOS-specific Korn Shell-lookalike by Steve Koren.</p>
+<p>The <a href="@@RELPATH@@ksh-chan.htm">Homepage of the <tt>#ksh</tt>
+ channel on Freenode IRC</a> contains more information about the Korn
+ Shell in general and its flavours.</p>
+----
+ToC: packaging
+Title: How should I package mksh? (common cases)
+
+<p>Export a few environment variables, namely <tt>CC</tt> (the C compiler),
+ <tt>CPPFLAGS</tt> (all C præprocessor definitions), <tt>CFLAGS</tt> (only
+ compiler flags, <em>no</em> <tt>-Dfoo</tt> or anything!), <tt>LDFLAGS</tt>
+ (for anything to pass to the C compiler while linking) and <tt>LIBS</tt>
+ (appended to the linking command line after everything else. You might
+ wish to <tt>export LDSTATIC=-static</tt> for a static build as well.</p>
+<p>When cross-compiling, <tt>CC</tt> is the <em>cross</em> compiler (mksh
+ currently does not require a compiler targetting the build system), but
+ you <em>must</em> also export <tt>TARGET_OS</tt> to whatever system you
+ are compiling for, e.g. “Linux”. For most operating systems, that’s just
+ the uname(1) output. Some very rare systems also need <tt>TARGET_OSREV</tt>;
+ consult the source code of <tt>Build.sh</tt> for details.</p>
+<p>Create two subdirectories, say <tt>build-mksh</tt> and <tt>build-lksh</tt>.
+ In each of them, start a compilation by issuing <tt>sh ../Build.sh -r</tt>
+ followed by running the testsuite<a href="#packaging-fn1">¹</a> via
+ <tt>./test.sh</tt>. For lksh(1) add <tt>-DMKSH_BINSHPOSIX</tt> to
+ <tt>CPPFLAGS</tt> and use <tt>sh ../Build.sh -r -L</tt> to compile.</p>
+<p>See <a href="#testsuite-fails">below</a> if the testsuite fails.</p>
+<p>Install <tt>build-mksh/mksh</tt> as <tt>/bin/mksh</tt> (or similar),
+ <tt>build-lksh/lksh</tt> as <tt>/bin/lksh</tt> with a symlink(7) to it
+ from <tt>/bin/sh</tt> (if desred), and <tt>mksh.1</tt> and <tt>lksh.1</tt>
+ as manpages (mdoc macropackage required). Install <tt>dot.mkshrc</tt>
+ either as <tt>/etc/skel/.mkshrc</tt> (meaning your users will have to
+ manually resynchronise their home directories’ copies after every package
+ upgrade) or as <tt>/etc/mkshrc</tt>, in which case you install a <a
+ href="https://evolvis.org/plugins/scmgit/cgi-bin/gitweb.cgi?p=alioth/mksh.git;a=blob;f=debian/.mkshrc;hb=HEAD">redirection
+ script like Debian’s</a> into <tt>/etc/skel/.mkshrc</tt>. You may need a <a
+ href="@@RELPATH@@TaC-mksh.txt">summary of the licence information</a>.</p>
+<p>At runtime, the presence of <tt>/bin/ed</tt> as default history editor
+ is recommended, as well as a manpage formatter; you can also install
+ preformatted manpages from <tt>build-*ksh/*ksh.cat1</tt> if nroff(1) (or
+ <tt>$NROFF</tt>) is available at build time by removing the <tt>-r</tt>
+ flag from either <tt>Build.sh</tt> invocation.</p>
+<p>Some shell features require the ability to create temporary files and
+ FIFOS (cf. mkfifo(2))/pipes at runtime. Set <tt>TMPDIR</tt> to a suitable
+ location if <tt>/tmp</tt> isn’t it; if this is known ahead of time, you
+ can add <tt>-DMKSH_DEFAULT_TMPDIR=\"/path/to/tmp\"</tt> to CPPFLAGS. We
+ currently are unable to determine one on Android because its bionic libc
+ does not expose any method suitable to do so in the generic case.</p>
+<p id="packaging-fn1">① To run the testsuite, ed(1) must be available as
+ <tt>/bin/ed</tt>, and perl(1) is needed. When cross-compiling, the version
+ of the first <tt>ed</tt> binary on the <tt>PATH</tt> <em>must</em> be the
+ same as that in the target system on which the tests are to be run, in
+ order to be able to detect which flavour of ed to adjust the tests for.
+ Busybox ed is broken beyond repair, and all three ed-related tests will
+ always fail with it.</p>
+----
+ToC: mkshrc
+Title: How does mksh load configuration files?
+
+<p>The shell loads first <tt>/etc/profile</tt> then <tt>~/.profile</tt>
+ if called as login shell or with the <tt>-l</tt> flag, then loads the file
+ <tt>$ENV</tt> points to (defaulting to <tt>~/.mkshrc</tt>) for interactive
+ shells (that includes login shells).</p>
+<p>Distributors should take care to either install the <tt>dot.mkshrc</tt>
+ example provided into <tt>/etc/skel/.mkshrc</tt> (so that it’s available
+ for newly created user accounts) and ensure it can propagate to existing
+ accounts or, if upgrading these is difficult, install the shipped file
+ as, for example, <tt>/etc/mkshrc</tt> and install a skeleton file, such
+ as the one in Debian, that sources the file in <tt>/etc</tt>.</p>
+<p>It’s vital that users can change the configuration, so do not force a
+ root-provided config file onto them; the shipped file, after all, is just
+ an example.</p>
+<p>If you need central user and configuration management and cannot use
+ something that installs skeleton files upon home directory creation
+ (like pam_mkhomedir), you can <tt>export ENV</tt> in <tt>/etc/profile</tt>
+ to a file (say <tt>/etc/shellrc</tt>) that sources the per-shell file.
+ Users can, this way, still override it by setting a different <tt>$ENV</tt>
+ in their <tt>~/.profile</tt>.</p>
+----
+ToC: testsuite-fails
+Title: The testsuite fails!
+
+<p>The mksh testsuite has uncovered numerous bugs in operating systems
+ (kernels, libraries), compilers and toolchains. It is likely that you
+ just ran into one. If you’re using LTO (the <tt>Build.sh</tt> option
+ <tt>-c lto</tt>) try to disable it first — especially GCC is a repeat
+ offender breaking LTO and its antecessor <tt>-fwhole-program --combine</tt>
+ and tends to do wrong code generation quite a bit. Otherwise, try
+ lowering the optimisation levels, bisecting, etc.</p>
+----
+ToC: selinux-androidiocy
+Title: I forbid stat(2) in my SELinux policy, and some things do not work!
+
+Don’t break Unix. Read up on the GIGO principle. Duh.
+----
+ToC: makefile
+Title: Why doesn’t this use a Makefile to build?
+
+<p>Not all supported target operating environments have a make utility
+ available, and shell was required for “mirtoconf” (like autoconf)
+ already anyway, so it was chosen to run the make part as well.</p>
+<p>You can, however, add the <tt>-M</tt> flag to your <tt>Build.sh</tt>
+ invocations to let it produce a <tt>Makefrag.inc</tt> file <em>tailored
+ for this specific build</em> which you can then include in a Makefile,
+ such as with the BSD make(1) “.include” command or <a
+ href="https://www.gnu.org/software/make/manual/make.html#Include">GNU
+ make</a> equivalent. It even contains, for the user to start out with,
+ a commented-out example of how to do that in the most basic manner.</p>
+----
+ToC: oldbsd
+Title: Why do other BSDs and QNX still use pdksh instead of mksh?
+
+<p>Some systems are resistent to change, mostly due to bikeshedding
+ (some people would, for example, rather see all shells banned to
+ ports/pkgsrc®) and hysterial raisins (historical reasons ☻). Most
+ BSDs have mksh packages available, and it works on all of them and
+ QNX just fine.</p>
+<p>In fact, on all of these systems, you can replace their 1999-era
+ <tt>/bin/ksh</tt> (which is a pdksh) with mksh. On at least NetBSD®
+ 1.6 and up (not 1.5) and OpenBSD, even <tt>/bin/sh</tt> is fair game.</p>
+<p>MidnightBSD notably has adopted mksh as system shell (thanks laffer1).</p>
+----
+ToC: openbsd
+Title: Why is there no mksh in OpenBSD’s ports tree?
+
+OpenBSD don’t like people who fork off their project at all; heck,
+they don’t even like the people they themselves forked off (NetBSD®).
+Several people tried over the years to get one committed, but nobody
+dared so as to not lose their commit bit. If you try, succeed, and
+survive Theo, however, kudos to you! See also <a href="#oldbsd">the
+“other BSDs” FAQ entry</a>.
+----
+ToC: book
+Title: I’d like an introduction.
+
+Unfortunately, nobody has written a book about mksh yet, although
+other shells have received (sometimes decent) attention from authors
+and publishers. This FAQ lists a subset of things packagers and
+generic people ask, and the mksh(1) manpage is more of a reference,
+so you are probably best off starting with a shell-agnostic, POSIX
+or ksh88 reference such as the first edition (the second one deals
+with ksh93 which differs far more from mksh than ksh88, as ancient
+as it is, does) of the O’Reilly book (⚠ disclaimer: only an example,
+not a recommendation) and going forward by reading scripts (the
+“shellsnippets” repository referenced in the <tt>#ksh</tt> channel
+homepage (see the top of this document) has many examples) and
+trying to understand them and the mksh specifics from the manpage.
+----
+ToC: ps1conv
+Title: My prompt from &lt;<i>some other shell</i>&gt; does not work!
+
+<a href="#contact">Contact</a> us on the mailing list or on IRC,
+we’ll convert it for you. Also have a look at the PS1 section in
+the mksh(1) manpage (search for “otherwise unused char”, e.g. with
+<tt>/</tt> in less(1), to spot it quickly).
+----
+ToC: ps1weird
+Title: My prompt is weird!
+
+<p>There are several reasons why your <tt>PS1</tt> might be not
+ what you’d expect:</p><ul>
+<li><tt>$PS1</tt> is <tt>export</tt>ed. <strong>Do not export PS1!</strong>
+ (This was agreed upon as suggestion in a discussion between bash, zsh and
+ Korn shell developers.) The feature set of different shells vastly differs
+ and each shell should use its default PS1 or from its startup files.</li>
+<li><tt>$ENV</tt> <a href="#env">is set and/or <tt>export</tt>ed</a>.</li>
+<li>Your prompt is just “<tt># </tt>”: you’re entering a root shell, and
+ <tt>$PS1</tt> does not contain the ‘#’ character, in which case the shell
+ forces this prompt, making extra privileges obvious.</li>
+<li>Your prompt is just “<tt>$ </tt>”: perhaps your system administrator
+ did not install the shipped <tt>dot.mkshrc</tt> file, or you did not copy
+ <tt>/etc/skel/.mkshrc</tt> into your home directory (perhaps it was created
+ before <tt>mksh</tt> was installed?). Without another idea for a fix, get <a
+ href="http://www.mirbsd.org/cvs.cgi/~checkout~/src/bin/mksh/dot.mkshrc?rev=HEAD;content-type=text%2Fplain">this
+ file</a> and store it as <tt>~/.mkshrc</tt> then run <tt>mksh</tt>; this
+ will at the very least install our sample (“user@host:path $ ”) prompt.</li>
+<li>Your prompt contains things like “\u” or “\w”: it is for another shell
+ and <a href="#ps1conv">needs converting</a>.</li>
+<li>Your prompt contains colours, and when the command line is long the
+ cursor position or screen contents, especially using the history, is off:
+ terminal escapes must be escaped from the shell; check the PS1 section in
+ the manpage: search for “otherwise unused char” (see above).</li>
+<li>If the prompt doesn’t leave enough space on the right, the shell inserts
+ a line break after it when rendering.</li>
+</ul>
+----
+ToC: env
+Title: On startup files and <tt>$ENV</tt> across and detecting various shells
+
+Interactive shells look at <tt>~/.mkshrc</tt> (or <tt>/system/etc/mkshrc</tt>
+on Android and <tt>/etc/mkshrc</tt> on FreeWRT and OpenWrt) by default. This
+location can, however, be overridden by setting the <tt>ENV</tt> environment
+variable. (FreeBSD is rumoured to set it in their system profile.) It’s better
+to not set <tt>$ENV</tt> if possible and let every shell user their native
+startup files; otherwise, you must ensure that it runs under all shells. Check
+<tt>$BASH_VERSION</tt> (GNU bash), <tt>$KSH_VERSION</tt> (contains “LEGACY KSH”
+or “MIRBSD KSH” for mksh, “PD KSH” for ancient mirbsdksh/oksh/pdksh, “Version”
+for ksh93); <tt>$NETBSD_SHELL</tt> (NetBSD ash); <tt>POSH_VERSION</tt> (posh, a
+pdksh derivative); <tt>$SH_VERSION</tt> (“PD KSH” as sh), <tt>$YASH_VERSION</tt>
+(yash), <tt>$ZSH_VERSION</tt> (or if <tt>$VERSION</tt> begins with “zsh”); a <a
+href="@@RELPATH@@ksh-chan.htm#which-shell">list of more approaches</a> exists.
+----
+ToC: ctrl-l-cls
+Title: ^L (Ctrl-L) does not clear the screen
+
+Use ^[^L (Escape+Ctrl-L) or rebind it:<br />
+<tt>bind '^L=clear-screen'</tt>
+----
+ToC: ctrl-u-pico
+Title: ^U (Ctrl-U) clears the entire line
+
+If it should only delete the line up to the cursor, use:<br />
+<tt>bind -m ^U='^[0^K'</tt>
+----
+ToC: cur-up-zsh
+Title: Cursor Up behaves differently from zsh
+
+Some shells make Cursor Up search in the history only for commands
+starting with what was already entered. mksh separates the shortcuts:
+Cursor Up goes up one command and PgUp searches the history as described
+above. You can, of course, rebind:<br />
+<tt>bind '^XA=search-history-up'</tt>
+----
+ToC: current
+Title: Can mksh set the title of the window according to the command running?
+
+There’s no such thing as “the command currently running”; consider
+pipelines and delays (<tt>cmd1 | (cmd2; sleep 3; cmd3) | cmd4</tt>).
+There is, however, a way to make the shell display the command <em>line</em>
+during the time it is executed; for testing, you will need to download <a
+href="https://evolvis.org/plugins/scmgit/cgi-bin/gitweb.cgi?p=shellsnippets/shellsnippets.git;a=blob;f=mksh/terminal-title;hb=HEAD">this
+script</a> and <tt>source</tt> it. For merging into your <tt>~/.mkshrc</tt>
+you should first understand how it works: lines 4–18 set a <tt>PS1</tt>
+(prompt) equivalent to lines 84–96 of the stock <tt>dot.mkshrc</tt>, with
+one change: line 15 (<tt>print &gt;/dev/tty …</tt>) is new, inserted just
+before the <tt>return</tt> command of the function substitution in the
+default prompt; this is what you’ll need to merge into your own, custom,
+prompt (if you have one; otherwise pull this adaption to the default
+one). Line 19 is the only other thing in this script rebinding the Ctrl-M
+key (which is normally produced by the Enter/Return key) to code that…
+does <em>something crazy</em>. This trick however <em>does funny things with
+multiline commands</em>, so if you type something out in multiple lines,
+for example <strong>here documents</strong> or <strong>loops</strong> press
+<strong>Ctrl-J instead of Enter/Return</strong> after <em>each</em> line
+including the first (at PS1) and final (at PS2) one.
+----
+ToC: other-tty
+Title: How do I start mksh on a specific terminal?
+
+<p>Normally: <tt>mksh -T<i>/dev/tty2</i></tt></p>
+<p>However, if you want for it to return (e.g. for an embedded system rescue
+ shell), use this on your real console device instead:
+ <tt>mksh -T!<i>/dev/ttyACM0</i></tt></p>
+<p>mksh can also daemonise (send to the background):
+ <tt>mksh -T- -c 'exec cdio lock'</tt></p>
+----
+ToC: completion
+Title: What about programmable tab completion?
+
+The shell itself provides static deterministic tab completion.
+However, you can use hooks like reprogramming the Tab key to a
+command line editor macro, and using the <tt>evaluate-region</tt>
+editor command (modulo a bugfix) together with <tt>quote-region</tt> and shell functions to
+implement a programmable completion engine. Multiple people have
+been considering doing so in our IRC channel; we’ll hyperlink to
+these engines when they are available.
+----
+ToC: posix-mode
+Title: How POSIX compliant is mksh? Also, UTF-8 vs. locales?
+
+<p>You’ll need to use the <tt>lksh</tt> binary, unless your C <tt>long</tt>
+ type is 32 bits wide, for POSIX-compliant arithmetic in the shell. This is
+ because <tt>mksh</tt> provides consistent, wraparound-defined, 32-bit
+ arithmetics on all platforms normally. You’ll also need to enable POSIX mode
+ (<tt>set -o posix</tt>) explicitly, which also disables brace expansion upon
+ being enabled (use <tt>set -o braceexpand</tt> to reenable if needed).</p>
+<p>For the purpose of POSIX, mksh supports only the <tt>C</tt> locale. mksh’s
+ <tt>utf8-mode</tt> (which only supports the BMP (Basic Multilingual Plane) of
+ UCS and maps raw octets into the U+EF80‥U+EFFF wide character range; see
+ <tt>Arithmetic expressions</tt> in mksh(1) for details) <em>must</em> stay
+ disabled in POSIX mode (it is disabled upon enabling POSIX mode in R56+).</p>
+<p class="boxhead">The following POSIX sh-compatible code toggles the
+ <tt>utf8-mode</tt> option dependent on the current POSIX locale, for mksh
+ to allow using the UTF-8 mode, within the constraints outlined above, in
+ code portable across various shell implementations:</p>
+<div class="boxtext">
+ <pre>
+	case ${KSH_VERSION:-} in
+	*MIRBSD KSH*|*LEGACY KSH*)
+		case ${LC_ALL:-${LC_CTYPE:-${LANG:-}}} in
+		*[Uu][Tt][Ff]8*|*[Uu][Tt][Ff]-8*) set -U ;;
+		*) set +U ;;
+		esac ;;
+	esac
+ </pre>
+</div><p class="boxfoot">In near future, (UTF-8) locale tracking will
+ be implemented, though.</p>
+<p>The shell is pretty close to POSIX, when run as <tt>lksh -o posix</tt>
+ under the "C" locale it is intended to match. It does not do everything
+ like other POSIX-compatible or ‑compliant shells, though.</p>
+----
+ToC: function-local-scopes
+Title: What differences in function-local scopes are there?
+
+<p><tt>mksh</tt> has a different scope model from AT&amp;T <tt>ksh</tt>,
+ which leads to subtle differences in semantics for identical builtins.
+ This can cause issues with a <tt>nameref</tt> to suddenly point to a
+ local variable by accident. (Other common shells share mksh’s scoping
+ model.)</p>
+<p class="boxhead">GNU <tt>bash</tt> allows unsetting local variables; in
+ <tt>mksh</tt>, doing so in a function allows back access to the global
+ variable (actually the one in the next scope up) with the same name. The
+ following code, when run before function definitions, changes the behaviour
+ of <tt>unset</tt> to behave like other shells (the alias can be removed
+ after the definitions):</p>
+<div class="boxtext">
+ <pre>
+	case ${KSH_VERSION:-} in
+	*MIRBSD KSH*|*LEGACY KSH*)
+		function unset_compat {
+			\\builtin typeset unset_compat_x
+
+			for unset_compat_x in "$@"; do
+				eval "\\\\builtin unset $unset_compat_x[*]"
+			done
+		}
+		\\builtin alias unset=unset_compat
+		;;
+	esac
+ </pre>
+</div><p class="boxfoot">When a local variable is created (e.g. using
+ <tt>local</tt>, <tt>typeset</tt>, <tt>integer</tt> or
+ <tt>\\builtin typeset</tt>) it does not, like in other shells, inherit
+ the value from the global (next scope up) variable with the same name;
+ it is rather created without any value (unset but defined).</p>
+----
+ToC: regex-comparison
+Title: I get an error in this regex comparison
+
+<p>Use extglobs instead of regexes:<br />
+ <tt>[[ foo =~ (foo|bar).*baz ]]</tt><br />
+ … becomes…<br />
+ <tt>[[ foo = *@(foo|bar)*baz* ]]</tt></p>
+----
+ToC: extensions-to-avoid
+Title: Are there any extensions to avoid?
+
+<p>GNU <tt>bash</tt> supports “<tt>&amp;&gt;</tt>” (and “|&amp;”) to redirect
+ both stdout and stderr in one go, but this breaks POSIX and Korn Shell syntax;
+ use POSIX redirections instead:</p>
+<table border="1" cellpadding="3">
+ <tr><td>GNU bash</td><td>
+  <tt>foo |&amp; bar |&amp; baz &amp;&gt;log</tt>
+ </td></tr>
+ <tr><td>POSIX</td><td>
+  <tt>foo 2&gt;&amp;1 | bar 2&gt;&amp;1 | baz &gt;log 2&gt;&amp;1</tt>
+ </td></tr>
+</table>
+----
+ToC: while-read-pipe
+Title: Something is going wrong with my while...read loop
+
+<p class="boxhead">Most likely, you’ve encountered the problem in which
+ the shell runs all parts of a pipeline as subshell. The inner loop will
+ be executed in a subshell and variable changes cannot be propagated if
+ run in a pipeline:</p>
+<div class="boxtext">
+ <pre>
+	bar | baz | while read foo; do ...; done
+ </pre>
+</div><p class="boxfoot">Note that <tt>exit</tt> in the inner loop will
+ also only exit the subshell and not the original shell. Likewise, if the
+ code is inside a function, <tt>return</tt> in the inner loop will only
+ exit the subshell and won’t terminate the function.</p>
+<p class="boxhead">Use co-processes instead:</p>
+<div class="boxtext">
+ <pre>
+	bar | baz |&amp;
+	while read -p foo; do ...; done
+	exec 3&gt;&amp;p; exec 3&gt;&amp;-
+ </pre>
+</div><p class="boxfoot">If <tt>read</tt> is run in a way such as
+ <tt>while read foo; do ...; done</tt> then leading whitespace will be
+ removed (IFS) and backslashes processed. You might want to use
+ <tt>while IFS= read -r foo; do ...; done</tt> for pristine I/O.</p>
+<p class="boxhead">Similarly, when using the <tt>-a</tt> option, use of the
+ <tt>-r</tt> option might be prudent (<tt>read -raN-1 arr &lt;file</tt>);
+ the same applies for NUL-terminated lines:</p>
+<div class="boxtext">
+ <pre>
+	find . -type f -print0 |&amp; \
+	    while IFS= read -d '' -pr filename; do
+		print -r -- "found &lt;${filename#./}&gt;"
+	done
+ </pre>
+</div>
+----
+ToC: command-alias
+Title: “command” doesn’t expand aliases as in ksh93
+
+This is because AT&amp;T ksh93 ships a predefined alias enabling this:<br />
+<tt>alias command='command '</tt><br />
+put this into your <tt>~/.mkshrc</tt>
+(note the space before the closing single quote)
+----
+ToC: builtin-rename
+Title: “rename” doesn’t work as expected!
+
+<p>There’s a <tt>rename</tt> built-in utility in mksh, which is a very
+ thin wrapper around the rename(2) syscall. It receives two pathnames,
+ source and destination where the first is then atomically renamed to
+ the latter. It does not move, i.e. fails for different filesystems.</p>
+<p>The GNU package <tt>util-linux</tt> has a different <tt>rename</tt>
+ command. If you wish to invoke an external utility (in favour over a
+ builtin), you can use <tt>dot.mkshrc</tt>’s function <tt>enable</tt>
+ or put the following into your <tt>~/.mkshrc</tt>:</p>
+<pre>alias rename="$(whence -p rename)"</pre>
+----
+ToC: builtin-sleep
+Title: “sleep” does not accept ‘m’ for minutes!
+
+<p>mksh contains a <tt>sleep</tt> built-in utility, in order to be
+ able to offer sub-second sleep to shell scripts for most platforms.
+ (It does not exist if the platform lacks select(2) — which should
+ be rare.)</p>
+<p>GNU coreutils contains a sleep implementation accepting suffixed
+ numbers. If you wish to invoke an external utility (in favour over a
+ builtin), you can use <tt>dot.mkshrc</tt>’s function <tt>enable</tt>
+ or put something along the following lines into <tt>~/.mkshrc</tt>:</p>
+<pre>alias sleep="$(whence -p sleep)"</pre>
+<pre>timer() { sleep $(($1*60${2:++$2})); } # timer mins [secs]</pre>
+<pre>timer() {
+	local arg=${1/m/'*60+'}
+	[[ $arg = *+ ]] &amp;&amp; arg+=0
+	sleep $(($arg)
+}</pre>
+----
+ToC: string-concat
+Title: “+=” behaves differently from other shells
+
+<p>In POSIX shell, “=” in code like <tt>var=content</tt> is a string
+ assignment, always. You can use <tt>var=$((content))</tt> for an
+ arithmetic assignment that mostly uses C language rules.</p>
+<p>It stands to consider that the common shell extension “+=” as in
+ <tt>var+=content</tt> would always do string concatenation; it does
+ in mksh, but not in some other shells, in which, when <tt>var</tt> has
+ been declared integer, addition is done instead.</p>
+<p>You can make the code portable by using “((…))” (a.k.a. <tt>let</tt>)
+ instead: <tt>(( var += content ))</tt> does arithmetic addition in
+ all shells involved.</p>
+----
+ToC: set-e
+Title: I use “set -e” and my code unexpectedly errors out
+
+<p>I personally recommend people to not use “<tt>set -e</tt>”, as it
+makes error handling more difficult. However, some insist. There have
+been bugfixes (relative to e.g. oksh/loksh and posh) in this aspect,
+and the user has to make sure <tt>$?</tt> is always 0 ASAP even after
+a command that doesn’t check it.</p>
+<pre>istwo() {
+        for i in "$@"; do
+                test x"$i" = x"2" &amp;&amp; echo two
+        done
+}
+set -e
+istwo 1
+echo END</pre>
+<p>This can be fixed by either adding an explicit “<tt>:</tt>” (or
+“<tt>true</tt>”) after the comparison, or even…</p>
+<pre>test x"$i" = x"2" &amp;&amp; echo two || :</pre>
+<p>… or right after the <tt>done</tt> inside the function, but…</p>
+<pre>test x"$i" != x"2" || echo two</pre>
+<p>… negating the condition and using “<tt>||</tt>” is preferable.</p>
+
+<p>Remember that Korn shell-style functions (with <tt>function</tt>
+ keyword and <strong>without</strong> parenthesēs) in AT&amp;T ksh93
+ and mksh R51 and up have their own shell option scope, but while…</p>
+<pre>function istwo {
+        set +e
+        …
+}</pre>
+<p>… might help in error handling, the return status of a function is
+ still the last errorlevel inside, so an explicit true (“<tt>:</tt>”)
+ or, more explicitly, “<tt>return 0</tt>” at its end is still needed
+ if the <em>caller</em> runs under <tt>set -e</tt>.</p>
+----
+ToC: set-eo-pipefail
+Title: I use “set -eo pipefail” and my code unexpectedly errors out
+
+<p class="boxhead">Related to the above FAQ entry, using
+ <tt>set -o pipefail</tt> makes the following construct error out:</p>
+<div class="boxtext">
+ <pre>
+	set -e
+	for x in 1 2; do
+		false &amp;&amp; echo $x
+	done | cat
+ </pre>
+</div><p class="boxfoot">This is because, while the <tt>&amp;&amp;</tt>
+ ensures that the inner command’s failure is not taken, it sets the entire
+ <tt>for</tt>‥<tt>done</tt> loop’s errorlevel, which is passed on by
+ <tt>-o pipefail</tt>.</p>
+<p>Invert the inner command:<br />
+ <tt>true || echo $x</tt></p>
+----
+ToC: faq
+Title: My question is not answered here!
+
+Do read the mksh(1) manual page. You might also wish to read the <a
+ href="@@RELPATH@@ksh-chan.htm">homepage of the <tt>#ksh</tt> IRC channel
+on Freenode</a> which lists several resources for Korn or POSIX-compatible
+shells in general. Or, <a href="#contact">contact</a> us (developer and
+users), for example via IRC.
+----
+ToC: contact
+Title: How do I contact you (to say thanks)?
+
+You can say hi in the <tt>#!/bin/mksh</tt> channel on Freenode <a
+ href="@@RELPATH@@irc.htm">IRC</a>, although a <a
+ href="@@RELPATH@@danke.htm">donation</a> wouldn’t be amiss ☺ The <a
+ href="http://www.mail-archive.com/miros-mksh@mirbsd.org/">mailing
+list</a> can also be used.
+----
diff --git a/src/os2.c b/src/os2.c
index 2bc63ed..8a80782 100644
--- a/src/os2.c
+++ b/src/os2.c
@@ -1,5 +1,5 @@
 /*-
- * Copyright (c) 2015, 2017
+ * Copyright (c) 2015, 2017, 2020
  *	KO Myung-Hun <komh@chollian.net>
  * Copyright (c) 2017
  *	mirabilos <m@mirbsd.org>
@@ -20,6 +20,7 @@
  * of said person's immediate fault when using the work as intended.
  */
 
+#define INCL_KBD
 #define INCL_DOS
 #include <os2.h>
 
@@ -31,7 +32,7 @@
 #include <unistd.h>
 #include <process.h>
 
-__RCSID("$MirOS: src/bin/mksh/os2.c,v 1.8 2017/12/22 16:41:42 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/os2.c,v 1.10 2020/04/07 11:13:45 tg Exp $");
 
 static char *remove_trailing_dots(char *);
 static int access_stat_ex(int (*)(), const char *, void *);
@@ -172,6 +173,8 @@
 void
 os2_init(int *argcp, const char ***argvp)
 {
+	KBDINFO ki;
+
 	response(argcp, argvp);
 
 	init_extlibpath();
@@ -183,6 +186,12 @@
 	if (!isatty(STDERR_FILENO))
 		setmode(STDERR_FILENO, O_BINARY);
 
+	/* ensure ECHO mode is ON so that read command echoes. */
+	memset(&ki, 0, sizeof(ki));
+	ki.cb = sizeof(ki);
+	ki.fsMask |= KEYBOARD_ECHO_ON;
+	KbdSetStatus(&ki, 0);
+
 	atexit(cleanup);
 }
 
@@ -295,11 +304,12 @@
 	return (access_stat_ex(fn, name, (void *)mode));
 }
 
-/* stat() version */
+/* stat()/lstat() version */
 int
-stat_ex(const char *name, struct stat *buffer)
+stat_ex(int (*fn)(const char *, struct stat *),
+    const char *name, struct stat *buffer)
 {
-	return (access_stat_ex(stat, name, buffer));
+	return (access_stat_ex(fn, name, buffer));
 }
 
 static int
diff --git a/src/rlimits.gen b/src/rlimits.gen
index 96f5eff..5ccd007 100644
--- a/src/rlimits.gen
+++ b/src/rlimits.gen
@@ -21,7 +21,7 @@
 
 #ifndef RLIMITS_OPTCS
 #if defined(RLIMITS_DEFNS)
-__RCSID("$MirOS: src/bin/mksh/rlimits.opt,v 1.3 2015/12/12 21:08:44 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/rlimits.opt,v 1.4 2019/04/24 20:56:31 tg Exp $");
 #elif defined(RLIMITS_ITEMS)
 #define FN(lname,lid,lfac,lopt) (const struct limits *)(&rlimits_ ## lid),
 #endif
@@ -82,6 +82,9 @@
 #ifdef RLIMIT_PTHREAD
 FN("threadsperprocess", RLIMIT_PTHREAD, 1, 'P')
 #endif
+#ifdef RLIMIT_THREADS
+FN("threadsperprocess", RLIMIT_THREADS, 1, 'r')
+#endif
 #ifdef RLIMIT_NICE
 FN("maxnice", RLIMIT_NICE, 1, 'e')
 #endif
@@ -100,6 +103,9 @@
 #ifdef ULIMIT_V_IS_AS
 FN("address-space(KiB)", RLIMIT_AS, 1024, 'v')
 #endif
+#ifdef RLIMIT_LOCKS
+FN("filelocks", RLIMIT_LOCKS, 1, 'x')
+#endif
 #undef F0
 #undef FN
 #undef RLIMITS_DEFNS
@@ -158,6 +164,9 @@
 #ifdef RLIMIT_RTPRIO
 "r"
 #endif
+#ifdef RLIMIT_THREADS
+"r"
+#endif
 "S"
 #ifdef RLIMIT_STACK
 "s"
@@ -180,5 +189,8 @@
 #ifdef RLIMIT_SWAP
 "w"
 #endif
+#ifdef RLIMIT_LOCKS
+"x"
+#endif
 #undef RLIMITS_OPTCS
 #endif
diff --git a/src/rlimits.opt b/src/rlimits.opt
index 4f933ab..db2a531 100644
--- a/src/rlimits.opt
+++ b/src/rlimits.opt
@@ -19,7 +19,7 @@
  */
 
 @RLIMITS_DEFNS
-__RCSID("$MirOS: src/bin/mksh/rlimits.opt,v 1.3 2015/12/12 21:08:44 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/rlimits.opt,v 1.4 2019/04/24 20:56:31 tg Exp $");
 @RLIMITS_ITEMS
 #define FN(lname,lid,lfac,lopt) (const struct limits *)(&rlimits_ ## lid),
 @@
@@ -86,6 +86,9 @@
 >P|RLIMIT_PTHREAD
 FN("threadsperprocess", RLIMIT_PTHREAD, 1
 
+>r|RLIMIT_THREADS
+FN("threadsperprocess", RLIMIT_THREADS, 1
+
 >e|RLIMIT_NICE
 FN("maxnice", RLIMIT_NICE, 1
 
@@ -102,4 +105,7 @@
 >v|ULIMIT_V_IS_AS
 FN("address-space(KiB)", RLIMIT_AS, 1024
 
+>x|RLIMIT_LOCKS
+FN("filelocks", RLIMIT_LOCKS, 1
+
 |RLIMITS_OPTCS
diff --git a/src/sh.h b/src/sh.h
index a974bbd..8394c89 100644
--- a/src/sh.h
+++ b/src/sh.h
@@ -10,7 +10,8 @@
 
 /*-
  * Copyright © 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
- *	       2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018
+ *	       2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018,
+ *	       2019, 2020
  *	mirabilos <m@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -116,7 +117,7 @@
 #if (defined(__KLIBC__) || defined(__dietlibc__)) && \
     ((defined(__GNUC__) && (__GNUC__ > 3)) || defined(__NWCC__))
 #undef offsetof
-#define offsetof(s, e)		__builtin_offsetof(s, e)
+#define offsetof(s,e)		__builtin_offsetof(s, e)
 #endif
 
 #undef __attribute__
@@ -170,9 +171,17 @@
 #define __IDSTRING_CONCAT(l,p)		__LINTED__ ## l ## _ ## p
 #define __IDSTRING_EXPAND(l,p)		__IDSTRING_CONCAT(l,p)
 #ifdef MKSH_DONT_EMIT_IDSTRING
-#define __IDSTRING(prefix, string)	/* nothing */
+#define __IDSTRING(prefix,string)	/* nothing */
+#elif defined(__ELF__) && defined(__GNUC__) && \
+    !(defined(__GNUC__) && defined(__mips16) && (__GNUC__ >= 8)) && \
+    !defined(__llvm__) && !defined(__NWCC__) && !defined(NO_ASM)
+#define __IDSTRING(prefix,string)				\
+	__asm__(".section .comment"				\
+	"\n	.ascii	\"@(\"\"#)" #prefix ": \""		\
+	"\n	.asciz	\"" string "\""				\
+	"\n	.previous")
 #else
-#define __IDSTRING(prefix, string)				\
+#define __IDSTRING(prefix,string)				\
 	static const char __IDSTRING_EXPAND(__LINE__,prefix) []	\
 	    MKSH_A_USED = "@(""#)" #prefix ": " string
 #endif
@@ -182,9 +191,9 @@
 #endif
 
 #ifdef EXTERN
-__RCSID("$MirOS: src/bin/mksh/sh.h,v 1.870 2019/03/01 16:18:14 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/sh.h,v 1.898 2020/05/16 22:38:23 tg Exp $");
 #endif
-#define MKSH_VERSION "R57 2019/03/01"
+#define MKSH_VERSION "R59 2020/05/16"
 
 /* arithmetic types: C implementation */
 #if !HAVE_CAN_INTTYPES
@@ -255,6 +264,16 @@
 typedef MKSH_TYPEDEF_SSIZE_T ssize_t;
 #endif
 
+#if defined(MKSH_SMALL) && !defined(MKSH_SMALL_BUT_FAST)
+#define MKSH_SHF_NO_INLINE
+#endif
+
+/* do not merge these conditionals as neatcc’s preprocessor is simple */
+#ifdef __neatcc__
+/* parsing of comma operator <,> in expressions broken */
+#define MKSH_SHF_NO_INLINE
+#endif
+
 /* un-do vendor damage */
 
 #undef BAD		/* AIX defines that somewhere */
@@ -264,6 +283,9 @@
 
 #ifndef MKSH_INCLUDES_ONLY
 
+/* compile-time assertions */
+#define cta(name,expr)	struct cta_ ## name { char t[(expr) ? 1 : -1]; }
+
 /* EBCDIC fun */
 
 /* see the large comment in shf.c for an EBCDIC primer */
@@ -307,7 +329,7 @@
 	} while (/* CONSTCOND */ 0)
 #endif
 #ifndef timeradd
-#define timeradd(tvp, uvp, vvp)						\
+#define timeradd(tvp,uvp,vvp)						\
 	do {								\
 		(vvp)->tv_sec = (tvp)->tv_sec + (uvp)->tv_sec;		\
 		(vvp)->tv_usec = (tvp)->tv_usec + (uvp)->tv_usec;	\
@@ -318,7 +340,7 @@
 	} while (/* CONSTCOND */ 0)
 #endif
 #ifndef timersub
-#define timersub(tvp, uvp, vvp)						\
+#define timersub(tvp,uvp,vvp)						\
 	do {								\
 		(vvp)->tv_sec = (tvp)->tv_sec - (uvp)->tv_sec;		\
 		(vvp)->tv_usec = (tvp)->tv_usec - (uvp)->tv_usec;	\
@@ -453,7 +475,7 @@
 
 #if !HAVE_MEMMOVE
 /* we assume either memmove or bcopy exist, at the moment */
-#define memmove(dst, src, len)	bcopy((src), (dst), (len))
+#define memmove(dst,src,len)	bcopy((src), (dst), (len))
 #endif
 
 #if !HAVE_REVOKE_DECL
@@ -491,9 +513,8 @@
 #define O_BINARY	0
 #endif
 
-#ifndef O_MAYEXEC
+#undef O_MAYEXEC	/* https://lwn.net/Articles/820658/ */
 #define O_MAYEXEC	0
-#endif
 
 #ifdef MKSH__NO_SYMLINK
 #undef S_ISLNK
@@ -647,7 +668,7 @@
 #endif
 #endif
 
-#if (!defined(MKSH_BUILDMAKEFILE4BSD) && !defined(MKSH_BUILDSH)) || (MKSH_BUILD_R != 571)
+#if (!defined(MKSH_BUILDMAKEFILE4BSD) && !defined(MKSH_BUILDSH)) || (MKSH_BUILD_R != 592)
 #error Must run Build.sh to compile this.
 extern void thiswillneverbedefinedIhope(void);
 int
@@ -661,20 +682,20 @@
 /* use this ipv strchr(s, 0) but no side effects in s! */
 #define strnul(s)	((s) + strlen((const void *)s))
 
-#define utf_ptradjx(src, dst) do {					\
+#define utf_ptradjx(src,dst) do {					\
 	(dst) = (src) + utf_ptradj(src);				\
 } while (/* CONSTCOND */ 0)
 
 #if defined(MKSH_SMALL) && !defined(MKSH_SMALL_BUT_FAST)
-#define strdupx(d, s, ap) do {						\
+#define strdupx(d,s,ap) do {						\
 	(d) = strdup_i((s), (ap));					\
 } while (/* CONSTCOND */ 0)
-#define strndupx(d, s, n, ap) do {					\
+#define strndupx(d,s,n,ap) do {						\
 	(d) = strndup_i((s), (n), (ap));				\
 } while (/* CONSTCOND */ 0)
 #else
 /* be careful to evaluate arguments only once! */
-#define strdupx(d, s, ap) do {						\
+#define strdupx(d,s,ap) do {						\
 	const char *strdup_src = (const void *)(s);			\
 	char *strdup_dst = NULL;					\
 									\
@@ -685,7 +706,7 @@
 	}								\
 	(d) = strdup_dst;						\
 } while (/* CONSTCOND */ 0)
-#define strndupx(d, s, n, ap) do {					\
+#define strndupx(d,s,n,ap) do {						\
 	const char *strdup_src = (const void *)(s);			\
 	char *strdup_dst = NULL;					\
 									\
@@ -698,6 +719,33 @@
 	(d) = strdup_dst;						\
 } while (/* CONSTCOND */ 0)
 #endif
+#define strdup2x(d,s1,s2) do {						\
+	const char *strdup_src = (const void *)(s1);			\
+	const char *strdup_app = (const void *)(s2);			\
+	size_t strndup_len = strlen(strdup_src);			\
+	size_t strndup_ln2 = strlen(strdup_app) + 1;			\
+	char *strdup_dst = alloc(strndup_len + strndup_ln2, ATEMP);	\
+									\
+	memcpy(strdup_dst, strdup_src, strndup_len);			\
+	memcpy(strdup_dst + strndup_len, strdup_app, strndup_ln2);	\
+	(d) = strdup_dst;						\
+} while (/* CONSTCOND */ 0)
+#define strpathx(d,s1,s2,cond) do {					\
+	const char *strdup_src = (const void *)(s1);			\
+	const char *strdup_app = (const void *)(s2);			\
+	size_t strndup_len = strlen(strdup_src) + 1;			\
+	size_t strndup_ln2 = ((cond) || *strdup_app) ?			\
+	    strlen(strdup_app) + 1 : 0;					\
+	char *strdup_dst = alloc(strndup_len + strndup_ln2, ATEMP);	\
+									\
+	memcpy(strdup_dst, strdup_src, strndup_len);			\
+	if (strndup_ln2) {						\
+		strdup_dst[strndup_len - 1] = '/';			\
+		memcpy(strdup_dst + strndup_len, strdup_app,		\
+		    strndup_ln2);					\
+	}								\
+	(d) = strdup_dst;						\
+} while (/* CONSTCOND */ 0)
 
 #ifdef MKSH_SMALL
 #ifndef MKSH_NOPWNAM
@@ -839,6 +887,7 @@
 /* struct env.flag values */
 #define EF_BRKCONT_PASS	BIT(1)	/* set if E_LOOP must pass break/continue on */
 #define EF_FAKE_SIGDIE	BIT(2)	/* hack to get info from unwind to quitenv */
+#define EF_IN_EVAL	BIT(3)	/* inside an eval */
 
 /* Do breaks/continues stop at env type e? */
 #define STOP_BRKCONT(t)	((t) == E_NONE || (t) == E_PARSE || \
@@ -851,12 +900,13 @@
 #define LRETURN	1	/* return statement */
 #define LEXIT	2	/* exit statement */
 #define LERROR	3	/* errorf() called */
-#define LLEAVE	4	/* untrappable exit/error */
+#define LERREXT 4	/* set -e caused */
 #define LINTR	5	/* ^C noticed */
 #define LBREAK	6	/* break statement */
 #define LCONTIN	7	/* continue statement */
 #define LSHELL	8	/* return to interactive shell() */
 #define LAEXPR	9	/* error in arithmetic expression */
+#define LLEAVE	10	/* untrappable exit/error */
 
 /* sort of shell global state */
 EXTERN pid_t procpid;		/* PID of executing process */
@@ -866,11 +916,17 @@
 EXTERN short trap_exstat;	/* exit status before running a trap */
 EXTERN uint8_t trap_nested;	/* running nested traps */
 EXTERN uint8_t shell_flags[FNFLAGS];
+EXTERN uint8_t baseline_flags[FNFLAGS
+#if !defined(MKSH_SMALL) || defined(DEBUG)
+    + 1
+#endif
+    ];
+EXTERN bool as_builtin;		/* direct builtin call */
 EXTERN const char *kshname;	/* $0 */
 EXTERN struct {
-	uid_t kshuid_v;		/* real UID of shell */
+	uid_t kshuid_v;		/* real UID of shell at startup */
 	uid_t ksheuid_v;	/* effective UID of shell */
-	gid_t kshgid_v;		/* real GID of shell */
+	gid_t kshgid_v;		/* real GID of shell at startup */
 	gid_t kshegid_v;	/* effective GID of shell */
 	pid_t kshpgrp_v;	/* process group of shell */
 	pid_t kshppid_v;	/* PID of parent of shell */
@@ -955,6 +1011,7 @@
 EXTERN const char Tcreate[] E_INIT("create");
 EXTERN const char TELIF_unexpected[] E_INIT("TELIF unexpected");
 EXTERN const char TEXECSHELL[] E_INIT("EXECSHELL");
+EXTERN const char TENV[] E_INIT("ENV");
 EXTERN const char Tdsgexport[] E_INIT("^*=export");
 #define Texport (Tdsgexport + 3)
 #ifdef __OS2__
@@ -986,7 +1043,10 @@
 #define Tnot_started (Tjob_not_started + 4)
 #define TOLDPWD (Tno_OLDPWD + 3)
 #define Topen (Tcant_open + 6)
+EXTERN const char To_o_reset[] E_INIT(" -o .reset");
+#define To_reset (To_o_reset + 4)
 #define TPATH (TFPATH + 1)
+#define Tpo (Tset_po + 4)
 #define Tpv (TpVv + 1)
 EXTERN const char TpVv[] E_INIT("Vpv");
 #define TPWD (Tno_OLDPWD + 6)
@@ -997,10 +1057,12 @@
 #define Tredirection (Tredirection_dup + 19)
 #define Treal_sp1 (Treal_sp2 + 1)
 EXTERN const char Treal_sp2[] E_INIT(" real ");
+EXTERN const char TREPLY[] E_INIT("REPLY");
 EXTERN const char Treq_arg[] E_INIT("requires an argument");
 EXTERN const char Tselect[] E_INIT("select");
-EXTERN const char Tsgset[] E_INIT("*=set");
 #define Tset (Tf_parm + 18)
+EXTERN const char Tset_po[] E_INIT("set +o");
+EXTERN const char Tsghset[] E_INIT("*=#set");
 #define Tsh (Tmksh + 2)
 #define TSHELL (TEXECSHELL + 4)
 #define Tshell (Ttoo_many_files + 23)
@@ -1030,15 +1092,16 @@
 #define Twrite (Tshf_write + 4)
 EXTERN const char Tf__S[] E_INIT(" %S");
 #define Tf__d (Tunexpected_type + 22)
+#define Tf_ss (Tf__ss + 1)
 EXTERN const char Tf__ss[] E_INIT(" %s%s");
 #define Tf__sN (Tf_s_s_sN + 5)
-EXTERN const char Tf_sSs[] E_INIT("%s/%s");
 #define Tf_T (Tf_s_T + 3)
 EXTERN const char Tf_dN[] E_INIT("%d\n");
 EXTERN const char Tf_s_[] E_INIT("%s ");
 EXTERN const char Tf_s_T[] E_INIT("%s %T");
 EXTERN const char Tf_s_s_sN[] E_INIT("%s %s %s\n");
 #define Tf_s_s (Tf_sD_s_s + 4)
+#define Tf__s_s (Tf_sD_s_s + 3)
 #define Tf_s_sD_s (Tf_cant_ss_s + 6)
 EXTERN const char Tf_optfoo[] E_INIT("%s%s-%c: %s");
 EXTERN const char Tf_sD_[] E_INIT("%s: ");
@@ -1056,8 +1119,6 @@
 #define Tf_lu (Tf_toolarge + 17)
 EXTERN const char Tf_toolarge[] E_INIT("%s %s too large: %lu");
 EXTERN const char Tf_ldfailed[] E_INIT("%s %s(%d, %ld) failed: %s");
-#define Tf_ss (Tf_sss + 2)
-EXTERN const char Tf_sss[] E_INIT("%s%s%s");
 EXTERN const char Tf_sD_s_sD_s[] E_INIT("%s: %s %s: %s");
 EXTERN const char Tf_toomany[] E_INIT("too many %ss");
 EXTERN const char Tf_sd[] E_INIT("%s %d");
@@ -1116,6 +1177,7 @@
 #define Tcreate "create"
 #define TELIF_unexpected "TELIF unexpected"
 #define TEXECSHELL "EXECSHELL"
+#define TENV "ENV"
 #define Tdsgexport "^*=export"
 #define Texport "export"
 #ifdef __OS2__
@@ -1147,7 +1209,10 @@
 #define Tnot_started "not started"
 #define TOLDPWD "OLDPWD"
 #define Topen "open"
+#define To_o_reset " -o .reset"
+#define To_reset ".reset"
 #define TPATH "PATH"
+#define Tpo "+o"
 #define Tpv "pv"
 #define TpVv "Vpv"
 #define TPWD "PWD"
@@ -1158,10 +1223,12 @@
 #define Tredirection "redirection"
 #define Treal_sp1 "real "
 #define Treal_sp2 " real "
+#define TREPLY "REPLY"
 #define Treq_arg "requires an argument"
 #define Tselect "select"
-#define Tsgset "*=set"
 #define Tset "set"
+#define Tset_po "set +o"
+#define Tsghset "*=#set"
 #define Tsh "sh"
 #define TSHELL "SHELL"
 #define Tshell "shell"
@@ -1191,15 +1258,16 @@
 #define Twrite "write"
 #define Tf__S " %S"
 #define Tf__d " %d"
+#define Tf_ss "%s%s"
 #define Tf__ss " %s%s"
 #define Tf__sN " %s\n"
-#define Tf_sSs "%s/%s"
 #define Tf_T "%T"
 #define Tf_dN "%d\n"
 #define Tf_s_ "%s "
 #define Tf_s_T "%s %T"
 #define Tf_s_s_sN "%s %s %s\n"
 #define Tf_s_s "%s %s"
+#define Tf__s_s " %s %s"
 #define Tf_s_sD_s "%s %s: %s"
 #define Tf_optfoo "%s%s-%c: %s"
 #define Tf_sD_ "%s: "
@@ -1217,8 +1285,6 @@
 #define Tf_lu "%lu"
 #define Tf_toolarge "%s %s too large: %lu"
 #define Tf_ldfailed "%s %s(%d, %ld) failed: %s"
-#define Tf_ss "%s%s"
-#define Tf_sss "%s%s%s"
 #define Tf_sD_s_sD_s "%s: %s %s: %s"
 #define Tf_toomany "too many %ss"
 #define Tf_sd "%s %d"
@@ -1372,7 +1438,7 @@
 #define CiCOLON	BIT(26)	/* :				*/
 #define CiEQUAL	BIT(27)	/* =				*/
 #define CiQUEST	BIT(28)	/* ?				*/
-#define CiBRACK	BIT(29)	/* ]				*/
+#define CiBRACK	BIT(29)	/* []				*/
 #define CiUNDER	BIT(30)	/* _				*/
 #define CiGRAVE	BIT(31)	/* `				*/
 /* out of space, but one for *@ would make sense, possibly others */
@@ -1386,8 +1452,8 @@
 
 /* external types */
 
-/* !%,-.0‥9:@A‥Z[]_a‥z	valid characters in alias names */
-#define C_ALIAS	(CiALIAS | CiBRACK | CiCOLON | CiDIGIT | CiLOWER | CiMINUS | CiOCTAL | CiPERCT | CiUNDER | CiUPPER)
+/* !%+,-.0‥9:@A‥Z[]_a‥z	valid characters in alias names */
+#define C_ALIAS	(CiALIAS | CiBRACK | CiCOLON | CiDIGIT | CiLOWER | CiMINUS | CiOCTAL | CiPERCT | CiPLUS | CiUNDER | CiUPPER)
 /* 0‥9A‥Za‥z		alphanumerical */
 #define C_ALNUM	(CiDIGIT | CiLOWER | CiOCTAL | CiUPPER)
 /* 0‥9A‥Z_a‥z		alphanumerical plus underscore (“word character”) */
@@ -1533,6 +1599,12 @@
 #define ksh_toctrl(c)	asc2rtt(ord(c) == ORD('?') ? 0x7F : rtt2asc(c) & 0x9F)
 #define ksh_unctrl(c)	asc2rtt(rtt2asc(c) ^ 0x40U)
 
+#ifdef MKSH_SMALL
+#define SMALLP(x)	/* nothing */
+#else
+#define SMALLP(x)	, x
+#endif
+
 /* Argument parsing for built-in commands and getopts command */
 
 /* Values for Getopt.flags */
@@ -1626,7 +1698,7 @@
 #define shf_getc_i(shf)		((shf)->rnleft > 0 ? \
 				    (shf)->rnleft--, (int)ord(*(shf)->rp++) : \
 				    shf_getchar(shf))
-#define shf_putc_i(c, shf)	((shf)->wnleft == 0 ? \
+#define shf_putc_i(c,shf)	((shf)->wnleft == 0 ? \
 				    shf_putchar((uint8_t)(c), (shf)) : \
 				    ((shf)->wnleft--, *(shf)->wp++ = (c)))
 #define shf_eof(shf)		((shf)->flags & SHF_EOF)
@@ -1637,7 +1709,7 @@
 /* Flags passed to shf_*open() */
 #define SHF_RD		0x0001
 #define SHF_WR		0x0002
-#define SHF_RDWR	(SHF_RD|SHF_WR)
+#define SHF_RDWR	(SHF_RD | SHF_WR)
 #define SHF_ACCMODE	0x0003		/* mask */
 #define SHF_GETFL	0x0004		/* use fcntl() to figure RD/WR flags */
 #define SHF_UNBUF	0x0008		/* unbuffered I/O */
@@ -1756,14 +1828,15 @@
 #define LOW_BI		BIT(14)	/* external utility overrides built-in one */
 #define DECL_UTIL	BIT(15)	/* is declaration utility */
 #define DECL_FWDR	BIT(16) /* is declaration utility forwarder */
+#define NEXTLOC_BI	BIT(17)	/* needs BF_RESETSPEC on e->loc */
 
 /*
  * Attributes that can be set by the user (used to decide if an unset
  * param should be repoted by set/typeset). Does not include ARRAY or
  * LOCAL.
  */
-#define USERATTRIB	(EXPORT|INTEGER|RDONLY|LJUST|RJUST|ZEROFIL|\
-			    LCASEV|UCASEV_AL|INT_U|INT_L)
+#define USERATTRIB	(EXPORT | INTEGER | RDONLY | LJUST | RJUST | ZEROFIL | \
+			    LCASEV | UCASEV_AL | INT_U | INT_L)
 
 #define arrayindex(vp)	((unsigned long)((vp)->flag & AINDEX ? \
 			    (vp)->ua.index : 0))
@@ -1794,7 +1867,7 @@
 
 #define AF_ARGV_ALLOC	0x1	/* argv[] array allocated */
 #define AF_ARGS_ALLOCED	0x2	/* argument strings allocated */
-#define AI_ARGV(a, i)	((i) == 0 ? (a).argv[0] : (a).argv[(i) - (a).skip])
+#define AI_ARGV(a,i)	((i) == 0 ? (a).argv[0] : (a).argv[(i) - (a).skip])
 #define AI_ARGC(a)	((a).ai_argc - (a).skip)
 
 /* Argument info. Used for $#, $* for shell, functions, includes, etc. */
@@ -1824,6 +1897,8 @@
 /* Values for struct block.flags */
 #define BF_DOGETOPTS	BIT(0)	/* save/restore getopts state */
 #define BF_STOPENV	BIT(1)	/* do not export further */
+/* BF_RESETSPEC and NEXTLOC_BI must be numerically identical! */
+#define BF_RESETSPEC	BIT(17)	/* use ->next for set and shift */
 
 /*
  * Used by ktwalk() and ktnext() routines.
@@ -1993,8 +2068,14 @@
 #define DOSCALAR BIT(12)	/* change field handling to non-list context */
 #define DOHEREDOC BIT(13)	/* change scalar handling to heredoc body */
 #define DOHERESTR BIT(14)	/* append a newline char */
+#define DODBMAGIC BIT(15)	/* add magic to expansions for [[ x = $y ]] */
 
 #define X_EXTRA	20	/* this many extra bytes in X string */
+#if defined(MKSH_SMALL) && !defined(MKSH_SMALL_BUT_FAST)
+#define X_WASTE 15	/* allowed extra bytes to avoid shrinking, */
+#else
+#define X_WASTE 255	/* … must be 2ⁿ-1 */
+#endif
 
 typedef struct XString {
 	/* beginning of string */
@@ -2008,44 +2089,44 @@
 } XString;
 
 /* initialise expandable string */
-#define XinitN(xs, length, area) do {				\
+#define XinitN(xs,length,area) do {				\
 	(xs).len = (length);					\
 	(xs).areap = (area);					\
 	(xs).beg = alloc((xs).len + X_EXTRA, (xs).areap);	\
 	(xs).end = (xs).beg + (xs).len;				\
 } while (/* CONSTCOND */ 0)
-#define Xinit(xs, xp, length, area) do {			\
+#define Xinit(xs,xp,length,area) do {				\
 	XinitN((xs), (length), (area));				\
 	(xp) = (xs).beg;					\
 } while (/* CONSTCOND */ 0)
 
 /* stuff char into string */
-#define Xput(xs, xp, c)	(*xp++ = (c))
+#define Xput(xs,xp,c)	(*xp++ = (c))
 
 /* check if there are at least n bytes left */
-#define XcheckN(xs, xp, n) do {					\
+#define XcheckN(xs,xp,n) do {					\
 	ssize_t more = ((xp) + (n)) - (xs).end;			\
 	if (more > 0)						\
 		(xp) = Xcheck_grow(&(xs), (xp), (size_t)more);	\
 } while (/* CONSTCOND */ 0)
 
 /* check for overflow, expand string */
-#define Xcheck(xs, xp)	XcheckN((xs), (xp), 1)
+#define Xcheck(xs,xp)	XcheckN((xs), (xp), 1)
 
 /* free string */
-#define Xfree(xs, xp)	afree((xs).beg, (xs).areap)
+#define Xfree(xs,xp)	afree((xs).beg, (xs).areap)
 
 /* close, return string */
-#define Xclose(xs, xp)	aresize((xs).beg, (xp) - (xs).beg, (xs).areap)
+#define Xclose(xs,xp)	aresize((xs).beg, (xp) - (xs).beg, (xs).areap)
 
 /* beginning of string */
-#define Xstring(xs, xp)	((xs).beg)
+#define Xstring(xs,xp)	((xs).beg)
 
-#define Xnleft(xs, xp)	((xs).end - (xp))	/* may be less than 0 */
-#define Xlength(xs, xp)	((xp) - (xs).beg)
-#define Xsize(xs, xp)	((xs).end - (xs).beg)
-#define Xsavepos(xs, xp)	((xp) - (xs).beg)
-#define Xrestpos(xs, xp, n)	((xs).beg + (n))
+#define Xnleft(xs,xp)		((xs).end - (xp))	/* may be less than 0 */
+#define Xlength(xs,xp)		((xp) - (xs).beg)
+#define Xsize(xs,xp)		((xs).end - (xs).beg)
+#define Xsavepos(xs,xp)		((xp) - (xs).beg)
+#define Xrestpos(xs,xp,n)	((xs).beg + (n))
 
 char *Xcheck_grow(XString *, const char *, size_t);
 
@@ -2062,13 +2143,13 @@
 	size_t siz;
 } XPtrV;
 
-#define XPinit(x, n)	do {					\
+#define XPinit(x,n)	do {					\
 	(x).siz = (n);						\
 	(x).len = 0;						\
 	(x).beg = alloc2((x).siz, sizeof(void *), ATEMP);	\
 } while (/* CONSTCOND */ 0)					\
 
-#define XPput(x, p)	do {					\
+#define XPput(x,p)	do {					\
 	if ((x).len == (x).siz) {				\
 		(x).beg = aresize2((x).beg, (x).siz,		\
 		    2 * sizeof(void *), ATEMP);			\
@@ -2296,12 +2377,12 @@
 /* user and system time of last j_waitjed job */
 EXTERN struct timeval j_usrtime, j_systime;
 
-#define notok2mul(max, val, c)	(((val) != 0) && ((c) != 0) && \
+#define notok2mul(max,val,c)	(((val) != 0) && ((c) != 0) && \
 				    (((max) / (c)) < (val)))
-#define notok2add(max, val, c)	((val) > ((max) - (c)))
-#define notoktomul(val, cnst)	notok2mul(SIZE_MAX, (val), (cnst))
-#define notoktoadd(val, cnst)	notok2add(SIZE_MAX, (val), (cnst))
-#define checkoktoadd(val, cnst) do {					\
+#define notok2add(max,val,c)	((val) > ((max) - (c)))
+#define notoktomul(val,cnst)	notok2mul(SIZE_MAX, (val), (cnst))
+#define notoktoadd(val,cnst)	notok2add(SIZE_MAX, (val), (cnst))
+#define checkoktoadd(val,cnst) do {					\
 	if (notoktoadd((val), (cnst)))					\
 		internal_errorf(Tintovfl, (size_t)(val),		\
 		    '+', (size_t)(cnst));				\
@@ -2312,18 +2393,20 @@
 void ainit(Area *);
 void afreeall(Area *);
 /* these cannot fail and can take NULL (not for ap) */
-#define alloc(n, ap)		aresize(NULL, (n), (ap))
-#define alloc2(m, n, ap)	aresize2(NULL, (m), (n), (ap))
+#define alloc(n,ap)		aresize(NULL, (n), (ap))
+#define alloc2(m,n,ap)		aresize2(NULL, (m), (n), (ap))
 void *aresize(void *, size_t, Area *);
 void *aresize2(void *, size_t, size_t, Area *);
 void afree(void *, Area *);	/* can take NULL */
+#define aresizeif(z,p,n,ap)	(((p) == NULL) || ((z) < (n)) || \
+				    (((z) & ~X_WASTE) > ((n) & ~X_WASTE)) ? \
+				    aresize((p), (n), (ap)) : (p))
 /* edit.c */
 #ifndef MKSH_NO_CMDLINE_EDITING
-#ifndef MKSH_SMALL
-int x_bind(const char *, const char *, bool, bool);
-#else
-int x_bind(const char *, const char *, bool);
-#endif
+int x_bind(const char * SMALLP(bool));
+int x_bind_check(void);
+int x_bind_list(void);
+int x_bind_showall(void);
 void x_init(void);
 #ifdef DEBUG_LEAKS
 void x_done(void);
@@ -2511,6 +2594,8 @@
     MKSH_A_FORMAT(__printf__, 2, 3);
 void bi_errorf(const char *, ...)
     MKSH_A_FORMAT(__printf__, 1, 2);
+void maybe_errorf(int *, int, const char *, ...)
+    MKSH_A_FORMAT(__printf__, 3, 4);
 #define errorfz()	errorf(NULL)
 #define errorfxz(rc)	errorfx((rc), NULL)
 #define bi_errorfz()	bi_errorf(NULL)
@@ -2543,7 +2628,7 @@
 void ktinit(Area *, struct table *, uint8_t);
 struct tbl *ktscan(struct table *, const char *, uint32_t, struct tbl ***);
 /* table, name (key) to search for, hash(n) */
-#define ktsearch(tp, s, h) ktscan((tp), (s), (h), NULL)
+#define ktsearch(tp,s,h) ktscan((tp), (s), (h), NULL)
 struct tbl *ktenter(struct table *, const char *, uint32_t);
 #define ktdelete(p)	do { p->flag = 0; } while (/* CONSTCOND */ 0)
 void ktwalk(struct tstate *, struct table *);
@@ -2591,7 +2676,7 @@
 void os2_init(int *, const char ***);
 void setextlibpath(const char *, const char *);
 int access_ex(int (*)(const char *, int), const char *, int);
-int stat_ex(const char *, struct stat *);
+int stat_ex(int (*)(const char *, struct stat *), const char *, struct stat *);
 const char *real_exec_name(const char *);
 #endif
 /* shf.c */
@@ -2607,7 +2692,7 @@
 char *shf_getse(char *, ssize_t, struct shf *);
 int shf_getchar(struct shf *s);
 int shf_ungetc(int, struct shf *);
-#if defined(MKSH_SMALL) && !defined(MKSH_SMALL_BUT_FAST)
+#ifdef MKSH_SHF_NO_INLINE
 int shf_getc(struct shf *);
 int shf_putc(int, struct shf *);
 #else
@@ -2712,6 +2797,24 @@
 };
 typedef enum Test_meta Test_meta;
 
+struct t_op {
+	const char op_text[4];
+	Test_op op_num;
+};
+
+/* for string reuse */
+extern const struct t_op u_ops[];
+extern const struct t_op b_ops[];
+/* ensure order with funcs.c */
+#define Tda (u_ops[0].op_text)
+#define Tdn (u_ops[12].op_text)
+#define Tdo (u_ops[14].op_text)
+#define Tdr (u_ops[16].op_text)
+#define Tdu (u_ops[20].op_text)	/* "-u" */
+#define Tdx (u_ops[23].op_text)
+
+#define Tu (Tdu + 1)	/* "u" */
+
 #define TEF_ERROR	BIT(0)		/* set if we've hit an error */
 #define TEF_DBRACKET	BIT(1)		/* set if [[ .. ]] test */
 
diff --git a/src/sh_flags.gen b/src/sh_flags.gen
index af44328..538baf1 100644
--- a/src/sh_flags.gen
+++ b/src/sh_flags.gen
@@ -21,7 +21,7 @@
 
 #ifndef SHFLAGS_OPTCS
 #if defined(SHFLAGS_DEFNS)
-__RCSID("$MirOS: src/bin/mksh/sh_flags.opt,v 1.6 2018/08/10 02:53:39 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/sh_flags.opt,v 1.9 2020/05/16 22:38:25 tg Exp $");
 #elif defined(SHFLAGS_ENUMS)
 #define FN(sname,cname,flags,ochar)	cname,
 #define F0(sname,cname,flags,ochar)	cname = 0,
@@ -95,7 +95,6 @@
 #ifndef SHFLAGS_NOT_CMD
 FN("", FCOMMAND, OF_CMDLINE, 'c')
 #endif
-FN("", FAS_BUILTIN, OF_INTERNAL, 0)
 FN("", FTALKING_I, OF_INTERNAL, 0)
 #undef F0
 #undef FN
diff --git a/src/sh_flags.opt b/src/sh_flags.opt
index c2f554e..d84d1f1 100644
--- a/src/sh_flags.opt
+++ b/src/sh_flags.opt
@@ -19,7 +19,7 @@
  */
 
 @SHFLAGS_DEFNS
-__RCSID("$MirOS: src/bin/mksh/sh_flags.opt,v 1.6 2018/08/10 02:53:39 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/sh_flags.opt,v 1.9 2020/05/16 22:38:25 tg Exp $");
 @SHFLAGS_ENUMS
 #define FN(sname,cname,flags,ochar)	cname,
 #define F0(sname,cname,flags,ochar)	cname = 0,
@@ -184,13 +184,9 @@
 FN("", FCOMMAND, OF_CMDLINE
 
 /*
- * anonymous flags: used internally by shell only (not visible to user
+ * anonymous flags: used internally by shell only (not visible to user)
  */
 
-/* ./.	direct builtin call (divined from argv[0] multi-call binary) */
->|
-FN("", FAS_BUILTIN, OF_INTERNAL
-
 /* ./.	(internal) initial shell was interactive */
 >|
 FN("", FTALKING_I, OF_INTERNAL
diff --git a/src/shf.c b/src/shf.c
index a37f4da..8e95fc6 100644
--- a/src/shf.c
+++ b/src/shf.c
@@ -2,7 +2,7 @@
 
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2011,
- *		 2012, 2013, 2015, 2016, 2017, 2018
+ *		 2012, 2013, 2015, 2016, 2017, 2018, 2019
  *	mirabilos <m@mirbsd.org>
  * Copyright (c) 2015
  *	Daniel Richard G. <skunk@iSKUNK.ORG>
@@ -27,7 +27,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/shf.c,v 1.98 2018/08/10 02:53:39 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/shf.c,v 1.101 2019/12/11 17:56:58 tg Exp $");
 
 /* flags to shf_emptybuf() */
 #define EB_READSW	0x01	/* about to switch to reading */
@@ -523,7 +523,8 @@
 		buf += ncopy;
 		bsize -= ncopy;
 #ifdef MKSH_WITH_TEXTMODE
-		if (end && buf > orig_buf + 1 && buf[-2] == '\r') {
+		if (buf > orig_buf + 1 && ord(buf[-2]) == ORD('\r') &&
+		    ord(buf[-1]) == ORD('\n')) {
 			buf--;
 			bsize++;
 			buf[-1] = '\n';
@@ -531,9 +532,9 @@
 #endif
 	} while (!end && bsize);
 #ifdef MKSH_WITH_TEXTMODE
-	if (!bsize && buf[-1] == '\r') {
+	if (!bsize && ord(buf[-1]) == ORD('\r')) {
 		int c = shf_getc(shf);
-		if (c == '\n')
+		if (ord(c) == ORD('\n'))
 			buf[-1] = '\n';
 		else if (c != -1)
 			shf_ungetc(c, shf);
@@ -1073,7 +1074,7 @@
 	return (shf_error(shf) ? -1 : nwritten);
 }
 
-#if defined(MKSH_SMALL) && !defined(MKSH_SMALL_BUT_FAST)
+#ifdef MKSH_SHF_NO_INLINE
 int
 shf_getc(struct shf *shf)
 {
diff --git a/src/syn.c b/src/syn.c
index e4c38e3..3387cf5 100644
--- a/src/syn.c
+++ b/src/syn.c
@@ -3,7 +3,7 @@
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009,
  *		 2011, 2012, 2013, 2014, 2015, 2016, 2017,
- *		 2018
+ *		 2018, 2020
  *	mirabilos <m@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -24,7 +24,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/syn.c,v 1.127 2018/01/14 00:22:30 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/syn.c,v 1.128 2020/03/31 00:30:05 tg Exp $");
 
 struct nesting_state {
 	int start_token;	/* token than began nesting (eg, FOR) */
@@ -271,6 +271,7 @@
 	struct ioword *iop, **iops;
 	XPtrV args, vars;
 	struct nesting_state old_nesting;
+	bool check_decl_utility;
 
 	/* NUFILE is small enough to leave this addition unchecked */
 	iops = alloc2((NUFILE + 1), sizeof(struct ioword *), ATEMP);
@@ -294,100 +295,93 @@
 		t = newtp(TCOM);
 		t->lineno = source->line;
 		goto get_command_start;
-		while (/* CONSTCOND */ 1) {
-			bool check_decl_utility;
 
-			if (XPsize(args) == 0) {
+ get_command_loop:
+		if (XPsize(args) == 0) {
  get_command_start:
-				check_decl_utility = true;
-				cf = sALIAS | CMDASN;
-			} else if (t->u.evalflags)
-				cf = CMDWORD | CMDASN;
-			else
-				cf = CMDWORD;
-			switch (tpeek(cf)) {
-			case REDIR:
-				while ((iop = synio(cf)) != NULL) {
-					if (iopn >= NUFILE)
-						yyerror(Tf_toomany,
-						    Tredirection);
-					iops[iopn++] = iop;
-				}
-				break;
+			check_decl_utility = true;
+			cf = sALIAS | CMDASN;
+		} else if (t->u.evalflags)
+			cf = CMDWORD | CMDASN;
+		else
+			cf = CMDWORD;
 
-			case LWORD:
-				ACCEPT;
-				if (check_decl_utility) {
-					struct tbl *tt = get_builtin(ident);
-					uint32_t flag;
-
-					flag = tt ? tt->flag : 0;
-					if (flag & DECL_UTIL)
-						t->u.evalflags = DOVACHECK;
-					if (!(flag & DECL_FWDR))
-						check_decl_utility = false;
-				}
-				if ((XPsize(args) == 0 || Flag(FKEYWORD)) &&
-				    is_wdvarassign(yylval.cp))
-					XPput(vars, yylval.cp);
-				else
-					XPput(args, yylval.cp);
-				break;
-
-			case ORD('(' /*)*/):
-				if (XPsize(args) == 0 && XPsize(vars) == 1 &&
-				    is_wdvarassign(yylval.cp)) {
-					char *tcp;
-
-					/* wdarrassign: foo=(bar) */
-					ACCEPT;
-
-					/* manipulate the vars string */
-					tcp = XPptrv(vars)[(vars.len = 0)];
-					/* 'varname=' -> 'varname' */
-					tcp[wdscan(tcp, EOS) - tcp - 3] = EOS;
-
-					/* construct new args strings */
-					XPput(args, wdcopy(builtin_cmd, ATEMP));
-					XPput(args, wdcopy(setA_cmd0, ATEMP));
-					XPput(args, wdcopy(setA_cmd1, ATEMP));
-					XPput(args, tcp);
-					XPput(args, wdcopy(setA_cmd2, ATEMP));
-
-					/* slurp in words till closing paren */
-					while (token(CONTIN) == LWORD)
-						XPput(args, yylval.cp);
-					if (symbol != /*(*/ ')')
-						syntaxerr(NULL);
-				} else {
-					/*
-					 * Check for "> foo (echo hi)"
-					 * which AT&T ksh allows (not
-					 * POSIX, but not disallowed)
-					 */
-					afree(t, ATEMP);
-					if (XPsize(args) == 0 &&
-					    XPsize(vars) == 0) {
-						ACCEPT;
-						goto Subshell;
-					}
-
-					/* must be a function */
-					if (iopn != 0 || XPsize(args) != 1 ||
-					    XPsize(vars) != 0)
-						syntaxerr(NULL);
-					ACCEPT;
-					musthave(/*(*/ ')', 0);
-					t = function_body(XPptrv(args)[0],
-					    sALIAS, false);
-				}
-				goto Leave;
-
-			default:
-				goto Leave;
+		switch (tpeek(cf)) {
+		case REDIR:
+			while ((iop = synio(cf)) != NULL) {
+				if (iopn >= NUFILE)
+					yyerror(Tf_toomany, Tredirection);
+				iops[iopn++] = iop;
 			}
+			goto get_command_loop;
+
+		case LWORD:
+			ACCEPT;
+			if (check_decl_utility) {
+				struct tbl *tt = get_builtin(ident);
+				uint32_t flag;
+
+				flag = tt ? tt->flag : 0;
+				if (flag & DECL_UTIL)
+					t->u.evalflags = DOVACHECK;
+				if (!(flag & DECL_FWDR))
+					check_decl_utility = false;
+			}
+			if ((XPsize(args) == 0 || Flag(FKEYWORD)) &&
+			    is_wdvarassign(yylval.cp))
+				XPput(vars, yylval.cp);
+			else
+				XPput(args, yylval.cp);
+			goto get_command_loop;
+
+		case ORD('(' /*)*/):
+			if (XPsize(args) == 0 && XPsize(vars) == 1 &&
+			    is_wdvarassign(yylval.cp)) {
+				char *tcp;
+
+				/* wdarrassign: foo=(bar) */
+				ACCEPT;
+
+				/* manipulate the vars string */
+				tcp = XPptrv(vars)[(vars.len = 0)];
+				/* 'varname=' -> 'varname' */
+				tcp[wdscan(tcp, EOS) - tcp - 3] = EOS;
+
+				/* construct new args strings */
+				XPput(args, wdcopy(builtin_cmd, ATEMP));
+				XPput(args, wdcopy(setA_cmd0, ATEMP));
+				XPput(args, wdcopy(setA_cmd1, ATEMP));
+				XPput(args, tcp);
+				XPput(args, wdcopy(setA_cmd2, ATEMP));
+
+				/* slurp in words till closing paren */
+				while (token(CONTIN) == LWORD)
+					XPput(args, yylval.cp);
+				if (symbol != /*(*/ ')')
+					syntaxerr(NULL);
+				break;
+			}
+
+			afree(t, ATEMP);
+
+			/*
+			 * Check for "> foo (echo hi)" which AT&T ksh allows
+			 * (not POSIX, but not disallowed)
+			 */
+			if (XPsize(args) == 0 && XPsize(vars) == 0) {
+				ACCEPT;
+				goto Subshell;
+			}
+
+			/* must be a function */
+			if (iopn != 0 || XPsize(args) != 1 || XPsize(vars) != 0)
+				syntaxerr(NULL);
+			ACCEPT;
+			musthave(/*(*/ ')', 0);
+			t = function_body(XPptrv(args)[0],
+			    sALIAS, false);
+			break;
 		}
- Leave:
 		break;
 
 	case ORD('(' /*)*/): {
diff --git a/src/var.c b/src/var.c
index 1263547..ade60c5 100644
--- a/src/var.c
+++ b/src/var.c
@@ -2,7 +2,8 @@
 
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
- *		 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018
+ *		 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018,
+ *		 2019
  *	mirabilos <m@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -28,7 +29,7 @@
 #include <sys/sysctl.h>
 #endif
 
-__RCSID("$MirOS: src/bin/mksh/var.c,v 1.226 2018/07/15 17:21:24 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/var.c,v 1.236 2020/04/13 16:29:34 tg Exp $");
 
 /*-
  * Variables
@@ -49,7 +50,7 @@
 static void c_typeset_vardump_recursive(struct block *, uint32_t, int, bool,
     bool);
 static char *formatstr(struct tbl *, const char *);
-static void exportprep(struct tbl *, const char *);
+static void exportprep(struct tbl *, const char *, size_t);
 static int special(const char *);
 static void unspecial(const char *);
 static void getspec(struct tbl *);
@@ -57,6 +58,7 @@
 static void unsetspec(struct tbl *, bool);
 static int getint(struct tbl *, mksh_ari_u *, bool);
 static const char *array_index_calc(const char *, bool *, uint32_t *);
+static struct tbl *vtypeset(int *, const char *, uint32_t, uint32_t, int, int);
 
 /*
  * create a new block for function calls and simple commands
@@ -196,7 +198,7 @@
 			char *cp;
 
 			/* gotcha! */
-			cp = shf_smprintf(Tf_ss, str_val(vp), p);
+			strdup2x(cp, str_val(vp), p);
 			afree(ap, ATEMP);
 			n = ap = cp;
 			goto redo_from_ref;
@@ -207,16 +209,21 @@
 	if (p != n && ord(*p) == ORD('[') && (len = array_ref_len(p))) {
 		char *sub, *tmp;
 		mksh_ari_t rval;
+		size_t tmplen = p - n;
 
 		/* calculate the value of the subscript */
 		*arrayp = true;
-		strndupx(tmp, p + 1, len - 2, ATEMP);
+		len -= 2;
+		tmp = alloc((len > tmplen ? len : tmplen) + 1, ATEMP);
+		memcpy(tmp, p + 1, len);
+		tmp[len] = '\0';
 		sub = substitute(tmp, 0);
-		afree(tmp, ATEMP);
-		strndupx(n, n, p - n, ATEMP);
 		evaluate(sub, &rval, KSH_UNWIND_ERROR, true);
 		*valp = (uint32_t)rval;
 		afree(sub, ATEMP);
+		memcpy(tmp, n, tmplen);
+		tmp[tmplen] = '\0';
+		n = tmp;
 	}
 	return (n);
 }
@@ -273,7 +280,7 @@
 		vp->name[0] = c;
 		vp->name[1] = '\0';
 		vp->flag |= RDONLY;
-		if (vn[1] != '\0')
+		if (!c || vn[1] != '\0')
 			goto out;
 		vp->flag |= ISSET|INTEGER;
 		switch (c) {
@@ -450,7 +457,6 @@
 int
 setstr(struct tbl *vq, const char *s, int error_ok)
 {
-	char *salloc = NULL;
 	bool no_ro_check = tobool(error_ok & 0x4);
 
 	error_ok &= ~0x4;
@@ -462,28 +468,33 @@
 	}
 	if (!(vq->flag&INTEGER)) {
 		/* string dest */
+		char *salloc = NULL;
+		size_t cursz;
 		if ((vq->flag&ALLOC)) {
+			cursz = strlen(vq->val.s) + 1;
 #ifndef MKSH_SMALL
 			/* debugging */
-			if (s >= vq->val.s &&
-			    s <= strnul(vq->val.s)) {
+			if (s >= vq->val.s && s < (vq->val.s + cursz)) {
 				internal_errorf(
 				    "setstr: %s=%s: assigning to self",
 				    vq->name, s);
 			}
 #endif
-			afree(vq->val.s, vq->areap);
-		}
-		vq->flag &= ~(ISSET|ALLOC);
-		vq->type = 0;
+		} else
+			cursz = 0;
 		if (s && (vq->flag & (UCASEV_AL|LCASEV|LJUST|RJUST)))
 			s = salloc = formatstr(vq, s);
 		if ((vq->flag&EXPORT))
-			exportprep(vq, s);
+			exportprep(vq, s, cursz);
 		else {
-			strdupx(vq->val.s, s, vq->areap);
+			size_t n = strlen(s) + 1;
+			vq->val.s = aresizeif(cursz, (vq->flag & ALLOC) ?
+			    vq->val.s : NULL, n, vq->areap);
+			memcpy(vq->val.s, s, n);
 			vq->flag |= ALLOC;
+			vq->type = 0;
 		}
+		afree(salloc, ATEMP);
 	} else {
 		/* integer dest */
 		if (!v_evaluate(vq, s, error_ok, true))
@@ -492,7 +503,6 @@
 	vq->flag |= ISSET;
 	if ((vq->flag&SPECIAL))
 		setspec(vq);
-	afree(salloc, ATEMP);
 	return (1);
 }
 
@@ -728,25 +738,19 @@
  * make vp->val.s be "name=value" for quick exporting.
  */
 static void
-exportprep(struct tbl *vp, const char *val)
+exportprep(struct tbl *vp, const char *val, size_t cursz)
 {
-	char *xp;
-	char *op = (vp->flag&ALLOC) ? vp->val.s : NULL;
-	size_t namelen, vallen;
-
-	namelen = strlen(vp->name);
-	vallen = strlen(val) + 1;
+	char *cp = (vp->flag & ALLOC) ? vp->val.s : NULL;
+	size_t namelen = strlen(vp->name);
+	size_t vallen = strlen(val) + 1;
 
 	vp->flag |= ALLOC;
+	vp->type = namelen + 1;
 	/* since name+val are both in memory this can go unchecked */
-	xp = alloc(namelen + 1 + vallen, vp->areap);
-	memcpy(vp->val.s = xp, vp->name, namelen);
-	xp += namelen;
-	*xp++ = '=';
-	/* offset to value */
-	vp->type = xp - vp->val.s;
-	memcpy(xp, val, vallen);
-	afree(op, vp->areap);
+	vp->val.s = aresizeif(cursz, cp, vp->type + vallen, vp->areap);
+	memmove(vp->val.s + vp->type, val == cp ? vp->val.s : val, vallen);
+	memcpy(vp->val.s, vp->name, namelen);
+	vp->val.s[namelen] = '=';
 }
 
 /*
@@ -757,14 +761,23 @@
 struct tbl *
 typeset(const char *var, uint32_t set, uint32_t clr, int field, int base)
 {
+	return (vtypeset(NULL, var, set, clr, field, base));
+}
+static struct tbl *
+vtypeset(int *ep, const char *var, uint32_t set, uint32_t clr,
+    int field, int base)
+{
 	struct tbl *vp;
 	struct tbl *vpbase, *t;
-	char *tvar;
+	char *tvar, tvarbuf[32];
 	const char *val;
 	size_t len;
 	bool vappend = false;
 	enum namerefflag new_refflag = SRF_NOP;
 
+	if (ep)
+		*ep = 0;
+
 	if ((set & (ARRAY | ASSOC)) == ASSOC) {
 		new_refflag = SRF_ENABLE;
 		set &= ~(ARRAY | ASSOC);
@@ -782,8 +795,8 @@
 	}
 	if (ord(*val) == ORD('[')) {
 		if (new_refflag != SRF_NOP)
-			errorf(Tf_sD_s, var,
-			    "reference variable can't be an array");
+			return (maybe_errorf(ep, 1, Tf_sD_s, var,
+			    "reference variable can't be an array"), NULL);
 		len = array_ref_len(val);
 		if (len == 0)
 			return (NULL);
@@ -804,13 +817,19 @@
 		val += len;
 	}
 	if (ord(val[0]) == ORD('=')) {
-		strndupx(tvar, var, val - var, ATEMP);
+		len = val - var;
+		tvar = len < sizeof(tvarbuf) ? tvarbuf : alloc(len + 1, ATEMP);
+		memcpy(tvar, var, len);
+		tvar[len] = '\0';
 		++val;
 	} else if (set & IMPORT) {
 		/* environment invalid variable name or no assignment */
 		return (NULL);
 	} else if (ord(val[0]) == ORD('+') && ord(val[1]) == ORD('=')) {
-		strndupx(tvar, var, val - var, ATEMP);
+		len = val - var;
+		tvar = len < sizeof(tvarbuf) ? tvarbuf : alloc(len + 1, ATEMP);
+		memcpy(tvar, var, len);
+		tvar[len] = '\0';
 		val += 2;
 		vappend = true;
 	} else if (val[0] != '\0') {
@@ -818,10 +837,12 @@
 		return (NULL);
 	} else {
 		/* just varname with no value part nor equals sign */
-		strdupx(tvar, var, ATEMP);
+		len = strlen(var);
+		tvar = len < sizeof(tvarbuf) ? tvarbuf : alloc(len + 1, ATEMP);
+		memcpy(tvar, var, len);
+		tvar[len] = '\0';
 		val = NULL;
 		/* handle foo[*] => foo (whole array) mapping for R39b */
-		len = strlen(tvar);
 		if (len > 3 && ord(tvar[len - 3]) == ORD('[') &&
 		    ord(tvar[len - 2]) == ORD('*') &&
 		    ord(tvar[len - 1]) == ORD(']'))
@@ -833,7 +854,8 @@
 
 		/* bail out on 'nameref foo+=bar' */
 		if (vappend)
-			errorf("appending not allowed for nameref");
+			return (maybe_errorf(ep, 1,
+			    "appending not allowed for nameref"), NULL);
 		/* find value if variable already exists */
 		if ((qval = val) == NULL) {
 			varsearch(e->loc, &vp, tvar, hash(tvar));
@@ -859,7 +881,8 @@
 				goto nameref_rhs_checked;
 			}
  nameref_empty:
-			errorf(Tf_sD_s, var, "empty nameref target");
+			return (maybe_errorf(ep, 1, Tf_sD_s, var,
+			    "empty nameref target"), NULL);
 		}
 		len = (ord(*ccp) == ORD('[')) ? array_ref_len(ccp) : 0;
 		if (ccp[len]) {
@@ -868,15 +891,15 @@
 			 * junk after it" and "invalid array"; in the
 			 * latter case, len is also 0 and points to '['
 			 */
-			errorf(Tf_sD_s, qval,
-			    "nameref target not a valid parameter name");
+			return (maybe_errorf(ep, 1, Tf_sD_s, qval,
+			    "nameref target not a valid parameter name"), NULL);
 		}
  nameref_rhs_checked:
 		/* prevent nameref loops */
 		while (qval) {
 			if (!strcmp(qval, tvar))
-				errorf(Tf_sD_s, qval,
-				    "expression recurses on parameter");
+				return (maybe_errorf(ep, 1, Tf_sD_s, qval,
+				    "expression recurses on parameter"), NULL);
 			varsearch(e->loc, &vp, qval, hash(qval));
 			qval = NULL;
 			if (vp && ((vp->flag & (ARRAY | ASSOC)) == ASSOC))
@@ -886,8 +909,9 @@
 
 	/* prevent typeset from creating a local PATH/ENV/SHELL */
 	if (Flag(FRESTRICTED) && (strcmp(tvar, TPATH) == 0 ||
-	    strcmp(tvar, "ENV") == 0 || strcmp(tvar, TSHELL) == 0))
-		errorf(Tf_sD_s, tvar, "restricted");
+	    strcmp(tvar, TENV) == 0 || strcmp(tvar, TSHELL) == 0))
+		return (maybe_errorf(ep, 1, Tf_sD_s,
+		    tvar, "restricted"), NULL);
 
 	innermost_refflag = new_refflag;
 	vp = (set & LOCAL) ? local(tvar, tobool(set & LOCAL_COPY)) :
@@ -923,9 +947,9 @@
 	 */
 	if ((vpbase->flag & RDONLY) &&
 	    (val || clr || (set & ~(EXPORT | RDONLY))))
-		/* XXX check calls - is error here ok by POSIX? */
-		errorfx(2, Tf_ro, tvar);
-	afree(tvar, ATEMP);
+		return (maybe_errorf(ep, 2, Tf_ro, tvar), NULL);
+	if (tvar != tvarbuf)
+		afree(tvar, ATEMP);
 
 	/* most calls are with set/clr == 0 */
 	if (set | clr) {
@@ -990,18 +1014,24 @@
 			}
 		}
 		if (!ok)
-			errorfz();
+			return (maybe_errorf(ep, 1, NULL), NULL);
 	}
 
-	if (val != NULL) {
-		char *tval;
-
-		if (vappend) {
-			tval = shf_smprintf(Tf_ss, str_val(vp), val);
-			val = tval;
-		} else
-			tval = NULL;
-
+	if (vappend) {
+		size_t tlen;
+		if ((vp->flag & (ISSET|ALLOC|SPECIAL|INTEGER|UCASEV_AL|LCASEV|LJUST|RJUST)) != (ISSET|ALLOC)) {
+			/* cannot special-case this */
+			strdup2x(tvar, str_val(vp), val);
+			val = tvar;
+			goto vassign;
+		}
+		/* trivial string appending */
+		len = strlen(vp->val.s);
+		tlen = strlen(val) + 1;
+		vp->val.s = aresize(vp->val.s, len + tlen, vp->areap);
+		memcpy(vp->val.s + len, val, tlen);
+	} else if (val != NULL) {
+ vassign:
 		if (vp->flag&INTEGER) {
 			/* do not zero base before assignment */
 			setstr(vp, val, KSH_UNWIND_ERROR | 0x4);
@@ -1012,13 +1042,16 @@
 			/* setstr can't fail (readonly check already done) */
 			setstr(vp, val, KSH_RETURN_ERROR | 0x4);
 
-		afree(tval, ATEMP);
+		/* came here from vappend? need to free temp val */
+		if (vappend)
+			afree(tvar, ATEMP);
 	}
 
 	/* only x[0] is ever exported, so use vpbase */
-	if ((vpbase->flag&EXPORT) && !(vpbase->flag&INTEGER) &&
+	if ((vpbase->flag & (EXPORT|INTEGER)) == EXPORT &&
 	    vpbase->type == 0)
-		exportprep(vpbase, (vpbase->flag&ISSET) ? vpbase->val.s : null);
+		exportprep(vpbase, (vpbase->flag & ISSET) ?
+		    vpbase->val.s : null, 0);
 
 	return (vp);
 }
@@ -1829,7 +1862,7 @@
 	setstr(vp, istr, 0x4);
 }
 
-/* typeset, global(deprecated), export, and readonly */
+/* typeset, export and readonly */
 int
 c_typeset(const char **wp)
 {
@@ -2026,7 +2059,7 @@
 	if (wp[builtin_opt.optind] &&
 	    /* not "typeset -p varname" */
 	    !(!func && pflag && !(fset | fclr))) {
-		int rv = 0;
+		int rv = 0, x;
 		struct tbl *f;
 
 		if (localv && !func)
@@ -2049,7 +2082,10 @@
 					    wp[i], f->val.t);
 					shf_putc('\n', shl_stdout);
 				}
-			} else if (!typeset(wp[i], fset, fclr, field, base)) {
+			} else if (!vtypeset(&x, wp[i], fset, fclr,
+			    field, base)) {
+				if (x)
+					return (x);
 				bi_errorf(Tf_sD_s, wp[i], Tnot_ident);
 				return (1);
 			}