Please review changes against upstream code using SCM,
see the Vcs-* tags in debian/control for its location.

--- mksh-57.orig/Build.sh
+++ mksh-57/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.747 2020/01/03 21:58:50 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 @@ srcversion='$MirOS: src/bin/mksh/Build.s
 #
 # 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 ;;
@@ -234,7 +235,7 @@ vq() {
 rmf() {
 	for _f in "$@"; do
 		case $_f in
-		Build.sh|check.pl|check.t|dot.mkshrc|*.1|*.c|*.h|*.ico|*.opt) ;;
+		*.1|*.ico) ;;
 		*) rm -f "$_f" ;;
 		esac
 	done
@@ -512,7 +513,7 @@ ebcdic=false
 for i
 do
 	case $last:$i in
-	c:combine|c:dragonegg|c:llvm|c:lto)
+	c:dragonegg|c:llvm)
 		cm=$i
 		last=
 		;;
@@ -524,10 +525,6 @@ do
 		optflags=$i
 		last=
 		;;
-	t:*)
-		tfn=$i
-		last=
-		;;
 	:-c)
 		last=c
 		;;
@@ -573,9 +570,6 @@ do
 	:+T)
 		textmode=0
 		;;
-	:-t)
-		last=t
-		;;
 	:-v)
 		echo "Build.sh $srcversion"
 		echo "for mksh $dstversion"
@@ -652,7 +646,7 @@ oswarn=
 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|*.ico) ;; *) rm -f "$_f" ;; esac; done'
 
 # Evil hack
 if test x"$TARGET_OS" = x"Android"; then
@@ -847,6 +841,11 @@ Linux)
 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)
@@ -1088,7 +1087,8 @@ 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 +1115,10 @@ ct="icc"
 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 +1234,8 @@ dmc)
 	;;
 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 +1253,9 @@ icc)
 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
@@ -1281,6 +1287,12 @@ msc)
 		;;
 	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 +1345,7 @@ xlc)
 *)
 	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 +1388,14 @@ if ac_ifcpp 'if 0' compiler_fails '' \
 	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 +1405,8 @@ if ac_ifcpp 'if 0' compiler_fails '' \
 		;;
 	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
@@ -1526,6 +1537,7 @@ dmc)
 	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 +1720,7 @@ ac_test attribute_format '' 'for __attri
 	#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 +1745,7 @@ ac_test attribute_pure '' 'for __attribu
 	#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 +1757,7 @@ ac_test attribute_unused '' 'for __attri
 	#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 +1875,22 @@ rmf lft*	# end of large file support tes
 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 +1960,11 @@ else
 		#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 +2021,15 @@ ac_cppflags SYS_ERRLIST
 
 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
@@ -2614,8 +2630,8 @@ INDSRCS=	$extras
 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
 
@@ -2740,6 +2756,7 @@ HAVE_CAN_FSTACKPROTECTORALL	ac_flags
 ==== 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 +2785,7 @@ MKSH_S_NOVI=1			disable Vi editing mode
 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,7 +2795,7 @@ them, set to a value other than 0 or 1.
 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
@@ -2786,6 +2804,6 @@ http://anonscm.debian.org/cgit/collab-ma
 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
--- mksh-57.orig/check.pl
+++ mksh-57/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 @@ $all_tests = @ARGV == 0;
 # 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';
--- mksh-57.orig/check.t
+++ mksh-57/check.t
@@ -1,9 +1,9 @@
-# $MirOS: src/bin/mksh/check.t,v 1.812 2019/03/01 16:17:29 tg Exp $
+# $MirOS: src/bin/mksh/check.t,v 1.829 2020/01/11 21:11:28 tg Exp $
 # -*- mode: sh -*-
 #-
 # Copyright © 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
@@ -31,20 +31,29 @@
 # (2013/12/02 20:39:44) http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/regress/bin/ksh/?sortby=date
 
 expected-stdout:
-	@(#)MIRBSD KSH R57 2019/03/01
+	KSH R57 2019/12/29
 description:
 	Check base version of full shell
 stdin:
-	echo ${KSH_VERSION%%' +'*}
+	vsn=${KSH_VERSION%%' +'*}
+	echo "${vsn#* }"
 name: KSH_VERSION
+---
+expected-stdout:
+	@(#)MIRBSD
+description:
+	Check this identifies as legacy shell
+stdin:
+	echo "${KSH_VERSION%% *}"
+name: KSH_VERSION-modern
 category: !shell:legacy-yes
 ---
 expected-stdout:
-	@(#)LEGACY KSH R57 2019/03/01
+	@(#)LEGACY
 description:
-	Check base version of legacy shell
+	Check this identifies as legacy shell
 stdin:
-	echo ${KSH_VERSION%%' +'*}
+	echo "${KSH_VERSION%% *}"
 name: KSH_VERSION-legacy
 category: !shell:legacy-no
 ---
@@ -53,6 +62,7 @@ description:
 	Check that the shell version tag does not include EBCDIC
 category: !shell:ebcdic-yes
 stdin:
+	set -o noglob
 	for x in $KSH_VERSION; do
 		[[ $x = '+EBCDIC' ]] && exit 1
 	done
@@ -63,6 +73,7 @@ description:
 	Check that the shell version tag includes EBCDIC
 category: !shell:ebcdic-no
 stdin:
+	set -o noglob
 	for x in $KSH_VERSION; do
 		[[ $x = '+EBCDIC' ]] && exit 0
 	done
@@ -73,6 +84,7 @@ description:
 	Check that the shell version tag does not include TEXTMODE
 category: !shell:textmode-yes
 stdin:
+	set -o noglob
 	for x in $KSH_VERSION; do
 		[[ $x = '+TEXTMODE' ]] && exit 1
 	done
@@ -83,6 +95,7 @@ description:
 	Check that the shell version tag includes TEXTMODE
 category: !shell:textmode-no
 stdin:
+	set -o noglob
 	for x in $KSH_VERSION; do
 		[[ $x = '+TEXTMODE' ]] && exit 0
 	done
@@ -178,7 +191,7 @@ stdin:
 expected-stdout:
 	ok
 expected-stderr-pattern:
-	/mksh: warning: won't have full job control\nXX/
+	/ksh: warning: won't have full job control\nXX/
 ---
 name: selftest-tty-present
 description:
@@ -1392,7 +1405,7 @@ need-pass: no
 # the mv command fails on Cygwin and z/OS
 # Hurd aborts the testsuite (permission denied)
 # QNX does not find subdir to cd into
-category: !os:cygwin,!os:gnu,!os:msys,!os:nto,!os:os390,!nosymlink
+category: !os:cygwin,!os:gnu,!os:midipix,!os:msys,!os:nto,!os:os390,!nosymlink
 file-setup: file 644 "x"
 	mkdir noread noread/target noread/target/subdir
 	ln -s noread link
@@ -1999,7 +2012,7 @@ expected-stdout:
 name: eglob-bad-1
 description:
 	Check that globbing isn't done when glob has syntax error
-category: !os:cygwin,!os:msys,!os:os2
+category: !os:cygwin,!os:midipix,!os:msys,!os:os2
 file-setup: file 644 "@(a[b|)c]foo"
 stdin:
 	echo @(a[b|)c]*
@@ -2491,7 +2504,7 @@ description:
 # breaks on Mac OSX (HFS+ non-standard UTF-8 canonical decomposition)
 # breaks on Cygwin 1.7 (files are now UTF-16 or something)
 # breaks on QNX 6.4.1 (says RT)
-category: !os:cygwin,!os:darwin,!os:msys,!os:nto,!os:os2,!os:os390
+category: !os:cygwin,!os:midipix,!os:darwin,!os:msys,!os:nto,!os:os2,!os:os390
 need-pass: no
 file-setup: file 644 "ac"
 stdin:
@@ -2526,6 +2539,19 @@ expected-stdout:
 	-bc abc bbc cbc ebc
 	@bc
 ---
+name: glob-range-6
+description:
+	ksh93 fails this but POSIX probably demands it
+file-setup: file 644 "abc"
+file-setup: file 644 "cbc"
+stdin:
+	echo *b*
+	[ '*b*' = *b* ] && echo yep; echo $?
+expected-stdout:
+	abc cbc
+	2
+expected-stderr-pattern: /.*/
+---
 name: glob-word-1
 description:
 	Check BSD word boundary matches
@@ -7118,13 +7144,13 @@ stdin:
 name: exec-function-environment-1
 description:
 	Check assignments in function calls and whether they affect
-	the current execution environment (ksh93, SUSv4)
+	the current execution environment
 stdin:
 	f() { a=2; }; g() { b=3; echo y$c-; }; a=1 f; b=2; c=1 g
 	echo x$a-$b- z$c-
 expected-stdout:
 	y1-
-	x2-3- z1-
+	x-3- z-
 ---
 name: exec-modern-korn-shell
 description:
@@ -7389,6 +7415,8 @@ expected-stdout:
 name: xxx-stat-1
 description:
 	Check that tests on files are consistent
+	(fails when run as root, unfortunately)
+category: disabled
 stdin:
 	mkdir a
 	echo x >a/b
@@ -7761,6 +7789,32 @@ expected-stdout:
 	1 ok
 expected-exit: 1
 ---
+name: exit-err-10
+description:
+	Debian #269067 (cf. regression-38 but with eval)
+arguments: !-e!
+stdin:
+	eval false || true
+	echo = $? .
+expected-stdout:
+	= 0 .
+---
+name: exit-err-11
+description:
+	Fix -e inside eval, from Martijn Dekker; expected-stdout from ksh93
+stdin:
+	"$__progname" -c 'eval '\''echo ${-//[!eh]}; false; echo phantom e'\''; echo x$?'
+	echo = $?
+	"$__progname" -ec 'eval '\''echo ${-//[!eh]}; false; echo phantom e'\''; echo x$?'
+	echo = $?
+expected-stdout:
+	h
+	phantom e
+	x0
+	= 0
+	eh
+	= 1
+---
 name: exit-enoent-1
 description:
 	SUSv4 says that the shell should exit with 126/127 in some situations
@@ -7832,11 +7886,9 @@ expected-exit: 9
 ---
 name: exit-trap-2
 description:
-	Check that ERR and EXIT traps are run just like ksh93 does.
-	GNU bash does not run ERtrap in ±e eval-undef but runs it
-	twice (bug?) in +e eval-false, so does ksh93 (bug?), which
-	also has a bug to continue execution (echoing "and out" and
-	returning 0) in +e eval-undef.
+	Check that ERR and EXIT traps are run just like GNU bash does.
+	ksh93 runs ERtrap after “parameter null or not set” (which mksh
+	used to do) but (bug) continues “and out”, exit 0, in +e eval-undef.
 file-setup: file 644 "x"
 	v=; unset v
 	trap 'echo EXtrap' EXIT
@@ -7920,7 +7972,6 @@ expected-stdout:
 	= eval-false 1 .
 	and run ${v?}
 	x: v: parameter null or not set
-	ERtrap
 	EXtrap
 	= eval-undef 1 .
 	and run true
@@ -7942,12 +7993,12 @@ expected-stdout:
 	= eval-true 0 .
 	and run false
 	ERtrap
+	ERtrap
 	and out
 	EXtrap
 	= eval-false 0 .
 	and run ${v?}
 	x: v: parameter null or not set
-	ERtrap
 	EXtrap
 	= eval-undef 1 .
 	and run true
@@ -8672,7 +8723,7 @@ description:
 	note: Ultrix perl5 t4 returns 65280 (exit-code 255) and no text
 	XXX fails when LD_PRELOAD is set with -e and Perl chokes it (ASan)
 need-pass: no
-category: !os:cygwin,!os:msys,!os:ultrix,!os:uwin-nt,!smksh
+category: !os:cygwin,!os:midipix,!os:msys,!os:ultrix,!os:uwin-nt,!smksh
 env-setup: !FOO=BAR!
 stdin:
 	print '#!'"$__progname"'\nprint "1 a=$ENV{FOO}";' >t1
@@ -10841,6 +10892,43 @@ stdin:
 expected-stdout:
 	okay
 ---
+name: ulimit-3
+description:
+	Check that there are no duplicate limits (if this fails,
+	immediately contact with system information the developers)
+stdin:
+	[[ -z $(set | grep ^opt) ]]; mis=$?
+	set | grep ^opt | sed 's/^/unexpectedly set in environment: /'
+	opta='<used for showing all limits>'
+	optH='<used to set hard limits>'
+	optS='<used to set soft limits>'
+	ulimit -a >tmpf
+	set -o noglob
+	while IFS= read -r line; do
+		x=${line:1:1}
+		if [[ -z $x || ${#x}/${%x} != 1/1 ]]; then
+			print -r -- "weird line: $line"
+			(( mis |= 1 ))
+			continue
+		fi
+		set -- $line
+		nameref v=opt$x
+		if [[ -n $v ]]; then
+			print -r -- "duplicate -$x \"$2\" already seen as \"$v\""
+			(( mis |= 2 ))
+		fi
+		v=$2
+	done <tmpf
+	if (( mis & 2 )); then
+		echo failed
+	elif (( mis & 1 )); then
+		echo inconclusive
+	else
+		echo done
+	fi
+expected-stdout:
+	done
+---
 name: redir-1
 description:
 	Check some of the most basic invariants of I/O redirection
@@ -11201,7 +11289,7 @@ description:
 	(Inspired by PR 2450 on OpenBSD.) Calling
 		FOO=bar f
 	where f is a ksh style function, should not set FOO in the current
-	env. If f is a Bourne style function, FOO should be set. Furthermore,
+	env. If f is a Bourne style function, (new) also not. Furthermore,
 	the function should receive a correct value of FOO. However, differing
 	from oksh, setting FOO in the function itself must change the value in
 	setting FOO in the function itself should not change the value in
@@ -11247,7 +11335,7 @@ stdin:
 	if [ $? != 0 ]; then
 		exit 1
 	fi
-	if [ x$FOO != xfoo ]; then
+	if [ x$FOO != x ]; then
 		exit 1
 	fi
 	FOO=barbar
@@ -11262,7 +11350,7 @@ stdin:
 	if [ $? != 0 ]; then
 		exit 1
 	fi
-	if [ x$FOO != xfoo ]; then
+	if [ x$FOO != xbarbar ]; then
 		exit 1
 	fi
 ---
@@ -12659,7 +12747,7 @@ stdin:
 	echo =14
 	(mypid=$$; try mypid)
 	echo =15
-	) 2>&1 | sed -e 's/^[^]]*]//' -e 's/^[^:]*: *//'
+	) 2>&1 | sed -e 's/^[A-Za-z]://' -e 's/^[^]]*]//' -e 's/^[^:]*: *//'
 	exit ${PIPESTATUS[0]}
 expected-stdout:
 	y
@@ -13238,7 +13326,7 @@ description:
 	Crashed during March 2011, fixed on vernal equinōx ☺
 category: os:mirbsd,os:openbsd
 stdin:
-	export MALLOC_OPTIONS=FGJPRSX
+	export MALLOC_OPTIONS=FGJRSX
 	"$__progname" -c 'x=$(tr z r <<<baz); echo $x'
 expected-stdout:
 	bar
@@ -13362,6 +13450,59 @@ expected-stdout:
 	after	0='swc' 1='二' 2=''
 	= done
 ---
+name: command-set
+description:
+	Same but with set
+stdin:
+	showargs() { for s_arg in "$@"; do echo -n "<$s_arg> "; done; echo .; }
+	showargs 1 "$@"
+	set -- foo bar baz
+	showargs 2 "$@"
+	command set -- miau 'meow nyao'
+	showargs 3 "$@"
+expected-stdout:
+	<1> .
+	<2> <foo> <bar> <baz> .
+	<3> <miau> <meow nyao> .
+---
+name: command-readonly
+description:
+	These should not exit on error when prefixed
+stdin:
+	exec 2>/dev/null
+	"$__progname" -c 'readonly v; export v=foo || echo ok'
+	echo ef=$?
+	"$__progname" -c 'readonly v; command export v=foo || echo ok'
+	echo en=$?
+	"$__progname" -c 'readonly v; readonly v=foo || echo ok'
+	echo rf=$?
+	"$__progname" -c 'readonly v; command readonly v=foo || echo ok'
+	echo rn=$?
+expected-stdout:
+	ef=2
+	ok
+	en=0
+	rf=2
+	ok
+	rn=0
+---
+name: command-dot-regression
+description:
+	Check a regression in fixing the above does not appear
+stdin:
+	cat >test.mksh <<\EOF
+	set -- one two
+	shift
+	for s_arg in "$#" "$@"; do echo -n "<$s_arg> "; done; echo .
+	EOF
+	"$__progname" -c '. ./test.mksh' dummy oh dear this is not good
+	echo =
+	"$__progname" -c 'command . ./test.mksh' dummy oh dear this is not good
+expected-stdout:
+	<1> <two> .
+	=
+	<1> <two> .
+---
 name: command-pvV-posix-priorities
 description:
 	For POSIX compatibility, command -v should find aliases and reserved
--- mksh-57.orig/dot.mkshrc
+++ mksh-57/dot.mkshrc
@@ -1,8 +1,8 @@
 # $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.124 2019/09/25 22:50:11 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
 #	mirabilos <m@mirbsd.org>
 #
 # Provided that these terms and disclaimer and all copyright notices
@@ -64,11 +64,10 @@ for EDITOR in "${EDITOR:-}" jupp jstar m
 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
@@ -604,7 +603,7 @@ function enable {
 
 \: place customisations below this line
 
-# some defaults follow — you are supposed to adjust these to your
+# some defaults/samples — you are supposed to adjust these 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 +617,15 @@ done
 \\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
 
--- mksh-57.orig/edit.c
+++ mksh-57/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
  *	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.346 2019/12/11 23:58:16 tg Exp $");
 
 /*
  * in later versions we might use libtermcap for this, but since external
@@ -176,7 +177,7 @@ x_getc(void)
 static void
 x_putcf(int c)
 {
-	shf_putc(c, shl_out);
+	shf_putc_i(c, shl_out);
 }
 
 /*********************************
@@ -353,7 +354,7 @@ x_glob_hlp_tilde_and_rem_qchar(char *s,
 			*--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;
@@ -3651,16 +3652,19 @@ vi_hook(int ch)
 
 	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 = '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 (insert != 0) {
 			if (ch == CTRL_V) {
--- mksh-57.orig/eval.c
+++ mksh-57/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
  *	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.222 2019/12/30 04:43:37 tg Exp $");
 
 /*
  * string expansion
@@ -437,17 +438,8 @@ expand(
 						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 +489,11 @@ expand(
 					    }
 					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 +509,17 @@ expand(
 						    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 +566,17 @@ expand(
 						 */
 						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 +589,56 @@ expand(
 									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);
+						}
+
+						/*
+						 * 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;
 						}
-						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;
 						if (stype & STYPE_AT) {
 							afree(tpat1, ATEMP);
 							afree(pat, ATEMP);
@@ -769,11 +802,22 @@ expand(
 					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;
--- mksh-57.orig/exec.c
+++ mksh-57/exec.c
@@ -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.217 2019/12/30 04:18:50 tg Exp $");
 
 #ifndef MKSH_DEFAULT_EXECSHELL
 #define MKSH_DEFAULT_EXECSHELL	MKSH_UNIXROOT "/bin/sh"
@@ -108,8 +108,8 @@ execute(struct op * volatile t,
 
 			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);
@@ -446,7 +446,7 @@ execute(struct op * volatile t,
 		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 @@ execute(struct op * volatile t,
 		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);
 }
@@ -629,12 +624,8 @@ comexec(struct op *t, struct tbl * volat
 	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 @@ comexec(struct op *t, struct tbl * volat
 	/* 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 @@ comexec(struct op *t, struct tbl * volat
 		switch (i) {
 		case LRETURN:
 		case LERROR:
+		case LERREXT:
 			rv = exstat & 0xFF;
 			break;
 		case LINTR:
@@ -1147,6 +1137,10 @@ builtin(const char *name, int (*func) (c
 		/* is declaration utility (POSIX: export, readonly) */
 		flag |= DECL_UTIL;
 		break;
+	case '#':
+		/* is set or shift */
+		flag |= NEXTLOC_BI;
+		break;
 	default:
 		goto flags_seen;
 	}
--- mksh-57.orig/expr.c
+++ mksh-57/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.106 2019/12/11 20:04:50 tg Exp $");
 
 #define EXPRTOK_DEFNS
 #include "exprtok.h"
@@ -864,7 +864,8 @@ ksh_access(const char *fn, int mode)
 	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;
--- mksh-57.orig/funcs.c
+++ mksh-57/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
  *	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.360 2019/12/30 03:58:55 tg Exp $");
 
 #if HAVE_KILLPG
 /*
@@ -129,8 +130,8 @@ const struct builtin mkshbuiltins[] = {
 	{"!realpath", c_realpath},
 	{"~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},
@@ -1347,10 +1348,17 @@ c_bind(const char **wp)
 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);
@@ -1369,6 +1377,7 @@ c_shift(const char **wp)
 		bi_errorf(Tf_sD_s, Tbadnum, arg);
 		return (1);
 	}
+
 	if (l->argc < n) {
 		bi_errorf("nothing to shift");
 		return (1);
@@ -2023,7 +2032,6 @@ int
 c_eval(const char **wp)
 {
 	struct source *s, *saves = source;
-	unsigned char savef;
 	int rv;
 
 	if (ksh_getopt(wp, &builtin_opt, null) == '?')
@@ -2066,10 +2074,7 @@ c_eval(const char **wp)
 	/* 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)
@@ -2231,7 +2236,13 @@ c_set(const char **wp)
 	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 };
@@ -2242,6 +2253,8 @@ c_set(const char **wp)
 		return (2);
 	/* set $# and $* */
 	if (setargs) {
+		const char **owp;
+
 		wp += argi - 1;
 		owp = wp;
 		/* save $0 */
--- mksh-57.orig/histrap.c
+++ mksh-57/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 @@ findhist(int start, int fwd, const char
 void
 sethistsize(mksh_ari_t n)
 {
+	if (n > 65535)
+		n = 65535;
 	if (n > 0 && n != histsize) {
 		int cursize = histptr - history;
 
--- mksh-57.orig/jobs.c
+++ mksh-57/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 @@ struct proc {
 
 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 @@ new_job(void)
 	if (free_jobs != NULL) {
 		newj = free_jobs;
 		free_jobs = free_jobs->next;
-	} else
-		newj = alloc(sizeof(Job), APERM);
+	} else {
+		char *cp;
 
-	/* brute force method */
-	for (i = 1; ; i++) {
-		for (j = job_list; j && j->job != i; j = j->next)
-			;
-		if (j == NULL)
-			break;
+		/*
+		 * 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 */
+	i = 0;
+	do {
+		++i;
+		j = job_list;
+		while (j && j->job != i)
+			j = j->next;
+	} while (j);
 	newj->job = i;
 
 	return (newj);
--- mksh-57.orig/main.c
+++ mksh-57/main.c
@@ -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.360 2019/12/30 03:58:56 tg Exp $");
 
 #ifndef MKSHRC_PATH
 #define MKSHRC_PATH	"~/.mkshrc"
@@ -650,7 +650,16 @@ main_init(int argc, const char *argv[],
 
 	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);
+	} else {
 		if (Flag(FLOGIN))
 			include(substitute("$HOME/.profile", 0), 0, NULL, true);
 		if (Flag(FTALKING)) {
@@ -658,13 +667,7 @@ main_init(int argc, const char *argv[],
 			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... */
@@ -735,6 +738,7 @@ include(const char *name, int argc, cons
 		switch (i) {
 		case LRETURN:
 		case LERROR:
+		case LERREXT:
 			/* see below */
 			return (exstat & 0xFF);
 		case LINTR:
@@ -802,6 +806,8 @@ shell(Source * volatile s, volatile int
 	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))) {
@@ -809,18 +815,21 @@ shell(Source * volatile s, volatile int
 		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)
@@ -851,6 +860,8 @@ shell(Source * volatile s, volatile int
 	case LRETURN:
 		source = old_source;
 		quitenv(NULL);
+		if (i == LERREXT && level == 2)
+			return (exstat & 0xFF);
 		/* keep on going */
 		unwind(i);
 		/* NOTREACHED */
@@ -910,8 +921,8 @@ shell(Source * volatile s, volatile int
  source_no_tree:
 		reclaim();
 	}
-	quitenv(NULL);
 	source = old_source;
+	quitenv(NULL);
 	return (exstat & 0xFF);
 }
 
@@ -920,36 +931,25 @@ shell(Source * volatile s, volatile int
 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) {
@@ -992,8 +992,7 @@ newenv(int type)
 	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;
 }
 
@@ -1330,6 +1329,39 @@ bi_errorf(const char *fmt, ...)
 	}
 }
 
+/*
+ * 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, ...)
@@ -1429,7 +1461,7 @@ initio(void)
 	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)
@@ -1991,7 +2023,7 @@ init_environ(void)
 	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",
--- mksh-57.orig/misc.c
+++ mksh-57/misc.c
@@ -3,7 +3,7 @@
 
 /*-
  * 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
  *	mirabilos <m@mirbsd.org>
  * Copyright (c) 2015
  *	Daniel Richard G. <skunk@iSKUNK.ORG>
@@ -32,7 +32,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.296 2019/12/11 23:58:19 tg Exp $");
 
 #define KSH_CHVT_FLAG
 #ifdef MKSH_SMALL
@@ -216,70 +216,85 @@ getoptions(void)
 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
 	}
 }
 
@@ -1674,14 +1689,13 @@ do_realpath(const char *upath)
 		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 +1797,7 @@ do_realpath(const char *upath)
  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 +2213,7 @@ c_cd(const char **wp)
 	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;
--- mksh-57.orig/mksh.1
+++ mksh-57/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.472 2020/01/11 21:12:29 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
@@ -77,7 +77,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: January 11 2020 $
 .\"
 .\" Check which macro package we use, and do other -mdoc setup.
 .\"
@@ -221,7 +221,8 @@ into account all information is first an
 .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.
@@ -279,7 +280,7 @@ effective user ID or group ID (see
 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
@@ -1438,7 +1439,8 @@ Note that both the parameter name and th
 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 ,
@@ -1924,9 +1926,7 @@ This is different from
 .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 +1948,16 @@ above for details.
 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
@@ -2203,7 +2203,7 @@ set or does not contain the absolute pat
 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,
@@ -2965,7 +2965,7 @@ Note that when called in a subshell,
 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 +2978,6 @@ notation:
 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
@@ -4195,6 +4192,11 @@ It is set automatically if, when the she
 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.
@@ -4605,14 +4607,20 @@ instead of
 .Dq Li xtrace .
 .It Ar string No = Ar string
 Strings are equal.
+If the right-hand side is not quoted, pattern matching occurs.
 .It Ar string No == Ar string
-Strings are equal.
+Same as
+.Sq =
+.Pq deprecated .
 .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.
+See
+.Sq =
+regarding pattern matching.
 .It Ar number Fl eq Ar number
 Numbers compare equal.
 .It Ar number Fl ne Ar number
@@ -5005,7 +5013,7 @@ unless they are also given on the same c
 .Pp
 .It Xo
 .Ic ulimit
-.Op Fl aBCcdefHilMmnOPpqrSsTtVvw
+.Op Fl aBCcdefHilMmnOPpqrSsTtVvwx
 .Op Ar value
 .Xc
 Display or set process limits.
@@ -5078,6 +5086,17 @@ Set the number of AIO operations to
 .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 ;
+on
+.Tn AIX ,
+see
+.Fl r
+.Pq as used by its Nm ksh
+though.
 .It Fl p Ar n
 Impose a limit of
 .Ar n
@@ -5089,6 +5108,11 @@ message queues to
 .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
@@ -5116,6 +5140,9 @@ kibibytes on the amount of virtual memor
 Impose a limit of
 .Ar n
 kibibytes on the amount of swap space used.
+.It Fl x Ar n
+Set the maximum number of file locks to
+.Ar n .
 .El
 .Pp
 As far as
@@ -6494,7 +6521,7 @@ removed to the history and a new prompt
 .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 +6534,7 @@ System profile (login shells); see
 .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 +6572,12 @@ contains the system and suid profile.
 .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
@@ -6669,70 +6702,20 @@ The complete legalese is at:
 .\"
 .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.
-.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.
+.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.
 .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 +6739,7 @@ for the in-memory portion of the history
 .Xr memmove 3 .
 .Pp
 This document attempts to describe
-.Nm mksh\ R57
+.Nm mksh\ R58
 and up,
 .\" with vendor patches from insert-your-name-here,
 compiled without any options impacting functionality, such as
@@ -6784,187 +6767,3 @@ IRC channel at
 .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.
--- /dev/null
+++ mksh-57/mksh.faq
@@ -0,0 +1,575 @@
+RCSID: $MirOS: src/bin/mksh/mksh.faq,v 1.4 2019/12/30 22:16:20 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: 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 and possibly adding another,
+convenience, editor command) together with 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.
+----
--- mksh-57.orig/os2.c
+++ mksh-57/os2.c
@@ -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.9 2019/08/01 20:05:01 tg Exp $");
 
 static char *remove_trailing_dots(char *);
 static int access_stat_ex(int (*)(), const char *, void *);
@@ -172,6 +173,8 @@ init_extlibpath(void)
 void
 os2_init(int *argcp, const char ***argvp)
 {
+	KBDINFO ki;
+
 	response(argcp, argvp);
 
 	init_extlibpath();
@@ -183,6 +186,12 @@ os2_init(int *argcp, const char ***argvp
 	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);
 }
 
--- mksh-57.orig/rlimits.opt
+++ mksh-57/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 @@ FN("sockbufsiz(KiB)", RLIMIT_SBSIZE, 102
 >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 @@ FN("virtual-memory(KiB)", RLIMIT_VMEM, 1
 >v|ULIMIT_V_IS_AS
 FN("address-space(KiB)", RLIMIT_AS, 1024
 
+>x|RLIMIT_LOCKS
+FN("filelocks", RLIMIT_LOCKS, 1
+
 |RLIMITS_OPTCS
--- mksh-57.orig/sh.h
+++ mksh-57/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
@@ -171,6 +172,14 @@
 #define __IDSTRING_EXPAND(l,p)		__IDSTRING_CONCAT(l,p)
 #ifdef MKSH_DONT_EMIT_IDSTRING
 #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)				\
 	static const char __IDSTRING_EXPAND(__LINE__,prefix) []	\
@@ -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.880 2020/01/09 14:39:23 tg Exp $");
 #endif
-#define MKSH_VERSION "R57 2019/03/01"
+#define MKSH_VERSION "R57 2019/12/29"
 
 /* arithmetic types: C implementation */
 #if !HAVE_CAN_INTTYPES
@@ -255,6 +264,16 @@ typedef MKSH_TYPEDEF_SIG_ATOMIC_T sig_at
 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 */
@@ -698,6 +717,33 @@ im_sorry_dave(void)
 	(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 +885,7 @@ extern struct env {
 /* 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 +898,13 @@ extern struct env {
 #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 */
@@ -868,9 +916,9 @@ EXTERN uint8_t trap_nested;	/* running n
 EXTERN uint8_t shell_flags[FNFLAGS];
 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 */
@@ -999,8 +1047,8 @@ EXTERN const char Tredirection_dup[] E_I
 EXTERN const char Treal_sp2[] E_INIT(" real ");
 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 Tsghset[] E_INIT("*=#set");
 #define Tsh (Tmksh + 2)
 #define TSHELL (TEXECSHELL + 4)
 #define Tshell (Ttoo_many_files + 23)
@@ -1032,7 +1080,6 @@ EXTERN const char Tf__S[] E_INIT(" %S");
 #define Tf__d (Tunexpected_type + 22)
 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 ");
@@ -1056,8 +1103,6 @@ EXTERN const char Tf_S_[] E_INIT("%S ");
 #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");
@@ -1160,8 +1205,8 @@ EXTERN const char T_devtty[] E_INIT("/de
 #define Treal_sp2 " real "
 #define Treq_arg "requires an argument"
 #define Tselect "select"
-#define Tsgset "*=set"
 #define Tset "set"
+#define Tsghset "*=#set"
 #define Tsh "sh"
 #define TSHELL "SHELL"
 #define Tshell "shell"
@@ -1193,7 +1238,6 @@ EXTERN const char T_devtty[] E_INIT("/de
 #define Tf__d " %d"
 #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 "
@@ -1217,8 +1261,6 @@ EXTERN const char T_devtty[] E_INIT("/de
 #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"
@@ -1756,6 +1798,7 @@ EXTERN bool last_lookup_was_array;
 #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
@@ -1824,6 +1867,8 @@ struct block {
 /* 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.
@@ -1995,6 +2040,11 @@ struct ioword {
 #define DOHERESTR BIT(14)	/* append a newline char */
 
 #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 */
@@ -2317,6 +2367,9 @@ void afreeall(Area *);
 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
@@ -2511,6 +2564,8 @@ void warningf(bool, const char *, ...)
     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)
@@ -2607,7 +2662,7 @@ ssize_t shf_read(char *, ssize_t, struct
 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
--- mksh-57.orig/sh_flags.opt
+++ mksh-57/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.8 2019/12/30 03:58:58 tg Exp $");
 @SHFLAGS_ENUMS
 #define FN(sname,cname,flags,ochar)	cname,
 #define F0(sname,cname,flags,ochar)	cname = 0,
@@ -184,7 +184,7 @@ FN("xtrace", FXTRACE, OF_ANY
 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) */
--- mksh-57.orig/shf.c
+++ mksh-57/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 @@ shf_getse(char *buf, ssize_t bsize, stru
 		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 @@ shf_getse(char *buf, ssize_t bsize, stru
 #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 @@ shf_vfprintf(struct shf *shf, const char
 	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)
 {
--- mksh-57.orig/var.c
+++ mksh-57/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.234 2019/12/30 04:49:31 tg Exp $");
 
 /*-
  * Variables
@@ -49,7 +50,7 @@ static void c_typeset_vardump(struct tbl
 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 setspec(struct tbl *);
 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 @@ array_index_calc(const char *n, bool *ar
 			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 @@ array_index_calc(const char *n, bool *ar
 	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 @@ isglobal(const char *n, bool docreate)
 		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 @@ str_val(struct tbl *vp)
 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 @@ setstr(struct tbl *vq, const char *s, in
 	}
 	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 @@ setstr(struct tbl *vq, const char *s, in
 	vq->flag |= ISSET;
 	if ((vq->flag&SPECIAL))
 		setspec(vq);
-	afree(salloc, ATEMP);
 	return (1);
 }
 
@@ -728,25 +738,19 @@ formatstr(struct tbl *vp, const char *s)
  * 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 @@ exportprep(struct tbl *vp, const char *v
 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 @@ typeset(const char *var, uint32_t set, u
 	}
 	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 @@ typeset(const char *var, uint32_t set, u
 		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 @@ typeset(const char *var, uint32_t set, u
 		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 @@ typeset(const char *var, uint32_t set, u
 
 		/* 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 @@ typeset(const char *var, uint32_t set, u
 				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 @@ typeset(const char *var, uint32_t set, u
 			 * 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))
@@ -887,7 +910,8 @@ typeset(const char *var, uint32_t set, u
 	/* 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");
+		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 @@ typeset(const char *var, uint32_t set, u
 	 */
 	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 @@ typeset(const char *var, uint32_t set, u
 			}
 		}
 		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 @@ typeset(const char *var, uint32_t set, u
 			/* 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);
 }
@@ -2026,7 +2059,7 @@ c_typeset(const char **wp)
 	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 @@ c_typeset(const char **wp)
 					    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);
 			}
