Updated mksh to ToT as of 12 October 2011.

This includes several security fixes and brings us in
line with upstream, who has included fixes for a
number of issues originally reported by the Android
team.

Change-Id: I1e0f3adf292b86fa7679b3364a774e5b6004beb8
diff --git a/src/Build.sh b/src/Build.sh
index c98b1ca..6561cf6 100644
--- a/src/Build.sh
+++ b/src/Build.sh
@@ -1,7 +1,7 @@
 #!/bin/sh
-srcversion='$MirOS: src/bin/mksh/Build.sh,v 1.459 2010/08/24 15:46:06 tg Exp $'
+srcversion='$MirOS: src/bin/mksh/Build.sh,v 1.488 2011/10/07 19:51:41 tg Exp $'
 #-
-# Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+# Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
 #	Thorsten Glaser <tg@mirbsd.org>
 #
 # Provided that these terms and disclaimer and all copyright notices
@@ -30,6 +30,8 @@
 #			MKSH_NOPWNAM MKSH_NO_LIMITS MKSH_SMALL MKSH_S_NOVI
 #			MKSH_UNEMPLOYED MKSH_DEFAULT_EXECSHELL MKSHRC_PATH
 #			MKSH_DEFAULT_TMPDIR MKSH_CLRTOEOL_STRING MKSH_A4PB
+#			MKSH_NO_DEPRECATED_WARNING MKSH_DONT_EMIT_IDSTRING
+#			MKSH_NOPROSPECTOFWORK MKSH_NO_EXTERNAL_CAT
 
 LC_ALL=C
 export LC_ALL
@@ -204,11 +206,15 @@
 	test x"$fv" = x"1"
 }
 
+add_cppflags() {
+	CPPFLAGS="$CPPFLAGS $*"
+}
+
 ac_cppflags() {
 	test x"$1" = x"" || fu=$1
 	fv=$2
 	test x"$2" = x"" && eval fv=\$HAVE_$fu
-	CPPFLAGS="$CPPFLAGS -DHAVE_$fu=$fv"
+	add_cppflags -DHAVE_$fu=$fv
 }
 
 ac_test() {
@@ -216,7 +222,7 @@
 	ac_cppflags
 }
 
-# ac_flags [-] add varname flags [text]
+# ac_flags [-] add varname cflags [text] [ldflags]
 ac_flags() {
 	if test x"$1" = x"-"; then
 		shift
@@ -228,9 +234,14 @@
 	vn=$2
 	f=$3
 	ft=$4
+	fl=$5
 	test x"$ft" = x"" && ft="if $f can be used"
 	save_CFLAGS=$CFLAGS
 	CFLAGS="$CFLAGS $f"
+	if test -n "$fl"; then
+		save_LDFLAGS=$LDFLAGS
+		LDFLAGS="$LDFLAGS $fl"
+	fi
 	if test 1 = $hf; then
 		ac_testn can_$vn '' "$ft"
 	else
@@ -240,6 +251,9 @@
 		EOF
 	fi
 	eval fv=\$HAVE_CAN_`upper $vn`
+	if test -n "$fl"; then
+		test 11 = $fa$fv || LDFLAGS=$save_LDFLAGS
+	fi
 	test 11 = $fa$fv || CFLAGS=$save_CFLAGS
 }
 
@@ -287,7 +301,7 @@
     Rebuild.sh signames.inc test.sh x vv.out
 
 curdir=`pwd` srcdir=`dirname "$0"` check_categories=
-test -n "$dirname" || dirname=.
+test -n "$srcdir" || srcdir=.
 dstversion=`sed -n '/define MKSH_VERSION/s/^.*"\(.*\)".*$/\1/p' $srcdir/sh.h`
 
 e=echo
@@ -301,7 +315,7 @@
 for i
 do
 	case $last:$i in
-	c:combine|c:dragonegg|c:llvm)
+	c:combine|c:dragonegg|c:llvm|c:lto)
 		cm=$i
 		last=
 		;;
@@ -316,28 +330,14 @@
 	:-c)
 		last=c
 		;;
-	:-combine)
-		cm=combine
-		echo "$me: Warning: '$i' is deprecated, use '-c combine' instead!" >&2
-		;;
 	:-g)
 		# checker, debug, valgrind build
-		CPPFLAGS="$CPPFLAGS -DDEBUG"
+		add_cppflags -DDEBUG
 		CFLAGS="$CFLAGS -g3 -fno-builtin"
 		;;
 	:-j)
 		pm=1
 		;;
-	:-llvm)
-		cm=llvm
-		optflags=-std-compile-opts
-		echo "$me: Warning: '$i' is deprecated, use '-c llvm -O' instead!" >&2
-		;;
-	:-llvm=*)
-		cm=llvm
-		optflags=`echo "x$i" | sed 's/^x-llvm=//'`
-		echo "$me: Warning: '$i' is deprecated, use '-c llvm -o $llvm' instead!" >&2
-		;;
 	:-M)
 		cm=makefile
 		;;
@@ -383,12 +383,22 @@
 fi
 
 test x"$TARGET_OS" = x"" && TARGET_OS=`uname -s 2>/dev/null || uname`
+if test x"$TARGET_OS" = x""; then
+	echo "$me: Set TARGET_OS, your uname is broken!" >&2
+	exit 1
+fi
 oswarn=
 ccpc=-Wc,
 ccpl=-Wl,
 tsts=
 ccpr='|| for _f in ${tcfn}*; do test x"${_f}" = x"mksh.1" || rm -f "${_f}"; done'
 
+# Evil hack
+if test x"$TARGET_OS" = x"Android"; then
+	check_categories="$check_categories android"
+	TARGET_OS=Linux
+fi
+
 # Configuration depending on OS revision, on OSes that need them
 case $TARGET_OS in
 QNX)
@@ -399,7 +409,7 @@
 # Configuration depending on OS name
 case $TARGET_OS in
 AIX)
-	CPPFLAGS="$CPPFLAGS -D_ALL_SOURCE"
+	add_cppflags -D_ALL_SOURCE
 	: ${HAVE_SETLOCALE_CTYPE=0}
 	;;
 BeOS)
@@ -417,22 +427,34 @@
 	;;
 FreeBSD)
 	;;
+FreeMiNT)
+	oswarn="; it has minor issues"
+	add_cppflags -D_GNU_SOURCE
+	: ${HAVE_SETLOCALE_CTYPE=0}
+	;;
 GNU)
+	case $CC in
+	*tendracc*) ;;
+	*) add_cppflags -D_GNU_SOURCE ;;
+	esac
 	# define NO_PATH_MAX to use Hurd-only functions
-	CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE -DNO_PATH_MAX"
+	add_cppflags -DNO_PATH_MAX
 	;;
 GNU/kFreeBSD)
-	CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE"
+	case $CC in
+	*tendracc*) ;;
+	*) add_cppflags -D_GNU_SOURCE ;;
+	esac
 	;;
 Haiku)
-	CPPFLAGS="$CPPFLAGS -DMKSH_ASSUME_UTF8"
+	add_cppflags -DMKSH_ASSUME_UTF8
 	;;
 HP-UX)
 	;;
 Interix)
 	ccpc='-X '
 	ccpl='-Y '
-	CPPFLAGS="$CPPFLAGS -D_ALL_SOURCE"
+	add_cppflags -D_ALL_SOURCE
 	: ${LIBS='-lcrypt'}
 	: ${HAVE_SETLOCALE_CTYPE=0}
 	;;
@@ -440,19 +462,29 @@
 	: ${HAVE_SETLOCALE_CTYPE=0}
 	;;
 Linux)
-	CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE"
+	case $CC in
+	*tendracc*) ;;
+	*) add_cppflags -D_GNU_SOURCE ;;
+	esac
+	add_cppflags -DSETUID_CAN_FAIL_WITH_EAGAIN
 	: ${HAVE_REVOKE=0}
 	;;
 MidnightBSD)
 	;;
 Minix)
-	CPPFLAGS="$CPPFLAGS -DMKSH_UNEMPLOYED -DMKSH_CONSERVATIVE_FDS"
-	CPPFLAGS="$CPPFLAGS -D_POSIX_SOURCE -D_POSIX_1_SOURCE=2 -D_MINIX"
+	add_cppflags -DMKSH_UNEMPLOYED
+	add_cppflags -DMKSH_CONSERVATIVE_FDS
+	add_cppflags -DMKSH_NO_LIMITS
+	add_cppflags -D_POSIX_SOURCE -D_POSIX_1_SOURCE=2 -D_MINIX
 	oldish_ed=no-stderr-ed		# /usr/bin/ed(!) is broken
 	: ${HAVE_SETLOCALE_CTYPE=0}
 	;;
 MirBSD)
 	;;
+MSYS_*)
+	# probably same as CYGWIN* – need to test; from RT|Chatzilla
+	oswarn='but will probably work'
+	;;
 NetBSD)
 	;;
 OpenBSD)
@@ -460,15 +492,20 @@
 	;;
 OSF1)
 	HAVE_SIG_T=0	# incompatible
-	CPPFLAGS="$CPPFLAGS -D_OSF_SOURCE -D_POSIX_C_SOURCE=200112L"
-	CPPFLAGS="$CPPFLAGS -D_XOPEN_SOURCE=600 -D_XOPEN_SOURCE_EXTENDED"
+	add_cppflags -D_OSF_SOURCE
+	add_cppflags -D_POSIX_C_SOURCE=200112L
+	add_cppflags -D_XOPEN_SOURCE=600
+	add_cppflags -D_XOPEN_SOURCE_EXTENDED
 	: ${HAVE_SETLOCALE_CTYPE=0}
 	;;
 Plan9)
-	CPPFLAGS="$CPPFLAGS -D_POSIX_SOURCE -D_LIMITS_EXTENSION"
-	CPPFLAGS="$CPPFLAGS -D_BSD_EXTENSION -D_SUSV2_SOURCE"
+	add_cppflags -D_POSIX_SOURCE
+	add_cppflags -D_LIMITS_EXTENSION
+	add_cppflags -D_BSD_EXTENSION
+	add_cppflags -D_SUSV2_SOURCE
+	add_cppflags -DMKSH_ASSUME_UTF8
 	oswarn=' and will currently not work'
-	CPPFLAGS="$CPPFLAGS -DMKSH_ASSUME_UTF8 -DMKSH_UNEMPLOYED"
+	add_cppflags -DMKSH_UNEMPLOYED
 	;;
 PW32*)
 	HAVE_SIG_T=0	# incompatible
@@ -476,7 +513,7 @@
 	: ${HAVE_SETLOCALE_CTYPE=0}
 	;;
 QNX)
-	CPPFLAGS="$CPPFLAGS -D__NO_EXT_QNX"
+	add_cppflags -D__NO_EXT_QNX
 	case $TARGET_OSREV in
 	[012345].*|6.[0123].*|6.4.[01])
 		oldish_ed=no-stderr-ed		# oldish /bin/ed is broken
@@ -485,15 +522,16 @@
 	: ${HAVE_SETLOCALE_CTYPE=0}
 	;;
 SunOS)
-	CPPFLAGS="$CPPFLAGS -D_BSD_SOURCE -D__EXTENSIONS__"
+	add_cppflags -D_BSD_SOURCE
+	add_cppflags -D__EXTENSIONS__
 	;;
 syllable)
-	CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE"
+	add_cppflags -D_GNU_SOURCE
 	oswarn=' and will currently not work'
 	;;
 ULTRIX)
 	: ${CC=cc -YPOSIX}
-	CPPFLAGS="$CPPFLAGS -Dssize_t=int"
+	add_cppflags -Dssize_t=int
 	: ${HAVE_SETLOCALE_CTYPE=0}
 	;;
 UWIN*)
@@ -509,6 +547,8 @@
 	;;
 esac
 
+: ${HAVE_MKNOD=0}
+
 : ${CC=cc} ${NROFF=nroff}
 test 0 = $r && echo | $NROFF -v 2>&1 | grep GNU >/dev/null 2>&1 && \
     NROFF="$NROFF -c"
@@ -516,6 +556,10 @@
 # this aids me in tracing FTBFSen without access to the buildd
 $e "Hi from$ao $bi$srcversion$ao on:"
 case $TARGET_OS in
+AIX)
+	vv '|' "oslevel >&2"
+	vv '|' "uname -a >&2"
+	;;
 Darwin)
 	vv '|' "hwprefs machine_type os_type os_class >&2"
 	vv '|' "uname -a >&2"
@@ -735,6 +779,7 @@
     own risk, please report success/failure to the developers.'
 	;;
 xlc)
+	vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -qversion"
 	vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -qversion=verbose"
 	vv '|' "ld -V"
 	;;
@@ -866,9 +911,46 @@
 	ac_flags 1 fnostrictaliasing -fno-strict-aliasing
 	ac_flags 1 fstackprotectorall -fstack-protector-all
 	ac_flags 1 fwrapv -fwrapv
-	test $cm = combine && ac_flags 0 combine \
-	    '-fwhole-program --combine' \
-	    'if gcc supports -fwhole-program --combine'
+	test $cm = dragonegg && case " $CC $CFLAGS $LDFLAGS " in
+	*\ -fplugin=*dragonegg*) ;;
+	*) ac_flags 1 fplugin_dragonegg -fplugin=dragonegg ;;
+	esac
+	if test $cm = lto; then
+		fv=0
+		checks='1 2 3 4 5 6 7 8'
+	elif test $cm = combine; then
+		fv=0
+		checks='7 8'
+	else
+		fv=1
+	fi
+	test $fv = 1 || for what in $checks; do
+		test $fv = 1 && break
+		case $what in
+		1)	t_cflags='-flto=jobserver'
+			t_ldflags='-fuse-linker-plugin'
+			t_use=1 t_name=fltojs_lp ;;
+		2)	t_cflags='-flto=jobserver' t_ldflags=''
+			t_use=1 t_name=fltojs_nn ;;
+		3)	t_cflags='-flto=jobserver'
+			t_ldflags='-fno-use-linker-plugin -fwhole-program'
+			t_use=1 t_name=fltojs_np ;;
+		4)	t_cflags='-flto'
+			t_ldflags='-fuse-linker-plugin'
+			t_use=1 t_name=fltons_lp ;;
+		5)	t_cflags='-flto' t_ldflags=''
+			t_use=1 t_name=fltons_nn ;;
+		6)	t_cflags='-flto'
+			t_ldflags='-fno-use-linker-plugin -fwhole-program'
+			t_use=1 t_name=fltons_np ;;
+		7)	t_cflags='-fwhole-program --combine' t_ldflags=''
+			t_use=0 t_name=combine cm=combine ;;
+		8)	fv=1 cm=normal ;;
+		esac
+		test $fv = 1 && break
+		ac_flags $t_use $t_name "$t_cflags" \
+		    "if gcc supports $t_cflags $t_ldflags" "$t_ldflags"
+	done
 	i=1
 elif test $ct = icc; then
 	ac_flags 1 fnobuiltinsetmode -fno-builtin-setmode
@@ -933,8 +1015,8 @@
 	    ac_flags 1 stdc99 -std=c99 'for support of ISO C99'
 	ac_flags 1 wall -Wall
 fi
-phase=x
 
+phase=x
 # The following tests run with -Werror or similar (all compilers) if possible
 NOWARN=$DOWARN
 test $ct = pcc && phase=u
@@ -942,76 +1024,77 @@
 #
 # Compiler: check for stuff that only generates warnings
 #
-ac_test attribute_bounded '' 'for __attribute__((bounded))' <<-'EOF'
-	#if defined(__GNUC__) && (__GNUC__ < 2)
-	/* force a failure: gcc 1.42 has a false positive here */
+ac_test attribute_bounded '' 'for __attribute__((__bounded__))' <<-'EOF'
+	#if defined(__TenDRA__) || (defined(__GNUC__) && (__GNUC__ < 2))
+	/* force a failure: TenDRA and gcc 1.42 have false positive here */
 	int main(void) { return (thiswillneverbedefinedIhope()); }
 	#else
 	#include <string.h>
 	#undef __attribute__
 	int xcopy(const void *, void *, size_t)
-	    __attribute__((bounded (buffer, 1, 3)))
-	    __attribute__((bounded (buffer, 2, 3)));
+	    __attribute__((__bounded__ (__buffer__, 1, 3)))
+	    __attribute__((__bounded__ (__buffer__, 2, 3)));
 	int main(int ac, char *av[]) { return (xcopy(av[0], av[--ac], 1)); }
 	int xcopy(const void *s, void *d, size_t n) {
 		memmove(d, s, n); return ((int)n);
 	}
 	#endif
 EOF
-ac_test attribute_format '' 'for __attribute__((format))' <<-'EOF'
-	#if defined(__GNUC__) && (__GNUC__ < 2)
-	/* force a failure: gcc 1.42 has a false positive here */
+ac_test attribute_format '' 'for __attribute__((__format__))' <<-'EOF'
+	#if defined(__TenDRA__) || (defined(__GNUC__) && (__GNUC__ < 2))
+	/* force a failure: TenDRA and gcc 1.42 have false positive here */
 	int main(void) { return (thiswillneverbedefinedIhope()); }
 	#else
+	#define fprintf printfoo
 	#include <stdio.h>
 	#undef __attribute__
-	#undef printf
-	extern int printf(const char *format, ...)
-	    __attribute__((format (printf, 1, 2)));
-	int main(int ac, char **av) { return (printf("%s%d", *av, ac)); }
+	#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)); }
 	#endif
 EOF
-ac_test attribute_nonnull '' 'for __attribute__((nonnull))' <<-'EOF'
-	#if defined(__GNUC__) && (__GNUC__ < 2)
-	/* force a failure: gcc 1.42 has a false positive here */
+ac_test attribute_nonnull '' 'for __attribute__((__nonnull__))' <<-'EOF'
+	#if defined(__TenDRA__) || (defined(__GNUC__) && (__GNUC__ < 2))
+	/* force a failure: TenDRA and gcc 1.42 have false positive here */
 	int main(void) { return (thiswillneverbedefinedIhope()); }
 	#else
-	int foo(char *s1, char *s2) __attribute__((nonnull));
-	int bar(char *s1, char *s2) __attribute__((nonnull (1, 2)));
-	int baz(char *s) __attribute__((nonnull (1)));
+	int foo(char *s1, char *s2) __attribute__((__nonnull__));
+	int bar(char *s1, char *s2) __attribute__((__nonnull__ (1, 2)));
+	int baz(char *s) __attribute__((__nonnull__ (1)));
 	int foo(char *s1, char *s2) { return (bar(s2, s1)); }
 	int bar(char *s1, char *s2) { return (baz(s1) - baz(s2)); }
 	int baz(char *s) { return (*s); }
 	int main(int ac, char **av) { return (ac == foo(av[0], av[ac-1])); }
 	#endif
 EOF
-ac_test attribute_noreturn '' 'for __attribute__((noreturn))' <<-'EOF'
-	#if defined(__GNUC__) && (__GNUC__ < 2)
-	/* force a failure: gcc 1.42 has a false positive here */
+ac_test attribute_noreturn '' 'for __attribute__((__noreturn__))' <<-'EOF'
+	#if defined(__TenDRA__) || (defined(__GNUC__) && (__GNUC__ < 2))
+	/* force a failure: TenDRA and gcc 1.42 have false positive here */
 	int main(void) { return (thiswillneverbedefinedIhope()); }
 	#else
 	#include <stdlib.h>
 	#undef __attribute__
-	void fnord(void) __attribute__((noreturn));
+	void fnord(void) __attribute__((__noreturn__));
 	int main(void) { fnord(); }
 	void fnord(void) { exit(0); }
 	#endif
 EOF
-ac_test attribute_unused '' 'for __attribute__((unused))' <<-'EOF'
-	#if defined(__GNUC__) && (__GNUC__ < 2)
-	/* force a failure: gcc 1.42 has a false positive here */
+ac_test attribute_unused '' 'for __attribute__((__unused__))' <<-'EOF'
+	#if defined(__TenDRA__) || (defined(__GNUC__) && (__GNUC__ < 2))
+	/* force a failure: TenDRA and gcc 1.42 have false positive here */
 	int main(void) { return (thiswillneverbedefinedIhope()); }
 	#else
-	int main(int ac __attribute__((unused)), char **av
-	    __attribute__((unused))) { return (0); }
+	int main(int ac __attribute__((__unused__)), char **av
+	    __attribute__((__unused__))) { return (0); }
 	#endif
 EOF
-ac_test attribute_used '' 'for __attribute__((used))' <<-'EOF'
-	#if defined(__GNUC__) && (__GNUC__ < 2)
-	/* force a failure: gcc 1.42 has a false positive here */
+ac_test attribute_used '' 'for __attribute__((__used__))' <<-'EOF'
+	#if defined(__TenDRA__) || (defined(__GNUC__) && (__GNUC__ < 2))
+	/* force a failure: TenDRA and gcc 1.42 have false positive here */
 	int main(void) { return (thiswillneverbedefinedIhope()); }
 	#else
-	static const char fnord[] __attribute__((used)) = "42";
+	static const char fnord[] __attribute__((__used__)) = "42";
 	int main(void) { return (0); }
 	#endif
 EOF
@@ -1043,43 +1126,93 @@
 		;;
 	esac
 
-	: ${HAVE_MKNOD=0}
 	: ${HAVE_NICE=0}
-	: ${HAVE_REVOKE=0}
 	: ${HAVE_PERSISTENT_HISTORY=0}
-	check_categories=$check_categories,smksh
+	check_categories="$check_categories smksh"
 	HAVE_ISSET_MKSH_CONSERVATIVE_FDS=1	# from sh.h
 fi
 ac_ifcpp 'ifdef MKSH_BINSHREDUCED' isset_MKSH_BINSHREDUCED '' \
     "if a reduced-feature sh is requested" && \
-    check_categories=$check_categories,binsh
+    check_categories="$check_categories binsh"
 ac_ifcpp 'ifdef MKSH_UNEMPLOYED' isset_MKSH_UNEMPLOYED '' \
     "if mksh will be built without job control" && \
-    check_categories=$check_categories,arge
+    check_categories="$check_categories arge"
+ac_ifcpp 'ifdef MKSH_NOPROSPECTOFWORK' isset_MKSH_NOPROSPECTOFWORK '' \
+    "if mksh will be built without job signals" && \
+    check_categories="$check_categories arge nojsig"
 ac_ifcpp 'ifdef MKSH_ASSUME_UTF8' isset_MKSH_ASSUME_UTF8 '' \
     'if the default UTF-8 mode is specified' && : ${HAVE_SETLOCALE_CTYPE=0}
 ac_ifcpp 'ifdef MKSH_CONSERVATIVE_FDS' isset_MKSH_CONSERVATIVE_FDS '' \
     'if traditional/conservative fd use is requested' && \
-    check_categories=$check_categories,convfds
+    check_categories="$check_categories convfds"
 
 #
 # Environment: headers
 #
-ac_header sys/param.h
+ac_header sys/bsdtypes.h
+ac_header sys/file.h sys/types.h
 ac_header sys/mkdev.h sys/types.h
 ac_header sys/mman.h sys/types.h
+ac_header sys/param.h
+ac_header sys/select.h sys/types.h
 ac_header sys/sysmacros.h
+ac_header bstring.h
 ac_header grp.h sys/types.h
 ac_header libgen.h
 ac_header libutil.h sys/types.h
 ac_header paths.h
-ac_header stdbool.h
 ac_header stdint.h stdarg.h
-ac_header strings.h sys/types.h
+# include strings.h only if compatible with string.h
+ac_header strings.h sys/types.h string.h
 ac_header ulimit.h sys/types.h
 ac_header values.h
 
 #
+# check whether whatever we use for the final link will succeed
+#
+if test $cm = makefile; then
+	: nothing to check
+else
+	HAVE_LINK_WORKS=x
+	ac_testinit link_works '' 'checking if the final link command may succeed'
+	fv=1
+	cat >conftest.c <<-'EOF'
+		#define EXTERN
+		#define MKSH_INCLUDES_ONLY
+		#include "sh.h"
+		__RCSID("$MirOS: src/bin/mksh/Build.sh,v 1.488 2011/10/07 19:51:41 tg Exp $");
+		int main(void) { printf("Hello, World!\n"); return (0); }
+EOF
+	case $cm in
+	llvm)
+		v "$CC $CFLAGS $CPPFLAGS $NOWARN -emit-llvm -c conftest.c" || fv=0
+		rmf mksh.s
+		test $fv = 0 || v "llvm-link -o - conftest.o | opt $optflags | llc -o mksh.s" || fv=0
+		test $fv = 0 || v "$CC $CFLAGS $LDFLAGS -o $tcfn mksh.s $LIBS $ccpr"
+		;;
+	dragonegg)
+		v "$CC $CFLAGS $CPPFLAGS $NOWARN -S -flto conftest.c" || fv=0
+		test $fv = 0 || v "mv conftest.s conftest.ll"
+		test $fv = 0 || v "llvm-as conftest.ll" || fv=0
+		rmf mksh.s
+		test $fv = 0 || v "llvm-link -o - conftest.bc | opt $optflags | llc -o mksh.s" || fv=0
+		test $fv = 0 || v "$CC $CFLAGS $LDFLAGS -o $tcfn mksh.s $LIBS $ccpr"
+		;;
+	combine)
+		v "$CC $CFLAGS $CPPFLAGS $LDFLAGS -fwhole-program --combine $NOWARN -o $tcfn conftest.c $LIBS $ccpr"
+		;;
+	lto|normal)
+		cm=normal
+		v "$CC $CFLAGS $CPPFLAGS $NOWARN -c conftest.c" || fv=0
+		test $fv = 0 || v "$CC $CFLAGS $LDFLAGS -o $tcfn conftest.o $LIBS $ccpr"
+		;;
+	esac
+	test -f $tcfn || fv=0
+	ac_testdone
+	test $fv = 1 || exit 1
+fi
+
+#
 # Environment: definitions
 #
 echo '#include <sys/types.h>
@@ -1090,10 +1223,11 @@
 int main(void) { return (0); }' >lft.c
 ac_testn can_lfs '' "for large file support" <lft.c
 save_CPPFLAGS=$CPPFLAGS
-CPPFLAGS="$CPPFLAGS -D_FILE_OFFSET_BITS=64"
+add_cppflags -D_FILE_OFFSET_BITS=64
 ac_testn can_lfs_sus '!' can_lfs 0 "... with -D_FILE_OFFSET_BITS=64" <lft.c
 if test 0 = $HAVE_CAN_LFS_SUS; then
-	CPPFLAGS="$save_CPPFLAGS -D_LARGE_FILES=1"
+	CPPFLAGS=$save_CPPFLAGS
+	add_cppflags -D_LARGE_FILES=1
 	ac_testn can_lfs_aix '!' can_lfs 0 "... with -D_LARGE_FILES=1" <lft.c
 	test 1 = $HAVE_CAN_LFS_AIX || CPPFLAGS=$save_CPPFLAGS
 fi
@@ -1136,17 +1270,17 @@
 	#include <sys/types.h>
 	#include <signal.h>
 	#include <stddef.h>
-	int main(void) { return ((int)(ptrdiff_t)(sig_t)kill(0,0)); }
+	int main(void) { return ((int)(ptrdiff_t)(sig_t)(ptrdiff_t)kill(0,0)); }
 EOF
 
 ac_testn sighandler_t '!' sig_t 0 <<-'EOF'
 	#include <sys/types.h>
 	#include <signal.h>
 	#include <stddef.h>
-	int main(void) { return ((int)(ptrdiff_t)(sighandler_t)kill(0,0)); }
+	int main(void) { return ((int)(ptrdiff_t)(sighandler_t)(ptrdiff_t)kill(0,0)); }
 EOF
 if test 1 = $HAVE_SIGHANDLER_T; then
-	CPPFLAGS="$CPPFLAGS -Dsig_t=sighandler_t"
+	add_cppflags -Dsig_t=sighandler_t
 	HAVE_SIG_T=1
 fi
 
@@ -1154,14 +1288,14 @@
 	#include <sys/types.h>
 	#include <signal.h>
 	#include <stddef.h>
-	int main(void) { return ((int)(ptrdiff_t)(__sighandler_t)kill(0,0)); }
+	int main(void) { return ((int)(ptrdiff_t)(__sighandler_t)(ptrdiff_t)kill(0,0)); }
 EOF
 if test 1 = $HAVE___SIGHANDLER_T; then
-	CPPFLAGS="$CPPFLAGS -Dsig_t=__sighandler_t"
+	add_cppflags -Dsig_t=__sighandler_t
 	HAVE_SIG_T=1
 fi
 
-test 1 = $HAVE_SIG_T || CPPFLAGS="$CPPFLAGS -Dsig_t=nosig_t"
+test 1 = $HAVE_SIG_T || add_cppflags -Dsig_t=nosig_t
 ac_cppflags SIG_T
 
 #
@@ -1179,9 +1313,10 @@
 		extern const char *const _sys_sig${what}[];
 		int main(void) { return (_sys_sig${what}[0][0]); }
 	EOF
-	if eval "test 1 = \$HAVE__SYS_SIG$uwhat"; then
-		CPPFLAGS="$CPPFLAGS -Dsys_sig$what=_sys_sig$what"
-		eval "HAVE_SYS_SIG$uwhat=1"
+	eval uwhat_v=\$HAVE__SYS_SIG$uwhat
+	if test 1 = "$uwhat_v"; then
+		add_cppflags -Dsys_sig$what=_sys_sig$what
+		eval HAVE_SYS_SIG$uwhat=1
 	fi
 	ac_cppflags SYS_SIG$uwhat
 done
@@ -1197,8 +1332,12 @@
 #
 ac_testn flock_ex '' 'flock and mmap' <<-'EOF'
 	#include <sys/types.h>
+	#if HAVE_SYS_FILE_H
 	#include <sys/file.h>
+	#endif
+	#if HAVE_SYS_MMAN_H
 	#include <sys/mman.h>
+	#endif
 	#include <fcntl.h>
 	#include <stdlib.h>
 	int main(void) { return ((void *)mmap(NULL, (size_t)flock(0, LOCK_EX),
@@ -1264,18 +1403,30 @@
 	int main(void) { return ((int)(ptrdiff_t)(void *)nl_langinfo(CODESET)); }
 EOF
 
-ac_test setmode mknod 1 <<-'EOF'
-	/* XXX imake style */
-	/* XXX conditions correct? */
-	#if defined(__MSVCRT__) || defined(__CYGWIN__)
-	/* force a failure: Win32 setmode() is not what we want... */
-	int main(void) { return (thiswillneverbedefinedIhope()); }
-	#else
+ac_test select <<-'EOF'
 	#include <sys/types.h>
-	#include <unistd.h>
-	int main(int ac, char *av[]) { return (getmode(setmode(av[0]),
-	    (mode_t)ac)); }
+	#include <sys/time.h>
+	#if HAVE_SYS_BSDTYPES_H
+	#include <sys/bsdtypes.h>
 	#endif
+	#if HAVE_SYS_SELECT_H
+	#include <sys/select.h>
+	#endif
+	#if HAVE_BSTRING_H
+	#include <bstring.h>
+	#endif
+	#include <stddef.h>
+	#include <stdlib.h>
+	#include <string.h>
+	#if HAVE_STRINGS_H
+	#include <strings.h>
+	#endif
+	#include <unistd.h>
+	int main(void) {
+		struct timeval tv = { 1, 200000 };
+		fd_set fds; FD_ZERO(&fds); FD_SET(0, &fds);
+		return (select(FD_SETSIZE, &fds, NULL, NULL, &tv));
+	}
 EOF
 
 ac_test setresugid <<-'EOF'
@@ -1340,7 +1491,7 @@
 #
 fd='if to use persistent history'
 ac_cache PERSISTENT_HISTORY || test 0 = $HAVE_FLOCK_EX || fv=1
-test 1 = $fv || check_categories=$check_categories,no-histfile
+test 1 = $fv || check_categories="$check_categories no-histfile"
 ac_testdone
 ac_cppflags
 
@@ -1365,7 +1516,7 @@
 # the character count to standard output; cope for that
 echo wq >x
 ed x <x 2>/dev/null | grep 3 >/dev/null 2>&1 && \
-    check_categories=$check_categories,$oldish_ed
+    check_categories="$check_categories $oldish_ed"
 rmf x vv.out
 
 if test 0 = $HAVE_SYS_SIGNAME; then
@@ -1407,7 +1558,7 @@
 		vq "$CPP $CFLAGS $CPPFLAGS $NOWARN conftest.c" | \
 		    grep mksh_cfg: | \
 		    sed 's/^mksh_cfg:[	 ]*\([0-9x]*\).*$/\1:'$name/
-	done | grep -v '^:' | while IFS=: read nr name; do
+	done | grep -v '^:' | sed 's/:/ /g' | while read nr name; do
 		test $printf = echo || nr=`printf %d "$nr" 2>/dev/null`
 		test $nr -gt 0 && test $nr -le $NSIG || continue
 		case $sigseen in
@@ -1422,11 +1573,9 @@
 	$e done.
 fi
 
-addsrcs '!' HAVE_SETMODE setmode.c
 addsrcs '!' HAVE_STRLCPY strlcpy.c
 addsrcs USE_PRINTF_BUILTIN printf.c
-test 1 = "$USE_PRINTF_BUILTIN" && CPPFLAGS="$CPPFLAGS -DMKSH_PRINTF_BUILTIN"
-test 0 = "$HAVE_SETMODE" && CPPFLAGS="$CPPFLAGS -DHAVE_CONFIG_H -DCONFIG_H_FILENAME=\\\"sh.h\\\""
+test 1 = "$USE_PRINTF_BUILTIN" && add_cppflags -DMKSH_PRINTF_BUILTIN
 test 1 = "$HAVE_CAN_VERB" && CFLAGS="$CFLAGS -verbose"
 
 $e $bi$me: Finished configuration testing, now producing output.$ao
@@ -1441,7 +1590,42 @@
 cat >>test.sh <<-EOF
 	LC_ALL=C PATH='$PATH'; export LC_ALL PATH
 	test -n "\$KSH_VERSION" || exit 1
-	check_categories=$check_categories
+	set -A check_categories -- $check_categories
+	pflag='$curdir/mksh'
+	sflag='$srcdir/check.t'
+	usee=0 Pflag=0 uset=0 vflag=0 xflag=0
+	while getopts "C:e:Pp:s:t:v" ch; do case \$ch {
+	(C)	check_categories[\${#check_categories[*]}]=\$OPTARG ;;
+	(e)	usee=1; eflag=\$OPTARG ;;
+	(P)	Pflag=1 ;;
+	(p)	pflag=\$OPTARG ;;
+	(s)	sflag=\$OPTARG ;;
+	(t)	uset=1; tflag=\$OPTARG ;;
+	(v)	vflag=1 ;;
+	(*)	xflag=1 ;;
+	}
+	done
+	shift \$((OPTIND - 1))
+	set -A args -- '$srcdir/check.pl' -p "\$pflag" -s "\$sflag"
+	x=
+	for y in "\${check_categories[@]}"; do
+		x=\$x,\$y
+	done
+	if [[ -n \$x ]]; then
+		args[\${#args[*]}]=-C
+		args[\${#args[*]}]=\${x#,}
+	fi
+	if (( usee )); then
+		args[\${#args[*]}]=-e
+		args[\${#args[*]}]=\$eflag
+	fi
+	(( Pflag )) && args[\${#args[*]}]=-P
+	if (( uset )); then
+		args[\${#args[*]}]=-t
+		args[\${#args[*]}]=\$tflag
+	fi
+	(( vflag )) && args[\${#args[*]}]=-v
+	(( xflag )) && args[\${#args[*]}]=-x	# force usage by synerr
 	print Testing mksh for conformance:
 	fgrep MirOS: '$srcdir/check.t'
 	fgrep MIRBSD '$srcdir/check.t'
@@ -1451,14 +1635,13 @@
 	cstr="\$cstr"'print \$os . ", Perl version " . \$];'
 	for perli in \$PERL perl5 perl no; do
 		[[ \$perli = no ]] && exit 1
-		perlos=\$(\$perli -e "\$cstr") 2>&- || continue
+		perlos=\$(\$perli -e "\$cstr") 2>/dev/null || continue
 		print "Perl interpreter '\$perli' running on '\$perlos'"
 		[[ -n \$perlos ]] && break
 	done
-	exec \$perli '$srcdir/check.pl' -s '$srcdir/check.t' -p '$curdir/mksh' \${check_categories:+-C} \${check_categories#,} \$*$tsts
+	exec \$perli "\${args[@]}" "\$@"$tsts
 EOF
 chmod 755 test.sh
-test $HAVE_CAN_COMBINE$cm = 0combine && cm=normal
 if test $cm = llvm; then
 	emitbc="-emit-llvm -c"
 elif test $cm = dragonegg; then
diff --git a/src/Makefile b/src/Makefile
new file mode 100644
index 0000000..03ca8bf
--- /dev/null
+++ b/src/Makefile
@@ -0,0 +1,97 @@
+# $MirOS: src/bin/mksh/Makefile,v 1.88 2011/10/07 19:51:17 tg Exp $
+#-
+# Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
+#	Thorsten Glaser <tg@mirbsd.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un-
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person's immediate fault when using the work as intended.
+#-
+# use CPPFLAGS=-DDEBUG __CRAZY=Yes to check for certain more stuff
+
+.include <bsd.own.mk>
+
+PROG=		mksh
+SRCS=		edit.c eval.c exec.c expr.c funcs.c histrap.c jobs.c \
+		lalloc.c lex.c main.c misc.c shf.c syn.c tree.c var.c
+.if !make(test-build)
+CPPFLAGS+=	-DMKSH_ASSUME_UTF8 \
+		-DHAVE_ATTRIBUTE_BOUNDED=1 -DHAVE_ATTRIBUTE_FORMAT=1 \
+		-DHAVE_ATTRIBUTE_NONNULL=1 -DHAVE_ATTRIBUTE_NORETURN=1 \
+		-DHAVE_ATTRIBUTE_UNUSED=1 -DHAVE_ATTRIBUTE_USED=1 \
+		-DHAVE_SYS_BSDTYPES_H=0 -DHAVE_SYS_FILE_H=1 \
+		-DHAVE_SYS_MKDEV_H=0 -DHAVE_SYS_MMAN_H=1 -DHAVE_SYS_PARAM_H=1 \
+		-DHAVE_SYS_SELECT_H=1 -DHAVE_SYS_SYSMACROS_H=0 \
+		-DHAVE_BSTRING_H=0 -DHAVE_GRP_H=1 -DHAVE_LIBGEN_H=1 \
+		-DHAVE_LIBUTIL_H=0 -DHAVE_PATHS_H=1 -DHAVE_STDINT_H=1 \
+		-DHAVE_STRINGS_H=1 -DHAVE_ULIMIT_H=0 -DHAVE_VALUES_H=0 \
+		-DHAVE_CAN_INTTYPES=1 -DHAVE_CAN_UCBINTS=1 \
+		-DHAVE_CAN_INT8TYPE=1 -DHAVE_CAN_UCBINT8=1 -DHAVE_RLIM_T=1 \
+		-DHAVE_SIG_T=1 -DHAVE_SYS_SIGNAME=1 -DHAVE_SYS_SIGLIST=1 \
+		-DHAVE_STRSIGNAL=0 -DHAVE_GETRUSAGE=1 -DHAVE_KILLPG=1 \
+		-DHAVE_MKNOD=0 -DHAVE_MKSTEMP=1 -DHAVE_NICE=1 -DHAVE_REVOKE=1 \
+		-DHAVE_SETLOCALE_CTYPE=0 -DHAVE_LANGINFO_CODESET=0 \
+		-DHAVE_SELECT=1 -DHAVE_SETRESUGID=1 -DHAVE_SETGROUPS=1 \
+		-DHAVE_STRCASESTR=1 -DHAVE_STRLCPY=1 -DHAVE_FLOCK_DECL=1 \
+		-DHAVE_REVOKE_DECL=1 -DHAVE_SYS_SIGLIST_DECL=1 \
+		-DHAVE_PERSISTENT_HISTORY=1
+COPTS+=		-std=gnu99 -Wall
+.endif
+
+USE_PRINTF_BUILTIN?=	0
+.if ${USE_PRINTF_BUILTIN} == 1
+.PATH: ${BSDSRCDIR}/usr.bin/printf
+SRCS+=		printf.c
+CPPFLAGS+=	-DMKSH_PRINTF_BUILTIN
+.endif
+
+MANLINKS=	[ false pwd sh sleep test true
+BINLINKS=	${MANLINKS} echo domainname kill
+.for _i in ${BINLINKS}
+LINKS+=		${BINDIR}/${PROG} ${BINDIR}/${_i}
+.endfor
+.for _i in ${MANLINKS}
+MLINKS+=	${PROG}.1 ${_i}.1
+.endfor
+
+regress: ${PROG} check.pl check.t
+	-rm -rf regress-dir
+	mkdir -p regress-dir
+	echo export FNORD=666 >regress-dir/.mkshrc
+	HOME=$$(realpath regress-dir) perl ${.CURDIR}/check.pl \
+	    -s ${.CURDIR}/check.t -v -p ./${PROG}
+
+test-build: .PHONY
+	-rm -rf build-dir
+	mkdir -p build-dir
+.if ${USE_PRINTF_BUILTIN} == 1
+	cp ${BSDSRCDIR}/usr.bin/printf/printf.c build-dir/
+.endif
+	cd build-dir; env CC=${CC:Q} CFLAGS=${CFLAGS:M*:Q} \
+	    CPPFLAGS=${CPPFLAGS:M*:Q} LDFLAGS=${LDFLAGS:M*:Q} \
+	    LIBS= NOWARN=-Wno-error TARGET_OS= CPP= /bin/sh \
+	    ${.CURDIR}/Build.sh -Q -r && ./test.sh -v
+
+cleandir: clean-extra
+
+clean-extra: .PHONY
+	-rm -rf build-dir regress-dir printf.o printf.ln
+
+distribution:
+	sed 's!\$$I''d\([:$$]\)!$$M''irSecuCron\1!g' \
+	    ${.CURDIR}/dot.mkshrc >${DESTDIR}/etc/skel/.mkshrc
+	chown ${BINOWN}:${CONFGRP} ${DESTDIR}/etc/skel/.mkshrc
+	chmod 0644 ${DESTDIR}/etc/skel/.mkshrc
+
+.include <bsd.prog.mk>
diff --git a/src/check.pl b/src/check.pl
index e793e95..7dfab36 100644
--- a/src/check.pl
+++ b/src/check.pl
@@ -1,7 +1,7 @@
-# $MirOS: src/bin/mksh/check.pl,v 1.23 2009/06/10 18:12:43 tg Rel $
+# $MirOS: src/bin/mksh/check.pl,v 1.27 2011/05/29 02:18:47 tg Exp $
 # $OpenBSD: th,v 1.13 2006/05/18 21:27:23 miod Exp $
 #-
-# Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009
+# Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2011
 #	Thorsten Glaser <tg@mirbsd.org>
 #
 # Provided that these terms and disclaimer and all copyright notices
@@ -71,7 +71,8 @@
 #					environment. Programs are run with
 #					the following minimal environment:
 #					    HOME, LD_LIBRARY_PATH, LOCPATH,
-#					    LOGNAME, PATH, SHELL, USER
+#					    LOGNAME, PATH, SHELL, UNIXMODE,
+#					    USER
 #					(values taken from the environment of
 #					the test harness).
 #					ENV is set to /nonexistant.
@@ -135,6 +136,8 @@
 #					One category os:XXX is predefined
 #					(XXX is the operating system name,
 #					eg, linux, dec_osf).
+#	need-ctty			'yes' if the test needs a ctty, run
+#					with -C regress:no-ctty to disable.
 # Flag meanings:
 #	r	tag is required (eg, a test must have a name tag).
 #	m	value can be multiple lines. Lines must be prefixed with
@@ -156,19 +159,19 @@
 ($prog = $0) =~ s#.*/##;
 
 $Usage = <<EOF ;
-Usage: $prog [-s test-set] [-C category] [-p prog] [-v] [-e e=v] name ...
-	-p p	Use p as the program to test
+Usage: $prog [-Pv] [-C cat] [-e e=v] [-p prog] [-s fn] [-t tmo] name ...
 	-C c	Specify the comma separated list of categories the program
 		belongs to (see category field).
-	-s s	Read tests from file s; if s is a directory, it is recursively
-		scaned for test files (which end in .t).
-	-t t	Use t as default time limit for tests (default is unlimited)
-	-P	program (-p) string has multiple words, and the program is in
-		the path (kludge option)
-	-v	Verbose mode: print reason test failed.
 	-e e=v	Set the environment variable e to v for all tests
 		(if no =v is given, the current value is used)
 		Only one -e option can be given at the moment, sadly.
+	-P	program (-p) string has multiple words, and the program is in
+		the path (kludge option)
+	-p p	Use p as the program to test
+	-s s	Read tests from file s; if s is a directory, it is recursively
+		scaned for test files (which end in .t).
+	-t t	Use t as default time limit for tests (default is unlimited)
+	-v	Verbose mode: print reason test failed.
 	name	specifies the name of the test(s) to run; if none are
 		specified, all tests are run.
 EOF
@@ -193,6 +196,8 @@
 	'expected-stderr',		'm',
 	'expected-stderr-pattern',	'm',
 	'category',			'm',
+	'need-ctty',			'',
+	'need-pass',			'',
 	);
 # Filled in by read_test()
 %internal_test_fields = (
@@ -213,13 +218,14 @@
 $tempdir = "/tmp/rtd$$";
 
 $nfailed = 0;
+$nifailed = 0;
 $nxfailed = 0;
 $npassed = 0;
 $nxpassed = 0;
 
 %known_tests = ();
 
-if (!getopts('C:p:Ps:t:ve:')) {
+if (!getopts('C:e:Pp:s:t:v')) {
     print STDERR $Usage;
     exit 1;
 }
@@ -253,7 +259,7 @@
 # Set up a very minimal environment
 %new_env = ();
 foreach $env (('HOME', 'LD_LIBRARY_PATH', 'LOCPATH', 'LOGNAME',
-  'PATH', 'SHELL', 'USER')) {
+  'PATH', 'SHELL', 'UNIXMODE', 'USER')) {
     $new_env{$env} = $ENV{$env} if defined $ENV{$env};
 }
 $new_env{'ENV'} = '/nonexistant';
@@ -300,12 +306,13 @@
 }
 &cleanup_exit() if !defined $ret;
 
-$tot_failed = $nfailed + $nxfailed;
+$tot_failed = $nfailed + $nifailed + $nxfailed;
 $tot_passed = $npassed + $nxpassed;
 if ($tot_failed || $tot_passed) {
     print "Total failed: $tot_failed";
+    print " ($nifailed ignored)" if $nifailed;
     print " ($nxfailed unexpected)" if $nxfailed;
-    print " (as expected)" if $nfailed && !$nxfailed;
+    print " (as expected)" if $nfailed && !$nxfailed && !$nifailed;
     print "\nTotal passed: $tot_passed";
     print " ($nxpassed unexpected)" if $nxpassed;
     print "\n";
@@ -319,7 +326,11 @@
     local($sig, $exitcode) = ('', 1);
 
     if ($_[0] eq 'ok') {
-	$exitcode = 0;
+	unless ($nxfailed) {
+		$exitcode = 0;
+	} else {
+		$exitcode = 1;
+	}
     } elsif ($_[0] ne '') {
 	$sig = $_[0];
     }
@@ -616,8 +627,13 @@
 
     if ($failed) {
 	if (!$test{'expected-fail'}) {
-	    print "FAIL $name\n";
-	    $nxfailed++;
+	    if ($test{'need-pass'}) {
+		print "FAIL $name\n";
+		$nxfailed++;
+	    } else {
+		print "FAIL $name (ignored)\n";
+		$nifailed++;
+	    }
 	} else {
 	    print "fail $name (as expected)\n";
 	    $nfailed++;
@@ -642,6 +658,7 @@
     local(*test) = @_;
     local($c);
 
+    return 0 if ($test{'need-ctty'} && defined $categories{'regress:no-ctty'});
     return 1 if (!defined $test{'category'});
     local($ok) = 0;
     foreach $c (split(',', $test{'category'})) {
@@ -1064,6 +1081,26 @@
     } else {
 	$test{'expected-fail'} = 0;
     }
+    if (defined $test{'need-ctty'}) {
+	if ($test{'need-ctty'} !~ /^(yes|no)$/) {
+	    print STDERR
+	      "$prog:$test{':long-name'}: bad value for need-ctty field\n";
+	    return undef;
+	}
+	$test{'need-ctty'} = $1 eq 'yes';
+    } else {
+	$test{'need-ctty'} = 0;
+    }
+    if (defined $test{'need-pass'}) {
+	if ($test{'need-pass'} !~ /^(yes|no)$/) {
+	    print STDERR
+	      "$prog:$test{':long-name'}: bad value for need-pass field\n";
+	    return undef;
+	}
+	$test{'need-pass'} = $1 eq 'yes';
+    } else {
+	$test{'need-pass'} = 1;
+    }
     if (defined $test{'arguments'}) {
 	local($firstc) = substr($test{'arguments'}, 0, 1);
 
diff --git a/src/check.t b/src/check.t
index c8e8caf..8df403a 100644
--- a/src/check.t
+++ b/src/check.t
@@ -1,9 +1,9 @@
-# $MirOS: src/bin/mksh/check.t,v 1.388 2010/08/24 15:47:44 tg Exp $
+# $MirOS: src/bin/mksh/check.t,v 1.483 2011/10/07 19:51:42 tg Exp $
 # $OpenBSD: bksl-nl.t,v 1.2 2001/01/28 23:04:56 niklas Exp $
 # $OpenBSD: history.t,v 1.5 2001/01/28 23:04:56 niklas Exp $
 # $OpenBSD: read.t,v 1.3 2003/03/10 03:48:16 david Exp $
 #-
-# Copyright © 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+# Copyright © 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
 #	Thorsten Glaser <tg@mirbsd.org>
 #
 # Provided that these terms and disclaimer and all copyright notices
@@ -25,7 +25,7 @@
 # http://www.research.att.com/~gsf/public/ifs.sh
 
 expected-stdout:
-	@(#)MIRBSD KSH R39 2010/08/24
+	@(#)MIRBSD KSH R40 2011/10/07
 description:
 	Check version of shell.
 stdin:
@@ -65,6 +65,16 @@
 stdin:
 	set
 ---
+name: selftest-direct-builtin-call
+description:
+	Check that direct builtin calls work
+stdin:
+	ln -s "$__progname" cat
+	ln -s "$__progname" echo
+	./echo -c 'echo  foo' | ./cat -u
+expected-stdout:
+	-c echo  foo
+---
 name: alias-1
 description:
 	Check that recursion is detected/avoided in aliases.
@@ -346,7 +356,7 @@
 ---
 name: bksl-nl-ign-1
 description:
-	Check that \newline is not collasped after #
+	Check that \newline is not collapsed after #
 stdin:
 	echo hi #there \
 	echo folks
@@ -356,7 +366,7 @@
 ---
 name: bksl-nl-ign-2
 description:
-	Check that \newline is not collasped inside single quotes
+	Check that \newline is not collapsed inside single quotes
 stdin:
 	echo 'hi \
 	there'
@@ -368,7 +378,7 @@
 ---
 name: bksl-nl-ign-3
 description:
-	Check that \newline is not collasped inside single quotes
+	Check that \newline is not collapsed inside single quotes
 stdin:
 	cat << \EOF
 	hi \
@@ -418,7 +428,7 @@
 #
 name: bksl-nl-1
 description:
-	Check that \newline is collasped before, in the middle of, and
+	Check that \newline is collapsed before, in the middle of, and
 	after words
 stdin:
 	 	 	\
@@ -430,7 +440,7 @@
 ---
 name: bksl-nl-2
 description:
-	Check that \newline is collasped in $ sequences
+	Check that \newline is collapsed in $ sequences
 	(ksh93 fails this)
 stdin:
 	a=12
@@ -454,7 +464,7 @@
 ---
 name: bksl-nl-3
 description:
-	Check that \newline is collasped in $(..) and `...` sequences
+	Check that \newline is collapsed in $(..) and `...` sequences
 	(ksh93 fails this)
 stdin:
 	echo $\
@@ -479,7 +489,7 @@
 ---
 name: bksl-nl-4
 description:
-	Check that \newline is collasped in $((..)) sequences
+	Check that \newline is collapsed in $((..)) sequences
 	(ksh93 fails this)
 stdin:
 	echo $\
@@ -501,7 +511,7 @@
 ---
 name: bksl-nl-5
 description:
-	Check that \newline is collasped in double quoted strings
+	Check that \newline is collapsed in double quoted strings
 stdin:
 	echo "\
 	hi"
@@ -516,7 +526,7 @@
 ---
 name: bksl-nl-6
 description:
-	Check that \newline is collasped in here document delimiters
+	Check that \newline is collapsed in here document delimiters
 	(ksh93 fails second part of this)
 stdin:
 	a=12
@@ -539,7 +549,7 @@
 ---
 name: bksl-nl-7
 description:
-	Check that \newline is collasped in double-quoted here-document
+	Check that \newline is collapsed in double-quoted here-document
 	delimiter.
 stdin:
 	a=12
@@ -558,7 +568,7 @@
 ---
 name: bksl-nl-8
 description:
-	Check that \newline is collasped in various 2+ character tokens
+	Check that \newline is collapsed in various 2+ character tokens
 	delimiter.
 	(ksh93 fails this)
 stdin:
@@ -995,6 +1005,45 @@
 	  1 /bin
 	  0 /tmp
 ---
+name: cd-pe
+description:
+	Check package for cd -Pe
+need-pass: no
+# the mv command fails on Cygwin
+category: !os:cygwin
+file-setup: file 644 "x"
+	mkdir noread noread/target noread/target/subdir
+	ln -s noread link
+	chmod 311 noread
+	cd -P$1 .
+	echo 0=$?
+	bwd=$PWD
+	cd -P$1 link/target
+	echo 1=$?,${PWD#$bwd/}
+	epwd=$($TSHELL -c pwd 2>/dev/null)
+	# This unexpectedly succeeds on GNU/Linux and MidnightBSD
+	#echo pwd=$?,$epwd
+	# expect:	pwd=1,
+	mv ../../noread ../../renamed
+	cd -P$1 subdir
+	echo 2=$?,${PWD#$bwd/}
+	cd $bwd
+	chmod 755 renamed
+	rm -rf noread link renamed
+stdin:
+	export TSHELL="$__progname"
+	"$__progname" x
+	echo "now with -e:"
+	"$__progname" x e
+expected-stdout:
+	0=0
+	1=0,noread/target
+	2=0,noread/target/subdir
+	now with -e:
+	0=0
+	1=0,noread/target
+	2=1,noread/target/subdir
+---
 name: env-prompt
 description:
 	Check that prompt not printed when processing ENV
@@ -1003,6 +1052,7 @@
 	XXX=_
 	PS1=X
 	false && echo hmmm
+need-ctty: yes
 arguments: !-i!
 stdin:
 	echo hi${XXX}there
@@ -1360,6 +1410,56 @@
 	9 EQAL brac foo x c x} baz
 	9 QSTN brac foo x c x} baz
 ---
+name: expand-threecolons-dblq
+description:
+	Check for a particular thing that used to segfault
+stdin:
+	TEST=1234
+	echo "${TEST:1:2:3}"
+	echo $? but still living
+expected-stderr-pattern:
+	/bad substitution/
+expected-exit: 1
+---
+name: expand-threecolons-unq
+description:
+	Check for a particular thing that used to not error out
+stdin:
+	TEST=1234
+	echo ${TEST:1:2:3}
+	echo $? but still living
+expected-stderr-pattern:
+	/bad substitution/
+expected-exit: 1
+---
+name: expand-weird-1
+description:
+	Check corner case of trim expansion vs. $# vs. ${#var}
+stdin:
+	set 1 2 3 4 5 6 7 8 9 10 11
+	echo ${#}	# value of $#
+	echo ${##}	# length of $#
+	echo ${##1}	# $# trimmed 1
+	set 1 2 3 4 5 6 7 8 9 10 11 12
+	echo ${##1}
+expected-stdout:
+	11
+	2
+	1
+	2
+---
+name: expand-weird-2
+description:
+	Check corner case of ${var?} vs. ${#var}
+stdin:
+	(exit 0)
+	echo $? = ${#?} .
+	(exit 111)
+	echo $? = ${#?} .
+expected-stdout:
+	0 = 1 .
+	111 = 3 .
+---
 name: eglob-bad-1
 description:
 	Check that globbing isn't done when glob has syntax error
@@ -1495,6 +1595,27 @@
 	3: abcdef
 	4: cdef
 ---
+name: eglob-trim-3
+description:
+	Check eglobbing works in trims, for Korn Shell
+	Ensure eglobbing does not work for reduced-feature /bin/sh
+stdin:
+	set +o sh
+	x=foobar
+	y=foobaz
+	z=fooba\?
+	echo "<${x%bar|baz},${y%bar|baz},${z%\?}>"
+	echo "<${x%ba(r|z)},${y%ba(r|z)}>"
+	set -o sh
+	echo "<${x%bar|baz},${y%bar|baz},${z%\?}>"
+	z='foo(bar'
+	echo "<${z%(*}>"
+expected-stdout:
+	<foo,foo,fooba>
+	<foo,foo>
+	<foobar,foobaz,fooba>
+	<foo>
+---
 name: eglob-substrpl-1
 description:
 	Check eglobbing works in substs... and they work at all
@@ -1742,6 +1863,8 @@
 name: glob-bad-2
 description:
 	Check that symbolic links aren't stat()'d
+# breaks on FreeMiNT (cannot unlink dangling symlinks)
+category: !os:mint
 file-setup: dir 755 "dir"
 file-setup: symlink 644 "dir/abc"
 	non-existent-file
@@ -1787,7 +1910,8 @@
 description:
 	Check that globbing matches the right things...
 # breaks on Mac OSX (HFS+ non-standard Unicode canonical decomposition)
-category: !os:darwin
+# breaks on Cygwin 1.7 (files are now UTF-16 or something)
+category: !os:cygwin,!os:darwin
 file-setup: file 644 "aÂc"
 stdin:
 	echo a[Á-Ú]*
@@ -2007,6 +2131,230 @@
 expected-stdout:
 	one
 ---
+name: heredoc-9e
+description:
+	Check here string related regression with multiple iops
+stdin:
+	echo $(tr r z <<<'bar' 2>&-)
+expected-stdout:
+	baz
+---
+name: heredoc-10
+description:
+	Check direct here document assignment
+stdin:
+	x=u
+	va=<<EOF
+	=a $x \x40=
+	EOF
+	vb=<<'EOF'
+	=b $x \x40=
+	EOF
+	function foo {
+		vc=<<-EOF
+			=c $x \x40=
+		EOF
+	}
+	typeset -f foo
+	foo
+	# rather nonsensical, but…
+	vd=<<<"=d $x \x40="
+	ve=<<<'=e $x \x40='
+	vf=<<<$'=f $x \x40='
+	# now check
+	print -r -- "| va={$va} vb={$vb} vc={$vc} vd={$vd} ve={$ve} vf={$vf} |"
+expected-stdout:
+	function foo {
+		vc= <<-EOF 
+	=c $x \x40=
+	EOF
+	
+	} 
+	| va={=a u \x40=
+	} vb={=b $x \x40=
+	} vc={=c u \x40=
+	} vd={=d u \x40=
+	} ve={=e $x \x40=
+	} vf={=f $x @=
+	} |
+---
+name: heredoc-11
+description:
+	Check here documents with no or empty delimiter
+stdin:
+	x=u
+	va=<<
+	=a $x \x40=
+	<<
+	vb=<<''
+	=b $x \x40=
+	
+	function foo {
+		vc=<<-
+			=c $x \x40=
+		<<
+		vd=<<-''
+			=d $x \x40=
+	
+	}
+	typeset -f foo
+	foo
+	print -r -- "| va={$va} vb={$vb} vc={$vc} vd={$vd} |"
+expected-stdout:
+	function foo {
+		vc= <<- 
+	=c $x \x40=
+	<<
+	
+		vd= <<-"" 
+	=d $x \x40=
+	
+	
+	} 
+	| va={=a u \x40=
+	} vb={=b $x \x40=
+	} vc={=c u \x40=
+	} vd={=d $x \x40=
+	} |
+---
+name: heredoc-comsub-1
+description:
+	Tests for here documents in COMSUB, taken from Austin ML
+stdin:
+	text=$(cat <<EOF
+	here is the text
+	EOF)
+	echo = $text =
+expected-stdout:
+	= here is the text =
+---
+name: heredoc-comsub-2
+description:
+	Tests for here documents in COMSUB, taken from Austin ML
+stdin:
+	unbalanced=$(cat <<EOF
+	this paren ) is a problem
+	EOF)
+	echo = $unbalanced =
+expected-stdout:
+	= this paren ) is a problem =
+---
+name: heredoc-comsub-3
+description:
+	Tests for here documents in COMSUB, taken from Austin ML
+stdin:
+	balanced=$(cat <<EOF
+	these parens ( ) are not a problem
+	EOF)
+	echo = $balanced =
+expected-stdout:
+	= these parens ( ) are not a problem =
+---
+name: heredoc-comsub-4
+description:
+	Tests for here documents in COMSUB, taken from Austin ML
+stdin:
+	balanced=$(cat <<EOF
+	these parens \( ) are a problem
+	EOF)
+	echo = $balanced =
+expected-stdout:
+	= these parens \( ) are a problem =
+---
+name: heredoc-subshell-1
+description:
+	Tests for here documents in subshells, taken from Austin ML
+stdin:
+	(cat <<EOF
+	some text
+	EOF)
+	echo end
+expected-stdout:
+	some text
+	end
+---
+name: heredoc-subshell-2
+description:
+	Tests for here documents in subshells, taken from Austin ML
+stdin:
+	(cat <<EOF
+	some text
+	EOF
+	)
+	echo end
+expected-stdout:
+	some text
+	end
+---
+name: heredoc-subshell-3
+description:
+	Tests for here documents in subshells, taken from Austin ML
+stdin:
+	(cat <<EOF; )
+	some text
+	EOF
+	echo end
+expected-stdout:
+	some text
+	end
+---
+name: heredoc-weird-1
+description:
+	Tests for here documents, taken from Austin ML
+	Documents current state in mksh, *NOT* necessarily correct!
+stdin:
+	cat <<END
+	hello
+	END\
+	END
+	END
+	echo end
+expected-stdout:
+	hello
+	ENDEND
+	end
+---
+name: heredoc-weird-2
+description:
+	Tests for here documents, taken from Austin ML
+stdin:
+	cat <<'    END    '
+	hello
+	    END    
+	echo end
+expected-stdout:
+	hello
+	end
+---
+name: heredoc-weird-4
+description:
+	Tests for here documents, taken from Austin ML
+	Documents current state in mksh, *NOT* necessarily correct!
+stdin:
+	cat <<END
+	hello\
+	END
+	END
+	echo end
+expected-stdout:
+	helloEND
+	end
+---
+name: heredoc-weird-5
+description:
+	Tests for here documents, taken from Austin ML
+	Documents current state in mksh, *NOT* necessarily correct!
+stdin:
+	cat <<END
+	hello
+	\END
+	END
+	echo end
+expected-stdout:
+	hello
+	\END
+	end
+---
 name: heredoc-quoting-unsubst
 description:
 	Check for correct handling of quoted characters in
@@ -2188,6 +2536,7 @@
 	late. Heredoc in function, backgrounded call to function.
 	This check can fail on slow machines (<100 MHz), or Cygwin,
 	that's normal.
+need-pass: no
 stdin:
 	TMPDIR=$PWD
 	# Background eval so main shell doesn't do parsing
@@ -2215,6 +2564,7 @@
 name: history-basic
 description:
 	See if we can test history at all
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2231,6 +2581,7 @@
 name: history-dups
 description:
 	Verify duplicates and spaces are not entered
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2251,6 +2602,7 @@
 name: history-unlink
 description:
 	Check if broken HISTFILEs do not cause trouble
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=foo/hist.file!
 file-setup: file 644 "Env"
@@ -2268,11 +2620,12 @@
 	hi
 	1	echo hi
 expected-stderr-pattern:
-	/(.*cannot unlink HISTFILE.*\n)?X*$/
+	/(.*can't unlink HISTFILE.*\n)?X*$/
 ---
 name: history-e-minus-1
 description:
 	Check if more recent command is executed
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2292,6 +2645,7 @@
 description:
 	Check that repeated command is printed before command
 	is re-executed.
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2311,6 +2665,7 @@
 	fc -e - fails when there is no history
 	(ksh93 has a bug that causes this to fail)
 	(ksh88 loops on this)
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2326,6 +2681,7 @@
 name: history-e-minus-4
 description:
 	Check if "fc -e -" command output goes to stdout.
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2344,6 +2700,7 @@
 name: history-e-minus-5
 description:
 	fc is replaced in history by new command.
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2369,6 +2726,7 @@
 description:
 	List lists correct range
 	(ksh88 fails 'cause it lists the fc command)
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2392,6 +2750,7 @@
 	Lists oldest history if given pre-historic number
 	(ksh93 has a bug that causes this to fail)
 	(ksh88 fails 'cause it lists the fc command)
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2414,6 +2773,7 @@
 name: history-list-3
 description:
 	Can give number 'options' to fc
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2437,6 +2797,7 @@
 name: history-list-4
 description:
 	-1 refers to previous command
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2459,6 +2820,7 @@
 name: history-list-5
 description:
 	List command stays in history
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2485,6 +2847,7 @@
 description:
 	HISTSIZE limits about of history kept.
 	(ksh88 fails 'cause it lists the fc command)
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!HISTSIZE=3!
 file-setup: file 644 "Env"
@@ -2510,6 +2873,7 @@
 name: history-list-7
 description:
 	fc allows too old/new errors in range specification
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!HISTSIZE=3!
 file-setup: file 644 "Env"
@@ -2536,6 +2900,7 @@
 name: history-list-r-1
 description:
 	test -r flag in history
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2562,6 +2927,7 @@
 name: history-list-r-2
 description:
 	If first is newer than last, -r is implied.
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2588,6 +2954,7 @@
 name: history-list-r-3
 description:
 	If first is newer than last, -r is cancelled.
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2614,6 +2981,7 @@
 name: history-subst-1
 description:
 	Basic substitution
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2632,6 +3000,7 @@
 name: history-subst-2
 description:
 	Does subst find previous command?
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2650,6 +3019,7 @@
 name: history-subst-3
 description:
 	Does subst find previous command when no arguments given
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2669,6 +3039,7 @@
 description:
 	Global substitutions work
 	(ksh88 and ksh93 do not have -g option)
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2686,6 +3057,7 @@
 description:
 	Make sure searches don't find current (fc) command
 	(ksh88/ksh93 don't have the ? prefix thing so they fail this test)
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2707,6 +3079,8 @@
 	that prints no prompts). This is for oldish ed(1) which write
 	the character count to stdout.
 category: stdout-ed
+need-ctty: yes
+need-pass: no
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2729,6 +3103,8 @@
 description:
 	Correct command is edited when number given
 category: stdout-ed
+need-ctty: yes
+need-pass: no
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2760,6 +3136,8 @@
 	(NOTE: adjusted for COMPLEX HISTORY compile time option)
 	(ksh88 fails 'cause it lists the fc command)
 category: stdout-ed
+need-ctty: yes
+need-pass: no
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2791,6 +3169,8 @@
 	Basic (ed) editing works (assumes you have generic ed editor
 	that prints no prompts). This is for newish ed(1) and stderr.
 category: !no-stderr-ed
+need-ctty: yes
+need-pass: no
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2811,6 +3191,8 @@
 description:
 	Correct command is edited when number given
 category: !no-stderr-ed
+need-ctty: yes
+need-pass: no
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2838,6 +3220,8 @@
 	Newly created multi line commands show up as single command
 	in history.
 category: !no-stderr-ed
+need-ctty: yes
+need-pass: no
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2943,23 +3327,6 @@
 expected-stdout:
 	 <:>
 ---
-name: IFS-space-colon-3
-description:
-	Simple test, IFS=<white-space>:
-	pdksh fails both of these tests
-	not sure whether #2 is correct
-stdin:
-	showargs() { for i; do echo -n " <$i>"; done; echo; }
-	IFS="$IFS:"
-	x=
-	set --
-	showargs "$x$@" 1
-	showargs "$@$x" 2
-expected-fail: yes
-expected-stdout:
-	 <> <1>
-	 <> <2>
----
 name: IFS-space-colon-4
 description:
 	Simple test, IFS=<white-space>:
@@ -3051,6 +3418,7 @@
 	Syntax errors in expressions and effects on bases
 	(interactive so errors don't cause exits)
 	(ksh88 fails this test - shell exits, even with -i)
+need-ctty: yes
 arguments: !-i!
 stdin:
 	PS1= # minimise prompt hassles
@@ -3346,6 +3714,26 @@
 	line <6>
 expected-exit: 1
 ---
+name: unknown-trap
+description:
+	Ensure unknown traps are not a syntax error
+stdin:
+	(
+	trap "echo trap 1 executed" UNKNOWNSIGNAL || echo "foo"
+	echo =1
+	trap "echo trap 2 executed" UNKNOWNSIGNAL EXIT 999999 FNORD
+	echo = $?
+	) 2>&1 | sed "s^${__progname}: <stdin>\[[0-9]*]PROG"
+expected-stdout:
+	PROG: trap: bad signal 'UNKNOWNSIGNAL'
+	foo
+	=1
+	PROG: trap: bad signal 'UNKNOWNSIGNAL'
+	PROG: trap: bad signal '999999'
+	PROG: trap: bad signal 'FNORD'
+	= 3
+	trap 2 executed
+---
 name: read-IFS-1
 description:
 	Simple test, default IFS
@@ -3372,6 +3760,74 @@
 expected-stdout:
 	[abc]
 ---
+name: read-regress-1
+description:
+	Check a regression of read
+file-setup: file 644 "foo"
+	foo bar
+	baz
+	blah
+stdin:
+	while read a b c; do
+		read d
+		break
+	done <foo
+	echo "<$a|$b|$c><$d>"
+expected-stdout:
+	<foo|bar|><baz>
+---
+name: read-delim-1
+description:
+	Check read with delimiters
+stdin:
+	emit() {
+		printf 'foo bar\tbaz\nblah \0blub\tblech\nmyok meck \0'
+	}
+	emit | while IFS= read -d "" foo; do print -r -- "<$foo>"; done
+	emit | while read -d "" foo; do print -r -- "<$foo>"; done
+	emit | while read -d "eh?" foo; do print -r -- "<$foo>"; done
+expected-stdout:
+	<foo bar	baz
+	blah >
+	<blub	blech
+	myok meck >
+	<foo bar	baz
+	blah>
+	<blub	blech
+	myok meck>
+	<foo bar	baz
+	blah blub	bl>
+	<ch
+	myok m>
+---
+name: read-ext-1
+description:
+	Check read with number of bytes specified, and -A
+stdin:
+	print 'foo\nbar' >x1
+	print -n x >x2
+	print 'foo\\ bar baz' >x3
+	x1a=u; read x1a <x1
+	x1b=u; read -N-1 x1b <x1
+	x2a=u; read x2a <x2; r2a=$?
+	x2b=u; read -N2 x2c <x2; r2b=$?
+	x2c=u; read -n2 x2c <x2; r2c=$?
+	x3a=u; read -A x3a <x3
+	print -r "x1a=<$x1a>"
+	print -r "x1b=<$x1b>"
+	print -r "x2a=$r2a<$x2a>"
+	print -r "x2b=$r2b<$x2b>"
+	print -r "x2c=$r2c<$x2c>"
+	print -r "x3a=<${x3a[0]}|${x3a[1]}|${x3a[2]}>"
+expected-stdout:
+	x1a=<foo>
+	x1b=<foo
+	bar>
+	x2a=1<x>
+	x2b=1<u>
+	x2c=0<x>
+	x3a=<foo bar|baz|>
+---
 name: regression-1
 description:
 	Lex array code had problems with this.
@@ -3782,6 +4238,9 @@
 description:
 	Does umask print a leading 0 when umask is 3 digits?
 stdin:
+	# on MiNT, the first umask call seems to fail
+	umask 022
+	# now, the test proper
 	umask 222
 	umask
 expected-stdout:
@@ -4006,6 +4465,7 @@
 	PS1=Y
 	PS2=X
 env-setup: !ENV=./env!
+need-ctty: yes
 arguments: !-i!
 stdin:
 	alias foo='echo hi ; '
@@ -4036,6 +4496,7 @@
 file-setup: file 644 "abc"
 	stuff
 env-setup: !ENV=./env!
+need-ctty: yes
 arguments: !-i!
 stdin:
 	sed 's/^/X /' < ab*
@@ -4369,6 +4830,78 @@
 		time
 	}
 ---
+name: regression-65
+description:
+	check for a regression with sleep builtin and signal mask
+category: !nojsig
+time-limit: 3
+stdin:
+	sleep 1
+	echo blub |&
+	while read -p line; do :; done
+	echo ok
+expected-stdout:
+	ok
+---
+name: readonly-0
+description:
+	Ensure readonly is honoured for assignments and unset
+stdin:
+	"$__progname" -c 'u=x; echo $? $u .' || echo aborted, $?
+	echo =
+	"$__progname" -c 'readonly u; u=x; echo $? $u .' || echo aborted, $?
+	echo =
+	"$__progname" -c 'u=x; readonly u; unset u; echo $? $u .' || echo aborted, $?
+expected-stdout:
+	0 x .
+	=
+	aborted, 2
+	=
+	1 x .
+expected-stderr-pattern:
+	/read *only/
+---
+name: readonly-1
+description:
+	http://austingroupbugs.net/view.php?id=367 for export
+stdin:
+	"$__progname" -c 'readonly foo; export foo=a; echo $?' || echo aborted, $?
+expected-stdout:
+	aborted, 2
+expected-stderr-pattern:
+	/read *only/
+---
+name: readonly-2a
+description:
+	Check that getopts works as intended, for readonly-2b to be valid
+stdin:
+	"$__progname" -c 'set -- -a b; getopts a c; echo $? $c .; getopts a c; echo $? $c .' || echo aborted, $?
+expected-stdout:
+	0 a .
+	1 ? .
+---
+name: readonly-2b
+description:
+	http://austingroupbugs.net/view.php?id=367 for getopts
+stdin:
+	"$__progname" -c 'readonly c; set -- -a b; getopts a c; echo $? $c .' || echo aborted, $?
+expected-stdout:
+	2 .
+expected-stderr-pattern:
+	/read *only/
+---
+name: readonly-3
+description:
+	http://austingroupbugs.net/view.php?id=367 for read
+stdin:
+	echo x | "$__progname" -c 'read s; echo $? $s .' || echo aborted, $?
+	echo y | "$__progname" -c 'readonly s; read s; echo $? $s .' || echo aborted, $?
+expected-stdout:
+	0 x .
+	2 .
+expected-stderr-pattern:
+	/read *only/
+---
 name: syntax-1
 description:
 	Check that lone ampersand is a syntax error
@@ -4567,6 +5100,7 @@
 name: xxx-exec-1
 description:
 	Check that exec exits for built-ins
+need-ctty: yes
 arguments: !-i!
 stdin:
 	exec echo hi
@@ -4614,6 +5148,7 @@
 name: xxx-status-1
 description:
 	Check that blank lines don't clear $?
+need-ctty: yes
 arguments: !-i!
 stdin:
 	(exit 1)
@@ -4702,12 +5237,12 @@
 	Check some "exit on error" conditions
 stdin:
 	set -ex
-	/usr/bin/env false && echo something
+	env false && echo something
 	echo END
 expected-stdout:
 	END
 expected-stderr:
-	+ /usr/bin/env false
+	+ env false
 	+ echo END
 ---
 name: exit-err-2
@@ -4715,15 +5250,15 @@
 	Check some "exit on error" edge conditions (POSIXly)
 stdin:
 	set -ex
-	if /usr/bin/env true; then
-		/usr/bin/env false && echo something
+	if env true; then
+		env false && echo something
 	fi
 	echo END
 expected-stdout:
 	END
 expected-stderr:
-	+ /usr/bin/env true
-	+ /usr/bin/env false
+	+ env true
+	+ env false
 	+ echo END
 ---
 name: exit-err-3
@@ -4845,6 +5380,16 @@
 	E 0
 	F 0
 ---
+name: exit-trap-1
+description:
+	Check that "exit" with no arguments behaves SUSv4 conformant.
+stdin:
+	trap 'echo hi; exit' EXIT
+	exit 9
+expected-stdout:
+	hi
+expected-exit: 9
+---
 name: test-stlt-1
 description:
 	Check that test also can handle string1 < string2 etc.
@@ -4981,6 +5526,7 @@
 	Part 2: verify mkshrc can be read (interactive shells)
 file-setup: file 644 ".mkshrc"
 	FNORD=42
+need-ctty: yes
 arguments: !-i!
 env-setup: !HOME=.!ENV=!PS1=!
 stdin:
@@ -5104,6 +5650,7 @@
 name: pipeline-2
 description:
 	check that co-processes work with TCOMs, TPIPEs and TPARENs
+category: !nojsig
 stdin:
 	"$__progname" -c 'i=100; echo hi |& while read -p line; do echo "$((i++)) $line"; done'
 	"$__progname" -c 'i=200; echo hi | cat |& while read -p line; do echo "$((i++)) $line"; done'
@@ -5113,10 +5660,33 @@
 	200 hi
 	300 hi
 ---
+name: pipeline-3
+description:
+	Check that PIPESTATUS does what it's supposed to
+stdin:
+	echo 1 $PIPESTATUS .
+	echo 2 ${PIPESTATUS[0]} .
+	echo 3 ${PIPESTATUS[1]} .
+	(echo x; exit 12) | (cat; exit 23) | (cat; exit 42)
+	echo 5 $? , $PIPESTATUS , ${PIPESTATUS[0]} , ${PIPESTATUS[1]} , ${PIPESTATUS[2]} , ${PIPESTATUS[3]} .
+	echo 6 ${PIPESTATUS[0]} .
+	set | fgrep PIPESTATUS
+	echo 8 $(set | fgrep PIPESTATUS) .
+expected-stdout:
+	1 0 .
+	2 0 .
+	3 .
+	x
+	5 42 , 12 , 12 , 23 , 42 , .
+	6 0 .
+	PIPESTATUS[0]=0
+	8 PIPESTATUS[0]=0 PIPESTATUS[1]=0 .
+---
 name: persist-history-1
 description:
 	Check if persistent history saving works
 category: !no-histfile
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -5128,6 +5698,40 @@
 expected-stderr-pattern:
 	/^X*$/
 ---
+name: typeset-1
+description:
+	Check that global does what typeset is supposed to do
+stdin:
+	set -A arrfoo 65
+	foo() {
+		global -Uui16 arrfoo[*]
+	}
+	echo before ${arrfoo[0]} .
+	foo
+	echo after ${arrfoo[0]} .
+	set -A arrbar 65
+	bar() {
+		echo inside before ${arrbar[0]} .
+		arrbar[0]=97
+		echo inside changed ${arrbar[0]} .
+		global -Uui16 arrbar[*]
+		echo inside typeset ${arrbar[0]} .
+		arrbar[0]=48
+		echo inside changed ${arrbar[0]} .
+	}
+	echo before ${arrbar[0]} .
+	bar
+	echo after ${arrbar[0]} .
+expected-stdout:
+	before 65 .
+	after 16#41 .
+	before 65 .
+	inside before 65 .
+	inside changed 97 .
+	inside typeset 16#61 .
+	inside changed 16#30 .
+	after 16#30 .
+---
 name: typeset-padding-1
 description:
 	Check if left/right justification works as per TFM
@@ -5208,7 +5812,7 @@
 	mit
 	ohne
 	=
-	: mit
+	: ohne
 ---
 name: utf8bom-2
 description:
@@ -5216,6 +5820,7 @@
 	XXX if the OS can already execute them, we lose
 	note: cygwin execve(2) doesn't return to us with ENOEXEC, we lose
 	note: Ultrix perl5 t4 returns 65280 (exit-code 255) and no text
+need-pass: no
 category: !os:cygwin,!os:uwin-nt,!os:ultrix,!smksh
 env-setup: !FOO=BAR!
 stdin:
@@ -5239,12 +5844,17 @@
 name: utf8bom-3
 description:
 	Reading the UTF-8 BOM should enable the utf8-mode flag
+	(temporarily for COMSUBs)
 stdin:
 	"$__progname" -c ':; if [[ $- = *U* ]]; then echo 1 on; else echo 1 off; fi'
 	"$__progname" -c ':; if [[ $- = *U* ]]; then echo 2 on; else echo 2 off; fi'
+	"$__progname" -c 'if [[ $- = *U* ]]; then echo 3 on; else echo 3 off; fi; x=$(:; if [[ $- = *U* ]]; then echo 4 on; else echo 4 off; fi); echo $x; if [[ $- = *U* ]]; then echo 5 on; else echo 5 off; fi'
 expected-stdout:
 	1 off
 	2 on
+	3 off
+	4 on
+	5 off
 ---
 name: utf8opt-1a
 description:
@@ -5281,7 +5891,9 @@
 	-DMKSH_ASSUME_UTF8=1 => not expected, please investigate
 	-UMKSH_ASSUME_UTF8 => not expected, but if your OS is old,
 	 try passing HAVE_SETLOCALE_CTYPE=0 to Build.sh
+need-pass: no
 category: !os:hpux
+need-ctty: yes
 arguments: !-i!
 env-setup: !PS1=!PS2=!LC_CTYPE=en_US.UTF-8!
 stdin:
@@ -5300,6 +5912,7 @@
 	Check that the utf8-mode flag is set at interactive startup
 	Expected failure if -DMKSH_ASSUME_UTF8=0
 category: os:hpux
+need-ctty: yes
 arguments: !-i!
 env-setup: !PS1=!PS2=!LC_CTYPE=en_US.utf8!
 stdin:
@@ -5313,29 +5926,40 @@
 expected-stderr-pattern:
 	/(# )*/
 ---
-name: utf8opt-3
+name: utf8opt-3a
 description:
 	Ensure ±U on the command line is honoured
-	(this test may pass falsely depending on CPPFLAGS)
+	(these two tests may pass falsely depending on CPPFLAGS)
 stdin:
 	export i=0
 	code='if [[ $- = *U* ]]; then echo $i on; else echo $i off; fi'
 	let i++; "$__progname" -U -c "$code"
 	let i++; "$__progname" +U -c "$code"
+	echo $((++i)) done
+expected-stdout:
+	1 on
+	2 off
+	3 done
+---
+name: utf8opt-3b
+description:
+	Ensure ±U on the command line is honoured, interactive shells
+need-ctty: yes
+stdin:
+	export i=0
+	code='if [[ $- = *U* ]]; then echo $i on; else echo $i off; fi'
 	let i++; "$__progname" -U -ic "$code"
 	let i++; "$__progname" +U -ic "$code"
 	echo $((++i)) done
 expected-stdout:
 	1 on
 	2 off
-	3 on
-	4 off
-	5 done
+	3 done
 ---
 name: aliases-1
 description:
 	Check if built-in shell aliases are okay
-category: !arge
+category: !android,!arge
 stdin:
 	alias
 	typeset -f
@@ -5351,13 +5975,14 @@
 	nohup='nohup '
 	r='fc -e -'
 	source='PATH=$PATH:. command .'
+	stop='kill -STOP'
 	suspend='kill -STOP $$'
 	type='whence -v'
 ---
 name: aliases-1-hartz4
 description:
 	Check if built-in shell aliases are okay
-category: arge
+category: android,arge
 stdin:
 	alias
 	typeset -f
@@ -5391,7 +6016,6 @@
 description:
 	Check if running as sh disables built-in aliases (except a few)
 category: disabled
-arguments: !-o!sh!
 stdin:
 	cp "$__progname" sh
 	./sh -c 'alias; typeset -f'
@@ -5403,7 +6027,7 @@
 name: aliases-2b
 description:
 	Check if “set -o sh” does not influence built-in aliases
-category: !arge
+category: !android,!arge
 arguments: !-o!sh!
 stdin:
 	alias
@@ -5420,14 +6044,14 @@
 	nohup='nohup '
 	r='fc -e -'
 	source='PATH=$PATH:. command .'
+	stop='kill -STOP'
 	suspend='kill -STOP $$'
 	type='whence -v'
 ---
 name: aliases-3b
 description:
 	Check if running as sh does not influence built-in aliases
-category: !arge
-arguments: !-o!sh!
+category: !android,!arge
 stdin:
 	cp "$__progname" sh
 	./sh -c 'alias; typeset -f'
@@ -5444,13 +6068,14 @@
 	nohup='nohup '
 	r='fc -e -'
 	source='PATH=$PATH:. command .'
+	stop='kill -STOP'
 	suspend='kill -STOP $$'
 	type='whence -v'
 ---
 name: aliases-2b-hartz4
 description:
 	Check if “set -o sh” does not influence built-in aliases
-category: arge
+category: android,arge
 arguments: !-o!sh!
 stdin:
 	alias
@@ -5472,8 +6097,7 @@
 name: aliases-3b-hartz4
 description:
 	Check if running as sh does not influence built-in aliases
-category: arge
-arguments: !-o!sh!
+category: android,arge
 stdin:
 	cp "$__progname" sh
 	./sh -c 'alias; typeset -f'
@@ -5528,6 +6152,17 @@
 expected-stdout:
 	makro
 ---
+name: aliases-funcdef-4
+description:
+	Functions should only take over if actually being defined
+stdin:
+	alias local
+	:|| local() { :; }
+	alias local
+expected-stdout:
+	local=typeset
+	local=typeset
+---
 name: arrays-1
 description:
 	Check if Korn Shell arrays work as expected
@@ -5538,7 +6173,7 @@
 expected-stdout:
 	5|a|$v|c d|$v|b|
 ---
-name: arrays-2
+name: arrays-2a
 description:
 	Check if bash-style arrays work as expected
 category: !smksh
@@ -5549,6 +6184,33 @@
 expected-stdout:
 	5|a|$v|c d|$v|b|
 ---
+name: arrays-2b
+description:
+	Check if bash-style arrays work as expected, with newlines
+category: !smksh
+stdin:
+	test -n "$ZSH_VERSION" && setopt KSH_ARRAYS
+	v="e f"
+	foo=(a
+		bc
+		d \$v "$v" '$v' g
+	)
+	printf '%s|' "${#foo[*]}" "${foo[0]}" "${foo[1]}" "${foo[2]}" "${foo[3]}" "${foo[4]}" "${foo[5]}" "${foo[6]}"; echo
+	foo=(a\
+		bc
+		d \$v "$v" '$v' g
+	)
+	printf '%s|' "${#foo[*]}" "${foo[0]}" "${foo[1]}" "${foo[2]}" "${foo[3]}" "${foo[4]}" "${foo[5]}" "${foo[6]}"; echo
+	foo=(a\
+	bc\\
+		d \$v "$v" '$v'
+	g)
+	printf '%s|' "${#foo[*]}" "${foo[0]}" "${foo[1]}" "${foo[2]}" "${foo[3]}" "${foo[4]}" "${foo[5]}" "${foo[6]}"; echo
+expected-stdout:
+	7|a|bc|d|$v|e f|$v|g|
+	7|a|bc|d|$v|e f|$v|g|
+	6|abc\|d|$v|e f|$v|g||
+---
 name: arrays-3
 description:
 	Check if array bounds are uint32_t
@@ -5743,6 +6405,29 @@
 	2g 009 .
 	2h 00001 00002 .
 ---
+name: arrays-9a
+description:
+	Check that we can concatenate arrays
+category: !smksh
+stdin:
+	unset foo; foo=(bar); foo+=(baz); echo 1 ${!foo[*]} : ${foo[*]} .
+	unset foo; foo=(foo bar); foo+=(baz); echo 2 ${!foo[*]} : ${foo[*]} .
+	unset foo; foo=([2]=foo [0]=bar); foo+=(baz [5]=quux); echo 3 ${!foo[*]} : ${foo[*]} .
+expected-stdout:
+	1 0 1 : bar baz .
+	2 0 1 2 : foo bar baz .
+	3 0 2 3 5 : bar foo baz quux .
+---
+name: arrays-9b
+description:
+	Check that we can concatenate parameters too
+stdin:
+	unset foo; foo=bar; foo+=baz; echo 1 $foo .
+	unset foo; typeset -i16 foo=10; foo+=20; echo 2 $foo .
+expected-stdout:
+	1 barbaz .
+	2 16#a20 .
+---
 name: varexpand-substr-1
 description:
 	Check if bash-style substring expansion works
@@ -5865,6 +6550,17 @@
 	c we
 	d we
 ---
+name: varexpand-special-hash
+description:
+	Check special ${var@x} expansion for x=hash
+stdin:
+	typeset -i8 foo=10
+	bar=baz
+	unset baz
+	print ${foo@#} ${bar@#} ${baz@#} .
+expected-stdout:
+	D50219A0 20E5DB5B 00000001 .
+---
 name: varexpand-null-1
 description:
 	Ensure empty strings expand emptily
@@ -5924,8 +6620,9 @@
 	    '\U\V\W\X\Y\Z\[\\\]\^\_\`\a\b  \d\e\f\g\h\i\j\k\l\m\n\o\p' \
 	    '\q\r\s\t\u\v\w\x\y\z\{\|\}\~' '\u20acd' '\U20acd' '\x123' \
 	    '\0x' '\0123' '\01234' | {
+		# integer-base-one-3As
 		typeset -Uui16 -Z11 pos=0
-		typeset -Uui16 -Z5 hv
+		typeset -Uui16 -Z5 hv=2147483647
 		typeset -i1 wc=0x0A
 		dasc=
 		nl=${wc#1#}
@@ -5948,13 +6645,11 @@
 				line=${line:1}
 			done
 		done
-		if (( (pos & 15) != 1 )); then
-			while (( pos & 15 )); do
-				print -n '   '
-				(( (pos++ & 15) == 7 )) && print -n -- '- '
-			done
-			print "$dasc|"
-		fi
+		while (( pos & 15 )); do
+			print -n '   '
+			(( (pos++ & 15) == 7 )) && print -n -- '- '
+		done
+		(( hv == 2147483647 )) || print "$dasc|"
 	}
 expected-stdout:
 	00000000  5C 20 5C 21 5C 22 5C 23 - 5C 24 5C 25 5C 26 5C 27  |\ \!\"\#\$\%\&\'|
@@ -5971,6 +6666,22 @@
 	000000B0  E2 82 AC 64 20 EF BF BD - 20 12 33 20 78 20 53 20  |...d ... .3 x S |
 	000000C0  53 34 0A                -                          |S4.|
 ---
+name: dollar-doublequoted-strings
+description:
+	Check that a $ preceding "…" is ignored
+stdin:
+	echo $"Localise me!"
+	cat <<<$"Me too!"
+	V=X
+	aol=aol
+	cat <<-$"aol"
+		I do not take a $V for a V!
+	aol
+expected-stdout:
+	Localise me!
+	Me too!
+	I do not take a $V for a V!
+---
 name: dollar-quoted-strings
 description:
 	Check backslash expansion by $'…' strings
@@ -5982,8 +6693,9 @@
 	    $'\u20acd' $'\U20acd' $'\x123' $'fn\x0rd' $'\0234' $'\234' \
 	    $'\2345' $'\ca' $'\c!' $'\c?' $'\c€' $'a\
 	b' | {
+		# integer-base-one-3As
 		typeset -Uui16 -Z11 pos=0
-		typeset -Uui16 -Z5 hv
+		typeset -Uui16 -Z5 hv=2147483647
 		typeset -i1 wc=0x0A
 		dasc=
 		nl=${wc#1#}
@@ -6006,13 +6718,11 @@
 				line=${line:1}
 			done
 		done
-		if (( (pos & 15) != 1 )); then
-			while (( pos & 15 )); do
-				print -n '   '
-				(( (pos++ & 15) == 7 )) && print -n -- '- '
-			done
-			print "$dasc|"
-		fi
+		while (( pos & 15 )); do
+			print -n '   '
+			(( (pos++ & 15) == 7 )) && print -n -- '- '
+		done
+		(( hv == 2147483647 )) || print "$dasc|"
 	}
 expected-stdout:
 	00000000  20 21 22 23 24 25 26 27 - 28 29 2A 2B 2C 2D 2E 2F  | !"#$%&'()*+,-./|
@@ -6249,9 +6959,10 @@
 	/1#à€€: unexpected '€'/
 expected-exit: e != 0
 ---
-name: integer-base-one-3A
+name: integer-base-one-3As
 description:
 	some sample code for hexdumping
+	not NUL safe; input lines must be NL terminated
 stdin:
 	{
 		print 'Hello, World!\\\nこんにちは!'
@@ -6261,10 +6972,11 @@
 		while (( i++ < 0x1FF )); do
 			print -n "\x${i#16#1}"
 		done
-		print
+		print '\0z'
 	} | {
+		# integer-base-one-3As
 		typeset -Uui16 -Z11 pos=0
-		typeset -Uui16 -Z5 hv
+		typeset -Uui16 -Z5 hv=2147483647
 		typeset -i1 wc=0x0A
 		dasc=
 		nl=${wc#1#}
@@ -6287,13 +6999,11 @@
 				line=${line:1}
 			done
 		done
-		if (( (pos & 15) != 1 )); then
-			while (( pos & 15 )); do
-				print -n '   '
-				(( (pos++ & 15) == 7 )) && print -n -- '- '
-			done
-			print "$dasc|"
-		fi
+		while (( pos & 15 )); do
+			print -n '   '
+			(( (pos++ & 15) == 7 )) && print -n -- '- '
+		done
+		(( hv == 2147483647 )) || print "$dasc|"
 	}
 expected-stdout:
 	00000000  48 65 6C 6C 6F 2C 20 57 - 6F 72 6C 64 21 5C 0A E3  |Hello, World!\..|
@@ -6314,11 +7024,12 @@
 	000000F0  CF D0 D1 D2 D3 D4 D5 D6 - D7 D8 D9 DA DB DC DD DE  |................|
 	00000100  DF E0 E1 E2 E3 E4 E5 E6 - E7 E8 E9 EA EB EC ED EE  |................|
 	00000110  EF F0 F1 F2 F3 F4 F5 F6 - F7 F8 F9 FA FB FC FD FE  |................|
-	00000120  FF 0A                   -                          |..|
+	00000120  FF 7A 0A                -                          |.z.|
 ---
-name: integer-base-one-3W
+name: integer-base-one-3Ws
 description:
 	some sample code for hexdumping Unicode
+	not NUL safe; input lines must be NL terminated
 stdin:
 	set -U
 	{
@@ -6336,7 +7047,9 @@
 		print \\xc0\\x80	# non-minimalistic
 		print \\xe0\\x80\\x80	# non-minimalistic
 		print '�￾￿'	# end of range
+		print '\0z'		# embedded NUL
 	} | {
+		# integer-base-one-3Ws
 		typeset -Uui16 -Z11 pos=0
 		typeset -Uui16 -Z7 hv
 		typeset -i1 wc=0x0A
@@ -6371,13 +7084,11 @@
 				dasc=$dasc$dch
 			done
 		done
-		if (( pos & 7 )); then
-			while (( pos & 7 )); do
-				print -n '     '
-				(( (pos++ & 7) == 3 )) && print -n -- '- '
-			done
-			print "$dasc|"
-		fi
+		while (( pos & 7 )); do
+			print -n '     '
+			(( (pos++ & 7) == 3 )) && print -n -- '- '
+		done
+		(( hv == 2147483647 )) || print "$dasc|"
 	}
 expected-stdout:
 	00000000  0048 0065 006C 006C - 006F 002C 0020 0057  |Hello, W|
@@ -6417,7 +7128,170 @@
 	00000110  00FB 00FC 00FD 00FE - 00FF 000A EFFF 000A  |ûüýþÿ.�.|
 	00000118  EFC2 000A EFEF EFBF - EFC0 000A EFC0 EF80  |�.���.��|
 	00000120  000A EFE0 EF80 EF80 - 000A FFFD EFEF EFBF  |.���.���|
-	00000128  EFBE EFEF EFBF EFBF - 000A                 |����.|
+	00000128  EFBE EFEF EFBF EFBF - 000A 007A 000A       |����.z.|
+---
+name: integer-base-one-3Ar
+description:
+	some sample code for hexdumping; NUL and binary safe
+stdin:
+	{
+		print 'Hello, World!\\\nこんにちは!'
+		typeset -Uui16 i=0x100
+		# change that to 0xFF once we can handle embedded
+		# NUL characters in strings / here documents
+		while (( i++ < 0x1FF )); do
+			print -n "\x${i#16#1}"
+		done
+		print '\0z'
+	} | {
+		# integer-base-one-3Ar
+		typeset -Uui16 -Z11 pos=0
+		typeset -Uui16 -Z5 hv=2147483647
+		dasc=
+		if read -arN -1 line; then
+			typeset -i1 line
+			i=0
+			while (( i < ${#line[*]} )); do
+				hv=${line[i++]}
+				if (( (pos & 15) == 0 )); then
+					(( pos )) && print "$dasc|"
+					print -n "${pos#16#}  "
+					dasc=' |'
+				fi
+				print -n "${hv#16#} "
+				if (( (hv < 32) || (hv > 126) )); then
+					dasc=$dasc.
+				else
+					dasc=$dasc${line[i-1]#1#}
+				fi
+				(( (pos++ & 15) == 7 )) && print -n -- '- '
+			done
+		fi
+		while (( pos & 15 )); do
+			print -n '   '
+			(( (pos++ & 15) == 7 )) && print -n -- '- '
+		done
+		(( hv == 2147483647 )) || print "$dasc|"
+	}
+expected-stdout:
+	00000000  48 65 6C 6C 6F 2C 20 57 - 6F 72 6C 64 21 5C 0A E3  |Hello, World!\..|
+	00000010  81 93 E3 82 93 E3 81 AB - E3 81 A1 E3 81 AF EF BC  |................|
+	00000020  81 0A 01 02 03 04 05 06 - 07 08 09 0A 0B 0C 0D 0E  |................|
+	00000030  0F 10 11 12 13 14 15 16 - 17 18 19 1A 1B 1C 1D 1E  |................|
+	00000040  1F 20 21 22 23 24 25 26 - 27 28 29 2A 2B 2C 2D 2E  |. !"#$%&'()*+,-.|
+	00000050  2F 30 31 32 33 34 35 36 - 37 38 39 3A 3B 3C 3D 3E  |/0123456789:;<=>|
+	00000060  3F 40 41 42 43 44 45 46 - 47 48 49 4A 4B 4C 4D 4E  |?@ABCDEFGHIJKLMN|
+	00000070  4F 50 51 52 53 54 55 56 - 57 58 59 5A 5B 5C 5D 5E  |OPQRSTUVWXYZ[\]^|
+	00000080  5F 60 61 62 63 64 65 66 - 67 68 69 6A 6B 6C 6D 6E  |_`abcdefghijklmn|
+	00000090  6F 70 71 72 73 74 75 76 - 77 78 79 7A 7B 7C 7D 7E  |opqrstuvwxyz{|}~|
+	000000A0  7F 80 81 82 83 84 85 86 - 87 88 89 8A 8B 8C 8D 8E  |................|
+	000000B0  8F 90 91 92 93 94 95 96 - 97 98 99 9A 9B 9C 9D 9E  |................|
+	000000C0  9F A0 A1 A2 A3 A4 A5 A6 - A7 A8 A9 AA AB AC AD AE  |................|
+	000000D0  AF B0 B1 B2 B3 B4 B5 B6 - B7 B8 B9 BA BB BC BD BE  |................|
+	000000E0  BF C0 C1 C2 C3 C4 C5 C6 - C7 C8 C9 CA CB CC CD CE  |................|
+	000000F0  CF D0 D1 D2 D3 D4 D5 D6 - D7 D8 D9 DA DB DC DD DE  |................|
+	00000100  DF E0 E1 E2 E3 E4 E5 E6 - E7 E8 E9 EA EB EC ED EE  |................|
+	00000110  EF F0 F1 F2 F3 F4 F5 F6 - F7 F8 F9 FA FB FC FD FE  |................|
+	00000120  FF 00 7A 0A             -                          |..z.|
+---
+name: integer-base-one-3Wr
+description:
+	some sample code for hexdumping Unicode; NUL and binary safe
+stdin:
+	set -U
+	{
+		print 'Hello, World!\\\nこんにちは!'
+		typeset -Uui16 i=0x100
+		# change that to 0xFF once we can handle embedded
+		# NUL characters in strings / here documents
+		while (( i++ < 0x1FF )); do
+			print -n "\u${i#16#1}"
+		done
+		print
+		print \\xff		# invalid utf-8
+		print \\xc2		# invalid 2-byte
+		print \\xef\\xbf\\xc0	# invalid 3-byte
+		print \\xc0\\x80	# non-minimalistic
+		print \\xe0\\x80\\x80	# non-minimalistic
+		print '�￾￿'	# end of range
+		print '\0z'		# embedded NUL
+	} | {
+		# integer-base-one-3Wr
+		typeset -Uui16 -Z11 pos=0
+		typeset -Uui16 -Z7 hv=2147483647
+		dasc=
+		if read -arN -1 line; then
+			typeset -i1 line
+			i=0
+			while (( i < ${#line[*]} )); do
+				hv=${line[i++]}
+				if (( (hv < 32) || \
+				    ((hv > 126) && (hv < 160)) )); then
+					dch=.
+				elif (( (hv & 0xFF80) == 0xEF80 )); then
+					dch=�
+				else
+					dch=${line[i-1]#1#}
+				fi
+				if (( (pos & 7) == 7 )); then
+					dasc=$dasc$dch
+					dch=
+				elif (( (pos & 7) == 0 )); then
+					(( pos )) && print "$dasc|"
+					print -n "${pos#16#}  "
+					dasc=' |'
+				fi
+				print -n "${hv#16#} "
+				(( (pos++ & 7) == 3 )) && \
+				    print -n -- '- '
+				dasc=$dasc$dch
+			done
+		fi
+		while (( pos & 7 )); do
+			print -n '     '
+			(( (pos++ & 7) == 3 )) && print -n -- '- '
+		done
+		(( hv == 2147483647 )) || print "$dasc|"
+	}
+expected-stdout:
+	00000000  0048 0065 006C 006C - 006F 002C 0020 0057  |Hello, W|
+	00000008  006F 0072 006C 0064 - 0021 005C 000A 3053  |orld!\.こ|
+	00000010  3093 306B 3061 306F - FF01 000A 0001 0002  |んにちは!...|
+	00000018  0003 0004 0005 0006 - 0007 0008 0009 000A  |........|
+	00000020  000B 000C 000D 000E - 000F 0010 0011 0012  |........|
+	00000028  0013 0014 0015 0016 - 0017 0018 0019 001A  |........|
+	00000030  001B 001C 001D 001E - 001F 0020 0021 0022  |..... !"|
+	00000038  0023 0024 0025 0026 - 0027 0028 0029 002A  |#$%&'()*|
+	00000040  002B 002C 002D 002E - 002F 0030 0031 0032  |+,-./012|
+	00000048  0033 0034 0035 0036 - 0037 0038 0039 003A  |3456789:|
+	00000050  003B 003C 003D 003E - 003F 0040 0041 0042  |;<=>?@AB|
+	00000058  0043 0044 0045 0046 - 0047 0048 0049 004A  |CDEFGHIJ|
+	00000060  004B 004C 004D 004E - 004F 0050 0051 0052  |KLMNOPQR|
+	00000068  0053 0054 0055 0056 - 0057 0058 0059 005A  |STUVWXYZ|
+	00000070  005B 005C 005D 005E - 005F 0060 0061 0062  |[\]^_`ab|
+	00000078  0063 0064 0065 0066 - 0067 0068 0069 006A  |cdefghij|
+	00000080  006B 006C 006D 006E - 006F 0070 0071 0072  |klmnopqr|
+	00000088  0073 0074 0075 0076 - 0077 0078 0079 007A  |stuvwxyz|
+	00000090  007B 007C 007D 007E - 007F 0080 0081 0082  |{|}~....|
+	00000098  0083 0084 0085 0086 - 0087 0088 0089 008A  |........|
+	000000A0  008B 008C 008D 008E - 008F 0090 0091 0092  |........|
+	000000A8  0093 0094 0095 0096 - 0097 0098 0099 009A  |........|
+	000000B0  009B 009C 009D 009E - 009F 00A0 00A1 00A2  |..... ¡¢|
+	000000B8  00A3 00A4 00A5 00A6 - 00A7 00A8 00A9 00AA  |£¤¥¦§¨©ª|
+	000000C0  00AB 00AC 00AD 00AE - 00AF 00B0 00B1 00B2  |«¬­®¯°±²|
+	000000C8  00B3 00B4 00B5 00B6 - 00B7 00B8 00B9 00BA  |³´µ¶·¸¹º|
+	000000D0  00BB 00BC 00BD 00BE - 00BF 00C0 00C1 00C2  |»¼½¾¿ÀÁÂ|
+	000000D8  00C3 00C4 00C5 00C6 - 00C7 00C8 00C9 00CA  |ÃÄÅÆÇÈÉÊ|
+	000000E0  00CB 00CC 00CD 00CE - 00CF 00D0 00D1 00D2  |ËÌÍÎÏÐÑÒ|
+	000000E8  00D3 00D4 00D5 00D6 - 00D7 00D8 00D9 00DA  |ÓÔÕÖ×ØÙÚ|
+	000000F0  00DB 00DC 00DD 00DE - 00DF 00E0 00E1 00E2  |ÛÜÝÞßàáâ|
+	000000F8  00E3 00E4 00E5 00E6 - 00E7 00E8 00E9 00EA  |ãäåæçèéê|
+	00000100  00EB 00EC 00ED 00EE - 00EF 00F0 00F1 00F2  |ëìíîïðñò|
+	00000108  00F3 00F4 00F5 00F6 - 00F7 00F8 00F9 00FA  |óôõö÷øùú|
+	00000110  00FB 00FC 00FD 00FE - 00FF 000A EFFF 000A  |ûüýþÿ.�.|
+	00000118  EFC2 000A EFEF EFBF - EFC0 000A EFC0 EF80  |�.���.��|
+	00000120  000A EFE0 EF80 EF80 - 000A FFFD EFEF EFBF  |.���.���|
+	00000128  EFBE EFEF EFBF EFBF - 000A 0000 007A 000A  |����..z.|
 ---
 name: integer-base-one-4
 description:
@@ -6440,6 +7314,32 @@
 	5 97
 	6 97
 ---
+name: integer-base-one-5A
+description:
+	Check to see that we’re NUL and Unicode safe
+stdin:
+	set +U
+	print 'a\0b\xfdz' >x
+	read -a y <x
+	set -U
+	typeset -Uui16 y
+	print ${y[*]} .
+expected-stdout:
+	16#61 16#0 16#62 16#FD 16#7A .
+---
+name: integer-base-one-5W
+description:
+	Check to see that we’re NUL and Unicode safe
+stdin:
+	set -U
+	print 'a\0b€c' >x
+	read -a y <x
+	set +U
+	typeset -Uui16 y
+	print ${y[*]} .
+expected-stdout:
+	16#61 16#0 16#62 16#20AC 16#63 .
+---
 name: ulimit-1
 description:
 	Check if we can use a specific syntax idiom for ulimit
@@ -6555,7 +7455,7 @@
 expected-stdout:
 	===
 	mir
-expected-stderr-pattern: /.*: cannot (create|overwrite) .*/
+expected-stderr-pattern: /.*: can't (create|overwrite) .*/
 ---
 name: bashiop-3b
 description:
@@ -6720,21 +7620,22 @@
 name: fd-cloexec-1
 description:
 	Verify that file descriptors > 2 are private for Korn shells
+	AT&T ksh93 does this still, which means we must keep it as well
 file-setup: file 644 "test.sh"
-	print -u3 Fowl
+	echo >&3 Fowl
 stdin:
 	exec 3>&1
 	"$__progname" test.sh
 expected-exit: e != 0
-expected-stderr:
-	test.sh[1]: print: -u: 3: bad file descriptor
+expected-stderr-pattern:
+	/bad file descriptor/
 ---
 name: fd-cloexec-2
 description:
 	Verify that file descriptors > 2 are not private for POSIX shells
 	See Debian Bug #154540, Closes: #499139
 file-setup: file 644 "test.sh"
-	print -u3 Fowl
+	echo >&3 Fowl
 stdin:
 	test -n "$POSH_VERSION" || set -o sh
 	exec 3>&1
@@ -6742,28 +7643,61 @@
 expected-stdout:
 	Fowl
 ---
-name: comsub-1
+name: comsub-1a
 description:
-	COMSUB are currently parsed by hacking lex.c instead of
-	recursively (see regression-6): matching parenthesēs bug
-	Fails on: pdksh mksh bash2 bash3 zsh
-	Passes on: bash4 ksh93
-expected-fail: yes
+	COMSUB are now parsed recursively, so this works
+	see also regression-6: matching parenthesēs bug
+	Fails on: pdksh bash2 bash3 zsh
+	Passes on: bash4 ksh93 mksh(20110313+)
 stdin:
 	echo $(case 1 in (1) echo yes;; (2) echo no;; esac)
 	echo $(case 1 in 1) echo yes;; 2) echo no;; esac)
+	TEST=1234; echo ${TEST: $(case 1 in (1) echo 1;; (*) echo 2;; esac)}
+	TEST=5678; echo ${TEST: $(case 1 in 1) echo 1;; *) echo 2;; esac)}
 expected-stdout:
 	yes
 	yes
+	234
+	678
+---
+name: comsub-1b
+description:
+	COMSUB are now parsed recursively, so this works
+	Fails on GNU bash even, ksh93 passes
+stdin:
+	echo $(($(case 1 in (1) echo 1;; (*) echo 2;; esac)+10))
+	echo $(($(case 1 in 1) echo 1;; *) echo 2;; esac)+20))
+	(( a = $(case 1 in (1) echo 1;; (*) echo 2;; esac) )); echo $a.
+	(( a = $(case 1 in 1) echo 1;; *) echo 2;; esac) )); echo $a.
+expected-stdout:
+	11
+	21
+	1.
+	1.
+---
+name: comsub-1c
+description:
+	COMSUB are now parsed recursively, so this works (ksh93, mksh)
+	First test passes on bash4, second fails there
+category: !smksh
+stdin:
+	a=($(case 1 in (1) echo 1;; (*) echo 2;; esac)); echo ${a[0]}.
+	a=($(case 1 in 1) echo 1;; *) echo 2;; esac)); echo ${a[0]}.
+	a=($(($(case 1 in (1) echo 1;; (*) echo 2;; esac)+10))); echo ${a[0]}.
+	a=($(($(case 1 in 1) echo 1;; *) echo 2;; esac)+20))); echo ${a[0]}.
+expected-stdout:
+	1.
+	1.
+	11.
+	21.
 ---
 name: comsub-2
 description:
 	RedHat BZ#496791 – another case of missing recursion
 	in parsing COMSUB expressions
-	Fails on: pdksh mksh bash2 bash3¹ bash4¹ zsh
-	Passes on: ksh93
+	Fails on: pdksh bash2 bash3¹ bash4¹ zsh
+	Passes on: ksh93 mksh(20110305+)
 	① bash[34] seem to choke on comment ending with backslash-newline
-expected-fail: yes
 stdin:
 	# a comment with " ' \
 	x=$(
@@ -6774,6 +7708,707 @@
 expected-stdout:
 	yes
 ---
+name: comsub-3
+description:
+	Extended test for COMSUB explaining why a recursive parser
+	is a must (a non-recursive parser cannot pass all three of
+	these test cases, especially the ‘#’ is difficult)
+stdin:
+	echo $(typeset -i10 x=16#20; echo $x)
+	echo $(typeset -Uui16 x=16#$(id -u)
+	) .
+	echo $(c=1; d=1
+	typeset -Uui16 a=36#foo; c=2
+	typeset -Uui16 b=36 #foo; d=2
+	echo $a $b $c $d)
+expected-stdout:
+	32
+	.
+	16#4F68 16#24 2 1
+---
+name: comsub-4
+description:
+	Check the tree dump functions for !MKSH_SMALL functionality
+category: !smksh
+stdin:
+	x() { case $1 in a) a+=b ;;& *) c+=(d e) ;; esac; }
+	typeset -f x
+expected-stdout:
+	x() {
+		case $1 in
+		(a)
+			a+=b 
+			;|
+		(*)
+			set -A c+ -- d e 
+			;;
+		esac 
+	} 
+---
+name: comsub-torture
+description:
+	Check the tree dump functions work correctly
+stdin:
+	if [[ -z $__progname ]]; then echo >&2 call me with __progname; exit 1; fi
+	while IFS= read -r line; do
+		if [[ $line = '#1' ]]; then
+			lastf=0
+			continue
+		elif [[ $line = EOFN* ]]; then
+			fbody=$fbody$'\n'$line
+			continue
+		elif [[ $line != '#'* ]]; then
+			fbody=$fbody$'\n\t'$line
+			continue
+		fi
+		if (( lastf )); then
+			x="inline_${nextf}() {"$fbody$'\n}\n'
+			print -nr -- "$x"
+			print -r -- "${x}typeset -f inline_$nextf" | "$__progname"
+			x="function comsub_$nextf { x=\$("$fbody$'\n); }\n'
+			print -nr -- "$x"
+			print -r -- "${x}typeset -f comsub_$nextf" | "$__progname"
+			x="function reread_$nextf { x=\$(("$fbody$'\n)|tr u x); }\n'
+			print -nr -- "$x"
+			print -r -- "${x}typeset -f reread_$nextf" | "$__progname"
+		fi
+		lastf=1
+		fbody=
+		nextf=${line#?}
+	done <<'EOD'
+	#1
+	#TCOM
+	vara=1  varb='2  3'  cmd  arg1  $arg2  "$arg3  4"
+	#TPAREN_TPIPE_TLIST
+	(echo $foo  |  tr -dc 0-9; echo)
+	#TAND_TOR
+	cmd  &&  echo ja  ||  echo nein
+	#TSELECT
+	select  file  in  *;  do  echo  "<$file>" ;  break ;  done
+	#TFOR_TTIME
+	for  i  in  {1,2,3}  ;  do  time  echo  $i ;  done
+	#TCASE
+	case  $foo  in  1)  echo eins;& 2) echo zwei  ;| *) echo kann net bis drei zählen;;  esac
+	#TIF_TBANG_TDBRACKET_TELIF
+	if  !  [[  1  =  1  ]]  ;  then  echo eins;  elif [[ 1 = 2 ]]; then echo zwei  ;else echo drei; fi
+	#TWHILE
+	i=1; while (( i < 10 )); do echo $i; let ++i; done
+	#TUNTIL
+	i=10; until  (( !--i )) ; do echo $i; done
+	#TCOPROC
+	cat  *  |&  ls
+	#TFUNCT_TBRACE_TASYNC
+	function  korn  {  echo eins; echo zwei ;  }
+	bourne  ()  {  logger *  &  }
+	#IOREAD_IOCAT
+	tr  x  u  0<foo  >>bar
+	#IOWRITE_IOCLOB_IOHERE_noIOSKIP
+	cat  >|bar  <<'EOFN'
+	foo
+	EOFN
+	#IOWRITE_noIOCLOB_IOHERE_IOSKIP
+	cat  1>bar  <<-EOFI
+	foo
+	EOFI
+	#IORDWR_IODUP
+	sh  1<>/dev/console  0<&1  2>&1
+	#COMSUB_EXPRSUB
+	echo $(true) $((1+ 2))
+	#QCHAR_OQUOTE_CQUOTE
+	echo fo\ob\"a\`r\'b\$az
+	echo "fo\ob\"a\`r\'b\$az"
+	echo 'fo\ob\"a\`r'\''b\$az'
+	#OSUBST_CSUBST_OPAT_SPAT_CPAT
+	[[ ${foo#bl\(u\)b} = @(bar|baz) ]]
+	#heredoc_closed
+	x=$(cat <<EOFN
+	note there must be no space between EOFN and )
+	EOFN); echo $x
+	#heredoc_space
+	x=$(cat <<EOFN\ 
+	note the space between EOFN and ) is actually part of the here document marker
+	EOFN ); echo $x
+	#patch_motd
+	x=$(sysctl -n kern.version | sed 1q)
+	[[ -s /etc/motd && "$([[ "$(head -1 /etc/motd)" != $x ]] && \
+	    ed -s /etc/motd 2>&1 <<-EOF
+		1,/^\$/d
+		0a
+			$x
+	
+		.
+		wq
+	EOF)" = @(?) ]] && rm -f /etc/motd
+	if [[ ! -s /etc/motd ]]; then
+		install -c -o root -g wheel -m 664 /dev/null /etc/motd
+		print -- "$x\n" >/etc/motd
+	fi
+	#0
+	EOD
+expected-stdout:
+	inline_TCOM() {
+		vara=1  varb='2  3'  cmd  arg1  $arg2  "$arg3  4"
+	}
+	inline_TCOM() {
+		vara=1 varb="2  3" cmd arg1 $arg2 "$arg3  4" 
+	} 
+	function comsub_TCOM { x=$(
+		vara=1  varb='2  3'  cmd  arg1  $arg2  "$arg3  4"
+	); }
+	function comsub_TCOM {
+		x=$(vara=1 varb="2  3" cmd arg1 $arg2 "$arg3  4" ) 
+	} 
+	function reread_TCOM { x=$((
+		vara=1  varb='2  3'  cmd  arg1  $arg2  "$arg3  4"
+	)|tr u x); }
+	function reread_TCOM {
+		x=$(( vara=1 varb="2  3" cmd arg1 $arg2 "$arg3  4" ) | tr u x ) 
+	} 
+	inline_TPAREN_TPIPE_TLIST() {
+		(echo $foo  |  tr -dc 0-9; echo)
+	}
+	inline_TPAREN_TPIPE_TLIST() {
+		( echo $foo | tr -dc 0-9 
+		  echo ) 
+	} 
+	function comsub_TPAREN_TPIPE_TLIST { x=$(
+		(echo $foo  |  tr -dc 0-9; echo)
+	); }
+	function comsub_TPAREN_TPIPE_TLIST {
+		x=$(( echo $foo | tr -dc 0-9 ; echo ) ) 
+	} 
+	function reread_TPAREN_TPIPE_TLIST { x=$((
+		(echo $foo  |  tr -dc 0-9; echo)
+	)|tr u x); }
+	function reread_TPAREN_TPIPE_TLIST {
+		x=$(( ( echo $foo | tr -dc 0-9 ; echo ) ) | tr u x ) 
+	} 
+	inline_TAND_TOR() {
+		cmd  &&  echo ja  ||  echo nein
+	}
+	inline_TAND_TOR() {
+		cmd && echo ja || echo nein 
+	} 
+	function comsub_TAND_TOR { x=$(
+		cmd  &&  echo ja  ||  echo nein
+	); }
+	function comsub_TAND_TOR {
+		x=$(cmd && echo ja || echo nein ) 
+	} 
+	function reread_TAND_TOR { x=$((
+		cmd  &&  echo ja  ||  echo nein
+	)|tr u x); }
+	function reread_TAND_TOR {
+		x=$(( cmd && echo ja || echo nein ) | tr u x ) 
+	} 
+	inline_TSELECT() {
+		select  file  in  *;  do  echo  "<$file>" ;  break ;  done
+	}
+	inline_TSELECT() {
+		select file in * 
+		do
+			echo "<$file>" 
+			break 
+		done 
+	} 
+	function comsub_TSELECT { x=$(
+		select  file  in  *;  do  echo  "<$file>" ;  break ;  done
+	); }
+	function comsub_TSELECT {
+		x=$(select file in * ; do echo "<$file>" ; break ; done ) 
+	} 
+	function reread_TSELECT { x=$((
+		select  file  in  *;  do  echo  "<$file>" ;  break ;  done
+	)|tr u x); }
+	function reread_TSELECT {
+		x=$(( select file in * ; do echo "<$file>" ; break ; done ) | tr u x ) 
+	} 
+	inline_TFOR_TTIME() {
+		for  i  in  {1,2,3}  ;  do  time  echo  $i ;  done
+	}
+	inline_TFOR_TTIME() {
+		for i in {1,2,3} 
+		do
+			time echo $i 
+		done 
+	} 
+	function comsub_TFOR_TTIME { x=$(
+		for  i  in  {1,2,3}  ;  do  time  echo  $i ;  done
+	); }
+	function comsub_TFOR_TTIME {
+		x=$(for i in {1,2,3} ; do time echo $i ; done ) 
+	} 
+	function reread_TFOR_TTIME { x=$((
+		for  i  in  {1,2,3}  ;  do  time  echo  $i ;  done
+	)|tr u x); }
+	function reread_TFOR_TTIME {
+		x=$(( for i in {1,2,3} ; do time echo $i ; done ) | tr u x ) 
+	} 
+	inline_TCASE() {
+		case  $foo  in  1)  echo eins;& 2) echo zwei  ;| *) echo kann net bis drei zählen;;  esac
+	}
+	inline_TCASE() {
+		case $foo in
+		(1)
+			echo eins 
+			;&
+		(2)
+			echo zwei 
+			;|
+		(*)
+			echo kann net bis drei zählen 
+			;;
+		esac 
+	} 
+	function comsub_TCASE { x=$(
+		case  $foo  in  1)  echo eins;& 2) echo zwei  ;| *) echo kann net bis drei zählen;;  esac
+	); }
+	function comsub_TCASE {
+		x=$(case $foo in (1) echo eins  ;& (2) echo zwei  ;| (*) echo kann net bis drei zählen  ;; esac ) 
+	} 
+	function reread_TCASE { x=$((
+		case  $foo  in  1)  echo eins;& 2) echo zwei  ;| *) echo kann net bis drei zählen;;  esac
+	)|tr u x); }
+	function reread_TCASE {
+		x=$(( case $foo in (1) echo eins  ;& (2) echo zwei  ;| (*) echo kann net bis drei zählen  ;; esac ) | tr u x ) 
+	} 
+	inline_TIF_TBANG_TDBRACKET_TELIF() {
+		if  !  [[  1  =  1  ]]  ;  then  echo eins;  elif [[ 1 = 2 ]]; then echo zwei  ;else echo drei; fi
+	}
+	inline_TIF_TBANG_TDBRACKET_TELIF() {
+		if ! [[ 1 = 1 ]] 
+		then
+			echo eins 
+		elif [[ 1 = 2 ]] 
+		then
+			echo zwei 
+		else
+			echo drei 
+		fi 
+	} 
+	function comsub_TIF_TBANG_TDBRACKET_TELIF { x=$(
+		if  !  [[  1  =  1  ]]  ;  then  echo eins;  elif [[ 1 = 2 ]]; then echo zwei  ;else echo drei; fi
+	); }
+	function comsub_TIF_TBANG_TDBRACKET_TELIF {
+		x=$(if ! [[ 1 = 1 ]] ; then echo eins ; elif [[ 1 = 2 ]] ; then echo zwei ; else echo drei ; fi ) 
+	} 
+	function reread_TIF_TBANG_TDBRACKET_TELIF { x=$((
+		if  !  [[  1  =  1  ]]  ;  then  echo eins;  elif [[ 1 = 2 ]]; then echo zwei  ;else echo drei; fi
+	)|tr u x); }
+	function reread_TIF_TBANG_TDBRACKET_TELIF {
+		x=$(( if ! [[ 1 = 1 ]] ; then echo eins ; elif [[ 1 = 2 ]] ; then echo zwei ; else echo drei ; fi ) | tr u x ) 
+	} 
+	inline_TWHILE() {
+		i=1; while (( i < 10 )); do echo $i; let ++i; done
+	}
+	inline_TWHILE() {
+		i=1 
+		while let " i < 10 " 
+		do
+			echo $i 
+			let ++i 
+		done 
+	} 
+	function comsub_TWHILE { x=$(
+		i=1; while (( i < 10 )); do echo $i; let ++i; done
+	); }
+	function comsub_TWHILE {
+		x=$(i=1 ; while let " i < 10 " ; do echo $i ; let ++i ; done ) 
+	} 
+	function reread_TWHILE { x=$((
+		i=1; while (( i < 10 )); do echo $i; let ++i; done
+	)|tr u x); }
+	function reread_TWHILE {
+		x=$(( i=1 ; while let " i < 10 " ; do echo $i ; let ++i ; done ) | tr u x ) 
+	} 
+	inline_TUNTIL() {
+		i=10; until  (( !--i )) ; do echo $i; done
+	}
+	inline_TUNTIL() {
+		i=10 
+		until let " !--i " 
+		do
+			echo $i 
+		done 
+	} 
+	function comsub_TUNTIL { x=$(
+		i=10; until  (( !--i )) ; do echo $i; done
+	); }
+	function comsub_TUNTIL {
+		x=$(i=10 ; until let " !--i " ; do echo $i ; done ) 
+	} 
+	function reread_TUNTIL { x=$((
+		i=10; until  (( !--i )) ; do echo $i; done
+	)|tr u x); }
+	function reread_TUNTIL {
+		x=$(( i=10 ; until let " !--i " ; do echo $i ; done ) | tr u x ) 
+	} 
+	inline_TCOPROC() {
+		cat  *  |&  ls
+	}
+	inline_TCOPROC() {
+		cat * |& 
+		ls 
+	} 
+	function comsub_TCOPROC { x=$(
+		cat  *  |&  ls
+	); }
+	function comsub_TCOPROC {
+		x=$(cat * |&  ls ) 
+	} 
+	function reread_TCOPROC { x=$((
+		cat  *  |&  ls
+	)|tr u x); }
+	function reread_TCOPROC {
+		x=$(( cat * |&  ls ) | tr u x ) 
+	} 
+	inline_TFUNCT_TBRACE_TASYNC() {
+		function  korn  {  echo eins; echo zwei ;  }
+		bourne  ()  {  logger *  &  }
+	}
+	inline_TFUNCT_TBRACE_TASYNC() {
+		function korn {
+			echo eins 
+			echo zwei 
+		} 
+		bourne() {
+			logger * & 
+		} 
+	} 
+	function comsub_TFUNCT_TBRACE_TASYNC { x=$(
+		function  korn  {  echo eins; echo zwei ;  }
+		bourne  ()  {  logger *  &  }
+	); }
+	function comsub_TFUNCT_TBRACE_TASYNC {
+		x=$(function korn { echo eins ; echo zwei ; } ; bourne() { logger * &  } ) 
+	} 
+	function reread_TFUNCT_TBRACE_TASYNC { x=$((
+		function  korn  {  echo eins; echo zwei ;  }
+		bourne  ()  {  logger *  &  }
+	)|tr u x); }
+	function reread_TFUNCT_TBRACE_TASYNC {
+		x=$(( function korn { echo eins ; echo zwei ; } ; bourne() { logger * &  } ) | tr u x ) 
+	} 
+	inline_IOREAD_IOCAT() {
+		tr  x  u  0<foo  >>bar
+	}
+	inline_IOREAD_IOCAT() {
+		tr x u <foo >>bar 
+	} 
+	function comsub_IOREAD_IOCAT { x=$(
+		tr  x  u  0<foo  >>bar
+	); }
+	function comsub_IOREAD_IOCAT {
+		x=$(tr x u <foo >>bar ) 
+	} 
+	function reread_IOREAD_IOCAT { x=$((
+		tr  x  u  0<foo  >>bar
+	)|tr u x); }
+	function reread_IOREAD_IOCAT {
+		x=$(( tr x u <foo >>bar ) | tr u x ) 
+	} 
+	inline_IOWRITE_IOCLOB_IOHERE_noIOSKIP() {
+		cat  >|bar  <<'EOFN'
+		foo
+	EOFN
+	}
+	inline_IOWRITE_IOCLOB_IOHERE_noIOSKIP() {
+		cat >|bar <<"EOFN" 
+		foo
+	EOFN
+	
+	} 
+	function comsub_IOWRITE_IOCLOB_IOHERE_noIOSKIP { x=$(
+		cat  >|bar  <<'EOFN'
+		foo
+	EOFN
+	); }
+	function comsub_IOWRITE_IOCLOB_IOHERE_noIOSKIP {
+		x=$(cat >|bar <<"EOFN" 
+		foo
+	EOFN
+	) 
+	} 
+	function reread_IOWRITE_IOCLOB_IOHERE_noIOSKIP { x=$((
+		cat  >|bar  <<'EOFN'
+		foo
+	EOFN
+	)|tr u x); }
+	function reread_IOWRITE_IOCLOB_IOHERE_noIOSKIP {
+		x=$(( cat >|bar <<"EOFN" 
+		foo
+	EOFN
+	) | tr u x ) 
+	} 
+	inline_IOWRITE_noIOCLOB_IOHERE_IOSKIP() {
+		cat  1>bar  <<-EOFI
+		foo
+		EOFI
+	}
+	inline_IOWRITE_noIOCLOB_IOHERE_IOSKIP() {
+		cat >bar <<-EOFI 
+	foo
+	EOFI
+	
+	} 
+	function comsub_IOWRITE_noIOCLOB_IOHERE_IOSKIP { x=$(
+		cat  1>bar  <<-EOFI
+		foo
+		EOFI
+	); }
+	function comsub_IOWRITE_noIOCLOB_IOHERE_IOSKIP {
+		x=$(cat >bar <<-EOFI 
+	foo
+	EOFI
+	) 
+	} 
+	function reread_IOWRITE_noIOCLOB_IOHERE_IOSKIP { x=$((
+		cat  1>bar  <<-EOFI
+		foo
+		EOFI
+	)|tr u x); }
+	function reread_IOWRITE_noIOCLOB_IOHERE_IOSKIP {
+		x=$(( cat >bar <<-EOFI 
+	foo
+	EOFI
+	) | tr u x ) 
+	} 
+	inline_IORDWR_IODUP() {
+		sh  1<>/dev/console  0<&1  2>&1
+	}
+	inline_IORDWR_IODUP() {
+		sh 1<>/dev/console <&1 2>&1 
+	} 
+	function comsub_IORDWR_IODUP { x=$(
+		sh  1<>/dev/console  0<&1  2>&1
+	); }
+	function comsub_IORDWR_IODUP {
+		x=$(sh 1<>/dev/console <&1 2>&1 ) 
+	} 
+	function reread_IORDWR_IODUP { x=$((
+		sh  1<>/dev/console  0<&1  2>&1
+	)|tr u x); }
+	function reread_IORDWR_IODUP {
+		x=$(( sh 1<>/dev/console <&1 2>&1 ) | tr u x ) 
+	} 
+	inline_COMSUB_EXPRSUB() {
+		echo $(true) $((1+ 2))
+	}
+	inline_COMSUB_EXPRSUB() {
+		echo $(true ) $((1+ 2)) 
+	} 
+	function comsub_COMSUB_EXPRSUB { x=$(
+		echo $(true) $((1+ 2))
+	); }
+	function comsub_COMSUB_EXPRSUB {
+		x=$(echo $(true ) $((1+ 2)) ) 
+	} 
+	function reread_COMSUB_EXPRSUB { x=$((
+		echo $(true) $((1+ 2))
+	)|tr u x); }
+	function reread_COMSUB_EXPRSUB {
+		x=$(( echo $(true ) $((1+ 2)) ) | tr u x ) 
+	} 
+	inline_QCHAR_OQUOTE_CQUOTE() {
+		echo fo\ob\"a\`r\'b\$az
+		echo "fo\ob\"a\`r\'b\$az"
+		echo 'fo\ob\"a\`r'\''b\$az'
+	}
+	inline_QCHAR_OQUOTE_CQUOTE() {
+		echo fo\ob\"a\`r\'b\$az 
+		echo "fo\ob\"a\`r\'b\$az" 
+		echo "fo\\ob\\\"a\\\`r"\'"b\\\$az" 
+	} 
+	function comsub_QCHAR_OQUOTE_CQUOTE { x=$(
+		echo fo\ob\"a\`r\'b\$az
+		echo "fo\ob\"a\`r\'b\$az"
+		echo 'fo\ob\"a\`r'\''b\$az'
+	); }
+	function comsub_QCHAR_OQUOTE_CQUOTE {
+		x=$(echo fo\ob\"a\`r\'b\$az ; echo "fo\ob\"a\`r\'b\$az" ; echo "fo\\ob\\\"a\\\`r"\'"b\\\$az" ) 
+	} 
+	function reread_QCHAR_OQUOTE_CQUOTE { x=$((
+		echo fo\ob\"a\`r\'b\$az
+		echo "fo\ob\"a\`r\'b\$az"
+		echo 'fo\ob\"a\`r'\''b\$az'
+	)|tr u x); }
+	function reread_QCHAR_OQUOTE_CQUOTE {
+		x=$(( echo fo\ob\"a\`r\'b\$az ; echo "fo\ob\"a\`r\'b\$az" ; echo "fo\\ob\\\"a\\\`r"\'"b\\\$az" ) | tr u x ) 
+	} 
+	inline_OSUBST_CSUBST_OPAT_SPAT_CPAT() {
+		[[ ${foo#bl\(u\)b} = @(bar|baz) ]]
+	}
+	inline_OSUBST_CSUBST_OPAT_SPAT_CPAT() {
+		[[ ${foo#bl\(u\)b} = @(bar|baz) ]] 
+	} 
+	function comsub_OSUBST_CSUBST_OPAT_SPAT_CPAT { x=$(
+		[[ ${foo#bl\(u\)b} = @(bar|baz) ]]
+	); }
+	function comsub_OSUBST_CSUBST_OPAT_SPAT_CPAT {
+		x=$([[ ${foo#bl\(u\)b} = @(bar|baz) ]] ) 
+	} 
+	function reread_OSUBST_CSUBST_OPAT_SPAT_CPAT { x=$((
+		[[ ${foo#bl\(u\)b} = @(bar|baz) ]]
+	)|tr u x); }
+	function reread_OSUBST_CSUBST_OPAT_SPAT_CPAT {
+		x=$(( [[ ${foo#bl\(u\)b} = @(bar|baz) ]] ) | tr u x ) 
+	} 
+	inline_heredoc_closed() {
+		x=$(cat <<EOFN
+		note there must be no space between EOFN and )
+	EOFN); echo $x
+	}
+	inline_heredoc_closed() {
+		x=$(cat <<EOFN 
+		note there must be no space between EOFN and )
+	EOFN
+	) 
+		echo $x 
+	} 
+	function comsub_heredoc_closed { x=$(
+		x=$(cat <<EOFN
+		note there must be no space between EOFN and )
+	EOFN); echo $x
+	); }
+	function comsub_heredoc_closed {
+		x=$(x=$(cat <<EOFN 
+		note there must be no space between EOFN and )
+	EOFN
+	) ; echo $x ) 
+	} 
+	function reread_heredoc_closed { x=$((
+		x=$(cat <<EOFN
+		note there must be no space between EOFN and )
+	EOFN); echo $x
+	)|tr u x); }
+	function reread_heredoc_closed {
+		x=$(( x=$(cat <<EOFN 
+		note there must be no space between EOFN and )
+	EOFN
+	) ; echo $x ) | tr u x ) 
+	} 
+	inline_heredoc_space() {
+		x=$(cat <<EOFN\ 
+		note the space between EOFN and ) is actually part of the here document marker
+	EOFN ); echo $x
+	}
+	inline_heredoc_space() {
+		x=$(cat <<EOFN\  
+		note the space between EOFN and ) is actually part of the here document marker
+	EOFN 
+	) 
+		echo $x 
+	} 
+	function comsub_heredoc_space { x=$(
+		x=$(cat <<EOFN\ 
+		note the space between EOFN and ) is actually part of the here document marker
+	EOFN ); echo $x
+	); }
+	function comsub_heredoc_space {
+		x=$(x=$(cat <<EOFN\  
+		note the space between EOFN and ) is actually part of the here document marker
+	EOFN 
+	) ; echo $x ) 
+	} 
+	function reread_heredoc_space { x=$((
+		x=$(cat <<EOFN\ 
+		note the space between EOFN and ) is actually part of the here document marker
+	EOFN ); echo $x
+	)|tr u x); }
+	function reread_heredoc_space {
+		x=$(( x=$(cat <<EOFN\  
+		note the space between EOFN and ) is actually part of the here document marker
+	EOFN 
+	) ; echo $x ) | tr u x ) 
+	} 
+	inline_patch_motd() {
+		x=$(sysctl -n kern.version | sed 1q)
+		[[ -s /etc/motd && "$([[ "$(head -1 /etc/motd)" != $x ]] && \
+		    ed -s /etc/motd 2>&1 <<-EOF
+			1,/^\$/d
+			0a
+				$x
+		
+			.
+			wq
+		EOF)" = @(?) ]] && rm -f /etc/motd
+		if [[ ! -s /etc/motd ]]; then
+			install -c -o root -g wheel -m 664 /dev/null /etc/motd
+			print -- "$x\n" >/etc/motd
+		fi
+	}
+	inline_patch_motd() {
+		x=$(sysctl -n kern.version | sed 1q ) 
+		[[ -s /etc/motd && "$([[ "$(head -1 /etc/motd )" != $x ]] && ed -s /etc/motd 2>&1 <<-EOF 
+	1,/^\$/d
+	0a
+	$x
+	
+	.
+	wq
+	EOF
+	)" = @(?) ]] && rm -f /etc/motd 
+		if [[ ! -s /etc/motd ]] 
+		then
+			install -c -o root -g wheel -m 664 /dev/null /etc/motd 
+			print -- "$x\n" >/etc/motd 
+		fi 
+	} 
+	function comsub_patch_motd { x=$(
+		x=$(sysctl -n kern.version | sed 1q)
+		[[ -s /etc/motd && "$([[ "$(head -1 /etc/motd)" != $x ]] && \
+		    ed -s /etc/motd 2>&1 <<-EOF
+			1,/^\$/d
+			0a
+				$x
+		
+			.
+			wq
+		EOF)" = @(?) ]] && rm -f /etc/motd
+		if [[ ! -s /etc/motd ]]; then
+			install -c -o root -g wheel -m 664 /dev/null /etc/motd
+			print -- "$x\n" >/etc/motd
+		fi
+	); }
+	function comsub_patch_motd {
+		x=$(x=$(sysctl -n kern.version | sed 1q ) ; [[ -s /etc/motd && "$([[ "$(head -1 /etc/motd )" != $x ]] && ed -s /etc/motd 2>&1 <<-EOF 
+	1,/^\$/d
+	0a
+	$x
+	
+	.
+	wq
+	EOF
+	)" = @(?) ]] && rm -f /etc/motd ; if [[ ! -s /etc/motd ]] ; then install -c -o root -g wheel -m 664 /dev/null /etc/motd ; print -- "$x\n" >/etc/motd ; fi ) 
+	} 
+	function reread_patch_motd { x=$((
+		x=$(sysctl -n kern.version | sed 1q)
+		[[ -s /etc/motd && "$([[ "$(head -1 /etc/motd)" != $x ]] && \
+		    ed -s /etc/motd 2>&1 <<-EOF
+			1,/^\$/d
+			0a
+				$x
+		
+			.
+			wq
+		EOF)" = @(?) ]] && rm -f /etc/motd
+		if [[ ! -s /etc/motd ]]; then
+			install -c -o root -g wheel -m 664 /dev/null /etc/motd
+			print -- "$x\n" >/etc/motd
+		fi
+	)|tr u x); }
+	function reread_patch_motd {
+		x=$(( x=$(sysctl -n kern.version | sed 1q ) ; [[ -s /etc/motd && "$([[ "$(head -1 /etc/motd )" != $x ]] && ed -s /etc/motd 2>&1 <<-EOF 
+	1,/^\$/d
+	0a
+	$x
+	
+	.
+	wq
+	EOF
+	)" = @(?) ]] && rm -f /etc/motd ; if [[ ! -s /etc/motd ]] ; then install -c -o root -g wheel -m 664 /dev/null /etc/motd ; print -- "$x\n" >/etc/motd ; fi ) | tr u x ) 
+	} 
+---
 name: test-stnze-1
 description:
 	Check that the short form [ $x ] works
@@ -6875,6 +8510,7 @@
 file-setup: file 755 "!false"
 	#! /bin/sh
 	echo si
+need-ctty: yes
 arguments: !-i!
 stdin:
 	export PATH=.:$PATH
@@ -6903,6 +8539,7 @@
 file-setup: file 755 "!"
 	#! /bin/sh
 	echo si
+need-ctty: yes
 arguments: !-i!
 stdin:
 	export PATH=.:$PATH
@@ -6929,6 +8566,7 @@
 file-setup: file 755 "!"
 	#! /bin/sh
 	echo si
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!
 file-setup: file 644 "Env"
@@ -7152,11 +8790,28 @@
 	2 a .
 	3 .
 ---
+name: nameref-4
+description:
+	Ensure we don't run in an infinite loop
+time-limit: 3
+stdin:
+	baz() {
+		typeset -n foo=foo
+		foo[0]=bar
+	}
+	set -A foo bad
+	echo sind $foo .
+	baz
+	echo blah $foo .
+expected-stdout:
+	sind bad .
+	blah bar .
+---
 name: better-parens-1a
 description:
 	Check support for ((…)) and $((…)) vs (…) and $(…)
 stdin:
-	if ( (echo fubar) | tr u x); then
+	if ( (echo fubar)|tr u x); then
 		echo ja
 	else
 		echo nein
@@ -7169,7 +8824,15 @@
 description:
 	Check support for ((…)) and $((…)) vs (…) and $(…)
 stdin:
-	echo $( (echo fubar) | tr u x) $?
+	echo $( (echo fubar)|tr u x) $?
+expected-stdout:
+	fxbar 0
+---
+name: better-parens-1c
+description:
+	Check support for ((…)) and $((…)) vs (…) and $(…)
+stdin:
+	x=$( (echo fubar)|tr u x); echo $x $?
 expected-stdout:
 	fxbar 0
 ---
@@ -7177,7 +8840,7 @@
 description:
 	Check support for ((…)) and $((…)) vs (…) and $(…)
 stdin:
-	if ((echo fubar) | tr u x); then
+	if ((echo fubar)|tr u x); then
 		echo ja
 	else
 		echo nein
@@ -7190,7 +8853,15 @@
 description:
 	Check support for ((…)) and $((…)) vs (…) and $(…)
 stdin:
-	echo $((echo fubar) | tr u x) $?
+	echo $((echo fubar)|tr u x) $?
+expected-stdout:
+	fxbar 0
+---
+name: better-parens-2c
+description:
+	Check support for ((…)) and $((…)) vs (…) and $(…)
+stdin:
+	x=$((echo fubar)|tr u x); echo $x $?
 expected-stdout:
 	fxbar 0
 ---
@@ -7198,7 +8869,7 @@
 description:
 	Check support for ((…)) and $((…)) vs (…) and $(…)
 stdin:
-	if ( (echo fubar) | (tr u x)); then
+	if ( (echo fubar)|(tr u x)); then
 		echo ja
 	else
 		echo nein
@@ -7211,7 +8882,15 @@
 description:
 	Check support for ((…)) and $((…)) vs (…) and $(…)
 stdin:
-	echo $( (echo fubar) | (tr u x)) $?
+	echo $( (echo fubar)|(tr u x)) $?
+expected-stdout:
+	fxbar 0
+---
+name: better-parens-3c
+description:
+	Check support for ((…)) and $((…)) vs (…) and $(…)
+stdin:
+	x=$( (echo fubar)|(tr u x)); echo $x $?
 expected-stdout:
 	fxbar 0
 ---
@@ -7219,7 +8898,7 @@
 description:
 	Check support for ((…)) and $((…)) vs (…) and $(…)
 stdin:
-	if ((echo fubar) | (tr u x)); then
+	if ((echo fubar)|(tr u x)); then
 		echo ja
 	else
 		echo nein
@@ -7232,7 +8911,15 @@
 description:
 	Check support for ((…)) and $((…)) vs (…) and $(…)
 stdin:
-	echo $((echo fubar) | (tr u x)) $?
+	echo $((echo fubar)|(tr u x)) $?
+expected-stdout:
+	fxbar 0
+---
+name: better-parens-4c
+description:
+	Check support for ((…)) and $((…)) vs (…) and $(…)
+stdin:
+	x=$((echo fubar)|(tr u x)); echo $x $?
 expected-stdout:
 	fxbar 0
 ---
@@ -7441,3 +9128,117 @@
 	24 ?lnnix/nix =lnnix/nix: No such file or directory !2
 	25 ?lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself =lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself: Too many levels of symbolic links !62
 ---
+name: realpath-2
+description:
+	Ensure that exactly two leading slashes are not collapsed
+	POSIX guarantees this exception, e.g. for UNC paths on Cygwin
+category: os:mirbsd
+stdin:
+	ln -s /bin t1
+	ln -s //bin t2
+	ln -s ///bin t3
+	realpath /bin
+	realpath //bin
+	realpath ///bin
+	realpath /usr/bin
+	realpath /usr//bin
+	realpath /usr///bin
+	realpath t1
+	realpath t2
+	realpath t3
+	rm -f t1 t2 t3
+	cd //usr/bin
+	pwd
+	cd ../lib
+	pwd
+	realpath //usr/include/../bin
+expected-stdout:
+	/bin
+	//bin
+	/bin
+	/usr/bin
+	/usr/bin
+	/usr/bin
+	/bin
+	//bin
+	/bin
+	//usr/bin
+	//usr/lib
+	//usr/bin
+---
+name: crash-1
+description:
+	Crashed during March 2011, fixed on vernal equinōx ☺
+category: os:mirbsd,os:openbsd
+stdin:
+	export MALLOC_OPTIONS=FGJPRSX
+	"$__progname" -c 'x=$(tr z r <<<baz); echo $x'
+expected-stdout:
+	bar
+---
+name: debian-117-1
+description:
+	Check test - bug#465250
+stdin:
+	test \( ! -e \) ; echo $?
+expected-stdout:
+	1
+---
+name: debian-117-2
+description:
+	Check test - bug#465250
+stdin:
+	test \(  -e \) ; echo $?
+expected-stdout:
+	0
+---
+name: debian-117-3
+description:
+	Check test - bug#465250
+stdin:
+	test ! -e  ; echo $?
+expected-stdout:
+	1
+---
+name: debian-117-4
+description:
+	Check test - bug#465250
+stdin:
+	test  -e  ; echo $?
+expected-stdout:
+	0
+---
+name: case-zsh
+description:
+	Check that zsh case variants work
+stdin:
+	case 'b' in
+	  a) echo a ;;
+	  b) echo b ;;
+	  c) echo c ;;
+	  *) echo x ;;
+	esac
+	echo =
+	case 'b' in
+	  a) echo a ;&
+	  b) echo b ;&
+	  c) echo c ;&
+	  *) echo x ;&
+	esac
+	echo =
+	case 'b' in
+	  a) echo a ;|
+	  b) echo b ;|
+	  c) echo c ;|
+	  *) echo x ;|
+	esac
+expected-stdout:
+	b
+	=
+	b
+	c
+	x
+	=
+	b
+	x
+---
diff --git a/src/dot.mkshrc b/src/dot.mkshrc
new file mode 100644
index 0000000..518f031
--- /dev/null
+++ b/src/dot.mkshrc
@@ -0,0 +1,375 @@
+# $Id$
+# $MirOS: src/bin/mksh/dot.mkshrc,v 1.65 2011/08/27 18:06:40 tg Exp $
+#-
+# Copyright (c) 2002, 2003, 2004, 2006, 2007, 2008, 2009, 2010, 2011
+#	Thorsten Glaser <tg@mirbsd.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un-
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person's immediate fault when using the work as intended.
+#-
+# ${ENV:-~/.mkshrc}: mksh initialisation file for interactive shells
+
+: ${EDITOR:=/bin/ed} ${TERM:=vt100} ${HOSTNAME:=$(ulimit -c 0;hostname -s 2>&-)}
+[[ $HOSTNAME = @(localhost|*([	 ])) ]] && HOSTNAME=$(ulimit -c 0;hostname 2>&-)
+: ${HOSTNAME:=nil}; if (( USER_ID )); then PS1='$'; else PS1='#'; fi
+function precmd {
+	local e=$?
+
+	(( e )) && print -n "$e|"
+}
+PS1=$'\001\r''$(precmd)${USER:=$(ulimit -c 0; id -un 2>/dev/null || echo \?
+	)}@${HOSTNAME%%.*}:$(local d=${PWD:-?} p=~; [[ $p = ?(*/) ]] || \
+	d=${d/#$p/~}; local m=${%d} n p=...; (( m > 0 )) || m=${#d}
+	(( m > (n = (COLUMNS/3 < 7 ? 7 : COLUMNS/3)) )) && d=${d:(-n)} || \
+	p=; print -nr -- "$p$d") '"$PS1 "
+: ${MKSH:=$(whence -p mksh)}; export EDITOR HOSTNAME MKSH TERM USER
+alias ls=ls
+unalias ls
+alias l='ls -F'
+alias la='l -a'
+alias ll='l -l'
+alias lo='l -alo'
+whence -p rot13 >&- || alias rot13='tr \
+    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ \
+    nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM'
+whence -p hd >&- || function hd {
+	hexdump -e '"%08.8_ax  " 8/1 "%02X " " - " 8/1 "%02X "' \
+	    -e '"  |" "%_p"' -e '"|\n"' "$@"
+}
+
+# Berkeley C shell compatible dirs, popd, and pushd functions
+# Z shell compatible chpwd() hook, used to update DIRSTACK[0]
+DIRSTACKBASE=$(realpath ~/. 2>&- || print -nr -- "$HOME")
+set -A DIRSTACK
+function chpwd {
+	DIRSTACK[0]=$(realpath . 2>&- || print -r -- "$PWD")
+	[[ $DIRSTACKBASE = ?(*/) ]] || \
+	    DIRSTACK[0]=${DIRSTACK[0]/#$DIRSTACKBASE/~}
+	:
+}
+chpwd .
+function cd {
+	builtin cd "$@"
+	chpwd "$@"
+}
+function cd_csh {
+	local d t=${1/#~/$DIRSTACKBASE}
+
+	if ! d=$(builtin cd "$t" 2>&1); then
+		print -u2 "${1}: ${d##*$t - }."
+		return 1
+	fi
+	cd "$t"
+}
+function dirs {
+	local d dwidth
+	local -i isnoglob=0 fl=0 fv=0 fn=0 cpos=0
+
+	[[ $(set +o) == *@(-o noglob)@(| *) ]] && isnoglob=1
+	set -o noglob
+	while getopts ":lvn" d; do
+		case $d {
+		(l)	fl=1 ;;
+		(v)	fv=1 ;;
+		(n)	fn=1 ;;
+		(*)	print -u2 'Usage: dirs [-lvn].'
+			return 1 ;;
+		}
+	done
+	shift $((OPTIND - 1))
+	if (( $# > 0 )); then
+		print -u2 'Usage: dirs [-lvn].'
+		return 1
+	fi
+	if (( fv )); then
+		fv=0
+		while (( fv < ${#DIRSTACK[*]} )); do
+			d=${DIRSTACK[fv]}
+			(( fl )) && d=${d/#~/$DIRSTACKBASE}
+			print -r -- "$fv	$d"
+			let fv++
+		done
+	else
+		fv=0
+		while (( fv < ${#DIRSTACK[*]} )); do
+			d=${DIRSTACK[fv]}
+			(( fl )) && d=${d/#~/$DIRSTACKBASE}
+			(( dwidth = (${%d} > 0 ? ${%d} : ${#d}) ))
+			if (( fn && (cpos += dwidth + 1) >= 79 && \
+			    dwidth < 80 )); then
+				print
+				(( cpos = dwidth + 1 ))
+			fi
+			print -nr -- "$d "
+			let fv++
+		done
+		print
+	fi
+	(( isnoglob )) || set +o noglob
+	return 0
+}
+function popd {
+	local d fa
+	local -i isnoglob=0 n=1
+
+	[[ $(set +o) == *@(-o noglob)@(| *) ]] && isnoglob=1
+	set -o noglob
+	while getopts ":0123456789lvn" d; do
+		case $d {
+		(l|v|n)	fa="$fa -$d" ;;
+		(+*)	n=2
+			break ;;
+		(*)	print -u2 'Usage: popd [-lvn] [+<n>].'
+			return 1 ;;
+		}
+	done
+	shift $((OPTIND - n))
+	n=0
+	if (( $# > 1 )); then
+		print -u2 popd: Too many arguments.
+		return 1
+	elif [[ $1 = ++([0-9]) && $1 != +0 ]]; then
+		if (( (n = ${1#+}) >= ${#DIRSTACK[*]} )); then
+			print -u2 popd: Directory stack not that deep.
+			return 1
+		fi
+	elif [[ -n $1 ]]; then
+		print -u2 popd: Bad directory.
+		return 1
+	fi
+	if (( ${#DIRSTACK[*]} < 2 )); then
+		print -u2 popd: Directory stack empty.
+		return 1
+	fi
+	unset DIRSTACK[n]
+	set -A DIRSTACK -- "${DIRSTACK[@]}"
+	cd_csh "${DIRSTACK[0]}" || return 1
+	(( isnoglob )) || set +o noglob
+	dirs $fa
+}
+function pushd {
+	local d fa
+	local -i isnoglob=0 n=1
+
+	[[ $(set +o) == *@(-o noglob)@(| *) ]] && isnoglob=1
+	set -o noglob
+	while getopts ":0123456789lvn" d; do
+		case $d {
+		(l|v|n)	fa="$fa -$d" ;;
+		(+*)	n=2
+			break ;;
+		(*)	print -u2 'Usage: pushd [-lvn] [<dir>|+<n>].'
+			return 1 ;;
+		}
+	done
+	shift $((OPTIND - n))
+	if (( $# == 0 )); then
+		if (( ${#DIRSTACK[*]} < 2 )); then
+			print -u2 pushd: No other directory.
+			return 1
+		fi
+		d=${DIRSTACK[1]}
+		DIRSTACK[1]=${DIRSTACK[0]}
+		cd_csh "$d" || return 1
+	elif (( $# > 1 )); then
+		print -u2 pushd: Too many arguments.
+		return 1
+	elif [[ $1 = ++([0-9]) && $1 != +0 ]]; then
+		if (( (n = ${1#+}) >= ${#DIRSTACK[*]} )); then
+			print -u2 pushd: Directory stack not that deep.
+			return 1
+		fi
+		while (( n-- )); do
+			d=${DIRSTACK[0]}
+			unset DIRSTACK[0]
+			set -A DIRSTACK -- "${DIRSTACK[@]}" "$d"
+		done
+		cd_csh "${DIRSTACK[0]}" || return 1
+	else
+		set -A DIRSTACK -- placeholder "${DIRSTACK[@]}"
+		cd_csh "$1" || return 1
+	fi
+	(( isnoglob )) || set +o noglob
+	dirs $fa
+}
+
+# pager (not control character safe)
+function smores {
+	local dummy line llen curlin=0
+
+	cat "$@" | while IFS= read -r line; do
+		llen=${%line}
+		(( llen == -1 )) && llen=${#line}
+		(( llen = llen ? (llen + COLUMNS - 1) / COLUMNS : 1 ))
+		if (( (curlin += llen) >= LINES )); then
+			print -n -- '\033[7m--more--\033[0m'
+			read -u1 dummy
+			[[ $dummy = [Qq]* ]] && return 0
+			curlin=$llen
+		fi
+		print -r -- "$line"
+	done
+}
+
+# base64 encoder and decoder, RFC compliant, NUL safe
+function Lb64decode {
+	[[ -o utf8-mode ]]; local u=$?
+	set +U
+	local c s="$*" t=
+	[[ -n $s ]] || { s=$(cat;print x); s=${s%x}; }
+	local -i i=0 n=${#s} p=0 v x
+	local -i16 o
+
+	while (( i < n )); do
+		c=${s:(i++):1}
+		case $c {
+		(=)	break ;;
+		([A-Z])	(( v = 1#$c - 65 )) ;;
+		([a-z])	(( v = 1#$c - 71 )) ;;
+		([0-9])	(( v = 1#$c + 4 )) ;;
+		(+)	v=62 ;;
+		(/)	v=63 ;;
+		(*)	continue ;;
+		}
+		(( x = (x << 6) | v ))
+		case $((p++)) {
+		(0)	continue ;;
+		(1)	(( o = (x >> 4) & 255 )) ;;
+		(2)	(( o = (x >> 2) & 255 )) ;;
+		(3)	(( o = x & 255 ))
+			p=0
+			;;
+		}
+		t=$t\\x${o#16#}
+	done
+	print -n $t
+	(( u )) || set -U
+}
+
+set -A Lb64encode_code -- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z \
+    a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9 + /
+function Lb64encode {
+	[[ -o utf8-mode ]]; local u=$?
+	set +U
+	local c s t
+	if (( $# )); then
+		read -raN-1 s <<<"$*"
+		unset s[${#s[*]}-1]
+	else
+		read -raN-1 s
+	fi
+	local -i i=0 n=${#s[*]} j v
+
+	while (( i < n )); do
+		(( v = s[i++] << 16 ))
+		(( j = i < n ? s[i++] : 0 ))
+		(( v |= j << 8 ))
+		(( j = i < n ? s[i++] : 0 ))
+		(( v |= j ))
+		t=$t${Lb64encode_code[v >> 18]}${Lb64encode_code[v >> 12 & 63]}
+		c=${Lb64encode_code[v >> 6 & 63]}
+		if (( i <= n )); then
+			t=$t$c${Lb64encode_code[v & 63]}
+		elif (( i == n + 1 )); then
+			t=$t$c=
+		else
+			t=$t==
+		fi
+		if (( ${#t} == 76 || i >= n )); then
+			print $t
+			t=
+		fi
+	done
+	(( u )) || set -U
+}
+
+# mksh NUL counting, never zero
+typeset -Z11 -Uui16 Lnzathash_v
+function Lnzathash_add {
+	[[ -o utf8-mode ]]; local u=$?
+	set +U
+	local s
+	if (( $# )); then
+		read -raN-1 s <<<"$*"
+		unset s[${#s[*]}-1]
+	else
+		read -raN-1 s
+	fi
+	local -i i=0 n=${#s[*]}
+
+	while (( i < n )); do
+		((# Lnzathash_v = (Lnzathash_v + s[i++] + 1) * 1025 ))
+		((# Lnzathash_v ^= Lnzathash_v >> 6 ))
+	done
+
+	(( u )) || set -U
+}
+function Lnzaathash_end {
+	((# Lnzathash_v *= 1025 ))
+	((# Lnzathash_v ^= Lnzathash_v >> 6 ))
+	((# Lnzathash_v += Lnzathash_v << 3 ))
+	((# Lnzathash_v = (Lnzathash_v ^
+	    (Lnzathash_v >> 11)) * 32769 ))
+	print ${Lnzathash_v#16#}
+}
+function Lnzaathash {
+	Lnzathash_v=0
+	Lnzathash_add "$@"
+	Lnzaathash_end
+}
+function Lnzathash {
+	Lnzathash_v=0
+	Lnzathash_add "$@"
+	if (( Lnzathash_v )); then
+		Lnzaathash_end
+	else
+		Lnzathash_v=1
+		print ${Lnzathash_v#16#}
+	fi
+}
+
+# strip comments (and leading/trailing whitespace if IFS is set) from
+# any file(s) given as argument, or stdin if none, and spew to stdout
+function Lstripcom {
+	cat "$@" | { set -o noglob; while read _line; do
+		_line=${_line%%#*}
+		[[ -n $_line ]] && print -r -- $_line
+	done; }
+}
+
+# give MidnightBSD's laffer1 a bit of csh feeling
+function setenv {
+	eval export $1'="$2"'
+}
+
+: place customisations below this line
+
+for p in ~/.etc/bin ~/bin; do
+	[[ -d $p/. ]] || continue
+	[[ :$PATH: = *:$p:* ]] || PATH=$p:$PATH
+done
+
+export SHELL=$MKSH MANWIDTH=80 LESSHISTFILE=-
+alias cls='print -n \\033c'
+
+#unset LANGUAGE LC_ADDRESS LC_ALL LC_COLLATE LC_IDENTIFICATION LC_MONETARY \
+#    LC_NAME LC_NUMERIC LC_TELEPHONE LC_TIME
+#p=en_GB.UTF-8
+#set -U
+#export LANG=C LC_CTYPE=$p LC_MEASUREMENT=$p LC_MESSAGES=$p LC_PAPER=$p
+
+unset p
+
+: place customisations above this line
diff --git a/src/edit.c b/src/edit.c
index 905de7e..8242563 100644
--- a/src/edit.c
+++ b/src/edit.c
@@ -1,10 +1,10 @@
 /*	$OpenBSD: edit.c,v 1.34 2010/05/20 01:13:07 fgsch Exp $	*/
-/*	$OpenBSD: edit.h,v 1.8 2005/03/28 21:28:22 deraadt Exp $	*/
-/*	$OpenBSD: emacs.c,v 1.42 2009/06/02 06:47:47 halex Exp $	*/
+/*	$OpenBSD: edit.h,v 1.9 2011/05/30 17:14:35 martynas Exp $	*/
+/*	$OpenBSD: emacs.c,v 1.44 2011/09/05 04:50:33 marco Exp $	*/
 /*	$OpenBSD: vi.c,v 1.26 2009/06/29 22:50:19 martynas Exp $	*/
 
 /*-
- * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
  *	Thorsten Glaser <tg@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -25,7 +25,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/edit.c,v 1.196 2010/07/25 11:35:40 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/edit.c,v 1.222 2011/10/07 19:45:08 tg Exp $");
 
 /*
  * in later versions we might use libtermcap for this, but since external
@@ -52,11 +52,14 @@
 
 static X_chars edchars;
 
-/* x_fc_glob() flags */
+/* x_cf_glob() flags */
 #define XCF_COMMAND	BIT(0)	/* Do command completion */
 #define XCF_FILE	BIT(1)	/* Do file completion */
 #define XCF_FULLPATH	BIT(2)	/* command completion: store full path */
-#define XCF_COMMAND_FILE (XCF_COMMAND|XCF_FILE)
+#define XCF_COMMAND_FILE (XCF_COMMAND | XCF_FILE)
+#define XCF_IS_COMMAND	BIT(3)	/* return flag: is command */
+#define XCF_IS_SUBGLOB	BIT(4)	/* return flag: is $FOO or ~foo substitution */
+#define XCF_IS_EXTGLOB	BIT(5)	/* return flag: is foo* expansion */
 
 static char editmode;
 static int xx_cols;			/* for Emacs mode */
@@ -65,12 +68,11 @@
 
 static int x_getc(void);
 static void x_putcf(int);
-static bool x_mode(bool);
-static int x_do_comment(char *, int, int *);
+static void x_mode(bool);
+static int x_do_comment(char *, ssize_t, ssize_t *);
 static void x_print_expansions(int, char *const *, bool);
-static int x_cf_glob(int, const char *, int, int, int *, int *, char ***,
-    bool *);
-static int x_longest_prefix(int, char *const *);
+static int x_cf_glob(int *, const char *, int, int, int *, int *, char ***);
+static size_t x_longest_prefix(int, char *const *);
 static int x_basename(const char *, const char *);
 static void x_free_words(int, char **);
 static int x_escape(const char *, size_t, int (*)(const char *, size_t));
@@ -89,17 +91,10 @@
 #endif
 
 static int path_order_cmp(const void *aa, const void *bb);
-static char *add_glob(const char *, int)
-    MKSH_A_NONNULL((nonnull (1)))
-    MKSH_A_BOUNDED(string, 1, 2);
 static void glob_table(const char *, XPtrV *, struct table *);
 static void glob_path(int flags, const char *, XPtrV *, const char *);
-static int x_file_glob(int, const char *, int, char ***)
-    MKSH_A_NONNULL((nonnull (2)))
-    MKSH_A_BOUNDED(string, 2, 3);
-static int x_command_glob(int, const char *, int, char ***)
-    MKSH_A_NONNULL((nonnull (2)))
-    MKSH_A_BOUNDED(string, 2, 3);
+static int x_file_glob(int, char *, char ***);
+static int x_command_glob(int, char *, char ***);
 static int x_locate_word(const char *, int, int, int *, bool *);
 
 static int x_e_getmbc(char *);
@@ -111,11 +106,14 @@
 void
 x_init(void)
 {
-	/* set to -2 to force initial binding */
+	/*
+	 * Set edchars to -2 to force initial binding, except
+	 * we need default values for some deficient systems…
+	 */
 	edchars.erase = edchars.kill = edchars.intr = edchars.quit =
 	    edchars.eof = -2;
-	/* default value for deficient systems */
-	edchars.werase = 027;	/* ^W */
+	/* ^W */
+	edchars.werase = 027;
 	x_init_emacs();
 }
 
@@ -136,7 +134,8 @@
 		i = x_vi(buf, len);
 #endif
 	else
-		i = -1;		/* internal error */
+		/* internal error */
+		i = -1;
 	editmode = 0;
 	x_mode(false);
 	return (i);
@@ -148,7 +147,7 @@
 x_getc(void)
 {
 	char c;
-	int n;
+	ssize_t n;
 
 	while ((n = blocking_read(STDIN_FILENO, &c, 1)) < 0 && errno == EINTR)
 		if (trap) {
@@ -179,7 +178,8 @@
  * Misc common code for vi/emacs *
  *********************************/
 
-/* Handle the commenting/uncommenting of a line.
+/*-
+ * Handle the commenting/uncommenting of a line.
  * Returns:
  *	1 if a carriage return is indicated (comment added)
  *	0 if no return (comment removed)
@@ -188,12 +188,13 @@
  * moved to the start of the line after (un)commenting.
  */
 static int
-x_do_comment(char *buf, int bsize, int *lenp)
+x_do_comment(char *buf, ssize_t bsize, ssize_t *lenp)
 {
-	int i, j, len = *lenp;
+	ssize_t i, j, len = *lenp;
 
 	if (len == 0)
-		return (1); /* somewhat arbitrary - it's what AT&T ksh does */
+		/* somewhat arbitrary - it's what AT&T ksh does */
+		return (1);
 
 	/* Already commented? */
 	if (buf[0] == '#') {
@@ -238,7 +239,8 @@
 	int prefix_len;
 	XPtrV l = { NULL, NULL, NULL };
 
-	/* Check if all matches are in the same directory (in this
+	/*
+	 * Check if all matches are in the same directory (in this
 	 * case, we want to omit the directory name)
 	 */
 	if (!is_command &&
@@ -272,7 +274,8 @@
 	pr_list(use_copy ? (char **)XPptrv(l) : words);
 
 	if (use_copy)
-		XPfree(l); /* not x_free_words() */
+		/* not x_free_words() */
+		XPfree(l);
 }
 
 /**
@@ -283,19 +286,14 @@
  *	- returns number of matching strings
  */
 static int
-x_file_glob(int flags MKSH_A_UNUSED, const char *str, int slen, char ***wordsp)
+x_file_glob(int flags MKSH_A_UNUSED, char *toglob, char ***wordsp)
 {
-	char *toglob, **words;
+	char **words;
 	int nwords, i, idx;
 	bool escaping;
 	XPtrV w;
 	struct source *s, *sold;
 
-	if (slen < 0)
-		return (0);
-
-	toglob = add_glob(str, slen);
-
 	/* remove all escaping backward slashes */
 	escaping = false;
 	for (i = 0, idx = 0; toglob[i]; i++) {
@@ -324,7 +322,7 @@
 	source = s;
 	if (yylex(ONEWORD | LQCHAR) != LWORD) {
 		source = sold;
-		internal_warningf("fileglob: substitute error");
+		internal_warningf("%s: %s", "fileglob", "bad substitution");
 		return (0);
 	}
 	source = sold;
@@ -338,8 +336,9 @@
 	if (nwords == 1) {
 		struct stat statb;
 
-		/* Check if globbing failed (returned glob pattern),
-		 * but be careful (E.g. toglob == "ab*" when the file
+		/*
+		 * Check if globbing failed (returned glob pattern),
+		 * but be careful (e.g. toglob == "ab*" when the file
 		 * "ab*" exists is not an error).
 		 * Also, check for empty result - happens if we tried
 		 * to glob something which evaluated to an empty
@@ -353,7 +352,6 @@
 			nwords = 0;
 		}
 	}
-	afree(toglob, ATEMP);
 
 	if ((*wordsp = nwords ? words : NULL) == NULL && words != NULL)
 		x_free_words(nwords, words);
@@ -381,21 +379,15 @@
 }
 
 static int
-x_command_glob(int flags, const char *str, int slen, char ***wordsp)
+x_command_glob(int flags, char *toglob, char ***wordsp)
 {
-	char *toglob, *pat, *fpath;
+	char *pat, *fpath;
 	int nwords;
 	XPtrV w;
 	struct block *l;
 
-	if (slen < 0)
-		return (0);
-
-	toglob = add_glob(str, slen);
-
 	/* Convert "foo*" (toglob) to a pattern for future use */
 	pat = evalstr(toglob, DOPAT | DOTILDE);
-	afree(toglob, ATEMP);
 
 	XPinit(w, 32);
 
@@ -424,7 +416,7 @@
 		int i, path_order = 0;
 
 		info = (struct path_order_info *)
-		    alloc(nwords * sizeof(struct path_order_info), ATEMP);
+		    alloc2(nwords, sizeof(struct path_order_info), ATEMP);
 		for (i = 0; i < nwords; i++) {
 			info[i].word = words[i];
 			info[i].base = x_basename(words[i], NULL);
@@ -481,7 +473,8 @@
 	/* The case where pos == buflen happens to take care of itself... */
 
 	start = pos;
-	/* Keep going backwards to start of word (has effect of allowing
+	/*
+	 * Keep going backwards to start of word (has effect of allowing
 	 * one blank after the end of a word)
 	 */
 	for (; (start > 0 && IS_WORDC(buf[start - 1])) ||
@@ -502,7 +495,8 @@
 			p--;
 		iscmd = p < 0 || vstrchr(";|&()`", buf[p]);
 		if (iscmd) {
-			/* If command has a /, path, etc. is not searched;
+			/*
+			 * If command has a /, path, etc. is not searched;
 			 * only current directory is searched which is just
 			 * like file globbing.
 			 */
@@ -519,86 +513,105 @@
 }
 
 static int
-x_cf_glob(int flags, const char *buf, int buflen, int pos, int *startp,
-    int *endp, char ***wordsp, bool *is_commandp)
+x_cf_glob(int *flagsp, const char *buf, int buflen, int pos, int *startp,
+    int *endp, char ***wordsp)
 {
-	int len, nwords;
+	int len, nwords = 0;
 	char **words = NULL;
 	bool is_command;
 
 	len = x_locate_word(buf, buflen, pos, startp, &is_command);
-	if (!(flags & XCF_COMMAND))
+	if (!((*flagsp) & XCF_COMMAND))
 		is_command = false;
-	/* Don't do command globing on zero length strings - it takes too
+	/*
+	 * Don't do command globing on zero length strings - it takes too
 	 * long and isn't very useful. File globs are more likely to be
 	 * useful, so allow these.
 	 */
 	if (len == 0 && is_command)
 		return (0);
 
-	nwords = is_command ?
-	    x_command_glob(flags, buf + *startp, len, &words) :
-	    x_file_glob(flags, buf + *startp, len, &words);
+	if (len >= 0) {
+		char *toglob, *s;
+		bool saw_dollar = false, saw_glob = false;
+
+		/*
+		 * Given a string, copy it and possibly add a '*' to the end.
+		 */
+
+		strndupx(toglob, buf + *startp, len + /* the '*' */ 1, ATEMP);
+		toglob[len] = '\0';
+
+		/*
+		 * If the pathname contains a wildcard (an unquoted '*',
+		 * '?', or '[') or parameter expansion ('$'), or a ~username
+		 * with no trailing slash, then it is globbed based on that
+		 * value (i.e., without the appended '*').
+		 */
+		for (s = toglob; *s; s++) {
+			if (*s == '\\' && s[1])
+				s++;
+			else if (*s == '$') {
+				/*
+				 * Do not append a space after the value
+				 * if expanding a parameter substitution
+				 * as in: “cat $HOME/.ss↹” (LP: #710539)
+				 */
+				saw_dollar = true;
+			} else if (*s == '?' || *s == '*' || *s == '[' ||
+			    /* ?() *() +() @() !() but two already checked */
+			    (s[1] == '(' /*)*/ &&
+			    (*s == '+' || *s == '@' || *s == '!'))) {
+				/* just expand based on the extglob */
+				saw_glob = true;
+			}
+		}
+		if (saw_glob) {
+			/*
+			 * do not append a glob, we already have a
+			 * glob or extglob; it works even if this is
+			 * a parameter expansion as we have a glob
+			 */
+			*flagsp |= XCF_IS_EXTGLOB;
+		} else if (saw_dollar ||
+		    (*toglob == '~' && !vstrchr(toglob, '/'))) {
+			/* do not append a glob, nor later a space */
+			*flagsp |= XCF_IS_SUBGLOB;
+		} else {
+			/* append a glob, this is not just a tilde */
+			toglob[len] = '*';
+			toglob[len + 1] = '\0';
+		}
+
+		/*
+		 * Expand (glob) it now.
+		 */
+
+		nwords = is_command ?
+		    x_command_glob(*flagsp, toglob, &words) :
+		    x_file_glob(*flagsp, toglob, &words);
+		afree(toglob, ATEMP);
+	}
 	if (nwords == 0) {
 		*wordsp = NULL;
 		return (0);
 	}
-	if (is_commandp)
-		*is_commandp = is_command;
+	if (is_command)
+		*flagsp |= XCF_IS_COMMAND;
 	*wordsp = words;
 	*endp = *startp + len;
 
 	return (nwords);
 }
 
-/* Given a string, copy it and possibly add a '*' to the end.
- * The new string is returned.
- */
-static char *
-add_glob(const char *str, int slen)
-{
-	char *toglob, *s;
-	bool saw_slash = false;
-
-	if (slen < 0)
-		return (NULL);
-
-	/* for clang's static analyser, the nonnull attribute isn't enough */
-	mkssert(str != NULL);
-
-	strndupx(toglob, str, slen + 1, ATEMP); /* + 1 for "*" */
-	toglob[slen] = '\0';
-
-	/*
-	 * If the pathname contains a wildcard (an unquoted '*',
-	 * '?', or '[') or parameter expansion ('$'), or a ~username
-	 * with no trailing slash, then it is globbed based on that
-	 * value (i.e., without the appended '*').
-	 */
-	for (s = toglob; *s; s++) {
-		if (*s == '\\' && s[1])
-			s++;
-		else if (*s == '*' || *s == '[' || *s == '?' || *s == '$' ||
-		    (s[1] == '(' /*)*/ && /* *s in '*','?' already checked */
-		    (*s == '+' || *s == '@' || *s == '!')))
-			break;
-		else if (*s == '/')
-			saw_slash = true;
-	}
-	if (!*s && (*toglob != '~' || saw_slash)) {
-		toglob[slen] = '*';
-		toglob[slen + 1] = '\0';
-	}
-	return (toglob);
-}
-
 /*
  * Find longest common prefix
  */
-static int
+static size_t
 x_longest_prefix(int nwords, char * const * words)
 {
-	int i, j, prefix_len;
+	int i;
+	size_t j, prefix_len;
 	char *p;
 
 	if (nwords <= 0)
@@ -622,7 +635,8 @@
 	afree(words, ATEMP);
 }
 
-/* Return the offset of the basename of string s (which ends at se - need not
+/*-
+ * Return the offset of the basename of string s (which ends at se - need not
  * be null terminated). Trailing slashes are ignored. If s is just a slash,
  * then the offset is 0 (actually, length - 1).
  *	s		Return
@@ -678,13 +692,14 @@
 static void
 glob_path(int flags, const char *pat, XPtrV *wp, const char *lpath)
 {
-	const char *sp, *p;
+	const char *sp = lpath, *p;
 	char *xp, **words;
-	int staterr, pathlen, patlen, oldsize, newsize, i, j;
+	size_t pathlen, patlen, oldsize, newsize, i, j;
 	XString xs;
 
-	patlen = strlen(pat) + 1;
-	sp = lpath;
+	patlen = strlen(pat);
+	checkoktoadd(patlen, 129 + X_EXTRA);
+	++patlen;
 	Xinit(xs, xp, patlen + 128, ATEMP);
 	while (sp) {
 		xp = Xstring(xs, xp);
@@ -692,7 +707,8 @@
 			p = sp + strlen(sp);
 		pathlen = p - sp;
 		if (pathlen) {
-			/* Copy sp into xp, stuffing any MAGIC characters
+			/*
+			 * Copy sp into xp, stuffing any MAGIC characters
 			 * on the way
 			 */
 			const char *s = sp;
@@ -711,15 +727,14 @@
 		memcpy(xp, pat, patlen);
 
 		oldsize = XPsize(*wp);
-		glob_str(Xstring(xs, xp), wp, 1); /* mark dirs */
+		/* mark dirs */
+		glob_str(Xstring(xs, xp), wp, 1);
 		newsize = XPsize(*wp);
 
 		/* Check that each match is executable... */
 		words = (char **)XPptrv(*wp);
 		for (i = j = oldsize; i < newsize; i++) {
-			staterr = 0;
-			if ((search_access(words[i], X_OK, &staterr) >= 0) ||
-			    (staterr == EISDIR)) {
+			if (ksh_access(words[i], X_OK) == 0) {
 				words[j] = words[i];
 				if (!(flags & XCF_FULLPATH))
 					memmove(words[j], words[j] + pathlen,
@@ -807,7 +822,8 @@
 #define X_NTABS		3			/* normal, meta1, meta2 */
 #define X_TABSZ		256			/* size of keydef tables etc */
 
-/* Arguments for do_complete()
+/*-
+ * Arguments for do_complete()
  * 0 = enumerate	M-=	complete as much as possible and then list
  * 1 = complete		M-Esc
  * 2 = list		M-?
@@ -837,7 +853,7 @@
 static int x_col;
 static int x_displen;
 static int x_arg;		/* general purpose arg */
-static int x_arg_defaulted;	/* x_arg not explicitly set; defaulted to 1 */
+static bool x_arg_defaulted;	/* x_arg not explicitly set; defaulted to 1 */
 
 static int xlp_valid;
 
@@ -855,7 +871,7 @@
 static int killsp, killtp;
 static int x_curprefix;
 #ifndef MKSH_SMALL
-static char *macroptr = NULL;	/* bind key macro active? */
+static char *macroptr;		/* bind key macro active? */
 #endif
 #if !MKSH_S_NOVI
 static int cur_col;		/* current column on line */
@@ -891,7 +907,7 @@
 static void x_redraw(int);
 static void x_push(int);
 static char *x_mapin(const char *, Area *)
-    MKSH_A_NONNULL((nonnull (1)));
+    MKSH_A_NONNULL((__nonnull__ (1)));
 static char *x_mapout(int);
 static void x_mapout2(int, char **);
 static void x_print(int, int);
@@ -998,7 +1014,8 @@
 	{ XFUNC_fold_capitalise,	1,	'C'	},
 	{ XFUNC_fold_capitalise,	1,	'c'	},
 #endif
-	/* These for ansi arrow keys: arguablely shouldn't be here by
+	/*
+	 * These for ANSI arrow keys: arguablely shouldn't be here by
 	 * default, but its simpler/faster/smaller than using termcap
 	 * entries.
 	 */
@@ -1120,7 +1137,7 @@
 		x_nextcmd = -1;
 	}
 	editmode = 1;
-	while (1) {
+	while (/* CONSTCOND */ 1) {
 		x_flush();
 		if ((c = x_e_getc()) < 0)
 			return (0);
@@ -1142,7 +1159,7 @@
 		if (!(x_ftab[f].xf_flags & XF_PREFIX) &&
 		    x_last_command != XFUNC_set_arg) {
 			x_arg = 1;
-			x_arg_defaulted = 1;
+			x_arg_defaulted = true;
 		}
 		i = c | (x_curprefix << 8);
 		x_curprefix = 0;
@@ -1154,7 +1171,8 @@
 		case KEOL:
 			i = xep - xbuf;
 			return (i);
-		case KINTR:	/* special case for interrupt */
+		case KINTR:
+			/* special case for interrupt */
 			trapsig(SIGINT);
 			x_mode(false);
 			unwind(LSHELL);
@@ -1167,7 +1185,7 @@
 static int
 x_insert(int c)
 {
-	static int left = 0, pos, save_arg;
+	static int left, pos, save_arg;
 	static char str[4];
 
 	/*
@@ -1265,7 +1283,8 @@
 	x_lastcp();
 	x_adj_ok = (xcp >= xlp);
 	x_zots(cp);
-	if (adj == x_adj_done) {	/* has x_adjust() been called? */
+	/* has x_adjust() been called? */
+	if (adj == x_adj_done) {
 		/* no */
 		cp = xlp;
 		while (cp > xcp)
@@ -1354,13 +1373,15 @@
 		x_push(nb);
 
 	xep -= nb;
-	memmove(xcp, xcp + nb, xep - xcp + 1);	/* Copies the NUL */
-	x_adj_ok = 0;			/* don't redraw */
+	/* Copies the NUL */
+	memmove(xcp, xcp + nb, xep - xcp + 1);
+	/* don't redraw */
+	x_adj_ok = 0;
 	xlp_valid = false;
 	x_zots(xcp);
 	/*
 	 * if we are already filling the line,
-	 * there is no need to ' ','\b'.
+	 * there is no need to ' ', '\b'.
 	 * But if we must, make sure we do the minimum.
 	 */
 	if ((i = xx_cols - 2 - x_col) > 0 || xep - xlp == 0) {
@@ -1472,10 +1493,12 @@
 		/* we are heading off screen */
 		xcp = cp;
 		x_adjust();
-	} else if (cp < xcp) {		/* move back */
+	} else if (cp < xcp) {
+		/* move back */
 		while (cp < xcp)
 			x_bs3(&xcp);
-	} else if (cp > xcp) {		/* move forward */
+	} else if (cp > xcp) {
+		/* move forward */
 		while (cp > xcp)
 			x_zotc3(&xcp);
 	}
@@ -1515,9 +1538,11 @@
 	if (dcp)
 		*dcp = cp + 1;
 	if (c == '\t')
-		return (4);	/* Kludge, tabs are always four spaces. */
+		/* Kludge, tabs are always four spaces. */
+		return (4);
 	if (c < ' ' || c == 0x7f)
-		return (2);	/* control unsigned char */
+		/* control unsigned char */
+		return (2);
 	return (1);
 }
 
@@ -1700,7 +1725,8 @@
 	return (KSTD);
 }
 
-/* Goto a particular history number obtained from argument.
+/*
+ * Goto a particular history number obtained from argument.
  * If no argument is given history 1 is probably not what you
  * want so we'll simply go to the oldest one.
  */
@@ -1770,7 +1796,7 @@
 	unsigned char f;
 
 	*p = '\0';
-	while (1) {
+	while (/* CONSTCOND */ 1) {
 		if (offset < 0) {
 			x_e_puts("\nI-search: ");
 			x_e_puts(pat);
@@ -1833,7 +1859,8 @@
 			if (offset >= 0)
 				x_load_hist(histptr + 1);
 			break;
-		} else { /* other command */
+		} else {
+			/* other command */
 			x_e_ungetc(c);
 			break;
 		}
@@ -1965,7 +1992,8 @@
 	return (x_e_rebuildline(MKSH_CLS_STRING));
 }
 
-/* Redraw (part of) the line. If limit is < 0, the everything is redrawn
+/*
+ * Redraw (part of) the line. If limit is < 0, the everything is redrawn
  * on a NEW line, otherwise limit is the screen column up to which needs
  * redrawing.
  */
@@ -2002,7 +2030,8 @@
 		limit = xx_cols;
 	if (limit >= 0) {
 		if (xep > xlp)
-			i = 0;			/* we fill the line */
+			/* we fill the line */
+			i = 0;
 		else {
 			char *cpl = xbp;
 
@@ -2019,7 +2048,8 @@
 			j++;
 		}
 		i = ' ';
-		if (xep > xlp) {		/* more off screen */
+		if (xep > xlp) {
+			/* more off screen */
 			if (xbp > xbuf)
 				i = '*';
 			else
@@ -2043,7 +2073,8 @@
 {
 	unsigned int tmpa, tmpb;
 
-	/* What transpose is meant to do seems to be up for debate. This
+	/*-
+	 * What transpose is meant to do seems to be up for debate. This
 	 * is a general summary of the options; the text is abcd with the
 	 * upper case character or underscore indicating the cursor position:
 	 *	Who			Before	After	Before	After
@@ -2064,8 +2095,9 @@
 			x_e_putc2(7);
 			return (KSTD);
 		}
-		/* Gosling/Unipress emacs style: Swap two characters before the
-		 * cursor, do not change cursor position
+		/*
+		 * Gosling/Unipress emacs style: Swap two characters before
+		 * the cursor, do not change cursor position
 		 */
 		x_bs3(&xcp);
 		if (utf_mbtowc(&tmpa, xcp) == (size_t)-1) {
@@ -2082,7 +2114,8 @@
 		utf_wctomb(xcp, tmpb);
 		x_zotc3(&xcp);
 	} else {
-		/* GNU emacs style: Swap the characters before and under the
+		/*
+		 * GNU emacs style: Swap the characters before and under the
 		 * cursor, move cursor position along one.
 		 */
 		if (utf_mbtowc(&tmpa, xcp) == (size_t)-1) {
@@ -2177,7 +2210,7 @@
 static int
 x_meta_yank(int c MKSH_A_UNUSED)
 {
-	int len;
+	size_t len;
 
 	if ((x_last_command != XFUNC_yank && x_last_command != XFUNC_meta_yank) ||
 	    killstack[killtp] == 0) {
@@ -2230,7 +2263,7 @@
 	switch ((c = x_e_getc())) {
 	case '~':
 		x_arg = 1;
-		x_arg_defaulted = 1;
+		x_arg_defaulted = true;
 		return (x_mv_begin(0));
 	case ';':
 		/* "interesting" sequence detected */
@@ -2245,7 +2278,7 @@
 
 	/*-
 	 * At this point, we have read the following octets so far:
-	 * - ESC+[ or ESC+O or Ctrl-X (Præfix 2)
+	 * - ESC+[ or ESC+O or Ctrl-X (Prefix 2)
 	 * - 1 (vt_hack)
 	 * - ;
 	 * - 5 (Ctrl key combiner) or 3 (Alt key combiner)
@@ -2279,7 +2312,8 @@
 		/* XXX -- should handle \^ escape? */
 		if (*cp == '^') {
 			cp++;
-			if (*cp >= '?')	/* includes '?'; ASCII */
+			if (*cp >= '?')
+				/* includes '?'; ASCII */
 				*op++ = CTRL(*cp);
 			else {
 				*op++ = '^';
@@ -2343,9 +2377,11 @@
 int
 x_bind(const char *a1, const char *a2,
 #ifndef MKSH_SMALL
-    bool macro,			/* bind -m */
+    /* bind -m */
+    bool macro,
 #endif
-    bool list)			/* bind -l */
+    /* bind -l */
+    bool list)
 {
 	unsigned char f;
 	int prefix, key;
@@ -2356,7 +2392,7 @@
 #endif
 
 	if (x_tab == NULL) {
-		bi_errorf("cannot bind, not a tty");
+		bi_errorf("can't bind, not a tty");
 		return (1);
 	}
 	/* List function names */
@@ -2398,16 +2434,16 @@
 	    && ((*m1 != '~') || *(m1 + 1))
 #endif
 	    ) {
-		char msg[256] = "key sequence '";
+		char msg[256];
 		const char *c = a1;
-		m1 = msg + strlen(msg);
+		m1 = msg;
 		while (*c && m1 < (msg + sizeof(msg) - 3))
 			x_mapout2(*c++, &m1);
-		bi_errorf("%s' too long", msg);
+		bi_errorf("%s: %s", "too long key sequence", msg);
 		return (1);
 	}
 #ifndef MKSH_SMALL
-	hastilde = *m1;
+	hastilde = tobool(*m1);
 #endif
 	afree(m2, ATEMP);
 
@@ -2428,7 +2464,7 @@
 			    strcmp(x_ftab[f].xf_name, a2) == 0)
 				break;
 		if (f == NELEM(x_ftab) || x_ftab[f].xf_flags & XF_NOBIND) {
-			bi_errorf("%s: no such function", a2);
+			bi_errorf("%s: %s %s", a2, "no such", Tfunction);
 			return (1);
 		}
 	}
@@ -2466,7 +2502,7 @@
 	ainit(AEDIT);
 	x_nextcmd = -1;
 
-	x_tab = alloc(X_NTABS * sizeof(*x_tab), AEDIT);
+	x_tab = alloc2(X_NTABS, sizeof(*x_tab), AEDIT);
 	for (j = 0; j < X_TABSZ; j++)
 		x_tab[0][j] = XFUNC_insert;
 	for (i = 1; i < X_NTABS; i++)
@@ -2477,7 +2513,7 @@
 		    = x_defbindings[i].xdb_func;
 
 #ifndef MKSH_SMALL
-	x_atab = alloc(X_NTABS * sizeof(*x_atab), AEDIT);
+	x_atab = alloc2(X_NTABS, sizeof(*x_atab), AEDIT);
 	for (i = 1; i < X_NTABS; i++)
 		for (j = 0; j < X_TABSZ; j++)
 			x_atab[i][j] = NULL;
@@ -2603,10 +2639,10 @@
 {
 	char **words;
 	int start, end, nwords, i;
-	bool is_command;
 
-	nwords = x_cf_glob(XCF_FILE, xbuf, xep - xbuf, xcp - xbuf,
-	    &start, &end, &words, &is_command);
+	i = XCF_FILE;
+	nwords = x_cf_glob(&i, xbuf, xep - xbuf, xcp - xbuf,
+	    &start, &end, &words);
 
 	if (nwords == 0) {
 		x_e_putc2(7);
@@ -2614,7 +2650,9 @@
 	}
 	x_goto(xbuf + start);
 	x_delete(end - start, false);
-	for (i = 0; i < nwords;) {
+
+	i = 0;
+	while (i < nwords) {
 		if (x_escape(words[i], strlen(words[i]), x_do_ins) < 0 ||
 		    (++i < nwords && x_ins(" ") < 0)) {
 			x_e_putc2(7);
@@ -2626,24 +2664,27 @@
 	return (KSTD);
 }
 
-/* type == 0 for list, 1 for complete and 2 for complete-list */
 static void
-do_complete(int flags,	/* XCF_{COMMAND,FILE,COMMAND_FILE} */
+do_complete(
+    /* XCF_{COMMAND,FILE,COMMAND_FILE} */
+    int flags,
+    /* 0 for list, 1 for complete and 2 for complete-list */
     Comp_type type)
 {
 	char **words;
 	int start, end, nlen, olen, nwords;
-	bool is_command, completed = false;
+	bool completed = false;
 
-	nwords = x_cf_glob(flags, xbuf, xep - xbuf, xcp - xbuf,
-	    &start, &end, &words, &is_command);
+	nwords = x_cf_glob(&flags, xbuf, xep - xbuf, xcp - xbuf,
+	    &start, &end, &words);
 	/* no match */
 	if (nwords == 0) {
 		x_e_putc2(7);
 		return;
 	}
 	if (type == CT_LIST) {
-		x_print_expansions(nwords, words, is_command);
+		x_print_expansions(nwords, words,
+		    tobool(flags & XCF_IS_COMMAND));
 		x_redraw(0);
 		x_free_words(nwords, words);
 		return;
@@ -2658,13 +2699,18 @@
 		x_adjust();
 		completed = true;
 	}
-	/* add space if single non-dir match */
-	if (nwords == 1 && words[0][nlen - 1] != '/') {
+	/*
+	 * append a space if this is a single non-directory match
+	 * and not a parameter or homedir substitution
+	 */
+	if (nwords == 1 && words[0][nlen - 1] != '/' &&
+	    !(flags & XCF_IS_SUBGLOB)) {
 		x_ins(" ");
 		completed = true;
 	}
 	if (type == CT_COMPLIST && !completed) {
-		x_print_expansions(nwords, words, is_command);
+		x_print_expansions(nwords, words,
+		    tobool(flags & XCF_IS_COMMAND));
 		completed = true;
 	}
 	if (completed)
@@ -2673,7 +2719,8 @@
 	x_free_words(nwords, words);
 }
 
-/* NAME:
+/*-
+ * NAME:
  *	x_adjust - redraw the line adjusting starting point etc.
  *
  * DESCRIPTION:
@@ -2689,7 +2736,8 @@
 static void
 x_adjust(void)
 {
-	x_adj_done++;			/* flag the fact that we were called. */
+	/* flag the fact that we were called. */
+	x_adj_done++;
 	/*
 	 * we had a problem if the prompt length > xx_cols / 2
 	 */
@@ -2817,7 +2865,8 @@
 		x_e_putc3(&s);
 }
 
-/* NAME:
+/*-
+ * NAME:
  *	x_set_arg - set an arg value for next function
  *
  * DESCRIPTION:
@@ -2829,19 +2878,28 @@
 static int
 x_set_arg(int c)
 {
-	int n = 0, first = 1;
+	unsigned int n = 0;
+	bool first = true;
 
-	c &= 255;	/* strip command prefix */
-	for (; c >= 0 && ksh_isdigit(c); c = x_e_getc(), first = 0)
+	/* strip command prefix */
+	c &= 255;
+	while (c >= 0 && ksh_isdigit(c)) {
 		n = n * 10 + (c - '0');
+		if (n > LINE)
+			/* upper bound for repeat */
+			goto x_set_arg_too_big;
+		c = x_e_getc();
+		first = false;
+	}
 	if (c < 0 || first) {
+ x_set_arg_too_big:
 		x_e_putc2(7);
 		x_arg = 1;
-		x_arg_defaulted = 1;
+		x_arg_defaulted = true;
 	} else {
 		x_e_ungetc(c);
 		x_arg = n;
-		x_arg_defaulted = 0;
+		x_arg_defaulted = false;
 	}
 	return (KSTD);
 }
@@ -2851,7 +2909,7 @@
 x_comment(int c MKSH_A_UNUSED)
 {
 	int oldsize = x_size_str(xbuf);
-	int len = xep - xbuf;
+	ssize_t len = xep - xbuf;
 	int ret = x_do_comment(xbuf, xend - xbuf, &len);
 
 	if (ret < 0)
@@ -2873,7 +2931,8 @@
 {
 	char *o_xbuf = xbuf, *o_xend = xend;
 	char *o_xbp = xbp, *o_xep = xep, *o_xcp = xcp;
-	int vlen, lim = x_lastcp() - xbp;
+	int lim = x_lastcp() - xbp;
+	size_t vlen;
 	char *v;
 
 	strdupx(v, KSH_VERSION, ATEMP);
@@ -2889,7 +2948,7 @@
 	xbp = o_xbp;
 	xep = o_xep;
 	xcp = o_xcp;
-	x_redraw(vlen);
+	x_redraw((int)vlen);
 
 	if (c < 0)
 		return (KSTD);
@@ -2927,7 +2986,8 @@
 }
 #endif
 
-/* NAME:
+/*-
+ * NAME:
  *	x_prev_histword - recover word from prev command
  *
  * DESCRIPTION:
@@ -2948,11 +3008,17 @@
 {
 	char *rcp, *cp;
 	char **xhp;
-	int m;
+	int m = 1;
+	/* -1 = defaulted; 0+ = argument */
+	static int last_arg = -1;
 
-	if (xmp && modified > 1)
-		x_kill_region(0);
-	m = modified ? modified : 1;
+	if (x_last_command == XFUNC_prev_histword) {
+		if (xmp && modified > 1)
+			x_kill_region(0);
+		if (modified)
+			m = modified;
+	} else
+		last_arg = x_arg_defaulted ? -1 : x_arg;
 	xhp = histptr - (m - 1);
 	if ((xhp < history) || !(cp = *xhp)) {
 		x_e_putc2(7);
@@ -2960,7 +3026,9 @@
 		return (KSTD);
 	}
 	x_set_mark(0);
-	if (x_arg_defaulted) {
+	if ((x_arg = last_arg) == -1) {
+		/* x_arg_defaulted */
+
 		rcp = &cp[strlen(cp) - 1];
 		/*
 		 * ignore white-space after the last word
@@ -2973,6 +3041,7 @@
 			rcp++;
 		x_ins(rcp);
 	} else {
+		/* not x_arg_defaulted */
 		char ch;
 
 		rcp = cp;
@@ -2981,7 +3050,7 @@
 		 */
 		while (*rcp && is_cfs(*rcp))
 			rcp++;
-		while (x_arg-- > 1) {
+		while (x_arg-- > 0) {
 			while (*rcp && !is_cfs(*rcp))
 				rcp++;
 			while (*rcp && is_cfs(*rcp))
@@ -3021,7 +3090,8 @@
 	return (x_fold_case('C'));
 }
 
-/* NAME:
+/*-
+ * NAME:
  *	x_fold_case - convert word to UPPER/lower/Capital case
  *
  * DESCRIPTION:
@@ -3051,9 +3121,11 @@
 		 * a different action than for the rest.
 		 */
 		if (cp != xep) {
-			if (c == 'L')		/* lowercase */
+			if (c == 'L')
+				/* lowercase */
 				*cp = ksh_tolower(*cp);
-			else			/* uppercase, capitalise */
+			else
+				/* uppercase, capitalise */
 				*cp = ksh_toupper(*cp);
 			cp++;
 		}
@@ -3061,9 +3133,11 @@
 		 * now for the rest of the word
 		 */
 		while (cp != xep && !is_mfs(*cp)) {
-			if (c == 'U')		/* uppercase */
+			if (c == 'U')
+				/* uppercase */
 				*cp = ksh_toupper(*cp);
-			else			/* lowercase, capitalise */
+			else
+				/* lowercase, capitalise */
 				*cp = ksh_tolower(*cp);
 			cp++;
 		}
@@ -3074,7 +3148,8 @@
 }
 #endif
 
-/* NAME:
+/*-
+ * NAME:
  *	x_lastcp - last visible char
  *
  * SYNOPSIS:
@@ -3114,44 +3189,26 @@
 	return (xlp);
 }
 
-static bool
+static void
 x_mode(bool onoff)
 {
 	static bool x_cur_mode;
-	bool prev;
 
 	if (x_cur_mode == onoff)
-		return (x_cur_mode);
-	prev = x_cur_mode;
+		return;
 	x_cur_mode = onoff;
 
 	if (onoff) {
-		struct termios cb;
+		x_mkraw(tty_fd, NULL, false);
 
-		cb = tty_state;
-
-		edchars.erase = cb.c_cc[VERASE];
-		edchars.kill = cb.c_cc[VKILL];
-		edchars.intr = cb.c_cc[VINTR];
-		edchars.quit = cb.c_cc[VQUIT];
-		edchars.eof = cb.c_cc[VEOF];
+		edchars.erase = tty_state.c_cc[VERASE];
+		edchars.kill = tty_state.c_cc[VKILL];
+		edchars.intr = tty_state.c_cc[VINTR];
+		edchars.quit = tty_state.c_cc[VQUIT];
+		edchars.eof = tty_state.c_cc[VEOF];
 #ifdef VWERASE
-		edchars.werase = cb.c_cc[VWERASE];
+		edchars.werase = tty_state.c_cc[VWERASE];
 #endif
-		cb.c_iflag &= ~(INLCR | ICRNL);
-		cb.c_lflag &= ~(ISIG | ICANON | ECHO);
-#if defined(VLNEXT) && defined(_POSIX_VDISABLE)
-		/* osf/1 processes lnext when ~icanon */
-		cb.c_cc[VLNEXT] = _POSIX_VDISABLE;
-#endif
-		/* sunos 4.1.x & osf/1 processes discard(flush) when ~icanon */
-#if defined(VDISCARD) && defined(_POSIX_VDISABLE)
-		cb.c_cc[VDISCARD] = _POSIX_VDISABLE;
-#endif
-		cb.c_cc[VTIME] = 0;
-		cb.c_cc[VMIN] = 1;
-
-		tcsetattr(tty_fd, TCSADRAIN, &cb);
 
 #ifdef _POSIX_VDISABLE
 		/* Convert unset values to internal 'unset' value */
@@ -3183,8 +3240,6 @@
 			bind_if_not_bound(0, edchars.quit, XFUNC_noop);
 	} else
 		tcsetattr(tty_fd, TCSADRAIN, &tty_state);
-
-	return (prev);
 }
 
 #if !MKSH_S_NOVI
@@ -3194,10 +3249,10 @@
 
 struct edstate {
 	char *cbuf;
-	int winleft;
-	int cbufsize;
-	int linelen;
-	int cursor;
+	ssize_t winleft;
+	ssize_t cbufsize;
+	ssize_t linelen;
+	ssize_t cursor;
 };
 
 static int vi_hook(int);
@@ -3210,7 +3265,7 @@
 static int bracktype(int);
 static void save_cbuf(void);
 static void restore_cbuf(void);
-static int putbuf(const char *, int, int);
+static int putbuf(const char *, ssize_t, int);
 static void del_range(int, int);
 static int findch(int, int, int, int);
 static int forwword(int);
@@ -3221,7 +3276,7 @@
 static int Endword(int);
 static int grabhist(int, int);
 static int grabsearch(int, int, int, char *);
-static void redraw_line(int);
+static void redraw_line(bool);
 static void refresh(int);
 static int outofwin(void);
 static void rewindow(void);
@@ -3237,58 +3292,58 @@
 static void vi_macro_reset(void);
 static int x_vi_putbuf(const char *, size_t);
 
-#define C_	0x1		/* a valid command that isn't a M_, E_, U_ */
-#define M_	0x2		/* movement command (h, l, etc.) */
-#define E_	0x4		/* extended command (c, d, y) */
-#define X_	0x8		/* long command (@, f, F, t, T, etc.) */
-#define U_	0x10		/* an UN-undoable command (that isn't a M_) */
-#define B_	0x20		/* bad command (^@) */
-#define Z_	0x40		/* repeat count defaults to 0 (not 1) */
-#define S_	0x80		/* search (/, ?) */
+#define vC	0x01		/* a valid command that isn't a vM, vE, vU */
+#define vM	0x02		/* movement command (h, l, etc.) */
+#define vE	0x04		/* extended command (c, d, y) */
+#define vX	0x08		/* long command (@, f, F, t, T, etc.) */
+#define vU	0x10		/* an UN-undoable command (that isn't a vM) */
+#define vB	0x20		/* bad command (^@) */
+#define vZ	0x40		/* repeat count defaults to 0 (not 1) */
+#define vS	0x80		/* search (/, ?) */
 
-#define is_bad(c)	(classify[(c)&0x7f]&B_)
-#define is_cmd(c)	(classify[(c)&0x7f]&(M_|E_|C_|U_))
-#define is_move(c)	(classify[(c)&0x7f]&M_)
-#define is_extend(c)	(classify[(c)&0x7f]&E_)
-#define is_long(c)	(classify[(c)&0x7f]&X_)
-#define is_undoable(c)	(!(classify[(c)&0x7f]&U_))
-#define is_srch(c)	(classify[(c)&0x7f]&S_)
-#define is_zerocount(c)	(classify[(c)&0x7f]&Z_)
+#define is_bad(c)	(classify[(c)&0x7f]&vB)
+#define is_cmd(c)	(classify[(c)&0x7f]&(vM|vE|vC|vU))
+#define is_move(c)	(classify[(c)&0x7f]&vM)
+#define is_extend(c)	(classify[(c)&0x7f]&vE)
+#define is_long(c)	(classify[(c)&0x7f]&vX)
+#define is_undoable(c)	(!(classify[(c)&0x7f]&vU))
+#define is_srch(c)	(classify[(c)&0x7f]&vS)
+#define is_zerocount(c)	(classify[(c)&0x7f]&vZ)
 
 static const unsigned char classify[128] = {
 /*	 0	1	2	3	4	5	6	7	*/
 /* 0	^@	^A	^B	^C	^D	^E	^F	^G	*/
-	B_,	0,	0,	0,	0,	C_|U_,	C_|Z_,	0,
+	vB,	0,	0,	0,	0,	vC|vU,	vC|vZ,	0,
 /* 1	^H	^I	^J	^K	^L	^M	^N	^O	*/
-	M_,	C_|Z_,	0,	0,	C_|U_,	0,	C_,	0,
+	vM,	vC|vZ,	0,	0,	vC|vU,	0,	vC,	0,
 /* 2	^P	^Q	^R	^S	^T	^U	^V	^W	*/
-	C_,	0,	C_|U_,	0,	0,	0,	C_,	0,
+	vC,	0,	vC|vU,	0,	0,	0,	vC,	0,
 /* 3	^X	^Y	^Z	^[	^\	^]	^^	^_	*/
-	C_,	0,	0,	C_|Z_,	0,	0,	0,	0,
+	vC,	0,	0,	vC|vZ,	0,	0,	0,	0,
 /* 4	<space>	!	"	#	$	%	&	'	*/
-	M_,	0,	0,	C_,	M_,	M_,	0,	0,
+	vM,	0,	0,	vC,	vM,	vM,	0,	0,
 /* 5	(	)	*	+	,	-	.	/	*/
-	0,	0,	C_,	C_,	M_,	C_,	0,	C_|S_,
+	0,	0,	vC,	vC,	vM,	vC,	0,	vC|vS,
 /* 6	0	1	2	3	4	5	6	7	*/
-	M_,	0,	0,	0,	0,	0,	0,	0,
+	vM,	0,	0,	0,	0,	0,	0,	0,
 /* 7	8	9	:	;	<	=	>	?	*/
-	0,	0,	0,	M_,	0,	C_,	0,	C_|S_,
+	0,	0,	0,	vM,	0,	vC,	0,	vC|vS,
 /* 8	@	A	B	C	D	E	F	G	*/
-	C_|X_,	C_,	M_,	C_,	C_,	M_,	M_|X_,	C_|U_|Z_,
+	vC|vX,	vC,	vM,	vC,	vC,	vM,	vM|vX,	vC|vU|vZ,
 /* 9	H	I	J	K	L	M	N	O	*/
-	0,	C_,	0,	0,	0,	0,	C_|U_,	0,
+	0,	vC,	0,	0,	0,	0,	vC|vU,	0,
 /* A	P	Q	R	S	T	U	V	W	*/
-	C_,	0,	C_,	C_,	M_|X_,	C_,	0,	M_,
+	vC,	0,	vC,	vC,	vM|vX,	vC,	0,	vM,
 /* B	X	Y	Z	[	\	]	^	_	*/
-	C_,	C_|U_,	0,	0,	C_|Z_,	0,	M_,	C_|Z_,
+	vC,	vC|vU,	0,	0,	vC|vZ,	0,	vM,	vC|vZ,
 /* C	`	a	b	c	d	e	f	g	*/
-	0,	C_,	M_,	E_,	E_,	M_,	M_|X_,	C_|Z_,
+	0,	vC,	vM,	vE,	vE,	vM,	vM|vX,	vC|vZ,
 /* D	h	i	j	k	l	m	n	o	*/
-	M_,	C_,	C_|U_,	C_|U_,	M_,	0,	C_|U_,	0,
+	vM,	vC,	vC|vU,	vC|vU,	vM,	0,	vC|vU,	0,
 /* E	p	q	r	s	t	u	v	w	*/
-	C_,	0,	X_,	C_,	M_|X_,	C_|U_,	C_|U_|Z_, M_,
+	vC,	0,	vX,	vC,	vM|vX,	vC|vU,	vC|vU|vZ, vM,
 /* F	x	y	z	{	|	}	~	^?	*/
-	C_,	E_|U_,	0,	0,	M_|Z_,	0,	C_,	0
+	vC,	vE|vU,	0,	0,	vM|vZ,	0,	vC,	0
 };
 
 #define MAXVICMD	3
@@ -3340,7 +3395,8 @@
 static int hlast;			/* 1 past last position in history */
 static int state;
 
-/* Information for keeping track of macros that are being expanded.
+/*
+ * Information for keeping track of macros that are being expanded.
  * The format of buf is the alias contents followed by a NUL byte followed
  * by the name (letter) of the alias. The end of the buffer is marked by
  * a double NUL. The name of the alias is stored so recursive macros can
@@ -3349,14 +3405,14 @@
 struct macro_state {
 	unsigned char *p;	/* current position in buf */
 	unsigned char *buf;	/* pointer to macro(s) being expanded */
-	int len;		/* how much data in buffer */
+	size_t len;		/* how much data in buffer */
 };
 static struct macro_state macro;
 
-enum expand_mode {
-	NONE, EXPAND, COMPLETE, PRINT
-};
-static enum expand_mode expanded = NONE;	/* last input was expanded */
+/* last input was expanded */
+static enum expand_mode {
+	NONE = 0, EXPAND, COMPLETE, PRINT
+} expanded;
 
 static int
 x_vi(char *buf, size_t len)
@@ -3397,8 +3453,10 @@
 		wbuf[0] = aresize(wbuf[0], wbuf_len, APERM);
 		wbuf[1] = aresize(wbuf[1], wbuf_len, APERM);
 	}
-	(void)memset(wbuf[0], ' ', wbuf_len);
-	(void)memset(wbuf[1], ' ', wbuf_len);
+	if (wbuf_len) {
+		memset(wbuf[0], ' ', wbuf_len);
+		memset(wbuf[1], ' ', wbuf_len);
+	}
 	winwidth = x_cols - pwidth - 3;
 	win = 0;
 	morec = ' ';
@@ -3407,7 +3465,7 @@
 
 	editmode = 2;
 	x_flush();
-	while (1) {
+	while (/* CONSTCOND */ 1) {
 		if (macro.p) {
 			c = *macro.p++;
 			/* end of current macro? */
@@ -3804,7 +3862,8 @@
 		expanded = NONE;
 		return (0);
 	}
-	/* If any chars are entered before escape, trash the saved insert
+	/*
+	 * If any chars are entered before escape, trash the saved insert
 	 * buffer (if user inserts & deletes char, ibuf gets trashed and
 	 * we don't want to use it)
 	 */
@@ -3905,14 +3964,14 @@
 
 		case Ctrl('l'):
 		case Ctrl('r'):
-			redraw_line(1);
+			redraw_line(true);
 			break;
 
 		case '@':
 			{
 				static char alias[] = "_\0";
 				struct tbl *ap;
-				int olen, nlen;
+				size_t olen, nlen;
 				char *p, *nbuf;
 
 				/* lookup letter in alias list... */
@@ -3929,6 +3988,10 @@
 				nlen = strlen(ap->val.s) + 1;
 				olen = !macro.p ? 2 :
 				    macro.len - (macro.p - macro.buf);
+				/*
+				 * at this point, it's fairly reasonable that
+				 * nlen + olen + 2 doesn't overflow
+				 */
 				nbuf = alloc(nlen + 1 + olen, APERM);
 				memcpy(nbuf, ap->val.s, nlen);
 				nbuf[nlen++] = cmd[1];
@@ -4323,29 +4386,37 @@
 				return (ret);
 			}
 
-		case '=':			/* AT&T ksh */
-		case Ctrl('e'):			/* Nonstandard vi/ksh */
+		/* AT&T ksh */
+		case '=':
+		/* Nonstandard vi/ksh */
+		case Ctrl('e'):
 			print_expansions(es, 1);
 			break;
 
 
-		case Ctrl('i'):			/* Nonstandard vi/ksh */
+		/* Nonstandard vi/ksh */
+		case Ctrl('i'):
 			if (!Flag(FVITABCOMPLETE))
 				return (-1);
 			complete_word(1, argcnt);
 			break;
 
-		case Ctrl('['):			/* some annoying AT&T kshs */
+		/* some annoying AT&T kshs */
+		case Ctrl('['):
 			if (!Flag(FVIESCCOMPLETE))
 				return (-1);
-		case '\\':			/* AT&T ksh */
-		case Ctrl('f'):			/* Nonstandard vi/ksh */
+		/* AT&T ksh */
+		case '\\':
+		/* Nonstandard vi/ksh */
+		case Ctrl('f'):
 			complete_word(1, argcnt);
 			break;
 
 
-		case '*':			/* AT&T ksh */
-		case Ctrl('x'):			/* Nonstandard vi/ksh */
+		/* AT&T ksh */
+		case '*':
+		/* Nonstandard vi/ksh */
+		case Ctrl('x'):
 			expand_word(1);
 			break;
 		}
@@ -4613,7 +4684,7 @@
 }
 
 static int
-putbuf(const char *buf, int len, int repl)
+putbuf(const char *buf, ssize_t len, int repl)
 {
 	if (len == 0)
 		return (0);
@@ -4811,7 +4882,7 @@
 	}
 	(void)histnum(n);
 	if ((hptr = *histpos()) == NULL) {
-		internal_warningf("grabhist: bad history array");
+		internal_warningf("%s: %s", "grabhist", "bad history array");
 		return (-1);
 	}
 	if (save)
@@ -4839,7 +4910,7 @@
 		start--;
 	anchored = *pat == '^' ? (++pat, 1) : 0;
 	if ((hist = findhist(start, fwd, pat, anchored)) < 0) {
-		/* if (start != 0 && fwd && match(holdbuf, pat) >= 0) { */
+		/* if (start != 0 && fwd && match(holdbuf, pat) >= 0) {} */
 		/* XXX should strcmp be strncmp? */
 		if (start != 0 && fwd && strcmp(holdbuf, pat) >= 0) {
 			restore_cbuf();
@@ -4859,9 +4930,10 @@
 }
 
 static void
-redraw_line(int newl)
+redraw_line(bool newl)
 {
-	(void)memset(wbuf[win], ' ', wbuf_len);
+	if (wbuf_len)
+		memset(wbuf[win], ' ', wbuf_len);
 	if (newl) {
 		x_putc('\r');
 		x_putc('\n');
@@ -4996,7 +5068,8 @@
 		col++;
 	}
 	if (es->winleft > 0 && moreright)
-		/* POSIX says to use * for this but that is a globbing
+		/*
+		 * POSIX says to use * for this but that is a globbing
 		 * character and may confuse people; + is more innocuous
 		 */
 		mc = '+';
@@ -5045,11 +5118,8 @@
 expand_word(int cmd)
 {
 	static struct edstate *buf;
-	int rval = 0;
-	int nwords;
-	int start, end;
+	int rval = 0, nwords, start, end, i;
 	char **words;
-	int i;
 
 	/* Undo previous expansion */
 	if (cmd == 0 && expanded == EXPAND && buf) {
@@ -5063,9 +5133,9 @@
 		buf = 0;
 	}
 
-	nwords = x_cf_glob(XCF_COMMAND_FILE|XCF_FULLPATH,
-	    es->cbuf, es->linelen, es->cursor,
-	    &start, &end, &words, NULL);
+	i = XCF_COMMAND_FILE | XCF_FULLPATH;
+	nwords = x_cf_glob(&i, es->cbuf, es->linelen, es->cursor,
+	    &start, &end, &words);
 	if (nwords == 0) {
 		vi_error();
 		return (-1);
@@ -5075,7 +5145,8 @@
 	expanded = EXPAND;
 	del_range(start, end);
 	es->cursor = start;
-	for (i = 0; i < nwords; ) {
+	i = 0;
+	while (i < nwords) {
 		if (x_escape(words[i], strlen(words[i]), x_vi_putbuf) != 0) {
 			rval = -1;
 			break;
@@ -5100,10 +5171,11 @@
 complete_word(int cmd, int count)
 {
 	static struct edstate *buf;
-	int rval, nwords, start, end, match_len;
+	int rval, nwords, start, end, flags;
+	size_t match_len;
 	char **words;
 	char *match;
-	bool is_command, is_unique;
+	bool is_unique;
 
 	/* Undo previous completion */
 	if (cmd == 0 && expanded == COMPLETE && buf) {
@@ -5122,12 +5194,15 @@
 		buf = 0;
 	}
 
-	/* XCF_FULLPATH for count 'cause the menu printed by print_expansions()
-	 * was done this way.
+	/*
+	 * XCF_FULLPATH for count 'cause the menu printed by
+	 * print_expansions() was done this way.
 	 */
-	nwords = x_cf_glob(XCF_COMMAND_FILE | (count ? XCF_FULLPATH : 0),
-	    es->cbuf, es->linelen, es->cursor,
-	    &start, &end, &words, &is_command);
+	flags = XCF_COMMAND_FILE;
+	if (count)
+		flags |= XCF_FULLPATH;
+	nwords = x_cf_glob(&flags, es->cbuf, es->linelen, es->cursor,
+	    &start, &end, &words);
 	if (nwords == 0) {
 		vi_error();
 		return (-1);
@@ -5138,15 +5213,16 @@
 		count--;
 		if (count >= nwords) {
 			vi_error();
-			x_print_expansions(nwords, words, is_command);
+			x_print_expansions(nwords, words,
+			    tobool(flags & XCF_IS_COMMAND));
 			x_free_words(nwords, words);
-			redraw_line(0);
+			redraw_line(false);
 			return (-1);
 		}
 		/*
 		 * Expand the count'th word to its basename
 		 */
-		if (is_command) {
+		if (flags & XCF_IS_COMMAND) {
 			match = words[count] +
 			    x_basename(words[count], NULL);
 			/* If more than one possible match, use full path */
@@ -5165,7 +5241,8 @@
 	} else {
 		match = words[0];
 		match_len = x_longest_prefix(nwords, words);
-		expanded = COMPLETE;	/* next call will list completions */
+		/* next call will list completions */
+		expanded = COMPLETE;
 		is_unique = nwords == 1;
 	}
 
@@ -5173,18 +5250,25 @@
 	del_range(start, end);
 	es->cursor = start;
 
-	/* escape all shell-sensitive characters and put the result into
-	 * command buffer */
+	/*
+	 * escape all shell-sensitive characters and put the result into
+	 * command buffer
+	 */
 	rval = x_escape(match, match_len, x_vi_putbuf);
 
 	if (rval == 0 && is_unique) {
-		/* If exact match, don't undo. Allows directory completions
+		/*
+		 * If exact match, don't undo. Allows directory completions
 		 * to be used (ie, complete the next portion of the path).
 		 */
 		expanded = NONE;
 
-		/* If not a directory, add a space to the end... */
-		if (match_len > 0 && match[match_len - 1] != '/')
+		/*
+		 * append a space if this is a non-directory match
+		 * and not a parameter or homedir substitution
+		 */
+		if (match_len > 0 && match[match_len - 1] != '/' &&
+		    !(flags & XCF_IS_SUBGLOB))
 			rval = putbuf(" ", 1, 0);
 	}
 	x_free_words(nwords, words);
@@ -5192,7 +5276,8 @@
 	modified = 1;
 	hnum = hlast;
 	insert = INSERT;
-	lastac = 0;	 /* prevent this from being redone... */
+	/* prevent this from being redone... */
+	lastac = 0;
 	refresh(0);
 
 	return (rval);
@@ -5201,20 +5286,19 @@
 static int
 print_expansions(struct edstate *est, int cmd MKSH_A_UNUSED)
 {
-	int start, end, nwords;
+	int start, end, nwords, i;
 	char **words;
-	bool is_command;
 
-	nwords = x_cf_glob(XCF_COMMAND_FILE | XCF_FULLPATH,
-	    est->cbuf, est->linelen, est->cursor,
-	    &start, &end, &words, &is_command);
+	i = XCF_COMMAND_FILE | XCF_FULLPATH;
+	nwords = x_cf_glob(&i, est->cbuf, est->linelen, est->cursor,
+	    &start, &end, &words);
 	if (nwords == 0) {
 		vi_error();
 		return (-1);
 	}
-	x_print_expansions(nwords, words, is_command);
+	x_print_expansions(nwords, words, tobool(i & XCF_IS_COMMAND));
 	x_free_words(nwords, words);
-	redraw_line(0);
+	redraw_line(false);
 	return (0);
 }
 
@@ -5247,3 +5331,34 @@
 	}
 }
 #endif /* !MKSH_S_NOVI */
+
+void
+x_mkraw(int fd, struct termios *ocb, bool forread)
+{
+	struct termios cb;
+
+	if (ocb)
+		tcgetattr(fd, ocb);
+	else
+		ocb = &tty_state;
+
+	cb = *ocb;
+	if (forread) {
+		cb.c_lflag &= ~(ICANON) | ECHO;
+	} else {
+		cb.c_iflag &= ~(INLCR | ICRNL);
+		cb.c_lflag &= ~(ISIG | ICANON | ECHO);
+	}
+#if defined(VLNEXT) && defined(_POSIX_VDISABLE)
+	/* OSF/1 processes lnext when ~icanon */
+	cb.c_cc[VLNEXT] = _POSIX_VDISABLE;
+#endif
+	/* SunOS 4.1.x & OSF/1 processes discard(flush) when ~icanon */
+#if defined(VDISCARD) && defined(_POSIX_VDISABLE)
+	cb.c_cc[VDISCARD] = _POSIX_VDISABLE;
+#endif
+	cb.c_cc[VTIME] = 0;
+	cb.c_cc[VMIN] = 1;
+
+	tcsetattr(fd, TCSADRAIN, &cb);
+}
diff --git a/src/eval.c b/src/eval.c
index c22e346..49e5e6e 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -1,7 +1,7 @@
-/*	$OpenBSD: eval.c,v 1.35 2010/03/24 08:27:26 fgsch Exp $	*/
+/*	$OpenBSD: eval.c,v 1.37 2011/10/11 14:32:43 otto Exp $	*/
 
 /*-
- * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
  *	Thorsten Glaser <tg@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -22,7 +22,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/eval.c,v 1.90 2010/07/17 22:09:33 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/eval.c,v 1.109 2011/10/11 19:06:07 tg Exp $");
 
 /*
  * string expansion
@@ -67,11 +67,11 @@
 static char *homedir(char *);
 #endif
 static void alt_expand(XPtrV *, char *, char *, char *, int);
-static size_t utflen(const char *);
+static int utflen(const char *);
 static void utfincptr(const char *, mksh_ari_t *);
 
 /* UTFMODE functions */
-static size_t
+static int
 utflen(const char *s)
 {
 	size_t n;
@@ -84,7 +84,10 @@
 		}
 	} else
 		n = strlen(s);
-	return (n);
+
+	if (n > 2147483647)
+		n = 2147483647;
+	return ((int)n);
 }
 
 static void
@@ -108,7 +111,7 @@
 	s->start = s->str = cp;
 	source = s;
 	if (yylex(ONEWORD) != LWORD)
-		internal_errorf("substitute");
+		internal_errorf("bad substitution");
 	source = sold;
 	afree(s, ATEMP);
 	return (evalstr(yylval.cp, f));
@@ -129,7 +132,8 @@
 		return (vap.rw);
 	}
 	XPinit(w, 32);
-	XPput(w, NULL);		/* space for shell name */
+	/* space for shell name */
+	XPput(w, NULL);
 	while (*ap != NULL)
 		expand(*ap++, &w, f);
 	XPput(w, NULL);
@@ -205,11 +209,13 @@
 	const char *sp;		/* source */
 	int fdo, word;		/* second pass flags; have word */
 	int doblank;		/* field splitting of parameter/command subst */
-	Expand x = {		/* expansion variables */
+	Expand x = {
+		/* expansion variables */
 		NULL, { NULL }, NULL, 0
 	};
 	SubType st_head, *st;
-	int newlines = 0; /* For trailing newlines in COMSUB */
+	/* For trailing newlines in COMSUB */
+	int newlines = 0;
 	int saw_eq, tilde_ok;
 	int make_magic;
 	size_t len;
@@ -226,14 +232,16 @@
 	if (Flag(FMARKDIRS))
 		f |= DOMARKDIRS;
 	if (Flag(FBRACEEXPAND) && (f & DOGLOB))
-		f |= DOBRACE_;
+		f |= DOBRACE;
 
-	Xinit(ds, dp, 128, ATEMP);	/* init dest. string */
+	/* init destination string */
+	Xinit(ds, dp, 128, ATEMP);
 	type = XBASE;
 	sp = cp;
 	fdo = 0;
 	saw_eq = 0;
-	tilde_ok = (f & (DOTILDE|DOASNTILDE)) ? 1 : 0; /* must be 1/0 */
+	/* must be 1/0 */
+	tilde_ok = (f & (DOTILDE|DOASNTILDE)) ? 1 : 0;
 	doblank = 0;
 	make_magic = 0;
 	word = (f&DOBLANK) ? IFS_WS : IFS_WORD;
@@ -241,11 +249,12 @@
 	memset(&st_head, 0, sizeof(st_head));
 	st = &st_head;
 
-	while (1) {
+	while (/* CONSTCOND */ 1) {
 		Xcheck(ds, dp);
 
 		switch (type) {
-		case XBASE:	/* original prefixed string */
+		case XBASE:
+			/* original prefixed string */
 			c = *sp++;
 			switch (c) {
 			case EOS:
@@ -255,7 +264,8 @@
 				c = *sp++;
 				break;
 			case QCHAR:
-				quote |= 2; /* temporary quote */
+				/* temporary quote */
+				quote |= 2;
 				c = *sp++;
 				break;
 			case OQUOTE:
@@ -299,7 +309,8 @@
 					char *p;
 
 					v.flag = DEFINED|ISSET|INTEGER;
-					v.type = 10; /* not default */
+					/* not default */
+					v.type = 10;
 					v.name[0] = '\0';
 					v_evaluate(&v, substitute(sp, 0),
 					    KSH_UNWIND_ERROR, true);
@@ -310,23 +321,27 @@
 					}
 				}
 				continue;
-			case OSUBST: {	/* ${{#}var{:}[=+-?#%]word} */
-			/* format is:
+			case OSUBST: {
+				/* ${{#}var{:}[=+-?#%]word} */
+			/*-
+			 * format is:
 			 *	OSUBST [{x] plain-variable-part \0
 			 *	    compiled-word-part CSUBST [}x]
 			 * This is where all syntax checking gets done...
 			 */
-				const char *varname = ++sp; /* skip the { or x (}) */
+				/* skip the { or x (}) */
+				const char *varname = ++sp;
 				int stype;
 				int slen = 0;
 
-				sp = cstrchr(sp, '\0') + 1; /* skip variable */
+				/* skip variable */
+				sp = cstrchr(sp, '\0') + 1;
 				type = varsub(&x, varname, sp, &stype, &slen);
 				if (type < 0) {
 					char *beg, *end, *str;
-
  unwind_substsyn:
-					sp = varname - 2; /* restore sp */
+					/* restore sp */
+					sp = varname - 2;
 					end = (beg = wdcopy(sp, ATEMP)) +
 					    (wdscan(sp, CSUBST) - sp);
 					/* ({) the } or x is already skipped */
@@ -334,12 +349,13 @@
 						*end = EOS;
 					str = snptreef(NULL, 64, "%S", beg);
 					afree(beg, ATEMP);
-					errorf("%s: bad substitution", str);
+					errorf("%s: %s", str, "bad substitution");
 				}
 				if (f & DOBLANK)
 					doblank++;
 				tilde_ok = 0;
-				if (type == XBASE) {	/* expand? */
+				if (type == XBASE) {
+					/* expand? */
 					if (!st->next) {
 						SubType *newst;
 
@@ -357,7 +373,11 @@
 					/* skip qualifier(s) */
 					if (stype)
 						sp += slen;
-					switch (stype & 0x7f) {
+					switch (stype & 0x17F) {
+					case 0x100 | '#':
+						x.str = shf_smprintf("%08X",
+						    (unsigned int)hash(str_val(st->var)));
+						break;
 					case '0': {
 						char *beg, *mid, *end, *stg;
 						mksh_ari_t from = 0, num = -1, flen, finc = 0;
@@ -374,16 +394,18 @@
 						} else {
 							end = mid +
 							    (wdscan(mid, ADELIM) - mid);
-							if (end >= stg)
+							if (end >= stg ||
+							    /* more than max delimiters */
+							    end[-1] != /*{*/ '}')
 								goto unwind_substsyn;
 							end[-2] = EOS;
 							sp += end - beg - 1;
 						}
-						evaluate(substitute(stg = wdstrip(beg, false, false), 0),
+						evaluate(substitute(stg = wdstrip(beg, 0), 0),
 						    &from, KSH_UNWIND_ERROR, true);
 						afree(stg, ATEMP);
 						if (end) {
-							evaluate(substitute(stg = wdstrip(mid, false, false), 0),
+							evaluate(substitute(stg = wdstrip(mid, 0), 0),
 							    &num, KSH_UNWIND_ERROR, true);
 							afree(stg, ATEMP);
 						}
@@ -422,10 +444,11 @@
 						else
 							d[-2] = EOS;
 						sp += (d ? d : p) - s - 1;
-						tpat0 = wdstrip(s, true, true);
+						tpat0 = wdstrip(s,
+						    WDS_KEEPQ | WDS_MAGIC);
 						pat = substitute(tpat0, 0);
 						if (d) {
-							d = wdstrip(p, true, false);
+							d = wdstrip(p, WDS_KEEPQ);
 							rrep = substitute(d, 0);
 							afree(d, ATEMP);
 						} else
@@ -445,12 +468,44 @@
 						*d = '\0';
 						afree(tpat0, ATEMP);
 
-						/* reject empty pattern */
-						if (!*pat || gmatchx("", pat, false))
+						/* check for special cases */
+						d = str_val(st->var);
+						switch (*pat) {
+						case '#':
+							/* anchor at begin */
+							tpat0 = pat + 1;
+							tpat1 = rrep;
+							tpat2 = d;
+							break;
+						case '%':
+							/* anchor at end */
+							tpat0 = pat + 1;
+							tpat1 = d;
+							tpat2 = rrep;
+							break;
+						case '\0':
+							/* empty pattern */
 							goto no_repl;
+						default:
+							tpat0 = pat;
+							/* silence gcc */
+							tpat1 = tpat2 = NULL;
+						}
+						if (gmatchx(null, tpat0, false)) {
+							/*
+							 * pattern matches
+							 * the empty string
+							 */
+							if (tpat0 == pat)
+								goto no_repl;
+							/* but is anchored */
+							s = shf_smprintf("%s%s",
+							    tpat1, tpat2);
+							goto do_repl;
+						}
 
 						/* prepare string on which to work */
-						strdupx(s, str_val(st->var), ATEMP);
+						strdupx(s, d, ATEMP);
 						sbeg = s;
 
 						/* first see if we have any match at all */
@@ -469,7 +524,8 @@
 							tpat2 = tpat1 + 2;
 						}
  again_repl:
-						/* this would not be necessary if gmatchx would return
+						/*
+						 * this would not be necessary if gmatchx would return
 						 * the start and end values of a match found, like re*
 						 */
 						if (!gmatchx(sbeg, tpat1, false))
@@ -489,8 +545,9 @@
 							while (p >= sbeg) {
 								bool gotmatch;
 
-								c = *p; *p = '\0';
-								gotmatch = gmatchx(sbeg, tpat0, false);
+								c = *p;
+								*p = '\0';
+								gotmatch = tobool(gmatchx(sbeg, tpat0, false));
 								*p = c;
 								if (gotmatch)
 									break;
@@ -506,6 +563,7 @@
 							goto again_repl;
  end_repl:
 						afree(tpat1, ATEMP);
+ do_repl:
 						x.str = s;
  no_repl:
 						afree(pat, ATEMP);
@@ -515,19 +573,23 @@
 					}
 					case '#':
 					case '%':
-						/* ! DOBLANK,DOBRACE_,DOTILDE */
+						/* ! DOBLANK,DOBRACE,DOTILDE */
 						f = DOPAT | (f&DONTRUNCOMMAND) |
-						    DOTEMP_;
+						    DOTEMP;
 						st->quotew = quote = 0;
-						/* Prepend open pattern (so |
+						/*
+						 * Prepend open pattern (so |
 						 * in a trim will work as
 						 * expected)
 						 */
-						*dp++ = MAGIC;
-						*dp++ = (char)('@' | 0x80);
+						if (!Flag(FSH)) {
+							*dp++ = MAGIC;
+							*dp++ = '@' | 0x80;
+						}
 						break;
 					case '=':
-						/* Enabling tilde expansion
+						/*
+						 * Enabling tilde expansion
 						 * after :s here is
 						 * non-standard ksh, but is
 						 * consistent with rules for
@@ -542,16 +604,17 @@
 						 */
 						if (!(x.var->flag & INTEGER))
 							f |= DOASNTILDE|DOTILDE;
-						f |= DOTEMP_;
-						/* These will be done after the
+						f |= DOTEMP;
+						/*
+						 * These will be done after the
 						 * value has been assigned.
 						 */
-						f &= ~(DOBLANK|DOGLOB|DOBRACE_);
+						f &= ~(DOBLANK|DOGLOB|DOBRACE);
 						tilde_ok = 1;
 						break;
 					case '?':
 						f &= ~DOBLANK;
-						f |= DOTEMP_;
+						f |= DOTEMP;
 						/* FALLTHROUGH */
 					default:
 						/* Enable tilde expansion */
@@ -563,22 +626,30 @@
 					sp += wdscan(sp, CSUBST) - sp;
 				continue;
 			}
-			case CSUBST: /* only get here if expanding word */
+			case CSUBST:
+				/* only get here if expanding word */
  do_CSUBST:
-				sp++; /* ({) skip the } or x */
-				tilde_ok = 0;	/* in case of ${unset:-} */
+				/* ({) skip the } or x */
+				sp++;
+				/* in case of ${unset:-} */
+				tilde_ok = 0;
 				*dp = '\0';
 				quote = st->quotep;
 				f = st->f;
 				if (f&DOBLANK)
 					doblank--;
-				switch (st->stype&0x7f) {
+				switch (st->stype & 0x17F) {
 				case '#':
 				case '%':
-					/* Append end-pattern */
-					*dp++ = MAGIC; *dp++ = ')'; *dp = '\0';
+					if (!Flag(FSH)) {
+						/* Append end-pattern */
+						*dp++ = MAGIC;
+						*dp++ = ')';
+					}
+					*dp = '\0';
 					dp = Xrestpos(ds, dp, st->base);
-					/* Must use st->var since calling
+					/*
+					 * Must use st->var since calling
 					 * global would break things
 					 * like x[i+=1].
 					 */
@@ -593,20 +664,24 @@
 					st = st->prev;
 					continue;
 				case '=':
-					/* Restore our position and substitute
+					/*
+					 * Restore our position and substitute
 					 * the value of st->var (may not be
 					 * the assigned value in the presence
 					 * of integer/right-adj/etc attributes).
 					 */
 					dp = Xrestpos(ds, dp, st->base);
-					/* Must use st->var since calling
+					/*
+					 * Must use st->var since calling
 					 * global would cause with things
 					 * like x[i+=1] to be evaluated twice.
 					 */
-					/* Note: not exported by FEXPORT
+					/*
+					 * Note: not exported by FEXPORT
 					 * in AT&T ksh.
 					 */
-					/* XXX POSIX says readonly is only
+					/*
+					 * XXX POSIX says readonly is only
 					 * fatal for special builtins (setstr
 					 * does readonly check).
 					 */
@@ -630,6 +705,7 @@
 				}
 				case '0':
 				case '/':
+				case 0x100 | '#':
 					dp = Xrestpos(ds, dp, st->base);
 					type = XSUB;
 					if (f&DOBLANK)
@@ -641,18 +717,21 @@
 				type = XBASE;
 				continue;
 
-			case OPAT: /* open pattern: *(foo|bar) */
+			case OPAT:
+				/* open pattern: *(foo|bar) */
 				/* Next char is the type of pattern */
 				make_magic = 1;
-				c = *sp++ + 0x80;
+				c = *sp++ | 0x80;
 				break;
 
-			case SPAT: /* pattern separator (|) */
+			case SPAT:
+				/* pattern separator (|) */
 				make_magic = 1;
 				c = '|';
 				break;
 
-			case CPAT: /* close pattern */
+			case CPAT:
+				/* close pattern */
 				make_magic = 1;
 				c = /*(*/ ')';
 				break;
@@ -660,14 +739,16 @@
 			break;
 
 		case XNULLSUB:
-			/* Special case for "$@" (and "${foo[@]}") - no
+			/*
+			 * Special case for "$@" (and "${foo[@]}") - no
 			 * word is generated if $# is 0 (unless there is
 			 * other stuff inside the quotes).
 			 */
 			type = XBASE;
 			if (f&DOBLANK) {
 				doblank--;
-				/* not really correct: x=; "$x$@" should
+				/*
+				 * not really correct: x=; "$x$@" should
 				 * generate a null argument and
 				 * set A; "${@:+}" shouldn't.
 				 */
@@ -691,7 +772,8 @@
 			quote = 1;
 		case XARG:
 			if ((c = *x.str++) == '\0') {
-				/* force null words to be created so
+				/*
+				 * force null words to be created so
 				 * set -- '' 2 ''; foo "$@" will do
 				 * the right thing
 				 */
@@ -718,7 +800,8 @@
 			break;
 
 		case XCOM:
-			if (newlines) {		/* Spit out saved NLs */
+			if (newlines) {
+				/* Spit out saved NLs */
 				c = '\n';
 				--newlines;
 			} else {
@@ -748,7 +831,8 @@
 		/* check for end of word or IFS separation */
 		if (c == 0 || (!quote && (f & DOBLANK) && doblank &&
 		    !make_magic && ctype(c, C_IFS))) {
-			/* How words are broken up:
+			/*-
+			 * How words are broken up:
 			 *			|	value of c
 			 *	word		|	ws	nws	0
 			 *	-----------------------------------
@@ -765,14 +849,14 @@
 
 				*dp++ = '\0';
 				p = Xclose(ds, dp);
-				if (fdo & DOBRACE_)
+				if (fdo & DOBRACE)
 					/* also does globbing */
 					alt_expand(wp, p, p,
 					    p + Xlength(ds, (dp - 1)),
 					    fdo | (f & DOMARKDIRS));
 				else if (fdo & DOGLOB)
 					glob(p, wp, f & DOMARKDIRS);
-				else if ((f & DOPAT) || !(fdo & DOMAGIC_))
+				else if ((f & DOPAT) || !(fdo & DOMAGIC))
 					XPput(*wp, p);
 				else
 					XPput(*wp, debunk(p, p, strlen(p) + 1));
@@ -807,12 +891,13 @@
 				case NOT:
 				case '-':
 				case ']':
-					/* For character classes - doesn't hurt
+					/*
+					 * For character classes - doesn't hurt
 					 * to have magic !,-,]s outside of
 					 * [...] expressions.
 					 */
 					if (f & (DOPAT | DOGLOB)) {
-						fdo |= DOMAGIC_;
+						fdo |= DOMAGIC;
 						if (c == '[')
 							fdo |= f & DOGLOB;
 						*dp++ = MAGIC;
@@ -821,35 +906,37 @@
 				case '*':
 				case '?':
 					if (f & (DOPAT | DOGLOB)) {
-						fdo |= DOMAGIC_ | (f & DOGLOB);
+						fdo |= DOMAGIC | (f & DOGLOB);
 						*dp++ = MAGIC;
 					}
 					break;
 				case OBRACE:
 				case ',':
 				case CBRACE:
-					if ((f & DOBRACE_) && (c == OBRACE ||
-					    (fdo & DOBRACE_))) {
-						fdo |= DOBRACE_|DOMAGIC_;
+					if ((f & DOBRACE) && (c == OBRACE ||
+					    (fdo & DOBRACE))) {
+						fdo |= DOBRACE|DOMAGIC;
 						*dp++ = MAGIC;
 					}
 					break;
 				case '=':
 					/* Note first unquoted = for ~ */
-					if (!(f & DOTEMP_) && !saw_eq &&
+					if (!(f & DOTEMP) && !saw_eq &&
 					    (Flag(FBRACEEXPAND) ||
 					    (f & DOASNTILDE))) {
 						saw_eq = 1;
 						tilde_ok = 1;
 					}
 					break;
-				case ':': /* : */
+				case ':':
+					/* : */
 					/* Note unquoted : for ~ */
-					if (!(f & DOTEMP_) && (f & DOASNTILDE))
+					if (!(f & DOTEMP) && (f & DOASNTILDE))
 						tilde_ok = 1;
 					break;
 				case '~':
-					/* tilde_ok is reset whenever
+					/*
+					 * tilde_ok is reset whenever
 					 * any of ' " $( $(( ${ } are seen.
 					 * Note that tilde_ok must be preserved
 					 * through the sequence ${A=a=}~
@@ -875,17 +962,19 @@
 					break;
 				}
 			else
-				quote &= ~2; /* undo temporary */
+				/* undo temporary */
+				quote &= ~2;
 
 			if (make_magic) {
 				make_magic = 0;
-				fdo |= DOMAGIC_ | (f & DOGLOB);
+				fdo |= DOMAGIC | (f & DOGLOB);
 				*dp++ = MAGIC;
 			} else if (ISMAGIC(c)) {
-				fdo |= DOMAGIC_;
+				fdo |= DOMAGIC;
 				*dp++ = MAGIC;
 			}
-			*dp++ = c; /* save output char */
+			/* save output char */
+			*dp++ = c;
 			word = IFS_WORD;
 		}
 	}
@@ -907,7 +996,8 @@
 	struct tbl *vp;
 	bool zero_ok = false;
 
-	if ((stype = sp[0]) == '\0')	/* Bad variable name */
+	if ((stype = sp[0]) == '\0')
+		/* Bad variable name */
 		return (-1);
 
 	xp->var = NULL;
@@ -974,8 +1064,9 @@
 			}
 		}
 		if (Flag(FNOUNSET) && c == 0 && !zero_ok)
-			errorf("%s: parameter not set", sp);
-		*stypep = 0; /* unqualified variable/string substitution */
+			errorf("%s: %s", sp, "parameter not set");
+		/* unqualified variable/string substitution */
+		*stypep = 0;
 		xp->str = shf_smprintf("%d", c);
 		return (XSUB);
 	}
@@ -1000,14 +1091,24 @@
 	} else if (ctype(c, C_SUBOP1)) {
 		slen += 2;
 		stype |= c;
-	} else if (ctype(c, C_SUBOP2)) { /* Note: ksh88 allows :%, :%%, etc */
+	} else if (ctype(c, C_SUBOP2)) {
+		/* Note: ksh88 allows :%, :%%, etc */
 		slen += 2;
 		stype = c;
 		if (word[slen + 0] == CHAR && c == word[slen + 1]) {
 			stype |= 0x80;
 			slen += 2;
 		}
-	} else if (stype)	/* : is not ok */
+	} else if (c == '@') {
+		/* @x where x is command char */
+		slen += 2;
+		stype |= 0x100;
+		if (word[slen] == CHAR) {
+			stype |= word[slen + 1];
+			slen += 2;
+		}
+	} else if (stype)
+		/* : is not ok */
 		return (-1);
 	if (!stype && *word != CSUBST)
 		return (-1);
@@ -1016,12 +1117,13 @@
 
 	c = sp[0];
 	if (c == '*' || c == '@') {
-		switch (stype & 0x7f) {
+		switch (stype & 0x17F) {
 		case '=':	/* can't assign to a vector */
 		case '%':	/* can't trim a vector (yet) */
 		case '#':
 		case '0':
 		case '/':
+		case 0x100 | '#':
 			return (-1);
 		}
 		if (e->loc->argc == 0) {
@@ -1034,19 +1136,21 @@
 			xp->split = c == '@'; /* $@ */
 			state = XARG;
 		}
-		zero_ok = true;	/* POSIX 2009? */
+		/* POSIX 2009? */
+		zero_ok = true;
 	} else {
 		if ((p = cstrchr(sp, '[')) && (p[1] == '*' || p[1] == '@') &&
 		    p[2] == ']') {
 			XPtrV wv;
 
-			switch (stype & 0x7f) {
+			switch (stype & 0x17F) {
 			case '=':	/* can't assign to a vector */
 			case '%':	/* can't trim a vector (yet) */
 			case '#':
 			case '?':
 			case '0':
 			case '/':
+			case 0x100 | '#':
 				return (-1);
 			}
 			XPinit(wv, 32);
@@ -1073,13 +1177,13 @@
 			}
 		} else {
 			/* Can't assign things like $! or $1 */
-			if ((stype & 0x7f) == '=' &&
+			if ((stype & 0x17F) == '=' &&
 			    ctype(*sp, C_VAR1 | C_DIGIT))
 				return (-1);
 			if (*sp == '!' && sp[1]) {
 				++sp;
 				xp->var = global(sp);
-				if (cstrchr(sp, '[')) {
+				if (vstrchr(sp, '[')) {
 					if (xp->var->flag & ISSET)
 						xp->str = shf_smprintf("%lu",
 						    arrayindex(xp->var));
@@ -1088,7 +1192,8 @@
 				} else if (xp->var->flag & ISSET)
 					xp->str = xp->var->name;
 				else
-					xp->str = "0";	/* ksh93 compat */
+					/* ksh93 compat */
+					xp->str = "0";
 			} else {
 				xp->var = global(sp);
 				xp->str = str_val(xp->var);
@@ -1097,15 +1202,17 @@
 		}
 	}
 
-	c = stype&0x7f;
+	c = stype & 0x7F;
 	/* test the compiler's code generator */
-	if (ctype(c, C_SUBOP2) || stype == (0x80 | '0') || c == '/' ||
+	if (((stype < 0x100) && (ctype(c, C_SUBOP2) || c == '/' ||
 	    (((stype&0x80) ? *xp->str=='\0' : xp->str==null) ? /* undef? */
-	    c == '=' || c == '-' || c == '?' : c == '+'))
-		state = XBASE;	/* expand word instead of variable value */
+	    c == '=' || c == '-' || c == '?' : c == '+'))) ||
+	    stype == (0x80 | '0') || stype == (0x100 | '#'))
+		/* expand word instead of variable value */
+		state = XBASE;
 	if (Flag(FNOUNSET) && xp->str == null && !zero_ok &&
 	    (ctype(c, C_SUBOP2) || (state != XBASE && c != '+')))
-		errorf("%s: parameter not set", sp);
+		errorf("%s: %s", sp, "parameter not set");
 	return (state);
 }
 
@@ -1118,30 +1225,33 @@
 	Source *s, *sold;
 	struct op *t;
 	struct shf *shf;
+	uint8_t old_utfmode = UTFMODE;
 
 	s = pushs(SSTRING, ATEMP);
 	s->start = s->str = cp;
 	sold = source;
-	t = compile(s);
+	t = compile(s, true);
 	afree(s, ATEMP);
 	source = sold;
 
 	if (t == NULL)
 		return (XBASE);
 
-	if (t != NULL && t->type == TCOM && /* $(<file) */
+	if (t != NULL && t->type == TCOM &&
 	    *t->args == NULL && *t->vars == NULL && t->ioact != NULL) {
+		/* $(<file) */
 		struct ioword *io = *t->ioact;
 		char *name;
 
-		if ((io->flag&IOTYPE) != IOREAD)
-			errorf("funny $() command: %s",
+		if ((io->flag & IOTYPE) != IOREAD)
+			errorf("%s: %s", "funny $() command",
 			    snptreef(NULL, 32, "%R", io));
 		shf = shf_open(name = evalstr(io->name, DOTILDE), O_RDONLY, 0,
 			SHF_MAPHI|SHF_CLEXEC);
 		if (shf == NULL)
-			errorf("%s: cannot open $() input", name);
-		xp->split = 0;	/* no waitlast() */
+			errorf("%s: %s %s", name, "can't open", "$() input");
+		/* no waitlast() */
+		xp->split = 0;
 	} else {
 		int ofd1, pv[2];
 		openpipe(pv);
@@ -1154,9 +1264,11 @@
 		execute(t, XFORK|XXCOM|XPIPEO, NULL);
 		restfd(1, ofd1);
 		startlast();
-		xp->split = 1;	/* waitlast() */
+		/* waitlast() */
+		xp->split = 1;
 	}
 
+	UTFMODE = old_utfmode;
 	xp->u.shf = shf;
 	return (XCOM);
 }
@@ -1172,7 +1284,8 @@
 	char *p, c;
 
 	switch (how & 0xFF) {
-	case '#':		/* shortest at beginning */
+	case '#':
+		/* shortest match at beginning */
 		for (p = str; p <= end; p += utf_ptradj(p)) {
 			c = *p; *p = '\0';
 			if (gmatchx(str, pat, false)) {
@@ -1182,7 +1295,8 @@
 			*p = c;
 		}
 		break;
-	case '#'|0x80:		/* longest match at beginning */
+	case '#'|0x80:
+		/* longest match at beginning */
 		for (p = end; p >= str; p--) {
 			c = *p; *p = '\0';
 			if (gmatchx(str, pat, false)) {
@@ -1192,7 +1306,8 @@
 			*p = c;
 		}
 		break;
-	case '%':		/* shortest match at end */
+	case '%':
+		/* shortest match at end */
 		p = end;
 		while (p >= str) {
 			if (gmatchx(p, pat, false))
@@ -1207,7 +1322,8 @@
 				--p;
 		}
 		break;
-	case '%'|0x80:		/* longest match at end */
+	case '%'|0x80:
+		/* longest match at end */
 		for (p = str; p <= end; p++)
 			if (gmatchx(p, pat, false)) {
  trimsub_match:
@@ -1217,7 +1333,8 @@
 		break;
 	}
 
-	return (str);		/* no match, return string */
+	/* no match, return string */
+	return (str);
 }
 
 /*
@@ -1243,7 +1360,8 @@
 #define GF_GLOBBED	BIT(1)		/* some globbing has been done */
 #define GF_MARKDIR	BIT(2)		/* add trailing / to directories */
 
-/* Apply file globbing to cp and store the matching files in wp. Returns
+/*
+ * Apply file globbing to cp and store the matching files in wp. Returns
  * the number of matches found.
  */
 int
@@ -1275,8 +1393,10 @@
 	/* This to allow long expansions to be interrupted */
 	intrcheck();
 
-	if (sp == NULL) {	/* end of source path */
-		/* We only need to check if the file exists if a pattern
+	if (sp == NULL) {
+		/* end of source path */
+		/*
+		 * We only need to check if the file exists if a pattern
 		 * is followed by a non-pattern (eg, foo*x/bar; no check
 		 * is needed for foo* since the match must exist) or if
 		 * any patterns were expanded and the markdirs option is set.
@@ -1292,7 +1412,8 @@
 
 			if (lstat(Xstring(*xs, xp), &lstatb) < 0)
 				return;
-			/* special case for systems which strip trailing
+			/*
+			 * special case for systems which strip trailing
 			 * slashes from regular files (eg, /etc/passwd/).
 			 * SunOS 4.1.3 does this...
 			 */
@@ -1301,7 +1422,8 @@
 			    (!S_ISLNK(lstatb.st_mode) ||
 			    stat_check() < 0 || !S_ISDIR(statb.st_mode)))
 				return;
-			/* Possibly tack on a trailing / if there isn't already
+			/*
+			 * Possibly tack on a trailing / if there isn't already
 			 * one and if the file is a directory or a symlink to a
 			 * directory
 			 */
@@ -1328,7 +1450,8 @@
 	np = strchr(sp, '/');
 	if (np != NULL) {
 		se = np;
-		odirsep = *np;	/* don't assume '/', can be multiple kinds */
+		/* don't assume '/', can be multiple kinds */
+		odirsep = *np;
 		*np++ = '\0';
 	} else {
 		odirsep = '\0'; /* keep gcc quiet */
@@ -1336,7 +1459,8 @@
 	}
 
 
-	/* Check if sp needs globbing - done to avoid pattern checks for strings
+	/*
+	 * Check if sp needs globbing - done to avoid pattern checks for strings
 	 * containing MAGIC characters, open [s without the matching close ],
 	 * etc. (otherwise opendir() will be called which may fail because the
 	 * directory isn't readable - if no globbing is needed, only execute
@@ -1352,8 +1476,7 @@
 		DIR *dirp;
 		struct dirent *d;
 		char *name;
-		int len;
-		int prefix_len;
+		size_t len, prefix_len;
 
 		/* xp = *xpp;	copy_non_glob() may have re-alloc'd xs */
 		*xp = '\0';
@@ -1365,7 +1488,8 @@
 			name = d->d_name;
 			if (name[0] == '.' &&
 			    (name[1] == 0 || (name[1] == '.' && name[2] == 0)))
-				continue; /* always ignore . and .. */
+				/* always ignore . and .. */
+				continue;
 			if ((*name == '.' && *sp != '.') ||
 			    !gmatchx(name, sp, true))
 				continue;
@@ -1416,7 +1540,8 @@
 	return (dp);
 }
 
-/* Check if p is an unquoted name, possibly followed by a / or :. If so
+/*
+ * Check if p is an unquoted name, possibly followed by a / or :. If so
  * puts the expanded version in *dcp,dp and returns a pointer in p just
  * past the name, otherwise returns 0.
  */
@@ -1534,7 +1659,8 @@
 	}
 	/* no valid expansions... */
 	if (!p || count != 0) {
-		/* Note that given a{{b,c} we do not expand anything (this is
+		/*
+		 * Note that given a{{b,c} we do not expand anything (this is
 		 * what AT&T ksh does. This may be changed to do the {b,c}
 		 * expansion. }
 		 */
@@ -1562,6 +1688,10 @@
 				char *news;
 				int l1, l2, l3;
 
+				/*
+				 * addition safe since these operate on
+				 * one string (separate substrings)
+				 */
 				l1 = brace_start - start;
 				l2 = (p - 1) - field_start;
 				l3 = end - brace_end;
diff --git a/src/exec.c b/src/exec.c
index 391321a..c8dd4c4 100644
--- a/src/exec.c
+++ b/src/exec.c
@@ -1,7 +1,7 @@
 /*	$OpenBSD: exec.c,v 1.49 2009/01/29 23:27:26 jaredy Exp $	*/
 
 /*-
- * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
  *	Thorsten Glaser <tg@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -22,39 +22,42 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/exec.c,v 1.75 2010/07/17 22:09:34 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/exec.c,v 1.96 2011/09/07 15:24:14 tg Exp $");
 
 #ifndef MKSH_DEFAULT_EXECSHELL
 #define MKSH_DEFAULT_EXECSHELL	"/bin/sh"
 #endif
 
-static int comexec(struct op *, struct tbl *volatile, const char **,
+static int comexec(struct op *, struct tbl * volatile, const char **,
     int volatile, volatile int *);
 static void scriptexec(struct op *, const char **) MKSH_A_NORETURN;
 static int call_builtin(struct tbl *, const char **);
 static int iosetup(struct ioword *, struct tbl *);
-static int herein(const char *, int);
+static int herein(const char *, int, char **);
 static const char *do_selectargs(const char **, bool);
 static Test_op dbteste_isa(Test_env *, Test_meta);
 static const char *dbteste_getopnd(Test_env *, Test_op, bool);
 static void dbteste_error(Test_env *, int, const char *);
+static int search_access(const char *, int);
 
 /*
  * execute command tree
  */
 int
-execute(struct op *volatile t,
-    volatile int flags,		/* if XEXEC don't fork */
+execute(struct op * volatile t,
+    /* if XEXEC don't fork */
+    volatile int flags,
     volatile int * volatile xerrok)
 {
 	int i;
 	volatile int rv = 0, dummy = 0;
 	int pv[2];
-	const char ** volatile ap;
+	const char ** volatile ap = NULL;
 	char ** volatile up;
-	const char *s, *cp;
+	const char *s, *ccp;
 	struct ioword **iowp;
 	struct tbl *tp = NULL;
+	char *cp;
 
 	if (t == NULL)
 		return (0);
@@ -71,15 +74,60 @@
 	if (trap)
 		runtraps(0);
 
+	/* we want to run an executable, do some variance checks */
 	if (t->type == TCOM) {
-		/* Clear subst_exstat before argument expansion. Used by
+		/* check if this is 'var=<<EOF' */
+		if (
+		    /* we have zero arguments, i.e. no programme to run */
+		    t->args[0] == NULL &&
+		    /* we have exactly one variable assignment */
+		    t->vars[0] != NULL && t->vars[1] == NULL &&
+		    /* we have exactly one I/O redirection */
+		    t->ioact != NULL && t->ioact[0] != NULL &&
+		    t->ioact[1] == NULL &&
+		    /* of type "here document" (or "here string") */
+		    (t->ioact[0]->flag & IOTYPE) == IOHERE &&
+		    /* the variable assignment begins with a valid varname */
+		    (ccp = skip_wdvarname(t->vars[0], true)) != t->vars[0] &&
+		    /* and has no right-hand side (i.e. "varname=") */
+		    ccp[0] == CHAR && ccp[1] == '=' && ccp[2] == EOS &&
+		    /* plus we can have a here document content */
+		    herein(t->ioact[0]->heredoc, t->ioact[0]->flag & IOEVAL,
+		    &cp) == 0 && cp && *cp) {
+			char *sp = cp, *dp;
+			size_t n = ccp - t->vars[0] + 2, z;
+
+			/* drop redirection (will be garbage collected) */
+			t->ioact = NULL;
+
+			/* set variable to its expanded value */
+			z = strlen(cp) + 1;
+			if (notoktomul(z, 2) || notoktoadd(z * 2, n))
+				internal_errorf(Toomem, (unsigned long)-1);
+			dp = alloc(z * 2 + n, ATEMP);
+			memcpy(dp, t->vars[0], n);
+			t->vars[0] = dp;
+			dp += n;
+			while (*sp) {
+				*dp++ = QCHAR;
+				*dp++ = *sp++;
+			}
+			*dp = EOS;
+			/* free the expanded value */
+			afree(cp, APERM);
+		}
+
+		/*
+		 * Clear subst_exstat before argument expansion. Used by
 		 * null commands (see comexec() and c_eval()) and by c_set().
 		 */
 		subst_exstat = 0;
 
-		current_lineno = t->lineno;	/* for $LINENO */
+		/* for $LINENO */
+		current_lineno = t->lineno;
 
-		/* POSIX says expand command words first, then redirections,
+		/*
+		 * POSIX says expand command words first, then redirections,
 		 * and assignments last..
 		 */
 		up = eval(t->args, t->u.evalflags | DOBLANK | DOGLOB | DOTILDE);
@@ -88,8 +136,8 @@
 			timex_hook(t, &up);
 		ap = (const char **)up;
 		if (Flag(FXTRACE) && ap[0]) {
-			shf_fprintf(shl_out, "%s",
-				substitute(str_val(global("PS4")), 0));
+			shf_puts(substitute(str_val(global("PS4")), 0),
+			    shl_out);
 			for (i = 0; ap[i]; i++)
 				shf_fprintf(shl_out, "%s%c", ap[i],
 				    ap[i + 1] ? ' ' : '\n');
@@ -101,17 +149,21 @@
 	flags &= ~XTIME;
 
 	if (t->ioact != NULL || t->type == TPIPE || t->type == TCOPROC) {
-		e->savefd = alloc(NUFILE * sizeof(short), ATEMP);
+		e->savefd = alloc2(NUFILE, sizeof(short), ATEMP);
 		/* initialise to not redirected */
 		memset(e->savefd, 0, NUFILE * sizeof(short));
 	}
 
+	/* mark for replacement later (unless TPIPE) */
+	vp_pipest->flag |= INT_L;
+
 	/* do redirection, to be restored in quitenv() */
 	if (t->ioact != NULL)
 		for (iowp = t->ioact; *iowp != NULL; iowp++) {
 			if (iosetup(*iowp, tp) < 0) {
 				exstat = rv = 1;
-				/* Redirection failures for special commands
+				/*
+				 * Redirection failures for special commands
 				 * cause (non-interactive) shell to exit.
 				 */
 				if (tp && tp->type == CSHELL &&
@@ -138,7 +190,8 @@
 		e->savefd[1] = savefd(1);
 		while (t->type == TPIPE) {
 			openpipe(pv);
-			ksh_dup2(pv[1], 1, false); /* stdout of curr */
+			/* stdout of curr */
+			ksh_dup2(pv[1], 1, false);
 			/**
 			 * Let exchild() close pv[0] in child
 			 * (if this isn't done, commands like
@@ -147,15 +200,18 @@
 			 */
 			exchild(t->left, flags | XPIPEO | XCCLOSE,
 			    NULL, pv[0]);
-			ksh_dup2(pv[0], 0, false); /* stdin of next */
+			/* stdin of next */
+			ksh_dup2(pv[0], 0, false);
 			closepipe(pv);
 			flags |= XPIPEI;
 			t = t->right;
 		}
-		restfd(1, e->savefd[1]); /* stdout of last */
-		e->savefd[1] = 0; /* no need to re-restore this */
+		/* stdout of last */
+		restfd(1, e->savefd[1]);
+		/* no need to re-restore this */
+		e->savefd[1] = 0;
 		/* Let exchild() close 0 in parent, after fork, before wait */
-		i = exchild(t, flags | XPCLOSE, xerrok, 0);
+		i = exchild(t, flags | XPCLOSE | XPIPEST, xerrok, 0);
 		if (!(flags&XBGND) && !(flags&XXCOM))
 			rv = i;
 		break;
@@ -169,9 +225,11 @@
 		break;
 
 	case TCOPROC: {
+#ifndef MKSH_NOPROSPECTOFWORK
 		sigset_t omask;
 
-		/* Block sigchild as we are using things changed in the
+		/*
+		 * Block sigchild as we are using things changed in the
 		 * signal handler
 		 */
 		sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
@@ -183,6 +241,7 @@
 			unwind(i);
 			/* NOTREACHED */
 		}
+#endif
 		/* Already have a (live) co-process? */
 		if (coproc.job && coproc.write >= 0)
 			errorf("coprocess already exists");
@@ -208,15 +267,20 @@
 			openpipe(pv);
 			coproc.read = pv[0];
 			ksh_dup2(pv[1], 1, false);
-			coproc.readw = pv[1];	 /* closed before first read */
+			/* closed before first read */
+			coproc.readw = pv[1];
 			coproc.njobs = 0;
 			/* create new coprocess id */
 			++coproc.id;
 		}
+#ifndef MKSH_NOPROSPECTOFWORK
 		sigprocmask(SIG_SETMASK, &omask, NULL);
-		e->type = E_EXEC; /* no more need for error handler */
+		/* no more need for error handler */
+		e->type = E_EXEC;
+#endif
 
-		/* exchild() closes coproc.* in child after fork,
+		/*
+		 * exchild() closes coproc.* in child after fork,
 		 * will also increment coproc.njobs when the
 		 * job is actually created.
 		 */
@@ -227,7 +291,8 @@
 	}
 
 	case TASYNC:
-		/* XXX non-optimal, I think - "(foo &)", forks for (),
+		/*
+		 * XXX non-optimal, I think - "(foo &)", forks for (),
 		 * forks again for async... parent should optimise
 		 * this to "foo &"...
 		 */
@@ -272,7 +337,7 @@
 		    (const char **)eval((const char **)t->vars,
 		    DOBLANK | DOGLOB | DOTILDE);
 		e->type = E_LOOP;
-		while (1) {
+		while (/* CONSTCOND */ 1) {
 			i = sigsetjmp(e->jbuf, 0);
 			if (!i)
 				break;
@@ -285,20 +350,22 @@
 				goto Break;
 			}
 		}
-		rv = 0; /* in case of a continue */
+		/* in case of a continue */
+		rv = 0;
 		if (t->type == TFOR) {
 			while (*ap != NULL) {
 				setstr(global(t->str), *ap++, KSH_UNWIND_ERROR);
 				rv = execute(t->left, flags & XERROK, xerrok);
 			}
-		} else { /* TSELECT */
+		} else {
+			/* TSELECT */
 			for (;;) {
-				if (!(cp = do_selectargs(ap, is_first))) {
+				if (!(ccp = do_selectargs(ap, is_first))) {
 					rv = 1;
 					break;
 				}
 				is_first = false;
-				setstr(global(t->str), cp, KSH_UNWIND_ERROR);
+				setstr(global(t->str), ccp, KSH_UNWIND_ERROR);
 				execute(t->left, flags & XERROK, xerrok);
 			}
 		}
@@ -308,7 +375,7 @@
 	case TWHILE:
 	case TUNTIL:
 		e->type = E_LOOP;
-		while (1) {
+		while (/* CONSTCOND */ 1) {
 			i = sigsetjmp(e->jbuf, 0);
 			if (!i)
 				break;
@@ -321,7 +388,8 @@
 				goto Break;
 			}
 		}
-		rv = 0; /* in case of a continue */
+		/* in case of a continue */
+		rv = 0;
 		while ((execute(t->left, XERROK, NULL) == 0) ==
 		    (t->type == TWHILE))
 			rv = execute(t->right, flags & XERROK, xerrok);
@@ -330,22 +398,38 @@
 	case TIF:
 	case TELIF:
 		if (t->right == NULL)
-			break;	/* should be error */
+			/* should be error */
+			break;
 		rv = execute(t->left, XERROK, NULL) == 0 ?
 		    execute(t->right->left, flags & XERROK, xerrok) :
 		    execute(t->right->right, flags & XERROK, xerrok);
 		break;
 
 	case TCASE:
-		cp = evalstr(t->str, DOTILDE);
-		for (t = t->left; t != NULL && t->type == TPAT; t = t->right)
-		    for (ap = (const char **)t->vars; *ap; ap++)
-			if ((s = evalstr(*ap, DOTILDE|DOPAT)) &&
-			    gmatchx(cp, s, false))
-				goto Found;
-		break;
- Found:
-		rv = execute(t->left, flags & XERROK, xerrok);
+		i = 0;
+		ccp = evalstr(t->str, DOTILDE);
+		for (t = t->left; t != NULL && t->type == TPAT; t = t->right) {
+			for (ap = (const char **)t->vars; *ap; ap++) {
+				if (i || ((s = evalstr(*ap, DOTILDE|DOPAT)) &&
+				    gmatchx(ccp, s, false))) {
+					rv = execute(t->left, flags & XERROK,
+					    xerrok);
+					i = 0;
+					switch (t->u.charflag) {
+					case '&':
+						i = 1;
+						/* FALLTHROUGH */
+					case '|':
+						goto TCASE_next;
+					}
+					goto TCASE_out;
+				}
+			}
+			i = 0;
+ TCASE_next:
+			/* empty */;
+		}
+ TCASE_out:
 		break;
 
 	case TBRACE:
@@ -357,13 +441,15 @@
 		break;
 
 	case TTIME:
-		/* Clear XEXEC so nested execute() call doesn't exit
+		/*
+		 * Clear XEXEC so nested execute() call doesn't exit
 		 * (allows "ls -l | time grep foo").
 		 */
 		rv = timex(t, flags & ~XEXEC, xerrok);
 		break;
 
-	case TEXEC:		/* an eval'd TCOM */
+	case TEXEC:
+		/* an eval'd TCOM */
 		s = t->args[0];
 		up = makenv();
 		restoresigs();
@@ -382,13 +468,21 @@
 	}
  Break:
 	exstat = rv;
+	if (vp_pipest->flag & INT_L) {
+		unset(vp_pipest, 1);
+		vp_pipest->flag = DEFINED | ISSET | INTEGER | RDONLY |
+		    ARRAY | INT_U;
+		vp_pipest->val.i = rv;
+	}
 
-	quitenv(NULL);		/* restores IO */
+	/* restores IO */
+	quitenv(NULL);
 	if ((flags&XEXEC))
-		unwind(LEXIT);	/* exit child */
+		/* exit child */
+		unwind(LEXIT);
 	if (rv != 0 && !(flags & XERROK) &&
 	    (xerrok == NULL || !*xerrok)) {
-		trapsig(SIGERR_);
+		trapsig(ksh_SIGERR);
 		if (Flag(FERREXIT))
 			unwind(LERROR);
 	}
@@ -400,21 +494,23 @@
  */
 
 static int
-comexec(struct op *t, struct tbl *volatile tp, const char **ap,
+comexec(struct op *t, struct tbl * volatile tp, const char **ap,
     volatile int flags, volatile int *xerrok)
 {
 	int i;
 	volatile int rv = 0;
 	const char *cp;
 	const char **lastp;
-	static struct op texec; /* Must be static (XXX but why?) */
+	/* Must be static (XXX but why?) */
+	static struct op texec;
 	int type_flags;
 	int keepasn_ok;
 	int fcflags = FC_BI|FC_FUNC|FC_PATH;
 	bool bourne_function_call = false;
 	struct block *l_expand, *l_assign;
 
-	/* snag the last argument for $_ XXX not the same as AT&T ksh,
+	/*
+	 * snag the last argument for $_ XXX not the same as AT&T ksh,
 	 * which only seems to set $_ after a newline (but not in
 	 * functions/dot scripts, but in interactive and script) -
 	 * perhaps save last arg here and set it in shell()?.
@@ -427,7 +523,8 @@
 		    KSH_RETURN_ERROR);
 	}
 
-	/* Deal with the shell builtins builtin, exec and command since
+	/**
+	 * Deal with the shell builtins builtin, exec and command since
 	 * they can be followed by other commands. This must be done before
 	 * we know if we should create a local block which must be done
 	 * before we can do a path search (in case the assignments change
@@ -441,15 +538,16 @@
 	 */
 	keepasn_ok = 1;
 	while (tp && tp->type == CSHELL) {
-		fcflags = FC_BI|FC_FUNC|FC_PATH;/* undo effects of command */
+		/* undo effects of command */
+		fcflags = FC_BI|FC_FUNC|FC_PATH;
 		if (tp->val.f == c_builtin) {
-			if ((cp = *++ap) == NULL) {
+			if ((cp = *++ap) == NULL ||
+			    (!strcmp(cp, "--") && (cp = *++ap) == NULL)) {
 				tp = NULL;
 				break;
 			}
-			tp = findcom(cp, FC_BI);
-			if (tp == NULL)
-				errorf("builtin: %s: not a builtin", cp);
+			if ((tp = findcom(cp, FC_BI)) == NULL)
+				errorf("%s: %s: %s", Tbuiltin, cp, "not a builtin");
 			continue;
 		} else if (tp->val.f == c_exec) {
 			if (ap[1] == NULL)
@@ -459,27 +557,30 @@
 		} else if (tp->val.f == c_command) {
 			int optc, saw_p = 0;
 
-			/* Ugly dealing with options in two places (here and
-			 * in c_command(), but such is life)
+			/*
+			 * Ugly dealing with options in two places (here
+			 * and in c_command(), but such is life)
 			 */
 			ksh_getopt_reset(&builtin_opt, 0);
 			while ((optc = ksh_getopt(ap, &builtin_opt, ":p")) == 'p')
 				saw_p = 1;
 			if (optc != EOF)
-				break;	/* command -vV or something */
+				/* command -vV or something */
+				break;
 			/* don't look for functions */
 			fcflags = FC_BI|FC_PATH;
 			if (saw_p) {
 				if (Flag(FRESTRICTED)) {
-					warningf(true,
-					    "command -p: restricted");
+					warningf(true, "%s: %s",
+					    "command -p", "restricted");
 					rv = 1;
 					goto Leave;
 				}
 				fcflags |= FC_DEFPATH;
 			}
 			ap += builtin_opt.optind;
-			/* POSIX says special builtins lose their status
+			/*
+			 * POSIX says special builtins lose their status
 			 * if accessed using command.
 			 */
 			keepasn_ok = 0;
@@ -488,6 +589,25 @@
 				subst_exstat = 0;
 				break;
 			}
+#ifndef MKSH_NO_EXTERNAL_CAT
+		} else if (tp->val.f == c_cat) {
+			/*
+			 * if we have any flags, do not use the builtin
+			 * in theory, we could allow -u, but that would
+			 * mean to use ksh_getopt here and possibly ad-
+			 * ded complexity and more code and isn't worth
+			 * additional hassle (and the builtin must call
+			 * ksh_getopt already but can't come back here)
+			 */
+			if (ap[1] && ap[1][0] == '-' && ap[1][1] != '\0' &&
+			    /* argument, begins with -, is not - or -- */
+			    (ap[1][1] != '-' || ap[1][2] != '\0'))
+				/* don't look for builtins or functions */
+				fcflags = FC_PATH;
+			else
+				/* go on, use the builtin */
+				break;
+#endif
 		} else
 			break;
 		tp = findcom(ap[0], fcflags & (FC_BI|FC_FUNC));
@@ -518,8 +638,8 @@
 
 		if (Flag(FXTRACE)) {
 			if (i == 0)
-				shf_fprintf(shl_out, "%s",
-					substitute(str_val(global("PS4")), 0));
+				shf_puts(substitute(str_val(global("PS4")), 0),
+				    shl_out);
 			shf_fprintf(shl_out, "%s%c", cp,
 			    t->vars[i + 1] ? ' ' : '\n');
 			if (!t->vars[i + 1])
@@ -535,7 +655,7 @@
 		goto Leave;
 	} else if (!tp) {
 		if (Flag(FRESTRICTED) && vstrchr(cp, '/')) {
-			warningf(true, "%s: restricted", cp);
+			warningf(true, "%s: %s", cp, "restricted");
 			rv = 1;
 			goto Leave;
 		}
@@ -543,54 +663,49 @@
 	}
 
 	switch (tp->type) {
-	case CSHELL:			/* shell built-in */
+
+	/* shell built-in */
+	case CSHELL:
 		rv = call_builtin(tp, (const char **)ap);
 		break;
 
-	case CFUNC: {			/* function call */
+	/* function call */
+	case CFUNC: {
 		volatile unsigned char old_xflag;
-		volatile Tflag old_inuse;
-		const char *volatile old_kshname;
+		volatile uint32_t old_inuse;
+		const char * volatile old_kshname;
 
 		if (!(tp->flag & ISSET)) {
 			struct tbl *ftp;
 
 			if (!tp->u.fpath) {
-				if (tp->u2.errno_) {
-					warningf(true,
-					    "%s: can't find function "
-					    "definition file - %s",
-					    cp, strerror(tp->u2.errno_));
-					rv = 126;
-				} else {
-					warningf(true,
-					    "%s: can't find function "
-					    "definition file", cp);
-					rv = 127;
-				}
+				rv = (tp->u2.errnov == ENOENT) ? 127 : 126;
+				warningf(true, "%s: %s %s: %s", cp,
+				    "can't find", "function definition file",
+				    strerror(tp->u2.errnov));
 				break;
 			}
 			if (include(tp->u.fpath, 0, NULL, 0) < 0) {
 				rv = errno;
-				warningf(true,
-				    "%s: can't open function definition file %s - %s",
-				    cp, tp->u.fpath, strerror(rv));
+				warningf(true, "%s: %s %s %s: %s", cp,
+				    "can't open", "function definition file",
+				    tp->u.fpath, strerror(rv));
 				rv = 127;
 				break;
 			}
 			if (!(ftp = findfunc(cp, hash(cp), false)) ||
 			    !(ftp->flag & ISSET)) {
-				warningf(true,
-				    "%s: function not defined by %s",
-				    cp, tp->u.fpath);
+				warningf(true, "%s: %s %s", cp,
+				    "function not defined by", tp->u.fpath);
 				rv = 127;
 				break;
 			}
 			tp = ftp;
 		}
 
-		/* ksh functions set $0 to function name, POSIX functions leave
-		 * $0 unchanged.
+		/*
+		 * ksh functions set $0 to function name, POSIX
+		 * functions leave $0 unchanged.
 		 */
 		old_kshname = kshname;
 		if (tp->flag & FKSH)
@@ -601,7 +716,8 @@
 		for (i = 0; *ap++ != NULL; i++)
 			;
 		e->loc->argc = i - 1;
-		/* ksh-style functions handle getopts sanely,
+		/*
+		 * ksh-style functions handle getopts sanely,
 		 * Bourne/POSIX functions are insane...
 		 */
 		if (tp->flag & FKSH) {
@@ -611,7 +727,7 @@
 		}
 
 		old_xflag = Flag(FXTRACE);
-		Flag(FXTRACE) = tp->flag & TRACE ? 1 : 0;
+		Flag(FXTRACE) |= tp->flag & TRACE ? 1 : 0;
 
 		old_inuse = tp->flag & FINUSE;
 		tp->flag |= FINUSE;
@@ -626,9 +742,10 @@
 		kshname = old_kshname;
 		Flag(FXTRACE) = old_xflag;
 		tp->flag = (tp->flag & ~FINUSE) | old_inuse;
-		/* Were we deleted while executing? If so, free the execution
-		 * tree. todo: Unfortunately, the table entry is never re-used
-		 * until the lookup table is expanded.
+		/*
+		 * Were we deleted while executing? If so, free the
+		 * execution tree. TODO: Unfortunately, the table entry
+		 * is never re-used until the lookup table is expanded.
 		 */
 		if ((tp->flag & (FDELETE|FINUSE)) == FDELETE) {
 			if (tp->flag & ALLOC) {
@@ -651,26 +768,23 @@
 			/* NOTREACHED */
 		default:
 			quitenv(NULL);
-			internal_errorf("CFUNC %d", i);
+			internal_errorf("%s %d", "CFUNC", i);
 		}
 		break;
 	}
 
-	case CEXEC:		/* executable command */
-	case CTALIAS:		/* tracked alias */
+	/* executable command */
+	case CEXEC:
+	/* tracked alias */
+	case CTALIAS:
 		if (!(tp->flag&ISSET)) {
-			/* errno_ will be set if the named command was found
-			 * but could not be executed (permissions, no execute
-			 * bit, directory, etc). Print out a (hopefully)
-			 * useful error message and set the exit status to 126.
-			 */
-			if (tp->u2.errno_) {
-				warningf(true, "%s: cannot execute - %s", cp,
-				    strerror(tp->u2.errno_));
-				rv = 126;	/* POSIX */
-			} else {
-				warningf(true, "%s: not found", cp);
+			if (tp->u2.errnov == ENOENT) {
 				rv = 127;
+				warningf(true, "%s: %s", cp, "not found");
+			} else {
+				rv = 126;
+				warningf(true, "%s: %s: %s", cp, "can't execute",
+				    strerror(tp->u2.errnov));
 			}
 			break;
 		}
@@ -694,7 +808,8 @@
 
 		/* to fork we set up a TEXEC node and call execute */
 		texec.type = TEXEC;
-		texec.left = t;	/* for tprint */
+		/* for tprint */
+		texec.left = t;
 		texec.str = tp->val.s;
 		texec.args = ap;
 		rv = exchild(&texec, flags, xerrok, -1);
@@ -714,14 +829,15 @@
 	const char *sh;
 #ifndef MKSH_SMALL
 	unsigned char *cp;
-	char buf[64];		/* 64 == MAXINTERP in MirBSD <sys/param.h> */
+	/* 64 == MAXINTERP in MirBSD <sys/param.h> */
+	char buf[64];
 	int fd;
 #endif
 	union mksh_ccphack args, cap;
 
 	sh = str_val(global("EXECSHELL"));
 	if (sh && *sh)
-		sh = search(sh, path, X_OK, NULL);
+		sh = search_path(sh, path, X_OK, NULL);
 	if (!sh || !*sh)
 		sh = MKSH_DEFAULT_EXECSHELL;
 
@@ -734,8 +850,15 @@
 			/* read error -> no good */
 			buf[0] = '\0';
 		close(fd);
-		/* scan for newline (or CR) or NUL _before_ end of buffer */
+
+		/* skip UTF-8 Byte Order Mark, if present */
 		cp = (unsigned char *)buf;
+		if ((cp[0] == 0xEF) && (cp[1] == 0xBB) && (cp[2] == 0xBF))
+			cp += 3;
+		/* save begin of shebang for later */
+		fd = (char *)cp - buf;		/* either 0 or (if BOM) 3 */
+
+		/* scan for newline (or CR) or NUL _before_ end of buffer */
 		while ((char *)cp < (buf + sizeof(buf)))
 			if (*cp == '\0' || *cp == '\n' || *cp == '\r') {
 				*cp = '\0';
@@ -745,13 +868,13 @@
 		/* if the shebang line is longer than MAXINTERP, bail out */
 		if ((char *)cp >= (buf + sizeof(buf)))
 			goto noshebang;
-		/* skip UTF-8 Byte Order Mark, if present */
-		cp = (unsigned char *)buf;
-		if ((cp[0] == 0xEF) && (cp[1] == 0xBB) && (cp[2] == 0xBF))
-			cp += 3;
+
+		/* restore begin of shebang position (buf+0 or buf+3) */
+		cp = (unsigned char *)(buf + fd);
 		/* bail out if read error (above) or no shebang */
 		if ((cp[0] != '#') || (cp[1] != '!'))
 			goto noshebang;
+
 		cp += 2;
 		/* skip whitespace before shell name */
 		while (*cp == ' ' || *cp == '\t')
@@ -806,7 +929,7 @@
 
 	tp = ktsearch(&builtins, *wp, hash(*wp));
 	if (tp == NULL)
-		internal_errorf("shcomexec: %s", *wp);
+		internal_errorf("%s: %s", "shcomexec", *wp);
 	return (call_builtin(tp, wp));
 }
 
@@ -842,20 +965,31 @@
 int
 define(const char *name, struct op *t)
 {
+	uint32_t nhash;
 	struct tbl *tp;
 	bool was_set = false;
 
-	while (1) {
-		tp = findfunc(name, hash(name), true);
+	nhash = hash(name);
+
+	if (t != NULL && !tobool(t->u.ksh_func)) {
+		/* drop same-name aliases for POSIX functions */
+		if ((tp = ktsearch(&aliases, name, nhash)))
+			ktdelete(tp);
+	}
+
+	while (/* CONSTCOND */ 1) {
+		tp = findfunc(name, nhash, true);
 
 		if (tp->flag & ISSET)
 			was_set = true;
-		/* If this function is currently being executed, we zap this
-		 * table entry so findfunc() won't see it
+		/*
+		 * If this function is currently being executed, we zap
+		 * this table entry so findfunc() won't see it
 		 */
 		if (tp->flag & FINUSE) {
 			tp->name[0] = '\0';
-			tp->flag &= ~DEFINED; /* ensure it won't be found */
+			/* ensure it won't be found */
+			tp->flag &= ~DEFINED;
 			tp->flag |= FDELETE;
 		} else
 			break;
@@ -866,7 +1000,8 @@
 		tfree(tp->val.t, tp->areap);
 	}
 
-	if (t == NULL) {		/* undefine */
+	if (t == NULL) {
+		/* undefine */
 		ktdelete(tp);
 		return (was_set ? 0 : 1);
 	}
@@ -882,19 +1017,22 @@
 /*
  * add builtin
  */
-void
+const char *
 builtin(const char *name, int (*func) (const char **))
 {
 	struct tbl *tp;
-	Tflag flag;
+	uint32_t flag;
 
 	/* see if any flags should be set for this builtin */
 	for (flag = 0; ; name++) {
-		if (*name == '=')	/* command does variable assignment */
+		if (*name == '=')
+			/* command does variable assignment */
 			flag |= KEEPASN;
-		else if (*name == '*')	/* POSIX special builtin */
+		else if (*name == '*')
+			/* POSIX special builtin */
 			flag |= SPEC_BI;
-		else if (*name == '+')	/* POSIX regular builtin */
+		else if (*name == '+')
+			/* POSIX regular builtin */
 			flag |= REG_BI;
 		else
 			break;
@@ -904,6 +1042,8 @@
 	tp->flag = DEFINED | flag;
 	tp->type = CSHELL;
 	tp->val.f = func;
+
+	return (name);
 }
 
 /*
@@ -916,8 +1056,10 @@
 	static struct tbl temp;
 	uint32_t h = hash(name);
 	struct tbl *tp = NULL, *tbi;
-	unsigned char insert = Flag(FTRACKALL);	/* insert if not found */
-	char *fpath;			/* for function autoloading */
+	/* insert if not found */
+	unsigned char insert = Flag(FTRACKALL);
+	/* for function autoloading */
+	char *fpath;
 	union mksh_cchack npath;
 
 	if (vstrchr(name, '/')) {
@@ -927,7 +1069,8 @@
 		goto Search;
 	}
 	tbi = (flags & FC_BI) ? ktsearch(&builtins, name, h) : NULL;
-	/* POSIX says special builtins first, then functions, then
+	/*
+	 * POSIX says special builtins first, then functions, then
 	 * POSIX regular builtins, then search path...
 	 */
 	if ((flags & FC_SPECBI) && tbi && (tbi->flag & SPEC_BI))
@@ -937,10 +1080,10 @@
 		if (tp && !(tp->flag & ISSET)) {
 			if ((fpath = str_val(global("FPATH"))) == null) {
 				tp->u.fpath = NULL;
-				tp->u2.errno_ = 0;
+				tp->u2.errnov = ENOENT;
 			} else
-				tp->u.fpath = search(name, fpath, R_OK,
-				    &tp->u2.errno_);
+				tp->u.fpath = search_path(name, fpath, R_OK,
+				    &tp->u2.errnov);
 		}
 	}
 	if (!tp && (flags & FC_REGBI) && tbi && (tbi->flag & REG_BI))
@@ -949,7 +1092,8 @@
 		tp = tbi;
 	if (!tp && (flags & FC_PATH) && !(flags & FC_DEFPATH)) {
 		tp = ktsearch(&taliases, name, h);
-		if (tp && (tp->flag & ISSET) && access(tp->val.s, X_OK) != 0) {
+		if (tp && (tp->flag & ISSET) &&
+		    ksh_access(tp->val.s, X_OK) != 0) {
 			if (tp->flag & ALLOC) {
 				tp->flag &= ~ALLOC;
 				afree(tp->val.s, APERM);
@@ -969,10 +1113,12 @@
 				tp = &temp;
 				tp->type = CEXEC;
 			}
-			tp->flag = DEFINED;	/* make ~ISSET */
+			/* make ~ISSET */
+			tp->flag = DEFINED;
 		}
-		npath.ro = search(name, flags & FC_DEFPATH ? def_path : path,
-		    X_OK, &tp->u2.errno_);
+		npath.ro = search_path(name,
+		    (flags & FC_DEFPATH) ? def_path : path,
+		    X_OK, &tp->u2.errnov);
 		if (npath.ro) {
 			strdupx(tp->val.s, npath.ro, APERM);
 			if (npath.ro != name)
@@ -980,16 +1126,18 @@
 			tp->flag |= ISSET|ALLOC;
 		} else if ((flags & FC_FUNC) &&
 		    (fpath = str_val(global("FPATH"))) != null &&
-		    (npath.ro = search(name, fpath, R_OK,
-		    &tp->u2.errno_)) != NULL) {
-			/* An undocumented feature of AT&T ksh is that it
-			 * searches FPATH if a command is not found, even
-			 * if the command hasn't been set up as an autoloaded
-			 * function (ie, no typeset -uf).
+		    (npath.ro = search_path(name, fpath, R_OK,
+		    &tp->u2.errnov)) != NULL) {
+			/*
+			 * An undocumented feature of AT&T ksh is that
+			 * it searches FPATH if a command is not found,
+			 * even if the command hasn't been set up as an
+			 * autoloaded function (ie, no typeset -uf).
 			 */
 			tp = &temp;
 			tp->type = CFUNC;
-			tp->flag = DEFINED; /* make ~ISSET */
+			/* make ~ISSET */
+			tp->flag = DEFINED;
 			tp->u.fpath = npath.ro;
 		}
 	}
@@ -998,9 +1146,10 @@
 
 /*
  * flush executable commands with relative paths
+ * (just relative or all?)
  */
 void
-flushcom(int all)	/* just relative or all */
+flushcom(bool all)
 {
 	struct tbl *tp;
 	struct tstate ts;
@@ -1015,49 +1164,50 @@
 		}
 }
 
-/* Check if path is something we want to find. Returns -1 for failure. */
-int
-search_access(const char *lpath, int mode,
-    int *errnop)	/* set if candidate found, but not suitable */
+/* check if path is something we want to find */
+static int
+search_access(const char *fn, int mode)
 {
-	int ret, err = 0;
-	struct stat statb;
+	struct stat sb;
 
-	if (stat(lpath, &statb) < 0)
-		return (-1);
-	ret = access(lpath, mode);
-	if (ret < 0)
-		err = errno; /* File exists, but we can't access it */
-	else if (mode == X_OK && (!S_ISREG(statb.st_mode) ||
-	    !(statb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)))) {
-		/* This 'cause access() says root can execute everything */
-		ret = -1;
-		err = S_ISDIR(statb.st_mode) ? EISDIR : EACCES;
-	}
-	if (err && errnop && !*errnop)
-		*errnop = err;
-	return (ret);
+	if (stat(fn, &sb) < 0)
+		/* file does not exist */
+		return (ENOENT);
+	/* LINTED use of access */
+	if (access(fn, mode) < 0)
+		/* file exists, but we can't access it */
+		return (errno);
+	if (mode == X_OK && (!S_ISREG(sb.st_mode) ||
+	    !(sb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH))))
+		/* access(2) may say root can execute everything */
+		return (S_ISDIR(sb.st_mode) ? EISDIR : EACCES);
+	return (0);
 }
 
 /*
  * search for command with PATH
  */
 const char *
-search(const char *name, const char *lpath,
-    int mode,		/* R_OK or X_OK */
-    int *errnop)	/* set if candidate found, but not suitable */
+search_path(const char *name, const char *lpath,
+    /* R_OK or X_OK */
+    int mode,
+    /* set if candidate found, but not suitable */
+    int *errnop)
 {
 	const char *sp, *p;
 	char *xp;
 	XString xs;
-	int namelen;
+	size_t namelen;
+	int ec = 0, ev;
 
-	if (errnop)
-		*errnop = 0;
 	if (vstrchr(name, '/')) {
-		if (search_access(name, mode, errnop) == 0)
+		if ((ec = search_access(name, mode)) == 0) {
+ search_path_ok:
+			if (errnop)
+				*errnop = 0;
 			return (name);
-		return (NULL);
+		}
+		goto search_path_err;
 	}
 
 	namelen = strlen(name) + 1;
@@ -1077,12 +1227,20 @@
 		sp = p;
 		XcheckN(xs, xp, namelen);
 		memcpy(xp, name, namelen);
-		if (search_access(Xstring(xs, xp), mode, errnop) == 0)
-			return (Xclose(xs, xp + namelen));
+		if ((ev = search_access(Xstring(xs, xp), mode)) == 0) {
+			name = Xclose(xs, xp + namelen);
+			goto search_path_ok;
+		}
+		/* accumulate non-ENOENT errors only */
+		if (ev != ENOENT && ec == 0)
+			ec = ev;
 		if (*sp++ == '\0')
 			sp = NULL;
 	}
 	Xfree(xs, xp);
+ search_path_err:
+	if (errnop)
+		*errnop = ec ? ec : ENOENT;
 	return (NULL);
 }
 
@@ -1094,11 +1252,11 @@
 	builtin_argv0 = wp[0];
 	builtin_flag = tp->flag;
 	shf_reopen(1, SHF_WR, shl_stdout);
-	shl_stdout_ok = 1;
+	shl_stdout_ok = true;
 	ksh_getopt_reset(&builtin_opt, GF_ERROR);
 	rv = (*tp->val.f)(wp);
 	shf_flush(shl_stdout);
-	shl_stdout_ok = 0;
+	shl_stdout_ok = false;
 	builtin_flag = 0;
 	builtin_argv0 = NULL;
 	return (rv);
@@ -1141,7 +1299,8 @@
 
 	case IOWRITE:
 		flags = O_WRONLY | O_CREAT | O_TRUNC;
-		/* The stat() is here to allow redirections to
+		/*
+		 * The stat() is here to allow redirections to
 		 * things like /dev/null without error.
 		 */
 		if (Flag(FNOCLOBBER) && !(iop->flag & IOCLOB) &&
@@ -1156,7 +1315,7 @@
 	case IOHERE:
 		do_open = 0;
 		/* herein() returns -2 if error has been printed */
-		u = herein(iop->heredoc, iop->flag & IOEVAL);
+		u = herein(iop->heredoc, iop->flag & IOEVAL, NULL);
 		/* cp may have wrong name */
 		break;
 
@@ -1165,7 +1324,8 @@
 
 		do_open = 0;
 		if (*cp == '-' && !cp[1]) {
-			u = 1009;	 /* prevent error return below */
+			/* prevent error return below */
+			u = 1009;
 			do_close = 1;
 		} else if ((u = check_fd(cp,
 		    X_OK | ((iop->flag & IORDUP) ? R_OK : W_OK),
@@ -1175,14 +1335,15 @@
 			return (-1);
 		}
 		if (u == iop->unit)
-			return (0);		/* "dup from" == "dup to" */
+			/* "dup from" == "dup to" */
+			return (0);
 		break;
 	}
 	}
 
 	if (do_open) {
 		if (Flag(FRESTRICTED) && (flags & O_CREAT)) {
-			warningf(true, "%s: restricted", cp);
+			warningf(true, "%s: %s", cp, "restricted");
 			return (-1);
 		}
 		u = open(cp, flags, 0666);
@@ -1191,7 +1352,7 @@
 		/* herein() may already have printed message */
 		if (u == -1) {
 			u = errno;
-			warningf(true, "cannot %s %s: %s",
+			warningf(true, "can't %s %s: %s",
 			    iotype == IODUP ? "dup" :
 			    (iotype == IOREAD || iotype == IOHERE) ?
 			    "open" : "create", cp, strerror(u));
@@ -1204,7 +1365,8 @@
 		if (u == iop->unit)
 			e->savefd[iop->unit] = -1;
 		else
-			/* c_exec() assumes e->savefd[fd] set for any
+			/*
+			 * c_exec() assumes e->savefd[fd] set for any
 			 * redirections. Ask savefd() not to close iop->unit;
 			 * this allows error messages to be seen if iop->unit
 			 * is 2; also means we can't lose the fd (eg, both
@@ -1220,8 +1382,8 @@
 			int ev;
 
 			ev = errno;
-			warningf(true,
-			    "could not finish (dup) redirection %s: %s",
+			warningf(true, "%s %s %s",
+			    "can't finish (dup) redirection",
 			    snptreef(NULL, 32, "%R", &iotmp),
 			    strerror(ev));
 			if (iotype != IODUP)
@@ -1230,84 +1392,111 @@
 		}
 		if (iotype != IODUP)
 			close(u);
-		/* Touching any co-process fd in an empty exec
+		/*
+		 * Touching any co-process fd in an empty exec
 		 * causes the shell to close its copies
 		 */
 		else if (tp && tp->type == CSHELL && tp->val.f == c_exec) {
-			if (iop->flag & IORDUP)	/* possible exec <&p */
+			if (iop->flag & IORDUP)
+				/* possible exec <&p */
 				coproc_read_close(u);
-			else			/* possible exec >&p */
+			else
+				/* possible exec >&p */
 				coproc_write_close(u);
 		}
 	}
-	if (u == 2) /* Clear any write errors */
+	if (u == 2)
+		/* Clear any write errors */
 		shf_reopen(2, SHF_WR, shl_out);
 	return (0);
 }
 
 /*
- * open here document temp file.
- * if unquoted here, expand here temp file into second temp file.
+ * Process here documents by providing the content, either as
+ * result (globally allocated) string or in a temp file; if
+ * unquoted, the string is expanded first.
  */
 static int
-herein(const char *content, int sub)
+hereinval(const char *content, int sub, char **resbuf, struct shf *shf)
 {
-	volatile int fd = -1;
-	struct source *s, *volatile osource;
-	struct shf *volatile shf;
+	const char *ccp;
+	struct source *s, *osource;
+
+	osource = source;
+	newenv(E_ERRH);
+	if (sigsetjmp(e->jbuf, 0)) {
+		source = osource;
+		quitenv(shf);
+		/* special to iosetup(): don't print error */
+		return (-2);
+	}
+	if (sub) {
+		/* do substitutions on the content of heredoc */
+		s = pushs(SSTRING, ATEMP);
+		s->start = s->str = content;
+		source = s;
+		if (yylex(ONEWORD|HEREDOC) != LWORD)
+			internal_errorf("%s: %s", "herein", "yylex");
+		source = osource;
+		ccp = evalstr(yylval.cp, 0);
+	} else
+		ccp = content;
+
+	if (resbuf == NULL)
+		shf_puts(ccp, shf);
+	else
+		strdupx(*resbuf, ccp, APERM);
+
+	quitenv(NULL);
+	return (0);
+}
+
+static int
+herein(const char *content, int sub, char **resbuf)
+{
+	int fd = -1;
+	struct shf *shf;
 	struct temp *h;
 	int i;
 
 	/* ksh -c 'cat << EOF' can cause this... */
 	if (content == NULL) {
-		warningf(true, "here document missing");
-		return (-2); /* special to iosetup(): don't print error */
+		warningf(true, "%s missing", "here document");
+		/* special to iosetup(): don't print error */
+		return (-2);
 	}
 
-	/* Create temp file to hold content (done before newenv so temp
-	 * doesn't get removed too soon).
+	/* skip all the fd setup if we just want the value */
+	if (resbuf != NULL)
+		return (hereinval(content, sub, resbuf, NULL));
+
+	/*
+	 * Create temp file to hold content (done before newenv
+	 * so temp doesn't get removed too soon).
 	 */
 	h = maketemp(ATEMP, TT_HEREDOC_EXP, &e->temps);
 	if (!(shf = h->shf) || (fd = open(h->name, O_RDONLY, 0)) < 0) {
-		fd = errno;
+		i = errno;
 		warningf(true, "can't %s temporary file %s: %s",
-		    !shf ? "create" : "open",
-		    h->name, strerror(fd));
+		    !shf ? "create" : "open", h->name, strerror(i));
 		if (shf)
 			shf_close(shf);
-		return (-2 /* special to iosetup(): don't print error */);
+		/* special to iosetup(): don't print error */
+		return (-2);
 	}
 
-	osource = source;
-	newenv(E_ERRH);
-	i = sigsetjmp(e->jbuf, 0);
-	if (i) {
-		source = osource;
-		quitenv(shf);
+	if (hereinval(content, sub, NULL, shf) == -2) {
 		close(fd);
-		return (-2); /* special to iosetup(): don't print error */
+		/* special to iosetup(): don't print error */
+		return (-2);
 	}
-	if (sub) {
-		/* Do substitutions on the content of heredoc */
-		s = pushs(SSTRING, ATEMP);
-		s->start = s->str = content;
-		source = s;
-		if (yylex(ONEWORD|HEREDOC) != LWORD)
-			internal_errorf("herein: yylex");
-		source = osource;
-		shf_puts(evalstr(yylval.cp, 0), shf);
-	} else
-		shf_puts(content, shf);
-
-	quitenv(NULL);
 
 	if (shf_close(shf) == EOF) {
 		i = errno;
 		close(fd);
-		fd = errno;
-		warningf(true, "error writing %s: %s, %s", h->name,
-		    strerror(i), strerror(fd));
-		return (-2);	/* special to iosetup(): don't print error */
+		warningf(true, "%s: %s: %s", "write", h->name, strerror(i));
+		/* special to iosetup(): don't print error */
+		return (-2);
 	}
 
 	return (fd);
@@ -1328,8 +1517,9 @@
 
 	for (argct = 0; ap[argct]; argct++)
 		;
-	while (1) {
-		/* Menu is printed if
+	while (/* CONSTCOND */ 1) {
+		/*-
+		 * Menu is printed if
 		 *	- this is the first time around the select loop
 		 *	- the user enters a blank line
 		 *	- the REPLY parameter is empty
@@ -1353,11 +1543,11 @@
 	int num_width;
 };
 
-static char *select_fmt_entry(char *, int, int, const void *);
+static char *select_fmt_entry(char *, size_t, int, const void *);
 
 /* format a single select menu item */
 static char *
-select_fmt_entry(char *buf, int buflen, int i, const void *arg)
+select_fmt_entry(char *buf, size_t buflen, int i, const void *arg)
 {
 	const struct select_menu_info *smi =
 	    (const struct select_menu_info *)arg;
@@ -1375,7 +1565,8 @@
 {
 	struct select_menu_info smi;
 	const char * const *pp;
-	int acols = 0, aocts = 0, i, n;
+	size_t acols = 0, aocts = 0, i;
+	int n;
 
 	/*
 	 * width/column calculations were done once and saved, but this
@@ -1412,19 +1603,20 @@
 }
 
 /* XXX: horrible kludge to fit within the framework */
-static char *plain_fmt_entry(char *, int, int, const void *);
+static char *plain_fmt_entry(char *, size_t, int, const void *);
 
 static char *
-plain_fmt_entry(char *buf, int buflen, int i, const void *arg)
+plain_fmt_entry(char *buf, size_t buflen, int i, const void *arg)
 {
-	shf_snprintf(buf, buflen, "%s", ((const char * const *)arg)[i]);
+	strlcpy(buf, ((const char * const *)arg)[i], buflen);
 	return (buf);
 }
 
 int
 pr_list(char * const *ap)
 {
-	int acols = 0, aocts = 0, i, n;
+	size_t acols = 0, aocts = 0, i;
+	int n;
 	char * const *pp;
 
 	for (n = 0, pp = ap; *pp; n++, pp++) {
@@ -1468,8 +1660,10 @@
 
 	if (meta == TM_UNOP || meta == TM_BINOP) {
 		if (uqword) {
-			char buf[8];	/* longer than the longest operator */
+			/* longer than the longest operator */
+			char buf[8];
 			char *q = buf;
+
 			for (p = *te->pos.wp;
 			    *p == CHAR && q < &buf[sizeof(buf) - 1]; p += 2)
 				*q++ = p[1];
diff --git a/src/expr.c b/src/expr.c
index 6c5710c..3c8252d 100644
--- a/src/expr.c
+++ b/src/expr.c
@@ -1,7 +1,7 @@
 /*	$OpenBSD: expr.c,v 1.21 2009/06/01 19:00:57 deraadt Exp $	*/
 
 /*-
- * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
  *	Thorsten Glaser <tg@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -22,7 +22,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/expr.c,v 1.44 2010/08/14 21:35:13 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/expr.c,v 1.49 2011/09/07 15:24:14 tg Exp $");
 
 /* The order of these enums is constrained by the order of opinfo[] */
 enum token {
@@ -141,12 +141,6 @@
 	(mksh_ari_t)((x)->val.u op (y)->val.u) :	\
 	(mksh_ari_t)((x)->val.i op (y)->val.i)		\
 )
-#define chvui(x, op)	do {			\
-	if (es->natural)			\
-		(x)->val.u = op (x)->val.u;	\
-	else					\
-		(x)->val.i = op (x)->val.i;	\
-} while (/* CONSTCOND */ 0)
 #define stvui(x, n)	do {			\
 	if (es->natural)			\
 		(x)->val.u = (n);		\
@@ -269,26 +263,28 @@
 		default:
 			s = opinfo[(int)es->tok].name;
 		}
-		warningf(true, "%s: unexpected '%s'", es->expression, s);
+		warningf(true, "%s: %s '%s'", es->expression,
+		    "unexpected", s);
 		break;
 
 	case ET_BADLIT:
-		warningf(true, "%s: bad number '%s'", es->expression, str);
+		warningf(true, "%s: %s '%s'", es->expression,
+		    "bad number", str);
 		break;
 
 	case ET_RECURSIVE:
-		warningf(true, "%s: expression recurses on parameter '%s'",
-		    es->expression, str);
+		warningf(true, "%s: %s '%s'", es->expression,
+		    "expression recurses on parameter", str);
 		break;
 
 	case ET_LVALUE:
-		warningf(true, "%s: %s requires lvalue",
-		    es->expression, str);
+		warningf(true, "%s: %s %s",
+		    es->expression, str, "requires lvalue");
 		break;
 
 	case ET_RDONLY:
-		warningf(true, "%s: %s applied to read only variable",
-		    es->expression, str);
+		warningf(true, "%s: %s %s",
+		    es->expression, str, "applied to read only variable");
 		break;
 
 	default: /* keep gcc happy */
@@ -313,11 +309,11 @@
 			exprtoken(es);
 			vl = intvar(es, evalexpr(es, P_PRIMARY));
 			if (op == O_BNOT)
-				chvui(vl, ~);
+				vl->val.i = ~vl->val.i;
 			else if (op == O_LNOT)
-				chvui(vl, !);
+				vl->val.i = !vl->val.i;
 			else if (op == O_MINUS)
-				chvui(vl, -);
+				vl->val.i = -vl->val.i;
 			/* op == O_PLUS is a no-op */
 		} else if (op == OPEN_PAREN) {
 			exprtoken(es);
@@ -503,7 +499,7 @@
 		for (; ksh_isalnux(c); c = *cp)
 			cp++;
 		if (c == '[') {
-			int len;
+			size_t len;
 
 			len = array_ref_len(cp);
 			if (len == 0)
@@ -680,12 +676,12 @@
 	return (width);
 }
 
-int
+size_t
 utf_mbswidth(const char *s)
 {
-	size_t len;
+	size_t len, width = 0;
 	unsigned int wc;
-	int width = 0, cw;
+	int cw;
 
 	if (!UTFMODE)
 		return (strlen(s));
@@ -808,7 +804,7 @@
  * disclaims all warranties with regard to this software.
  */
 
-__RCSID("$miros: src/lib/libc/i18n/wcwidth.c,v 1.8 2008/09/20 12:01:18 tg Exp $");
+__RCSID("$miros: src/lib/libc/i18n/wcwidth.c,v 1.10 2010/12/11 16:05:03 tg Exp $");
 
 int
 utf_wcwidth(unsigned int c)
@@ -817,54 +813,77 @@
 		unsigned short first;
 		unsigned short last;
 	} comb[] = {
-		{ 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 },
-		{ 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 },
-		{ 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 },
-		{ 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 },
-		{ 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED },
+		/* Unicode 6.0.0 BMP */
+		{ 0x0300, 0x036F }, { 0x0483, 0x0489 }, { 0x0591, 0x05BD },
+		{ 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, { 0x05C4, 0x05C5 },
+		{ 0x05C7, 0x05C7 }, { 0x0600, 0x0603 }, { 0x0610, 0x061A },
+		{ 0x064B, 0x065F }, { 0x0670, 0x0670 }, { 0x06D6, 0x06DD },
+		{ 0x06DF, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED },
 		{ 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A },
-		{ 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 },
+		{ 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0816, 0x0819 },
+		{ 0x081B, 0x0823 }, { 0x0825, 0x0827 }, { 0x0829, 0x082D },
+		{ 0x0859, 0x085B }, { 0x0900, 0x0902 }, { 0x093A, 0x093A },
 		{ 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D },
-		{ 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 },
+		{ 0x0951, 0x0957 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 },
 		{ 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD },
 		{ 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C },
 		{ 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D },
-		{ 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC },
-		{ 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD },
-		{ 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C },
-		{ 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D },
-		{ 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 },
+		{ 0x0A51, 0x0A51 }, { 0x0A70, 0x0A71 }, { 0x0A75, 0x0A75 },
+		{ 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC }, { 0x0AC1, 0x0AC5 },
+		{ 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD }, { 0x0AE2, 0x0AE3 },
+		{ 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C }, { 0x0B3F, 0x0B3F },
+		{ 0x0B41, 0x0B44 }, { 0x0B4D, 0x0B4D }, { 0x0B56, 0x0B56 },
+		{ 0x0B62, 0x0B63 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 },
 		{ 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 },
-		{ 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC },
-		{ 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD },
-		{ 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D },
-		{ 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 },
-		{ 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E },
-		{ 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC },
-		{ 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 },
-		{ 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E },
-		{ 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 },
-		{ 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 },
-		{ 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 },
-		{ 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F },
-		{ 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 },
-		{ 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD },
-		{ 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD },
-		{ 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 },
-		{ 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B },
-		{ 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 },
+		{ 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0C62, 0x0C63 },
+		{ 0x0CBC, 0x0CBC }, { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 },
+		{ 0x0CCC, 0x0CCD }, { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D44 },
+		{ 0x0D4D, 0x0D4D }, { 0x0D62, 0x0D63 }, { 0x0DCA, 0x0DCA },
+		{ 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, { 0x0E31, 0x0E31 },
+		{ 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, { 0x0EB1, 0x0EB1 },
+		{ 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, { 0x0EC8, 0x0ECD },
+		{ 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, { 0x0F37, 0x0F37 },
+		{ 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, { 0x0F80, 0x0F84 },
+		{ 0x0F86, 0x0F87 }, { 0x0F8D, 0x0F97 }, { 0x0F99, 0x0FBC },
+		{ 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, { 0x1032, 0x1037 },
+		{ 0x1039, 0x103A }, { 0x103D, 0x103E }, { 0x1058, 0x1059 },
+		{ 0x105E, 0x1060 }, { 0x1071, 0x1074 }, { 0x1082, 0x1082 },
+		{ 0x1085, 0x1086 }, { 0x108D, 0x108D }, { 0x109D, 0x109D },
+		{ 0x1160, 0x11FF }, { 0x135D, 0x135F }, { 0x1712, 0x1714 },
+		{ 0x1732, 0x1734 }, { 0x1752, 0x1753 }, { 0x1772, 0x1773 },
+		{ 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, { 0x17C6, 0x17C6 },
+		{ 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, { 0x180B, 0x180D },
+		{ 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, { 0x1927, 0x1928 },
+		{ 0x1932, 0x1932 }, { 0x1939, 0x193B }, { 0x1A17, 0x1A18 },
+		{ 0x1A56, 0x1A56 }, { 0x1A58, 0x1A5E }, { 0x1A60, 0x1A60 },
+		{ 0x1A62, 0x1A62 }, { 0x1A65, 0x1A6C }, { 0x1A73, 0x1A7C },
+		{ 0x1A7F, 0x1A7F }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 },
 		{ 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 },
-		{ 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF },
-		{ 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 },
-		{ 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F },
-		{ 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B },
-		{ 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F },
-		{ 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }
+		{ 0x1B6B, 0x1B73 }, { 0x1B80, 0x1B81 }, { 0x1BA2, 0x1BA5 },
+		{ 0x1BA8, 0x1BA9 }, { 0x1BE6, 0x1BE6 }, { 0x1BE8, 0x1BE9 },
+		{ 0x1BED, 0x1BED }, { 0x1BEF, 0x1BF1 }, { 0x1C2C, 0x1C33 },
+		{ 0x1C36, 0x1C37 }, { 0x1CD0, 0x1CD2 }, { 0x1CD4, 0x1CE0 },
+		{ 0x1CE2, 0x1CE8 }, { 0x1CED, 0x1CED }, { 0x1DC0, 0x1DE6 },
+		{ 0x1DFC, 0x1DFF }, { 0x200B, 0x200F }, { 0x202A, 0x202E },
+		{ 0x2060, 0x2064 }, { 0x206A, 0x206F }, { 0x20D0, 0x20F0 },
+		{ 0x2CEF, 0x2CF1 }, { 0x2D7F, 0x2D7F }, { 0x2DE0, 0x2DFF },
+		{ 0x302A, 0x302F }, { 0x3099, 0x309A }, { 0xA66F, 0xA672 },
+		{ 0xA67C, 0xA67D }, { 0xA6F0, 0xA6F1 }, { 0xA802, 0xA802 },
+		{ 0xA806, 0xA806 }, { 0xA80B, 0xA80B }, { 0xA825, 0xA826 },
+		{ 0xA8C4, 0xA8C4 }, { 0xA8E0, 0xA8F1 }, { 0xA926, 0xA92D },
+		{ 0xA947, 0xA951 }, { 0xA980, 0xA982 }, { 0xA9B3, 0xA9B3 },
+		{ 0xA9B6, 0xA9B9 }, { 0xA9BC, 0xA9BC }, { 0xAA29, 0xAA2E },
+		{ 0xAA31, 0xAA32 }, { 0xAA35, 0xAA36 }, { 0xAA43, 0xAA43 },
+		{ 0xAA4C, 0xAA4C }, { 0xAAB0, 0xAAB0 }, { 0xAAB2, 0xAAB4 },
+		{ 0xAAB7, 0xAAB8 }, { 0xAABE, 0xAABF }, { 0xAAC1, 0xAAC1 },
+		{ 0xABE5, 0xABE5 }, { 0xABE8, 0xABE8 }, { 0xABED, 0xABED },
+		{ 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, { 0xFE20, 0xFE26 },
+		{ 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }
 	};
 	size_t min = 0, mid, max = NELEM(comb) - 1;
 
 	/* test for 8-bit control characters */
-	if (c < 32 || (c >= 0x7f && c < 0xa0))
+	if (c < 32 || (c >= 0x7F && c < 0xA0))
 		return (c ? -1 : 0);
 
 	/* binary search in table of non-spacing characters */
@@ -880,16 +899,36 @@
 		}
 
 	/* if we arrive here, c is not a combining or C0/C1 control char */
+
 	return ((c >= 0x1100 && (
-	    c <= 0x115f || /* Hangul Jamo init. consonants */
-	    c == 0x2329 || c == 0x232a ||
-	    (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f) || /* CJK ... Yi */
-	    (c >= 0xac00 && c <= 0xd7a3) || /* Hangul Syllables */
-	    (c >= 0xf900 && c <= 0xfaff) || /* CJK Compatibility Ideographs */
-	    (c >= 0xfe10 && c <= 0xfe19) || /* Vertical forms */
-	    (c >= 0xfe30 && c <= 0xfe6f) || /* CJK Compatibility Forms */
-	    (c >= 0xff00 && c <= 0xff60) || /* Fullwidth Forms */
-	    (c >= 0xffe0 && c <= 0xffe6))) ? 2 : 1);
+	    c <= 0x115F || /* Hangul Jamo init. consonants */
+	    c == 0x2329 || c == 0x232A ||
+	    (c >= 0x2E80 && c <= 0xA4CF && c != 0x303F) || /* CJK ... Yi */
+	    (c >= 0xAC00 && c <= 0xD7A3) || /* Hangul Syllables */
+	    (c >= 0xF900 && c <= 0xFAFF) || /* CJK Compatibility Ideographs */
+	    (c >= 0xFE10 && c <= 0xFE19) || /* Vertical forms */
+	    (c >= 0xFE30 && c <= 0xFE6F) || /* CJK Compatibility Forms */
+	    (c >= 0xFF00 && c <= 0xFF60) || /* Fullwidth Forms */
+	    (c >= 0xFFE0 && c <= 0xFFE6))) ? 2 : 1);
 }
 /* --- end of wcwidth.c excerpt --- */
 #endif
+
+/*
+ * Wrapper around access(2) because it says root can execute everything
+ * on some operating systems. Does not set errno, no user needs it. Use
+ * this iff mode can have the X_OK bit set, access otherwise.
+ */
+int
+ksh_access(const char *fn, int mode)
+{
+	int rv;
+	struct stat sb;
+
+	if ((rv = access(fn, mode)) == 0 && kshuid == 0 && (mode & X_OK) &&
+	    (rv = stat(fn, &sb)) == 0 && !S_ISDIR(sb.st_mode) &&
+	    (sb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) == 0)
+		rv = -1;
+
+	return (rv);
+}
diff --git a/src/funcs.c b/src/funcs.c
index 9d9c03a..23f45de 100644
--- a/src/funcs.c
+++ b/src/funcs.c
@@ -4,7 +4,8 @@
 /*	$OpenBSD: c_ulimit.c,v 1.17 2008/03/21 12:51:19 millert Exp $	*/
 
 /*-
- * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
+ *		 2010, 2011
  *	Thorsten Glaser <tg@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -25,7 +26,19 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/funcs.c,v 1.157 2010/08/24 14:42:01 tg Exp $");
+#if HAVE_SELECT
+#if HAVE_SYS_BSDTYPES_H
+#include <sys/bsdtypes.h>
+#endif
+#if HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+#if HAVE_BSTRING_H
+#include <bstring.h>
+#endif
+#endif
+
+__RCSID("$MirOS: src/bin/mksh/funcs.c,v 1.197 2011/09/07 15:24:15 tg Exp $");
 
 #if HAVE_KILLPG
 /*
@@ -40,51 +53,69 @@
 
 /* XXX conditions correct? */
 #if !defined(RLIM_INFINITY) && !defined(MKSH_NO_LIMITS)
-#define MKSH_NO_LIMITS
+#define MKSH_NO_LIMITS	1
 #endif
 
 #ifdef MKSH_NO_LIMITS
-#define c_ulimit c_label
+#define c_ulimit	c_true
 #endif
 
-extern uint8_t set_refflag;
+#if defined(ANDROID)
+static int c_android_lsmod(const char **);
+#endif
 
-/* A leading = means assignments before command are kept;
+static int
+c_true(const char **wp MKSH_A_UNUSED)
+{
+	return (0);
+}
+
+static int
+c_false(const char **wp MKSH_A_UNUSED)
+{
+	return (1);
+}
+
+/*
+ * A leading = means assignments before command are kept;
  * a leading * means a POSIX special builtin;
  * a leading + means a POSIX regular builtin
  * (* and + should not be combined).
  */
 const struct builtin mkshbuiltins[] = {
 	{"*=.", c_dot},
-	{"*=:", c_label},
+	{"*=:", c_true},
 	{"[", c_test},
 	{"*=break", c_brkcont},
-	{"=builtin", c_builtin},
+	{Tgbuiltin, c_builtin},
 	{"*=continue", c_brkcont},
 	{"*=eval", c_eval},
 	{"*=exec", c_exec},
 	{"*=exit", c_exitreturn},
-	{"+false", c_label},
+	{"+false", c_false},
 	{"*=return", c_exitreturn},
-	{"*=set", c_set},
+	{Tsgset, c_set},
 	{"*=shift", c_shift},
 	{"=times", c_times},
 	{"*=trap", c_trap},
 	{"+=wait", c_wait},
 	{"+read", c_read},
 	{"test", c_test},
-	{"+true", c_label},
+	{"+true", c_true},
 	{"ulimit", c_ulimit},
 	{"+umask", c_umask},
 	{"*=unset", c_unset},
-	{"+alias", c_alias},	/* no =: AT&T manual wrong */
+	/* no =: AT&T manual wrong */
+	{Tpalias, c_alias},
 	{"+cd", c_cd},
-	{"chdir", c_cd},	/* dash compatibility hack */
+	/* dash compatibility hack */
+	{"chdir", c_cd},
 	{"+command", c_command},
 	{"echo", c_print},
 	{"*=export", c_typeset},
 	{"+fc", c_fc},
 	{"+getopts", c_getopts},
+	{"=global", c_typeset},
 	{"+jobs", c_jobs},
 	{"+kill", c_kill},
 	{"let", c_let},
@@ -94,19 +125,30 @@
 #endif
 	{"pwd", c_pwd},
 	{"*=readonly", c_typeset},
-	{T__typeset, c_typeset},
-	{"+unalias", c_unalias},
+	{T_typeset, c_typeset},
+	{Tpunalias, c_unalias},
 	{"whence", c_whence},
 #ifndef MKSH_UNEMPLOYED
 	{"+bg", c_fgbg},
 	{"+fg", c_fgbg},
 #endif
 	{"bind", c_bind},
+	{"cat", c_cat},
 #if HAVE_MKNOD
 	{"mknod", c_mknod},
 #endif
 	{"realpath", c_realpath},
 	{"rename", c_rename},
+#if HAVE_SELECT
+	{"sleep", c_sleep},
+#endif
+#ifdef __MirBSD__
+	/* alias to "true" for historical reasons */
+	{"domainname", c_true},
+#endif
+#if defined(ANDROID)
+	{"lsmod", c_android_lsmod},
+#endif
 	{NULL, (int (*)(const char **))NULL}
 };
 
@@ -163,7 +205,6 @@
 	{"",	TO_NONOP }
 };
 
-static int test_eaccess(const char *, int);
 static int test_oexpr(Test_env *, bool);
 static int test_aexpr(Test_env *, bool);
 static int test_nexpr(Test_env *, bool);
@@ -171,331 +212,16 @@
 static Test_op ptest_isa(Test_env *, Test_meta);
 static const char *ptest_getopnd(Test_env *, Test_op, bool);
 static void ptest_error(Test_env *, int, const char *);
-static char *kill_fmt_entry(char *, int, int, const void *);
+static char *kill_fmt_entry(char *, size_t, int, const void *);
 static void p_time(struct shf *, bool, long, int, int,
     const char *, const char *)
-    MKSH_A_NONNULL((nonnull (6, 7)));
-static char *do_realpath(const char *);
-
-static char *
-do_realpath(const char *upath)
-{
-	char *xp, *ip, *tp, *ipath, *ldest = NULL;
-	XString xs;
-	ptrdiff_t pos;
-	size_t len;
-	int symlinks = 32;	/* max. recursion depth */
-	int llen;
-	struct stat sb;
-#ifdef NO_PATH_MAX
-	size_t ldestlen = 0;
-#define pathlen sb.st_size
-#define pathcnd (ldestlen < (pathlen + 1))
-#else
-#define pathlen PATH_MAX
-#define pathcnd (!ldest)
-#endif
-
-	if (upath[0] == '/') {
-		/* upath is an absolute pathname */
-		strdupx(ipath, upath, ATEMP);
-	} else {
-		/* upath is a relative pathname, prepend cwd */
-		if ((tp = ksh_get_wd(NULL)) == NULL || tp[0] != '/')
-			return (NULL);
-		ipath = shf_smprintf("%s/%s", tp, upath);
-		afree(tp, ATEMP);
-	}
-
-	Xinit(xs, xp, strlen(ip = ipath) + 1, ATEMP);
-
-	while (*ip) {
-		/* skip slashes in input */
-		while (*ip == '/')
-			++ip;
-		if (!*ip)
-			break;
-
-		/* get next pathname component from input */
-		tp = ip;
-		while (*ip && *ip != '/')
-			++ip;
-		len = ip - tp;
-
-		/* check input for "." and ".." */
-		if (tp[0] == '.') {
-			if (len == 1)
-				/* just continue with the next one */
-				continue;
-			else if (len == 2 && tp[1] == '.') {
-				/* strip off last pathname component */
-				while (xp > Xstring(xs, xp))
-					if (*--xp == '/')
-						break;
-				/* then continue with the next one */
-				continue;
-			}
-		}
-
-		/* store output position away, then append slash to output */
-		pos = Xsavepos(xs, xp);
-		/* 1 for the '/' and len + 1 for tp and the NUL from below */
-		XcheckN(xs, xp, 1 + len + 1);
-		Xput(xs, xp, '/');
-
-		/* append next pathname component to output */
-		memcpy(xp, tp, len);
-		xp += len;
-		*xp = '\0';
-
-		/* lstat the current output, see if it's a symlink */
-		if (lstat(Xstring(xs, xp), &sb)) {
-			/* lstat failed */
-			if (errno == ENOENT) {
-				/* because the pathname does not exist */
-				while (*ip == '/')
-					/* skip any trailing slashes */
-					++ip;
-				/* no more components left? */
-				if (!*ip)
-					/* we can still return successfully */
-					break;
-				/* more components left? fall through */
-			}
-			/* not ENOENT or not at the end of ipath */
-			goto notfound;
-		}
-
-		/* check if we encountered a symlink? */
-		if (S_ISLNK(sb.st_mode)) {
-			/* reached maximum recursion depth? */
-			if (!symlinks--) {
-				/* yep, prevent infinite loops */
-				errno = ELOOP;
-				goto notfound;
-			}
-
-			/* get symlink(7) target */
-			if (pathcnd)
-				ldest = aresize(ldest, pathlen + 1, ATEMP);
-			llen = readlink(Xstring(xs, xp), ldest, pathlen);
-			if (llen < 0)
-				/* oops... */
-				goto notfound;
-			ldest[llen] = '\0';
-
-			/*
-			 * restart if symlink target is an absolute path,
-			 * otherwise continue with currently resolved prefix
-			 */
-			xp = (ldest[0] == '/') ? Xstring(xs, xp) :
-			    Xrestpos(xs, xp, pos);
-			tp = shf_smprintf("%s%s%s", ldest, *ip ? "/" : "", ip);
-			afree(ipath, ATEMP);
-			ip = ipath = tp;
-		}
-		/* otherwise (no symlink) merely go on */
-	}
-
-	/*
-	 * either found the target and successfully resolved it,
-	 * or found its parent directory and may create it
-	 */
-	if (Xlength(xs, xp) == 0)
-		/*
-		 * if the resolved pathname is "", make it "/",
-		 * otherwise do not add a trailing slash
-		 */
-		Xput(xs, xp, '/');
-	Xput(xs, xp, '\0');
-
-	/*
-	 * if source path had a trailing slash, check if target path
-	 * is not a non-directory existing file
-	 */
-	if (ip > ipath && ip[-1] == '/') {
-		if (stat(Xstring(xs, xp), &sb)) {
-			if (errno != ENOENT)
-				goto notfound;
-		} else if (!S_ISDIR(sb.st_mode)) {
-			errno = ENOTDIR;
-			goto notfound;
-		}
-		/* target now either does not exist or is a directory */
-	}
-
-	/* return target path */
-	if (ldest != NULL)
-		afree(ldest, ATEMP);
-	afree(ipath, ATEMP);
-	return (Xclose(xs, xp));
-
- notfound:
-	llen = errno;	/* save; free(3) might trash it */
-	if (ldest != NULL)
-		afree(ldest, ATEMP);
-	afree(ipath, ATEMP);
-	Xfree(xs, xp);
-	errno = llen;
-	return (NULL);
-
-#undef pathlen
-#undef pathcnd
-}
-
-int
-c_cd(const char **wp)
-{
-	int optc, rv, phys_path;
-	bool physical = Flag(FPHYSICAL) ? true : false;
-	int cdnode;			/* was a node from cdpath added in? */
-	bool printpath = false;		/* print where we cd'd? */
-	struct tbl *pwd_s, *oldpwd_s;
-	XString xs;
-	char *dir, *allocd = NULL, *tryp, *pwd, *cdpath;
-
-	while ((optc = ksh_getopt(wp, &builtin_opt, "LP")) != -1)
-		switch (optc) {
-		case 'L':
-			physical = false;
-			break;
-		case 'P':
-			physical = true;
-			break;
-		case '?':
-			return (1);
-		}
-	wp += builtin_opt.optind;
-
-	if (Flag(FRESTRICTED)) {
-		bi_errorf("restricted shell - can't cd");
-		return (1);
-	}
-
-	pwd_s = global("PWD");
-	oldpwd_s = global("OLDPWD");
-
-	if (!wp[0]) {
-		/* No arguments - go home */
-		if ((dir = str_val(global("HOME"))) == null) {
-			bi_errorf("no home directory (HOME not set)");
-			return (1);
-		}
-	} else if (!wp[1]) {
-		/* One argument: - or dir */
-		strdupx(allocd, wp[0], ATEMP);
-		if (ksh_isdash((dir = allocd))) {
-			afree(allocd, ATEMP);
-			allocd = NULL;
-			dir = str_val(oldpwd_s);
-			if (dir == null) {
-				bi_errorf("no OLDPWD");
-				return (1);
-			}
-			printpath = true;
-		}
-	} else if (!wp[2]) {
-		/* Two arguments - substitute arg1 in PWD for arg2 */
-		int ilen, olen, nlen, elen;
-		char *cp;
-
-		if (!current_wd[0]) {
-			bi_errorf("don't know current directory");
-			return (1);
-		}
-		/* substitute arg1 for arg2 in current path.
-		 * if the first substitution fails because the cd fails
-		 * we could try to find another substitution. For now
-		 * we don't
-		 */
-		if ((cp = strstr(current_wd, wp[0])) == NULL) {
-			bi_errorf("bad substitution");
-			return (1);
-		}
-		ilen = cp - current_wd;
-		olen = strlen(wp[0]);
-		nlen = strlen(wp[1]);
-		elen = strlen(current_wd + ilen + olen) + 1;
-		dir = allocd = alloc(ilen + nlen + elen, ATEMP);
-		memcpy(dir, current_wd, ilen);
-		memcpy(dir + ilen, wp[1], nlen);
-		memcpy(dir + ilen + nlen, current_wd + ilen + olen, elen);
-		printpath = true;
-	} else {
-		bi_errorf("too many arguments");
-		return (1);
-	}
-
-#ifdef NO_PATH_MAX
-	/* only a first guess; make_path will enlarge xs if necessary */
-	XinitN(xs, 1024, ATEMP);
-#else
-	XinitN(xs, PATH_MAX, ATEMP);
-#endif
-
-	cdpath = str_val(global("CDPATH"));
-	do {
-		cdnode = make_path(current_wd, dir, &cdpath, &xs, &phys_path);
-		if (physical)
-			rv = chdir(tryp = Xstring(xs, xp) + phys_path);
-		else {
-			simplify_path(Xstring(xs, xp));
-			rv = chdir(tryp = Xstring(xs, xp));
-		}
-	} while (rv < 0 && cdpath != NULL);
-
-	if (rv < 0) {
-		if (cdnode)
-			bi_errorf("%s: bad directory", dir);
-		else
-			bi_errorf("%s - %s", tryp, strerror(errno));
-		afree(allocd, ATEMP);
-		return (1);
-	}
-
-	/* allocd (above) => dir, which is no longer used */
-	afree(allocd, ATEMP);
-	allocd = NULL;
-
-	/* Clear out tracked aliases with relative paths */
-	flushcom(0);
-
-	/* Set OLDPWD (note: unsetting OLDPWD does not disable this
-	 * setting in AT&T ksh)
-	 */
-	if (current_wd[0])
-		/* Ignore failure (happens if readonly or integer) */
-		setstr(oldpwd_s, current_wd, KSH_RETURN_ERROR);
-
-	if (Xstring(xs, xp)[0] != '/') {
-		pwd = NULL;
-	} else if (!physical || !(pwd = allocd = do_realpath(Xstring(xs, xp))))
-		pwd = Xstring(xs, xp);
-
-	/* Set PWD */
-	if (pwd) {
-		char *ptmp = pwd;
-
-		set_current_wd(ptmp);
-		/* Ignore failure (happens if readonly or integer) */
-		setstr(pwd_s, ptmp, KSH_RETURN_ERROR);
-	} else {
-		set_current_wd(null);
-		pwd = Xstring(xs, xp);
-		/* XXX unset $PWD? */
-	}
-	if (printpath || cdnode)
-		shprintf("%s\n", pwd);
-
-	afree(allocd, ATEMP);
-	return (0);
-}
+    MKSH_A_NONNULL((__nonnull__ (6, 7)));
 
 int
 c_pwd(const char **wp)
 {
 	int optc;
-	bool physical = Flag(FPHYSICAL) ? true : false;
+	bool physical = tobool(Flag(FPHYSICAL));
 	char *p, *allocd = NULL;
 
 	while ((optc = ksh_getopt(wp, &builtin_opt, "LP")) != -1)
@@ -517,10 +243,12 @@
 	}
 	p = current_wd[0] ? (physical ? allocd = do_realpath(current_wd) :
 	    current_wd) : NULL;
+	/* LINTED use of access */
 	if (p && access(p, R_OK) < 0)
 		p = NULL;
-	if (!p && !(p = allocd = ksh_get_wd(NULL))) {
-		bi_errorf("can't get current directory - %s", strerror(errno));
+	if (!p && !(p = allocd = ksh_get_wd())) {
+		bi_errorf("%s: %s", "can't determine current directory",
+		    strerror(errno));
 		return (1);
 	}
 	shprintf("%s\n", p);
@@ -549,7 +277,7 @@
 	if (wp[0][0] == 'e') {
 		/* echo builtin */
 		wp++;
-		if (Flag(FPOSIX) || Flag(FSH)) {
+		if (Flag(FPOSIX) || Flag(FSH) || Flag(FAS_BUILTIN)) {
 			/* Debian Policy 10.4 compliant "echo" builtin */
 			if (*wp && !strcmp(*wp, "-n")) {
 				/* we recognise "-n" only as the first arg */
@@ -599,7 +327,8 @@
 
 		while ((optc = ksh_getopt(wp, &builtin_opt, opts)) != -1)
 			switch (optc) {
-			case 'R': /* fake BSD echo command */
+			case 'R':
+				/* fake BSD echo command */
 				flags |= PO_PMINUSMINUS;
 				flags &= ~PO_EXPAND;
 				opts = "ne";
@@ -612,7 +341,7 @@
 				break;
 			case 'p':
 				if ((fd = coproc_getfd(W_OK, &emsg)) < 0) {
-					bi_errorf("-p: %s", emsg);
+					bi_errorf("%s: %s", "-p", emsg);
 					return (1);
 				}
 				break;
@@ -626,7 +355,7 @@
 				if (!*(s = builtin_opt.optarg))
 					fd = 0;
 				else if ((fd = check_fd(s, W_OK, &emsg)) < 0) {
-					bi_errorf("-u: %s: %s", s, emsg);
+					bi_errorf("%s: %s: %s", "-u", s, emsg);
 					return (1);
 				}
 				break;
@@ -672,8 +401,7 @@
 					/* generic function returned Unicode */
 					char ts[4];
 
-					c = utf_wctomb(ts, c - 0x100);
-					ts[c] = 0;
+					ts[utf_wctomb(ts, c - 0x100)] = 0;
 					for (c = 0; ts[c]; ++c)
 						Xput(xs, xp, ts[c]);
 					continue;
@@ -695,7 +423,8 @@
 		int len = Xlength(xs, xp);
 		int opipe = 0;
 
-		/* Ensure we aren't killed by a SIGPIPE while writing to
+		/*
+		 * Ensure we aren't killed by a SIGPIPE while writing to
 		 * a coprocess. AT&T ksh doesn't seem to do this (seems
 		 * to just check that the co-process is alive which is
 		 * not enough).
@@ -770,7 +499,8 @@
 		/* Note that -p on its own is deal with in comexec() */
 		if (pflag)
 			fcflags |= FC_DEFPATH;
-		/* Convert command options to whence options - note that
+		/*
+		 * Convert command options to whence options - note that
 		 * command -pV uses a different path search than whence -v
 		 * or whence -pv. This should be considered a feature.
 		 */
@@ -795,22 +525,32 @@
 		if (vflag || (tp->type != CALIAS && tp->type != CEXEC &&
 		    tp->type != CTALIAS))
 			shf_puts(id, shl_stdout);
+		if (vflag)
+			switch (tp->type) {
+			case CKEYWD:
+			case CALIAS:
+			case CFUNC:
+			case CSHELL:
+				shf_puts(" is a", shl_stdout);
+				break;
+			}
+
 		switch (tp->type) {
 		case CKEYWD:
 			if (vflag)
-				shf_puts(" is a reserved word", shl_stdout);
+				shf_puts(" reserved word", shl_stdout);
 			break;
 		case CALIAS:
 			if (vflag)
-				shprintf(" is an %salias for ",
-				    (tp->flag & EXPORT) ? "exported " : null);
+				shprintf("n %s%s for ",
+				    (tp->flag & EXPORT) ? "exported " : null,
+				    Talias);
 			if (!iam_whence && !vflag)
-				shprintf("alias %s=", id);
+				shprintf("%s %s=", Talias, id);
 			print_value_quoted(tp->val.s);
 			break;
 		case CFUNC:
 			if (vflag) {
-				shf_puts(" is a", shl_stdout);
 				if (tp->flag & EXPORT)
 					shf_puts("n exported", shl_stdout);
 				if (tp->flag & TRACE)
@@ -821,13 +561,14 @@
 						shprintf(" (autoload from %s)",
 						    tp->u.fpath);
 				}
-				shf_puts(" function", shl_stdout);
+				shf_puts(T_function, shl_stdout);
 			}
 			break;
 		case CSHELL:
 			if (vflag)
-				shprintf(" is a%s shell builtin",
-				    (tp->flag & SPEC_BI) ? " special" : null);
+				shprintf("%s %s %s",
+				    (tp->flag & SPEC_BI) ? " special" : null,
+				    "shell", Tbuiltin);
 			break;
 		case CTALIAS:
 		case CEXEC:
@@ -835,14 +576,15 @@
 				if (vflag) {
 					shf_puts(" is ", shl_stdout);
 					if (tp->type == CTALIAS)
-						shprintf("a tracked %salias for ",
+						shprintf("a tracked %s%s for ",
 						    (tp->flag & EXPORT) ?
-						    "exported " : null);
+						    "exported " : null,
+						    Talias);
 				}
 				shf_puts(tp->val.s, shl_stdout);
 			} else {
 				if (vflag)
-					shf_puts(" not found", shl_stdout);
+					shprintf(" %s\n", "not found");
 				rv = 1;
 			}
 			break;
@@ -860,37 +602,46 @@
 int
 c_command(const char **wp)
 {
-	/* Let c_whence do the work. Note that c_command() must be
+	/*
+	 * Let c_whence do the work. Note that c_command() must be
 	 * a distinct function from c_whence() (tested in comexec()).
 	 */
 	return (c_whence(wp));
 }
 
-/* typeset, export, and readonly */
+/* typeset, global, export, and readonly */
 int
 c_typeset(const char **wp)
 {
 	struct block *l;
 	struct tbl *vp, **p;
-	Tflag fset = 0, fclr = 0, flag;
+	uint32_t fset = 0, fclr = 0, flag;
 	int thing = 0, field, base, optc;
 	const char *opts;
 	const char *fieldstr, *basestr;
 	bool localv = false, func = false, pflag = false, istset = true;
 
 	switch (**wp) {
-	case 'e':		/* export */
+
+	/* export */
+	case 'e':
 		fset |= EXPORT;
 		istset = false;
 		break;
-	case 'r':		/* readonly */
+
+	/* readonly */
+	case 'r':
 		fset |= RDONLY;
 		istset = false;
 		break;
-	case 's':		/* set */
+
+	/* set */
+	case 's':
 		/* called with 'typeset -' */
 		break;
-	case 't':		/* typeset */
+
+	/* typeset */
+	case 't':
 		localv = true;
 		break;
 	}
@@ -900,7 +651,8 @@
 
 	fieldstr = basestr = NULL;
 	builtin_opt.flags |= GF_PLUSOPT;
-	/* AT&T ksh seems to have 0-9 as options which are multiplied
+	/*
+	 * AT&T ksh seems to have 0-9 as options which are multiplied
 	 * to get a number that is used with -L, -R, -Z or -i (eg, -1R2
 	 * sets right justify in a field of 12). This allows options
 	 * to be grouped in an order (eg, -Lu12), but disallows -i8 -L3 and
@@ -920,7 +672,8 @@
 			fieldstr = builtin_opt.optarg;
 			break;
 		case 'U':
-			/* AT&T ksh uses u, but this conflicts with
+			/*
+			 * AT&T ksh uses u, but this conflicts with
 			 * upper/lower case. If this option is changed,
 			 * need to change the -U below as well
 			 */
@@ -948,10 +701,11 @@
 			flag = LCASEV;
 			break;
 		case 'n':
-			set_refflag = (builtin_opt.info & GI_PLUS) ? 2 : 1;
+			set_refflag = (builtin_opt.info & GI_PLUS) ?
+			    SRF_DISABLE : SRF_ENABLE;
 			break;
+		/* export, readonly: POSIX -p flag */
 		case 'p':
-			/* export, readonly: POSIX -p flag */
 			/* typeset: show values as well */
 			pflag = true;
 			if (istset)
@@ -964,7 +718,8 @@
 			flag = TRACE;
 			break;
 		case 'u':
-			flag = UCASEV_AL;	/* upper case / autoload */
+			/* upper case / autoload */
+			flag = UCASEV_AL;
 			break;
 		case 'x':
 			flag = EXPORT;
@@ -998,29 +753,35 @@
 		builtin_opt.optind++;
 	}
 
-	if (func && (((fset|fclr) & ~(TRACE|UCASEV_AL|EXPORT)) || set_refflag)) {
+	if (func && (((fset|fclr) & ~(TRACE|UCASEV_AL|EXPORT)) ||
+	    set_refflag != SRF_NOP)) {
 		bi_errorf("only -t, -u and -x options may be used with -f");
-		set_refflag = 0;
+		set_refflag = SRF_NOP;
 		return (1);
 	}
 	if (wp[builtin_opt.optind]) {
-		/* Take care of exclusions.
+		/*
+		 * Take care of exclusions.
 		 * At this point, flags in fset are cleared in fclr and vice
 		 * versa. This property should be preserved.
 		 */
-		if (fset & LCASEV)	/* LCASEV has priority over UCASEV_AL */
+		if (fset & LCASEV)
+			/* LCASEV has priority over UCASEV_AL */
 			fset &= ~UCASEV_AL;
-		if (fset & LJUST)	/* LJUST has priority over RJUST */
+		if (fset & LJUST)
+			/* LJUST has priority over RJUST */
 			fset &= ~RJUST;
-		if ((fset & (ZEROFIL|LJUST)) == ZEROFIL) { /* -Z implies -ZR */
+		if ((fset & (ZEROFIL|LJUST)) == ZEROFIL) {
+			/* -Z implies -ZR */
 			fset |= RJUST;
 			fclr &= ~RJUST;
 		}
-		/* Setting these attributes clears the others, unless they
+		/*
+		 * Setting these attributes clears the others, unless they
 		 * are also set in this command
 		 */
 		if ((fset & (LJUST | RJUST | ZEROFIL | UCASEV_AL | LCASEV |
-		    INTEGER | INT_U | INT_L)) || set_refflag)
+		    INTEGER | INT_U | INT_L)) || set_refflag != SRF_NOP)
 			fclr |= ~fset & (LJUST | RJUST | ZEROFIL | UCASEV_AL |
 			    LCASEV | INTEGER | INT_U | INT_L);
 	}
@@ -1035,7 +796,7 @@
 		for (i = builtin_opt.optind; wp[i]; i++) {
 			if (func) {
 				f = findfunc(wp[i], hash(wp[i]),
-				    (fset&UCASEV_AL) ? true : false);
+				    tobool(fset & UCASEV_AL));
 				if (!f) {
 					/* AT&T ksh does ++rv: bogus */
 					rv = 1;
@@ -1044,34 +805,38 @@
 				if (fset | fclr) {
 					f->flag |= fset;
 					f->flag &= ~fclr;
-				} else
-					fptreef(shl_stdout, 0,
-					    f->flag & FKSH ?
-					    "function %s %T\n" :
-					    "%s() %T\n", wp[i], f->val.t);
+				} else {
+					fpFUNCTf(shl_stdout, 0,
+					    tobool(f->flag & FKSH),
+					    wp[i], f->val.t);
+					shf_putc('\n', shl_stdout);
+				}
 			} else if (!typeset(wp[i], fset, fclr, field, base)) {
-				bi_errorf("%s: not identifier", wp[i]);
-				set_refflag = 0;
+				bi_errorf("%s: %s", wp[i], "not identifier");
+				set_refflag = SRF_NOP;
 				return (1);
 			}
 		}
-		set_refflag = 0;
+		set_refflag = SRF_NOP;
 		return (rv);
 	}
 
 	/* list variables and attributes */
-	flag = fset | fclr; /* no difference at this point.. */
+
+	/* no difference at this point.. */
+	flag = fset | fclr;
 	if (func) {
 		for (l = e->loc; l; l = l->next) {
 			for (p = ktsort(&l->funs); (vp = *p++); ) {
 				if (flag && (vp->flag & flag) == 0)
 					continue;
 				if (thing == '-')
-					fptreef(shl_stdout, 0, vp->flag & FKSH ?
-					    "function %s %T\n" : "%s() %T\n",
+					fpFUNCTf(shl_stdout, 0,
+					    tobool(vp->flag & FKSH),
 					    vp->name, vp->val.t);
 				else
-					shprintf("%s\n", vp->name);
+					shf_puts(vp->name, shl_stdout);
+				shf_putc('\n', shl_stdout);
 			}
 		}
 	} else {
@@ -1103,15 +868,18 @@
 				if (flag && (vp->flag & flag) == 0)
 					continue;
 				for (; vp; vp = vp->u.array) {
-					/* Ignore array elements that aren't
+					/*
+					 * Ignore array elements that aren't
 					 * set unless there are no set elements,
-					 * in which case the first is reported on */
+					 * in which case the first is reported on
+					 */
 					if ((vp->flag&ARRAY) && any_set &&
 					    !(vp->flag & ISSET))
 						continue;
 					/* no arguments */
 					if (thing == 0 && flag == 0) {
-						/* AT&T ksh prints things
+						/*
+						 * AT&T ksh prints things
 						 * like export, integer,
 						 * leftadj, zerofill, etc.,
 						 * but POSIX says must
@@ -1119,34 +887,36 @@
 						 */
 						shf_puts("typeset ", shl_stdout);
 						if (((vp->flag&(ARRAY|ASSOC))==ASSOC))
-							shf_puts("-n ", shl_stdout);
+							shprintf("%s ", "-n");
 						if ((vp->flag&INTEGER))
-							shf_puts("-i ", shl_stdout);
+							shprintf("%s ", "-i");
 						if ((vp->flag&EXPORT))
-							shf_puts("-x ", shl_stdout);
+							shprintf("%s ", "-x");
 						if ((vp->flag&RDONLY))
-							shf_puts("-r ", shl_stdout);
+							shprintf("%s ", "-r");
 						if ((vp->flag&TRACE))
-							shf_puts("-t ", shl_stdout);
+							shprintf("%s ", "-t");
 						if ((vp->flag&LJUST))
 							shprintf("-L%d ", vp->u2.field);
 						if ((vp->flag&RJUST))
 							shprintf("-R%d ", vp->u2.field);
 						if ((vp->flag&ZEROFIL))
-							shf_puts("-Z ", shl_stdout);
+							shprintf("%s ", "-Z");
 						if ((vp->flag&LCASEV))
-							shf_puts("-l ", shl_stdout);
+							shprintf("%s ", "-l");
 						if ((vp->flag&UCASEV_AL))
-							shf_puts("-u ", shl_stdout);
+							shprintf("%s ", "-u");
 						if ((vp->flag&INT_U))
-							shf_puts("-U ", shl_stdout);
+							shprintf("%s ", "-U");
 						shf_puts(vp->name, shl_stdout);
 						if (pflag) {
 							char *s = str_val(vp);
 
 							shf_putc('=', shl_stdout);
-							/* AT&T ksh can't have
-							 * justified integers.. */
+							/*
+							 * AT&T ksh can't have
+							 * justified integers...
+							 */
 							if ((vp->flag &
 							    (INTEGER|LJUST|RJUST)) ==
 							    INTEGER)
@@ -1175,8 +945,10 @@
 							char *s = str_val(vp);
 
 							shf_putc('=', shl_stdout);
-							/* AT&T ksh can't have
-							 * justified integers.. */
+							/*
+							 * AT&T ksh can't have
+							 * justified integers...
+							 */
 							if ((vp->flag &
 							    (INTEGER|LJUST|RJUST)) ==
 							    INTEGER)
@@ -1186,7 +958,8 @@
 						}
 						shf_putc('\n', shl_stdout);
 					}
-					/* Only report first 'element' of an array with
+					/*
+					 * Only report first 'element' of an array with
 					 * no set elements.
 					 */
 					if (!any_set)
@@ -1204,7 +977,7 @@
 	struct table *t = &aliases;
 	int rv = 0, prefix = 0;
 	bool rflag = false, tflag, Uflag = false, pflag = false;
-	Tflag xflag = 0;
+	uint32_t xflag = 0;
 	int optc;
 
 	builtin_opt.flags |= GF_PLUSOPT;
@@ -1258,12 +1031,12 @@
 	/* "hash -r" means reset all the tracked aliases.. */
 	if (rflag) {
 		static const char *args[] = {
-			"unalias", "-ta", NULL
+			Tunalias, "-ta", NULL
 		};
 
 		if (!tflag || *wp) {
-			shf_puts("alias: -r flag can only be used with -t"
-			    " and without arguments\n", shl_stdout);
+			shprintf("%s: -r flag can only be used with -t"
+			    " and without arguments\n", Talias);
 			return (1);
 		}
 		ksh_getopt_reset(&builtin_opt, GF_ERROR);
@@ -1276,7 +1049,7 @@
 		for (p = ktsort(t); (ap = *p++) != NULL; )
 			if ((ap->flag & (ISSET|xflag)) == (ISSET|xflag)) {
 				if (pflag)
-					shf_puts("alias ", shl_stdout);
+					shprintf("%s ", Talias);
 				shf_puts(ap->name, shl_stdout);
 				if (prefix != '+') {
 					shf_putc('=', shl_stdout);
@@ -1301,7 +1074,7 @@
 			ap = ktsearch(t, alias, h);
 			if (ap != NULL && (ap->flag&ISSET)) {
 				if (pflag)
-					shf_puts("alias ", shl_stdout);
+					shprintf("%s ", Talias);
 				shf_puts(ap->name, shl_stdout);
 				if (prefix != '+') {
 					shf_putc('=', shl_stdout);
@@ -1309,7 +1082,8 @@
 				}
 				shf_putc('\n', shl_stdout);
 			} else {
-				shprintf("%s alias not found\n", alias);
+				shprintf("%s %s %s\n", alias, Talias,
+				    "not found");
 				rv = 1;
 			}
 			continue;
@@ -1323,7 +1097,9 @@
 				afree(ap->val.s, APERM);
 			}
 			/* ignore values for -t (AT&T ksh does this) */
-			newval = tflag ? search(alias, path, X_OK, NULL) : val;
+			newval = tflag ?
+			    search_path(alias, path, X_OK, NULL) :
+			    val;
 			if (newval) {
 				strdupx(ap->val.s, newval, APERM);
 				ap->flag |= ALLOC|ISSET;
@@ -1356,7 +1132,8 @@
 			break;
 		case 'd':
 #ifdef MKSH_NOPWNAM
-			t = NULL;	/* fix "unalias -dt" */
+			/* fix "unalias -dt" */
+			t = NULL;
 #else
 			t = &homedirs;
 #endif
@@ -1376,7 +1153,8 @@
 	for (; *wp != NULL; wp++) {
 		ap = ktsearch(t, *wp, hash(*wp));
 		if (ap == NULL) {
-			rv = 1;	/* POSIX */
+			/* POSIX */
+			rv = 1;
 			continue;
 		}
 		if (ap->flag&ALLOC) {
@@ -1407,12 +1185,14 @@
 	int rv = 1;
 	mksh_ari_t val;
 
-	if (wp[1] == NULL) /* AT&T ksh does this */
+	if (wp[1] == NULL)
+		/* AT&T ksh does this */
 		bi_errorf("no arguments");
 	else
 		for (wp++; *wp; wp++)
 			if (!evaluate(*wp, &val, KSH_RETURN_ERROR, true)) {
-				rv = 2;	/* distinguish error from zero result */
+				/* distinguish error from zero result */
+				rv = 2;
 				break;
 			} else
 				rv = val == 0;
@@ -1435,7 +1215,8 @@
 		case 'n':
 			nflag = 1;
 			break;
-		case 'z':	/* debugging: print zombies */
+		case 'z':
+			/* debugging: print zombies */
 			nflag = -1;
 			break;
 		case '?':
@@ -1478,7 +1259,7 @@
 
 /* format a single kill item */
 static char *
-kill_fmt_entry(char *buf, int buflen, int i, const void *arg)
+kill_fmt_entry(char *buf, size_t buflen, int i, const void *arg)
 {
 	const struct kill_info *ki = (const struct kill_info *)arg;
 
@@ -1549,7 +1330,8 @@
 					shprintf("%d\n", n);
 			}
 		} else {
-			int w, j, mess_cols, mess_octs;
+			ssize_t w, mess_cols, mess_octs;
+			int j;
 			struct kill_info ki;
 
 			for (j = NSIG, ki.num_width = 1; j >= 10; j /= 10)
@@ -1582,8 +1364,8 @@
 			if (j_kill(p, sig))
 				rv = 1;
 		} else if (!getn(p, &n)) {
-			bi_errorf("%s: arguments must be jobs or process IDs",
-			    p);
+			bi_errorf("%s: %s", p,
+			    "arguments must be jobs or process IDs");
 			rv = 1;
 		} else {
 			if (mksh_kill(n, sig) < 0) {
@@ -1618,22 +1400,22 @@
 
 	opts = *wp++;
 	if (!opts) {
-		bi_errorf("missing options argument");
+		bi_errorf("missing %s argument", "options");
 		return (1);
 	}
 
 	var = *wp++;
 	if (!var) {
-		bi_errorf("missing name argument");
+		bi_errorf("missing %s argument", "name");
 		return (1);
 	}
 	if (!*var || *skip_varname(var, true)) {
-		bi_errorf("%s: is not an identifier", var);
+		bi_errorf("%s: %s", var, "is not an identifier");
 		return (1);
 	}
 
 	if (e->loc->next == NULL) {
-		internal_warningf("c_getopts: no argv");
+		internal_warningf("%s: %s", "c_getopts", "no argv");
 		return (1);
 	}
 	/* Which arguments are we parsing... */
@@ -1660,7 +1442,8 @@
 		buf[1] = optc;
 		buf[2] = '\0';
 	} else {
-		/* POSIX says var is set to ? at end-of-options, AT&T ksh
+		/*
+		 * POSIX says var is set to ? at end-of-options, AT&T ksh
 		 * sets it to null - we go with POSIX...
 		 */
 		buf[0] = optc < 0 ? '?' : optc;
@@ -1671,7 +1454,8 @@
 	user_opt.uoptind = user_opt.optind;
 
 	voptarg = global("OPTARG");
-	voptarg->flag &= ~RDONLY;	/* AT&T ksh clears ro and int */
+	/* AT&T ksh clears ro and int */
+	voptarg->flag &= ~RDONLY;
 	/* Paranoia: ensure no bizarre results. */
 	if (voptarg->flag & INTEGER)
 	    typeset("OPTARG", 0, INTEGER, 0, 0);
@@ -1686,7 +1470,7 @@
 	vq = global(var);
 	/* Error message already printed (integer, readonly) */
 	if (!setstr(vq, buf, KSH_RETURN_ERROR))
-		rv = 1;
+		rv = 2;
 	if (Flag(FEXPORT))
 		typeset(var, EXPORT, 0, 0, 0);
 
@@ -1725,7 +1509,8 @@
 		}
 	wp += builtin_opt.optind;
 
-	if (*wp == NULL)	/* list all */
+	if (*wp == NULL)
+		/* list all */
 		rv = x_bind(NULL, NULL,
 #ifndef MKSH_SMALL
 		    false,
@@ -1751,13 +1536,6 @@
 	return (rv);
 }
 
-/* :, false and true (and ulimit if MKSH_NO_LIMITS) */
-int
-c_label(const char **wp)
-{
-	return (wp[0][0] == 'f' ? 1 : 0);
-}
-
 int
 c_shift(const char **wp)
 {
@@ -1776,7 +1554,7 @@
 	} else
 		n = 1;
 	if (n < 0) {
-		bi_errorf("%s: bad number", arg);
+		bi_errorf("%s: %s", arg, "bad number");
 		return (1);
 	}
 	if (l->argc < n) {
@@ -1843,7 +1621,8 @@
 			char op;
 
 			old_umask = umask((mode_t)0);
-			umask(old_umask); /* in case of error */
+			/* in case of error */
+			umask(old_umask);
 			old_umask = ~old_umask;
 			new_umask = old_umask;
 			positions = 0;
@@ -1864,7 +1643,8 @@
 						break;
 					}
 				if (!positions)
-					positions = 0111; /* default is a */
+					/* default is a */
+					positions = 0111;
 				if (!vstrchr("=+-", op = *cp))
 					break;
 				cp++;
@@ -1933,16 +1713,16 @@
 		bi_errorf("missing argument");
 		return (1);
 	}
-	if ((file = search(cp, path, R_OK, &errcode)) == NULL) {
-		bi_errorf("%s: %s", cp,
-		    errcode ? strerror(errcode) : "not found");
+	if ((file = search_path(cp, path, R_OK, &errcode)) == NULL) {
+		bi_errorf("%s: %s", cp, strerror(errcode));
 		return (1);
 	}
 
 	/* Set positional parameters? */
 	if (wp[builtin_opt.optind + 1]) {
 		argv = wp + builtin_opt.optind;
-		argv[0] = e->loc->argv[0]; /* preserve $0 */
+		/* preserve $0 */
+		argv[0] = e->loc->argv[0];
 		for (argc = 0; argv[argc + 1]; argc++)
 			;
 	} else {
@@ -1973,7 +1753,8 @@
 		for (; *wp; wp++)
 			rv = waitfor(*wp, &sig);
 		if (rv < 0)
-			rv = sig ? sig : 127; /* magic exit code: bad job-id */
+			/* magic exit code: bad job-id */
+			rv = sig ? sig : 127;
 	}
 	return (rv);
 }
@@ -1981,175 +1762,399 @@
 int
 c_read(const char **wp)
 {
-	int c = 0, ecode = 0, fd = 0, optc;
-	bool expande = true, historyr = false, expanding;
-	const char *cp, *emsg;
-	struct shf *shf;
-	XString cs, xs = { NULL, NULL, 0, NULL};
-	struct tbl *vp;
-	char *ccp, *xp = NULL, *wpalloc = NULL;
+#define is_ifsws(c) (ctype((c), C_IFS) && ctype((c), C_IFSWS))
 	static char REPLY[] = "REPLY";
+	int c, fd = 0, rv = 0, lastparm = 0;
+	bool savehist = false, intoarray = false, aschars = false;
+	bool rawmode = false, expanding = false;
+	enum { LINES, BYTES, UPTO, READALL } readmode = LINES;
+	char delim = '\n';
+	size_t bytesleft = 128, bytesread;
+	struct tbl *vp /* FU gcc */ = NULL, *vq;
+	char *cp, *allocd = NULL, *xp;
+	const char *ccp;
+	XString xs;
+	ptrdiff_t xsave = 0;
+	struct termios tios;
+	bool restore_tios = false;
+#if HAVE_SELECT
+	bool hastimeout = false;
+	struct timeval tv, tvlim;
+#define c_read_opts "Aad:N:n:prst:u,"
+#else
+#define c_read_opts "Aad:N:n:prsu,"
+#endif
 
-	while ((optc = ksh_getopt(wp, &builtin_opt, "prsu,")) != -1)
-		switch (optc) {
-		case 'p':
-			if ((fd = coproc_getfd(R_OK, &emsg)) < 0) {
-				bi_errorf("-p: %s", emsg);
-				return (1);
-			}
-			break;
-		case 'r':
-			expande = false;
-			break;
-		case 's':
-			historyr = true;
-			break;
-		case 'u':
-			if (!*(cp = builtin_opt.optarg))
-				fd = 0;
-			else if ((fd = check_fd(cp, R_OK, &emsg)) < 0) {
-				bi_errorf("-u: %s: %s", cp, emsg);
-				return (1);
-			}
-			break;
-		case '?':
-			return (1);
+	while ((c = ksh_getopt(wp, &builtin_opt, c_read_opts)) != -1)
+	switch (c) {
+	case 'a':
+		aschars = true;
+		/* FALLTHROUGH */
+	case 'A':
+		intoarray = true;
+		break;
+	case 'd':
+		delim = builtin_opt.optarg[0];
+		break;
+	case 'N':
+	case 'n':
+		readmode = c == 'N' ? BYTES : UPTO;
+		if (!bi_getn(builtin_opt.optarg, &c))
+			return (2);
+		if (c == -1) {
+			readmode = READALL;
+			bytesleft = 1024;
+		} else
+			bytesleft = (unsigned int)c;
+		break;
+	case 'p':
+		if ((fd = coproc_getfd(R_OK, &ccp)) < 0) {
+			bi_errorf("%s: %s", "-p", ccp);
+			return (2);
 		}
+		break;
+	case 'r':
+		rawmode = true;
+		break;
+	case 's':
+		savehist = true;
+		break;
+#if HAVE_SELECT
+	case 't':
+		if (parse_usec(builtin_opt.optarg, &tv)) {
+			bi_errorf("%s: %s '%s'", Tsynerr, strerror(errno),
+			    builtin_opt.optarg);
+			return (2);
+		}
+		hastimeout = true;
+		break;
+#endif
+	case 'u':
+		if (!builtin_opt.optarg[0])
+			fd = 0;
+		else if ((fd = check_fd(builtin_opt.optarg, R_OK, &ccp)) < 0) {
+			bi_errorf("%s: %s: %s", "-u", builtin_opt.optarg, ccp);
+			return (2);
+		}
+		break;
+	case '?':
+		return (2);
+	}
 	wp += builtin_opt.optind;
-
 	if (*wp == NULL)
 		*--wp = REPLY;
 
-	/* Since we can't necessarily seek backwards on non-regular files,
-	 * don't buffer them so we can't read too much.
-	 */
-	shf = shf_reopen(fd, SHF_RD | SHF_INTERRUPT | can_seek(fd), shl_spare);
+	if (intoarray && wp[1] != NULL) {
+		bi_errorf("too many arguments");
+		return (2);
+	}
 
-	if ((cp = cstrchr(*wp, '?')) != NULL) {
-		strdupx(wpalloc, *wp, ATEMP);
-		wpalloc[cp - *wp] = '\0';
-		*wp = wpalloc;
+	if ((ccp = cstrchr(*wp, '?')) != NULL) {
+		strdupx(allocd, *wp, ATEMP);
+		allocd[ccp - *wp] = '\0';
+		*wp = allocd;
 		if (isatty(fd)) {
-			/* AT&T ksh says it prints prompt on fd if it's open
+			/*
+			 * AT&T ksh says it prints prompt on fd if it's open
 			 * for writing and is a tty, but it doesn't do it
 			 * (it also doesn't check the interactive flag,
-			 * as is indicated in the Kornshell book).
+			 * as is indicated in the Korn Shell book).
 			 */
-			shellf("%s", cp+1);
+			shf_puts(ccp + 1, shl_out);
+			shf_flush(shl_out);
 		}
 	}
 
-	/* If we are reading from the co-process for the first time,
-	 * make sure the other side of the pipe is closed first. This allows
-	 * the detection of eof.
-	 *
-	 * This is not compatible with AT&T ksh... the fd is kept so another
-	 * coproc can be started with same output, however, this means eof
-	 * can't be detected... This is why it is closed here.
-	 * If this call is removed, remove the eof check below, too.
-	 * coproc_readw_close(fd);
+	Xinit(xs, xp, bytesleft, ATEMP);
+
+	if (readmode == LINES)
+		bytesleft = 1;
+	else if (isatty(fd)) {
+		x_mkraw(fd, &tios, true);
+		restore_tios = true;
+	}
+
+#if HAVE_SELECT
+	if (hastimeout) {
+		gettimeofday(&tvlim, NULL);
+		timeradd(&tvlim, &tv, &tvlim);
+	}
+#endif
+
+ c_read_readloop:
+#if HAVE_SELECT
+	if (hastimeout) {
+		fd_set fdset;
+
+		FD_ZERO(&fdset);
+		FD_SET(fd, &fdset);
+		gettimeofday(&tv, NULL);
+		timersub(&tvlim, &tv, &tv);
+		if (tv.tv_sec < 0) {
+			/* timeout expired globally */
+			rv = 1;
+			goto c_read_out;
+		}
+
+		switch (select(fd + 1, &fdset, NULL, NULL, &tv)) {
+		case 1:
+			break;
+		case 0:
+			/* timeout expired for this call */
+			rv = 1;
+			goto c_read_out;
+		default:
+			bi_errorf("%s: %s", Tselect, strerror(errno));
+			rv = 2;
+			goto c_read_out;
+		}
+	}
+#endif
+
+	bytesread = blocking_read(fd, xp, bytesleft);
+	if (bytesread == (size_t)-1) {
+		/* interrupted */
+		if (errno == EINTR && fatal_trap_check()) {
+			/*
+			 * Was the offending signal one that would
+			 * normally kill a process? If so, pretend
+			 * the read was killed.
+			 */
+			rv = 2;
+			goto c_read_out;
+		}
+		/* just ignore the signal */
+		goto c_read_readloop;
+	}
+
+	switch (readmode) {
+	case READALL:
+		if (bytesread == 0) {
+			/* end of file reached */
+			rv = 1;
+			goto c_read_readdone;
+		}
+		xp += bytesread;
+		XcheckN(xs, xp, bytesleft);
+		break;
+
+	case UPTO:
+		if (bytesread == 0)
+			/* end of file reached */
+			rv = 1;
+		xp += bytesread;
+		goto c_read_readdone;
+
+	case BYTES:
+		if (bytesread == 0) {
+			/* end of file reached */
+			rv = 1;
+			xp = Xstring(xs, xp);
+			goto c_read_readdone;
+		}
+		xp += bytesread;
+		if ((bytesleft -= bytesread) == 0)
+			goto c_read_readdone;
+		break;
+	case LINES:
+		if (bytesread == 0) {
+			/* end of file reached */
+			rv = 1;
+			goto c_read_readdone;
+		}
+		if ((c = *xp) == '\0' && !aschars && delim != '\0') {
+			/* skip any read NULs unless delimiter */
+			break;
+		}
+		if (expanding) {
+			expanding = false;
+			if (c == delim) {
+				if (Flag(FTALKING_I) && isatty(fd)) {
+					/*
+					 * set prompt in case this is
+					 * called from .profile or $ENV
+					 */
+					set_prompt(PS2, NULL);
+					pprompt(prompt, 0);
+				}
+				/* drop the backslash */
+				--xp;
+				/* and the delimiter */
+				break;
+			}
+		} else if (c == delim) {
+			goto c_read_readdone;
+		} else if (!rawmode && c == '\\') {
+			expanding = true;
+		}
+		Xcheck(xs, xp);
+		++xp;
+		break;
+	}
+	goto c_read_readloop;
+
+ c_read_readdone:
+	bytesread = Xlength(xs, xp);
+	Xput(xs, xp, '\0');
+
+	/*-
+	 * state: we finished reading the input and NUL terminated it
+	 * Xstring(xs, xp) -> xp-1 = input string without trailing delim
+	 * rv = 1 if EOF, 0 otherwise (errors handled already)
 	 */
 
-	if (historyr)
-		Xinit(xs, xp, 128, ATEMP);
-	expanding = false;
-	Xinit(cs, ccp, 128, ATEMP);
-	for (; *wp != NULL; wp++) {
-		for (ccp = Xstring(cs, ccp); ; ) {
-			if (c == '\n' || c == EOF)
-				break;
-			while (1) {
-				c = shf_getc(shf);
-				if (c == '\0')
-					continue;
-				if (c == EOF && shf_error(shf) &&
-				    shf_errno(shf) == EINTR) {
-					/* Was the offending signal one that
-					 * would normally kill a process?
-					 * If so, pretend the read was killed.
-					 */
-					ecode = fatal_trap_check();
+	if (rv == 1) {
+		/* clean up coprocess if needed, on EOF */
+		coproc_read_close(fd);
+		if (readmode == READALL)
+			/* EOF is no error here */
+			rv = 0;
+	}
 
-					/* non fatal (eg, CHLD), carry on */
-					if (!ecode) {
-						shf_clearerr(shf);
-						continue;
-					}
-				}
-				break;
-			}
-			if (historyr) {
-				Xcheck(xs, xp);
-				Xput(xs, xp, c);
-			}
-			Xcheck(cs, ccp);
-			if (expanding) {
-				expanding = false;
-				if (c == '\n') {
-					c = 0;
-					if (Flag(FTALKING_I) && isatty(fd)) {
-						/* set prompt in case this is
-						 * called from .profile or $ENV
-						 */
-						set_prompt(PS2, NULL);
-						pprompt(prompt, 0);
-					}
-				} else if (c != EOF)
-					Xput(cs, ccp, c);
-				continue;
-			}
-			if (expande && c == '\\') {
-				expanding = true;
-				continue;
-			}
-			if (c == '\n' || c == EOF)
-				break;
-			if (ctype(c, C_IFS)) {
-				if (Xlength(cs, ccp) == 0 && ctype(c, C_IFSWS))
-					continue;
-				if (wp[1])
-					break;
-			}
-			Xput(cs, ccp, c);
-		}
-		/* strip trailing IFS white space from last variable */
-		if (!wp[1])
-			while (Xlength(cs, ccp) && ctype(ccp[-1], C_IFS) &&
-			    ctype(ccp[-1], C_IFSWS))
-				ccp--;
-		Xput(cs, ccp, '\0');
+	if (savehist)
+		histsave(&source->line, Xstring(xs, xp), true, false);
+
+	ccp = cp = Xclose(xs, xp);
+	expanding = false;
+	XinitN(xs, 128, ATEMP);
+	if (intoarray) {
 		vp = global(*wp);
-		/* Must be done before setting export. */
 		if (vp->flag & RDONLY) {
-			shf_flush(shf);
-			bi_errorf("%s is read only", *wp);
-			afree(wpalloc, ATEMP);
-			return (1);
+ c_read_splitro:
+			bi_errorf("%s: %s", *wp, "is read only");
+ c_read_spliterr:
+			rv = 2;
+			afree(cp, ATEMP);
+			goto c_read_out;
 		}
+		/* exporting an array is currently pointless */
+		unset(vp, 1);
+		/* counter for array index */
+		c = 0;
+	}
+	if (!aschars) {
+		/* skip initial IFS whitespace */
+		while (bytesread && is_ifsws(*ccp)) {
+			++ccp;
+			--bytesread;
+		}
+		/* trim trailing IFS whitespace */
+		while (bytesread && is_ifsws(ccp[bytesread - 1])) {
+			--bytesread;
+		}
+	}
+ c_read_splitloop:
+	xp = Xstring(xs, xp);
+	/* generate next word */
+	if (!bytesread) {
+		/* no more input */
+		if (intoarray)
+			goto c_read_splitdone;
+		/* zero out next parameters */
+		goto c_read_gotword;
+	}
+	if (aschars) {
+		Xput(xs, xp, '1');
+		Xput(xs, xp, '#');
+		bytesleft = utf_ptradj(ccp);
+		while (bytesleft && bytesread) {
+			*xp++ = *ccp++;
+			--bytesleft;
+			--bytesread;
+		}
+		if (xp[-1] == '\0') {
+			xp[-1] = '0';
+			xp[-3] = '2';
+		}
+		goto c_read_gotword;
+	}
+
+	if (!intoarray && wp[1] == NULL)
+		lastparm = 1;
+
+ c_read_splitlast:
+	/* copy until IFS character */
+	while (bytesread) {
+		char ch;
+
+		ch = *ccp;
+		if (expanding) {
+			expanding = false;
+			goto c_read_splitcopy;
+		} else if (ctype(ch, C_IFS)) {
+			break;
+		} else if (!rawmode && ch == '\\') {
+			expanding = true;
+		} else {
+ c_read_splitcopy:
+			Xcheck(xs, xp);
+			Xput(xs, xp, ch);
+		}
+		++ccp;
+		--bytesread;
+	}
+	xsave = Xsavepos(xs, xp);
+	/* copy word delimiter: IFSWS+IFS,IFSWS */
+	while (bytesread) {
+		char ch;
+
+		ch = *ccp;
+		if (!ctype(ch, C_IFS))
+			break;
+		Xcheck(xs, xp);
+		Xput(xs, xp, ch);
+		++ccp;
+		--bytesread;
+		if (!ctype(ch, C_IFSWS))
+			break;
+	}
+	while (bytesread && is_ifsws(*ccp)) {
+		Xcheck(xs, xp);
+		Xput(xs, xp, *ccp);
+		++ccp;
+		--bytesread;
+	}
+	/* if no more parameters, rinse and repeat */
+	if (lastparm && bytesread) {
+		++lastparm;
+		goto c_read_splitlast;
+	}
+	/* get rid of the delimiter unless we pack the rest */
+	if (lastparm < 2)
+		xp = Xrestpos(xs, xp, xsave);
+ c_read_gotword:
+	Xput(xs, xp, '\0');
+	if (intoarray) {
+		vq = arraysearch(vp, c++);
+	} else {
+		vq = global(*wp);
+		/* must be checked before exporting */
+		if (vq->flag & RDONLY)
+			goto c_read_splitro;
 		if (Flag(FEXPORT))
 			typeset(*wp, EXPORT, 0, 0, 0);
-		if (!setstr(vp, Xstring(cs, ccp), KSH_RETURN_ERROR)) {
-			shf_flush(shf);
-			afree(wpalloc, ATEMP);
-			return (1);
-		}
 	}
-
-	shf_flush(shf);
-	if (historyr) {
-		Xput(xs, xp, '\0');
-		histsave(&source->line, Xstring(xs, xp), true, false);
-		Xfree(xs, xp);
+	if (!setstr(vq, Xstring(xs, xp), KSH_RETURN_ERROR))
+		goto c_read_spliterr;
+	if (aschars) {
+		setint_v(vq, vq, false);
+		/* protect from UTFMODE changes */
+		vq->type = 0;
 	}
-	/* if this is the co-process fd, close the file descriptor
-	 * (can get eof if and only if all processes are have died, ie,
-	 * coproc.njobs is 0 and the pipe is closed).
-	 */
-	if (c == EOF && !ecode)
-		coproc_read_close(fd);
+	if (intoarray || *++wp != NULL)
+		goto c_read_splitloop;
 
-	afree(wpalloc, ATEMP);
-	return (ecode ? ecode : c == EOF);
+ c_read_splitdone:
+	/* free up */
+	afree(cp, ATEMP);
+
+ c_read_out:
+	afree(allocd, ATEMP);
+	Xfree(xs, xp);
+	if (restore_tios)
+		tcsetattr(fd, TCSADRAIN, &tios);
+	return (rv);
+#undef is_ifsws
 }
 
 int
@@ -2231,20 +2236,21 @@
 	 * command 'exit' isn't confused with the pseudo-signal
 	 * 'EXIT'.
 	 */
-	s = (gettrap(*wp, false) == NULL) ? *wp++ : NULL; /* get command */
+	/* get command */
+	s = (gettrap(*wp, false) == NULL) ? *wp++ : NULL;
 	if (s != NULL && s[0] == '-' && s[1] == '\0')
 		s = NULL;
 
 	/* set/clear traps */
-	while (*wp != NULL) {
-		p = gettrap(*wp++, true);
-		if (p == NULL) {
-			bi_errorf("bad signal %s", wp[-1]);
-			return (1);
-		}
-		settrap(p, s);
-	}
-	return (0);
+	i = 0;
+	while (*wp != NULL)
+		if ((p = gettrap(*wp++, true)) == NULL) {
+			warningf(true, "%s: %s '%s'", builtin_argv0,
+			    "bad signal", wp[-1]);
+			++i;
+		} else
+			settrap(p, s);
+	return (i);
 }
 
 int
@@ -2260,14 +2266,17 @@
 	if (arg) {
 		if (!getn(arg, &n)) {
 			exstat = 1;
-			warningf(true, "%s: bad number", arg);
+			warningf(true, "%s: %s", arg, "bad number");
 		} else
 			exstat = n;
-	}
-	if (wp[0][0] == 'r') { /* return */
+	} else if (trap_exstat != -1)
+		exstat = trap_exstat;
+	if (wp[0][0] == 'r') {
+		/* return */
 		struct env *ep;
 
-		/* need to tell if this is exit or return so trap exit will
+		/*
+		 * need to tell if this is exit or return so trap exit will
 		 * work right (POSIX)
 		 */
 		for (ep = e; ep; ep = ep->oenv)
@@ -2282,7 +2291,8 @@
 		how = LSHELL;
 	}
 
-	quitenv(NULL);	/* get rid of any i/o redirections */
+	/* get rid of any i/o redirections */
+	quitenv(NULL);
 	unwind(how);
 	/* NOTREACHED */
 }
@@ -2305,7 +2315,7 @@
 	quit = n;
 	if (quit <= 0) {
 		/* AT&T ksh does this for non-interactive shells only - weird */
-		bi_errorf("%s: bad value", arg);
+		bi_errorf("%s: %s", arg, "bad value");
 		return (1);
 	}
 
@@ -2319,15 +2329,17 @@
 		}
 
 	if (quit) {
-		/* AT&T ksh doesn't print a message - just does what it
+		/*
+		 * AT&T ksh doesn't print a message - just does what it
 		 * can. We print a message 'cause it helps in debugging
 		 * scripts, but don't generate an error (ie, keep going).
 		 */
 		if (n == quit) {
-			warningf(true, "%s: cannot %s", wp[0], wp[0]);
+			warningf(true, "%s: %s %s", wp[0], "can't", wp[0]);
 			return (0);
 		}
-		/* POSIX says if n is too big, the last enclosing loop
+		/*
+		 * POSIX says if n is too big, the last enclosing loop
 		 * shall be used. Doesn't say to print an error but we
 		 * do anyway 'cause the user messed up.
 		 */
@@ -2350,7 +2362,7 @@
 	const char **owp;
 
 	if (wp[1] == NULL) {
-		static const char *args[] = { "set", "-", NULL };
+		static const char *args[] = { Tset, "-", NULL };
 		return (c_typeset(args));
 	}
 
@@ -2361,11 +2373,12 @@
 	if (setargs) {
 		wp += argi - 1;
 		owp = wp;
-		wp[0] = l->argv[0]; /* save $0 */
+		/* save $0 */
+		wp[0] = l->argv[0];
 		while (*++wp != NULL)
 			strdupx(*wp, *wp, &l->area);
 		l->argc = wp - owp - 1;
-		l->argv = alloc((l->argc + 2) * sizeof(char *), &l->area);
+		l->argv = alloc2(l->argc + 2, sizeof(char *), &l->area);
 		for (wp = l->argv; (*wp++ = *owp++) != NULL; )
 			;
 	}
@@ -2385,7 +2398,7 @@
 c_unset(const char **wp)
 {
 	const char *id;
-	int optc;
+	int optc, rv = 0;
 	bool unset_var = true;
 
 	while ((optc = ksh_getopt(wp, &builtin_opt, "fv")) != -1)
@@ -2397,11 +2410,13 @@
 			unset_var = true;
 			break;
 		case '?':
-			return (1);
+			/*XXX not reached due to GF_ERROR */
+			return (2);
 		}
 	wp += builtin_opt.optind;
 	for (; (id = *wp) != NULL; wp++)
-		if (unset_var) {	/* unset variable */
+		if (unset_var) {
+			/* unset variable */
 			struct tbl *vp;
 			char *cp = NULL;
 			size_t n;
@@ -2419,13 +2434,15 @@
 			afree(cp, ATEMP);
 
 			if ((vp->flag&RDONLY)) {
-				bi_errorf("%s is read only", vp->name);
-				return (1);
-			}
-			unset(vp, optc);
-		} else			/* unset function */
+				warningf(true, "%s: %s", vp->name,
+				    "is read only");
+				rv = 1;
+			} else
+				unset(vp, optc);
+		} else
+			/* unset function */
 			define(id, NULL);
-	return (0);
+	return (rv);
 }
 
 static void
@@ -2497,7 +2514,8 @@
 	} else
 		tf = TF_NOARGS;
 
-	if (tf & TF_NOARGS) { /* ksh93 - report shell times (shell+kids) */
+	if (tf & TF_NOARGS) {
+		/* ksh93 - report shell times (shell+kids) */
 		tf |= TF_NOREAL;
 		timeradd(&ru0.ru_utime, &cru0.ru_utime, &usrtime);
 		timeradd(&ru0.ru_stime, &cru0.ru_stime, &systime);
@@ -2542,17 +2560,19 @@
 	Getopt opt;
 
 	ksh_getopt_reset(&opt, 0);
-	opt.optind = 0;	/* start at the start */
+	/* start at the start */
+	opt.optind = 0;
 	while ((optc = ksh_getopt((const char **)wp, &opt, ":p")) != -1)
 		switch (optc) {
 		case 'p':
 			t->str[0] |= TF_POSIX;
 			break;
 		case '?':
-			errorf("time: -%s unknown option", opt.optarg);
+			errorf("time: -%s %s", opt.optarg,
+			    "unknown option");
 		case ':':
-			errorf("time: -%s requires an argument",
-			    opt.optarg);
+			errorf("time: -%s %s", opt.optarg,
+			    "requires an argument");
 		}
 	/* Copy command words down over options. */
 	if (opt.optind != 0) {
@@ -2609,7 +2629,7 @@
 				return (1);
 			}
 			mode = getmode(set, (mode_t)(DEFFILEMODE));
-			free(set);
+			free_ossetmode(set);
 			break;
 		default:
 			goto c_mknod_usage;
@@ -2640,28 +2660,28 @@
 
 		majnum = strtoul(argv[2], &c, 0);
 		if ((c == argv[2]) || (*c != '\0')) {
-			bi_errorf("non-numeric device major '%s'", argv[2]);
+			bi_errorf("non-numeric %s %s '%s'", "device", "major", argv[2]);
 			goto c_mknod_err;
 		}
 		minnum = strtoul(argv[3], &c, 0);
 		if ((c == argv[3]) || (*c != '\0')) {
-			bi_errorf("non-numeric device minor '%s'", argv[3]);
+			bi_errorf("non-numeric %s %s '%s'", "device", "minor", argv[3]);
 			goto c_mknod_err;
 		}
 		dv = makedev(majnum, minnum);
 		if ((unsigned long)(major(dv)) != majnum) {
-			bi_errorf("device major too large: %lu", majnum);
+			bi_errorf("%s %s too large: %lu", "device", "major", majnum);
 			goto c_mknod_err;
 		}
 		if ((unsigned long)(minor(dv)) != minnum) {
-			bi_errorf("device minor too large: %lu", minnum);
+			bi_errorf("%s %s too large: %lu", "device", "minor", minnum);
 			goto c_mknod_err;
 		}
 		if (mknod(argv[0], mode, dv))
 			goto c_mknod_failed;
 	} else if (mkfifo(argv[0], mode)) {
  c_mknod_failed:
-		bi_errorf("%s: %s", *wp, strerror(errno));
+		bi_errorf("%s: %s", argv[0], strerror(errno));
  c_mknod_err:
 		rv = 1;
 	}
@@ -2670,20 +2690,14 @@
 		umask(oldmode);
 	return (rv);
  c_mknod_usage:
-	bi_errorf("usage: mknod [-m mode] name b|c major minor");
-	bi_errorf("usage: mknod [-m mode] name p");
+	bi_errorf("%s: %s", "usage", "mknod [-m mode] name b|c major minor");
+	bi_errorf("%s: %s", "usage", "mknod [-m mode] name p");
 	return (1);
 }
 #endif
 
-/* dummy function, special case in comexec() */
-int
-c_builtin(const char **wp MKSH_A_UNUSED)
-{
-	return (0);
-}
-
-/* test(1) accepts the following grammar:
+/*-
+   test(1) accepts the following grammar:
 	oexpr	::= aexpr | aexpr "-o" oexpr ;
 	aexpr	::= nexpr | nexpr "-a" aexpr ;
 	nexpr	::= primary | "!" nexpr ;
@@ -2704,7 +2718,8 @@
 	operand ::= <any thing>
 */
 
-#define T_ERR_EXIT	2	/* POSIX says > 1 for errors */
+/* POSIX says > 1 for errors */
+#define T_ERR_EXIT	2
 
 int
 c_test(const char **wp)
@@ -2737,11 +2752,23 @@
 	 * our parser does the right thing for the omitted steps.
 	 */
 	if (argc <= 5) {
-		const char **owp = wp;
+		const char **owp = wp, **owpend = te.wp_end;
 		int invert = 0;
 		Test_op op;
 		const char *opnd1, *opnd2;
 
+		if (argc >= 2 && ((*te.isa)(&te, TM_OPAREN))) {
+			te.pos.wp = te.wp_end - 1;
+			if ((*te.isa)(&te, TM_CPAREN)) {
+				argc -= 2;
+				te.wp_end--;
+				te.pos.wp = owp + 2;
+			} else {
+				te.pos.wp = owp + 1;
+				te.wp_end = owpend;
+			}
+		}
+
 		while (--argc >= 0) {
 			if ((*te.isa)(&te, TM_END))
 				return (!0);
@@ -2762,8 +2789,6 @@
 			}
 			if (argc == 1) {
 				opnd1 = (*te.getopnd)(&te, TO_NONOP, 1);
-				if (strcmp(opnd1, "-t") == 0)
-				    break;
 				res = (*te.eval)(&te, TO_STNZE, opnd1,
 				    NULL, 1);
 				if (invert & 1)
@@ -2776,6 +2801,7 @@
 				break;
 		}
 		te.pos.wp = owp + 1;
+		te.wp_end = owpend;
 	}
 
 	return (test_parse(&te));
@@ -2813,99 +2839,184 @@
 	if (!do_eval)
 		return (0);
 
-	switch ((int)op) {
+	switch (op) {
+
 	/*
 	 * Unary Operators
 	 */
-	case TO_STNZE: /* -n */
+
+	/* -n */
+	case TO_STNZE:
 		return (*opnd1 != '\0');
-	case TO_STZER: /* -z */
+
+	/* -z */
+	case TO_STZER:
 		return (*opnd1 == '\0');
-	case TO_OPTION: /* -o */
+
+	/* -o */
+	case TO_OPTION:
 		if ((i = *opnd1) == '!' || i == '?')
 			opnd1++;
 		if ((k = option(opnd1)) == (size_t)-1)
 			return (0);
 		return (i == '?' ? 1 : i == '!' ? !Flag(k) : Flag(k));
-	case TO_FILRD: /* -r */
-		return (test_eaccess(opnd1, R_OK) == 0);
-	case TO_FILWR: /* -w */
-		return (test_eaccess(opnd1, W_OK) == 0);
-	case TO_FILEX: /* -x */
-		return (test_eaccess(opnd1, X_OK) == 0);
-	case TO_FILAXST: /* -a */
-	case TO_FILEXST: /* -e */
+
+	/* -r */
+	case TO_FILRD:
+		/* LINTED use of access */
+		return (access(opnd1, R_OK) == 0);
+
+	/* -w */
+	case TO_FILWR:
+		/* LINTED use of access */
+		return (access(opnd1, W_OK) == 0);
+
+	/* -x */
+	case TO_FILEX:
+		return (ksh_access(opnd1, X_OK) == 0);
+
+	/* -a */
+	case TO_FILAXST:
+	/* -e */
+	case TO_FILEXST:
 		return (stat(opnd1, &b1) == 0);
-	case TO_FILREG: /* -r */
+
+	/* -r */
+	case TO_FILREG:
 		return (stat(opnd1, &b1) == 0 && S_ISREG(b1.st_mode));
-	case TO_FILID: /* -d */
+
+	/* -d */
+	case TO_FILID:
 		return (stat(opnd1, &b1) == 0 && S_ISDIR(b1.st_mode));
-	case TO_FILCDEV: /* -c */
+
+	/* -c */
+	case TO_FILCDEV:
 		return (stat(opnd1, &b1) == 0 && S_ISCHR(b1.st_mode));
-	case TO_FILBDEV: /* -b */
+
+	/* -b */
+	case TO_FILBDEV:
 		return (stat(opnd1, &b1) == 0 && S_ISBLK(b1.st_mode));
-	case TO_FILFIFO: /* -p */
+
+	/* -p */
+	case TO_FILFIFO:
 		return (stat(opnd1, &b1) == 0 && S_ISFIFO(b1.st_mode));
-	case TO_FILSYM: /* -h -L */
+
+	/* -h or -L */
+	case TO_FILSYM:
 		return (lstat(opnd1, &b1) == 0 && S_ISLNK(b1.st_mode));
-	case TO_FILSOCK: /* -S */
+
+	/* -S */
+	case TO_FILSOCK:
 		return (stat(opnd1, &b1) == 0 && S_ISSOCK(b1.st_mode));
-	case TO_FILCDF:/* -H HP context dependent files (directories) */
+
+	/* -H => HP context dependent files (directories) */
+	case TO_FILCDF:
+#ifdef S_ISCDF
+	{
+		char *nv;
+
+		/*
+		 * Append a + to filename and check to see if result is
+		 * a setuid directory. CDF stuff in general is hookey,
+		 * since it breaks for, e.g., the following sequence:
+		 * echo hi >foo+; mkdir foo; echo bye >foo/default;
+		 * chmod u+s foo (foo+ refers to the file with hi in it,
+		 * there is no way to get at the file with bye in it;
+		 * please correct me if I'm wrong about this).
+		 */
+
+		nv = shf_smprintf("%s+", opnd1);
+		i = (stat(nv, &b1) == 0 && S_ISCDF(b1.st_mode));
+		afree(nv, ATEMP);
+		return (i);
+	}
+#else
 		return (0);
-	case TO_FILSETU: /* -u */
+#endif
+
+	/* -u */
+	case TO_FILSETU:
 		return (stat(opnd1, &b1) == 0 &&
 		    (b1.st_mode & S_ISUID) == S_ISUID);
-	case TO_FILSETG: /* -g */
+
+	/* -g */
+	case TO_FILSETG:
 		return (stat(opnd1, &b1) == 0 &&
 		    (b1.st_mode & S_ISGID) == S_ISGID);
-	case TO_FILSTCK: /* -k */
+
+	/* -k */
+	case TO_FILSTCK:
 #ifdef S_ISVTX
 		return (stat(opnd1, &b1) == 0 &&
 		    (b1.st_mode & S_ISVTX) == S_ISVTX);
 #else
 		return (0);
 #endif
-	case TO_FILGZ: /* -s */
+
+	/* -s */
+	case TO_FILGZ:
 		return (stat(opnd1, &b1) == 0 && b1.st_size > 0L);
-	case TO_FILTT: /* -t */
+
+	/* -t */
+	case TO_FILTT:
 		if (opnd1 && !bi_getn(opnd1, &i)) {
 			te->flags |= TEF_ERROR;
 			i = 0;
 		} else
 			i = isatty(opnd1 ? i : 0);
 		return (i);
-	case TO_FILUID: /* -O */
+
+	/* -O */
+	case TO_FILUID:
 		return (stat(opnd1, &b1) == 0 && b1.st_uid == ksheuid);
-	case TO_FILGID: /* -G */
+
+	/* -G */
+	case TO_FILGID:
 		return (stat(opnd1, &b1) == 0 && b1.st_gid == getegid());
+
 	/*
 	 * Binary Operators
 	 */
-	case TO_STEQL: /* = */
+
+	/* = */
+	case TO_STEQL:
 		if (te->flags & TEF_DBRACKET)
 			return (gmatchx(opnd1, opnd2, false));
 		return (strcmp(opnd1, opnd2) == 0);
-	case TO_STNEQ: /* != */
+
+	/* != */
+	case TO_STNEQ:
 		if (te->flags & TEF_DBRACKET)
 			return (!gmatchx(opnd1, opnd2, false));
 		return (strcmp(opnd1, opnd2) != 0);
-	case TO_STLT: /* < */
+
+	/* < */
+	case TO_STLT:
 		return (strcmp(opnd1, opnd2) < 0);
-	case TO_STGT: /* > */
+
+	/* > */
+	case TO_STGT:
 		return (strcmp(opnd1, opnd2) > 0);
-	case TO_INTEQ: /* -eq */
-	case TO_INTNE: /* -ne */
-	case TO_INTGE: /* -ge */
-	case TO_INTGT: /* -gt */
-	case TO_INTLE: /* -le */
-	case TO_INTLT: /* -lt */
+
+	/* -eq */
+	case TO_INTEQ:
+	/* -ne */
+	case TO_INTNE:
+	/* -ge */
+	case TO_INTGE:
+	/* -gt */
+	case TO_INTGT:
+	/* -le */
+	case TO_INTLE:
+	/* -lt */
+	case TO_INTLT:
 		if (!evaluate(opnd1, &v1, KSH_RETURN_ERROR, false) ||
 		    !evaluate(opnd2, &v2, KSH_RETURN_ERROR, false)) {
 			/* error already printed.. */
 			te->flags |= TEF_ERROR;
 			return (1);
 		}
-		switch ((int)op) {
+		switch (op) {
 		case TO_INTEQ:
 			return (v1 == v2);
 		case TO_INTNE:
@@ -2918,49 +3029,47 @@
 			return (v1 <= v2);
 		case TO_INTLT:
 			return (v1 < v2);
+		default:
+			/* NOTREACHED */
+			break;
 		}
-	case TO_FILNT: /* -nt */
-		/* ksh88/ksh93 succeed if file2 can't be stated
+		/* NOTREACHED */
+
+	/* -nt */
+	case TO_FILNT:
+		/*
+		 * ksh88/ksh93 succeed if file2 can't be stated
 		 * (subtly different from 'does not exist').
 		 */
 		return (stat(opnd1, &b1) == 0 &&
 		    (((s = stat(opnd2, &b2)) == 0 &&
 		    b1.st_mtime > b2.st_mtime) || s < 0));
-	case TO_FILOT: /* -ot */
-		/* ksh88/ksh93 succeed if file1 can't be stated
+
+	/* -ot */
+	case TO_FILOT:
+		/*
+		 * ksh88/ksh93 succeed if file1 can't be stated
 		 * (subtly different from 'does not exist').
 		 */
 		return (stat(opnd2, &b2) == 0 &&
 		    (((s = stat(opnd1, &b1)) == 0 &&
 		    b1.st_mtime < b2.st_mtime) || s < 0));
-	case TO_FILEQ: /* -ef */
+
+	/* -ef */
+	case TO_FILEQ:
 		return (stat (opnd1, &b1) == 0 && stat (opnd2, &b2) == 0 &&
 		    b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino);
+
+	/* all other cases */
+	case TO_NONOP:
+	case TO_NONNULL:
+		/* throw the error */
+		break;
 	}
 	(*te->error)(te, 0, "internal error: unknown op");
 	return (1);
 }
 
-/* On most/all unixen, access() says everything is executable for root... */
-static int
-test_eaccess(const char *pathl, int mode)
-{
-	int rv;
-
-	if ((rv = access(pathl, mode)) == 0 && ksheuid == 0 && (mode & X_OK)) {
-		struct stat statb;
-
-		if (stat(pathl, &statb) < 0)
-			rv = -1;
-		else if (S_ISDIR(statb.st_mode))
-			rv = 0;
-		else
-			rv = (statb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) ?
-			    0 : -1;
-	}
-	return (rv);
-}
-
 int
 test_parse(Test_env *te)
 {
@@ -3020,7 +3129,7 @@
 		if (te->flags & TEF_ERROR)
 			return (0);
 		if (!(*te->isa)(te, TM_CPAREN)) {
-			(*te->error)(te, 0, "missing closing paren");
+			(*te->error)(te, 0, "missing )");
 			return (0);
 		}
 		return (rv);
@@ -3280,7 +3389,8 @@
 			all = true;
 			break;
 		case '?':
-			bi_errorf("usage: ulimit [-acdfHLlmnpSsTtvw] [value]");
+			bi_errorf("%s: %s", "usage",
+			    "ulimit [-acdfHLlmnpSsTtvw] [value]");
 			return (1);
 		default:
 			what = optc;
@@ -3336,7 +3446,7 @@
 	}
 
 	if (getrlimit(l->resource, &limit) < 0) {
-		/* some cannot be read, e.g. Linux RLIMIT_LOCKS */
+		/* some can't be read, e.g. Linux RLIMIT_LOCKS */
 		limit.rlim_cur = RLIM_INFINITY;
 		limit.rlim_max = RLIM_INFINITY;
 	}
@@ -3379,15 +3489,20 @@
 {
 	int rv = 1;
 
-	if (wp == NULL		/* argv */ ||
-	    wp[0] == NULL	/* name of builtin */ ||
-	    wp[1] == NULL	/* first argument */ ||
-	    wp[2] == NULL	/* second argument */ ||
-	    wp[3] != NULL	/* no further args please */)
-		bi_errorf(T_synerr);
-	else if ((rv = rename(wp[1], wp[2])) != 0) {
+	/* skip argv[0] */
+	++wp;
+	if (wp[0] && !strcmp(wp[0], "--"))
+		/* skip "--" (options separator) */
+		++wp;
+
+	/* check for exactly two arguments */
+	if (wp[0] == NULL	/* first argument */ ||
+	    wp[1] == NULL	/* second argument */ ||
+	    wp[2] != NULL	/* no further args please */)
+		bi_errorf(Tsynerr);
+	else if ((rv = rename(wp[0], wp[1])) != 0) {
 		rv = errno;
-		bi_errorf("failed: %s", strerror(rv));
+		bi_errorf("%s: %s", "failed", strerror(rv));
 	}
 
 	return (rv);
@@ -3399,31 +3514,161 @@
 	int rv = 1;
 	char *buf;
 
-	if (wp != NULL && wp[0] != NULL && wp[1] != NULL) {
-		if (strcmp(wp[1], "--")) {
-			if (wp[2] == NULL) {
-				wp += 1;
-				rv = 0;
-			}
-		} else {
-			if (wp[2] != NULL && wp[3] == NULL) {
-				wp += 2;
-				rv = 0;
-			}
-		}
-	}
+	/* skip argv[0] */
+	++wp;
+	if (wp[0] && !strcmp(wp[0], "--"))
+		/* skip "--" (options separator) */
+		++wp;
 
-	if (rv)
-		bi_errorf(T_synerr);
-	else if ((buf = do_realpath(*wp)) == NULL) {
+	/* check for exactly one argument */
+	if (wp[0] == NULL || wp[1] != NULL)
+		bi_errorf(Tsynerr);
+	else if ((buf = do_realpath(wp[0])) == NULL) {
 		rv = errno;
-		bi_errorf("%s: %s", *wp, strerror(rv));
+		bi_errorf("%s: %s", wp[0], strerror(rv));
 		if ((unsigned int)rv > 255)
 			rv = 255;
 	} else {
 		shprintf("%s\n", buf);
 		afree(buf, ATEMP);
+		rv = 0;
 	}
 
 	return (rv);
 }
+
+int
+c_cat(const char **wp)
+{
+	int fd = STDIN_FILENO, rv;
+	ssize_t n, w;
+	const char *fn = "<stdin>";
+	char *buf, *cp;
+#define MKSH_CAT_BUFSIZ 4096
+
+	if ((buf = malloc_osfunc(MKSH_CAT_BUFSIZ)) == NULL) {
+		bi_errorf(Toomem, (unsigned long)MKSH_CAT_BUFSIZ);
+		return (1);
+	}
+
+	/* parse options: POSIX demands we support "-u" as no-op */
+	while ((rv = ksh_getopt(wp, &builtin_opt, "u")) != -1) {
+		switch (rv) {
+		case 'u':
+			/* we already operate unbuffered */
+			break;
+		default:
+			bi_errorf(Tsynerr);
+			return (1);
+		}
+	}
+	wp += builtin_opt.optind;
+	rv = 0;
+
+	do {
+		if (*wp) {
+			fn = *wp++;
+			if (fn[0] == '-' && fn[1] == '\0')
+				fd = STDIN_FILENO;
+			else if ((fd = open(fn, O_RDONLY)) < 0) {
+				rv = errno;
+				bi_errorf("%s: %s", fn, strerror(rv));
+				rv = 1;
+				continue;
+			}
+		}
+		while (/* CONSTCOND */ 1) {
+			n = blocking_read(fd, (cp = buf), MKSH_CAT_BUFSIZ);
+			if (n == -1) {
+				if (errno == EINTR) {
+					/* give the user a chance to ^C out */
+					intrcheck();
+					/* interrupted, try again */
+					continue;
+				}
+				/* an error occured during reading */
+				rv = errno;
+				bi_errorf("%s: %s", fn, strerror(rv));
+				rv = 1;
+				break;
+			} else if (n == 0)
+				/* end of file reached */
+				break;
+			while (n) {
+				w = write(STDOUT_FILENO, cp, n);
+				if (w == -1) {
+					if (errno == EINTR)
+						/* interrupted, try again */
+						continue;
+					/* an error occured during writing */
+					rv = errno;
+					bi_errorf("%s: %s", "<stdout>",
+					    strerror(rv));
+					rv = 1;
+					if (fd != STDIN_FILENO)
+						close(fd);
+					goto out;
+				}
+				n -= w;
+				cp += w;
+			}
+		}
+		if (fd != STDIN_FILENO)
+			close(fd);
+	} while (*wp);
+
+ out:
+	free_osfunc(buf);
+	return (rv);
+}
+
+#if HAVE_SELECT
+int
+c_sleep(const char **wp)
+{
+	struct timeval tv;
+	int rv = 1;
+
+	/* skip argv[0] */
+	++wp;
+	if (wp[0] && !strcmp(wp[0], "--"))
+		/* skip "--" (options separator) */
+		++wp;
+
+	if (!wp[0] || wp[1])
+		bi_errorf(Tsynerr);
+	else if (parse_usec(wp[0], &tv))
+		bi_errorf("%s: %s '%s'", Tsynerr, strerror(errno), wp[0]);
+	else {
+#ifndef MKSH_NOPROSPECTOFWORK
+		sigset_t omask;
+
+		/* block SIGCHLD from interrupting us, though */
+		sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+#endif
+		if (select(0, NULL, NULL, NULL, &tv) == 0 || errno == EINTR)
+			/*
+			 * strictly speaking only for SIGALRM, but the
+			 * execution may be interrupted by other signals
+			 */
+			rv = 0;
+		else
+			bi_errorf("%s: %s", Tselect, strerror(errno));
+#ifndef MKSH_NOPROSPECTOFWORK
+		sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
+	}
+	return (rv);
+}
+#endif
+
+#if defined(ANDROID)
+static int
+c_android_lsmod(const char **wp MKSH_A_UNUSED)
+{
+	const char *cwp[3] = { "cat", "/proc/modules", NULL };
+
+	builtin_argv0 = cwp[0];
+	return (c_cat(cwp));
+}
+#endif
diff --git a/src/histrap.c b/src/histrap.c
index 2ac4c38..4a4a275 100644
--- a/src/histrap.c
+++ b/src/histrap.c
@@ -2,7 +2,7 @@
 /*	$OpenBSD: trap.c,v 1.23 2010/05/19 17:36:08 jasper Exp $	*/
 
 /*-
- * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
  *	Thorsten Glaser <tg@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -22,11 +22,11 @@
  */
 
 #include "sh.h"
-#if HAVE_PERSISTENT_HISTORY
+#if HAVE_SYS_FILE_H
 #include <sys/file.h>
 #endif
 
-__RCSID("$MirOS: src/bin/mksh/histrap.c,v 1.98 2010/07/24 17:08:29 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/histrap.c,v 1.111 2011/09/07 15:24:16 tg Exp $");
 
 /*-
  * MirOS: This is the default mapping type, and need not be specified.
@@ -60,11 +60,15 @@
 static Source *hist_source;
 
 #if HAVE_PERSISTENT_HISTORY
-static char *hname;		/* current name of history file */
+/* current history file: name, fd, size */
+static char *hname;
 static int histfd;
-static int hsize;
+static size_t hsize;
 #endif
 
+static const char Tnot_in_history[] = "not in history";
+#define Thistory (Tnot_in_history + 7)
+
 int
 c_fc(const char **wp)
 {
@@ -79,39 +83,50 @@
 	char **hfirst, **hlast, **hp;
 
 	if (!Flag(FTALKING_I)) {
-		bi_errorf("history functions not available");
+		bi_errorf("history %ss not available", Tfunction);
 		return (1);
 	}
 
 	while ((optc = ksh_getopt(wp, &builtin_opt,
 	    "e:glnrs0,1,2,3,4,5,6,7,8,9,")) != -1)
 		switch (optc) {
+
 		case 'e':
 			p = builtin_opt.optarg;
 			if (ksh_isdash(p))
 				sflag = true;
 			else {
 				size_t len = strlen(p);
+
+				/* almost certainly not overflowing */
 				editor = alloc(len + 4, ATEMP);
 				memcpy(editor, p, len);
 				memcpy(editor + len, " $_", 4);
 			}
 			break;
-		case 'g': /* non-AT&T ksh */
+
+		/* non-AT&T ksh */
+		case 'g':
 			gflag = true;
 			break;
+
 		case 'l':
 			lflag = true;
 			break;
+
 		case 'n':
 			nflag = true;
 			break;
+
 		case 'r':
 			rflag = true;
 			break;
-		case 's':	/* POSIX version of -e - */
+
+		/* POSIX version of -e - */
+		case 's':
 			sflag = true;
 			break;
+
 		/* kludge city - accept -num as -- -num (kind of) */
 		case '0': case '1': case '2': case '3': case '4':
 		case '5': case '6': case '7': case '8': case '9':
@@ -126,6 +141,7 @@
 				return (1);
 			}
 			break;
+
 		case '?':
 			return (1);
 		}
@@ -183,11 +199,12 @@
 		/* can't fail if hfirst didn't fail */
 		hlast = hist_get_newest(false);
 	} else {
-		/* POSIX says not an error if first/last out of bounds
-		 * when range is specified; AT&T ksh and pdksh allow out of
-		 * bounds for -l as well.
+		/*
+		 * POSIX says not an error if first/last out of bounds
+		 * when range is specified; AT&T ksh and pdksh allow out
+		 * of bounds for -l as well.
 		 */
-		hfirst = hist_get(first, (lflag || last) ? true : false, lflag);
+		hfirst = hist_get(first, tobool(lflag || last), lflag);
 		if (!hfirst)
 			return (1);
 		hlast = last ? hist_get(last, true, lflag) :
@@ -199,7 +216,8 @@
 		char **temp;
 
 		temp = hfirst; hfirst = hlast; hlast = temp;
-		rflag = !rflag; /* POSIX */
+		/* POSIX */
+		rflag = !rflag;
 	}
 
 	/* List history */
@@ -230,15 +248,16 @@
 
 	tf = maketemp(ATEMP, TT_HIST_EDIT, &e->temps);
 	if (!(shf = tf->shf)) {
-		bi_errorf("cannot create temp file %s - %s",
-		    tf->name, strerror(errno));
+		bi_errorf("can't %s temporary file %s: %s",
+		    "create", tf->name, strerror(errno));
 		return (1);
 	}
 	for (hp = rflag ? hlast : hfirst;
 	    hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1)
 		shf_fprintf(shf, "%s\n", *hp);
 	if (shf_close(shf) == EOF) {
-		bi_errorf("error writing temporary file - %s", strerror(errno));
+		bi_errorf("can't %s temporary file %s: %s",
+		    "write", tf->name, strerror(errno));
 		return (1);
 	}
 
@@ -263,11 +282,19 @@
 		int n;
 
 		if (!(shf = shf_open(tf->name, O_RDONLY, 0, 0))) {
-			bi_errorf("cannot open temp file %s", tf->name);
+			bi_errorf("can't %s temporary file %s: %s",
+			    "open", tf->name, strerror(errno));
 			return (1);
 		}
 
-		n = stat(tf->name, &statb) < 0 ? 128 : statb.st_size + 1;
+		if (stat(tf->name, &statb) < 0)
+			n = 128;
+		else if (statb.st_size > (1024 * 1048576)) {
+			bi_errorf("%s %s too large: %lu", Thistory,
+			    "file", (unsigned long)statb.st_size);
+			goto errout;
+		} else
+			n = statb.st_size + 1;
 		Xinit(xs, xp, n, hist_source->areap);
 		while ((n = shf_read(xp, Xnleft(xs, xp), shf)) > 0) {
 			xp += n;
@@ -275,8 +302,9 @@
 				XcheckN(xs, xp, Xlength(xs, xp));
 		}
 		if (n < 0) {
-			bi_errorf("error reading temp file %s - %s",
-			    tf->name, strerror(shf_errno(shf)));
+			bi_errorf("can't %s temporary file %s: %s",
+			    "read", tf->name, strerror(shf_errno(shf)));
+ errout:
 			shf_close(shf);
 			return (1);
 		}
@@ -299,18 +327,22 @@
 
 	for (p = cmd; p; p = q) {
 		if ((q = strchr(p, '\n'))) {
-			*q++ = '\0'; /* kill the newline */
-			if (!*q) /* ignore trailing newline */
+			/* kill the newline */
+			*q++ = '\0';
+			if (!*q)
+				/* ignore trailing newline */
 				q = NULL;
 		}
 		histsave(&hist_source->line, p, true, true);
 
-		shellf("%s\n", p); /* POSIX doesn't say this is done... */
-		if (q)		/* restore \n (trailing \n not restored) */
+		/* POSIX doesn't say this is done... */
+		shellf("%s\n", p);
+		if (q)
+			/* restore \n (trailing \n not restored) */
 			q[-1] = '\n';
 	}
 
-	/*
+	/*-
 	 * Commands are executed here instead of pushing them onto the
 	 * input 'cause POSIX says the redirection and variable assignments
 	 * in
@@ -333,9 +365,9 @@
 		strdupx(line, *hp, ATEMP);
 	else {
 		char *s, *s1;
-		int pat_len = strlen(pat);
-		int rep_len = strlen(rep);
-		int len;
+		size_t pat_len = strlen(pat);
+		size_t rep_len = strlen(rep);
+		size_t len;
 		XString xs;
 		char *xp;
 		bool any_subst = false;
@@ -346,13 +378,15 @@
 			any_subst = true;
 			len = s1 - s;
 			XcheckN(xs, xp, len + rep_len);
-			memcpy(xp, s, len);		/* first part */
+			/*; first part */
+			memcpy(xp, s, len);
 			xp += len;
-			memcpy(xp, rep, rep_len);	/* replacement */
+			/* replacement */
+			memcpy(xp, rep, rep_len);
 			xp += rep_len;
 		}
 		if (!any_subst) {
-			bi_errorf("substitution failed");
+			bi_errorf("bad substitution");
 			return (1);
 		}
 		len = strlen(s) + 1;
@@ -380,18 +414,18 @@
 			if (approx)
 				hp = hist_get_oldest();
 			else {
-				bi_errorf("%s: not in history", str);
+				bi_errorf("%s: %s", str, Tnot_in_history);
 				hp = NULL;
 			}
 		} else if ((ptrdiff_t)hp > (ptrdiff_t)histptr) {
 			if (approx)
 				hp = hist_get_newest(allow_cur);
 			else {
-				bi_errorf("%s: not in history", str);
+				bi_errorf("%s: %s", str, Tnot_in_history);
 				hp = NULL;
 			}
 		} else if (!allow_cur && hp == histptr) {
-			bi_errorf("%s: invalid range", str);
+			bi_errorf("%s: %s", str, "invalid range");
 			hp = NULL;
 		}
 	} else {
@@ -399,7 +433,7 @@
 
 		/* the -1 is to avoid the current fc command */
 		if ((n = findhist(histptr - history - 1, 0, str, anchored)) < 0)
-			bi_errorf("%s: not in history", str);
+			bi_errorf("%s: %s", str, Tnot_in_history);
 		else
 			hp = &history[n];
 	}
@@ -428,9 +462,9 @@
 	return (history);
 }
 
-/******************************/
-/* Back up over last histsave */
-/******************************/
+/*
+ * Back up over last histsave
+ */
 static void
 histbackup(void)
 {
@@ -475,10 +509,10 @@
 int
 findhist(int start, int fwd, const char *str, int anchored)
 {
-	char	**hp;
-	int	maxhist = histptr - history;
-	int	incr = fwd ? 1 : -1;
-	int	len = strlen(str);
+	char **hp;
+	int maxhist = histptr - history;
+	int incr = fwd ? 1 : -1;
+	size_t len = strlen(str);
 
 	if (start < 0 || start >= maxhist)
 		start = maxhist;
@@ -492,26 +526,6 @@
 	return (-1);
 }
 
-int
-findhistrel(const char *str)
-{
-	int	maxhist = histptr - history;
-	int	start = maxhist - 1;
-	int	rec;
-
-	getn(str, &rec);
-	if (rec == 0)
-		return (-1);
-	if (rec > 0) {
-		if (rec > maxhist)
-			return (-1);
-		return (rec - 1);
-	}
-	if (rec > maxhist)
-		return (-1);
-	return (start + rec + 1);
-}
-
 /*
  *	set history
  *	this means reallocating the dataspace
@@ -528,7 +542,7 @@
 			cursize = n;
 		}
 
-		history = aresize(history, n * sizeof(char *), APERM);
+		history = aresize2(history, n, sizeof(char *), APERM);
 
 		histsize = n;
 		histptr = history + cursize;
@@ -579,7 +593,7 @@
 {
 	if (history == (char **)NULL) {
 		histsize = HISTORYSIZE;
-		history = alloc(histsize * sizeof(char *), APERM);
+		history = alloc2(histsize, sizeof(char *), APERM);
 		histptr = history - 1;
 	}
 }
@@ -645,7 +659,8 @@
 
 	hp = histptr;
 
-	if (++hp >= history + histsize) { /* remove oldest command */
+	if (++hp >= history + histsize) {
+		/* remove oldest command */
 		afree(*history, APERM);
 		for (hp = history; hp < history + histsize - 1; hp++)
 			hp[0] = hp[1];
@@ -665,7 +680,7 @@
  *	if your system ain't got it - then you'll have to undef HISTORYFILE
  */
 
-/*
+/*-
  *	Open a history file
  *	Format is:
  *	Bytes 1, 2:
@@ -685,6 +700,7 @@
 #if HAVE_PERSISTENT_HISTORY
 	unsigned char *base;
 	int lines, fd, rv = 0;
+	off_t hfsize;
 #endif
 
 	if (Flag(FTALKING) == 0)
@@ -710,7 +726,10 @@
 
 	(void)flock(histfd, LOCK_EX);
 
-	hsize = lseek(histfd, (off_t)0, SEEK_END);
+	hfsize = lseek(histfd, (off_t)0, SEEK_END);
+	hsize = 1024 * 1048576;
+	if (hfsize < (off_t)hsize)
+		hsize = (size_t)hfsize;
 
 	if (hsize == 0) {
 		/* add magic */
@@ -746,8 +765,9 @@
 				hist_finish();
 				if (rv) {
  hiniterr:
-					bi_errorf("cannot unlink HISTFILE %s"
-					    " - %s", hname, strerror(errno));
+					bi_errorf("can't %s %s: %s",
+					    "unlink HISTFILE", hname,
+					    strerror(errno));
 					hsize = 0;
 					return;
 				}
@@ -758,7 +778,10 @@
 		munmap((caddr_t)base, hsize);
 	}
 	(void)flock(histfd, LOCK_UN);
-	hsize = lseek(histfd, (off_t)0, SEEK_END);
+	hfsize = lseek(histfd, (off_t)0, SEEK_END);
+	hsize = 1024 * 1048576;
+	if (hfsize < (off_t)hsize)
+		hsize = hfsize;
 #endif
 }
 
@@ -954,36 +977,34 @@
 static void
 writehistfile(int lno, char *cmd)
 {
-	int	sizenow;
-	unsigned char	*base;
-	unsigned char	*news;
-	int	bytes;
-	unsigned char	hdr[5];
+	off_t sizenow;
+	ssize_t bytes;
+	unsigned char *base, *news, hdr[5];
 
 	(void)flock(histfd, LOCK_EX);
 	sizenow = lseek(histfd, (off_t)0, SEEK_END);
-	if (sizenow != hsize) {
+	if ((sizenow <= (1024 * 1048576)) && ((size_t)sizenow != hsize)) {
 		/*
 		 *	Things have changed
 		 */
-		if (sizenow > hsize) {
+		if ((size_t)sizenow > hsize) {
 			/* someone has added some lines */
-			bytes = sizenow - hsize;
-			base = (void *)mmap(NULL, sizenow, PROT_READ,
+			bytes = (size_t)sizenow - hsize;
+			base = (void *)mmap(NULL, (size_t)sizenow, PROT_READ,
 			    MAP_FILE | MAP_PRIVATE, histfd, (off_t)0);
 			if (base == (unsigned char *)MAP_FAILED)
 				goto bad;
 			news = base + hsize;
 			if (*news != COMMAND) {
-				munmap((caddr_t)base, sizenow);
+				munmap((caddr_t)base, (size_t)sizenow);
 				goto bad;
 			}
 			hist_source->line--;
 			histload(hist_source, news, bytes);
 			hist_source->line++;
 			lno = hist_source->line;
-			munmap((caddr_t)base, sizenow);
-			hsize = sizenow;
+			munmap((caddr_t)base, (size_t)sizenow);
+			hsize = (size_t)sizenow;
 		} else {
 			/* it has shrunk */
 			/* but to what? */
@@ -1004,7 +1025,10 @@
 		if ((write(histfd, hdr, 5) != 5) ||
 		    (write(histfd, cmd, bytes) != bytes))
 			goto bad;
-		hsize = lseek(histfd, (off_t)0, SEEK_END);
+		sizenow = lseek(histfd, (off_t)0, SEEK_END);
+		hsize = 1024 * 1048576;
+		if (sizenow < (off_t)hsize)
+			hsize = (size_t)sizenow;
 	}
 	(void)flock(histfd, LOCK_UN);
 	return;
@@ -1048,10 +1072,12 @@
 	int i;
 	const char *cs;
 
+	trap_exstat = -1;
+
 	/* Populate sigtraps based on sys_signame and sys_siglist. */
 	for (i = 0; i <= NSIG; i++) {
 		sigtraps[i].signal = i;
-		if (i == SIGERR_) {
+		if (i == ksh_SIGERR) {
 			sigtraps[i].name = "ERR";
 			sigtraps[i].mess = "Error handler";
 		} else {
@@ -1085,10 +1111,12 @@
 #endif
 			if ((sigtraps[i].mess == NULL) ||
 			    (sigtraps[i].mess[0] == '\0'))
-				sigtraps[i].mess = shf_smprintf("Signal %d", i);
+				sigtraps[i].mess = shf_smprintf("%s %d",
+				    "Signal", i);
 		}
 	}
-	sigtraps[SIGEXIT_].name = "EXIT";	/* our name for signal 0 */
+	/* our name for signal 0 */
+	sigtraps[ksh_SIGEXIT].name = "EXIT";
 
 	(void)sigemptyset(&Sigact_ign.sa_mask);
 	Sigact_ign.sa_flags = 0; /* interruptible */
@@ -1096,7 +1124,8 @@
 
 	sigtraps[SIGINT].flags |= TF_DFL_INTR | TF_TTY_INTR;
 	sigtraps[SIGQUIT].flags |= TF_DFL_INTR | TF_TTY_INTR;
-	sigtraps[SIGTERM].flags |= TF_DFL_INTR;/* not fatal for interactive */
+	/* SIGTERM is not fatal for interactive */
+	sigtraps[SIGTERM].flags |= TF_DFL_INTR;
 	sigtraps[SIGHUP].flags |= TF_FATAL;
 	sigtraps[SIGCHLD].flags |= TF_SHELL_USES;
 
@@ -1165,7 +1194,7 @@
 trapsig(int i)
 {
 	Trap *p = &sigtraps[i];
-	int errno_ = errno;
+	int errno_sv = errno;
 
 	trap = p->set = 1;
 	if (p->flags & TF_DFL_INTR)
@@ -1176,7 +1205,7 @@
 	}
 	if (p->shtrap)
 		(*p->shtrap)(i);
-	errno = errno_;
+	errno = errno_sv;
 }
 
 /*
@@ -1252,22 +1281,29 @@
 		intrsig = 0;
 	if (flag & TF_FATAL)
 		fatal_trap = 0;
+	++trap_nested;
 	for (p = sigtraps, i = NSIG+1; --i >= 0; p++)
 		if (p->set && (!flag ||
 		    ((p->flags & flag) && p->trap == NULL)))
-			runtrap(p);
+			runtrap(p, false);
+	if (!--trap_nested)
+		runtrap(NULL, true);
 }
 
 void
-runtrap(Trap *p)
+runtrap(Trap *p, bool is_last)
 {
-	int	i = p->signal;
-	char	*trapstr = p->trap;
-	int	oexstat;
-	int	old_changed = 0;
+	int old_changed = 0, i;
+	char *trapstr;
 
+	if (p == NULL)
+		/* just clean up, see runtraps() above */
+		goto donetrap;
+	i = p->signal;
+	trapstr = p->trap;
 	p->set = 0;
-	if (trapstr == NULL) { /* SIG_DFL */
+	if (trapstr == NULL) {
+		/* SIG_DFL */
 		if (p->flags & TF_FATAL) {
 			/* eg, SIGHUP */
 			exstat = 128 + i;
@@ -1278,23 +1314,25 @@
 			exstat = 128 + i;
 			unwind(LINTR);
 		}
-		return;
+		goto donetrap;
 	}
-	if (trapstr[0] == '\0') /* SIG_IGN */
-		return;
-	if (i == SIGEXIT_ || i == SIGERR_) {	/* avoid recursion on these */
+	if (trapstr[0] == '\0')
+		/* SIG_IGN */
+		goto donetrap;
+	if (i == ksh_SIGEXIT || i == ksh_SIGERR) {
+		/* avoid recursion on these */
 		old_changed = p->flags & TF_CHANGED;
 		p->flags &= ~TF_CHANGED;
 		p->trap = NULL;
 	}
-	oexstat = exstat;
+	if (trap_exstat == -1)
+		trap_exstat = exstat;
 	/*
 	 * Note: trapstr is fully parsed before anything is executed, thus
 	 * no problem with afree(p->trap) in settrap() while still in use.
 	 */
 	command(trapstr, current_lineno);
-	exstat = oexstat;
-	if (i == SIGEXIT_ || i == SIGERR_) {
+	if (i == ksh_SIGEXIT || i == ksh_SIGERR) {
 		if (p->flags & TF_CHANGED)
 			/* don't clear TF_CHANGED */
 			afree(trapstr, APERM);
@@ -1302,6 +1340,13 @@
 			p->trap = trapstr;
 		p->flags |= old_changed;
 	}
+
+ donetrap:
+	/* we're the last trap of a sequence executed */
+	if (is_last && trap_exstat != -1) {
+		exstat = trap_exstat;
+		trap_exstat = -1;
+	}
 }
 
 /* clear pending traps and reset user's trap handlers; used after fork(2) */
@@ -1341,7 +1386,8 @@
 
 	if (p->trap)
 		afree(p->trap, APERM);
-	strdupx(p->trap, s, APERM); /* handles s == 0 */
+	/* handles s == NULL */
+	strdupx(p->trap, s, APERM);
 	p->flags |= TF_CHANGED;
 	f = !s ? SIG_DFL : s[0] ? trapsig : SIG_IGN;
 
@@ -1385,7 +1431,8 @@
 			restore_dfl = 1;
 	} else if (p->cursig == SIG_DFL) {
 		setsig(p, SIG_IGN, SS_RESTORE_CURR);
-		restore_dfl = 1; /* restore to SIG_DFL */
+		/* restore to SIG_DFL */
+		restore_dfl = 1;
 	}
 	return (restore_dfl);
 }
@@ -1407,7 +1454,7 @@
 {
 	struct sigaction sigact;
 
-	if (p->signal == SIGEXIT_ || p->signal == SIGERR_)
+	if (p->signal == ksh_SIGEXIT || p->signal == ksh_SIGERR)
 		return (1);
 
 	/*
@@ -1448,7 +1495,8 @@
 	if (p->cursig != f) {
 		p->cursig = f;
 		(void)sigemptyset(&sigact.sa_mask);
-		sigact.sa_flags = 0 /* interruptible */;
+		/* interruptible */
+		sigact.sa_flags = 0;
 		sigact.sa_handler = f;
 		sigaction(p->signal, &sigact, NULL);
 	}
@@ -1468,7 +1516,8 @@
 	/* restore original value for exec'd kids */
 	p->flags &= ~(TF_EXEC_IGN|TF_EXEC_DFL);
 	switch (restore & SS_RESTORE_MASK) {
-	case SS_RESTORE_CURR: /* leave things as they currently are */
+	case SS_RESTORE_CURR:
+		/* leave things as they currently are */
 		break;
 	case SS_RESTORE_ORIG:
 		p->flags |= p->flags & TF_ORIG_IGN ? TF_EXEC_IGN : TF_EXEC_DFL;
diff --git a/src/jobs.c b/src/jobs.c
index 47326a1..0b5df4e 100644
--- a/src/jobs.c
+++ b/src/jobs.c
@@ -1,7 +1,7 @@
 /*	$OpenBSD: jobs.c,v 1.38 2009/12/12 04:28:44 deraadt Exp $	*/
 
 /*-
- * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2011
  *	Thorsten Glaser <tg@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -22,7 +22,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/jobs.c,v 1.69 2010/07/04 17:33:54 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/jobs.c,v 1.81 2011/08/27 18:06:46 tg Exp $");
 
 #if HAVE_KILLPG
 #define mksh_killpg		killpg
@@ -63,7 +63,7 @@
 #define JF_W_ASYNCNOTIFY 0x004	/* set if waiting and async notification ok */
 #define JF_XXCOM	0x008	/* set for $(command) jobs */
 #define JF_FG		0x010	/* running in foreground (also has tty pgrp) */
-#define JF_SAVEDTTY	0x020	/* j->ttystate is valid */
+#define JF_SAVEDTTY	0x020	/* j->ttystat is valid */
 #define JF_CHANGED	0x040	/* process has changed state */
 #define JF_KNOWN	0x080	/* $! referenced */
 #define JF_ZOMBIE	0x100	/* known, unwaited process */
@@ -87,7 +87,7 @@
 	int32_t	age;		/* number of jobs started */
 	Coproc_id coproc_id;	/* 0 or id of coprocess output pipe */
 #ifndef MKSH_UNEMPLOYED
-	struct termios ttystate;/* saved tty state for stopped jobs */
+	struct termios ttystat;	/* saved tty state for stopped jobs */
 	pid_t saved_ttypgrp;	/* saved tty process group for stopped jobs */
 #endif
 };
@@ -97,6 +97,7 @@
 #define JW_INTERRUPT	0x01	/* ^C will stop the wait */
 #define JW_ASYNCNOTIFY	0x02	/* asynchronous notification during wait ok */
 #define JW_STOPPEDWAIT	0x04	/* wait even if job stopped */
+#define JW_PIPEST	0x08	/* want PIPESTATUS */
 
 /* Error codes for j_lookup() */
 #define JL_OK		0
@@ -124,8 +125,10 @@
 #define CHILD_MAX	25
 #endif
 
+#ifndef MKSH_NOPROSPECTOFWORK
 /* held_sigchld is set if sigchld occurs before a job is completely started */
 static volatile sig_atomic_t held_sigchld;
+#endif
 
 #ifndef MKSH_UNEMPLOYED
 static struct shf	*shl_j;
@@ -157,6 +160,7 @@
 	Flag(FMONITOR) = 0;
 #endif
 
+#ifndef MKSH_NOPROSPECTOFWORK
 	(void)sigemptyset(&sm_default);
 	sigprocmask(SIG_SETMASK, &sm_default, NULL);
 
@@ -165,6 +169,10 @@
 
 	setsig(&sigtraps[SIGCHLD], j_sigchld,
 	    SS_RESTORE_ORIG|SS_FORCE|SS_SHTRAP);
+#else
+	/* Make sure SIGCHLD isn't ignored - can do odd things under SYSV */
+	setsig(&sigtraps[SIGCHLD], SIG_DFL, SS_RESTORE_ORIG|SS_FORCE);
+#endif
 
 #ifndef MKSH_UNEMPLOYED
 	if (!mflagset && Flag(FTALKING))
@@ -200,13 +208,26 @@
 		tty_init(true, true);
 }
 
+static int
+proc_errorlevel(Proc *p)
+{
+	switch (p->state) {
+	case PEXITED:
+		return (WEXITSTATUS(p->status));
+	case PSIGNALLED:
+		return (128 + WTERMSIG(p->status));
+	default:
+		return (0);
+	}
+}
+
 /* job cleanup before shell exit */
 void
 j_exit(void)
 {
 	/* kill stopped, and possibly running, jobs */
-	Job	*j;
-	int	killed = 0;
+	Job *j;
+	bool killed = false;
 
 	for (j = job_list; j != NULL; j = j->next) {
 		if (j->ppid == procpid &&
@@ -214,7 +235,7 @@
 		    (j->state == PRUNNING &&
 		    ((j->flags & JF_FG) ||
 		    (Flag(FLOGIN) && !Flag(FNOHUP) && procpid == kshpid))))) {
-			killed = 1;
+			killed = true;
 			if (j->pgrp == 0)
 				kill_job(j, SIGHUP);
 			else
@@ -272,12 +293,12 @@
 			setsig(&sigtraps[SIGTTIN], SIG_DFL,
 			    SS_RESTORE_ORIG|SS_FORCE);
 			/* wait to be given tty (POSIX.1, B.2, job control) */
-			while (1) {
+			while (/* CONSTCOND */ 1) {
 				pid_t ttypgrp;
 
 				if ((ttypgrp = tcgetpgrp(tty_fd)) < 0) {
-					warningf(false,
-					    "j_init: tcgetpgrp() failed: %s",
+					warningf(false, "%s: %s %s: %s",
+					    "j_init", "tcgetpgrp", "failed",
 					    strerror(errno));
 					ttypgrp_ok = false;
 					break;
@@ -292,14 +313,13 @@
 			    SS_RESTORE_DFL|SS_FORCE);
 		if (ttypgrp_ok && kshpgrp != kshpid) {
 			if (setpgid(0, kshpid) < 0) {
-				warningf(false,
-				    "j_init: setpgid() failed: %s",
-				    strerror(errno));
+				warningf(false, "%s: %s %s: %s", "j_init",
+				    "setpgid", "failed", strerror(errno));
 				ttypgrp_ok = false;
 			} else {
 				if (tcsetpgrp(tty_fd, kshpid) < 0) {
-					warningf(false,
-					    "j_init: tcsetpgrp() failed: %s",
+					warningf(false, "%s: %s %s: %s",
+					    "j_init", "tcsetpgrp", "failed",
 					    strerror(errno));
 					ttypgrp_ok = false;
 				} else
@@ -308,7 +328,8 @@
 			}
 		}
 		if (use_tty && !ttypgrp_ok)
-			warningf(false, "warning: won't have full job control");
+			warningf(false, "%s: %s", "warning",
+			    "won't have full job control");
 		if (tty_fd >= 0)
 			tcgetattr(tty_fd, &tty_state);
 	} else {
@@ -332,21 +353,46 @@
 }
 #endif
 
+#if HAVE_NICE
+/* run nice(3) and ignore the result */
+static void
+ksh_nice(int ness)
+{
+#if defined(__USE_FORTIFY_LEVEL) && (__USE_FORTIFY_LEVEL > 0)
+	int e;
+
+	errno = 0;
+	/* this is gonna annoy users; complain to your distro, people! */
+	if (nice(ness) == -1 && (e = errno) != 0)
+		warningf(false, "%s: %s", "bgnice", strerror(e));
+#else
+	(void)nice(ness);
+#endif
+}
+#endif
+
 /* execute tree in child subprocess */
 int
 exchild(struct op *t, int flags,
     volatile int *xerrok,
-    /* used if XPCLOSE or XCCLOSE */ int close_fd)
+    /* used if XPCLOSE or XCCLOSE */
+    int close_fd)
 {
-	static Proc *last_proc;		/* for pipelines */
+	/* for pipelines */
+	static Proc *last_proc;
 
-	int rv = 0, forksleep;
+	int rv = 0, forksleep, jwflags = JW_NONE;
+#ifndef MKSH_NOPROSPECTOFWORK
 	sigset_t omask;
-	struct {
-		Proc *p;
-		Job *j;
-		pid_t cldpid;
-	} pi;
+#endif
+	Proc *p;
+	Job *j;
+	pid_t cldpid;
+
+	if (flags & XPIPEST) {
+		flags &= ~XPIPEST;
+		jwflags |= JW_PIPEST;
+	}
 
 	if (flags & XEXEC)
 		/*
@@ -355,103 +401,111 @@
 		 */
 		return (execute(t, flags & (XEXEC | XERROK), xerrok));
 
+#ifndef MKSH_NOPROSPECTOFWORK
 	/* no SIGCHLDs while messing with job and process lists */
 	sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+#endif
 
-	pi.p = new_proc();
-	pi.p->next = NULL;
-	pi.p->state = PRUNNING;
-	pi.p->status = 0;
-	pi.p->pid = 0;
+	p = new_proc();
+	p->next = NULL;
+	p->state = PRUNNING;
+	p->status = 0;
+	p->pid = 0;
 
 	/* link process into jobs list */
 	if (flags & XPIPEI) {
 		/* continuing with a pipe */
 		if (!last_job)
-			internal_errorf(
-			    "exchild: XPIPEI and no last_job - pid %d",
+			internal_errorf("%s %d",
+			    "exchild: XPIPEI and no last_job - pid",
 			    (int)procpid);
-		pi.j = last_job;
+		j = last_job;
 		if (last_proc)
-			last_proc->next = pi.p;
-		last_proc = pi.p;
+			last_proc->next = p;
+		last_proc = p;
 	} else {
-		pi.j = new_job(); /* fills in pi.j->job */
+		/* fills in j->job */
+		j = new_job();
 		/*
 		 * we don't consider XXCOMs foreground since they don't get
 		 * tty process group and we don't save or restore tty modes.
 		 */
-		pi.j->flags = (flags & XXCOM) ? JF_XXCOM :
+		j->flags = (flags & XXCOM) ? JF_XXCOM :
 		    ((flags & XBGND) ? 0 : (JF_FG|JF_USETTYMODE));
-		timerclear(&pi.j->usrtime);
-		timerclear(&pi.j->systime);
-		pi.j->state = PRUNNING;
-		pi.j->pgrp = 0;
-		pi.j->ppid = procpid;
-		pi.j->age = ++njobs;
-		pi.j->proc_list = pi.p;
-		pi.j->coproc_id = 0;
-		last_job = pi.j;
-		last_proc = pi.p;
-		put_job(pi.j, PJ_PAST_STOPPED);
+		timerclear(&j->usrtime);
+		timerclear(&j->systime);
+		j->state = PRUNNING;
+		j->pgrp = 0;
+		j->ppid = procpid;
+		j->age = ++njobs;
+		j->proc_list = p;
+		j->coproc_id = 0;
+		last_job = j;
+		last_proc = p;
+		put_job(j, PJ_PAST_STOPPED);
 	}
 
-	snptreef(pi.p->command, sizeof(pi.p->command), "%T", t);
+	vistree(p->command, sizeof(p->command), t);
 
 	/* create child process */
 	forksleep = 1;
-	while ((pi.cldpid = fork()) < 0 && errno == EAGAIN && forksleep < 32) {
-		if (intrsig)	 /* allow user to ^C out... */
+	while ((cldpid = fork()) < 0 && errno == EAGAIN && forksleep < 32) {
+		if (intrsig)
+			/* allow user to ^C out... */
 			break;
 		sleep(forksleep);
 		forksleep <<= 1;
 	}
-	if (pi.cldpid < 0) {
-		kill_job(pi.j, SIGKILL);
-		remove_job(pi.j, "fork failed");
+	/* ensure $RANDOM changes between parent and child */
+	rndset((long)cldpid);
+	/* fork failed? */
+	if (cldpid < 0) {
+		kill_job(j, SIGKILL);
+		remove_job(j, "fork failed");
+#ifndef MKSH_NOPROSPECTOFWORK
 		sigprocmask(SIG_SETMASK, &omask, NULL);
-		errorf("cannot fork - try again");
+#endif
+		errorf("can't fork - try again");
 	}
-	pi.p->pid = pi.cldpid ? pi.cldpid : (procpid = getpid());
-
-	/*
-	 * ensure next child gets a (slightly) different $RANDOM sequence
-	 * from its parent process and other child processes
-	 */
-	change_random(&pi, sizeof(pi));
+	p->pid = cldpid ? cldpid : (procpid = getpid());
 
 #ifndef MKSH_UNEMPLOYED
 	/* job control set up */
 	if (Flag(FMONITOR) && !(flags&XXCOM)) {
-		int	dotty = 0;
-		if (pi.j->pgrp == 0) {	/* First process */
-			pi.j->pgrp = pi.p->pid;
-			dotty = 1;
+		bool dotty = false;
+		if (j->pgrp == 0) {
+			/* First process */
+			j->pgrp = p->pid;
+			dotty = true;
 		}
 
-		/* set pgrp in both parent and child to deal with race
+		/*
+		 * set pgrp in both parent and child to deal with race
 		 * condition
 		 */
-		setpgid(pi.p->pid, pi.j->pgrp);
+		setpgid(p->pid, j->pgrp);
 		if (ttypgrp_ok && dotty && !(flags & XBGND))
-			tcsetpgrp(tty_fd, pi.j->pgrp);
+			tcsetpgrp(tty_fd, j->pgrp);
 	}
 #endif
 
 	/* used to close pipe input fd */
-	if (close_fd >= 0 && (((flags & XPCLOSE) && pi.cldpid) ||
-	    ((flags & XCCLOSE) && !pi.cldpid)))
+	if (close_fd >= 0 && (((flags & XPCLOSE) && cldpid) ||
+	    ((flags & XCCLOSE) && !cldpid)))
 		close(close_fd);
-	if (!pi.cldpid) {
+	if (!cldpid) {
 		/* child */
 
 		/* Do this before restoring signal */
 		if (flags & XCOPROC)
 			coproc_cleanup(false);
+#ifndef MKSH_NOPROSPECTOFWORK
 		sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
 		cleanup_parents_env();
 #ifndef MKSH_UNEMPLOYED
-		/* If FMONITOR or FTALKING is set, these signals are ignored,
+		/*
+		 * If FMONITOR or FTALKING is set, these signals are ignored,
 		 * if neither FMONITOR nor FTALKING are set, the signals have
 		 * their inherited values.
 		 */
@@ -463,7 +517,7 @@
 #endif
 #if HAVE_NICE
 		if (Flag(FBGNICE) && (flags & XBGND))
-			(void)nice(4);
+			ksh_nice(4);
 #endif
 		if ((flags & XBGND)
 #ifndef MKSH_UNEMPLOYED
@@ -480,7 +534,8 @@
 				close(forksleep);
 			}
 		}
-		remove_job(pi.j, "child");	/* in case of $(jobs) command */
+		/* in case of $(jobs) command */
+		remove_job(j, "child");
 		nzombie = 0;
 #ifndef MKSH_UNEMPLOYED
 		ttypgrp_ok = false;
@@ -494,8 +549,9 @@
 #ifndef MKSH_SMALL
 		if (t->type == TPIPE)
 			unwind(LLEAVE);
-		internal_warningf("exchild: execute() returned");
-		fptreef(shl_out, 2, "exchild: tried to execute {\n%T\n}\n", t);
+		internal_warningf("%s: %s", "exchild", "execute() returned");
+		fptreef(shl_out, 8, "%s: tried to execute {\n\t%T\n}\n",
+		    "exchild", t);
 		shf_flush(shl_out);
 #endif
 		unwind(LLEAVE);
@@ -503,31 +559,33 @@
 	}
 
 	/* shell (parent) stuff */
-	if (!(flags & XPIPEO)) {	/* last process in a job */
-		j_startjob(pi.j);
+	if (!(flags & XPIPEO)) {
+		/* last process in a job */
+		j_startjob(j);
 		if (flags & XCOPROC) {
-			pi.j->coproc_id = coproc.id;
+			j->coproc_id = coproc.id;
 			/* n jobs using co-process output */
 			coproc.njobs++;
 			/* j using co-process input */
-			coproc.job = (void *)pi.j;
+			coproc.job = (void *)j;
 		}
 		if (flags & XBGND) {
-			j_set_async(pi.j);
+			j_set_async(j);
 			if (Flag(FTALKING)) {
-				shf_fprintf(shl_out, "[%d]", pi.j->job);
-				for (pi.p = pi.j->proc_list; pi.p;
-				    pi.p = pi.p->next)
+				shf_fprintf(shl_out, "[%d]", j->job);
+				for (p = j->proc_list; p; p = p->next)
 					shf_fprintf(shl_out, " %d",
-					    (int)pi.p->pid);
+					    (int)p->pid);
 				shf_putchar('\n', shl_out);
 				shf_flush(shl_out);
 			}
 		} else
-			rv = j_waitj(pi.j, JW_NONE, "jw:last proc");
+			rv = j_waitj(j, jwflags, "jw:last proc");
 	}
 
+#ifndef MKSH_NOPROSPECTOFWORK
 	sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
 
 	return (rv);
 }
@@ -536,41 +594,53 @@
 void
 startlast(void)
 {
+#ifndef MKSH_NOPROSPECTOFWORK
 	sigset_t omask;
 
 	sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+#endif
 
-	if (last_job) { /* no need to report error - waitlast() will do it */
+	/* no need to report error - waitlast() will do it */
+	if (last_job) {
 		/* ensure it isn't removed by check_job() */
 		last_job->flags |= JF_WAITING;
 		j_startjob(last_job);
 	}
+#ifndef MKSH_NOPROSPECTOFWORK
 	sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
 }
 
 /* wait for last job: only used for $(command) jobs */
 int
 waitlast(void)
 {
-	int	rv;
-	Job	*j;
+	int rv;
+	Job *j;
+#ifndef MKSH_NOPROSPECTOFWORK
 	sigset_t omask;
 
 	sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+#endif
 
 	j = last_job;
 	if (!j || !(j->flags & JF_STARTED)) {
 		if (!j)
-			warningf(true, "waitlast: no last job");
+			warningf(true, "%s: %s", "waitlast", "no last job");
 		else
-			internal_warningf("waitlast: not started");
+			internal_warningf("%s: %s", "waitlast", "not started");
+#ifndef MKSH_NOPROSPECTOFWORK
 		sigprocmask(SIG_SETMASK, &omask, NULL);
-		return (125); /* not so arbitrary, non-zero value */
+#endif
+		/* not so arbitrary, non-zero value */
+		return (125);
 	}
 
-	rv = j_waitj(j, JW_NONE, "jw:waitlast");
+	rv = j_waitj(j, JW_NONE, "waitlast");
 
+#ifndef MKSH_NOPROSPECTOFWORK
 	sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
 
 	return (rv);
 }
@@ -579,13 +649,13 @@
 int
 waitfor(const char *cp, int *sigp)
 {
-	int	rv;
-	Job	*j;
-	int	ecode;
-	int	flags = JW_INTERRUPT|JW_ASYNCNOTIFY;
+	int rv, ecode, flags = JW_INTERRUPT|JW_ASYNCNOTIFY;
+	Job *j;
+#ifndef MKSH_NOPROSPECTOFWORK
 	sigset_t omask;
 
 	sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+#endif
 
 	*sigp = 0;
 
@@ -599,18 +669,24 @@
 			if (j->ppid == procpid && j->state == PRUNNING)
 				break;
 		if (!j) {
+#ifndef MKSH_NOPROSPECTOFWORK
 			sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
 			return (-1);
 		}
 	} else if ((j = j_lookup(cp, &ecode))) {
 		/* don't report normal job completion */
 		flags &= ~JW_ASYNCNOTIFY;
 		if (j->ppid != procpid) {
+#ifndef MKSH_NOPROSPECTOFWORK
 			sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
 			return (-1);
 		}
 	} else {
+#ifndef MKSH_NOPROSPECTOFWORK
 		sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
 		if (ecode != JL_NOSUCH)
 			bi_errorf("%s: %s", cp, lookup_msgs[ecode]);
 		return (-1);
@@ -619,9 +695,12 @@
 	/* AT&T ksh will wait for stopped jobs - we don't */
 	rv = j_waitj(j, flags, "jw:waitfor");
 
+#ifndef MKSH_NOPROSPECTOFWORK
 	sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
 
-	if (rv < 0) /* we were interrupted */
+	if (rv < 0)
+		/* we were interrupted */
 		*sigp = 128 + -rv;
 
 	return (rv);
@@ -631,20 +710,24 @@
 int
 j_kill(const char *cp, int sig)
 {
-	Job	*j;
-	int	rv = 0;
-	int	ecode;
+	Job *j;
+	int rv = 0, ecode;
+#ifndef MKSH_NOPROSPECTOFWORK
 	sigset_t omask;
 
 	sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+#endif
 
 	if ((j = j_lookup(cp, &ecode)) == NULL) {
+#ifndef MKSH_NOPROSPECTOFWORK
 		sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
 		bi_errorf("%s: %s", cp, lookup_msgs[ecode]);
 		return (1);
 	}
 
-	if (j->pgrp == 0) {	/* started when !Flag(FMONITOR) */
+	if (j->pgrp == 0) {
+		/* started when !Flag(FMONITOR) */
 		if (kill_job(j, sig) < 0) {
 			bi_errorf("%s: %s", cp, strerror(errno));
 			rv = 1;
@@ -660,7 +743,9 @@
 		}
 	}
 
+#ifndef MKSH_NOPROSPECTOFWORK
 	sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
 
 	return (rv);
 }
@@ -670,11 +755,10 @@
 int
 j_resume(const char *cp, int bg)
 {
-	Job	*j;
-	Proc	*p;
-	int	ecode;
-	int	running;
-	int	rv = 0;
+	Job *j;
+	Proc *p;
+	int ecode, rv = 0;
+	bool running;
 	sigset_t omask;
 
 	sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
@@ -694,12 +778,12 @@
 	if (bg)
 		shprintf("[%d] ", j->job);
 
-	running = 0;
+	running = false;
 	for (p = j->proc_list; p != NULL; p = p->next) {
 		if (p->state == PSTOPPED) {
 			p->state = PRUNNING;
 			p->status = 0;
-			running = 1;
+			running = true;
 		}
 		shf_puts(p->command, shl_stdout);
 		if (p->next)
@@ -717,7 +801,7 @@
 		/* attach tty to job */
 		if (j->state == PRUNNING) {
 			if (ttypgrp_ok && (j->flags & JF_SAVEDTTY))
-				tcsetattr(tty_fd, TCSADRAIN, &j->ttystate);
+				tcsetattr(tty_fd, TCSADRAIN, &j->ttystat);
 			/* See comment in j_waitj regarding saved_ttypgrp. */
 			if (ttypgrp_ok &&
 			    tcsetpgrp(tty_fd, (j->flags & JF_SAVEDTTYPGRP) ?
@@ -725,12 +809,11 @@
 				rv = errno;
 				if (j->flags & JF_SAVEDTTY)
 					tcsetattr(tty_fd, TCSADRAIN, &tty_state);
-				sigprocmask(SIG_SETMASK, &omask,
-				    NULL);
-				bi_errorf("1st tcsetpgrp(%d, %d) failed: %s",
-				    tty_fd,
-				    (int)((j->flags & JF_SAVEDTTYPGRP) ?
-				    j->saved_ttypgrp : j->pgrp),
+				sigprocmask(SIG_SETMASK, &omask, NULL);
+				bi_errorf("%s %s(%d, %ld) %s: %s",
+				    "1st", "tcsetpgrp", tty_fd,
+				    (long)((j->flags & JF_SAVEDTTYPGRP) ?
+				    j->saved_ttypgrp : j->pgrp), "failed",
 				    strerror(rv));
 				return (1);
 			}
@@ -749,12 +832,12 @@
 			if (ttypgrp_ok && (j->flags & JF_SAVEDTTY))
 				tcsetattr(tty_fd, TCSADRAIN, &tty_state);
 			if (ttypgrp_ok && tcsetpgrp(tty_fd, kshpgrp) < 0)
-				warningf(true,
-				    "fg: 2nd tcsetpgrp(%d, %ld) failed: %s",
-				    tty_fd, (long)kshpgrp, strerror(errno));
+				warningf(true, "%s %s(%d, %ld) %s: %s",
+				    "fg: 2nd", "tcsetpgrp", tty_fd,
+				    (long)kshpgrp, "failed", strerror(errno));
 		}
 		sigprocmask(SIG_SETMASK, &omask, NULL);
-		bi_errorf("cannot continue job %s: %s",
+		bi_errorf("%s %s %s", "can't continue job",
 		    cp, strerror(err));
 		return (1);
 	}
@@ -773,8 +856,8 @@
 int
 j_stopped_running(void)
 {
-	Job	*j;
-	int	which = 0;
+	Job *j;
+	int which = 0;
 
 	for (j = job_list; j != NULL; j = j->next) {
 #ifndef MKSH_UNEMPLOYED
@@ -796,35 +879,23 @@
 	return (0);
 }
 
-int
-j_njobs(void)
-{
-	Job *j;
-	int nj = 0;
-	sigset_t omask;
-
-	sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
-	for (j = job_list; j; j = j->next)
-		nj++;
-
-	sigprocmask(SIG_SETMASK, &omask, NULL);
-	return (nj);
-}
-
 
 /* list jobs for jobs built-in */
 int
 j_jobs(const char *cp, int slp,
-    int nflag)		/* 0: short, 1: long, 2: pgrp */
+    /* 0: short, 1: long, 2: pgrp */
+    int nflag)
 {
-	Job	*j, *tmp;
-	int	how;
-	int	zflag = 0;
+	Job *j, *tmp;
+	int how, zflag = 0;
+#ifndef MKSH_NOPROSPECTOFWORK
 	sigset_t omask;
 
 	sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+#endif
 
-	if (nflag < 0) { /* kludge: print zombies */
+	if (nflag < 0) {
+		/* kludge: print zombies */
 		nflag = 0;
 		zflag = 1;
 	}
@@ -832,7 +903,9 @@
 		int	ecode;
 
 		if ((j = j_lookup(cp, &ecode)) == NULL) {
+#ifndef MKSH_NOPROSPECTOFWORK
 			sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
 			bi_errorf("%s: %s", cp, lookup_msgs[ecode]);
 			return (1);
 		}
@@ -855,7 +928,9 @@
 		if (j->flags & JF_REMOVE)
 			remove_job(j, "jobs");
 	}
+#ifndef MKSH_NOPROSPECTOFWORK
 	sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
 	return (0);
 }
 
@@ -863,16 +938,19 @@
 void
 j_notify(void)
 {
-	Job	*j, *tmp;
+	Job *j, *tmp;
+#ifndef MKSH_NOPROSPECTOFWORK
 	sigset_t omask;
 
 	sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+#endif
 	for (j = job_list; j; j = j->next) {
 #ifndef MKSH_UNEMPLOYED
 		if (Flag(FMONITOR) && (j->flags & JF_CHANGED))
 			j_print(j, JP_MEDIUM, shl_out);
 #endif
-		/* Remove job after doing reports so there aren't
+		/*
+		 * Remove job after doing reports so there aren't
 		 * multiple +/- jobs.
 		 */
 		if (j->state == PEXITED || j->state == PSIGNALLED)
@@ -884,21 +962,27 @@
 			remove_job(j, "notify");
 	}
 	shf_flush(shl_out);
+#ifndef MKSH_NOPROSPECTOFWORK
 	sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
 }
 
 /* Return pid of last process in last asynchronous job */
 pid_t
 j_async(void)
 {
+#ifndef MKSH_NOPROSPECTOFWORK
 	sigset_t omask;
 
 	sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+#endif
 
 	if (async_job)
 		async_job->flags |= JF_KNOWN;
 
+#ifndef MKSH_NOPROSPECTOFWORK
 	sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
 
 	return (async_pid);
 }
@@ -916,7 +1000,7 @@
 	if (async_job && (async_job->flags & (JF_KNOWN|JF_ZOMBIE)) == JF_ZOMBIE)
 		remove_job(async_job, "async");
 	if (!(j->flags & JF_STARTED)) {
-		internal_warningf("j_async: job not started");
+		internal_warningf("%s: %s", "j_async", "job not started");
 		return;
 	}
 	async_job = j;
@@ -930,8 +1014,8 @@
 		if (!oldest) {
 			/* XXX debugging */
 			if (!(async_job->flags & JF_ZOMBIE) || nzombie != 1) {
-				internal_warningf("j_async: bad nzombie (%d)",
-				    nzombie);
+				internal_warningf("%s: bad nzombie (%d)",
+				    "j_async", nzombie);
 				nzombie = 0;
 			}
 			break;
@@ -955,11 +1039,13 @@
 		;
 	j->last_proc = p;
 
+#ifndef MKSH_NOPROSPECTOFWORK
 	if (held_sigchld) {
 		held_sigchld = 0;
 		/* Don't call j_sigchld() as it may remove job... */
 		kill(procpid, SIGCHLD);
 	}
+#endif
 }
 
 /*
@@ -969,10 +1055,11 @@
  */
 static int
 j_waitj(Job *j,
-    int flags,			/* see JW_* */
+    /* see JW_* */
+    int flags,
     const char *where)
 {
-	int	rv;
+	int rv;
 
 	/*
 	 * No auto-notify on the job we are waiting on.
@@ -988,12 +1075,17 @@
 
 	while (j->state == PRUNNING ||
 	    ((flags & JW_STOPPEDWAIT) && j->state == PSTOPPED)) {
+#ifndef MKSH_NOPROSPECTOFWORK
 		sigsuspend(&sm_default);
+#else
+		j_sigchld(SIGCHLD);
+#endif
 		if (fatal_trap) {
 			int oldf = j->flags & (JF_WAITING|JF_W_ASYNCNOTIFY);
 			j->flags &= ~(JF_WAITING|JF_W_ASYNCNOTIFY);
 			runtraps(TF_FATAL);
-			j->flags |= oldf; /* not reached... */
+			/* not reached... */
+			j->flags |= oldf;
 		}
 		if ((flags & JW_INTERRUPT) && (rv = trap_pending())) {
 			j->flags &= ~(JF_WAITING|JF_W_ASYNCNOTIFY);
@@ -1021,12 +1113,12 @@
 			    (j->saved_ttypgrp = tcgetpgrp(tty_fd)) >= 0)
 				j->flags |= JF_SAVEDTTYPGRP;
 			if (tcsetpgrp(tty_fd, kshpgrp) < 0)
-				warningf(true,
-				    "j_waitj: tcsetpgrp(%d, %ld) failed: %s",
-				    tty_fd, (long)kshpgrp, strerror(errno));
+				warningf(true, "%s %s(%d, %ld) %s: %s",
+				    "j_waitj:", "tcsetpgrp", tty_fd,
+				    (long)kshpgrp, "failed", strerror(errno));
 			if (j->state == PSTOPPED) {
 				j->flags |= JF_SAVEDTTY;
-				tcgetattr(tty_fd, &j->ttystate);
+				tcgetattr(tty_fd, &j->ttystat);
 			}
 		}
 #endif
@@ -1084,6 +1176,38 @@
 	j_systime = j->systime;
 	rv = j->status;
 
+	if ((flags & JW_PIPEST) && (j->proc_list != NULL)) {
+		uint32_t num = 0;
+		Proc *p = j->proc_list;
+		struct tbl *vp;
+
+		unset(vp_pipest, 1);
+		vp = vp_pipest;
+		vp->flag = DEFINED | ISSET | INTEGER | RDONLY | ARRAY | INT_U;
+		goto got_array;
+
+		while (p != NULL) {
+			{
+				struct tbl *vq;
+
+				/* strlen(vp_pipest->name) == 10 */
+				vq = alloc(offsetof(struct tbl, name[0]) + 11,
+				    vp_pipest->areap);
+				memset(vq, 0, offsetof(struct tbl, name[0]));
+				memcpy(vq->name, vp_pipest->name, 11);
+				vp->u.array = vq;
+				vp = vq;
+			}
+			vp->areap = vp_pipest->areap;
+			vp->ua.index = ++num;
+			vp->flag = DEFINED | ISSET | INTEGER | RDONLY |
+			    ARRAY | INT_U | AINDEX;
+ got_array:
+			vp->val.i = proc_errorlevel(p);
+			p = p->next;
+		}
+	}
+
 	if (!(flags & JW_ASYNCNOTIFY)
 #ifndef MKSH_UNEMPLOYED
 	    && (!Flag(FMONITOR) || j->state != PSTOPPED)
@@ -1119,6 +1243,7 @@
 	int status;
 	struct rusage ru0, ru1;
 
+#ifndef MKSH_NOPROSPECTOFWORK
 	/*
 	 * Don't wait for any processes if a job is partially started.
 	 * This is so we don't do away with the process group leader
@@ -1130,10 +1255,15 @@
 			held_sigchld = 1;
 			return;
 		}
+#endif
 
 	getrusage(RUSAGE_CHILDREN, &ru0);
 	do {
+#ifndef MKSH_NOPROSPECTOFWORK
 		pid = waitpid(-1, &status, (WNOHANG|WUNTRACED));
+#else
+		pid = wait(&status);
+#endif
 
 		/*
 		 * return if this would block (0) or no children
@@ -1175,8 +1305,14 @@
 		else
 			p->state = PEXITED;
 
-		check_job(j);	/* check to see if entire job is done */
-	} while (1);
+		/* check to see if entire job is done */
+		check_job(j);
+	}
+#ifndef MKSH_NOPROSPECTOFWORK
+	    while (/* CONSTCOND */ 1);
+#else
+	    while (/* CONSTCOND */ 0);
+#endif
 }
 
 /*
@@ -1203,23 +1339,13 @@
 	jstate = PRUNNING;
 	for (p=j->proc_list; p != NULL; p = p->next) {
 		if (p->state == PRUNNING)
-			return;	/* some processes still running */
+			/* some processes still running */
+			return;
 		if (p->state > jstate)
 			jstate = p->state;
 	}
 	j->state = jstate;
-
-	switch (j->last_proc->state) {
-	case PEXITED:
-		j->status = WEXITSTATUS(j->last_proc->status);
-		break;
-	case PSIGNALLED:
-		j->status = 128 + WTERMSIG(j->last_proc->status);
-		break;
-	default:
-		j->status = 0;
-		break;
-	}
+	j->status = proc_errorlevel(j->last_proc);
 
 	/*
 	 * Note when co-process dies: can't be done in j_wait() nor
@@ -1371,7 +1497,7 @@
 			if (p == j->proc_list)
 				shf_fprintf(shf, "[%d] %c ", j->job, jobchar);
 			else
-				shf_fprintf(shf, "%s", filler);
+				shf_puts(filler, shf);
 		}
 
 		if (how == JP_LONG)
@@ -1416,9 +1542,10 @@
 static Job *
 j_lookup(const char *cp, int *ecodep)
 {
-	Job		*j, *last_match;
-	Proc		*p;
-	int		len, job = 0;
+	Job *j, *last_match;
+	Proc *p;
+	size_t len;
+	int job = 0;
 
 	if (ksh_isdigit(*cp)) {
 		getn(cp, &job);
@@ -1463,7 +1590,8 @@
 				return (j);
 		break;
 
-	case '?':		/* %?string */
+	/* %?string */
+	case '?':
 		last_match = NULL;
 		for (j = job_list; j != NULL; j = j->next)
 			for (p = j->proc_list; p != NULL; p = p->next)
@@ -1479,7 +1607,8 @@
 			return (last_match);
 		break;
 
-	default:		/* %string */
+	/* %string */
+	default:
 		len = strlen(cp);
 		last_match = NULL;
 		for (j = job_list; j != NULL; j = j->next)
@@ -1568,7 +1697,7 @@
 	for (; curr != NULL && curr != j; prev = &curr->next, curr = *prev)
 		;
 	if (curr != j) {
-		internal_warningf("remove_job: job not found (%s)", where);
+		internal_warningf("remove_job: job %s (%s)", "not found", where);
 		return;
 	}
 	*prev = curr->next;
diff --git a/src/lalloc.c b/src/lalloc.c
index 79627d1..daaee57 100644
--- a/src/lalloc.c
+++ b/src/lalloc.c
@@ -1,32 +1,32 @@
 /*-
- * Copyright © 2009
+ * Copyright (c) 2009, 2010, 2011
  *	Thorsten Glaser <tg@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
  * are retained or reproduced in an accompanying document, permission
- * is granted to deal in this work without restriction, including un‐
+ * is granted to deal in this work without restriction, including un-
  * limited rights to use, publicly perform, distribute, sell, modify,
  * merge, give away, or sublicence.
  *
- * This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
  * the utmost extent permitted by applicable law, neither express nor
  * implied; without malicious intent or gross negligence. In no event
  * may a licensor, author or contributor be held liable for indirect,
  * direct, other damage, loss, or other issues arising in any way out
  * of dealing in the work, even if advised of the possibility of such
  * damage or existence of a defect, except proven that it results out
- * of said person’s immediate fault when using the work as intended.
+ * of said person's immediate fault when using the work as intended.
  */
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/lalloc.c,v 1.11 2009/08/08 13:08:51 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/lalloc.c,v 1.19 2011/09/07 15:24:16 tg Exp $");
 
 /* build with CPPFLAGS+= -DUSE_REALLOC_MALLOC=0 on ancient systems */
 #if defined(USE_REALLOC_MALLOC) && (USE_REALLOC_MALLOC == 0)
-#define remalloc(p,n)	((p) == NULL ? malloc(n) : realloc((p), (n)))
+#define remalloc(p,n)	((p) == NULL ? malloc_osi(n) : realloc_osi((p), (n)))
 #else
-#define remalloc(p,n)	realloc((p), (n))
+#define remalloc(p,n)	realloc_osi((p), (n))
 #endif
 
 #define ALLOC_ISUNALIGNED(p) (((ptrdiff_t)(p)) % ALLOC_SIZE)
@@ -61,12 +61,27 @@
 #ifndef MKSH_SMALL
  fail:
 #endif
-			internal_errorf("rogue pointer %p", ptr);
+#ifdef DEBUG
+			internal_warningf("rogue pointer %zX in ap %zX",
+			    (size_t)ptr, (size_t)ap);
+			/* try to get a coredump */
+			abort();
+#else
+			internal_errorf("rogue pointer %zX", (size_t)ptr);
+#endif
 		}
 	return (ap);
 }
 
 void *
+aresize2(void *ptr, size_t fac1, size_t fac2, Area *ap)
+{
+	if (notoktomul(fac1, fac2))
+		internal_errorf(Tintovfl, fac1, '*', fac2);
+	return (aresize(ptr, fac1 * fac2, ap));
+}
+
+void *
 aresize(void *ptr, size_t numb, Area *ap)
 {
 	ALLOC_ITEM *lp = NULL;
@@ -79,14 +94,13 @@
 		pp->next = lp->next;
 	}
 
-	if ((numb >= SIZE_MAX - ALLOC_SIZE) ||
+	if (notoktoadd(numb, ALLOC_SIZE) ||
 	    (lp = remalloc(lp, numb + ALLOC_SIZE)) == NULL
 #ifndef MKSH_SMALL
 	    || ALLOC_ISUNALIGNED(lp)
 #endif
 	    )
-		internal_errorf("cannot allocate %lu data bytes",
-		    (unsigned long)numb);
+		internal_errorf(Toomem, (unsigned long)numb);
 	/* this only works because Area is an ALLOC_ITEM */
 	lp->next = ap->next;
 	ap->next = lp;
@@ -104,7 +118,7 @@
 		/* unhook */
 		pp->next = lp->next;
 		/* now free ALLOC_ITEM */
-		free(lp);
+		free_osimalloc(lp);
 	}
 }
 
@@ -118,6 +132,6 @@
 		/* make next ALLOC_ITEM head of list */
 		ap->next = lp->next;
 		/* free old head */
-		free(lp);
+		free_osimalloc(lp);
 	}
 }
diff --git a/src/lex.c b/src/lex.c
index d0219e7..8a1959a 100644
--- a/src/lex.c
+++ b/src/lex.c
@@ -1,7 +1,7 @@
-/*	$OpenBSD: lex.c,v 1.44 2008/07/03 17:52:08 otto Exp $	*/
+/*	$OpenBSD: lex.c,v 1.45 2011/03/09 09:30:39 okan Exp $	*/
 
 /*-
- * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
  *	Thorsten Glaser <tg@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -22,7 +22,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/lex.c,v 1.118 2010/07/25 11:35:41 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/lex.c,v 1.156 2011/09/07 15:24:16 tg Exp $");
 
 /*
  * states while lexing word
@@ -35,151 +35,160 @@
 #define SEQUOTE		5	/* inside $'' */
 #define SBRACE		6	/* inside ${} */
 #define SQBRACE		7	/* inside "${}" */
-#define SCSPAREN	8	/* inside $() */
-#define SBQUOTE		9	/* inside `` */
-#define SASPAREN	10	/* inside $(( )) */
-#define SHEREDELIM	11	/* parsing <<,<<- delimiter */
-#define SHEREDQUOTE	12	/* parsing " in <<,<<- delimiter */
-#define SPATTERN	13	/* parsing *(...|...) pattern (*+?@!) */
-#define STBRACE		14	/* parsing ${...[#%]...} */
-#define SLETARRAY	15	/* inside =( ), just copy */
-#define SADELIM		16	/* like SBASE, looking for delimiter */
-#define SHERESTRING	17	/* parsing <<< string */
+#define SBQUOTE		8	/* inside `` */
+#define SASPAREN	9	/* inside $(( )) */
+#define SHEREDELIM	10	/* parsing <<,<<- delimiter */
+#define SHEREDQUOTE	11	/* parsing " in <<,<<- delimiter */
+#define SPATTERN	12	/* parsing *(...|...) pattern (*+?@!) */
+#define SADELIM		13	/* like SBASE, looking for delimiter */
+#define SHERESTRING	14	/* parsing <<< string */
+#define STBRACEKORN	15	/* parsing ${...[#%]...} !FSH */
+#define STBRACEBOURNE	16	/* parsing ${...[#%]...} FSH */
+#define SINVALID	255	/* invalid state */
 
-/* Structure to keep track of the lexing state and the various pieces of info
- * needed for each particular state. */
-typedef struct lex_state Lex_state;
-struct lex_state {
-	int ls_state;
-	union {
-		/* $(...) */
-		struct scsparen_info {
-			int nparen;	/* count open parenthesis */
-			int csstate;	/* XXX remove */
-#define ls_scsparen ls_info.u_scsparen
-		} u_scsparen;
-
-		/* $((...)) */
-		struct sasparen_info {
-			int nparen;	/* count open parenthesis */
-			int start;	/* marks start of $(( in output str */
-#define ls_sasparen ls_info.u_sasparen
-		} u_sasparen;
-
-		/* ((...)) */
-		struct sletparen_info {
-			int nparen;	/* count open parenthesis */
-#define ls_sletparen ls_info.u_sletparen
-		} u_sletparen;
-
-		/* `...` */
-		struct sbquote_info {
-			int indquotes;	/* true if in double quotes: "`...`" */
-#define ls_sbquote ls_info.u_sbquote
-		} u_sbquote;
-
-#ifndef MKSH_SMALL
-		/* =(...) */
-		struct sletarray_info {
-			int nparen;	/* count open parentheses */
-#define ls_sletarray ls_info.u_sletarray
-		} u_sletarray;
-#endif
-
-		/* ADELIM */
-		struct sadelim_info {
-			unsigned char nparen;	/* count open parentheses */
-#define SADELIM_BASH	0
-#define SADELIM_MAKE	1
-			unsigned char style;
-			unsigned char delimiter;
-			unsigned char num;
-			unsigned char flags;	/* ofs. into sadelim_flags[] */
-#define ls_sadelim ls_info.u_sadelim
-		} u_sadelim;
-
-		/* $'...' */
-		struct sequote_info {
-			bool got_NUL;	/* ignore rest of string */
-#define ls_sequote ls_info.u_sequote
-		} u_sequote;
-
-		Lex_state *base;	/* used to point to next state block */
-	} ls_info;
+struct sretrace_info {
+	struct sretrace_info *next;
+	XString xs;
+	char *xp;
 };
 
+/*
+ * Structure to keep track of the lexing state and the various pieces of info
+ * needed for each particular state.
+ */
+typedef struct lex_state {
+	union {
+		/* point to the next state block */
+		struct lex_state *base;
+		/* marks start of state output in output string */
+		int start;
+		/* SBQUOTE: true if in double quotes: "`...`" */
+		/* SEQUOTE: got NUL, ignore rest of string */
+		bool abool;
+		/* SADELIM information */
+		struct {
+			/* character to search for */
+			unsigned char delimiter;
+			/* max. number of delimiters */
+			unsigned char num;
+		} adelim;
+	} u;
+	/* count open parentheses */
+	short nparen;
+	/* type of this state */
+	uint8_t type;
+} Lex_state;
+#define ls_base		u.base
+#define ls_start	u.start
+#define ls_bool		u.abool
+#define ls_adelim	u.adelim
+
 typedef struct {
 	Lex_state *base;
 	Lex_state *end;
 } State_info;
 
 static void readhere(struct ioword *);
-static int getsc__(void);
+static void ungetsc(int);
+static void ungetsc_(int);
+static int getsc_uu(void);
 static void getsc_line(Source *);
 static int getsc_bn(void);
 static int s_get(void);
 static void s_put(int);
 static char *get_brace_var(XString *, char *);
-static int arraysub(char **);
-static const char *ungetsc(int);
+static bool arraysub(char **);
 static void gethere(bool);
 static Lex_state *push_state_(State_info *, Lex_state *);
 static Lex_state *pop_state_(State_info *, Lex_state *);
 
 static int dopprompt(const char *, int, bool);
+void yyskiputf8bom(void);
 
 static int backslash_skip;
 static int ignore_backslash_newline;
+static struct sretrace_info *retrace_info;
+short subshell_nesting_level = 0;
 
 /* optimised getsc_bn() */
-#define _getsc()	(*source->str != '\0' && *source->str != '\\' \
-			 && !backslash_skip && !(source->flags & SF_FIRST) \
-			 ? *source->str++ : getsc_bn())
-/* optimised getsc__() */
-#define	_getsc_()	((*source->str != '\0') && !(source->flags & SF_FIRST) \
-			 ? *source->str++ : getsc__())
+#define o_getsc()	(*source->str != '\0' && *source->str != '\\' && \
+			    !backslash_skip ? *source->str++ : getsc_bn())
+/* optimised getsc_uu() */
+#define	o_getsc_u()	((*source->str != '\0') ? *source->str++ : getsc_uu())
+
+/* retrace helper */
+#define o_getsc_r(carg)	{				\
+	int cev = (carg);				\
+	struct sretrace_info *rp = retrace_info;	\
+							\
+	while (rp) {					\
+		Xcheck(rp->xs, rp->xp);			\
+		*rp->xp++ = cev;			\
+		rp = rp->next;				\
+	}						\
+							\
+	return (cev);					\
+}
 
 #ifdef MKSH_SMALL
 static int getsc(void);
-static int getsc_(void);
 
 static int
 getsc(void)
 {
-	return (_getsc());
-}
-
-static int
-getsc_(void)
-{
-	return (_getsc_());
+	o_getsc_r(o_getsc());
 }
 #else
-/* !MKSH_SMALL: use them inline */
-#define getsc()		_getsc()
-#define getsc_()	_getsc_()
+static int getsc_r(int);
+
+static int
+getsc_r(int c)
+{
+	o_getsc_r(c);
+}
+
+#define getsc()		getsc_r(o_getsc())
 #endif
 
-#define STATE_BSIZE	32
+#define STATE_BSIZE	8
 
 #define PUSH_STATE(s)	do {					\
 	if (++statep == state_info.end)				\
 		statep = push_state_(&state_info, statep);	\
-	state = statep->ls_state = (s);				\
-} while (0)
+	state = statep->type = (s);				\
+} while (/* CONSTCOND */ 0)
 
 #define POP_STATE()	do {					\
 	if (--statep == state_info.base)			\
 		statep = pop_state_(&state_info, statep);	\
-	state = statep->ls_state;				\
-} while (0)
+	state = statep->type;					\
+} while (/* CONSTCOND */ 0)
+
+#define PUSH_SRETRACE()	do {					\
+	struct sretrace_info *ri;				\
+								\
+	statep->ls_start = Xsavepos(ws, wp);			\
+	ri = alloc(sizeof(struct sretrace_info), ATEMP);	\
+	Xinit(ri->xs, ri->xp, 64, ATEMP);			\
+	ri->next = retrace_info;				\
+	retrace_info = ri;					\
+} while (/* CONSTCOND */ 0)
+
+#define POP_SRETRACE()	do {					\
+	wp = Xrestpos(ws, wp, statep->ls_start);		\
+	*retrace_info->xp = '\0';				\
+	sp = Xstring(retrace_info->xs, retrace_info->xp);	\
+	dp = (void *)retrace_info;				\
+	retrace_info = retrace_info->next;			\
+	afree(dp, ATEMP);					\
+} while (/* CONSTCOND */ 0)
 
 /**
  * Lexical analyser
  *
  * tokens are not regular expressions, they are LL(1).
  * for example, "${var:-${PWD}}", and "$(size $(whence ksh))".
- * hence the state stack.
+ * hence the state stack. Note "$(...)" are now parsed recursively.
  */
 
 int
@@ -188,13 +197,14 @@
 	Lex_state states[STATE_BSIZE], *statep, *s2, *base;
 	State_info state_info;
 	int c, c2, state;
+	size_t cz;
 	XString ws;		/* expandable output word */
 	char *wp;		/* output word pointer */
 	char *sp, *dp;
 
  Again:
-	states[0].ls_state = -1;
-	states[0].ls_info.base = NULL;
+	states[0].type = SINVALID;
+	states[0].ls_base = NULL;
 	statep = &states[1];
 	state_info.base = states;
 	state_info.end = &state_info.base[STATE_BSIZE];
@@ -204,19 +214,15 @@
 	backslash_skip = 0;
 	ignore_backslash_newline = 0;
 
-	if (cf&ONEWORD)
+	if (cf & ONEWORD)
 		state = SWORD;
-	else if (cf&LETEXPR) {
+	else if (cf & LETEXPR) {
 		/* enclose arguments in (double) quotes */
 		*wp++ = OQUOTE;
 		state = SLETPAREN;
-		statep->ls_sletparen.nparen = 0;
-#ifndef MKSH_SMALL
-	} else if (cf&LETARRAY) {
-		state = SLETARRAY;
-		statep->ls_sletarray.nparen = 0;
-#endif
-	} else {		/* normal lexing */
+		statep->nparen = 0;
+	} else {
+		/* normal lexing */
 		state = (cf & HEREDELIM) ? SHEREDELIM : SBASE;
 		while ((c = getsc()) == ' ' || c == '\t')
 			;
@@ -228,13 +234,14 @@
 		}
 		ungetsc(c);
 	}
-	if (source->flags & SF_ALIAS) {	/* trailing ' ' in alias definition */
+	if (source->flags & SF_ALIAS) {
+		/* trailing ' ' in alias definition */
 		source->flags &= ~SF_ALIAS;
 		cf |= ALIAS;
 	}
 
-	/* Initial state: one of SBASE SHEREDELIM SWORD SASPAREN */
-	statep->ls_state = state;
+	/* Initial state: one of SWORD SLETPAREN SHEREDELIM SBASE */
+	statep->type = state;
 
 	/* check for here string */
 	if (state == SHEREDELIM) {
@@ -259,14 +266,14 @@
 		switch (state) {
 		case SADELIM:
 			if (c == '(')
-				statep->ls_sadelim.nparen++;
+				statep->nparen++;
 			else if (c == ')')
-				statep->ls_sadelim.nparen--;
-			else if (statep->ls_sadelim.nparen == 0 &&
-			    (c == /*{*/ '}' || c == statep->ls_sadelim.delimiter)) {
+				statep->nparen--;
+			else if (statep->nparen == 0 &&
+			    (c == /*{*/ '}' || c == statep->ls_adelim.delimiter)) {
 				*wp++ = ADELIM;
 				*wp++ = c;
-				if (c == /*{*/ '}' || --statep->ls_sadelim.num == 0)
+				if (c == /*{*/ '}' || --statep->ls_adelim.num == 0)
 					POP_STATE();
 				if (c == /*{*/ '}')
 					POP_STATE();
@@ -275,7 +282,8 @@
 			/* FALLTHROUGH */
 		case SBASE:
 			if (c == '[' && (cf & (VARASN|ARRAYVAR))) {
-				*wp = EOS;	/* temporary */
+				/* temporary */
+				*wp = EOS;
 				if (is_wdvarname(Xstring(ws, wp), false)) {
 					char *p, *tmp;
 
@@ -378,17 +386,20 @@
 				if (c == '(') /*)*/ {
 					c = getsc();
 					if (c == '(') /*)*/ {
-						PUSH_STATE(SASPAREN);
-						statep->ls_sasparen.nparen = 2;
-						statep->ls_sasparen.start =
-						    Xsavepos(ws, wp);
 						*wp++ = EXPRSUB;
+						PUSH_STATE(SASPAREN);
+						statep->nparen = 2;
+						PUSH_SRETRACE();
+						*retrace_info->xp++ = '(';
 					} else {
 						ungetsc(c);
-						PUSH_STATE(SCSPAREN);
-						statep->ls_scsparen.nparen = 1;
-						statep->ls_scsparen.csstate = 0;
+ subst_command:
+						sp = yyrecursive();
+						cz = strlen(sp) + 1;
+						XcheckN(ws, wp, cz);
 						*wp++ = COMSUB;
+						memcpy(wp, sp, cz);
+						wp += cz;
 					}
 				} else if (c == '{') /*}*/ {
 					*wp++ = OSUBST;
@@ -407,14 +418,14 @@
 							*wp++ = ':';
 							PUSH_STATE(SBRACE);
 							PUSH_STATE(SADELIM);
-							statep->ls_sadelim.style = SADELIM_BASH;
-							statep->ls_sadelim.delimiter = ':';
-							statep->ls_sadelim.num = 1;
-							statep->ls_sadelim.nparen = 0;
+							statep->ls_adelim.delimiter = ':';
+							statep->ls_adelim.num = 1;
+							statep->nparen = 0;
 							break;
 						} else if (ksh_isdigit(c) ||
 						    c == '('/*)*/ || c == ' ' ||
-						    c == '$' /* XXX what else? */) {
+						    /*XXX what else? */
+						    c == '$') {
 							/* substring subst. */
 							if (c != ' ') {
 								*wp++ = CHAR;
@@ -423,10 +434,9 @@
 							ungetsc(c);
 							PUSH_STATE(SBRACE);
 							PUSH_STATE(SADELIM);
-							statep->ls_sadelim.style = SADELIM_BASH;
-							statep->ls_sadelim.delimiter = ':';
-							statep->ls_sadelim.num = 2;
-							statep->ls_sadelim.nparen = 0;
+							statep->ls_adelim.delimiter = ':';
+							statep->ls_adelim.num = 2;
+							statep->nparen = 0;
 							break;
 						}
 					} else if (c == '/') {
@@ -439,18 +449,21 @@
 							ungetsc(c);
 						PUSH_STATE(SBRACE);
 						PUSH_STATE(SADELIM);
-						statep->ls_sadelim.style = SADELIM_BASH;
-						statep->ls_sadelim.delimiter = '/';
-						statep->ls_sadelim.num = 1;
-						statep->ls_sadelim.nparen = 0;
+						statep->ls_adelim.delimiter = '/';
+						statep->ls_adelim.num = 1;
+						statep->nparen = 0;
 						break;
 					}
-					/* If this is a trim operation,
+					/*
+					 * If this is a trim operation,
 					 * treat (,|,) specially in STBRACE.
 					 */
 					if (ctype(c, C_SUBOP2)) {
 						ungetsc(c);
-						PUSH_STATE(STBRACE);
+						if (Flag(FSH))
+							PUSH_STATE(STBRACEBOURNE);
+						else
+							PUSH_STATE(STBRACEKORN);
 					} else {
 						ungetsc(c);
 						if (state == SDQUOTE)
@@ -483,11 +496,15 @@
 					*wp++ = OQUOTE;
 					ignore_backslash_newline++;
 					PUSH_STATE(SEQUOTE);
-					statep->ls_sequote.got_NUL = false;
+					statep->ls_bool = false;
 					break;
+				} else if (c == '"' && (state == SBASE)) {
+					/* XXX which other states are valid? */
+					goto DEQUOTE;
 				} else {
 					*wp++ = CHAR;
 					*wp++ = '$';
+ DEQUOTE:
 					ungetsc(c);
 				}
 				break;
@@ -495,7 +512,8 @@
  subst_gravis:
 				PUSH_STATE(SBQUOTE);
 				*wp++ = COMSUB;
-				/* Need to know if we are inside double quotes
+				/*
+				 * Need to know if we are inside double quotes
 				 * since sh/AT&T-ksh translate the \" to " in
 				 * "`...\"...`".
 				 * This is not done in POSIX mode (section
@@ -515,19 +533,19 @@
 				 * literal meaning, except when followed by
 				 * $ ` \.").
 				 */
-				statep->ls_sbquote.indquotes = 0;
+				statep->ls_bool = false;
 				s2 = statep;
 				base = state_info.base;
-				while (1) {
+				while (/* CONSTCOND */ 1) {
 					for (; s2 != base; s2--) {
-						if (s2->ls_state == SDQUOTE) {
-							statep->ls_sbquote.indquotes = 1;
+						if (s2->type == SDQUOTE) {
+							statep->ls_bool = true;
 							break;
 						}
 					}
 					if (s2 != base)
 						break;
-					if (!(s2 = s2->ls_info.base))
+					if (!(s2 = s2->ls_base))
 						break;
 					base = s2-- - STATE_BSIZE;
 				}
@@ -555,23 +573,23 @@
 				if ((c2 = unbksl(true, s_get, s_put)) == -1)
 					c2 = s_get();
 				if (c2 == 0)
-					statep->ls_sequote.got_NUL = true;
-				if (!statep->ls_sequote.got_NUL) {
+					statep->ls_bool = true;
+				if (!statep->ls_bool) {
 					char ts[4];
 
 					if ((unsigned int)c2 < 0x100) {
 						*wp++ = QCHAR;
 						*wp++ = c2;
 					} else {
-						c = utf_wctomb(ts, c2 - 0x100);
-						ts[c] = 0;
-						for (c = 0; ts[c]; ++c) {
+						cz = utf_wctomb(ts, c2 - 0x100);
+						ts[cz] = 0;
+						for (cz = 0; ts[cz]; ++cz) {
 							*wp++ = QCHAR;
-							*wp++ = ts[c];
+							*wp++ = ts[cz];
 						}
 					}
 				}
-			} else if (!statep->ls_sequote.got_NUL) {
+			} else if (!statep->ls_bool) {
 				*wp++ = QCHAR;
 				*wp++ = c;
 			}
@@ -596,97 +614,47 @@
 				goto Subst;
 			break;
 
-		case SCSPAREN:	/* $( ... ) */
-			/* todo: deal with $(...) quoting properly
-			 * kludge to partly fake quoting inside $(...): doesn't
-			 * really work because nested $(...) or ${...} inside
-			 * double quotes aren't dealt with.
-			 */
-			switch (statep->ls_scsparen.csstate) {
-			case 0:	/* normal */
-				switch (c) {
-				case '(':
-					statep->ls_scsparen.nparen++;
-					break;
-				case ')':
-					statep->ls_scsparen.nparen--;
-					break;
-				case '\\':
-					statep->ls_scsparen.csstate = 1;
-					break;
-				case '"':
-					statep->ls_scsparen.csstate = 2;
-					break;
-				case '\'':
-					statep->ls_scsparen.csstate = 4;
-					ignore_backslash_newline++;
-					break;
-				}
-				break;
-
-			case 1:	/* backslash in normal mode */
-			case 3:	/* backslash in double quotes */
-				--statep->ls_scsparen.csstate;
-				break;
-
-			case 2:	/* double quotes */
-				if (c == '"')
-					statep->ls_scsparen.csstate = 0;
-				else if (c == '\\')
-					statep->ls_scsparen.csstate = 3;
-				break;
-
-			case 4:	/* single quotes */
-				if (c == '\'') {
-					statep->ls_scsparen.csstate = 0;
-					ignore_backslash_newline--;
-				}
-				break;
-			}
-			if (statep->ls_scsparen.nparen == 0) {
-				POP_STATE();
-				*wp++ = 0;	/* end of COMSUB */
-			} else
-				*wp++ = c;
-			break;
-
-		case SASPAREN:	/* $(( ... )) */
-			/* XXX should nest using existing state machine
-			 * (embed "...", $(...), etc.) */
+		/* $(( ... )) */
+		case SASPAREN:
 			if (c == '(')
-				statep->ls_sasparen.nparen++;
+				statep->nparen++;
 			else if (c == ')') {
-				statep->ls_sasparen.nparen--;
-				if (statep->ls_sasparen.nparen == 1) {
-					/*(*/
-					if ((c2 = getsc()) == ')') {
-						POP_STATE();
-						/* end of EXPRSUB */
-						*wp++ = 0;
+				statep->nparen--;
+				if (statep->nparen == 1) {
+					/* end of EXPRSUB */
+					POP_SRETRACE();
+					POP_STATE();
+
+					if ((c2 = getsc()) == /*(*/ ')') {
+						cz = strlen(sp) - 2;
+						XcheckN(ws, wp, cz);
+						memcpy(wp, sp + 1, cz);
+						wp += cz;
+						afree(sp, ATEMP);
+						*wp++ = '\0';
 						break;
 					} else {
-						char *s;
+						Source *s;
 
 						ungetsc(c2);
-						/* mismatched parenthesis -
+						/*
+						 * mismatched parenthesis -
 						 * assume we were really
 						 * parsing a $(...) expression
 						 */
-						s = Xrestpos(ws, wp,
-						    statep->ls_sasparen.start);
-						memmove(s + 1, s, wp - s);
-						*s++ = COMSUB;
-						*s = '('; /*)*/
-						wp++;
-						statep->ls_scsparen.nparen = 1;
-						statep->ls_scsparen.csstate = 0;
-						state = statep->ls_state =
-						    SCSPAREN;
+						--wp;
+						s = pushs(SREREAD,
+						    source->areap);
+						s->start = s->str =
+						    s->u.freeme = sp;
+						s->next = source;
+						source = s;
+						goto subst_command;
 					}
 				}
 			}
-			*wp++ = c;
-			break;
+			/* reuse existing state machine */
+			goto Sbase2;
 
 		case SQBRACE:
 			if (c == '\\') {
@@ -724,18 +692,21 @@
 			*wp++ = /*{*/ '}';
 			break;
 
-		case STBRACE:
-			/* Same as SBASE, except (,|,) treated specially */
-			if (c == /*{*/ '}') {
+		/* Same as SBASE, except (,|,) treated specially */
+		case STBRACEKORN:
+			if (c == '|')
+				*wp++ = SPAT;
+			else if (c == '(') {
+				*wp++ = OPAT;
+				/* simile for @ */
+				*wp++ = ' ';
+				PUSH_STATE(SPATTERN);
+			} else /* FALLTHROUGH */
+		case STBRACEBOURNE:
+			  if (c == /*{*/ '}') {
 				POP_STATE();
 				*wp++ = CSUBST;
 				*wp++ = /*{*/ '}';
-			} else if (c == '|') {
-				*wp++ = SPAT;
-			} else if (c == '(') {
-				*wp++ = OPAT;
-				*wp++ = ' ';	/* simile for @ */
-				PUSH_STATE(SPATTERN);
 			} else
 				goto Sbase1;
 			break;
@@ -746,36 +717,37 @@
 				POP_STATE();
 			} else if (c == '\\') {
 				switch (c = getsc()) {
+				case 0:
+					/* trailing \ is lost */
+					break;
 				case '\\':
 				case '$': case '`':
 					*wp++ = c;
 					break;
 				case '"':
-					if (statep->ls_sbquote.indquotes) {
+					if (statep->ls_bool) {
 						*wp++ = c;
 						break;
 					}
 					/* FALLTHROUGH */
 				default:
-					if (c) {
-						/* trailing \ is lost */
-						*wp++ = '\\';
-						*wp++ = c;
-					}
+					*wp++ = '\\';
+					*wp++ = c;
 					break;
 				}
 			} else
 				*wp++ = c;
 			break;
 
-		case SWORD:	/* ONEWORD */
+		/* ONEWORD */
+		case SWORD:
 			goto Subst;
 
-		case SLETPAREN:	/* LETEXPR: (( ... )) */
-			/*(*/
-			if (c == ')') {
-				if (statep->ls_sletparen.nparen > 0)
-					--statep->ls_sletparen.nparen;
+		/* LETEXPR: (( ... )) */
+		case SLETPAREN:
+			if (c == /*(*/ ')') {
+				if (statep->nparen > 0)
+					--statep->nparen;
 				else if ((c2 = getsc()) == /*(*/ ')') {
 					c = 0;
 					*wp++ = CQUOTE;
@@ -784,13 +756,14 @@
 					Source *s;
 
 					ungetsc(c2);
-					/* mismatched parenthesis -
+					/*
+					 * mismatched parenthesis -
 					 * assume we were really
-					 * parsing a $(...) expression
+					 * parsing a (...) expression
 					 */
 					*wp = EOS;
 					sp = Xstring(ws, wp);
-					dp = wdstrip(sp, true, false);
+					dp = wdstrip(sp, WDS_KEEPQ);
 					s = pushs(SREREAD, source->areap);
 					s->start = s->str = s->u.freeme = dp;
 					s->next = source;
@@ -798,28 +771,16 @@
 					return ('('/*)*/);
 				}
 			} else if (c == '(')
-				/* parenthesis inside quotes and backslashes
-				 * are lost, but AT&T ksh doesn't count them
-				 * either
+				/*
+				 * parentheses inside quotes and
+				 * backslashes are lost, but AT&T ksh
+				 * doesn't count them either
 				 */
-				++statep->ls_sletparen.nparen;
+				++statep->nparen;
 			goto Sbase2;
 
-#ifndef MKSH_SMALL
-		case SLETARRAY:	/* LETARRAY: =( ... ) */
-			if (c == '('/*)*/)
-				++statep->ls_sletarray.nparen;
-			else if (c == /*(*/')')
-				if (statep->ls_sletarray.nparen-- == 0) {
-					c = 0;
-					goto Done;
-				}
-			*wp++ = CHAR;
-			*wp++ = c;
-			break;
-#endif
-
-		case SHERESTRING:	/* <<< delimiter */
+		/* <<< delimiter */
+		case SHERESTRING:
 			if (c == '\\') {
 				c = getsc();
 				if (c) {
@@ -827,14 +788,13 @@
 					*wp++ = QCHAR;
 					*wp++ = c;
 				}
-				/* invoke quoting mode */
-				Xstring(ws, wp)[0] = QCHAR;
 			} else if (c == '$') {
 				if ((c2 = getsc()) == '\'') {
 					PUSH_STATE(SEQUOTE);
-					statep->ls_sequote.got_NUL = false;
+					statep->ls_bool = false;
 					goto sherestring_quoted;
-				}
+				} else if (c2 == '"')
+					goto sherestring_dquoted;
 				ungetsc(c2);
 				goto sherestring_regular;
 			} else if (c == '\'') {
@@ -842,10 +802,9 @@
  sherestring_quoted:
 				*wp++ = OQUOTE;
 				ignore_backslash_newline++;
-				/* invoke quoting mode */
-				Xstring(ws, wp)[0] = QCHAR;
 			} else if (c == '"') {
-				state = statep->ls_state = SHEREDQUOTE;
+ sherestring_dquoted:
+				state = statep->type = SHEREDQUOTE;
 				*wp++ = OQUOTE;
 				/* just don't IFS split; no quoting mode */
 			} else {
@@ -855,13 +814,16 @@
 			}
 			break;
 
-		case SHEREDELIM:	/* <<,<<- delimiter */
-			/* XXX chuck this state (and the next) - use
+		/* <<,<<- delimiter */
+		case SHEREDELIM:
+			/*
+			 * XXX chuck this state (and the next) - use
 			 * the existing states ($ and \`...` should be
 			 * stripped of their specialness after the
 			 * fact).
 			 */
-			/* here delimiters need a special case since
+			/*
+			 * here delimiters need a special case since
 			 * $ and `...` are not to be treated specially
 			 */
 			if (c == '\\') {
@@ -874,9 +836,10 @@
 			} else if (c == '$') {
 				if ((c2 = getsc()) == '\'') {
 					PUSH_STATE(SEQUOTE);
-					statep->ls_sequote.got_NUL = false;
+					statep->ls_bool = false;
 					goto sheredelim_quoted;
-				}
+				} else if (c2 == '"')
+					goto sheredelim_dquoted;
 				ungetsc(c2);
 				goto sheredelim_regular;
 			} else if (c == '\'') {
@@ -885,7 +848,8 @@
 				*wp++ = OQUOTE;
 				ignore_backslash_newline++;
 			} else if (c == '"') {
-				state = statep->ls_state = SHEREDQUOTE;
+ sheredelim_dquoted:
+				state = statep->type = SHEREDQUOTE;
 				*wp++ = OQUOTE;
 			} else {
  sheredelim_regular:
@@ -894,25 +858,27 @@
 			}
 			break;
 
-		case SHEREDQUOTE:	/* " in <<,<<- delimiter */
+		/* " in <<,<<- delimiter */
+		case SHEREDQUOTE:
 			if (c == '"') {
 				*wp++ = CQUOTE;
-				state = statep->ls_state =
+				state = statep->type =
 				    /* dp[1] == '<' means here string */
 				    Xstring(ws, wp)[1] == '<' ?
 				    SHERESTRING : SHEREDELIM;
 			} else {
 				if (c == '\\') {
 					switch (c = getsc()) {
-					case '\\': case '"':
-					case '$': case '`':
+					case 0:
+						/* trailing \ is lost */
+					case '\\':
+					case '"':
+					case '$':
+					case '`':
 						break;
 					default:
-						if (c) {
-							/* trailing \ lost */
-							*wp++ = CHAR;
-							*wp++ = '\\';
-						}
+						*wp++ = CHAR;
+						*wp++ = '\\';
 						break;
 					}
 				}
@@ -921,15 +887,17 @@
 			}
 			break;
 
-		case SPATTERN:	/* in *(...|...) pattern (*+?@!) */
-			if ( /*(*/ c == ')') {
+		/* in *(...|...) pattern (*+?@!) */
+		case SPATTERN:
+			if (c == /*(*/ ')') {
 				*wp++ = CPAT;
 				POP_STATE();
 			} else if (c == '|') {
 				*wp++ = SPAT;
 			} else if (c == '(') {
 				*wp++ = OPAT;
-				*wp++ = ' ';	/* simile for @ */
+				/* simile for @ */
+				*wp++ = ' ';
 				PUSH_STATE(SPATTERN);
 			} else
 				goto Sbase1;
@@ -942,11 +910,6 @@
 		/* XXX figure out what is missing */
 		yyerror("no closing quote\n");
 
-#ifndef MKSH_SMALL
-	if (state == SLETARRAY && statep->ls_sletarray.nparen != -1)
-		yyerror("%s: ')' missing\n", T_synerr);
-#endif
-
 	/* This done to avoid tests for SHEREDELIM wherever SBASE tested */
 	if (state == SHEREDELIM || state == SHERESTRING)
 		state = SBASE;
@@ -984,10 +947,14 @@
 			iop->flag |= c == c2 ?
 			    (c == '>' ? IOCAT : IOHERE) : IORDWR;
 			if (iop->flag == IOHERE) {
-				if ((c2 = getsc()) == '-')
+				if ((c2 = getsc()) == '-') {
 					iop->flag |= IOSKIP;
-				else
-					ungetsc(c2);
+					c2 = getsc();
+				} else if (c2 == '<')
+					iop->flag |= IOHERESTR;
+				ungetsc(c2);
+				if (c2 == '\n')
+					iop->flag |= IONDELIM;
 			}
 		} else if (c2 == '&')
 			iop->flag |= IODUP | (c == '<' ? IORDUP : 0);
@@ -1002,15 +969,17 @@
 		iop->name = NULL;
 		iop->delim = NULL;
 		iop->heredoc = NULL;
-		Xfree(ws, wp);	/* free word */
+		/* free word */
+		Xfree(ws, wp);
 		yylval.iop = iop;
 		return (REDIR);
  no_iop:
-		;
+		afree(iop, ATEMP);
 	}
 
 	if (wp == dp && state == SBASE) {
-		Xfree(ws, wp);	/* free word */
+		/* free word */
+		Xfree(ws, wp);
 		/* no word, process LEX1 character */
 		if ((c == '|') || (c == '&') || (c == ';') || (c == '('/*)*/)) {
 			if ((c2 = getsc()) == c)
@@ -1020,8 +989,20 @@
 				    /* c == '(' ) */ MDPAREN;
 			else if (c == '|' && c2 == '&')
 				c = COPROC;
+			else if (c == ';' && c2 == '|')
+				c = BRKEV;
+			else if (c == ';' && c2 == '&')
+				c = BRKFT;
 			else
 				ungetsc(c2);
+#ifndef MKSH_SMALL
+			if (c == BREAK) {
+				if ((c2 = getsc()) == '&')
+					c = BRKEV;
+				else
+					ungetsc(c2);
+			}
+#endif
 		} else if (c == '\n') {
 			gethere(false);
 			if (cf & CONTIN)
@@ -1032,14 +1013,11 @@
 		return (c);
 	}
 
-	*wp++ = EOS;		/* terminate word */
+	/* terminate word */
+	*wp++ = EOS;
 	yylval.cp = Xclose(ws, wp);
 	if (state == SWORD || state == SLETPAREN
-	    /* XXX ONEWORD? */
-#ifndef MKSH_SMALL
-	    || state == SLETARRAY
-#endif
-	    )
+	    /* XXX ONEWORD? */)
 		return (LWORD);
 
 	/* unget terminator */
@@ -1067,15 +1045,16 @@
 	/* Make sure the ident array stays '\0' padded */
 	memset(dp, 0, (ident+IDENT) - dp + 1);
 	if (c != EOS)
-		*ident = '\0';	/* word is not unquoted */
+		/* word is not unquoted */
+		*ident = '\0';
 
-	if (*ident != '\0' && (cf&(KEYWORD|ALIAS))) {
+	if (*ident != '\0' && (cf & (KEYWORD | ALIAS))) {
 		struct tbl *p;
 		uint32_t h = hash(ident);
 
-		/* { */
 		if ((cf & KEYWORD) && (p = ktsearch(&keywords, ident, h)) &&
-		    (!(cf & ESACONLY) || p->val.i == ESAC || p->val.i == '}')) {
+		    (!(cf & ESACONLY) || p->val.i == ESAC ||
+		    p->val.i == /*{*/ '}')) {
 			afree(yylval.cp, ATEMP);
 			return (p->val.i);
 		}
@@ -1101,13 +1080,7 @@
 				 */
 				++cp;
 			/* prefer functions over aliases */
-			if (*cp == '(' /*)*/)
-				/*
-				 * delete alias upon encountering function
-				 * definition
-				 */
-				ktdelete(p);
-			else {
+			if (cp[0] != '(' || cp[1] != ')') {
 				Source *s = source;
 
 				while (s && (s->flags & SF_HASALIAS))
@@ -1142,7 +1115,7 @@
 	struct ioword **p;
 
 	for (p = heres; p < herep; p++)
-		if (iseof && (*p)->delim[1] != '<')
+		if (iseof && !((*p)->flag & IOHERESTR))
 			/* only here strings at EOF */
 			return;
 		else
@@ -1158,62 +1131,90 @@
 readhere(struct ioword *iop)
 {
 	int c;
-	char *volatile eof;
-	char *eofp;
-	int skiptabs;
+	const char *eof, *eofp;
 	XString xs;
 	char *xp;
 	int xpos;
 
-	if (iop->delim[1] == '<') {
+	if (iop->flag & IOHERESTR) {
 		/* process the here string */
-		xp = iop->heredoc = evalstr(iop->delim, DOBLANK);
-		c = strlen(xp) - 1;
-		memmove(xp, xp + 1, c);
-		xp[c] = '\n';
+		iop->heredoc = xp = evalstr(iop->delim, DOBLANK);
+		xpos = strlen(xp) - 1;
+		memmove(xp, xp + 1, xpos);
+		xp[xpos] = '\n';
 		return;
 	}
 
-	eof = evalstr(iop->delim, 0);
+	eof = iop->flag & IONDELIM ? "<<" : evalstr(iop->delim, 0);
 
 	if (!(iop->flag & IOEVAL))
 		ignore_backslash_newline++;
 
 	Xinit(xs, xp, 256, ATEMP);
 
-	for (;;) {
-		eofp = eof;
-		skiptabs = iop->flag & IOSKIP;
-		xpos = Xsavepos(xs, xp);
-		while ((c = getsc()) != 0) {
-			if (skiptabs) {
-				if (c == '\t')
-					continue;
-				skiptabs = 0;
-			}
-			if (c != *eofp)
+ heredoc_read_line:
+	/* beginning of line */
+	eofp = eof;
+	xpos = Xsavepos(xs, xp);
+	if (iop->flag & IOSKIP) {
+		/* skip over leading tabs */
+		while ((c = getsc()) == '\t')
+			/* nothing */;
+		goto heredoc_parse_char;
+	}
+ heredoc_read_char:
+	c = getsc();
+ heredoc_parse_char:
+	/* compare with here document marker */
+	if (!*eofp) {
+		/* end of here document marker, what to do? */
+		switch (c) {
+		case /*(*/ ')':
+			if (!subshell_nesting_level)
+				/*-
+				 * not allowed outside $(...) or (...)
+				 * => mismatch
+				 */
 				break;
-			Xcheck(xs, xp);
-			Xput(xs, xp, c);
-			eofp++;
+			/* allow $(...) or (...) to close here */
+			ungetsc(/*(*/ ')');
+			/* FALLTHROUGH */
+		case 0:
+			/*
+			 * Allow EOF here to commands without trailing
+			 * newlines (mksh -c '...') will work as well.
+			 */
+		case '\n':
+			/* Newline terminates here document marker */
+			goto heredoc_found_terminator;
 		}
-		/* Allow EOF here so commands with out trailing newlines
-		 * will work (eg, ksh -c '...', $(...), etc).
-		 */
-		if (*eofp == '\0' && (c == 0 || c == '\n')) {
-			xp = Xrestpos(xs, xp, xpos);
-			break;
-		}
-		ungetsc(c);
-		while ((c = getsc()) != '\n') {
-			if (c == 0)
-				yyerror("here document '%s' unclosed\n", eof);
-			Xcheck(xs, xp);
-			Xput(xs, xp, c);
-		}
+	} else if (c == *eofp++)
+		/* store; then read and compare next character */
+		goto heredoc_store_and_loop;
+	/* nope, mismatch; read until end of line */
+	while (c != '\n') {
+		if (!c)
+			/* oops, reached EOF */
+			yyerror("%s '%s' unclosed\n", "here document", eof);
+		/* store character */
 		Xcheck(xs, xp);
 		Xput(xs, xp, c);
+		/* read next character */
+		c = getsc();
 	}
+	/* we read a newline as last character */
+ heredoc_store_and_loop:
+	/* store character */
+	Xcheck(xs, xp);
+	Xput(xs, xp, c);
+	if (c == '\n')
+		goto heredoc_read_line;
+	goto heredoc_read_char;
+
+ heredoc_found_terminator:
+	/* jump back to saved beginning of line */
+	xp = Xrestpos(xs, xp, xpos);
+	/* terminate, close and store */
 	Xput(xs, xp, '\0');
 	iop->heredoc = Xclose(xs, xp);
 
@@ -1229,7 +1230,8 @@
 	/* pop aliases and re-reads */
 	while (source->type == SALIAS || source->type == SREREAD)
 		source = source->next;
-	source->str = null;	/* zap pending input */
+	/* zap pending input */
+	source->str = null;
 
 	error_prefix(true);
 	va_start(va, fmt);
@@ -1258,14 +1260,14 @@
 }
 
 static int
-getsc__(void)
+getsc_uu(void)
 {
 	Source *s = source;
 	int c;
 
- getsc_again:
 	while ((c = *s->str++) == 0) {
-		s->str = NULL;		/* return 0 for EOF by default */
+		/* return 0 for EOF by default */
+		s->str = NULL;
 		switch (s->type) {
 		case SEOF:
 			s->str = null;
@@ -1305,25 +1307,28 @@
 				s = source;
 			} else if (*s->u.tblp->val.s &&
 			    (c = strnul(s->u.tblp->val.s)[-1], ksh_isspace(c))) {
-				source = s = s->next;	/* pop source stack */
-				/* Note that this alias ended with a space,
-				 * enabling alias expansion on the following
-				 * word.
+				/* pop source stack */
+				source = s = s->next;
+				/*
+				 * Note that this alias ended with a
+				 * space, enabling alias expansion on
+				 * the following word.
 				 */
 				s->flags |= SF_ALIAS;
 			} else {
-				/* At this point, we need to keep the current
+				/*
+				 * At this point, we need to keep the current
 				 * alias in the source list so recursive
-				 * aliases can be detected and we also need
-				 * to return the next character. Do this
-				 * by temporarily popping the alias to get
-				 * the next character and then put it back
-				 * in the source list with the SF_ALIASEND
-				 * flag set.
+				 * aliases can be detected and we also need to
+				 * return the next character. Do this by
+				 * temporarily popping the alias to get the
+				 * next character and then put it back in the
+				 * source list with the SF_ALIASEND flag set.
 				 */
-				source = s->next;	/* pop source stack */
+				/* pop source stack */
+				source = s->next;
 				source->flags |= s->flags & SF_ALIAS;
-				c = getsc__();
+				c = getsc_uu();
 				if (c) {
 					s->flags |= SF_ALIASEND;
 					s->ugbuf[0] = c; s->ugbuf[1] = '\0';
@@ -1332,7 +1337,7 @@
 					source = s;
 				} else {
 					s = source;
-					/* avoid reading eof twice */
+					/* avoid reading EOF twice */
 					s->str = NULL;
 					break;
 				}
@@ -1340,7 +1345,8 @@
 			continue;
 
 		case SREREAD:
-			if (s->start != s->ugbuf)	/* yuck */
+			if (s->start != s->ugbuf)
+				/* yuck */
 				afree(s->u.freeme, ATEMP);
 			source = s = s->next;
 			continue;
@@ -1355,17 +1361,6 @@
 			shf_flush(shl_out);
 		}
 	}
-	/* check for UTF-8 byte order mark */
-	if (s->flags & SF_FIRST) {
-		s->flags &= ~SF_FIRST;
-		if (((unsigned char)c == 0xEF) &&
-		    (((const unsigned char *)(s->str))[0] == 0xBB) &&
-		    (((const unsigned char *)(s->str))[1] == 0xBF)) {
-			s->str += 2;
-			UTFMODE = 1;
-			goto getsc_again;
-		}
-	}
 	return (c);
 }
 
@@ -1395,7 +1390,8 @@
 		int nread;
 
 		nread = x_read(xp, LINE);
-		if (nread < 0)	/* read error */
+		if (nread < 0)
+			/* read error */
 			nread = 0;
 		xp[nread] = '\0';
 		xp += nread;
@@ -1405,7 +1401,7 @@
 		else
 			s->line++;
 
-		while (1) {
+		while (/* CONSTCOND */ 1) {
 			char *p = shf_getse(xp, Xnleft(s->xs, xp), s->u.shf);
 
 			if (!p && shf_error(s->u.shf) &&
@@ -1418,20 +1414,24 @@
 			if (!p || (xp = p, xp[-1] == '\n'))
 				break;
 			/* double buffer size */
-			xp++;	/* move past NUL so doubling works... */
+			/* move past NUL so doubling works... */
+			xp++;
 			XcheckN(s->xs, xp, Xlength(s->xs, xp));
-			xp--;	/* ...and move back again */
+			/* ...and move back again */
+			xp--;
 		}
-		/* flush any unwanted input so other programs/builtins
+		/*
+		 * flush any unwanted input so other programs/builtins
 		 * can read it. Not very optimal, but less error prone
 		 * than flushing else where, dealing with redirections,
 		 * etc.
-		 * todo: reduce size of shf buffer (~128?) if SSTDIN
+		 * TODO: reduce size of shf buffer (~128?) if SSTDIN
 		 */
 		if (s->type == SSTDIN)
 			shf_flush(s->u.shf);
 	}
-	/* XXX: temporary kludge to restore source after a
+	/*
+	 * XXX: temporary kludge to restore source after a
 	 * trap may have been executed.
 	 */
 	source = s;
@@ -1445,7 +1445,7 @@
 		int linelen;
 
 		linelen = Xlength(s->xs, xp);
-		XcheckN(s->xs, xp, fc_e_n + /* NUL */ 1);
+		XcheckN(s->xs, xp, Zfc_e_dash + /* NUL */ 1);
 		/* reload after potential realloc */
 		cp = Xstring(s->xs, xp);
 		/* change initial '!' into space */
@@ -1453,10 +1453,10 @@
 		/* NUL terminate the current string */
 		*xp = '\0';
 		/* move the actual string forward */
-		memmove(cp + fc_e_n, cp, linelen + /* NUL */ 1);
-		xp += fc_e_n;
+		memmove(cp + Zfc_e_dash, cp, linelen + /* NUL */ 1);
+		xp += Zfc_e_dash;
 		/* prepend it with "fc -e -" */
-		memcpy(cp, fc_e_, fc_e_n);
+		memcpy(cp, Tfc_e_dash, Zfc_e_dash);
 	}
 #endif
 	s->start = s->str = cp;
@@ -1489,8 +1489,10 @@
 	cur_prompt = to;
 
 	switch (to) {
-	case PS1:	/* command */
-		/* Substitute ! and !! here, before substitutions are done
+	/* command */
+	case PS1:
+		/*
+		 * Substitute ! and !! here, before substitutions are done
 		 * so ! in expanded variables are not expanded.
 		 * NOTE: this is not what AT&T ksh does (it does it after
 		 * substitutions, POSIX doesn't say which is to be done.
@@ -1514,7 +1516,8 @@
 			newenv(E_ERRH);
 			if (sigsetjmp(e->jbuf, 0)) {
 				prompt = safe_prompt;
-				/* Don't print an error - assume it has already
+				/*
+				 * Don't print an error - assume it has already
 				 * been printed. Reason is we may have forked
 				 * to run a command and the child may be
 				 * unwinding its stack through this code as it
@@ -1527,7 +1530,8 @@
 			quitenv(NULL);
 		}
 		break;
-	case PS2:	/* command continuation */
+	/* command continuation */
+	case PS2:
 		prompt = str_val(global("PS2"));
 		break;
 	}
@@ -1539,11 +1543,12 @@
 	int columns = 0, lines = 0, indelimit = 0;
 	char delimiter = 0;
 
-	/* Undocumented AT&T ksh feature:
-	 * If the second char in the prompt string is \r then the first char
-	 * is taken to be a non-printing delimiter and any chars between two
-	 * instances of the delimiter are not considered to be part of the
-	 * prompt length
+	/*
+	 * Undocumented AT&T ksh feature:
+	 * If the second char in the prompt string is \r then the first
+	 * char is taken to be a non-printing delimiter and any chars
+	 * between two instances of the delimiter are not considered to
+	 * be part of the prompt length
 	 */
 	if (*cp && cp[1] == '\r') {
 		delimiter = *cp;
@@ -1594,20 +1599,20 @@
 	return (dopprompt(cp, 0, false));
 }
 
-/* Read the variable part of a ${...} expression (ie, up to but not including
- * the :[-+?=#%] or close-brace.
+/*
+ * Read the variable part of a ${...} expression (i.e. up to but not
+ * including the :[-+?=#%] or close-brace).
  */
 static char *
 get_brace_var(XString *wsp, char *wp)
 {
+	char c;
 	enum parse_state {
 		PS_INITIAL, PS_SAW_HASH, PS_IDENT,
 		PS_NUMBER, PS_VAR1
-	} state;
-	char c;
+	} state = PS_INITIAL;
 
-	state = PS_INITIAL;
-	while (1) {
+	while (/* CONSTCOND */ 1) {
 		c = getsc();
 		/* State machine to figure out where the variable part ends. */
 		switch (state) {
@@ -1622,7 +1627,19 @@
 				state = PS_IDENT;
 			else if (ksh_isdigit(c))
 				state = PS_NUMBER;
-			else if (ctype(c, C_VAR1))
+			else if (c == '#') {
+				if (state == PS_SAW_HASH) {
+					char c2;
+
+					c2 = getsc();
+					ungetsc(c2);
+					if (c2 != '}') {
+						ungetsc(c);
+						goto out;
+					}
+				}
+				state = PS_VAR1;
+			} else if (ctype(c, C_VAR1))
 				state = PS_VAR1;
 			else
 				goto out;
@@ -1640,7 +1657,8 @@
 						*wp++ = *p++;
 					}
 					afree(tmp, ATEMP);
-					c = getsc();	/* the ] */
+					/* the ] */
+					c = getsc();
 				}
 				goto out;
 			}
@@ -1656,7 +1674,8 @@
 		*wp++ = c;
 	}
  out:
-	*wp++ = '\0';	/* end of variable part */
+	/* end of variable part */
+	*wp++ = '\0';
 	ungetsc(c);
 	return (wp);
 }
@@ -1666,13 +1685,13 @@
  * if eof or newline was found.
  * (Returned string double null terminated)
  */
-static int
+static bool
 arraysub(char **strp)
 {
 	XString ws;
-	char	*wp;
-	char	c;
-	int	depth = 1;	/* we are just past the initial [ */
+	char *wp, c;
+	/* we are just past the initial [ */
+	int depth = 1;
 
 	Xinit(ws, wp, 32, ATEMP);
 
@@ -1689,18 +1708,30 @@
 	*wp++ = '\0';
 	*strp = Xclose(ws, wp);
 
-	return (depth == 0 ? 1 : 0);
+	return (tobool(depth == 0));
 }
 
 /* Unget a char: handles case when we are already at the start of the buffer */
-static const char *
+static void
 ungetsc(int c)
 {
+	struct sretrace_info *rp = retrace_info;
+
 	if (backslash_skip)
 		backslash_skip--;
-	/* Don't unget eof... */
+	/* Don't unget EOF... */
 	if (source->str == null && c == '\0')
-		return (source->str);
+		return;
+	while (rp) {
+		if (Xlength(rp->xs, rp->xp))
+			rp->xp--;
+		rp = rp->next;
+	}
+	ungetsc_(c);
+}
+static void
+ungetsc_(int c)
+{
 	if (source->str > source->start)
 		source->str--;
 	else {
@@ -1712,7 +1743,6 @@
 		s->next = source;
 		source = s;
 	}
-	return (source->str);
 }
 
 
@@ -1723,34 +1753,57 @@
 	int c, c2;
 
 	if (ignore_backslash_newline)
-		return (getsc_());
+		return (o_getsc_u());
 
 	if (backslash_skip == 1) {
 		backslash_skip = 2;
-		return (getsc_());
+		return (o_getsc_u());
 	}
 
 	backslash_skip = 0;
 
-	while (1) {
-		c = getsc_();
+	while (/* CONSTCOND */ 1) {
+		c = o_getsc_u();
 		if (c == '\\') {
-			if ((c2 = getsc_()) == '\n')
+			if ((c2 = o_getsc_u()) == '\n')
 				/* ignore the \newline; get the next char... */
 				continue;
-			ungetsc(c2);
+			ungetsc_(c2);
 			backslash_skip = 1;
 		}
 		return (c);
 	}
 }
 
+void
+yyskiputf8bom(void)
+{
+	int c;
+
+	if ((unsigned char)(c = o_getsc_u()) != 0xEF) {
+		ungetsc_(c);
+		return;
+	}
+	if ((unsigned char)(c = o_getsc_u()) != 0xBB) {
+		ungetsc_(c);
+		ungetsc_(0xEF);
+		return;
+	}
+	if ((unsigned char)(c = o_getsc_u()) != 0xBF) {
+		ungetsc_(c);
+		ungetsc_(0xBB);
+		ungetsc_(0xEF);
+		return;
+	}
+	UTFMODE |= 8;
+}
+
 static Lex_state *
 push_state_(State_info *si, Lex_state *old_end)
 {
-	Lex_state *news = alloc(STATE_BSIZE * sizeof(Lex_state), ATEMP);
+	Lex_state *news = alloc2(STATE_BSIZE, sizeof(Lex_state), ATEMP);
 
-	news[0].ls_info.base = old_end;
+	news[0].ls_base = old_end;
 	si->base = &news[0];
 	si->end = &news[STATE_BSIZE];
 	return (&news[1]);
@@ -1761,8 +1814,8 @@
 {
 	Lex_state *old_base = si->base;
 
-	si->base = old_end->ls_info.base - STATE_BSIZE;
-	si->end = old_end->ls_info.base;
+	si->base = old_end->ls_base - STATE_BSIZE;
+	si->end = old_end->ls_base;
 
 	afree(old_base, ATEMP);
 
diff --git a/src/main.c b/src/main.c
index f962dd4..b78965e 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1,10 +1,10 @@
-/*	$OpenBSD: main.c,v 1.46 2010/05/19 17:36:08 jasper Exp $	*/
+/*	$OpenBSD: main.c,v 1.47 2011/09/07 11:33:25 otto Exp $	*/
 /*	$OpenBSD: tty.c,v 1.9 2006/03/14 22:08:01 deraadt Exp $	*/
 /*	$OpenBSD: io.c,v 1.22 2006/03/17 16:30:13 millert Exp $	*/
 /*	$OpenBSD: table.c,v 1.13 2009/01/17 22:06:44 millert Exp $	*/
 
 /*-
- * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
  *	Thorsten Glaser <tg@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -23,7 +23,7 @@
  * of said person's immediate fault when using the work as intended.
  */
 
-#define	EXTERN
+#define EXTERN
 #include "sh.h"
 
 #if HAVE_LANGINFO_CODESET
@@ -33,15 +33,10 @@
 #include <locale.h>
 #endif
 
-__RCSID("$MirOS: src/bin/mksh/main.c,v 1.167 2010/07/04 17:45:15 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/main.c,v 1.200 2011/10/07 19:51:28 tg Exp $");
 
 extern char **environ;
 
-#if !HAVE_SETRESUGID
-extern uid_t kshuid;
-extern gid_t kshgid, kshegid;
-#endif
-
 #ifndef MKSHRC_PATH
 #define MKSHRC_PATH	"~/.mkshrc"
 #endif
@@ -50,10 +45,10 @@
 #define MKSH_DEFAULT_TMPDIR	"/tmp"
 #endif
 
+void chvt_reinit(void);
 static void reclaim(void);
 static void remove_temps(struct temp *);
-void chvt_reinit(void);
-Source *mksh_init(int, const char *[]);
+static mksh_uari_t rndsetup(void);
 #ifdef SIGWINCH
 static void x_sigwinch(int);
 #endif
@@ -64,16 +59,19 @@
     "${PS2=> } ${PS3=#? } ${PS4=+ } ${SECONDS=0} ${TMOUT=0}";
 
 static const char *initcoms[] = {
-	T_typeset, "-r", initvsn, NULL,
-	T_typeset, "-x", "HOME", "PATH", "RANDOM", "SHELL", NULL,
-	T_typeset, "-i10", "COLUMNS", "LINES", "OPTIND", "PGRP", "PPID",
-	    "RANDOM", "SECONDS", "TMOUT", "USER_ID", NULL,
-	"alias",
+	Ttypeset, "-r", initvsn, NULL,
+	Ttypeset, "-x", "HOME", "PATH", "RANDOM", "SHELL", NULL,
+	Ttypeset, "-i10", "SECONDS", "TMOUT", NULL,
+	Talias,
 	"integer=typeset -i",
-	T_local_typeset,
-	"hash=alias -t",	/* not "alias -t --": hash -r needs to work */
+	Tlocal_typeset,
+	/* not "alias -t --": hash -r needs to work */
+	"hash=alias -t",
 	"type=whence -v",
-#ifndef MKSH_UNEMPLOYED
+#if !defined(ANDROID) && !defined(MKSH_UNEMPLOYED)
+	/* not in Android for political reasons */
+	/* not in ARGE mksh due to no job control */
+	"stop=kill -STOP",
 	"suspend=kill -STOP $$",
 #endif
 	"autoload=typeset -fu",
@@ -81,20 +79,68 @@
 	"history=fc -l",
 	"nameref=typeset -n",
 	"nohup=nohup ",
-	r_fc_e_,
+	Tr_fc_e_dash,
 	"source=PATH=$PATH:. command .",
 	"login=exec login",
 	NULL,
 	 /* this is what AT&T ksh seems to track, with the addition of emacs */
-	"alias", "-tU",
+	Talias, "-tU",
 	"cat", "cc", "chmod", "cp", "date", "ed", "emacs", "grep", "ls",
 	"make", "mv", "pr", "rm", "sed", "sh", "vi", "who", NULL,
 	NULL
 };
 
+static const char *restr_com[] = {
+	Ttypeset, "-r", "PATH", "ENV", "SHELL", NULL
+};
+
 static int initio_done;
 
-struct env *e = &kshstate_v.env_;
+/* top-level parsing and execution environment */
+static struct env env;
+struct env *e = &env;
+
+static mksh_uari_t
+rndsetup(void)
+{
+	register uint32_t h;
+	struct {
+		ALLOC_ITEM alloc_INT;
+		void *dataptr, *stkptr, *mallocptr;
+		sigjmp_buf jbuf;
+		struct timeval tv;
+		struct timezone tz;
+	} *bufptr;
+	char *cp;
+
+	cp = alloc(sizeof(*bufptr) - ALLOC_SIZE, APERM);
+#ifdef DEBUG
+	/* clear the allocated space, for valgrind */
+	memset(cp, 0, sizeof(*bufptr) - ALLOC_SIZE);
+#endif
+	/* undo what alloc() did to the malloc result address */
+	bufptr = (void *)(cp - ALLOC_SIZE);
+	/* PIE or something similar provides us with deltas here */
+	bufptr->dataptr = &rndsetupstate;
+	/* ASLR in at least Windows, Linux, some BSDs */
+	bufptr->stkptr = &bufptr;
+	/* randomised malloc in BSD (and possibly others) */
+	bufptr->mallocptr = bufptr;
+	/* glibc pointer guard */
+	sigsetjmp(bufptr->jbuf, 1);
+	/* introduce variation */
+	gettimeofday(&bufptr->tv, &bufptr->tz);
+
+	NZATInit(h);
+	/* variation through pid, ppid, and the works */
+	NZATUpdateMem(h, &rndsetupstate, sizeof(rndsetupstate));
+	/* some variation, some possibly entropy, depending on OE */
+	NZATUpdateMem(h, bufptr, sizeof(*bufptr));
+	NZAATFinish(h);
+
+	afree(cp, APERM);
+	return ((mksh_uari_t)h);
+}
 
 void
 chvt_reinit(void)
@@ -105,19 +151,23 @@
 	kshppid = getppid();
 }
 
-Source *
-mksh_init(int argc, const char *argv[])
+static const char *empty_argv[] = {
+	"mksh", NULL
+};
+
+int
+main(int argc, const char *argv[])
 {
 	int argi, i;
-	Source *s;
+	Source *s = NULL;
 	struct block *l;
 	unsigned char restricted, errexit, utf_flag;
-	const char **wp;
+	char *cp;
+	const char *ccp, **wp;
 	struct tbl *vp;
 	struct stat s_stdin;
 #if !defined(_PATH_DEFPATH) && defined(_CS_PATH)
-	size_t k;
-	char *cp;
+	ssize_t k;
 #endif
 
 	/* do things like getpgrp() et al. */
@@ -125,28 +175,59 @@
 
 	/* make sure argv[] is sane */
 	if (!*argv) {
-		static const char *empty_argv[] = {
-			"mksh", NULL
-		};
-
 		argv = empty_argv;
 		argc = 1;
 	}
-	kshname = *argv;
+	kshname = argv[0];
 
-	ainit(&aperm);		/* initialise permanent Area */
+	/* initialise permanent Area */
+	ainit(&aperm);
 
 	/* set up base environment */
-	kshstate_v.env_.type = E_NONE;
-	ainit(&kshstate_v.env_.area);
-	newblock();		/* set up global l->vars and l->funs */
+	env.type = E_NONE;
+	ainit(&env.area);
+	/* set up global l->vars and l->funs */
+	newblock();
 
 	/* Do this first so output routines (eg, errorf, shellf) can work */
 	initio();
 
-	argi = parse_args(argv, OF_FIRSTTIME, NULL);
-	if (argi < 0)
-		return (NULL);
+	/* determine the basename (without '-' or path) of the executable */
+	ccp = kshname;
+	goto begin_parse_kshname;
+	while ((i = ccp[argi++])) {
+		if (i == '/') {
+			ccp += argi;
+ begin_parse_kshname:
+			argi = 0;
+			if (*ccp == '-')
+				++ccp;
+		}
+	}
+	if (!*ccp)
+		ccp = empty_argv[0];
+
+	/* define built-in commands and see if we were called as one */
+	ktinit(APERM, &builtins,
+	    /* currently 50 builtins -> 80% of 64 (2^6) */
+	    6);
+	for (i = 0; mkshbuiltins[i].name != NULL; i++)
+		if (!strcmp(ccp, builtin(mkshbuiltins[i].name,
+		    mkshbuiltins[i].func)))
+			Flag(FAS_BUILTIN) = 1;
+
+	if (!Flag(FAS_BUILTIN)) {
+		/* check for -T option early */
+		argi = parse_args(argv, OF_FIRSTTIME, NULL);
+		if (argi < 0)
+			return (1);
+
+#ifdef MKSH_BINSHREDUCED
+		/* set FSH if we're called as -sh or /bin/sh or so */
+		if (!strcmp(ccp, "sh"))
+			change_flag(FSH, OF_FIRSTTIME, 1);
+#endif
+	}
 
 	initvar();
 
@@ -157,28 +238,22 @@
 	coproc_init();
 
 	/* set up variable and command dictionaries */
-	ktinit(&taliases, APERM, 0);
-	ktinit(&aliases, APERM, 0);
+	ktinit(APERM, &taliases, 0);
+	ktinit(APERM, &aliases, 0);
 #ifndef MKSH_NOPWNAM
-	ktinit(&homedirs, APERM, 0);
+	ktinit(APERM, &homedirs, 0);
 #endif
 
 	/* define shell keywords */
 	initkeywords();
 
-	/* define built-in commands */
-	ktinit(&builtins, APERM,
-	    /* must be 80% of 2^n (currently 44 builtins) */ 64);
-	for (i = 0; mkshbuiltins[i].name != NULL; i++)
-		builtin(mkshbuiltins[i].name, mkshbuiltins[i].func);
-
 	init_histvec();
 
 #ifdef _PATH_DEFPATH
 	def_path = _PATH_DEFPATH;
 #else
 #ifdef _CS_PATH
-	if ((k = confstr(_CS_PATH, NULL, 0)) != (size_t)-1 && k > 0 &&
+	if ((k = confstr(_CS_PATH, NULL, 0)) > 0 &&
 	    confstr(_CS_PATH, cp = alloc(k + 1, APERM), k + 1) == k + 1)
 		def_path = cp;
 	else
@@ -194,91 +269,74 @@
 		def_path = "/bin:/usr/bin:/sbin:/usr/sbin";
 #endif
 
-	/* Set PATH to def_path (will set the path global variable).
+	/*
+	 * Set PATH to def_path (will set the path global variable).
 	 * (import of environment below will probably change this setting).
 	 */
 	vp = global("PATH");
 	/* setstr can't fail here */
 	setstr(vp, def_path, KSH_RETURN_ERROR);
 
-	/* Turn on nohup by default for now - will change to off
+	/*
+	 * Turn on nohup by default for now - will change to off
 	 * by default once people are aware of its existence
 	 * (AT&T ksh does not have a nohup option - it always sends
 	 * the hup).
 	 */
 	Flag(FNOHUP) = 1;
 
-	/* Turn on brace expansion by default. AT&T kshs that have
+	/*
+	 * Turn on brace expansion by default. AT&T kshs that have
 	 * alternation always have it on.
 	 */
 	Flag(FBRACEEXPAND) = 1;
 
-	/* Set edit mode to emacs by default, may be overridden
+	/*
+	 * Set edit mode to emacs by default, may be overridden
 	 * by the environment or the user. Also, we want tab completion
-	 * on in vi by default. */
+	 * on in vi by default.
+	 */
 	change_flag(FEMACS, OF_SPECIAL, 1);
 #if !MKSH_S_NOVI
 	Flag(FVITABCOMPLETE) = 1;
 #endif
 
-#ifdef MKSH_BINSHREDUCED
-	/* set FSH if we're called as -sh or /bin/sh or so */
-	{
-		const char *cc;
-
-		cc = kshname;
-		i = 0; argi = 0;
-		while (cc[i] != '\0')
-			/* the following line matches '-' and '/' ;-) */
-			if ((cc[i++] | 2) == '/')
-				argi = i;
-		if (((cc[argi] | 0x20) == 's') && ((cc[argi + 1] | 0x20) == 'h'))
-			change_flag(FSH, OF_FIRSTTIME, 1);
-	}
-#endif
-
 	/* import environment */
 	if (environ != NULL)
 		for (wp = (const char **)environ; *wp != NULL; wp++)
 			typeset(*wp, IMPORT | EXPORT, 0, 0, 0);
 
-	typeset(initifs, 0, 0, 0, 0);	/* for security */
+	/* for security */
+	typeset(initifs, 0, 0, 0, 0);
 
 	/* assign default shell variable values */
 	substitute(initsubs, 0);
 
 	/* Figure out the current working directory and set $PWD */
-	{
-		struct stat s_pwd, s_dot;
-		struct tbl *pwd_v = global("PWD");
-		char *pwd = str_val(pwd_v);
-		char *pwdx = pwd;
-
-		/* Try to use existing $PWD if it is valid */
-		if (pwd[0] != '/' ||
-		    stat(pwd, &s_pwd) < 0 || stat(".", &s_dot) < 0 ||
-		    s_pwd.st_dev != s_dot.st_dev ||
-		    s_pwd.st_ino != s_dot.st_ino)
-			pwdx = NULL;
-		set_current_wd(pwdx);
-		if (current_wd[0])
-			simplify_path(current_wd);
-		/* Only set pwd if we know where we are or if it had a
-		 * bogus value
-		 */
-		if (current_wd[0] || pwd != null)
-			/* setstr can't fail here */
-			setstr(pwd_v, current_wd, KSH_RETURN_ERROR);
-	}
+	vp = global("PWD");
+	cp = str_val(vp);
+	/* Try to use existing $PWD if it is valid */
+	set_current_wd((cp[0] == '/' && test_eval(NULL, TO_FILEQ, cp, ".",
+	    true)) ? cp : NULL);
+	if (current_wd[0])
+		simplify_path(current_wd);
+	/* Only set pwd if we know where we are or if it had a bogus value */
+	if (current_wd[0] || *cp)
+		/* setstr can't fail here */
+		setstr(vp, current_wd, KSH_RETURN_ERROR);
 
 	for (wp = initcoms; *wp != NULL; wp++) {
 		shcomexec(wp);
 		while (*wp != NULL)
 			wp++;
 	}
-	setint(global("COLUMNS"), 0);
-	setint(global("LINES"), 0);
-	setint(global("OPTIND"), 1);
+	setint_n(global("COLUMNS"), 0);
+	setint_n(global("LINES"), 0);
+	setint_n(global("OPTIND"), 1);
+
+	kshuid = getuid();
+	kshgid = getgid();
+	kshegid = getegid();
 
 	safe_prompt = ksheuid ? "$ " : "# ";
 	vp = global("PS1");
@@ -287,22 +345,24 @@
 	    (!ksheuid && !strchr(str_val(vp), '#')))
 		/* setstr can't fail here */
 		setstr(vp, safe_prompt, KSH_RETURN_ERROR);
-	setint((vp = global("PGRP")), (mksh_uari_t)kshpgrp);
+	setint_n((vp = global("PGRP")), (mksh_uari_t)kshpgrp);
 	vp->flag |= INT_U;
-	setint((vp = global("PPID")), (mksh_uari_t)kshppid);
+	setint_n((vp = global("PPID")), (mksh_uari_t)kshppid);
 	vp->flag |= INT_U;
-	setint((vp = global("RANDOM")), (mksh_uari_t)evilhash(kshname));
+	setint_n((vp = global("USER_ID")), (mksh_uari_t)ksheuid);
 	vp->flag |= INT_U;
-	setint((vp = global("USER_ID")), (mksh_uari_t)ksheuid);
+	setint_n((vp = global("KSHUID")), (mksh_uari_t)kshuid);
 	vp->flag |= INT_U;
+	setint_n((vp = global("KSHEGID")), (mksh_uari_t)kshegid);
+	vp->flag |= INT_U;
+	setint_n((vp = global("KSHGID")), (mksh_uari_t)kshgid);
+	vp->flag |= INT_U;
+	setint_n((vp = global("RANDOM")), rndsetup());
+	vp->flag |= INT_U;
+	setint_n((vp_pipest = global("PIPESTATUS")), 0);
 
 	/* Set this before parsing arguments */
-#if HAVE_SETRESUGID
-	Flag(FPRIVILEGED) = getuid() != ksheuid || getgid() != getegid();
-#else
-	Flag(FPRIVILEGED) = (kshuid = getuid()) != ksheuid ||
-	    (kshgid = getgid()) != (kshegid = getegid());
-#endif
+	Flag(FPRIVILEGED) = kshuid != ksheuid || kshgid != kshegid;
 
 	/* this to note if monitor is set on command line (see below) */
 #ifndef MKSH_UNEMPLOYED
@@ -311,18 +371,61 @@
 	/* this to note if utf-8 mode is set on command line (see below) */
 	UTFMODE = 2;
 
-	argi = parse_args(argv, OF_CMDLINE, NULL);
-	if (argi < 0)
-		return (NULL);
+	if (!Flag(FAS_BUILTIN)) {
+		argi = parse_args(argv, OF_CMDLINE, NULL);
+		if (argi < 0)
+			return (1);
+	}
+
+#ifdef DEBUG
+	/* test wraparound of arithmetic types */
+	{
+		volatile long xl;
+		volatile unsigned long xul;
+		volatile int xi;
+		volatile unsigned int xui;
+		volatile mksh_ari_t xa;
+		volatile mksh_uari_t xua, xua2;
+		volatile uint8_t xc;
+
+		xa = 2147483647;
+		xua = 2147483647;
+		++xa;
+		++xua;
+		xua2 = xa;
+		xl = xa;
+		xul = xua;
+		xa = 0;
+		xua = 0;
+		--xa;
+		--xua;
+		xi = xa;
+		xui = xua;
+		xa = -1;
+		xua = xa;
+		++xa;
+		++xua;
+		xc = 0;
+		--xc;
+		if ((xua2 != 2147483648UL) ||
+		    (xl != -2147483648L) || (xul != 2147483648UL) ||
+		    (xi != -1) || (xui != 4294967295U) ||
+		    (xa != 0) || (xua != 0) || (xc != 255))
+			errorf("integer wraparound test failed");
+	}
+#endif
 
 	/* process this later only, default to off (hysterical raisins) */
 	utf_flag = UTFMODE;
 	UTFMODE = 0;
 
-	if (Flag(FCOMMAND)) {
+	if (Flag(FAS_BUILTIN)) {
+		/* auto-detect from environment variables, always */
+		utf_flag = 3;
+	} else if (Flag(FCOMMAND)) {
 		s = pushs(SSTRING, ATEMP);
 		if (!(s->start = s->str = argv[argi++]))
-			errorf("-c requires an argument");
+			errorf("%s %s", "-c", "requires an argument");
 #ifdef MKSH_MIDNIGHTBSD01ASH_COMPAT
 		/* compatibility to MidnightBSD 0.1 /bin/sh (kludge) */
 		if (Flag(FSH) && argv[argi] && !strcmp(argv[argi], "--"))
@@ -336,7 +439,7 @@
 		s->u.shf = shf_open(s->file, O_RDONLY, 0,
 		    SHF_MAPHI | SHF_CLEXEC);
 		if (s->u.shf == NULL) {
-			shl_stdout_ok = 0;
+			shl_stdout_ok = false;
 			warningf(true, "%s: %s", s->file, strerror(errno));
 			/* mandated by SUSv4 */
 			exstat = 127;
@@ -365,37 +468,17 @@
 
 	/* initialise job control */
 	j_init();
-	/* set: 0/1; unset: 2->0 */
-	UTFMODE = utf_flag & 1;
 	/* Do this after j_init(), as tty_fd is not initialised until then */
 	if (Flag(FTALKING)) {
 		if (utf_flag == 2) {
 #ifndef MKSH_ASSUME_UTF8
-#define isuc(x)	(((x) != NULL) && \
-		    (stristr((x), "UTF-8") || stristr((x), "utf8")))
-		/* Check if we're in a UTF-8 locale */
-			const char *ccp;
-
-#if HAVE_SETLOCALE_CTYPE
-			ccp = setlocale(LC_CTYPE, "");
-#if HAVE_LANGINFO_CODESET
-			if (!isuc(ccp))
-				ccp = nl_langinfo(CODESET);
-#endif
-#else
-			/* these were imported from environ earlier */
-			ccp = str_val(global("LC_ALL"));
-			if (ccp == null)
-				ccp = str_val(global("LC_CTYPE"));
-			if (ccp == null)
-				ccp = str_val(global("LANG"));
-#endif
-			UTFMODE = isuc(ccp);
-#undef isuc
+			/* auto-detect from locale or environment */
+			utf_flag = 4;
 #elif MKSH_ASSUME_UTF8
-			UTFMODE = 1;
+			utf_flag = 1;
 #else
-			UTFMODE = 0;
+			/* always disable UTF-8 (for interactive) */
+			utf_flag = 0;
 #endif
 		}
 		x_init();
@@ -408,10 +491,62 @@
 #endif
 
 	l = e->loc;
-	l->argv = &argv[argi - 1];
-	l->argc = argc - argi;
-	l->argv[0] = kshname;
-	getopts_reset(1);
+	if (Flag(FAS_BUILTIN)) {
+		l->argc = argc;
+		l->argv = argv;
+		l->argv[0] = ccp;
+	} else {
+		l->argc = argc - argi;
+		l->argv = &argv[argi - 1];
+		l->argv[0] = kshname;
+		getopts_reset(1);
+	}
+
+	/* divine the initial state of the utf8-mode Flag */
+#define isuc(x)	(((x) != NULL) && \
+	    (stristr((x), "UTF-8") || stristr((x), "utf8")))
+	ccp = null;
+	switch (utf_flag) {
+
+	/* auto-detect from locale or environment */
+	case 4:
+#if HAVE_SETLOCALE_CTYPE
+		ccp = setlocale(LC_CTYPE, "");
+#if HAVE_LANGINFO_CODESET
+		if (!isuc(ccp))
+			ccp = nl_langinfo(CODESET);
+#endif
+		if (!isuc(ccp))
+			ccp = null;
+		/* FALLTHROUGH */
+#endif
+
+	/* auto-detect from environment */
+	case 3:
+		/* these were imported from environ earlier */
+		if (ccp == null)
+			ccp = str_val(global("LC_ALL"));
+		if (ccp == null)
+			ccp = str_val(global("LC_CTYPE"));
+		if (ccp == null)
+			ccp = str_val(global("LANG"));
+		UTFMODE = isuc(ccp);
+		break;
+
+	/* not set on command line, not FTALKING */
+	case 2:
+	/* unknown values */
+	default:
+		utf_flag = 0;
+		/* FALLTHROUGH */
+
+	/* known values */
+	case 1:
+	case 0:
+		UTFMODE = utf_flag;
+		break;
+	}
+#undef isuc
 
 	/* Disable during .profile/ENV reading */
 	restricted = Flag(FRESTRICTED);
@@ -419,20 +554,21 @@
 	errexit = Flag(FERREXIT);
 	Flag(FERREXIT) = 0;
 
-	/* Do this before profile/$ENV so that if it causes problems in them,
+	/*
+	 * Do this before profile/$ENV so that if it causes problems in them,
 	 * user will know why things broke.
 	 */
 	if (!current_wd[0] && Flag(FTALKING))
-		warningf(false, "Cannot determine current working directory");
+		warningf(false, "can't determine current directory");
 
 	if (Flag(FLOGIN)) {
-		include(KSH_SYSTEM_PROFILE, 0, NULL, 1);
+		include(MKSH_SYSTEM_PROFILE, 0, NULL, 1);
 		if (!Flag(FPRIVILEGED))
 			include(substitute("$HOME/.profile", 0), 0,
 			    NULL, 1);
 	}
 	if (Flag(FPRIVILEGED))
-		include("/etc/suid_profile", 0, NULL, 1);
+		include(MKSH_SUID_PROFILE, 0, NULL, 1);
 	else if (Flag(FTALKING)) {
 		char *env_file;
 
@@ -444,40 +580,27 @@
 	}
 
 	if (restricted) {
-		static const char *restr_com[] = {
-			T_typeset, "-r", "PATH",
-			"ENV", "SHELL",
-			NULL
-		};
 		shcomexec(restr_com);
 		/* After typeset command... */
 		Flag(FRESTRICTED) = 1;
 	}
 	Flag(FERREXIT) = errexit;
 
-	if (Flag(FTALKING)) {
+	if (Flag(FTALKING))
 		hist_init(s);
-		alarm_init();
-	} else
-		Flag(FTRACKALL) = 1;	/* set after ENV */
+	else
+		/* set after ENV */
+		Flag(FTRACKALL) = 1;
 
-	return (s);
-}
+	alarm_init();
 
-int
-main(int argc, const char *argv[])
-{
-	Source *s;
+	if (Flag(FAS_BUILTIN))
+		return (shcomexec(l->argv));
 
-	kshstate_v.lcg_state_ = 5381;
-
-	if ((s = mksh_init(argc, argv))) {
-		/* put more entropy into the LCG */
-		change_random(s, sizeof(*s));
-		/* doesn’t return */
-		shell(s, true);
-	}
-	return (1);
+	/* doesn't return */
+	shell(s, true);
+	/* NOTREACHED */
+	return (0);
 }
 
 int
@@ -511,9 +634,11 @@
 		switch (i) {
 		case LRETURN:
 		case LERROR:
-			return (exstat & 0xff); /* see below */
+			/* see below */
+			return (exstat & 0xFF);
 		case LINTR:
-			/* intr_ok is set if we are including .profile or $ENV.
+			/*
+			 * intr_ok is set if we are including .profile or $ENV.
 			 * If user ^Cs out, we don't want to kill the shell...
 			 */
 			if (intr_ok && (exstat - 128) != SIGTERM)
@@ -525,7 +650,7 @@
 			unwind(i);
 			/* NOTREACHED */
 		default:
-			internal_errorf("include: %d", i);
+			internal_errorf("%s %d", "include", i);
 			/* NOTREACHED */
 		}
 	}
@@ -542,7 +667,8 @@
 		e->loc->argv = old_argv;
 		e->loc->argc = old_argc;
 	}
-	return (i & 0xff);	/* & 0xff to ensure value not -1 */
+	/* & 0xff to ensure value not -1 */
+	return (i & 0xFF);
 }
 
 /* spawn a command into a shell optionally keeping track of the line number */
@@ -567,30 +693,32 @@
 	volatile int wastty = s->flags & SF_TTY;
 	volatile int attempts = 13;
 	volatile int interactive = Flag(FTALKING) && toplevel;
+	volatile bool sfirst = true;
 	Source *volatile old_source = source;
 	int i;
 
-	s->flags |= SF_FIRST;	/* enable UTF-8 BOM check */
-
 	newenv(E_PARSE);
 	if (interactive)
 		really_exit = 0;
 	i = sigsetjmp(e->jbuf, 0);
 	if (i) {
 		switch (i) {
-		case LINTR: /* we get here if SIGINT not caught or ignored */
+		case LINTR:
+			/* we get here if SIGINT not caught or ignored */
 		case LERROR:
 		case LSHELL:
 			if (interactive) {
 				if (i == LINTR)
 					shellf("\n");
-				/* Reset any eof that was read as part of a
+				/*
+				 * Reset any eof that was read as part of a
 				 * multiline command.
 				 */
 				if (Flag(FIGNOREEOF) && s->type == SEOF &&
 				    wastty)
 					s->type = SSTDIN;
-				/* Used by exit command to get back to
+				/*
+				 * Used by exit command to get back to
 				 * top level shell. Kind of strange since
 				 * interactive is set if we are reading from
 				 * a tty, but to have stopped jobs, one only
@@ -606,16 +734,17 @@
 		case LRETURN:
 			source = old_source;
 			quitenv(NULL);
-			unwind(i);	/* keep on going */
+			/* keep on going */
+			unwind(i);
 			/* NOTREACHED */
 		default:
 			source = old_source;
 			quitenv(NULL);
-			internal_errorf("shell: %d", i);
+			internal_errorf("%s %d", "shell", i);
 			/* NOTREACHED */
 		}
 	}
-	while (1) {
+	while (/* CONSTCOND */ 1) {
 		if (trap)
 			runtraps(0);
 
@@ -629,17 +758,19 @@
 			j_notify();
 			set_prompt(PS1, s);
 		}
-		t = compile(s);
+		t = compile(s, sfirst);
+		sfirst = false;
 		if (t != NULL && t->type == TEOF) {
 			if (wastty && Flag(FIGNOREEOF) && --attempts > 0) {
-				shellf("Use 'exit' to leave ksh\n");
+				shellf("Use 'exit' to leave mksh\n");
 				s->type = SSTDIN;
 			} else if (wastty && !really_exit &&
 			    j_stopped_running()) {
 				really_exit = 1;
 				s->type = SSTDIN;
 			} else {
-				/* this for POSIX which says EXIT traps
+				/*
+				 * this for POSIX which says EXIT traps
 				 * shall be taken in the environment
 				 * immediately after the last command
 				 * executed.
@@ -668,14 +799,18 @@
 {
 	/* ordering for EXIT vs ERR is a bit odd (this is what AT&T ksh does) */
 	if (i == LEXIT || (Flag(FERREXIT) && (i == LERROR || i == LINTR) &&
-	    sigtraps[SIGEXIT_].trap)) {
-		runtrap(&sigtraps[SIGEXIT_]);
+	    sigtraps[ksh_SIGEXIT].trap)) {
+		++trap_nested;
+		runtrap(&sigtraps[ksh_SIGEXIT], trap_nested == 1);
+		--trap_nested;
 		i = LLEAVE;
 	} else if (Flag(FERREXIT) && (i == LERROR || i == LINTR)) {
-		runtrap(&sigtraps[SIGERR_]);
+		++trap_nested;
+		runtrap(&sigtraps[ksh_SIGERR], trap_nested == 1);
+		--trap_nested;
 		i = LLEAVE;
 	}
-	while (1) {
+	while (/* CONSTCOND */ 1) {
 		switch (e->type) {
 		case E_PARSE:
 		case E_FUNC:
@@ -705,7 +840,8 @@
 	 * so first get the actually used memory, then assign it
 	 */
 	cp = alloc(sizeof(struct env) - ALLOC_SIZE, ATEMP);
-	ep = (void *)(cp - ALLOC_SIZE);	/* undo what alloc() did */
+	/* undo what alloc() did to the malloc result address */
+	ep = (void *)(cp - ALLOC_SIZE);
 	/* initialise public members of struct env (not the ALLOC_ITEM) */
 	ainit(&ep->area);
 	ep->oenv = e;
@@ -732,14 +868,17 @@
 			/* if ep->savefd[fd] < 0, means fd was closed */
 			if (ep->savefd[fd])
 				restfd(fd, ep->savefd[fd]);
-		if (ep->savefd[2])	/* Clear any write errors */
+		if (ep->savefd[2])
+			/* Clear any write errors */
 			shf_reopen(2, SHF_WR, shl_out);
 	}
-	/* Bottom of the stack.
+	/*
+	 * Bottom of the stack.
 	 * Either main shell is exiting or cleanup_parents_env() was called.
 	 */
 	if (ep->oenv == NULL) {
-		if (ep->type == E_NONE) {	/* Main shell exiting? */
+		if (ep->type == E_NONE) {
+			/* Main shell exiting? */
 #if HAVE_PERSISTENT_HISTORY
 			if (Flag(FTALKING))
 				hist_finish();
@@ -748,7 +887,8 @@
 			if (ep->flags & EF_FAKE_SIGDIE) {
 				int sig = exstat - 128;
 
-				/* ham up our death a bit (AT&T ksh
+				/*
+				 * ham up our death a bit (AT&T ksh
 				 * only seems to do this for SIGTERM)
 				 * Don't do it for SIGQUIT, since we'd
 				 * dump a core..
@@ -832,7 +972,8 @@
 			unlink(tp->name);
 }
 
-/* Initialise tty_fd. Used for saving/reseting tty modes upon
+/*
+ * Initialise tty_fd. Used for saving/reseting tty modes upon
  * foreground job completion and for setting up tty process group.
  */
 void
@@ -845,19 +986,21 @@
 		close(tty_fd);
 		tty_fd = -1;
 	}
-	tty_devtty = 1;
+	tty_devtty = true;
 
 #ifdef _UWIN
-	/* XXX imake style */
-	if (isatty(3))
+	/*XXX imake style */
+	if (isatty(3)) {
+		/* fd 3 on UWIN _is_ /dev/tty (or our controlling tty) */
 		tfd = 3;
-	else
+		do_close = false;
+	} else
 #endif
-	if ((tfd = open("/dev/tty", O_RDWR, 0)) < 0) {
-		tty_devtty = 0;
+	  if ((tfd = open("/dev/tty", O_RDWR, 0)) < 0) {
+		tty_devtty = false;
 		if (need_tty)
-			warningf(false,
-			    "No controlling tty (open /dev/tty: %s)",
+			warningf(false, "%s: %s %s: %s",
+			    "No controlling tty", "open", "/dev/tty",
 			    strerror(errno));
 	}
 	if (tfd < 0) {
@@ -868,20 +1011,18 @@
 			tfd = 2;
 		else {
 			if (need_tty)
-				warningf(false,
-				    "Can't find tty file descriptor");
+				warningf(false, "can't find tty fd");
 			return;
 		}
 	}
 	if ((tty_fd = fcntl(tfd, F_DUPFD, FDBASE)) < 0) {
 		if (need_tty)
-			warningf(false, "j_ttyinit: dup of tty fd failed: %s",
-			    strerror(errno));
+			warningf(false, "%s: %s %s: %s", "j_ttyinit",
+			    "dup of tty fd", "failed", strerror(errno));
 	} else if (fcntl(tty_fd, F_SETFD, FD_CLOEXEC) < 0) {
 		if (need_tty)
-			warningf(false,
-			    "j_ttyinit: can't set close-on-exec flag: %s",
-			    strerror(errno));
+			warningf(false, "%s: %s: %s", "j_ttyinit",
+			    "can't set close-on-exec flag", strerror(errno));
 		close(tty_fd);
 		tty_fd = -1;
 	} else if (init_ttystate)
@@ -900,21 +1041,62 @@
 }
 
 /* A shell error occurred (eg, syntax error, etc.) */
+
+#define VWARNINGF_ERRORPREFIX	1
+#define VWARNINGF_FILELINE	2
+#define VWARNINGF_BUILTIN	4
+#define VWARNINGF_INTERNAL	8
+
+static void vwarningf(unsigned int, const char *, va_list)
+    MKSH_A_FORMAT(__printf__, 2, 0);
+
+static void
+vwarningf(unsigned int flags, const char *fmt, va_list ap)
+{
+	if (*fmt != 1) {
+		if (flags & VWARNINGF_INTERNAL)
+			shf_fprintf(shl_out, "internal error: ");
+		if (flags & VWARNINGF_ERRORPREFIX)
+			error_prefix(tobool(flags & VWARNINGF_FILELINE));
+		if ((flags & VWARNINGF_BUILTIN) &&
+		    /* not set when main() calls parse_args() */
+		    builtin_argv0 && builtin_argv0 != kshname)
+			shf_fprintf(shl_out, "%s: ", builtin_argv0);
+		shf_vfprintf(shl_out, fmt, ap);
+		shf_putchar('\n', shl_out);
+	}
+	shf_flush(shl_out);
+}
+
+void
+errorfx(int rc, const char *fmt, ...)
+{
+	va_list va;
+
+	exstat = rc;
+
+	/* debugging: note that stdout not valid */
+	shl_stdout_ok = false;
+
+	va_start(va, fmt);
+	vwarningf(VWARNINGF_ERRORPREFIX | VWARNINGF_FILELINE, fmt, va);
+	va_end(va);
+	unwind(LERROR);
+}
+
 void
 errorf(const char *fmt, ...)
 {
 	va_list va;
 
-	shl_stdout_ok = 0;	/* debugging: note that stdout not valid */
 	exstat = 1;
-	if (*fmt != 1) {
-		error_prefix(true);
-		va_start(va, fmt);
-		shf_vfprintf(shl_out, fmt, va);
-		va_end(va);
-		shf_putchar('\n', shl_out);
-	}
-	shf_flush(shl_out);
+
+	/* debugging: note that stdout not valid */
+	shl_stdout_ok = false;
+
+	va_start(va, fmt);
+	vwarningf(VWARNINGF_ERRORPREFIX | VWARNINGF_FILELINE, fmt, va);
+	va_end(va);
 	unwind(LERROR);
 }
 
@@ -924,15 +1106,14 @@
 {
 	va_list va;
 
-	error_prefix(fileline);
 	va_start(va, fmt);
-	shf_vfprintf(shl_out, fmt, va);
+	vwarningf(VWARNINGF_ERRORPREFIX | (fileline ? VWARNINGF_FILELINE : 0),
+	    fmt, va);
 	va_end(va);
-	shf_putchar('\n', shl_out);
-	shf_flush(shl_out);
 }
 
-/* Used by built-in utilities to prefix shell and utility name to message
+/*
+ * Used by built-in utilities to prefix shell and utility name to message
  * (also unwinds environments for special builtins).
  */
 void
@@ -940,20 +1121,18 @@
 {
 	va_list va;
 
-	shl_stdout_ok = 0;	/* debugging: note that stdout not valid */
+	/* debugging: note that stdout not valid */
+	shl_stdout_ok = false;
+
 	exstat = 1;
-	if (*fmt != 1) {
-		error_prefix(true);
-		/* not set when main() calls parse_args() */
-		if (builtin_argv0)
-			shf_fprintf(shl_out, "%s: ", builtin_argv0);
-		va_start(va, fmt);
-		shf_vfprintf(shl_out, fmt, va);
-		va_end(va);
-		shf_putchar('\n', shl_out);
-	}
-	shf_flush(shl_out);
-	/* POSIX special builtins and ksh special builtins cause
+
+	va_start(va, fmt);
+	vwarningf(VWARNINGF_ERRORPREFIX | VWARNINGF_FILELINE |
+	    VWARNINGF_BUILTIN, fmt, va);
+	va_end(va);
+
+	/*
+	 * POSIX special builtins and ksh special builtins cause
 	 * non-interactive shells to exit.
 	 * XXX odd use of KEEPASN; also may not want LERROR here
 	 */
@@ -965,21 +1144,12 @@
 
 /* Called when something that shouldn't happen does */
 void
-internal_verrorf(const char *fmt, va_list ap)
-{
-	shf_fprintf(shl_out, "internal error: ");
-	shf_vfprintf(shl_out, fmt, ap);
-	shf_putchar('\n', shl_out);
-	shf_flush(shl_out);
-}
-
-void
 internal_errorf(const char *fmt, ...)
 {
 	va_list va;
 
 	va_start(va, fmt);
-	internal_verrorf(fmt, va);
+	vwarningf(VWARNINGF_INTERNAL, fmt, va);
 	va_end(va);
 	unwind(LERROR);
 }
@@ -990,7 +1160,7 @@
 	va_list va;
 
 	va_start(va, fmt);
-	internal_verrorf(fmt, va);
+	vwarningf(VWARNINGF_INTERNAL, fmt, va);
 	va_end(va);
 }
 
@@ -1015,7 +1185,8 @@
 {
 	va_list va;
 
-	if (!initio_done) /* shl_out may not be set up yet... */
+	if (!initio_done)
+		/* shl_out may not be set up yet... */
 		return;
 	va_start(va, fmt);
 	shf_vfprintf(shl_out, fmt, va);
@@ -1051,9 +1222,11 @@
 void
 initio(void)
 {
-	shf_fdopen(1, SHF_WR, shl_stdout);	/* force buffer allocation */
+	/* force buffer allocation */
+	shf_fdopen(1, SHF_WR, shl_stdout);
 	shf_fdopen(2, SHF_WR, shl_out);
-	shf_fdopen(2, SHF_WR, shl_spare);	/* force buffer allocation */
+	/* force buffer allocation */
+	shf_fdopen(2, SHF_WR, shl_spare);
 	initio_done = 1;
 }
 
@@ -1067,7 +1240,7 @@
 		errorf("too many files open in shell");
 
 #ifdef __ultrix
-	/* XXX imake style */
+	/*XXX imake style */
 	if (rv >= 0)
 		fcntl(nfd, F_SETFD, 0);
 #endif
@@ -1076,8 +1249,8 @@
 }
 
 /*
- * move fd from user space (0<=fd<10) to shell space (fd>=10),
- * set close-on-exec flag.
+ * Move fd from user space (0 <= fd < 10) to shell space (fd >= 10),
+ * set close-on-exec flag. See FDBASE in sh.h, maybe 24 not 10 here.
  */
 short
 savefd(int fd)
@@ -1098,10 +1271,12 @@
 {
 	if (fd == 2)
 		shf_flush(&shf_iob[fd]);
-	if (ofd < 0)		/* original fd closed */
+	if (ofd < 0)
+		/* original fd closed */
 		close(fd);
 	else if (fd != ofd) {
-		ksh_dup2(ofd, fd, true); /* XXX: what to do if this fails? */
+		/*XXX: what to do if this dup fails? */
+		ksh_dup2(ofd, fd, true);
 		close(ofd);
 	}
 }
@@ -1128,7 +1303,8 @@
 	close(pv[1]);
 }
 
-/* Called by iosetup() (deals with 2>&4, etc.), c_read, c_print to turn
+/*
+ * Called by iosetup() (deals with 2>&4, etc.), c_read, c_print to turn
  * a string (the X in 2>&X, read -uX, print -uX) into a file descriptor.
  */
 int
@@ -1151,7 +1327,8 @@
 		return (-1);
 	}
 	fl &= O_ACCMODE;
-	/* X_OK is a kludge to disable this check for dups (x<&1):
+	/*
+	 * X_OK is a kludge to disable this check for dups (x<&1):
 	 * historical shells never did this check (XXX don't know what
 	 * POSIX has to say).
 	 */
@@ -1187,7 +1364,8 @@
 	}
 }
 
-/* Called by c_read() and by iosetup() to close the other side of the
+/*
+ * Called by c_read() and by iosetup() to close the other side of the
  * read pipe, so reads will actually terminate.
  */
 void
@@ -1199,7 +1377,8 @@
 	}
 }
 
-/* Called by c_print when a write to a fd fails with EPIPE and by iosetup
+/*
+ * Called by c_print when a write to a fd fails with EPIPE and by iosetup
  * when co-process input is dup'd
  */
 void
@@ -1211,7 +1390,8 @@
 	}
 }
 
-/* Called to check for existence of/value of the co-process file descriptor.
+/*
+ * Called to check for existence of/value of the co-process file descriptor.
  * (Used by check_fd() and by c_read/c_print to deal with -p option).
  */
 int
@@ -1226,7 +1406,8 @@
 	return (-1);
 }
 
-/* called to close file descriptors related to the coprocess (if any)
+/*
+ * called to close file descriptors related to the coprocess (if any)
  * Should be called with SIGCHLD blocked.
  */
 void
@@ -1253,7 +1434,7 @@
 maketemp(Area *ap, Temp_type type, struct temp **tlist)
 {
 	struct temp *tp;
-	int len;
+	size_t len;
 	int fd;
 	char *pathname;
 	const char *dir;
@@ -1265,6 +1446,7 @@
 	pathname = tempnam(dir, "mksh.");
 	len = ((pathname == NULL) ? 0 : strlen(pathname)) + 1;
 #endif
+	/* reasonably sure that this will not overflow */
 	tp = alloc(sizeof(struct temp) + len, ap);
 	tp->name = (char *)&tp[1];
 #if !HAVE_MKSTEMP
@@ -1272,14 +1454,14 @@
 		tp->name[0] = '\0';
 	else {
 		memcpy(tp->name, pathname, len);
-		free(pathname);
+		free_ostempnam(pathname);
 	}
 #endif
 	pathname = tp->name;
 	tp->shf = NULL;
 	tp->type = type;
 #if HAVE_MKSTEMP
-	shf_snprintf(pathname, len, "%s/mksh.XXXXXXXXXX", dir);
+	shf_snprintf(pathname, len, "%s%s", dir, "/mksh.XXXXXXXXXX");
 	if ((fd = mkstemp(pathname)) >= 0)
 #else
 	if (tp->name[0] && (fd = open(tp->name, O_CREAT | O_RDWR, 0600)) >= 0)
@@ -1297,41 +1479,48 @@
  * but with a slightly tweaked implementation written from scratch.
  */
 
-#define	INIT_TBLS	8	/* initial table size (power of 2) */
+#define	INIT_TBLSHIFT	3	/* initial table shift (2^3 = 8) */
 #define PERTURB_SHIFT	5	/* see Python 2.5.4 Objects/dictobject.c */
 
-static void texpand(struct table *, size_t);
+static void tgrow(struct table *);
 static int tnamecmp(const void *, const void *);
-static struct tbl *ktscan(struct table *, const char *, uint32_t,
-    struct tbl ***);
 
 static void
-texpand(struct table *tp, size_t nsize)
+tgrow(struct table *tp)
 {
-	size_t i, j, osize = tp->size, perturb;
+	size_t i, j, osize, mask, perturb;
 	struct tbl *tblp, **pp;
 	struct tbl **ntblp, **otblp = tp->tbls;
 
-	ntblp = alloc(nsize * sizeof(struct tbl *), tp->areap);
-	for (i = 0; i < nsize; i++)
-		ntblp[i] = NULL;
-	tp->size = nsize;
-	tp->nfree = (nsize * 4) / 5;	/* table can get 80% full */
+	if (tp->tshift > 29)
+		internal_errorf("hash table size limit reached");
+
+	/* calculate old size, new shift and new size */
+	osize = (size_t)1 << (tp->tshift++);
+	i = osize << 1;
+
+	ntblp = alloc2(i, sizeof(struct tbl *), tp->areap);
+	/* multiplication cannot overflow: alloc2 checked that */
+	memset(ntblp, 0, i * sizeof(struct tbl *));
+
+	/* table can get 80% full except when reaching its limit */
+	tp->nfree = (tp->tshift == 30) ? 0x3FFF0000UL : ((i * 4) / 5);
 	tp->tbls = ntblp;
 	if (otblp == NULL)
 		return;
-	nsize--;			/* from here on nsize := mask */
+
+	mask = i - 1;
 	for (i = 0; i < osize; i++)
 		if ((tblp = otblp[i]) != NULL) {
 			if ((tblp->flag & DEFINED)) {
 				/* search for free hash table slot */
-				j = (perturb = tblp->ua.hval) & nsize;
+				j = (perturb = tblp->ua.hval) & mask;
 				goto find_first_empty_slot;
  find_next_empty_slot:
 				j = (j << 2) + j + perturb + 1;
 				perturb >>= PERTURB_SHIFT;
  find_first_empty_slot:
-				pp = &ntblp[j & nsize];
+				pp = &ntblp[j & mask];
 				if (*pp != NULL)
 					goto find_next_empty_slot;
 				/* found an empty hash table slot */
@@ -1345,23 +1534,23 @@
 }
 
 void
-ktinit(struct table *tp, Area *ap, size_t tsize)
+ktinit(Area *ap, struct table *tp, uint8_t initshift)
 {
 	tp->areap = ap;
 	tp->tbls = NULL;
-	tp->size = tp->nfree = 0;
-	if (tsize)
-		texpand(tp, tsize);
+	tp->tshift = ((initshift > INIT_TBLSHIFT) ?
+	    initshift : INIT_TBLSHIFT) - 1;
+	tgrow(tp);
 }
 
 /* table, name (key) to search for, hash(name), rv pointer to tbl ptr */
-static struct tbl *
+struct tbl *
 ktscan(struct table *tp, const char *name, uint32_t h, struct tbl ***ppp)
 {
 	size_t j, perturb, mask;
 	struct tbl **pp, *p;
 
-	mask = tp->size - 1;
+	mask = ((size_t)1 << (tp->tshift)) - 1;
 	/* search for hash table slot matching name */
 	j = (perturb = h) & mask;
 	goto find_first_slot;
@@ -1379,35 +1568,27 @@
 	return (p);
 }
 
-/* table, name (key) to search for, hash(n) */
-struct tbl *
-ktsearch(struct table *tp, const char *n, uint32_t h)
-{
-	return (tp->size ? ktscan(tp, n, h, NULL) : NULL);
-}
-
 /* table, name (key) to enter, hash(n) */
 struct tbl *
 ktenter(struct table *tp, const char *n, uint32_t h)
 {
 	struct tbl **pp, *p;
-	int len;
+	size_t len;
 
-	if (tp->size == 0)
-		texpand(tp, INIT_TBLS);
  Search:
 	if ((p = ktscan(tp, n, h, &pp)))
 		return (p);
 
-	if (tp->nfree <= 0) {
+	if (tp->nfree == 0) {
 		/* too full */
-		texpand(tp, 2 * tp->size);
+		tgrow(tp);
 		goto Search;
 	}
 
 	/* create new tbl entry */
-	len = strlen(n) + 1;
-	p = alloc(offsetof(struct tbl, name[0]) + len, tp->areap);
+	len = strlen(n);
+	checkoktoadd(len, offsetof(struct tbl, name[0]) + 1);
+	p = alloc(offsetof(struct tbl, name[0]) + ++len, tp->areap);
 	p->flag = 0;
 	p->type = 0;
 	p->areap = tp->areap;
@@ -1425,7 +1606,7 @@
 void
 ktwalk(struct tstate *ts, struct table *tp)
 {
-	ts->left = tp->size;
+	ts->left = (size_t)1 << (tp->tshift);
 	ts->next = tp->tbls;
 }
 
@@ -1455,15 +1636,19 @@
 	size_t i;
 	struct tbl **p, **sp, **dp;
 
-	p = alloc((tp->size + 1) * sizeof(struct tbl *), ATEMP);
+	/*
+	 * since the table is never entirely full, no need to reserve
+	 * additional space for the trailing NULL appended below
+	 */
+	i = (size_t)1 << (tp->tshift);
+	p = alloc2(i, sizeof(struct tbl *), ATEMP);
 	sp = tp->tbls;		/* source */
 	dp = p;			/* dest */
-	i = (size_t)tp->size;
 	while (i--)
 		if ((*dp = *sp++) != NULL && (((*dp)->flag & DEFINED) ||
 		    ((*dp)->flag & ARRAY)))
 			dp++;
-	qsort(p, (i = dp - p), sizeof(void *), tnamecmp);
+	qsort(p, (i = dp - p), sizeof(struct tbl *), tnamecmp);
 	p[i] = NULL;
 	return (p);
 }
diff --git a/src/misc.c b/src/misc.c
index 75a4de1..4adb7f2 100644
--- a/src/misc.c
+++ b/src/misc.c
@@ -2,7 +2,7 @@
 /*	$OpenBSD: path.c,v 1.12 2005/03/30 17:16:37 deraadt Exp $	*/
 
 /*-
- * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
  *	Thorsten Glaser <tg@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -29,15 +29,13 @@
 #include <grp.h>
 #endif
 
-__RCSID("$MirOS: src/bin/mksh/misc.c,v 1.141 2010/07/17 22:09:36 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/misc.c,v 1.172 2011/09/07 15:24:18 tg Exp $");
 
-unsigned char chtypes[UCHAR_MAX + 1];	/* type bits for unsigned char */
+/* type bits for unsigned char */
+unsigned char chtypes[UCHAR_MAX + 1];
 
-#if !HAVE_SETRESUGID
-uid_t kshuid;
-gid_t kshgid, kshegid;
-#endif
-
+static const unsigned char *pat_scan(const unsigned char *,
+    const unsigned char *, bool);
 static int do_gmatch(const unsigned char *, const unsigned char *,
     const unsigned char *, const unsigned char *);
 static const unsigned char *cclass(const unsigned char *, int);
@@ -45,6 +43,20 @@
 static void chvt(const char *);
 #endif
 
+/*XXX this should go away */
+static int make_path(const char *, const char *, char **, XString *, int *);
+
+#ifdef SETUID_CAN_FAIL_WITH_EAGAIN
+/* we don't need to check for other codes, EPERM won't happen */
+#define DO_SETUID(func, argvec) do {					\
+	if ((func argvec) && errno == EAGAIN)				\
+		errorf("%s failed with EAGAIN, probably due to a"	\
+		    " too low process limit; aborting", #func);		\
+} while (/* CONSTCOND */ 0)
+#else
+#define DO_SETUID(func, argvec) func argvec
+#endif
+
 /*
  * Fast character classes
  */
@@ -56,7 +68,8 @@
 	if (t & C_IFS) {
 		for (i = 0; i < UCHAR_MAX + 1; i++)
 			chtypes[i] &= ~C_IFS;
-		chtypes[0] |= C_IFS; /* include \0 in C_IFS */
+		/* include \0 in C_IFS */
+		chtypes[0] |= C_IFS;
 	}
 	while (*s != 0)
 		chtypes[(unsigned char)*s++] |= t;
@@ -73,7 +86,8 @@
 		chtypes[c] |= C_ALPHA;
 	chtypes['_'] |= C_ALPHA;
 	setctypes("0123456789", C_DIGIT);
-	setctypes(" \t\n|&;<>()", C_LEX1); /* \0 added automatically */
+	/* \0 added automatically */
+	setctypes(" \t\n|&;<>()", C_LEX1);
 	setctypes("*@#!$-?", C_VAR1);
 	setctypes(" \t\n", C_IFSWS);
 	setctypes("=-+?", C_SUBOP1);
@@ -82,12 +96,15 @@
 
 /* called from XcheckN() to grow buffer */
 char *
-Xcheck_grow_(XString *xsp, const char *xp, unsigned int more)
+Xcheck_grow_(XString *xsp, const char *xp, size_t more)
 {
 	const char *old_beg = xsp->beg;
 
-	xsp->len += more > xsp->len ? more : xsp->len;
-	xsp->beg = aresize(xsp->beg, xsp->len + 8, xsp->areap);
+	if (more < xsp->len)
+		more = xsp->len;
+	/* (xsp->len + X_EXTRA) never overflows */
+	checkoktoadd(more, xsp->len + X_EXTRA);
+	xsp->beg = aresize(xsp->beg, (xsp->len += more) + X_EXTRA, xsp->areap);
 	xsp->end = xsp->beg + xsp->len;
 	return (xsp->beg + (xp - old_beg));
 }
@@ -124,12 +141,12 @@
 	int opts[NELEM(options)];
 };
 
-static char *options_fmt_entry(char *, int, int, const void *);
+static char *options_fmt_entry(char *, size_t, int, const void *);
 static void printoptions(bool);
 
 /* format a single select menu item */
 static char *
-options_fmt_entry(char *buf, int buflen, int i, const void *arg)
+options_fmt_entry(char *buf, size_t buflen, int i, const void *arg)
 {
 	const struct options_info *oi = (const struct options_info *)arg;
 
@@ -142,17 +159,17 @@
 static void
 printoptions(bool verbose)
 {
-	int i = 0;
+	size_t i = 0;
 
 	if (verbose) {
-		int n = 0, len, octs = 0;
+		ssize_t n = 0, len, octs = 0;
 		struct options_info oi;
 
 		/* verbose version */
 		shf_puts("Current option settings\n", shl_stdout);
 
 		oi.opt_width = 0;
-		while (i < (int)NELEM(options)) {
+		while (i < NELEM(options)) {
 			if (options[i].name) {
 				oi.opts[n++] = i;
 				len = strlen(options[i].name);
@@ -167,11 +184,12 @@
 		print_columns(shl_stdout, n, options_fmt_entry, &oi,
 		    octs + 4, oi.opt_width + 4, true);
 	} else {
-		/* short version á la AT&T ksh93 */
-		shf_puts("set", shl_stdout);
+		/* short version like AT&T ksh93 */
+		shf_puts(Tset, shl_stdout);
 		while (i < (int)NELEM(options)) {
 			if (Flag(i) && options[i].name)
-				shprintf(" -o %s", options[i].name);
+				shprintf("%s %s %s", null, "-o",
+				    options[i].name);
 			++i;
 		}
 		shf_putc('\n', shl_stdout);
@@ -181,8 +199,8 @@
 char *
 getoptions(void)
 {
-	unsigned int i;
-	char m[(int) FNFLAGS + 1];
+	size_t i;
+	char m[(int)FNFLAGS + 1];
 	char *cp = m;
 
 	for (i = 0; i < NELEM(options); i++)
@@ -199,7 +217,8 @@
 	unsigned char oldval;
 
 	oldval = Flag(f);
-	Flag(f) = newval ? 1 : 0;	/* needed for tristates */
+	/* needed for tristates */
+	Flag(f) = newval ? 1 : 0;
 #ifndef MKSH_UNEMPLOYED
 	if (f == FMONITOR) {
 		if (what != OF_CMDLINE && newval != oldval)
@@ -218,18 +237,21 @@
 		Flag(f) = (unsigned char)newval;
 	} else if (f == FPRIVILEGED && oldval && !newval) {
 		/* Turning off -p? */
-#if HAVE_SETRESUGID
-		gid_t kshegid = getgid();
 
-		setresgid(kshegid, kshegid, kshegid);
+		/*XXX this can probably be optimised */
+		kshegid = kshgid = getgid();
+#if HAVE_SETRESUGID
+		DO_SETUID(setresgid, (kshegid, kshegid, kshegid));
 #if HAVE_SETGROUPS
+		/* setgroups doesn't EAGAIN on Linux */
 		setgroups(1, &kshegid);
 #endif
-		setresuid(ksheuid, ksheuid, ksheuid);
+		DO_SETUID(setresuid, (ksheuid, ksheuid, ksheuid));
 #else
+		/* seteuid, setegid, setgid don't EAGAIN on Linux */
 		seteuid(ksheuid = kshuid = getuid());
-		setuid(ksheuid);
-		setegid(kshegid = kshgid = getgid());
+		DO_SETUID(setuid, (ksheuid));
+		setegid(kshegid);
 		setgid(kshegid);
 #endif
 	} else if ((f == FPOSIX || f == FSH) && newval) {
@@ -243,12 +265,14 @@
 	}
 }
 
-/* Parse command line & set command arguments. Returns the index of
+/*
+ * Parse command line and set command arguments. Returns the index of
  * non-option arguments, -1 if there is an error.
  */
 int
 parse_args(const char **argv,
-    int what,			/* OF_CMDLINE or OF_SET */
+    /* OF_CMDLINE or OF_SET */
+    int what,
     bool *setargsp)
 {
 	static char cmd_opts[NELEM(options) + 5]; /* o:T:\0 */
@@ -257,7 +281,8 @@
 	const char *array = NULL;
 	Getopt go;
 	size_t i;
-	int optc, sortargs = 0, arrayset = 0;
+	int optc, arrayset = 0;
+	bool sortargs = false;
 
 	/* First call? Build option strings... */
 	if (cmd_opts[0] == '\0') {
@@ -291,7 +316,8 @@
 
 	if (what == OF_CMDLINE) {
 		const char *p = argv[0], *q;
-		/* Set FLOGIN before parsing options so user can clear
+		/*
+		 * Set FLOGIN before parsing options so user can clear
 		 * flag using +l.
 		 */
 		if (*p != '-')
@@ -319,7 +345,8 @@
 			if (what == OF_FIRSTTIME)
 				break;
 			if (go.optarg == NULL) {
-				/* lone -o: print options
+				/*
+				 * lone -o: print options
 				 *
 				 * Note that on the command line, -o requires
 				 * an option (ie, can't get here if what is
@@ -329,14 +356,9 @@
 				break;
 			}
 			i = option(go.optarg);
-			if ((enum sh_flag)i == FARC4RANDOM) {
-				warningf(true, "Do not use set ±o arc4random,"
-				    " it will be removed in the next version"
-				    " of mksh!");
-				return (0);
-			}
 			if ((i != (size_t)-1) && set == Flag(i))
-				/* Don't check the context if the flag
+				/*
+				 * Don't check the context if the flag
 				 * isn't changing - makes "set -o interactive"
 				 * work if you're already interactive. Needed
 				 * if the output of "set +o" is to be used.
@@ -345,7 +367,7 @@
 			else if ((i != (size_t)-1) && (options[i].flags & what))
 				change_flag((enum sh_flag)i, what, set);
 			else {
-				bi_errorf("%s: bad option", go.optarg);
+				bi_errorf("%s: %s", go.optarg, "bad option");
 				return (-1);
 			}
 			break;
@@ -371,7 +393,7 @@
 				break;
 			/* -s: sort positional params (AT&T ksh stupidity) */
 			if (what == OF_SET && optc == 's') {
-				sortargs = 1;
+				sortargs = true;
 				break;
 			}
 			for (i = 0; i < NELEM(options); i++)
@@ -398,9 +420,15 @@
 		*setargsp = !arrayset && ((go.info & GI_MINUSMINUS) ||
 		    argv[go.optind]);
 
-	if (arrayset && (!*array || *skip_varname(array, false))) {
-		bi_errorf("%s: is not an identifier", array);
-		return (-1);
+	if (arrayset) {
+		const char *ccp = NULL;
+
+		if (*array)
+			ccp = skip_varname(array, false);
+		if (!ccp || !(!ccp[0] || (ccp[0] == '+' && !ccp[1]))) {
+			bi_errorf("%s: %s", array, "is not an identifier");
+			return (-1);
+		}
 	}
 	if (sortargs) {
 		for (i = go.optind; argv[i]; i++)
@@ -409,7 +437,7 @@
 		    xstrcmp);
 	}
 	if (arrayset)
-		go.optind += set_array(array, arrayset > 0 ? true : false,
+		go.optind += set_array(array, tobool(arrayset > 0),
 		    argv + go.optind);
 
 	return (go.optind);
@@ -456,10 +484,83 @@
 	int rv;
 
 	if (!(rv = getn(as, ai)))
-		bi_errorf("%s: bad number", as);
+		bi_errorf("%s: %s", as, "bad number");
 	return (rv);
 }
 
+/**
+ * pattern simplifications:
+ * - @(x) -> x (not @(x|y) though)
+ * - ** -> *
+ */
+static void *
+simplify_gmatch_pattern(const unsigned char *sp)
+{
+	uint8_t c;
+	unsigned char *cp, *dp;
+	const unsigned char *ps, *se;
+
+	cp = alloc(strlen((const void *)sp) + 1, ATEMP);
+	goto simplify_gmatch_pat1a;
+
+	/* foo@(b@(a)r)b@(a|a)z -> foobarb@(a|a)z */
+ simplify_gmatch_pat1:
+	sp = cp;
+ simplify_gmatch_pat1a:
+	dp = cp;
+	se = sp + strlen((const void *)sp);
+	while ((c = *sp++)) {
+		if (!ISMAGIC(c)) {
+			*dp++ = c;
+			continue;
+		}
+		switch ((c = *sp++)) {
+		case 0x80|'@':
+		/* simile for @ */
+		case 0x80|' ':
+			/* check whether it has only one clause */
+			ps = pat_scan(sp, se, true);
+			if (!ps || ps[-1] != /*(*/ ')')
+				/* nope */
+				break;
+			/* copy inner clause until matching close */
+			ps -= 2;
+			while ((const unsigned char *)sp < ps)
+				*dp++ = *sp++;
+			/* skip MAGIC and closing parenthesis */
+			sp += 2;
+			/* copy the rest of the pattern */
+			memmove(dp, sp, strlen((const void *)sp) + 1);
+			/* redo from start */
+			goto simplify_gmatch_pat1;
+		}
+		*dp++ = MAGIC;
+		*dp++ = c;
+	}
+	*dp = '\0';
+
+	/* collapse adjacent asterisk wildcards */
+	sp = dp = cp;
+	while ((c = *sp++)) {
+		if (!ISMAGIC(c)) {
+			*dp++ = c;
+			continue;
+		}
+		switch ((c = *sp++)) {
+		case '*':
+			while (ISMAGIC(sp[0]) && sp[1] == c)
+				sp += 2;
+			break;
+		}
+		*dp++ = MAGIC;
+		*dp++ = c;
+	}
+	*dp = '\0';
+
+	/* return the result, allocated from ATEMP */
+	return (cp);
+}
+
 /* -------- gmatch.c -------- */
 
 /*
@@ -469,18 +570,20 @@
  * Match a pattern as in sh(1).
  * pattern character are prefixed with MAGIC by expand.
  */
-
 int
 gmatchx(const char *s, const char *p, bool isfile)
 {
 	const char *se, *pe;
+	char *pnew;
+	int rv;
 
 	if (s == NULL || p == NULL)
 		return (0);
 
 	se = s + strlen(s);
 	pe = p + strlen(p);
-	/* isfile is false iff no syntax check has been done on
+	/*
+	 * isfile is false iff no syntax check has been done on
 	 * the pattern. If check fails, just to a strcmp().
 	 */
 	if (!isfile && !has_globbing(p, pe)) {
@@ -490,11 +593,22 @@
 		debunk(t, p, len);
 		return (!strcmp(t, s));
 	}
-	return (do_gmatch((const unsigned char *) s, (const unsigned char *) se,
-	    (const unsigned char *) p, (const unsigned char *) pe));
+
+	/*
+	 * since the do_gmatch() engine sucks so much, we must do some
+	 * pattern simplifications
+	 */
+	pnew = simplify_gmatch_pattern((const unsigned char *)p);
+	pe = pnew + strlen(pnew);
+
+	rv = do_gmatch((const unsigned char *)s, (const unsigned char *)se,
+	    (const unsigned char *)pnew, (const unsigned char *)pe);
+	afree(pnew, ATEMP);
+	return (rv);
 }
 
-/* Returns if p is a syntacticly correct globbing pattern, false
+/**
+ * Returns if p is a syntacticly correct globbing pattern, false
  * if it contains no pattern characters or if there is a syntax error.
  * Syntax errors are:
  *	- [ with no closing ]
@@ -502,14 +616,14 @@
  *	- [...] and *(...) not nested (eg, [a$(b|]c), *(a[b|c]d))
  */
 /*XXX
-- if no magic,
-	if dest given, copy to dst
-	return ?
-- if magic && (no globbing || syntax error)
-	debunk to dst
-	return ?
-- return ?
-*/
+ * - if no magic,
+ *	if dest given, copy to dst
+ *	return ?
+ * - if magic && (no globbing || syntax error)
+ *	debunk to dst
+ *	return ?
+ * - return ?
+ */
 int
 has_globbing(const char *xp, const char *xpe)
 {
@@ -517,42 +631,46 @@
 	const unsigned char *pe = (const unsigned char *) xpe;
 	int c;
 	int nest = 0, bnest = 0;
-	int saw_glob = 0;
-	int in_bracket = 0; /* inside [...] */
+	bool saw_glob = false;
+	/* inside [...] */
+	bool in_bracket = false;
 
 	for (; p < pe; p++) {
 		if (!ISMAGIC(*p))
 			continue;
 		if ((c = *++p) == '*' || c == '?')
-			saw_glob = 1;
+			saw_glob = true;
 		else if (c == '[') {
 			if (!in_bracket) {
-				saw_glob = 1;
-				in_bracket = 1;
+				saw_glob = true;
+				in_bracket = true;
 				if (ISMAGIC(p[1]) && p[2] == NOT)
 					p += 2;
 				if (ISMAGIC(p[1]) && p[2] == ']')
 					p += 2;
 			}
-			/* XXX Do we need to check ranges here? POSIX Q */
+			/*XXX Do we need to check ranges here? POSIX Q */
 		} else if (c == ']') {
 			if (in_bracket) {
-				if (bnest)		/* [a*(b]) */
+				if (bnest)
+					/* [a*(b]) */
 					return (0);
-				in_bracket = 0;
+				in_bracket = false;
 			}
 		} else if ((c & 0x80) && vstrchr("*+?@! ", c & 0x7f)) {
-			saw_glob = 1;
+			saw_glob = true;
 			if (in_bracket)
 				bnest++;
 			else
 				nest++;
 		} else if (c == '|') {
-			if (in_bracket && !bnest)	/* *(a[foo|bar]) */
+			if (in_bracket && !bnest)
+				/* *(a[foo|bar]) */
 				return (0);
 		} else if (c == /*(*/ ')') {
 			if (in_bracket) {
-				if (!bnest--)		/* *(a[b)c] */
+				if (!bnest--)
+					/* *(a[b)c] */
 					return (0);
 			} else if (nest)
 				nest--;
@@ -614,9 +732,12 @@
 		 * [*+?@!](pattern|pattern|..)
 		 * This is also needed for ${..%..}, etc.
 		 */
-		case 0x80|'+': /* matches one or more times */
-		case 0x80|'*': /* matches zero or more times */
-			if (!(prest = pat_scan(p, pe, 0)))
+
+		/* matches one or more times */
+		case 0x80|'+':
+		/* matches zero or more times */
+		case 0x80|'*':
+			if (!(prest = pat_scan(p, pe, false)))
 				return (0);
 			s--;
 			/* take care of zero matches */
@@ -624,7 +745,7 @@
 			    do_gmatch(s, se, prest, pe))
 				return (1);
 			for (psub = p; ; psub = pnext) {
-				pnext = pat_scan(psub, pe, 1);
+				pnext = pat_scan(psub, pe, true);
 				for (srest = s; srest <= se; srest++) {
 					if (do_gmatch(s, srest, psub, pnext - 2) &&
 					    (do_gmatch(srest, se, prest, pe) ||
@@ -637,10 +758,13 @@
 			}
 			return (0);
 
-		case 0x80|'?': /* matches zero or once */
-		case 0x80|'@': /* matches one of the patterns */
-		case 0x80|' ': /* simile for @ */
-			if (!(prest = pat_scan(p, pe, 0)))
+		/* matches zero or once */
+		case 0x80|'?':
+		/* matches one of the patterns */
+		case 0x80|'@':
+		/* simile for @ */
+		case 0x80|' ':
+			if (!(prest = pat_scan(p, pe, false)))
 				return (0);
 			s--;
 			/* Take care of zero matches */
@@ -648,7 +772,7 @@
 			    do_gmatch(s, se, prest, pe))
 				return (1);
 			for (psub = p; ; psub = pnext) {
-				pnext = pat_scan(psub, pe, 1);
+				pnext = pat_scan(psub, pe, true);
 				srest = prest == pe ? se : s;
 				for (; srest <= se; srest++) {
 					if (do_gmatch(s, srest, psub, pnext - 2) &&
@@ -660,15 +784,16 @@
 			}
 			return (0);
 
-		case 0x80|'!': /* matches none of the patterns */
-			if (!(prest = pat_scan(p, pe, 0)))
+		/* matches none of the patterns */
+		case 0x80|'!':
+			if (!(prest = pat_scan(p, pe, false)))
 				return (0);
 			s--;
 			for (srest = s; srest <= se; srest++) {
 				int matched = 0;
 
 				for (psub = p; ; psub = pnext) {
-					pnext = pat_scan(psub, pe, 1);
+					pnext = pat_scan(psub, pe, true);
 					if (do_gmatch(s, srest, psub,
 					    pnext - 2)) {
 						matched = 1;
@@ -705,9 +830,11 @@
 		if (ISMAGIC(c)) {
 			c = *p++;
 			if ((c & 0x80) && !ISMAGIC(c)) {
-				c &= 0x7f;/* extended pattern matching: *+?@! */
+				/* extended pattern matching: *+?@! */
+				c &= 0x7F;
 				/* XXX the ( char isn't handled as part of [] */
-				if (c == ' ') /* simile for @: plain (..) */
+				if (c == ' ')
+					/* simile for @: plain (..) */
 					c = '(' /*)*/;
 			}
 		}
@@ -716,7 +843,8 @@
 			return (sub == '[' ? orig_p : NULL);
 		if (ISMAGIC(p[0]) && p[1] == '-' &&
 		    (!ISMAGIC(p[2]) || p[3] != ']')) {
-			p += 2; /* MAGIC- */
+			/* MAGIC- */
+			p += 2;
 			d = *p++;
 			if (ISMAGIC(d)) {
 				d = *p++;
@@ -736,8 +864,8 @@
 }
 
 /* Look for next ) or | (if match_sep) in *(foo|bar) pattern */
-const unsigned char *
-pat_scan(const unsigned char *p, const unsigned char *pe, int match_sep)
+static const unsigned char *
+pat_scan(const unsigned char *p, const unsigned char *pe, bool match_sep)
 {
 	int nest = 0;
 
@@ -772,7 +900,8 @@
 }
 
 
-/* getopt() used for shell built-in commands, the getopts command, and
+/**
+ * getopt() used for shell built-in commands, the getopts command, and
  * command line options.
  * A leading ':' in options means don't print errors, instead return '?'
  * or ':' and set go->optarg to the offending option character.
@@ -813,7 +942,8 @@
 			return (-1);
 		}
 		if (arg == NULL ||
-		    ((flag != '-' ) && /* neither a - nor a + (if + allowed) */
+		    ((flag != '-' ) &&
+		    /* neither a - nor a + (if + allowed) */
 		    (!(go->flags & GF_PLUSOPT) || flag != '+')) ||
 		    (c = arg[1]) == '\0') {
 			go->p = 0;
@@ -830,15 +960,17 @@
 			go->buf[0] = c;
 			go->optarg = go->buf;
 		} else {
-			warningf(true, "%s%s-%c: unknown option",
+			warningf(true, "%s%s-%c: %s",
 			    (go->flags & GF_NONAME) ? "" : argv[0],
-			    (go->flags & GF_NONAME) ? "" : ": ", c);
+			    (go->flags & GF_NONAME) ? "" : ": ", c,
+			    "unknown option");
 			if (go->flags & GF_ERROR)
 				bi_errorfz();
 		}
 		return ('?');
 	}
-	/* : means argument must be present, may be part of option argument
+	/**
+	 * : means argument must be present, may be part of option argument
 	 *   or the next argument
 	 * ; same as : but argument may be missing
 	 * , means argument is part of option argument, and may be null.
@@ -856,9 +988,10 @@
 				go->optarg = go->buf;
 				return (':');
 			}
-			warningf(true, "%s%s-'%c' requires argument",
+			warningf(true, "%s%s-%c: %s",
 			    (go->flags & GF_NONAME) ? "" : argv[0],
-			    (go->flags & GF_NONAME) ? "" : ": ", c);
+			    (go->flags & GF_NONAME) ? "" : ": ", c,
+			    "requires an argument");
 			if (go->flags & GF_ERROR)
 				bi_errorfz();
 			return ('?');
@@ -869,7 +1002,8 @@
 		go->optarg = argv[go->optind - 1] + go->p;
 		go->p = 0;
 	} else if (*o == '#') {
-		/* argument is optional and may be attached or unattached
+		/*
+		 * argument is optional and may be attached or unattached
 		 * but must start with a digit. optarg is set to 0 if the
 		 * argument is missing.
 		 */
@@ -890,7 +1024,8 @@
 	return (c);
 }
 
-/* print variable/alias value using necessary quotes
+/*
+ * print variable/alias value using necessary quotes
  * (POSIX says they should be suitable for re-entry...)
  * No trailing newline is printed.
  */
@@ -898,25 +1033,33 @@
 print_value_quoted(const char *s)
 {
 	const char *p;
-	int inquote = 0;
+	bool inquote = false;
 
-	/* Test if any quotes are needed */
+	/* first, check whether any quotes are needed */
 	for (p = s; *p; p++)
 		if (ctype(*p, C_QUOTE))
 			break;
 	if (!*p) {
+		/* nope, use the shortcut */
 		shf_puts(s, shl_stdout);
 		return;
 	}
+
+	/* quote via state machine */
 	for (p = s; *p; p++) {
 		if (*p == '\'') {
-			if (inquote)
+			/*
+			 * multiple '''s or any ' at beginning of string
+			 * look nicer this way than when simply substituting
+			 */
+			if (inquote) {
 				shf_putc('\'', shl_stdout);
+				inquote = false;
+			}
 			shf_putc('\\', shl_stdout);
-			inquote = 0;
 		} else if (!inquote) {
 			shf_putc('\'', shl_stdout);
-			inquote = 1;
+			inquote = true;
 		}
 		shf_putc(*p, shl_stdout);
 	}
@@ -930,10 +1073,10 @@
  */
 void
 print_columns(struct shf *shf, int n,
-    char *(*func)(char *, int, int, const void *),
-    const void *arg, int max_oct, int max_col, bool prefcol)
+    char *(*func)(char *, size_t, int, const void *),
+    const void *arg, size_t max_oct, size_t max_colz, bool prefcol)
 {
-	int i, r, c, rows, cols, nspace;
+	int i, r, c, rows, cols, nspace, max_col;
 	char *str;
 
 	if (n <= 0) {
@@ -943,6 +1086,15 @@
 		return;
 	}
 
+	if (max_colz > 2147483647) {
+#ifndef MKSH_SMALL
+		internal_warningf("print_columns called with max_col=%zu > INT_MAX",
+		    max_colz);
+#endif
+		return;
+	}
+	max_col = (int)max_colz;
+
 	++max_oct;
 	str = alloc(max_oct, ATEMP);
 
@@ -998,8 +1150,9 @@
 {
 	char *dst;
 
-	/* nbytes check because some systems (older FreeBSDs) have a buggy
-	 * memchr()
+	/*
+	 * nbytes check because some systems (older FreeBSDs) have a
+	 * buggy memchr()
 	 */
 	if (nbytes && (dst = memchr(buf, '\0', nbytes))) {
 		char *end = buf + nbytes;
@@ -1019,19 +1172,20 @@
 	}
 }
 
-/* Like read(2), but if read fails due to non-blocking flag, resets flag
- * and restarts read.
+/*
+ * Like read(2), but if read fails due to non-blocking flag,
+ * resets flag and restarts read.
  */
-int
-blocking_read(int fd, char *buf, int nbytes)
+ssize_t
+blocking_read(int fd, char *buf, size_t nbytes)
 {
-	int ret;
-	int tried_reset = 0;
+	ssize_t ret;
+	bool tried_reset = false;
 
 	while ((ret = read(fd, buf, nbytes)) < 0) {
 		if (!tried_reset && errno == EAGAIN) {
 			if (reset_nonblock(fd) > 0) {
-				tried_reset = 1;
+				tried_reset = true;
 				continue;
 			}
 			errno = EAGAIN;
@@ -1041,7 +1195,8 @@
 	return (ret);
 }
 
-/* Reset the non-blocking flag on the specified file descriptor.
+/*
+ * Reset the non-blocking flag on the specified file descriptor.
  * Returns -1 if there was an error, 0 if non-blocking wasn't set,
  * 1 if it was.
  */
@@ -1060,34 +1215,225 @@
 	return (1);
 }
 
-
-/* Like getcwd(), except bsize is ignored if buf is 0 (PATH_MAX is used) */
+/* getcwd(3) equivalent, allocates from ATEMP but doesn't resize */
 char *
-ksh_get_wd(size_t *dlen)
+ksh_get_wd(void)
 {
-	char *ret, *b;
-	size_t len = 1;
-
 #ifdef NO_PATH_MAX
-	if ((b = get_current_dir_name())) {
-		len = strlen(b) + 1;
-		strndupx(ret, b, len - 1, ATEMP);
-		free(b);
+	char *rv, *cp;
+
+	if ((cp = get_current_dir_name())) {
+		strdupx(rv, cp, ATEMP);
+		free_gnu_gcdn(cp);
 	} else
-		ret = NULL;
+		rv = NULL;
 #else
-	if ((ret = getcwd((b = alloc(PATH_MAX + 1, ATEMP)), PATH_MAX)))
-		ret = aresize(b, len = (strlen(b) + 1), ATEMP);
-	else
-		afree(b, ATEMP);
+	char *rv;
+
+	if (!getcwd((rv = alloc(PATH_MAX + 1, ATEMP)), PATH_MAX)) {
+		afree(rv, ATEMP);
+		rv = NULL;
+	}
 #endif
 
-	if (dlen)
-		*dlen = len;
-	return (ret);
+	return (rv);
 }
 
-/*
+char *
+do_realpath(const char *upath)
+{
+	char *xp, *ip, *tp, *ipath, *ldest = NULL;
+	XString xs;
+	ptrdiff_t pos;
+	size_t len;
+	int llen;
+	struct stat sb;
+#ifdef NO_PATH_MAX
+	size_t ldestlen = 0;
+#define pathlen sb.st_size
+#define pathcnd (ldestlen < (pathlen + 1))
+#else
+#define pathlen PATH_MAX
+#define pathcnd (!ldest)
+#endif
+	/* max. recursion depth */
+	int symlinks = 32;
+
+	if (upath[0] == '/') {
+		/* upath is an absolute pathname */
+		strdupx(ipath, upath, ATEMP);
+	} else {
+		/* upath is a relative pathname, prepend cwd */
+		if ((tp = ksh_get_wd()) == NULL || tp[0] != '/')
+			return (NULL);
+		ipath = shf_smprintf("%s%s%s", tp, "/", upath);
+		afree(tp, ATEMP);
+	}
+
+	/* ipath and upath are in memory at the same time -> unchecked */
+	Xinit(xs, xp, strlen(ip = ipath) + 1, ATEMP);
+
+	/* now jump into the deep of the loop */
+	goto beginning_of_a_pathname;
+
+	while (*ip) {
+		/* skip slashes in input */
+		while (*ip == '/')
+			++ip;
+		if (!*ip)
+			break;
+
+		/* get next pathname component from input */
+		tp = ip;
+		while (*ip && *ip != '/')
+			++ip;
+		len = ip - tp;
+
+		/* check input for "." and ".." */
+		if (tp[0] == '.') {
+			if (len == 1)
+				/* just continue with the next one */
+				continue;
+			else if (len == 2 && tp[1] == '.') {
+				/* strip off last pathname component */
+				while (xp > Xstring(xs, xp))
+					if (*--xp == '/')
+						break;
+				/* then continue with the next one */
+				continue;
+			}
+		}
+
+		/* store output position away, then append slash to output */
+		pos = Xsavepos(xs, xp);
+		/* 1 for the '/' and len + 1 for tp and the NUL from below */
+		XcheckN(xs, xp, 1 + len + 1);
+		Xput(xs, xp, '/');
+
+		/* append next pathname component to output */
+		memcpy(xp, tp, len);
+		xp += len;
+		*xp = '\0';
+
+		/* lstat the current output, see if it's a symlink */
+		if (lstat(Xstring(xs, xp), &sb)) {
+			/* lstat failed */
+			if (errno == ENOENT) {
+				/* because the pathname does not exist */
+				while (*ip == '/')
+					/* skip any trailing slashes */
+					++ip;
+				/* no more components left? */
+				if (!*ip)
+					/* we can still return successfully */
+					break;
+				/* more components left? fall through */
+			}
+			/* not ENOENT or not at the end of ipath */
+			goto notfound;
+		}
+
+		/* check if we encountered a symlink? */
+		if (S_ISLNK(sb.st_mode)) {
+			/* reached maximum recursion depth? */
+			if (!symlinks--) {
+				/* yep, prevent infinite loops */
+				errno = ELOOP;
+				goto notfound;
+			}
+
+			/* get symlink(7) target */
+			if (pathcnd) {
+#ifdef NO_PATH_MAX
+				if (notoktoadd(pathlen, 1)) {
+					errno = ENAMETOOLONG;
+					goto notfound;
+				}
+#endif
+				ldest = aresize(ldest, pathlen + 1, ATEMP);
+			}
+			llen = readlink(Xstring(xs, xp), ldest, pathlen);
+			if (llen < 0)
+				/* oops... */
+				goto notfound;
+			ldest[llen] = '\0';
+
+			/*
+			 * restart if symlink target is an absolute path,
+			 * otherwise continue with currently resolved prefix
+			 */
+			/* append rest of current input path to link target */
+			tp = shf_smprintf("%s%s%s", ldest, *ip ? "/" : "", ip);
+			afree(ipath, ATEMP);
+			ip = ipath = tp;
+			if (ldest[0] != '/') {
+				/* symlink target is a relative path */
+				xp = Xrestpos(xs, xp, pos);
+			} else {
+				/* symlink target is an absolute path */
+				xp = Xstring(xs, xp);
+ beginning_of_a_pathname:
+				/* assert: (ip == ipath)[0] == '/' */
+				/* assert: xp == xs.beg => start of path */
+
+				/* exactly two leading slashes? (SUSv4 3.266) */
+				if (ip[1] == '/' && ip[2] != '/') {
+					/* keep them, e.g. for UNC pathnames */
+					Xput(xs, xp, '/');
+				}
+			}
+		}
+		/* otherwise (no symlink) merely go on */
+	}
+
+	/*
+	 * either found the target and successfully resolved it,
+	 * or found its parent directory and may create it
+	 */
+	if (Xlength(xs, xp) == 0)
+		/*
+		 * if the resolved pathname is "", make it "/",
+		 * otherwise do not add a trailing slash
+		 */
+		Xput(xs, xp, '/');
+	Xput(xs, xp, '\0');
+
+	/*
+	 * if source path had a trailing slash, check if target path
+	 * is not a non-directory existing file
+	 */
+	if (ip > ipath && ip[-1] == '/') {
+		if (stat(Xstring(xs, xp), &sb)) {
+			if (errno != ENOENT)
+				goto notfound;
+		} else if (!S_ISDIR(sb.st_mode)) {
+			errno = ENOTDIR;
+			goto notfound;
+		}
+		/* target now either does not exist or is a directory */
+	}
+
+	/* return target path */
+	if (ldest != NULL)
+		afree(ldest, ATEMP);
+	afree(ipath, ATEMP);
+	return (Xclose(xs, xp));
+
+ notfound:
+	/* save; freeing memory might trash it */
+	llen = errno;
+	if (ldest != NULL)
+		afree(ldest, ATEMP);
+	afree(ipath, ATEMP);
+	Xfree(xs, xp);
+	errno = llen;
+	return (NULL);
+
+#undef pathlen
+#undef pathcnd
+}
+
+/**
  *	Makes a filename into result using the following algorithm.
  *	- make result NULL
  *	- if file starts with '/', append file to result & set cdpathp to NULL
@@ -1102,16 +1448,17 @@
  *	The return value indicates whether a non-null element from cdpathp
  *	was appended to result.
  */
-int
+static int
 make_path(const char *cwd, const char *file,
-    char **cdpathp,		/* & of : separated list */
+    /* pointer to colon-separated list */
+    char **cdpathp,
     XString *xsp,
     int *phys_pathp)
 {
 	int rval = 0;
 	bool use_cdpath = true;
 	char *plist;
-	int len, plen = 0;
+	size_t len, plen = 0;
 	char *xp = Xstring(*xsp, xp);
 
 	if (!file)
@@ -1172,96 +1519,296 @@
 	return (rval);
 }
 
-/*
+/*-
  * Simplify pathnames containing "." and ".." entries.
- * ie, simplify_path("/a/b/c/./../d/..") returns "/a/b"
+ *
+ * simplify_path(this)			= that
+ * /a/b/c/./../d/..			/a/b
+ * //./C/foo/bar/../baz			//C/foo/baz
+ * /foo/				/foo
+ * /foo/../../bar			/bar
+ * /foo/./blah/..			/foo
+ * .					.
+ * ..					..
+ * ./foo				foo
+ * foo/../../../bar			../../bar
  */
 void
-simplify_path(char *pathl)
+simplify_path(char *p)
 {
-	char *cur, *t;
-	bool isrooted;
-	char *very_start = pathl, *start;
+	char *dp, *ip, *sp, *tp;
+	size_t len;
+	bool needslash;
 
-	if (!*pathl)
+	switch (*p) {
+	case 0:
 		return;
+	case '/':
+		/* exactly two leading slashes? (SUSv4 3.266) */
+		if (p[1] == '/' && p[2] != '/')
+			/* keep them, e.g. for UNC pathnames */
+			++p;
+		needslash = true;
+		break;
+	default:
+		needslash = false;
+	}
+	dp = ip = sp = p;
 
-	if ((isrooted = pathl[0] == '/'))
-		very_start++;
-
-	/* Before			After
-	 * /foo/			/foo
-	 * /foo/../../bar		/bar
-	 * /foo/./blah/..		/foo
-	 * .				.
-	 * ..				..
-	 * ./foo			foo
-	 * foo/../../../bar		../../bar
-	 */
-
-	for (cur = t = start = very_start; ; ) {
-		/* treat multiple '/'s as one '/' */
-		while (*t == '/')
-			t++;
-
-		if (*t == '\0') {
-			if (cur == pathl)
-				/* convert empty path to dot */
-				*cur++ = '.';
-			*cur = '\0';
+	while (*ip) {
+		/* skip slashes in input */
+		while (*ip == '/')
+			++ip;
+		if (!*ip)
 			break;
-		}
 
-		if (t[0] == '.') {
-			if (!t[1] || t[1] == '/') {
-				t += 1;
+		/* get next pathname component from input */
+		tp = ip;
+		while (*ip && *ip != '/')
+			++ip;
+		len = ip - tp;
+
+		/* check input for "." and ".." */
+		if (tp[0] == '.') {
+			if (len == 1)
+				/* just continue with the next one */
 				continue;
-			} else if (t[1] == '.' && (!t[2] || t[2] == '/')) {
-				if (!isrooted && cur == start) {
-					if (cur != very_start)
-						*cur++ = '/';
-					*cur++ = '.';
-					*cur++ = '.';
-					start = cur;
-				} else if (cur != start)
-					while (--cur > start && *cur != '/')
-						;
-				t += 2;
+			else if (len == 2 && tp[1] == '.') {
+				/* parent level, but how? */
+				if (*p == '/')
+					/* absolute path, only one way */
+					goto strip_last_component;
+				else if (dp > sp) {
+					/* relative path, with subpaths */
+					needslash = false;
+ strip_last_component:
+					/* strip off last pathname component */
+					while (dp > sp)
+						if (*--dp == '/')
+							break;
+				} else {
+					/* relative path, at its beginning */
+					if (needslash)
+						/* or already dotdot-slash'd */
+						*dp++ = '/';
+					/* keep dotdot-slash if not absolute */
+					*dp++ = '.';
+					*dp++ = '.';
+					needslash = true;
+					sp = dp;
+				}
+				/* then continue with the next one */
 				continue;
 			}
 		}
 
-		if (cur != very_start)
-			*cur++ = '/';
+		if (needslash)
+			*dp++ = '/';
 
-		/* find/copy next component of pathname */
-		while (*t && *t != '/')
-			*cur++ = *t++;
+		/* append next pathname component to output */
+		memmove(dp, tp, len);
+		dp += len;
+
+		/* append slash if we continue */
+		needslash = true;
+		/* try next component */
 	}
+	if (dp == p)
+		/* empty path -> dot */
+		*dp++ = needslash ? '/' : '.';
+	*dp = '\0';
 }
 
-
 void
-set_current_wd(char *pathl)
+set_current_wd(const char *nwd)
 {
-	size_t len = 1;
-	char *p = pathl;
+	char *allocd = NULL;
 
-	if (p == NULL) {
-		if ((p = ksh_get_wd(&len)) == NULL)
-			p = null;
-	} else
-		len = strlen(p) + 1;
-
-	if (len > current_wd_size) {
-		afree(current_wd, APERM);
-		current_wd = alloc(current_wd_size = len, APERM);
+	if (nwd == NULL) {
+		allocd = ksh_get_wd();
+		nwd = allocd ? allocd : null;
 	}
-	memcpy(current_wd, p, len);
-	if (p != pathl && p != null)
-		afree(p, ATEMP);
+
+	afree(current_wd, APERM);
+	strdupx(current_wd, nwd, APERM);
+
+	afree(allocd, ATEMP);
 }
 
+int
+c_cd(const char **wp)
+{
+	int optc, rv, phys_path;
+	bool physical = tobool(Flag(FPHYSICAL));
+	/* was a node from cdpath added in? */
+	int cdnode;
+	/* show where we went?, error for $PWD */
+	bool printpath = false, eflag = false;
+	struct tbl *pwd_s, *oldpwd_s;
+	XString xs;
+	char *dir, *allocd = NULL, *tryp, *pwd, *cdpath;
+
+	while ((optc = ksh_getopt(wp, &builtin_opt, "eLP")) != -1)
+		switch (optc) {
+		case 'e':
+			eflag = true;
+			break;
+		case 'L':
+			physical = false;
+			break;
+		case 'P':
+			physical = true;
+			break;
+		case '?':
+			return (2);
+		}
+	wp += builtin_opt.optind;
+
+	if (Flag(FRESTRICTED)) {
+		bi_errorf("restricted shell - can't cd");
+		return (2);
+	}
+
+	pwd_s = global("PWD");
+	oldpwd_s = global("OLDPWD");
+
+	if (!wp[0]) {
+		/* No arguments - go home */
+		if ((dir = str_val(global("HOME"))) == null) {
+			bi_errorf("no home directory (HOME not set)");
+			return (2);
+		}
+	} else if (!wp[1]) {
+		/* One argument: - or dir */
+		strdupx(allocd, wp[0], ATEMP);
+		if (ksh_isdash((dir = allocd))) {
+			afree(allocd, ATEMP);
+			allocd = NULL;
+			dir = str_val(oldpwd_s);
+			if (dir == null) {
+				bi_errorf("no OLDPWD");
+				return (2);
+			}
+			printpath = true;
+		}
+	} else if (!wp[2]) {
+		/* Two arguments - substitute arg1 in PWD for arg2 */
+		size_t ilen, olen, nlen, elen;
+		char *cp;
+
+		if (!current_wd[0]) {
+			bi_errorf("can't determine current directory");
+			return (2);
+		}
+		/*
+		 * substitute arg1 for arg2 in current path.
+		 * if the first substitution fails because the cd fails
+		 * we could try to find another substitution. For now
+		 * we don't
+		 */
+		if ((cp = strstr(current_wd, wp[0])) == NULL) {
+			bi_errorf("bad substitution");
+			return (2);
+		}
+		/*-
+		 * ilen = part of current_wd before wp[0]
+		 * elen = part of current_wd after wp[0]
+		 * because current_wd and wp[1] need to be in memory at the
+		 * same time beforehand the addition can stay unchecked
+		 */
+		ilen = cp - current_wd;
+		olen = strlen(wp[0]);
+		nlen = strlen(wp[1]);
+		elen = strlen(current_wd + ilen + olen) + 1;
+		dir = allocd = alloc(ilen + nlen + elen, ATEMP);
+		memcpy(dir, current_wd, ilen);
+		memcpy(dir + ilen, wp[1], nlen);
+		memcpy(dir + ilen + nlen, current_wd + ilen + olen, elen);
+		printpath = true;
+	} else {
+		bi_errorf("too many arguments");
+		return (2);
+	}
+
+#ifdef NO_PATH_MAX
+	/* only a first guess; make_path will enlarge xs if necessary */
+	XinitN(xs, 1024, ATEMP);
+#else
+	XinitN(xs, PATH_MAX, ATEMP);
+#endif
+
+	cdpath = str_val(global("CDPATH"));
+	do {
+		cdnode = make_path(current_wd, dir, &cdpath, &xs, &phys_path);
+		if (physical)
+			rv = chdir(tryp = Xstring(xs, xp) + phys_path);
+		else {
+			simplify_path(Xstring(xs, xp));
+			rv = chdir(tryp = Xstring(xs, xp));
+		}
+	} while (rv < 0 && cdpath != NULL);
+
+	if (rv < 0) {
+		if (cdnode)
+			bi_errorf("%s: %s", dir, "bad directory");
+		else
+			bi_errorf("%s: %s", tryp, strerror(errno));
+		afree(allocd, ATEMP);
+		Xfree(xs, xp);
+		return (2);
+	}
+
+	rv = 0;
+
+	/* allocd (above) => dir, which is no longer used */
+	afree(allocd, ATEMP);
+	allocd = NULL;
+
+	/* Clear out tracked aliases with relative paths */
+	flushcom(false);
+
+	/*
+	 * Set OLDPWD (note: unsetting OLDPWD does not disable this
+	 * setting in AT&T ksh)
+	 */
+	if (current_wd[0])
+		/* Ignore failure (happens if readonly or integer) */
+		setstr(oldpwd_s, current_wd, KSH_RETURN_ERROR);
+
+	if (Xstring(xs, xp)[0] != '/') {
+		pwd = NULL;
+	} else if (!physical) {
+		goto norealpath_PWD;
+	} else if ((pwd = allocd = do_realpath(Xstring(xs, xp))) == NULL) {
+		if (eflag)
+			rv = 1;
+ norealpath_PWD:
+		pwd = Xstring(xs, xp);
+	}
+
+	/* Set PWD */
+	if (pwd) {
+		char *ptmp = pwd;
+
+		set_current_wd(ptmp);
+		/* Ignore failure (happens if readonly or integer) */
+		setstr(pwd_s, ptmp, KSH_RETURN_ERROR);
+	} else {
+		set_current_wd(null);
+		pwd = Xstring(xs, xp);
+		/* XXX unset $PWD? */
+		if (eflag)
+			rv = 1;
+	}
+	if (printpath || cdnode)
+		shprintf("%s\n", pwd);
+
+	afree(allocd, ATEMP);
+	Xfree(xs, xp);
+	return (rv);
+}
+
+
 #ifdef TIOCSCTTY
 extern void chvt_reinit(void);
 
@@ -1272,9 +1819,6 @@
 	struct stat sb;
 	int fd;
 
-	/* for entropy */
-	kshstate_f.h = evilhash(fn);
-
 	if (*fn == '-') {
 		memcpy(dv, "-/dev/null", sizeof("-/dev/null"));
 		fn = dv + 1;
@@ -1285,56 +1829,75 @@
 			if (stat(dv, &sb)) {
 				strlcpy(dv + 8, fn, sizeof(dv) - 8);
 				if (stat(dv, &sb))
-					errorf("chvt: can't find tty %s", fn);
+					errorf("%s: %s %s", "chvt",
+					    "can't find tty", fn);
 			}
 			fn = dv;
 		}
 		if (!(sb.st_mode & S_IFCHR))
-			errorf("chvt: not a char device: %s", fn);
+			errorf("%s %s %s", "chvt: not a char", "device", fn);
 		if ((sb.st_uid != 0) && chown(fn, 0, 0))
-			warningf(false, "chvt: cannot chown root %s", fn);
+			warningf(false, "%s: %s %s", "chvt", "can't chown root", fn);
 		if (((sb.st_mode & 07777) != 0600) && chmod(fn, (mode_t)0600))
-			warningf(false, "chvt: cannot chmod 0600 %s", fn);
+			warningf(false, "%s: %s %s", "chvt", "can't chmod 0600", fn);
 #if HAVE_REVOKE
 		if (revoke(fn))
 #endif
-			warningf(false, "chvt: cannot revoke %s, new shell is"
-			    " potentially insecure", fn);
+			warningf(false, "%s: %s %s", "chvt",
+			    "new shell is potentially insecure, can't revoke",
+			    fn);
 	}
 	if ((fd = open(fn, O_RDWR)) == -1) {
 		sleep(1);
 		if ((fd = open(fn, O_RDWR)) == -1)
-			errorf("chvt: cannot open %s", fn);
+			errorf("%s: %s %s", "chvt", "can't open", fn);
 	}
 	switch (fork()) {
 	case -1:
-		errorf("chvt: %s failed", "fork");
+		errorf("%s: %s %s", "chvt", "fork", "failed");
 	case 0:
 		break;
 	default:
 		exit(0);
 	}
 	if (setsid() == -1)
-		errorf("chvt: %s failed", "setsid");
+		errorf("%s: %s %s", "chvt", "setsid", "failed");
 	if (fn != dv + 1) {
 		if (ioctl(fd, TIOCSCTTY, NULL) == -1)
-			errorf("chvt: %s failed", "TIOCSCTTY");
+			errorf("%s: %s %s", "chvt", "TIOCSCTTY", "failed");
 		if (tcflush(fd, TCIOFLUSH))
-			errorf("chvt: %s failed", "TCIOFLUSH");
+			errorf("%s: %s %s", "chvt", "TCIOFLUSH", "failed");
 	}
 	ksh_dup2(fd, 0, false);
 	ksh_dup2(fd, 1, false);
 	ksh_dup2(fd, 2, false);
 	if (fd > 2)
 		close(fd);
+	{
+		register uint32_t h;
+
+		NZATInit(h);
+		NZATUpdateMem(h, &rndsetupstate, sizeof(rndsetupstate));
+		NZAATFinish(h);
+		rndset((long)h);
+	}
 	chvt_reinit();
 }
 #endif
 
 #ifdef DEBUG
-char longsizes_are_okay[sizeof(long) == sizeof(unsigned long) ? 1 : -1];
-char arisize_is_okay[sizeof(mksh_ari_t) == 4 ? 1 : -1];
-char uarisize_is_okay[sizeof(mksh_uari_t) == 4 ? 1 : -1];
+#define assert_eq(name, a, b) char name[a == b ? 1 : -1]
+#define assert_ge(name, a, b) char name[a >= b ? 1 : -1]
+assert_ge(intsize_is_okay, sizeof(int), 4);
+assert_eq(intsizes_are_okay, sizeof(int), sizeof(unsigned int));
+assert_ge(longsize_is_okay, sizeof(long), sizeof(int));
+assert_eq(arisize_is_okay, sizeof(mksh_ari_t), 4);
+assert_eq(uarisize_is_okay, sizeof(mksh_uari_t), 4);
+assert_eq(sizesizes_are_okay, sizeof(size_t), sizeof(ssize_t));
+assert_eq(ptrsizes_are_okay, sizeof(ptrdiff_t), sizeof(void *));
+assert_eq(ptrsize_is_sizet, sizeof(ptrdiff_t), sizeof(size_t));
+/* formatting routines assume this */
+assert_ge(ptr_fits_in_long, sizeof(long), sizeof(size_t));
 
 char *
 strchr(char *p, int ch)
@@ -1367,7 +1930,6 @@
 }
 #endif
 
-#ifndef MKSH_ASSUME_UTF8
 #if !HAVE_STRCASESTR
 const char *
 stristr(const char *b, const char *l)
@@ -1387,7 +1949,6 @@
 	return (b - 1);
 }
 #endif
-#endif
 
 #ifdef MKSH_SMALL
 char *
@@ -1527,15 +2088,15 @@
 		break;
 	case 'U':
 		i = 8;
-		if (0)
+		if (/* CONSTCOND */ 0)
 		/* FALLTHROUGH */
 	case 'u':
 		i = 4;
-		if (0)
+		if (/* CONSTCOND */ 0)
 		/* FALLTHROUGH */
 	case 'x':
 		i = cstyle ? -1 : 2;
-		/*
+		/**
 		 * x:	look for a hexadecimal number with up to
 		 *	two (C style: arbitrary) digits; convert
 		 *	to raw octet (C style: Unicode if >0xFF)
diff --git a/src/mksh.1 b/src/mksh.1
new file mode 100644
index 0000000..2c70ef0
--- /dev/null
+++ b/src/mksh.1
@@ -0,0 +1,6280 @@
+.\" $MirOS: src/bin/mksh/mksh.1,v 1.275 2011/10/07 19:51:29 tg Exp $
+.\" $OpenBSD: ksh.1,v 1.141 2011/09/03 22:59:08 jmc Exp $
+.\"-
+.\" Copyright © 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
+.\"		2010, 2011
+.\"	Thorsten Glaser <tg@mirbsd.org>
+.\"
+.\" Provided that these terms and disclaimer and all copyright notices
+.\" are retained or reproduced in an accompanying document, permission
+.\" is granted to deal in this work without restriction, including un‐
+.\" limited rights to use, publicly perform, distribute, sell, modify,
+.\" merge, give away, or sublicence.
+.\"
+.\" This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+.\" the utmost extent permitted by applicable law, neither express nor
+.\" implied; without malicious intent or gross negligence. In no event
+.\" may a licensor, author or contributor be held liable for indirect,
+.\" direct, other damage, loss, or other issues arising in any way out
+.\" of dealing in the work, even if advised of the possibility of such
+.\" damage or existence of a defect, except proven that it results out
+.\" of said person’s immediate fault when using the work as intended.
+.\"-
+.\" Try to make GNU groff and AT&T nroff more compatible
+.\" * ` generates ‘ in gnroff, so use \`
+.\" * ' generates ’ in gnroff, \' generates ´, so use \*(aq
+.\" * - generates ‐ in gnroff, \- generates −, so .tr it to -
+.\"   thus use - for hyphens and \- for minus signs and option dashes
+.\" * ~ is size-reduced and placed atop in groff, so use \*(TI
+.\" * ^ is size-reduced and placed atop in groff, so use \*(ha
+.\" * \(en does not work in nroff, so use \*(en
+.\" The section after the "doc" macropackage has been loaded contains
+.\" additional code to convene between the UCB mdoc macropackage (and
+.\" its variant as BSD mdoc in groff) and the GNU mdoc macropackage.
+.\"
+.ie \n(.g \{\
+.	if \*[.T]ascii .tr \-\N'45'
+.	if \*[.T]latin1 .tr \-\N'45'
+.	if \*[.T]utf8 .tr \-\N'45'
+.	ds <= \[<=]
+.	ds >= \[>=]
+.	ds Rq \[rq]
+.	ds Lq \[lq]
+.	ds sL \(aq
+.	ds sR \(aq
+.	if \*[.T]utf8 .ds sL `
+.	if \*[.T]ps .ds sL `
+.	if \*[.T]utf8 .ds sR '
+.	if \*[.T]ps .ds sR '
+.	ds aq \(aq
+.	ds TI \(ti
+.	ds ha \(ha
+.	ds en \(en
+.\}
+.el \{\
+.	ds aq '
+.	ds TI ~
+.	ds ha ^
+.	ds en \(em
+.\}
+.\"
+.\" Implement .Dd with the Mdocdate RCS keyword
+.\"
+.rn Dd xD
+.de Dd
+.ie \\$1$Mdocdate: \{\
+.	xD \\$2 \\$3, \\$4
+.\}
+.el .xD \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8
+..
+.\"
+.\" .Dd must come before definition of .Mx, because when called
+.\" with -mandoc, it might implement .Mx itself, but we want to
+.\" use our own definition. And .Dd must come *first*, always.
+.\"
+.Dd $Mdocdate: October 7 2011 $
+.\"
+.\" Check which macro package we use
+.\"
+.ie \n(.g \{\
+.	ie d volume-ds-1 .ds tT gnu
+.	el .ds tT bsd
+.\}
+.el .ds tT ucb
+.\"
+.\" Implement .Mx (MirBSD)
+.\"
+.ie "\*(tT"gnu" \{\
+.	eo
+.	de Mx
+.	nr curr-font \n[.f]
+.	nr curr-size \n[.ps]
+.	ds str-Mx \f[\n[curr-font]]\s[\n[curr-size]u]
+.	ds str-Mx1 \*[Tn-font-size]\%MirOS\*[str-Mx]
+.	if !\n[arg-limit] \
+.	if \n[.$] \{\
+.	ds macro-name Mx
+.	parse-args \$@
+.	\}
+.	if (\n[arg-limit] > \n[arg-ptr]) \{\
+.	nr arg-ptr +1
+.	ie (\n[type\n[arg-ptr]] == 2) \
+.	as str-Mx1 \~\*[arg\n[arg-ptr]]
+.	el \
+.	nr arg-ptr -1
+.	\}
+.	ds arg\n[arg-ptr] "\*[str-Mx1]
+.	nr type\n[arg-ptr] 2
+.	ds space\n[arg-ptr] "\*[space]
+.	nr num-args (\n[arg-limit] - \n[arg-ptr])
+.	nr arg-limit \n[arg-ptr]
+.	if \n[num-args] \
+.	parse-space-vector
+.	print-recursive
+..
+.	ec
+.	ds sP \s0
+.	ds tN \*[Tn-font-size]
+.\}
+.el \{\
+.	de Mx
+.	nr cF \\n(.f
+.	nr cZ \\n(.s
+.	ds aa \&\f\\n(cF\s\\n(cZ
+.	if \\n(aC==0 \{\
+.		ie \\n(.$==0 \&MirOS\\*(aa
+.		el .aV \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9
+.	\}
+.	if \\n(aC>\\n(aP \{\
+.		nr aP \\n(aP+1
+.		ie \\n(C\\n(aP==2 \{\
+.			as b1 \&MirOS\ #\&\\*(A\\n(aP\\*(aa
+.			ie \\n(aC>\\n(aP \{\
+.				nr aP \\n(aP+1
+.				nR
+.			\}
+.			el .aZ
+.		\}
+.		el \{\
+.			as b1 \&MirOS\\*(aa
+.			nR
+.		\}
+.	\}
+..
+.\}
+.\"-
+.Dt MKSH 1
+.Os MirBSD
+.Sh NAME
+.Nm mksh ,
+.Nm sh
+.Nd MirBSD Korn shell
+.Sh SYNOPSIS
+.Nm
+.Bk -words
+.Op Fl +abCefhiklmnprUuvXx
+.Op Fl T Ar /dev/ttyCn | \-
+.Op Fl +o Ar option
+.Oo
+.Fl c Ar string \*(Ba
+.Fl s \*(Ba
+.Ar file
+.Op Ar argument ...
+.Oc
+.Ek
+.Nm builtin-name
+.Op Ar argument ...
+.Sh DESCRIPTION
+.Nm
+is a command interpreter intended for both interactive and shell
+script use.
+Its command language is a superset of the
+.Xr sh C
+shell language and largely compatible to the original Korn shell.
+.Pp
+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.
+.Pp
+The options are as follows:
+.Bl -tag -width XcXstring
+.It Fl c Ar string
+.Nm
+will execute the command(s) contained in
+.Ar string .
+.It Fl i
+Interactive shell.
+A shell is
+.Dq interactive
+if this
+option is used or if both standard input and standard error are attached
+to a
+.Xr tty 4 .
+An interactive shell has job control enabled, ignores the
+.Dv SIGINT ,
+.Dv SIGQUIT ,
+and
+.Dv SIGTERM
+signals, and prints prompts before reading input (see the
+.Ev PS1
+and
+.Ev PS2
+parameters).
+It also processes the
+.Ev ENV
+parameter or the
+.Pa mkshrc
+file (see below).
+For non-interactive shells, the
+.Ic trackall
+option is on by default (see the
+.Ic set
+command below).
+.It Fl l
+Login shell.
+If the basename the shell is called with (i.e. argv[0])
+starts with
+.Ql \-
+or if this option is used,
+the shell is assumed to be a login shell; see
+.Sx Startup files
+below.
+.It Fl p
+Privileged shell.
+A shell is
+.Dq privileged
+if this option is used
+or if the real user ID or group ID does not match the
+effective user ID or group ID (see
+.Xr getuid 2
+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).
+For further implications, see
+.Sx Startup files .
+.It Fl r
+Restricted shell.
+A shell is
+.Dq restricted
+if this
+option is used.
+The following restrictions come into effect after the shell processes any
+profile and
+.Ev ENV
+files:
+.Pp
+.Bl -bullet -compact
+.It
+The
+.Ic cd
+.Po and Ic chdir Pc
+command is disabled.
+.It
+The
+.Ev SHELL ,
+.Ev ENV ,
+and
+.Ev PATH
+parameters cannot be changed.
+.It
+Command names can't be specified with absolute or relative paths.
+.It
+The
+.Fl p
+option of the built-in command
+.Ic command
+can't be used.
+.It
+Redirections that create files can't be used (i.e.\&
+.Ql \*(Gt ,
+.Ql \*(Gt\*(Ba ,
+.Ql \*(Gt\*(Gt ,
+.Ql \*(Lt\*(Gt ) .
+.El
+.It Fl s
+The shell reads commands from standard input; all non-option arguments
+are positional parameters.
+.It Fl T Ar tty
+Spawn
+.Nm
+on the
+.Xr tty 4
+device given.
+Superuser only.
+If
+.Ar tty
+is a dash, detach from controlling terminal (daemonise) instead.
+.El
+.Pp
+In addition to the above, the options described in the
+.Ic set
+built-in command can also be used on the command line:
+both
+.Op Fl +abCefhkmnuvXx
+and
+.Op Fl +o Ar option
+can be used for single letter or long options, respectively.
+.Pp
+If neither the
+.Fl c
+nor the
+.Fl s
+option is specified, the first non-option argument specifies the name
+of a file the shell reads commands from.
+If there are no non-option
+arguments, the shell reads commands from the standard input.
+The name of the shell (i.e. the contents of $0)
+is determined as follows: if the
+.Fl c
+option is used and there is a non-option argument, it is used as the name;
+if commands are being read from a file, the file is used as the name;
+otherwise, the basename the shell was called with (i.e. argv[0]) is used.
+.Pp
+The exit status of the shell is 127 if the command file specified on the
+command line could not be opened, or non-zero if a fatal syntax error
+occurred during the execution of a script.
+In the absence of fatal errors,
+the exit status is that of the last command executed, or zero, if no
+command is executed.
+.Ss Startup files
+For the actual location of these files, see
+.Sx FILES .
+A login shell processes the system profile first.
+A privileged shell then processes the suid profile.
+A non-privileged login shell processes the user profile next.
+A non-privileged interactive shell checks the value of the
+.Ev ENV
+parameter after subjecting it to parameter, command, arithmetic and tilde
+.Pq Sq \*(TI
+substitution; if unset or empty, the user mkshrc profile is processed;
+otherwise, if a file whose name is the substitution result exists,
+it is processed; non-existence is silently ignored.
+.Ss Command syntax
+The shell begins parsing its input by removing any backslash-newline
+combinations, then breaking it into
+.Em words .
+Words (which are sequences of characters) are delimited by unquoted whitespace
+characters (space, tab, and newline) or meta-characters
+.Po
+.Ql \*(Lt ,
+.Ql \*(Gt ,
+.Ql \*(Ba ,
+.Ql \&; ,
+.Ql \&( ,
+.Ql \&) ,
+and
+.Ql &
+.Pc .
+Aside from delimiting words, spaces and tabs are ignored, while newlines
+usually delimit commands.
+The meta-characters are used in building the following
+.Em tokens :
+.Ql \*(Lt ,
+.Ql \*(Lt& ,
+.Ql \*(Lt\*(Lt ,
+.Ql \*(Lt\*(Lt\*(Lt ,
+.Ql \*(Gt ,
+.Ql \*(Gt& ,
+.Ql \*(Gt\*(Gt ,
+.Ql &\*(Gt ,
+etc. are used to specify redirections (see
+.Sx Input/output redirection
+below);
+.Ql \*(Ba
+is used to create pipelines;
+.Ql \*(Ba&
+is used to create co-processes (see
+.Sx Co-processes
+below);
+.Ql \&;
+is used to separate commands;
+.Ql &
+is used to create asynchronous pipelines;
+.Ql &&
+and
+.Ql \*(Ba\*(Ba
+are used to specify conditional execution;
+.Ql ;; ,
+.Ql ;&\&
+and
+.Ql ;\*(Ba\&
+are used in
+.Ic case
+statements;
+.Ql \&(( .. ))
+is used in arithmetic expressions;
+and lastly,
+.Ql \&( .. )\&
+is used to create subshells.
+.Pp
+Whitespace and meta-characters can be quoted individually using a backslash
+.Pq Sq \e ,
+or in groups using double
+.Pq Sq \&"
+or single
+.Pq Sq \*(aq
+quotes.
+Note that the following characters are also treated specially by the
+shell and must be quoted if they are to represent themselves:
+.Ql \e ,
+.Ql \&" ,
+.Ql \*(aq ,
+.Ql # ,
+.Ql $ ,
+.Ql \` ,
+.Ql \*(TI ,
+.Ql { ,
+.Ql } ,
+.Ql * ,
+.Ql \&? ,
+and
+.Ql \&[ .
+The first three of these are the above mentioned quoting characters (see
+.Sx Quoting
+below);
+.Ql # ,
+if used at the beginning of a word, introduces a comment \*(en everything after
+the
+.Ql #
+up to the nearest newline is ignored;
+.Ql $
+is used to introduce parameter, command, and arithmetic substitutions (see
+.Sx Substitution
+below);
+.Ql \`
+introduces an old-style command substitution (see
+.Sx Substitution
+below);
+.Ql \*(TI
+begins a directory expansion (see
+.Sx Tilde expansion
+below);
+.Ql {
+and
+.Ql }
+delimit
+.Xr csh 1 Ns -style
+alterations (see
+.Sx Brace expansion
+below);
+and finally,
+.Ql * ,
+.Ql \&? ,
+and
+.Ql \&[
+are used in file name generation (see
+.Sx File name patterns
+below).
+.Pp
+As words and tokens are parsed, the shell builds commands, of which there
+are two basic types:
+.Em simple-commands ,
+typically programmes that are executed, and
+.Em compound-commands ,
+such as
+.Ic for
+and
+.Ic if
+statements, grouping constructs, and function definitions.
+.Pp
+A simple-command consists of some combination of parameter assignments
+(see
+.Sx Parameters
+below),
+input/output redirections (see
+.Sx Input/output redirections
+below),
+and command words; the only restriction is that parameter assignments come
+before any command words.
+The command words, if any, define the command
+that is to be executed and its arguments.
+The command may be a shell built-in command, a function,
+or an external command
+(i.e. a separate executable file that is located using the
+.Ev PATH
+parameter; see
+.Sx Command execution
+below).
+Note that all command constructs have an exit status: for external commands,
+this is related to the status returned by
+.Xr wait 2
+(if the command could not be found, the exit status is 127; if it could not
+be executed, the exit status is 126); the exit status of other command
+constructs (built-in commands, functions, compound-commands, pipelines, lists,
+etc.) are all well-defined and are described where the construct is
+described.
+The exit status of a command consisting only of parameter
+assignments is that of the last command substitution performed during the
+parameter assignment or 0 if there were no command substitutions.
+.Pp
+Commands can be chained together using the
+.Ql \*(Ba
+token to form pipelines, in which the standard output of each command but the
+last is piped (see
+.Xr pipe 2 )
+to the standard input of the following command.
+The exit status of a pipeline is that of its last command.
+All commands of a pipeline are executed in separate subshells;
+this is allowed by POSIX but differs from both variants of
+.At
+.Nm ksh ,
+where all but the last command were executed in subshells; see the
+.Ic read
+builtin's description for implications and workarounds.
+A pipeline may be prefixed by the
+.Ql \&!
+reserved word which causes the exit status of the pipeline to be logically
+complemented: if the original status was 0, the complemented status will be 1;
+if the original status was not 0, the complemented status will be 0.
+.Pp
+.Em Lists
+of commands can be created by separating pipelines by any of the following
+tokens:
+.Ql && ,
+.Ql \*(Ba\*(Ba ,
+.Ql & ,
+.Ql \*(Ba& ,
+and
+.Ql \&; .
+The first two are for conditional execution:
+.Dq Ar cmd1 No && Ar cmd2
+executes
+.Ar cmd2
+only if the exit status of
+.Ar cmd1
+is zero;
+.Ql \*(Ba\*(Ba
+is the opposite \*(en
+.Ar cmd2
+is executed only if the exit status of
+.Ar cmd1
+is non-zero.
+.Ql &&
+and
+.Ql \*(Ba\*(Ba
+have equal precedence which is higher than that of
+.Ql & ,
+.Ql \*(Ba& ,
+and
+.Ql \&; ,
+which also have equal precedence.
+Note that the
+.Ql &&
+and
+.Ql \*(Ba\*(Ba
+operators are
+.Qq left-associative .
+For example, both of these commands will print only
+.Qq bar :
+.Bd -literal -offset indent
+$ false && echo foo \*(Ba\*(Ba echo bar
+$ true \*(Ba\*(Ba echo foo && echo bar
+.Ed
+.Pp
+The
+.Ql &
+token causes the preceding command to be executed asynchronously; that is,
+the shell starts the command but does not wait for it to complete (the shell
+does keep track of the status of asynchronous commands; see
+.Sx Job control
+below).
+When an asynchronous command is started when job control is disabled
+(i.e. in most scripts), the command is started with signals
+.Dv SIGINT
+and
+.Dv SIGQUIT
+ignored and with input redirected from
+.Pa /dev/null
+(however, redirections specified in the asynchronous command have precedence).
+The
+.Ql \*(Ba&
+operator starts a co-process which is a special kind of asynchronous process
+(see
+.Sx Co-processes
+below).
+Note that a command must follow the
+.Ql &&
+and
+.Ql \*(Ba\*(Ba
+operators, while it need not follow
+.Ql & ,
+.Ql \*(Ba& ,
+or
+.Ql \&; .
+The exit status of a list is that of the last command executed, with the
+exception of asynchronous lists, for which the exit status is 0.
+.Pp
+Compound commands are created using the following reserved words.
+These words
+are only recognised if they are unquoted and if they are used as the first
+word of a command (i.e. they can't be preceded by parameter assignments or
+redirections):
+.Bd -literal -offset indent
+case     else     function     then      !       (
+do       esac     if           time      [[      ((
+done     fi       in           until     {
+elif     for      select       while     }
+.Ed
+.Pp
+In the following compound command descriptions, command lists (denoted as
+.Em list )
+that are followed by reserved words must end with a semicolon, a newline, or
+a (syntactically correct) reserved word.
+For example, the following are all valid:
+.Bd -literal -offset indent
+$ { echo foo; echo bar; }
+$ { echo foo; echo bar\*(Ltnewline\*(Gt}
+$ { { echo foo; echo bar; } }
+.Ed
+.Pp
+This is not valid:
+.Pp
+.Dl $ { echo foo; echo bar }
+.Bl -tag -width 4n
+.It Pq Ar list
+Execute
+.Ar list
+in a subshell.
+There is no implicit way to pass environment changes from a
+subshell back to its parent.
+.It { Ar list ; No }
+Compound construct;
+.Ar list
+is executed, but not in a subshell.
+Note that
+.Ql {
+and
+.Ql }
+are reserved words, not meta-characters.
+.It Xo case Ar word No in
+.Oo Op \&(
+.Ar pattern
+.Op \*(Ba Ar pat
+.No ... Ns )
+.Ar list
+.Op ;; \*(Ba ;&\& \*(Ba ;\*(Ba\ \&
+.Oc ... esac
+.Xc
+The
+.Ic case
+statement attempts to match
+.Ar word
+against a specified
+.Ar pattern ;
+the
+.Ar list
+associated with the first successfully matched pattern is executed.
+Patterns used in
+.Ic case
+statements are the same as those used for file name patterns except that the
+restrictions regarding
+.Ql \&.
+and
+.Ql /
+are dropped.
+Note that any unquoted space before and after a pattern is
+stripped; any space within a pattern must be quoted.
+Both the word and the
+patterns are subject to parameter, command, and arithmetic substitution, as
+well as tilde substitution.
+.Pp
+For historical reasons, open and close braces may be used instead of
+.Ic in
+and
+.Ic esac
+e.g.\&
+.Ic case $foo { *) echo bar;; } .
+.Pp
+The list terminators are:
+.Bl -tag -width 4n
+.It Ql ;;
+Terminate after the list.
+.It Ql ;&\&
+Fall through into the next list.
+.It Ql ;\*(Ba\&
+Evaluate the remaining pattern-list tuples.
+.El
+.Pp
+The exit status of a
+.Ic case
+statement is that of the executed
+.Ar list ;
+if no
+.Ar list
+is executed, the exit status is zero.
+.It Xo for Ar name
+.Oo in Ar word No ... Oc ;
+.No do Ar list ; No done
+.Xc
+For each
+.Ar word
+in the specified word list, the parameter
+.Ar name
+is set to the word and
+.Ar list
+is executed.
+If
+.Ic in
+is not used to specify a word list, the positional parameters
+($1, $2, etc.)\&
+are used instead.
+For historical reasons, open and close braces may be used instead of
+.Ic do
+and
+.Ic done
+e.g.\&
+.Ic for i; { echo $i; } .
+The exit status of a
+.Ic for
+statement is the last exit status of
+.Ar list ;
+if
+.Ar list
+is never executed, the exit status is zero.
+.It Xo if Ar list ;
+.No then Ar list ;
+.Oo elif Ar list ;
+.No then Ar list ; Oc
+.No ...
+.Oo else Ar list ; Oc
+.No fi
+.Xc
+If the exit status of the first
+.Ar list
+is zero, the second
+.Ar list
+is executed; otherwise, the
+.Ar list
+following the
+.Ic elif ,
+if any, is executed with similar consequences.
+If all the lists following the
+.Ic if
+and
+.Ic elif Ns s
+fail (i.e. exit with non-zero status), the
+.Ar list
+following the
+.Ic else
+is executed.
+The exit status of an
+.Ic if
+statement is that of non-conditional
+.Ar list
+that is executed; if no non-conditional
+.Ar list
+is executed, the exit status is zero.
+.It Xo select Ar name
+.Oo in Ar word No ... Oc ;
+.No do Ar list ; No done
+.Xc
+The
+.Ic select
+statement provides an automatic method of presenting the user with a menu and
+selecting from it.
+An enumerated list of the specified
+.Ar word Ns (s)
+is printed on standard error, followed by a prompt
+.Po
+.Ev PS3: normally
+.Sq #?\ \&
+.Pc .
+A number corresponding to one of the enumerated words is then read from
+standard input,
+.Ar name
+is set to the selected word (or unset if the selection is not valid),
+.Ev REPLY
+is set to what was read (leading/trailing space is stripped), and
+.Ar list
+is executed.
+If a blank line (i.e. zero or more
+.Ev IFS
+octets) is entered, the menu is reprinted without executing
+.Ar list .
+.Pp
+When
+.Ar list
+completes, the enumerated list is printed if
+.Ev REPLY
+is
+.Dv NULL ,
+the prompt is printed, and so on.
+This process continues until an end-of-file
+is read, an interrupt is received, or a
+.Ic break
+statement is executed inside the loop.
+If
+.Dq in word ...
+is omitted, the positional parameters are used
+(i.e. $1, $2, etc.).
+For historical reasons, open and close braces may be used instead of
+.Ic do
+and
+.Ic done
+e.g.\&
+.Ic select i; { echo $i; } .
+The exit status of a
+.Ic select
+statement is zero if a
+.Ic break
+statement is used to exit the loop, non-zero otherwise.
+.It Xo until Ar list ;
+.No do Ar list ;
+.No done
+.Xc
+This works like
+.Ic while ,
+except that the body is executed only while the exit status of the first
+.Ar list
+is non-zero.
+.It Xo while Ar list ;
+.No do Ar list ;
+.No done
+.Xc
+A
+.Ic while
+is a pre-checked loop.
+Its body is executed as often as the exit status of the first
+.Ar list
+is zero.
+The exit status of a
+.Ic while
+statement is the last exit status of the
+.Ar list
+in the body of the loop; if the body is not executed, the exit status is zero.
+.It Xo function Ar name
+.No { Ar list ; No }
+.Xc
+Defines the function
+.Ar name
+(see
+.Sx Functions
+below).
+Note that redirections specified after a function definition are
+performed whenever the function is executed, not when the function definition
+is executed.
+.It Ar name Ns \&() Ar command
+Mostly the same as
+.Ic function
+(see
+.Sx Functions
+below).
+Whitespace (space or tab) after
+.Ar name
+will be ignored most of the time.
+.It Xo function Ar name Ns \&()
+.No { Ar list ; No }
+.Xc
+The same as
+.Ar name Ns \&()
+.Pq Nm bash Ns ism .
+The
+.Ic function
+keyword is ignored.
+.It Xo Ic time Op Fl p
+.Op Ar pipeline
+.Xc
+The
+.Sx Command execution
+section describes the
+.Ic time
+reserved word.
+.It \&(( Ar expression No ))
+The arithmetic expression
+.Ar expression
+is evaluated; equivalent to
+.Dq let expression
+(see
+.Sx Arithmetic expressions
+and the
+.Ic let
+command, below).
+.It Bq Bq Ar \ \&expression\ \&
+Similar to the
+.Ic test
+and
+.Ic \&[ ... \&]
+commands (described later), with the following exceptions:
+.Bl -bullet
+.It
+Field splitting and file name generation are not performed on arguments.
+.It
+The
+.Fl a
+.Pq AND
+and
+.Fl o
+.Pq OR
+operators are replaced with
+.Ql &&
+and
+.Ql \*(Ba\*(Ba ,
+respectively.
+.It
+Operators (e.g.\&
+.Sq Fl f ,
+.Sq = ,
+.Sq \&! )
+must be unquoted.
+.It
+Parameter, command, and arithmetic substitutions are performed as expressions
+are evaluated and lazy expression evaluation is used for the
+.Ql &&
+and
+.Ql \*(Ba\*(Ba
+operators.
+This means that in the following statement,
+.Ic $(\*(Ltfoo)
+is evaluated if and only if the file
+.Pa foo
+exists and is readable:
+.Bd -literal -offset indent
+$ [[ \-r foo && $(\*(Ltfoo) = b*r ]]
+.Ed
+.It
+The second operand of the
+.Sq !=
+and
+.Sq =
+expressions are patterns (e.g. the comparison
+.Ic \&[[ foobar = f*r ]]
+succeeds).
+This even works indirectly:
+.Bd -literal -offset indent
+$ bar=foobar; baz=\*(aqf*r\*(aq
+$ [[ $bar = $baz ]]; echo $?
+$ [[ $bar = "$baz" ]]; echo $?
+.Ed
+.Pp
+Perhaps surprisingly, the first comparison succeeds,
+whereas the second doesn't.
+.El
+.El
+.Ss Quoting
+Quoting is used to prevent the shell from treating characters or words
+specially.
+There are three methods of quoting.
+First,
+.Ql \e
+quotes the following character, unless it is at the end of a line, in which
+case both the
+.Ql \e
+and the newline are stripped.
+Second, a single quote
+.Pq Sq \*(aq
+quotes everything up to the next single quote (this may span lines).
+Third, a double quote
+.Pq Sq \&"
+quotes all characters, except
+.Ql $ ,
+.Ql \`
+and
+.Ql \e ,
+up to the next unquoted double quote.
+.Ql $
+and
+.Ql \`
+inside double quotes have their usual meaning (i.e. parameter, command, or
+arithmetic substitution) except no field splitting is carried out on the
+results of double-quoted substitutions.
+If a
+.Ql \e
+inside a double-quoted string is followed by
+.Ql \e ,
+.Ql $ ,
+.Ql \` ,
+or
+.Ql \&" ,
+it is replaced by the second character; if it is followed by a newline, both
+the
+.Ql \e
+and the newline are stripped; otherwise, both the
+.Ql \e
+and the character following are unchanged.
+.Pp
+If a single-quoted string is preceded by an unquoted
+.Ql $ ,
+C style backslash expansion (see below) is applied (even single quote
+characters inside can be escaped and do not terminate the string then);
+the expanded result is treated as any other single-quoted string.
+If a double-quoted string is preceded by an unquoted
+.Ql $ ,
+the latter is ignored.
+.Ss Backslash expansion
+In places where backslashes are expanded, certain C and
+.At
+.Nm ksh
+or GNU
+.Nm bash
+style escapes are translated.
+These include
+.Ql \ea ,
+.Ql \eb ,
+.Ql \ef ,
+.Ql \en ,
+.Ql \er ,
+.Ql \et ,
+.Ql \eU######## ,
+.Ql \eu#### ,
+and
+.Ql \ev .
+For
+.Ql \eU########
+and
+.Ql \eu#### ,
+.Dq #
+means a hexadecimal digit, of thich there may be none up to four or eight;
+these escapes translate a Unicode codepoint to UTF-8.
+Furthermore,
+.Ql \eE
+and
+.Ql \ee
+expand to the escape character.
+.Pp
+In the
+.Ic print
+builtin mode,
+.Ql \e" ,
+.Ql \e\*(aq ,
+and
+.Ql \e?
+are explicitly excluded;
+octal sequences must have the none up to three octal digits
+.Dq #
+prefixed with the digit zero
+.Pq Ql \e0### ;
+hexadecimal sequences
+.Ql \ex##
+are limited to none up to two hexadecimal digits
+.Dq # ;
+both octal and hexadecimal sequences convert to raw octets;
+.Ql \e# ,
+where # is none of the above, translates to \e# (backslashes are retained).
+.Pp
+Backslash expansion in the C style mode slightly differs: octal sequences
+.Ql \e###
+must have no digit zero prefixing the one up to three octal digits
+.Dq #
+and yield raw octets; hexadecimal sequences
+.Ql \ex#*
+greedily eat up as many hexadecimal digits
+.Dq #
+as they can and terminate with the first non-hexadecimal digit;
+these translate a Unicode codepoint to UTF-8.
+The sequence
+.Ql \ec# ,
+where
+.Dq #
+is any octet, translates to Ctrl-# (which basically means,
+.Ql \ec?
+becomes DEL, everything else is bitwise ANDed with 0x1F).
+Finally,
+.Ql \e# ,
+where # is none of the above, translates to # (has the backslash trimmed),
+even if it is a newline.
+.Ss Aliases
+There are two types of aliases: normal command aliases and tracked aliases.
+Command aliases are normally used as a short hand for a long or often used
+command.
+The shell expands command aliases (i.e. substitutes the alias name
+for its value) when it reads the first word of a command.
+An expanded alias is re-processed to check for more aliases.
+If a command alias ends in a
+space or tab, the following word is also checked for alias expansion.
+The alias expansion process stops when a word that is not an alias is found,
+when a quoted word is found, or when an alias word that is currently being
+expanded is found.
+.Pp
+The following command aliases are defined automatically by the shell:
+.Bd -literal -offset indent
+autoload=\*(aqtypeset \-fu\*(aq
+functions=\*(aqtypeset \-f\*(aq
+hash=\*(aqalias \-t\*(aq
+history=\*(aqfc \-l\*(aq
+integer=\*(aqtypeset \-i\*(aq
+local=\*(aqtypeset\*(aq
+login=\*(aqexec login\*(aq
+nameref=\*(aqtypeset \-n\*(aq
+nohup=\*(aqnohup \*(aq
+r=\*(aqfc \-e \-\*(aq
+stop=\*(aqkill \-STOP\*(aq
+suspend=\*(aqkill \-STOP $$\*(aq
+type=\*(aqwhence \-v\*(aq
+.Ed
+.Pp
+Tracked aliases allow the shell to remember where it found a particular
+command.
+The first time the shell does a path search for a command that is
+marked as a tracked alias, it saves the full path of the command.
+The next
+time the command is executed, the shell checks the saved path to see that it
+is still valid, and if so, avoids repeating the path search.
+Tracked aliases can be listed and created using
+.Ic alias \-t .
+Note that changing the
+.Ev PATH
+parameter clears the saved paths for all tracked aliases.
+If the
+.Ic trackall
+option is set (i.e.\&
+.Ic set \-o Ic trackall
+or
+.Ic set \-h ) ,
+the shell tracks all commands.
+This option is set automatically for non-interactive shells.
+For interactive shells, only the following commands are
+automatically tracked:
+.Xr cat 1 ,
+.Xr cc 1 ,
+.Xr chmod 1 ,
+.Xr cp 1 ,
+.Xr date 1 ,
+.Xr ed 1 ,
+.Xr emacs 1 ,
+.Xr grep 1 ,
+.Xr ls 1 ,
+.Xr make 1 ,
+.Xr mv 1 ,
+.Xr pr 1 ,
+.Xr rm 1 ,
+.Xr sed 1 ,
+.Xr sh 1 ,
+.Xr vi 1 ,
+and
+.Xr who 1 .
+.Ss Substitution
+The first step the shell takes in executing a simple-command is to perform
+substitutions on the words of the command.
+There are three kinds of
+substitution: parameter, command, and arithmetic.
+Parameter substitutions,
+which are described in detail in the next section, take the form
+.Pf $ Ns Ar name
+or
+.Pf ${ Ns Ar ... Ns } ;
+command substitutions take the form
+.Pf $( Ns Ar command Ns \&)
+or (deprecated)
+.Pf \` Ns Ar command Ns \` ;
+and arithmetic substitutions take the form
+.Pf $(( Ns Ar expression Ns )) .
+.Pp
+If a substitution appears outside of double quotes, the results of the
+substitution are generally subject to word or field splitting according to
+the current value of the
+.Ev IFS
+parameter.
+The
+.Ev IFS
+parameter specifies a list of octets which are used to break a string up
+into several words; any octets from the set space, tab, and newline that
+appear in the
+.Ev IFS
+octets are called
+.Dq IFS whitespace .
+Sequences of one or more
+.Ev IFS
+whitespace octets, in combination with zero or one
+.Pf non- Ev IFS
+whitespace octets, delimit a field.
+As a special case, leading and trailing
+.Ev IFS
+whitespace and trailing
+.Ev IFS
+non-whitespace are stripped (i.e. no leading or trailing empty field
+is created by it); leading
+.Pf non- Ev IFS
+whitespace does create an empty field.
+.Pp
+Example: If
+.Ev IFS
+is set to
+.Dq \*(Ltspace\*(Gt: ,
+and VAR is set to
+.Dq \*(Ltspace\*(GtA\*(Ltspace\*(Gt:\*(Ltspace\*(Gt\*(Ltspace\*(GtB::D ,
+the substitution for $VAR results in four fields:
+.Sq A ,
+.Sq B ,
+.Sq
+(an empty field),
+and
+.Sq D .
+Note that if the
+.Ev IFS
+parameter is set to the
+.Dv NULL
+string, no field splitting is done; if the parameter is unset, the default
+value of space, tab, and newline is used.
+.Pp
+Also, note that the field splitting applies only to the immediate result of
+the substitution.
+Using the previous example, the substitution for $VAR:E
+results in the fields:
+.Sq A ,
+.Sq B ,
+.Sq ,
+and
+.Sq D:E ,
+not
+.Sq A ,
+.Sq B ,
+.Sq ,
+.Sq D ,
+and
+.Sq E .
+This behavior is POSIX compliant, but incompatible with some other shell
+implementations which do field splitting on the word which contained the
+substitution or use
+.Dv IFS
+as a general whitespace delimiter.
+.Pp
+The results of substitution are, unless otherwise specified, also subject to
+brace expansion and file name expansion (see the relevant sections below).
+.Pp
+A command substitution is replaced by the output generated by the specified
+command which is run in a subshell.
+For
+.Pf $( Ns Ar command Ns \&)
+substitutions, normal quoting rules are used when
+.Ar command
+is parsed; however, for the deprecated
+.Pf \` Ns Ar command Ns \`
+form, a
+.Ql \e
+followed by any of
+.Ql $ ,
+.Ql \` ,
+or
+.Ql \e
+is stripped (a
+.Ql \e
+followed by any other character is unchanged).
+As a special case in command substitutions, a command of the form
+.Pf \*(Lt Ar file
+is interpreted to mean substitute the contents of
+.Ar file .
+Note that
+.Ic $(\*(Ltfoo)
+has the same effect as
+.Ic $(cat foo) .
+.Pp
+Note that some shells do not use a recursive parser for command substitutions,
+leading to failure for certain constructs; to be portable, use as workaround
+.Ql x=$(cat) \*(Lt\*(Lt"EOF"
+(or the newline-keeping
+.Ql x=\*(Lt\*(Lt"EOF"
+extension) instead to merely slurp the string.
+.St -p1003.1
+recommends to use case statements of the form
+.Ql "x=$(case $foo in (bar) echo $bar ;; (*) echo $baz ;; esac)"
+instead, which would work but not serve as example for this portability issue.
+.Bd -literal -offset indent
+x=$(case $foo in bar) echo $bar ;; *) echo $baz ;; esac)
+# above fails to parse on old shells; below is the workaround
+x=$(eval $(cat)) \*(Lt\*(Lt"EOF"
+case $foo in bar) echo $bar ;; *) echo $baz ;; esac
+EOF
+.Ed
+.Pp
+Arithmetic substitutions are replaced by the value of the specified expression.
+For example, the command
+.Ic print $((2+3*4))
+displays 14.
+See
+.Sx Arithmetic expressions
+for a description of an expression.
+.Ss Parameters
+Parameters are shell variables; they can be assigned values and their values
+can be accessed using a parameter substitution.
+A parameter name is either one
+of the special single punctuation or digit character parameters described
+below, or a letter followed by zero or more letters or digits
+.Po
+.Ql _
+counts as a letter
+.Pc .
+The latter form can be treated as arrays by appending an array index of the
+form
+.Op Ar expr
+where
+.Ar expr
+is an arithmetic expression.
+Array indices in
+.Nm
+are limited to the range 0 through 4294967295, inclusive.
+That is, they are a 32-bit unsigned integer.
+.Pp
+Parameter substitutions take the form
+.Pf $ Ns Ar name ,
+.Pf ${ Ns Ar name Ns } ,
+or
+.Sm off
+.Pf ${ Ar name Oo Ar expr Oc }
+.Sm on
+where
+.Ar name
+is a parameter name.
+Substitution of all array elements with
+.Pf ${ Ns Ar name Ns \&[*]}
+and
+.Pf ${ Ns Ar name Ns \&[@]}
+works equivalent to $* and $@ for positional parameters.
+If substitution is performed on a parameter
+(or an array parameter element)
+that is not set, a null string is substituted unless the
+.Ic nounset
+option
+.Po
+.Ic set Fl o Ic nounset
+or
+.Ic set Fl u
+.Pc
+is set, in which case an error occurs.
+.Pp
+Parameters can be assigned values in a number of ways.
+First, the shell implicitly sets some parameters like
+.Ql # ,
+.Ql PWD ,
+and
+.Ql $ ;
+this is the only way the special single character parameters are set.
+Second, parameters are imported from the shell's environment at startup.
+Third, parameters can be assigned values on the command line: for example,
+.Ic FOO=bar
+sets the parameter
+.Dq FOO
+to
+.Dq bar ;
+multiple parameter assignments can be given on a single command line and they
+can be followed by a simple-command, in which case the assignments are in
+effect only for the duration of the command (such assignments are also
+exported; see below for the implications of this).
+Note that both the parameter name and the
+.Ql =
+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.
+The fourth way of setting a parameter is with the
+.Ic export ,
+.Ic global ,
+.Ic readonly ,
+and
+.Ic typeset
+commands; see their descriptions in the
+.Sx Command execution
+section.
+Fifth,
+.Ic for
+and
+.Ic select
+loops set parameters as well as the
+.Ic getopts ,
+.Ic read ,
+and
+.Ic set \-A
+commands.
+Lastly, parameters can be assigned values using assignment operators
+inside arithmetic expressions (see
+.Sx Arithmetic expressions
+below) or using the
+.Sm off
+.Pf ${ Ar name No = Ar value No }
+.Sm on
+form of the parameter substitution (see below).
+.Pp
+Parameters with the export attribute (set using the
+.Ic export
+or
+.Ic typeset Fl x
+commands, or by parameter assignments followed by simple commands) are put in
+the environment (see
+.Xr environ 7 )
+of commands run by the shell as
+.Ar name Ns = Ns Ar value
+pairs.
+The order in which parameters appear in the environment of a command is
+unspecified.
+When the shell starts up, it extracts parameters and their values
+from its environment and automatically sets the export attribute for those
+parameters.
+.Pp
+Modifiers can be applied to the
+.Pf ${ Ns Ar name Ns }
+form of parameter substitution:
+.Bl -tag -width Ds
+.Sm off
+.It ${ Ar name No :\- Ar word No }
+.Sm on
+If
+.Ar name
+is set and not
+.Dv NULL ,
+it is substituted; otherwise,
+.Ar word
+is substituted.
+.Sm off
+.It ${ Ar name No :+ Ar word No }
+.Sm on
+If
+.Ar name
+is set and not
+.Dv NULL ,
+.Ar word
+is substituted; otherwise, nothing is substituted.
+.Sm off
+.It ${ Ar name No := Ar word No }
+.Sm on
+If
+.Ar name
+is set and not
+.Dv NULL ,
+it is substituted; otherwise, it is assigned
+.Ar word
+and the resulting value of
+.Ar name
+is substituted.
+.Sm off
+.It ${ Ar name No :? Ar word No }
+.Sm on
+If
+.Ar name
+is set and not
+.Dv NULL ,
+it is substituted; otherwise,
+.Ar word
+is printed on standard error (preceded by
+.Ar name : )
+and an error occurs (normally causing termination of a shell script, function,
+or script sourced using the
+.Sq \&.
+built-in).
+If
+.Ar word
+is omitted, the string
+.Dq parameter null or not set
+is used instead.
+Currently a bug, if
+.Ar word
+is a variable which expands to the null string, the
+error message is also printed.
+.El
+.Pp
+Note that, for all of the above,
+.Ar word
+is actually considered quoted, and special parsing rules apply.
+The parsing rules also differ on whether the expression is double-quoted:
+.Ar word
+then uses double-quoting rules, except for the double quote itself
+.Pq Sq \&"
+and the closing brace, which, if backslash escaped, gets quote removal applied.
+.Pp
+In the above modifiers, the
+.Ql \&:
+can be omitted, in which case the conditions only depend on
+.Ar name
+being set (as opposed to set and not
+.Dv NULL ) .
+If
+.Ar word
+is needed, parameter, command, arithmetic, and tilde substitution are performed
+on it; if
+.Ar word
+is not needed, it is not evaluated.
+.Pp
+The following forms of parameter substitution can also be used (if
+.Ar name
+is an array, its element #0 will be substituted in a scalar context):
+.Pp
+.Bl -tag -width Ds -compact
+.It Pf ${# Ns Ar name Ns \&}
+The number of positional parameters if
+.Ar name
+is
+.Ql * ,
+.Ql @ ,
+or not specified; otherwise the length
+.Pq in characters
+of the string value of parameter
+.Ar name .
+.Pp
+.It Pf ${# Ns Ar name Ns \&[*]}
+.It Pf ${# Ns Ar name Ns \&[@]}
+The number of elements in the array
+.Ar name .
+.Pp
+.It Pf ${% Ns Ar name Ns \&}
+The width
+.Pq in screen columns
+of the string value of parameter
+.Ar name ,
+or \-1 if
+.Pf ${ Ns Ar name Ns }
+contains a control character.
+.Pp
+.It Pf ${! Ns Ar name Ns }
+The name of the variable referred to by
+.Ar name .
+This will be
+.Ar name
+except when
+.Ar name
+is a name reference (bound variable), created by the
+.Ic nameref
+command (which is an alias for
+.Ic typeset Fl n ) .
+.Pp
+.It Pf ${! Ns Ar name Ns \&[*]}
+.It Pf ${! Ns Ar name Ns \&[@]}
+The names of indices (keys) in the array
+.Ar name .
+.Pp
+.Sm off
+.It Xo
+.Pf ${ Ar name
+.Pf # Ar pattern No }
+.Xc
+.It Xo
+.Pf ${ Ar name
+.Pf ## Ar pattern No }
+.Xc
+.Sm on
+If
+.Ar pattern
+matches the beginning of the value of parameter
+.Ar name ,
+the matched text is deleted from the result of substitution.
+A single
+.Ql #
+results in the shortest match, and two
+of them result in the longest match.
+Cannot be applied to a vector
+.Pq ${*} or ${@} or ${array[*]} or ${array[@]} .
+.Pp
+.Sm off
+.It Xo
+.Pf ${ Ar name
+.Pf % Ar pattern No }
+.Xc
+.It Xo
+.Pf ${ Ar name
+.Pf %% Ar pattern No }
+.Xc
+.Sm on
+Like ${..#..} substitution, but it deletes from the end of the value.
+Cannot be applied to a vector.
+.Pp
+.Sm off
+.It Xo
+.Pf ${ Ar name
+.Pf / Ar pattern / Ar string No }
+.Xc
+.It Xo
+.Pf ${ Ar name
+.Pf // Ar pattern / Ar string No }
+.Xc
+.Sm on
+Like ${..#..} substitution, but it replaces the longest match of
+.Ar pattern ,
+anchored anywhere in the value, with
+.Ar string .
+If
+.Ar pattern
+begins with
+.Ql # ,
+it is anchored at the beginning of the value; if it begins with
+.Ql % ,
+it is anchored at the end.
+A single
+.Ql /
+replaces the first occurence of the search
+.Ar pattern ,
+and two of them replace all occurences.
+If
+.Pf / Ar string
+is omitted, the
+.Ar pattern
+is replaced by the empty string, i.e. deleted.
+Cannot be applied to a vector.
+Inefficiently implemented.
+.Pp
+.Sm off
+.It Xo
+.Pf ${ Ar name : Ns Ar pos
+.Pf : Ns Ar len Ns }
+.Xc
+.Sm on
+The first
+.Ar len
+characters of
+.Ar name ,
+starting at position
+.Ar pos ,
+are substituted.
+Both
+.Ar pos
+and
+.Pf : Ns Ar len
+are optional.
+If
+.Ar pos
+is negative, counting starts at the end of the string; if it
+is omitted, it defaults to 0.
+If
+.Ar len
+is omitted or greater than the length of the remaining string,
+all of it is substituted.
+Both
+.Ar pos
+and
+.Ar len
+are evaluated as arithmetic expressions.
+Currently,
+.Ar pos
+must start with a space, opening parenthesis or digit to be recognised.
+Cannot be applied to a vector.
+.Pp
+.It Pf ${ Ns Ar name Ns @#}
+The internal hash of the expansion of
+.Ar name .
+At the moment, this is NZAT (a never-zero 32-bit hash based on
+Bob Jenkins' one-at-a-time hash), but this is not set.
+This is the hash the shell uses internally for its associative arrays.
+.El
+.Pp
+Note that
+.Ar pattern
+may need extended globbing pattern
+.Pq @(...) ,
+single
+.Pq \&\*(aq...\&\*(aq
+or double
+.Pq \&"...\&"
+quote escaping unless
+.Fl o Ic sh
+is set.
+.Pp
+The following special parameters are implicitly set by the shell and cannot be
+set directly using assignments:
+.Bl -tag -width "1 .. 9"
+.It Ev \&!
+Process ID of the last background process started.
+If no background processes have been started, the parameter is not set.
+.It Ev \&#
+The number of positional parameters ($1, $2, etc.).
+.It Ev \&$
+The PID of the shell, or the PID of the original shell if it is a subshell.
+Do
+.Em NOT
+use this mechanism for generating temporary file names; see
+.Xr mktemp 1
+instead.
+.It Ev \-
+The concatenation of the current single letter options (see the
+.Ic set
+command below for a list of options).
+.It Ev \&?
+The exit status of the last non-asynchronous command executed.
+If the last command was killed by a signal,
+.Ic $?\&
+is set to 128 plus the signal number.
+.It Ev 0
+The name of the shell, determined as follows:
+the first argument to
+.Nm
+if it was invoked with the
+.Fl c
+option and arguments were given; otherwise the
+.Ar file
+argument, if it was supplied;
+or else the basename the shell was invoked with (i.e.\&
+.Li argv[0] ) .
+.Ev $0
+is also set to the name of the current script or
+the name of the current function, if it was defined with the
+.Ic function
+keyword (i.e. a Korn shell style function).
+.It Ev 1 No .. Ev 9
+The first nine positional parameters that were supplied to the shell, function,
+or script sourced using the
+.Sq \&.
+built-in.
+Further positional parameters may be accessed using
+.Pf ${ Ar number Ns } .
+.It Ev *
+All positional parameters (except 0), i.e. $1, $2, $3, ...
+.br
+If used
+outside of double quotes, parameters are separate words (which are subjected
+to word splitting); if used within double quotes, parameters are separated
+by the first character of the
+.Ev IFS
+parameter (or the empty string if
+.Ev IFS
+is
+.Dv NULL ) .
+.It Ev @
+Same as
+.Ic $* ,
+unless it is used inside double quotes, in which case a separate word is
+generated for each positional parameter.
+If there are no positional parameters, no word is generated.
+.Ic $@
+can be used to access arguments, verbatim, without losing
+.Dv NULL
+arguments or splitting arguments with spaces.
+.El
+.Pp
+The following parameters are set and/or used by the shell:
+.Bl -tag -width "KSH_VERSION"
+.It Ev _
+.Pq underscore
+When an external command is executed by the shell, this parameter is set in the
+environment of the new process to the path of the executed command.
+In interactive use, this parameter is also set in the parent shell to the last
+word of the previous command.
+.It Ev CDPATH
+Search path for the
+.Ic cd
+built-in command.
+It works the same way as
+.Ev PATH
+for those directories not beginning with
+.Ql /
+in
+.Ic cd
+commands.
+Note that if
+.Ev CDPATH
+is set and does not contain
+.Sq \&.
+or contains an empty path, the current directory is not searched.
+Also, the
+.Ic cd
+built-in command will display the resulting directory when a match is found
+in any search path other than the empty path.
+.It Ev COLUMNS
+Set to the number of columns on the terminal or window.
+Always set, defaults to 80, unless the
+value as reported by
+.Xr stty 1
+is non-zero and sane enough; similar for
+.Ev LINES .
+This parameter is used by the interactive line editing modes, and by the
+.Ic select ,
+.Ic set \-o ,
+and
+.Ic kill \-l
+commands to format information columns.
+.It Ev ENV
+If this parameter is found to be set after any profile files are executed, the
+expanded value is used as a shell startup file.
+It typically contains function and alias definitions.
+.It Ev ERRNO
+Integer value of the shell's
+.Va errno
+variable.
+It indicates the reason the last system call failed.
+Not yet implemented.
+.It Ev EXECSHELL
+If set, this parameter is assumed to contain the shell that is to be used to
+execute commands that
+.Xr execve 2
+fails to execute and which do not start with a
+.Dq #! Ns Ar shell
+sequence.
+.It Ev FCEDIT
+The editor used by the
+.Ic fc
+command (see below).
+.It Ev FPATH
+Like
+.Ev PATH ,
+but used when an undefined function is executed to locate the file defining the
+function.
+It is also searched when a command can't be found using
+.Ev PATH .
+See
+.Sx Functions
+below for more information.
+.It Ev HISTFILE
+The name of the file used to store command history.
+When assigned to, history is loaded from the specified file.
+Also, several invocations of the shell will share history if their
+.Ev HISTFILE
+parameters all point to the same file.
+.Pp
+.Sy Note :
+If
+.Ev HISTFILE
+isn't set, no history file is used.
+This is different from
+.At
+.Nm ksh .
+.It Ev HISTSIZE
+The number of commands normally stored for history.
+The default is 500.
+.It Ev HOME
+The default directory for the
+.Ic cd
+command and the value substituted for an unqualified
+.Ic \*(TI
+(see
+.Sx Tilde expansion
+below).
+.It Ev IFS
+Internal field separator, used during substitution and by the
+.Ic read
+command, to split values into distinct arguments; normally set to space, tab,
+and newline.
+See
+.Sx Substitution
+above for details.
+.Pp
+.Sy Note :
+This parameter is not imported from the environment when the shell is
+started.
+.It Ev KSHEGID
+The effective group id of the shell.
+.It Ev KSHGID
+The real group id of the shell.
+.It Ev KSHUID
+The real user id of the shell.
+.It Ev KSH_VERSION
+The name and version of the shell (read-only).
+See also the version commands in
+.Sx Emacs editing mode
+and
+.Sx Vi editing mode
+sections, below.
+.It Ev LINENO
+The line number of the function or shell script that is currently being
+executed.
+.It Ev LINES
+Set to the number of lines on the terminal or window.
+Always set, defaults to 24.
+.It Ev OLDPWD
+The previous working directory.
+Unset if
+.Ic cd
+has not successfully changed directories since the shell started, or if the
+shell doesn't know where it is.
+.It Ev OPTARG
+When using
+.Ic getopts ,
+it contains the argument for a parsed option, if it requires one.
+.It Ev OPTIND
+The index of the next argument to be processed when using
+.Ic getopts .
+Assigning 1 to this parameter causes
+.Ic getopts
+to process arguments from the beginning the next time it is invoked.
+.It Ev PATH
+A colon separated list of directories that are searched when looking for
+commands and files sourced using the
+.Sq \&.
+command (see below).
+An empty string resulting from a leading or trailing
+colon, or two adjacent colons, is treated as a
+.Sq \&.
+(the current directory).
+.It Ev PGRP
+The process ID of the shell's process group leader.
+.It Ev PIPESTATUS
+An array containing the errorlevel (exit status) codes,
+one by one, of the last pipeline run in the foreground.
+.It Ev PPID
+The process ID of the shell's parent.
+.It Ev PS1
+The primary prompt for interactive shells.
+Parameter, command, and arithmetic
+substitutions are performed, and
+.Ql \&!
+is replaced with the current command number (see the
+.Ic fc
+command below).
+A literal
+.Ql \&!
+can be put in the prompt by placing
+.Ql !!
+in
+.Ev PS1 .
+.Pp
+The default prompt is
+.Sq $\ \&
+for non-root users,
+.Sq #\ \&
+for root.
+If
+.Nm
+is invoked by root and
+.Ev PS1
+does not contain a
+.Sq #
+character, the default value will be used even if
+.Ev PS1
+already exists in the environment.
+.Pp
+The
+.Nm
+distribution comes with a sample
+.Pa dot.mkshrc
+containing a sophisticated example, but you might like the following one
+(note that ${HOSTNAME:=$(hostname)} and the
+root-vs-user distinguishing clause are (in this example) executed at
+.Ev PS1
+assignment time, while the $USER and $PWD are escaped
+and thus will be evaluated each time a prompt is displayed):
+.Bd -literal
+PS1=\*(aq${USER:=$(id \-un)}\*(aq"@${HOSTNAME:=$(hostname)}:\e$PWD $(
+	if (( USER_ID )); then print \e$; else print \e#; fi) "
+.Ed
+.Pp
+Note that since the command-line editors try to figure out how long the prompt
+is (so they know how far it is to the edge of the screen), escape codes in
+the prompt tend to mess things up.
+You can tell the shell not to count certain
+sequences (such as escape codes) by prefixing your prompt with a
+character (such as Ctrl-A) followed by a carriage return and then delimiting
+the escape codes with this character.
+Any occurences of that character in the prompt are not printed.
+By the way, don't blame me for
+this hack; it's derived from the original
+.Xr ksh88 1 ,
+which did print the delimiter character so you were out of luck
+if you did not have any non-printing characters.
+.Pp
+Since Backslashes and other special characters may be
+interpreted by the shell, to set
+.Ev PS1
+either escape the backslash itself,
+or use double quotes.
+The latter is more practical.
+This is a more complex example,
+avoiding to directly enter special characters (for example with
+.Ic \*(haV
+in the emacs editing mode),
+which embeds the current working directory,
+in reverse video
+.Pq colour would work, too ,
+in the prompt string:
+.Bd -literal -offset indent
+x=$(print \e\e001)
+PS1="$x$(print \e\er)$x$(tput smso)$x\e$PWD$x$(tput rmso)$x\*(Gt "
+.Ed
+.Pp
+Due to pressure from David G. Korn,
+.Nm
+now also supports the following form:
+.Bd -literal -offset indent
+PS1=$'\e1\er\e1\ee[7m\e1$PWD\e1\ee[0m\e1\*(Gt '
+.Ed
+.It Ev PS2
+Secondary prompt string, by default
+.Sq \*(Gt\ \& ,
+used when more input is needed to complete a command.
+.It Ev PS3
+Prompt used by the
+.Ic select
+statement when reading a menu selection.
+The default is
+.Sq #?\ \& .
+.It Ev PS4
+Used to prefix commands that are printed during execution tracing (see the
+.Ic set Fl x
+command below).
+Parameter, command, and arithmetic substitutions are performed
+before it is printed.
+The default is
+.Sq +\ \& .
+.It Ev PWD
+The current working directory.
+May be unset or
+.Dv NULL
+if the shell doesn't know where it is.
+.It Ev RANDOM
+Each time
+.Ev RANDOM
+is referenced, it is assigned a number between 0 and 32767 from
+a Linear Congruential PRNG first.
+.It Ev REPLY
+Default parameter for the
+.Ic read
+command if no names are given.
+Also used in
+.Ic select
+loops to store the value that is read from standard input.
+.It Ev SECONDS
+The number of seconds since the shell started or, if the parameter has been
+assigned an integer value, the number of seconds since the assignment plus the
+value that was assigned.
+.It Ev TMOUT
+If set to a positive integer in an interactive shell, it specifies the maximum
+number of seconds the shell will wait for input after printing the primary
+prompt
+.Pq Ev PS1 .
+If the time is exceeded, the shell exits.
+.It Ev TMPDIR
+The directory temporary shell files are created in.
+If this parameter is not
+set, or does not contain the absolute path of a writable directory, temporary
+files are created in
+.Pa /tmp .
+.It Ev USER_ID
+The effective user id of the shell.
+.El
+.Ss Tilde expansion
+Tilde expansion which is done in parallel with parameter substitution, is done
+on words starting with an unquoted
+.Ql \*(TI .
+The characters following the tilde, up to the first
+.Ql / ,
+if any, are assumed to be a login name.
+If the login name is empty,
+.Ql + ,
+or
+.Ql \- ,
+the value of the
+.Ev HOME ,
+.Ev PWD ,
+or
+.Ev OLDPWD
+parameter is substituted, respectively.
+Otherwise, the password file is
+searched for the login name, and the tilde expression is substituted with the
+user's home directory.
+If the login name is not found in the password file or
+if any quoting or parameter substitution occurs in the login name, no
+substitution is performed.
+.Pp
+In parameter assignments
+(such as those preceding a simple-command or those occurring
+in the arguments of
+.Ic alias ,
+.Ic export ,
+.Ic global ,
+.Ic readonly ,
+and
+.Ic typeset ) ,
+tilde expansion is done after any assignment
+(i.e. after the equals sign)
+or after an unquoted colon
+.Pq Sq \&: ;
+login names are also delimited by colons.
+.Pp
+The home directory of previously expanded login names are cached and re-used.
+The
+.Ic alias \-d
+command may be used to list, change, and add to this cache (e.g.\&
+.Ic alias \-d fac=/usr/local/facilities; cd \*(TIfac/bin ) .
+.Ss Brace expansion (alteration)
+Brace expressions take the following form:
+.Bd -unfilled -offset indent
+.Sm off
+.Xo
+.Ar prefix No { Ar str1 No ,...,
+.Ar strN No } Ar suffix
+.Xc
+.Sm on
+.Ed
+.Pp
+The expressions are expanded to
+.Ar N
+words, each of which is the concatenation of
+.Ar prefix ,
+.Ar str Ns i ,
+and
+.Ar suffix
+(e.g.\&
+.Dq a{c,b{X,Y},d}e
+expands to four words:
+.Dq ace ,
+.Dq abXe ,
+.Dq abYe ,
+and
+.Dq ade ) .
+As noted in the example, brace expressions can be nested and the resulting
+words are not sorted.
+Brace expressions must contain an unquoted comma
+.Pq Sq \&,
+for expansion to occur (e.g.\&
+.Ic {}
+and
+.Ic {foo}
+are not expanded).
+Brace expansion is carried out after parameter substitution
+and before file name generation.
+.Ss File name patterns
+A file name pattern is a word containing one or more unquoted
+.Ql \&? ,
+.Ql * ,
+.Ql + ,
+.Ql @ ,
+or
+.Ql \&!
+characters or
+.Dq \&[..]
+sequences.
+Once brace expansion has been performed, the shell replaces file
+name patterns with the sorted names of all the files that match the pattern
+(if no files match, the word is left unchanged).
+The pattern elements have the following meaning:
+.Bl -tag -width Ds
+.It \&?
+Matches any single character.
+.It \&*
+Matches any sequence of octets.
+.It \&[..]
+Matches any of the octets inside the brackets.
+Ranges of octets can be specified by separating two octets by a
+.Ql \-
+(e.g.\&
+.Dq \&[a0\-9]
+matches the letter
+.Sq a
+or any digit).
+In order to represent itself, a
+.Ql \-
+must either be quoted or the first or last octet in the octet list.
+Similarly, a
+.Ql \&]
+must be quoted or the first octet in the list if it is to represent itself
+instead of the end of the list.
+Also, a
+.Ql \&!
+appearing at the start of the list has special meaning (see below), so to
+represent itself it must be quoted or appear later in the list.
+.It \&[!..]
+Like [..],
+except it matches any octet not inside the brackets.
+.Sm off
+.It *( Ar pattern\*(Ba No ...\*(Ba Ar pattern )
+.Sm on
+Matches any string of octets that matches zero or more occurrences of the
+specified patterns.
+Example: The pattern
+.Ic *(foo\*(Babar)
+matches the strings
+.Dq ,
+.Dq foo ,
+.Dq bar ,
+.Dq foobarfoo ,
+etc.
+.Sm off
+.It +( Ar pattern\*(Ba No ...\*(Ba Ar pattern )
+.Sm on
+Matches any string of octets that matches one or more occurrences of the
+specified patterns.
+Example: The pattern
+.Ic +(foo\*(Babar)
+matches the strings
+.Dq foo ,
+.Dq bar ,
+.Dq foobar ,
+etc.
+.Sm off
+.It ?( Ar pattern\*(Ba No ...\*(Ba Ar pattern )
+.Sm on
+Matches the empty string or a string that matches one of the specified
+patterns.
+Example: The pattern
+.Ic ?(foo\*(Babar)
+only matches the strings
+.Dq ,
+.Dq foo ,
+and
+.Dq bar .
+.Sm off
+.It @( Ar pattern\*(Ba No ...\*(Ba Ar pattern )
+.Sm on
+Matches a string that matches one of the specified patterns.
+Example: The pattern
+.Ic @(foo\*(Babar)
+only matches the strings
+.Dq foo
+and
+.Dq bar .
+.Sm off
+.It !( Ar pattern\*(Ba No ...\*(Ba Ar pattern )
+.Sm on
+Matches any string that does not match one of the specified patterns.
+Examples: The pattern
+.Ic !(foo\*(Babar)
+matches all strings except
+.Dq foo
+and
+.Dq bar ;
+the pattern
+.Ic !(*)
+matches no strings; the pattern
+.Ic !(?)*\&
+matches all strings (think about it).
+.El
+.Pp
+Note that
+.Nm mksh
+.Po and Nm pdksh Pc
+never matches
+.Sq \&.
+and
+.Sq .. ,
+but
+.At
+.Nm ksh ,
+Bourne
+.Nm sh ,
+and GNU
+.Nm bash
+do.
+.Pp
+Note that none of the above pattern elements match either a period
+.Pq Sq \&.
+at the start of a file name or a slash
+.Pq Sq / ,
+even if they are explicitly used in a [..] sequence; also, the names
+.Sq \&.
+and
+.Sq ..
+are never matched, even by the pattern
+.Sq .* .
+.Pp
+If the
+.Ic markdirs
+option is set, any directories that result from file name generation are marked
+with a trailing
+.Ql / .
+.Ss Input/output redirection
+When a command is executed, its standard input, standard output, and standard
+error (file descriptors 0, 1, and 2, respectively) are normally inherited from
+the shell.
+Three exceptions to this are commands in pipelines, for which
+standard input and/or standard output are those set up by the pipeline,
+asynchronous commands created when job control is disabled, for which standard
+input is initially set to be from
+.Pa /dev/null ,
+and commands for which any of the following redirections have been specified:
+.Bl -tag -width XXxxmarker
+.It \*(Gt Ar file
+Standard output is redirected to
+.Ar file .
+If
+.Ar file
+does not exist, it is created; if it does exist, is a regular file, and the
+.Ic noclobber
+option is set, an error occurs; otherwise, the file is truncated.
+Note that this means the command
+.Ic cmd \*(Ltfoo \*(Gtfoo
+will open
+.Ar foo
+for reading and then truncate it when it opens it for writing, before
+.Ar cmd
+gets a chance to actually read
+.Ar foo .
+.It \*(Gt\*(Ba Ar file
+Same as
+.Ic \*(Gt ,
+except the file is truncated, even if the
+.Ic noclobber
+option is set.
+.It \*(Gt\*(Gt Ar file
+Same as
+.Ic \*(Gt ,
+except if
+.Ar file
+exists it is appended to instead of being truncated.
+Also, the file is opened
+in append mode, so writes always go to the end of the file (see
+.Xr open 2 ) .
+.It \*(Lt Ar file
+Standard input is redirected from
+.Ar file ,
+which is opened for reading.
+.It \*(Lt\*(Gt Ar file
+Same as
+.Ic \*(Lt ,
+except the file is opened for reading and writing.
+.It \*(Lt\*(Lt Ar marker
+After reading the command line containing this kind of redirection (called a
+.Dq here document ) ,
+the shell copies lines from the command source into a temporary file until a
+line matching
+.Ar marker
+is read.
+When the command is executed, standard input is redirected from the
+temporary file.
+If
+.Ar marker
+contains no quoted characters, the contents of the temporary file are processed
+as if enclosed in double quotes each time the command is executed, so
+parameter, command, and arithmetic substitutions are performed, along with
+backslash
+.Pq Sq \e
+escapes for
+.Ql $ ,
+.Ql \` ,
+.Ql \e ,
+and
+.Ql \enewline ,
+but not for
+.Ql \&" .
+If multiple here documents are used on the same command line, they are saved in
+order.
+.Pp
+If no
+.Ar marker
+is given, the here document ends at the next
+.Ic \*(Lt\*(Lt
+and substitution will be performed.
+If
+.Ar marker
+is only a set of either single
+.Dq \*(aq\*(aq
+or double
+.Sq \&""
+quotes with nothing in between, the here document ends at the next empty line
+and substitution will not be performed.
+.It \*(Lt\*(Lt\- Ar marker
+Same as
+.Ic \*(Lt\*(Lt ,
+except leading tabs are stripped from lines in the here document.
+.It \*(Lt\*(Lt\*(Lt Ar word
+Same as
+.Ic \*(Lt\*(Lt ,
+except that
+.Ar word
+.Em is
+the here document.
+This is called a here string.
+.It \*(Lt& Ar fd
+Standard input is duplicated from file descriptor
+.Ar fd .
+.Ar fd
+can be a number, indicating the number of an existing file descriptor;
+the letter
+.Ql p ,
+indicating the file descriptor associated with the output of the current
+co-process; or the character
+.Ql \- ,
+indicating standard input is to be closed.
+Note that
+.Ar fd
+is limited to a single digit in most shell implementations.
+.It \*(Gt& Ar fd
+Same as
+.Ic \*(Lt& ,
+except the operation is done on standard output.
+.It &\*(Gt Ar file
+Same as
+.Ic \*(Gt Ar file 2\*(Gt&1 .
+This is a GNU
+.Nm bash
+extension supported by
+.Nm
+which also supports the preceding explicit fd number, for example,
+.Ic 3&\*(Gt Ar file
+is the same as
+.Ic 3\*(Gt Ar file 2\*(Gt&3
+in
+.Nm
+but a syntax error in GNU
+.Nm bash .
+.It Xo
+.No &\*(Gt\*(Ba Ar file ,
+.No &\*(Gt\*(Gt Ar file ,
+.No &\*(Gt& Ar fd
+.Xc
+Same as
+.Ic \*(Gt\*(Ba Ar file ,
+.Ic \*(Gt\*(Gt Ar file ,
+or
+.Ic \*(Gt& Ar fd ,
+followed by
+.Ic 2\*(Gt&1 ,
+as above.
+These are
+.Nm
+extensions.
+.El
+.Pp
+In any of the above redirections, the file descriptor that is redirected
+(i.e. standard input or standard output)
+can be explicitly given by preceding the
+redirection with a number (portably, only a single digit).
+Parameter, command, and arithmetic
+substitutions, tilde substitutions, and (if the shell is interactive)
+file name generation are all performed on the
+.Ar file ,
+.Ar marker ,
+and
+.Ar fd
+arguments of redirections.
+Note, however, that the results of any file name
+generation are only used if a single file is matched; if multiple files match,
+the word with the expanded file name generation characters is used.
+Note
+that in restricted shells, redirections which can create files cannot be used.
+.Pp
+For simple-commands, redirections may appear anywhere in the command; for
+compound-commands
+.Po
+.Ic if
+statements, etc.
+.Pc ,
+any redirections must appear at the end.
+Redirections are processed after
+pipelines are created and in the order they are given, so the following
+will print an error with a line number prepended to it:
+.Pp
+.D1 $ cat /foo/bar 2\*(Gt&1 \*(Gt/dev/null \*(Ba pr \-n \-t
+.Pp
+File descriptors created by input/output redirections are private to the
+Korn shell, but passed to sub-processes if
+.Fl o Ic posix
+or
+.Fl o Ic sh
+is set.
+.Ss Arithmetic expressions
+Integer arithmetic expressions can be used with the
+.Ic let
+command, inside $((..)) expressions, inside array references (e.g.\&
+.Ar name Ns Bq Ar expr ) ,
+as numeric arguments to the
+.Ic test
+command, and as the value of an assignment to an integer parameter.
+.Pp
+Expressions are calculated using signed arithmetic and the
+.Vt mksh_ari_t
+type (a 32-bit signed integer), unless they begin with a sole
+.Sq #
+character, in which case they use
+.Vt mksh_uari_t
+.Po a 32-bit unsigned integer Pc .
+.Pp
+Expressions may contain alpha-numeric parameter identifiers, array references,
+and integer constants and may be combined with the following C operators
+(listed and grouped in increasing order of precedence):
+.Pp
+Unary operators:
+.Bd -literal -offset indent
++ \- ! \*(TI ++ \-\-
+.Ed
+.Pp
+Binary operators:
+.Bd -literal -offset indent
+,
+= *= /= %= += \-= \*(Lt\*(Lt= \*(Gt\*(Gt= &= \*(ha= \*(Ba=
+\*(Ba\*(Ba
+&&
+\*(Ba
+\*(ha
+&
+== !=
+\*(Lt \*(Lt= \*(Gt= \*(Gt
+\*(Lt\*(Lt \*(Gt\*(Gt
++ \-
+* / %
+.Ed
+.Pp
+Ternary operators:
+.Bd -literal -offset indent
+?: (precedence is immediately higher than assignment)
+.Ed
+.Pp
+Grouping operators:
+.Bd -literal -offset indent
+( )
+.Ed
+.Pp
+Integer constants and expressions are calculated using the
+.Vt mksh_ari_t
+.Po if signed Pc
+or
+.Vt mksh_uari_t
+.Po if unsigned Pc
+type, and are limited to 32 bits.
+Overflows wrap silently.
+Integer constants may be specified with arbitrary bases using the notation
+.Ar base Ns # Ns Ar number ,
+where
+.Ar base
+is a decimal integer specifying the base, and
+.Ar number
+is a number in the specified base.
+Additionally,
+integers may be prefixed with
+.Sq 0X
+or
+.Sq 0x
+(specifying base 16), similar to
+.At
+.Nm ksh ,
+or
+.Sq 0
+(base 8), as an
+.Nm
+extension, in all forms of arithmetic expressions,
+except as numeric arguments to the
+.Ic test
+command.
+As a special
+.Nm mksh
+extension, numbers to the base of one are treated as either (8-bit
+transparent) ASCII or Unicode codepoints, depending on the shell's
+.Ic utf8\-mode
+flag (current setting).
+The
+.At
+.Nm ksh93
+syntax of
+.Dq \*(aqx\*(aq
+instead of
+.Dq 1#x
+is also supported.
+Note that NUL bytes (integral value of zero) cannot be used.
+In Unicode mode, raw octets are mapped into the range EF80..EFFF as in
+OPTU-8, which is in the PUA and has been assigned by CSUR for this use.
+If more than one octet in ASCII mode, or a sequence of more than one
+octet not forming a valid and minimal CESU-8 sequence is passed, the
+behaviour is undefined (usually, the shell aborts with a parse error,
+but rarely, it succeeds, e.g. on the sequence C2 20).
+That's why you should always use ASCII mode unless you know that the
+input is well-formed UTF-8 in the range of 0000..FFFD.
+.Pp
+The operators are evaluated as follows:
+.Bl -tag -width Ds -offset indent
+.It unary +
+Result is the argument (included for completeness).
+.It unary \-
+Negation.
+.It \&!
+Logical NOT;
+the result is 1 if argument is zero, 0 if not.
+.It \*(TI
+Arithmetic (bit-wise) NOT.
+.It ++
+Increment; must be applied to a parameter (not a literal or other expression).
+The parameter is incremented by 1.
+When used as a prefix operator, the result
+is the incremented value of the parameter; when used as a postfix operator, the
+result is the original value of the parameter.
+.It \-\-
+Similar to
+.Ic ++ ,
+except the parameter is decremented by 1.
+.It \&,
+Separates two arithmetic expressions; the left-hand side is evaluated first,
+then the right.
+The result is the value of the expression on the right-hand side.
+.It =
+Assignment; the variable on the left is set to the value on the right.
+.It Xo
+.No *= /= += \-= \*(Lt\*(Lt=
+.No \*(Gt\*(Gt= &= \*(ha= \*(Ba=
+.Xc
+Assignment operators.
+.Sm off
+.Ao Ar var Ac Xo
+.Aq Ar op
+.No = Aq Ar expr
+.Xc
+.Sm on
+is the same as
+.Sm off
+.Ao Ar var Ac Xo
+.No = Aq Ar var
+.Aq Ar op
+.Aq Ar expr ,
+.Xc
+.Sm on
+with any operator precedence in
+.Aq Ar expr
+preserved.
+For example,
+.Dq var1 *= 5 + 3
+is the same as specifying
+.Dq var1 = var1 * (5 + 3) .
+.It \*(Ba\*(Ba
+Logical OR;
+the result is 1 if either argument is non-zero, 0 if not.
+The right argument is evaluated only if the left argument is zero.
+.It &&
+Logical AND;
+the result is 1 if both arguments are non-zero, 0 if not.
+The right argument is evaluated only if the left argument is non-zero.
+.It \*(Ba
+Arithmetic (bit-wise) OR.
+.It \*(ha
+Arithmetic (bit-wise) XOR
+(exclusive-OR).
+.It &
+Arithmetic (bit-wise) AND.
+.It ==
+Equal; the result is 1 if both arguments are equal, 0 if not.
+.It !=
+Not equal; the result is 0 if both arguments are equal, 1 if not.
+.It \*(Lt
+Less than; the result is 1 if the left argument is less than the right, 0 if
+not.
+.It \*(Lt= \*(Gt= \*(Gt
+Less than or equal, greater than or equal, greater than.
+See
+.Ic \*(Lt .
+.It \*(Lt\*(Lt \*(Gt\*(Gt
+Shift left (right); the result is the left argument with its bits shifted left
+(right) by the amount given in the right argument.
+.It + \- * /
+Addition, subtraction, multiplication, and division.
+.It %
+Remainder; the result is the remainder of the division of the left argument by
+the right.
+The sign of the result is unspecified if either argument is negative.
+.It Xo
+.Sm off
+.Aq Ar arg1 ?
+.Aq Ar arg2 :
+.Aq Ar arg3
+.Sm on
+.Xc
+If
+.Aq Ar arg1
+is non-zero, the result is
+.Aq Ar arg2 ;
+otherwise the result is
+.Aq Ar arg3 .
+.El
+.Ss Co-processes
+A co-process (which is a pipeline created with the
+.Sq \*(Ba&
+operator) is an asynchronous process that the shell can both write to (using
+.Ic print \-p )
+and read from (using
+.Ic read \-p ) .
+The input and output of the co-process can also be manipulated using
+.Ic \*(Gt&p
+and
+.Ic \*(Lt&p
+redirections, respectively.
+Once a co-process has been started, another can't
+be started until the co-process exits, or until the co-process's input has been
+redirected using an
+.Ic exec Ar n Ns Ic \*(Gt&p
+redirection.
+If a co-process's input is redirected in this way, the next
+co-process to be started will share the output with the first co-process,
+unless the output of the initial co-process has been redirected using an
+.Ic exec Ar n Ns Ic \*(Lt&p
+redirection.
+.Pp
+Some notes concerning co-processes:
+.Bl -bullet
+.It
+The only way to close the co-process's input (so the co-process reads an
+end-of-file) is to redirect the input to a numbered file descriptor and then
+close that file descriptor:
+.Ic exec 3\*(Gt&p; exec 3\*(Gt&\-
+.It
+In order for co-processes to share a common output, the shell must keep the
+write portion of the output pipe open.
+This means that end-of-file will not be
+detected until all co-processes sharing the co-process's output have exited
+(when they all exit, the shell closes its copy of the pipe).
+This can be
+avoided by redirecting the output to a numbered file descriptor (as this also
+causes the shell to close its copy).
+Note that this behaviour is slightly
+different from the original Korn shell which closes its copy of the write
+portion of the co-process output when the most recently started co-process
+(instead of when all sharing co-processes) exits.
+.It
+.Ic print \-p
+will ignore
+.Dv SIGPIPE
+signals during writes if the signal is not being trapped or ignored; the same
+is true if the co-process input has been duplicated to another file descriptor
+and
+.Ic print \-u Ns Ar n
+is used.
+.El
+.Ss Functions
+Functions are defined using either Korn shell
+.Ic function Ar function-name
+syntax or the Bourne/POSIX shell
+.Ar function-name Ns \&()
+syntax (see below for the difference between the two forms).
+Functions are like
+.Li .\(hyscripts
+(i.e. scripts sourced using the
+.Sq \&.
+built-in)
+in that they are executed in the current environment.
+However, unlike
+.Li .\(hyscripts ,
+shell arguments (i.e. positional parameters $1, $2, etc.)\&
+are never visible inside them.
+When the shell is determining the location of a command, functions
+are searched after special built-in commands, before regular and
+non-regular built-ins, and before the
+.Ev PATH
+is searched.
+.Pp
+An existing function may be deleted using
+.Ic unset Fl f Ar function-name .
+A list of functions can be obtained using
+.Ic typeset +f
+and the function definitions can be listed using
+.Ic typeset \-f .
+The
+.Ic autoload
+command (which is an alias for
+.Ic typeset \-fu )
+may be used to create undefined functions: when an undefined function is
+executed, the shell searches the path specified in the
+.Ev FPATH
+parameter for a file with the same name as the function which, if found, is
+read and executed.
+If after executing the file the named function is found to
+be defined, the function is executed; otherwise, the normal command search is
+continued (i.e. the shell searches the regular built-in command table and
+.Ev PATH ) .
+Note that if a command is not found using
+.Ev PATH ,
+an attempt is made to autoload a function using
+.Ev FPATH
+(this is an undocumented feature of the original Korn shell).
+.Pp
+Functions can have two attributes,
+.Dq trace
+and
+.Dq export ,
+which can be set with
+.Ic typeset \-ft
+and
+.Ic typeset \-fx ,
+respectively.
+When a traced function is executed, the shell's
+.Ic xtrace
+option is turned on for the function's duration.
+The
+.Dq export
+attribute of functions is currently not used.
+In the original Korn shell,
+exported functions are visible to shell scripts that are executed.
+.Pp
+Since functions are executed in the current shell environment, parameter
+assignments made inside functions are visible after the function completes.
+If this is not the desired effect, the
+.Ic typeset
+command can be used inside a function to create a local parameter.
+Note that
+.At
+.Nm ksh93
+uses static scoping (one global scope, one local scope per function), whereas
+.Nm mksh
+uses dynamic scoping (nested scopes of varying locality).
+Note that special parameters (e.g.\&
+.Ic \&$$ , $! )
+can't be scoped in this way.
+.Pp
+The exit status of a function is that of the last command executed in the
+function.
+A function can be made to finish immediately using the
+.Ic return
+command; this may also be used to explicitly specify the exit status.
+.Pp
+Functions defined with the
+.Ic function
+reserved word are treated differently in the following ways from functions
+defined with the
+.Ic \&()
+notation:
+.Bl -bullet
+.It
+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
+can be used properly both inside and outside the function (Bourne-style
+functions leave
+.Ev OPTIND
+untouched, so using
+.Ic getopts
+inside a function interferes with using
+.Ic getopts
+outside the function).
+.It
+Bourne-style function definitions take precedence over alias dereferences
+and remove alias definitions upon encounter, while aliases take precedence
+over Korn-style functions.
+.El
+.Pp
+In the future, the following differences will also be added:
+.Bl -bullet
+.It
+A separate trap/signal environment will be used during the execution of
+functions.
+This will mean that traps set inside a function will not affect the
+shell's traps and signals that are not ignored in the shell (but may be
+trapped) will have their default effect in a function.
+.It
+The EXIT trap, if set in a function, will be executed after the function
+returns.
+.El
+.Ss Command execution
+After evaluation of command-line arguments, redirections, and parameter
+assignments, the type of command is determined: a special built-in, a
+function, a regular built-in, or the name of a file to execute found using the
+.Ev PATH
+parameter.
+The checks are made in the above order.
+Special built-in commands differ from other commands in that the
+.Ev PATH
+parameter is not used to find them, an error during their execution can
+cause a non-interactive shell to exit, and parameter assignments that are
+specified before the command are kept after the command completes.
+Regular built-in commands are different only in that the
+.Ev PATH
+parameter is not used to find them.
+.Pp
+The original
+.Nm ksh
+and POSIX differ somewhat in which commands are considered
+special or regular:
+.Pp
+POSIX special commands
+.Pp
+.Ic \&. , \&: , break , continue ,
+.Ic eval , exec , exit , export ,
+.Ic readonly , return , set , shift ,
+.Ic trap , unset , wait
+.Pp
+Additional
+.Nm
+special commands
+.Pp
+.Ic builtin , global , times , typeset
+.Pp
+Very special commands
+.Pq non-POSIX
+.Pp
+.Ic alias , readonly , set , typeset
+.Pp
+POSIX regular commands
+.Pp
+.Ic alias , bg , cd , command ,
+.Ic false , fc , fg , getopts ,
+.Ic jobs , kill , read , true ,
+.Ic umask , unalias
+.Pp
+Additional
+.Nm
+regular commands
+.Pp
+.Ic \&[ , chdir , bind , cat ,
+.Ic echo , let , mknod , print ,
+.Ic printf , pwd , realpath , rename ,
+.Ic sleep , test , ulimit , whence
+.Pp
+In the future, the additional
+.Nm
+special and regular commands may be treated
+differently from the POSIX special and regular commands.
+.Pp
+Once the type of command has been determined, any command-line parameter
+assignments are performed and exported for the duration of the command.
+.Pp
+The following describes the special and regular built-in commands:
+.Pp
+.Bl -tag -width false -compact
+.It Ic \&. Ar file Op Ar arg ...
+This is called the
+.Dq dot
+command.
+Execute the commands in
+.Ar file
+in the current environment.
+The file is searched for in the directories of
+.Ev PATH .
+If arguments are given, the positional parameters may be used to access them
+while
+.Ar file
+is being executed.
+If no arguments are given, the positional parameters are
+those of the environment the command is used in.
+.Pp
+.It Ic \&: Op Ar ...
+The null command.
+Exit status is set to zero.
+.Pp
+.It Xo Ic alias
+.Oo Fl d \*(Ba t Oo Fl r Oc \*(Ba
+.Cm +\-x Oc
+.Op Fl p
+.Op Cm +
+.Oo Ar name
+.Op Ns = Ns Ar value
+.Ar ... Oc
+.Xc
+Without arguments,
+.Ic alias
+lists all aliases.
+For any name without a value, the existing alias is listed.
+Any name with a value defines an alias (see
+.Sx Aliases
+above).
+.Pp
+When listing aliases, one of two formats is used.
+Normally, aliases are listed as
+.Ar name Ns = Ns Ar value ,
+where
+.Ar value
+is quoted.
+If options were preceded with
+.Ql + ,
+or a lone
+.Ql +
+is given on the command line, only
+.Ar name
+is printed.
+.Pp
+The
+.Fl d
+option causes directory aliases which are used in tilde expansion to be
+listed or set (see
+.Sx Tilde expansion
+above).
+.Pp
+If the
+.Fl p
+option is used, each alias is prefixed with the string
+.Dq alias\ \& .
+.Pp
+The
+.Fl t
+option indicates that tracked aliases are to be listed/set (values specified on
+the command line are ignored for tracked aliases).
+The
+.Fl r
+option indicates that all tracked aliases are to be reset.
+.Pp
+The
+.Fl x
+option sets
+.Pq Ic +x No clears
+the export attribute of an alias, or, if no names are given, lists the aliases
+with the export attribute (exporting an alias has no effect).
+.Pp
+.It Ic bg Op Ar job ...
+Resume the specified stopped job(s) in the background.
+If no jobs are specified,
+.Ic %+
+is assumed.
+See
+.Sx Job control
+below for more information.
+.Pp
+.It Ic bind Op Fl l
+The current bindings are listed.
+If the
+.Fl l
+flag is given,
+.Ic bind
+instead lists the names of the functions to which keys may be bound.
+See
+.Sx Emacs editing mode
+for more information.
+.Pp
+.It Xo Ic bind Op Fl m
+.Ar string Ns = Ns Op Ar substitute
+.Ar ...
+.Xc
+.It Xo Ic bind
+.Ar string Ns = Ns Op Ar editing-command
+.Ar ...
+.Xc
+The specified editing command is bound to the given
+.Ar string ,
+which should consist of a control character
+optionally preceded by one of the two prefix characters
+and optionally succeded by a tilde character.
+Future input of the
+.Ar string
+will cause the editing command to be immediately invoked.
+If the
+.Fl m
+flag is given, the specified input
+.Ar string
+will afterwards be immediately replaced by the given
+.Ar substitute
+string which may contain editing commands but not other macros.
+If a tilde postfix is given, a tilde trailing the one or
+two prefices and the control character is ignored, any
+other trailing character will be processed afterwards.
+.Pp
+Control characters may be written using caret notation
+i.e. \*(haX represents Ctrl-X.
+Note that although only two prefix characters (usually ESC and \*(haX)
+are supported, some multi-character sequences can be supported.
+.Pp
+The following default bindings show how the arrow keys, the home, end and
+delete key on a BSD wsvt25, xterm\-xfree86 or GNU screen terminal are bound
+(of course some escape sequences won't work out quite this nicely):
+.Bd -literal -offset indent
+bind \*(aq\*(haX\*(aq=prefix\-2
+bind \*(aq\*(ha[[\*(aq=prefix\-2
+bind \*(aq\*(haXA\*(aq=up\-history
+bind \*(aq\*(haXB\*(aq=down\-history
+bind \*(aq\*(haXC\*(aq=forward\-char
+bind \*(aq\*(haXD\*(aq=backward\-char
+bind \*(aq\*(haX1\*(TI\*(aq=beginning\-of\-line
+bind \*(aq\*(haX7\*(TI\*(aq=beginning\-of\-line
+bind \*(aq\*(haXH\*(aq=beginning\-of\-line
+bind \*(aq\*(haX4\*(TI\*(aq=end\-of\-line
+bind \*(aq\*(haX8\*(TI\*(aq=end\-of\-line
+bind \*(aq\*(haXF\*(aq=end\-of\-line
+bind \*(aq\*(haX3\*(TI\*(aq=delete\-char\-forward
+.Ed
+.Pp
+.It Ic break Op Ar level
+Exit the
+.Ar level Ns th
+inner-most
+.Ic for ,
+.Ic select ,
+.Ic until ,
+or
+.Ic while
+loop.
+.Ar level
+defaults to 1.
+.Pp
+.It Xo
+.Ic builtin
+.Op Fl \-
+.Ar command Op Ar arg ...
+.Xc
+Execute the built-in command
+.Ar command .
+.Pp
+.It Xo
+.Ic cat
+.Op Fl u
+.Op Ar
+.Xc
+Read files sequentially, in command line order, and write them to
+standard output.
+If a
+.Ar file
+is a single dash
+.Pq Sq -
+or absent, read from standard input.
+Unless compiled with
+.Dv MKSH_NO_EXTERNAL_CAT ,
+if any options are given, an external
+.Xr cat 1
+utility is invoked instead if called from the shell.
+For direct builtin calls, the
+.Tn POSIX
+.Fl u
+option is supported as a no-op.
+.Pp
+.It Xo
+.Ic cd
+.Op Fl L
+.Op Ar dir
+.Xc
+.It Xo
+.Ic cd
+.Fl P Op Fl e
+.Op Ar dir
+.Xc
+.It Xo
+.Ic chdir
+.Op Fl eLP
+.Op Ar dir
+.Xc
+Set the working directory to
+.Ar dir .
+If the parameter
+.Ev CDPATH
+is set, it lists the search path for the directory containing
+.Ar dir .
+A
+.Dv NULL
+path means the current directory.
+If
+.Ar dir
+is found in any component of the
+.Ev CDPATH
+search path other than the
+.Dv NULL
+path, the name of the new working directory will be written to standard output.
+If
+.Ar dir
+is missing, the home directory
+.Ev HOME
+is used.
+If
+.Ar dir
+is
+.Ql \- ,
+the previous working directory is used (see the
+.Ev OLDPWD
+parameter).
+.Pp
+If the
+.Fl L
+option (logical path) is used or if the
+.Ic physical
+option isn't set (see the
+.Ic set
+command below), references to
+.Sq ..
+in
+.Ar dir
+are relative to the path used to get to the directory.
+If the
+.Fl P
+option (physical path) is used or if the
+.Ic physical
+option is set,
+.Sq ..
+is relative to the filesystem directory tree.
+The
+.Ev PWD
+and
+.Ev OLDPWD
+parameters are updated to reflect the current and old working directory,
+respectively.
+If the
+.Fl e
+option is set for physical filesystem traversal, and
+.Ev PWD
+could not be set, the exit code is 1; greater than 1 if an
+error occurred, 0 otherwise.
+.Pp
+.It Xo
+.Ic cd
+.Op Fl eLP
+.Ar old new
+.Xc
+.It Xo
+.Ic chdir
+.Op Fl eLP
+.Ar old new
+.Xc
+The string
+.Ar new
+is substituted for
+.Ar old
+in the current directory, and the shell attempts to change to the new
+directory.
+.Pp
+.It Xo
+.Ic command
+.Op Fl pVv
+.Ar cmd
+.Op Ar arg ...
+.Xc
+If neither the
+.Fl v
+nor
+.Fl V
+option is given,
+.Ar cmd
+is executed exactly as if
+.Ic command
+had not been specified, with two exceptions:
+firstly,
+.Ar cmd
+cannot be a shell function;
+and secondly, special built-in commands lose their specialness
+(i.e. redirection and utility errors do not cause the shell to
+exit, and command assignments are not permanent).
+.Pp
+If the
+.Fl p
+option is given, a default search path is used instead of the current value of
+.Ev PATH ,
+the actual value of which is system dependent.
+.Pp
+If the
+.Fl v
+option is given, instead of executing
+.Ar cmd ,
+information about what would be executed is given (and the same is done for
+.Ar arg ... ) .
+For special and regular built-in commands and functions, their names are simply
+printed; for aliases, a command that defines them is printed; and for commands
+found by searching the
+.Ev PATH
+parameter, the full path of the command is printed.
+If no command is found
+(i.e. the path search fails), nothing is printed and
+.Ic command
+exits with a non-zero status.
+The
+.Fl V
+option is like the
+.Fl v
+option, except it is more verbose.
+.Pp
+.It Ic continue Op Ar level
+Jumps to the beginning of the
+.Ar level Ns th
+inner-most
+.Ic for ,
+.Ic select ,
+.Ic until ,
+or
+.Ic while
+loop.
+.Ar level
+defaults to 1.
+.Pp
+.It Xo
+.Ic echo
+.Op Fl Een
+.Op Ar arg ...
+.Xc
+.Em Warning:
+this utility is not portable; use the Korn shell builtin
+.Ic print
+or the much slower POSIX utility
+.Ic printf
+instead.
+.Pp
+Prints its arguments (separated by spaces) followed by a newline, to the
+standard output.
+The newline is suppressed if any of the arguments contain the
+backslash sequence
+.Ql \ec .
+See the
+.Ic print
+command below for a list of other backslash sequences that are recognised.
+.Pp
+The options are provided for compatibility with
+.Bx
+shell scripts.
+The
+.Fl n
+option suppresses the trailing newline,
+.Fl e
+enables backslash interpretation (a no-op, since this is normally done), and
+.Fl E
+suppresses backslash interpretation.
+.Pp
+If the
+.Ic posix
+or
+.Ic sh
+option is set or this is a direct builtin call, only the first argument
+is treated as an option, and only if it is exactly
+.Dq Fl n .
+Backslash interpretation is disabled.
+.Pp
+.It Ic eval Ar command ...
+The arguments are concatenated (with spaces between them) to form a single
+string which the shell then parses and executes in the current environment.
+.Pp
+.It Xo
+.Ic exec
+.Op Ar command Op Ar arg ...
+.Xc
+The command is executed without forking, replacing the shell process.
+.Pp
+If no command is given except for I/O redirection, the I/O redirection is
+permanent and the shell is
+not replaced.
+Any file descriptors greater than 2 which are opened or
+.Xr dup 2 Ns 'd
+in this way are not made available to other executed commands (i.e. commands
+that are not built-in to the shell).
+Note that the Bourne shell differs here;
+it does pass these file descriptors on.
+.Pp
+.It Ic exit Op Ar status
+The shell exits with the specified exit status.
+If
+.Ar status
+is not specified, the exit status is the current value of the
+.Ic $?\&
+parameter.
+.Pp
+.It Xo
+.Ic export
+.Op Fl p
+.Op Ar parameter Ns Op = Ns Ar value
+.Xc
+Sets the export attribute of the named parameters.
+Exported parameters are passed in the environment to executed commands.
+If values are specified, the named parameters are also assigned.
+.Pp
+If no parameters are specified, the names of all parameters with the export
+attribute are printed one per line, unless the
+.Fl p
+option is used, in which case
+.Ic export
+commands defining all exported parameters, including their values, are printed.
+.Pp
+.It Ic false
+A command that exits with a non-zero status.
+.Pp
+.It Xo
+.Ic fc
+.Oo Fl e Ar editor \*(Ba
+.Fl l Op Fl n Oc
+.Op Fl r
+.Op Ar first Op Ar last
+.Xc
+.Ar first
+and
+.Ar last
+select commands from the history.
+Commands can be selected by history number
+or a string specifying the most recent command starting with that string.
+The
+.Fl l
+option lists the command on standard output, and
+.Fl n
+inhibits the default command numbers.
+The
+.Fl r
+option reverses the order of the list.
+Without
+.Fl l ,
+the selected commands are edited by the editor specified with the
+.Fl e
+option, or if no
+.Fl e
+is specified, the editor specified by the
+.Ev FCEDIT
+parameter (if this parameter is not set,
+.Pa /bin/ed
+is used), and then executed by the shell.
+.Pp
+.It Xo
+.Ic fc
+.Cm \-e \- \*(Ba Fl s
+.Op Fl g
+.Op Ar old Ns = Ns Ar new
+.Op Ar prefix
+.Xc
+Re-execute the selected command (the previous command by default) after
+performing the optional substitution of
+.Ar old
+with
+.Ar new .
+If
+.Fl g
+is specified, all occurrences of
+.Ar old
+are replaced with
+.Ar new .
+The meaning of
+.Cm \-e \-
+and
+.Fl s
+is identical: re-execute the selected command without invoking an editor.
+This command is usually accessed with the predefined
+.Ic alias r=\*(aqfc \-e \-\*(aq
+or by prefixing an interactive mode input line with
+.Sq \&!
+.Pq wbx extension .
+.Pp
+.It Ic fg Op Ar job ...
+Resume the specified job(s) in the foreground.
+If no jobs are specified,
+.Ic %+
+is assumed.
+See
+.Sx Job control
+below for more information.
+.Pp
+.It Xo
+.Ic getopts
+.Ar optstring name
+.Op Ar arg ...
+.Xc
+Used by shell procedures to parse the specified arguments (or positional
+parameters, if no arguments are given) and to check for legal options.
+.Ar optstring
+contains the option letters that
+.Ic getopts
+is to recognise.
+If a letter is followed by a colon, the option is expected to
+have an argument.
+Options that do not take arguments may be grouped in a single argument.
+If an option takes an argument and the option character is not the
+last character of the argument it is found in, the remainder of the argument is
+taken to be the option's argument; otherwise, the next argument is the option's
+argument.
+.Pp
+Each time
+.Ic getopts
+is invoked, it places the next option in the shell parameter
+.Ar name
+and the index of the argument to be processed by the next call to
+.Ic getopts
+in the shell parameter
+.Ev OPTIND .
+If the option was introduced with a
+.Ql + ,
+the option placed in
+.Ar name
+is prefixed with a
+.Ql + .
+When an option requires an argument,
+.Ic getopts
+places it in the shell parameter
+.Ev OPTARG .
+.Pp
+When an illegal option or a missing option argument is encountered, a question
+mark or a colon is placed in
+.Ar name
+(indicating an illegal option or missing argument, respectively) and
+.Ev OPTARG
+is set to the option character that caused the problem.
+Furthermore, if
+.Ar optstring
+does not begin with a colon, a question mark is placed in
+.Ar name ,
+.Ev OPTARG
+is unset, and an error message is printed to standard error.
+.Pp
+When the end of the options is encountered,
+.Ic getopts
+exits with a non-zero exit status.
+Options end at the first (non-option
+argument) argument that does not start with a
+.Ql \- ,
+or when a
+.Ql \-\-
+argument is encountered.
+.Pp
+Option parsing can be reset by setting
+.Ev OPTIND
+to 1 (this is done automatically whenever the shell or a shell procedure is
+invoked).
+.Pp
+Warning: Changing the value of the shell parameter
+.Ev OPTIND
+to a value other than 1, or parsing different sets of arguments without
+resetting
+.Ev OPTIND ,
+may lead to unexpected results.
+.Pp
+.It Xo
+.Ic hash
+.Op Fl r
+.Op Ar name ...
+.Xc
+Without arguments, any hashed executable command pathnames are listed.
+The
+.Fl r
+option causes all hashed commands to be removed from the hash table.
+Each
+.Ar name
+is searched as if it were a command name and added to the hash table if it is
+an executable command.
+.Pp
+.It Xo
+.Ic jobs
+.Op Fl lnp
+.Op Ar job ...
+.Xc
+Display information about the specified job(s); if no jobs are specified, all
+jobs are displayed.
+The
+.Fl n
+option causes information to be displayed only for jobs that have changed
+state since the last notification.
+If the
+.Fl l
+option is used, the process ID of each process in a job is also listed.
+The
+.Fl p
+option causes only the process group of each job to be printed.
+See
+.Sx Job control
+below for the format of
+.Ar job
+and the displayed job.
+.Pp
+.It Xo
+.Ic kill
+.Oo Fl s Ar signame \*(Ba
+.No \- Ns Ar signum \*(Ba
+.No \- Ns Ar signame Oc
+.No { Ar job \*(Ba pid \*(Ba pgrp No }
+.Ar ...
+.Xc
+Send the specified signal to the specified jobs, process IDs, or process
+groups.
+If no signal is specified, the
+.Dv TERM
+signal is sent.
+If a job is specified, the signal is sent to the job's process group.
+See
+.Sx Job control
+below for the format of
+.Ar job .
+.Pp
+.It Xo
+.Ic kill
+.Fl l
+.Op Ar exit-status ...
+.Xc
+Print the signal name corresponding to
+.Ar exit-status .
+If no arguments are specified, a list of all the signals, their numbers, and
+a short description of them are printed.
+.Pp
+.It Ic let Op Ar expression ...
+Each expression is evaluated (see
+.Sx Arithmetic expressions
+above).
+If all expressions are successfully evaluated, the exit status is 0 (1)
+if the last expression evaluated to non-zero (zero).
+If an error occurs during
+the parsing or evaluation of an expression, the exit status is greater than 1.
+Since expressions may need to be quoted,
+.No \&(( Ar expr No ))
+is syntactic sugar for
+.No let \&" Ns Ar expr Ns \&" .
+.Pp
+.It Xo
+.Ic mknod
+.Op Fl m Ar mode
+.Ar name
+.Cm b\*(Bac
+.Ar major minor
+.Xc
+.It Xo
+.Ic mknod
+.Op Fl m Ar mode
+.Ar name
+.Cm p
+.Xc
+Create a device special file.
+The file type may be
+.Cm b
+(block type device),
+.Cm c
+(character type device),
+or
+.Cm p
+(named pipe).
+The file created may be modified according to its
+.Ar mode
+(via the
+.Fl m
+option),
+.Ar major
+(major device number),
+and
+.Ar minor
+(minor device number).
+.Pp
+See
+.Xr mknod 8
+for further information.
+.Pp
+.It Xo
+.Ic print
+.Oo Fl nprsu Ns Oo Ar n Oc \*(Ba
+.Fl R Op Fl en Oc
+.Op Ar argument ...
+.Xc
+.Ic print
+prints its arguments on the standard output, separated by spaces and
+terminated with a newline.
+The
+.Fl n
+option suppresses the newline.
+By default, certain C escapes are translated.
+These include these mentioned in
+.Sx Backslash expansion
+above, as well as
+.Ql \ec ,
+which is equivalent to using the
+.Fl n
+option.
+Backslash expansion may be inhibited with the
+.Fl r
+option.
+The
+.Fl s
+option prints to the history file instead of standard output; the
+.Fl u
+option prints to file descriptor
+.Ar n
+.Po
+.Ar n
+defaults to 1 if omitted
+.Pc ;
+and the
+.Fl p
+option prints to the co-process (see
+.Sx Co-processes
+above).
+.Pp
+The
+.Fl R
+option is used to emulate, to some degree, the
+.Bx
+.Xr echo 1
+command which does not process
+.Ql \e
+sequences unless the
+.Fl e
+option is given.
+As above, the
+.Fl n
+option suppresses the trailing newline.
+.Pp
+.It Ic printf Ar format Op Ar arguments ...
+Formatted output.
+Approximately the same as the utility
+.Ic printf ,
+except that it uses the same
+.Sx Backslash expansion
+and I/O code as the rest of
+.Nm mksh .
+This is not normally part of
+.Nm mksh ;
+however, distributors may have added this as builtin as a speed hack.
+.Pp
+.It Ic pwd Op Fl LP
+Print the present working directory.
+If the
+.Fl L
+option is used or if the
+.Ic physical
+option isn't set (see the
+.Ic set
+command below), the logical path is printed (i.e. the path used to
+.Ic cd
+to the current directory).
+If the
+.Fl P
+option (physical path) is used or if the
+.Ic physical
+option is set, the path determined from the filesystem (by following
+.Sq ..
+directories to the root directory) is printed.
+.Pp
+.It Xo
+.Ic read
+.Op Fl A | Fl a
+.Op Fl d Ar x
+.Oo Fl N Ar z \*(Ba
+.Fl n Ar z Oc
+.Oo Fl p \*(Ba
+.Fl u Ns Op Ar n
+.Oc Op Fl t Ar n
+.Op Fl rs
+.Op Ar p ...
+.Xc
+Reads a line of input, separates the input into fields using the
+.Ev IFS
+parameter (see
+.Sx Substitution
+above), and assigns each field to the specified parameters
+.Ar p .
+If no parameters are specified, the
+.Ev REPLY
+parameter is used to store the result.
+With the
+.Fl A
+and
+.Fl a
+options, only no or one parameter is accepted.
+If there are more parameters than fields, the extra parameters are set to
+the empty string or 0; if there are more fields than parameters, the last
+parameter is assigned the remaining fields (including the word separators).
+.Pp
+The options are as follows:
+.Bl -tag -width XuXnX
+.It Fl A
+Store the result into the parameter
+.Ar p
+(or
+.Ev REPLY )
+as array of words.
+.It Fl a
+Store the result without word splitting into the parameter
+.Ar p
+(or
+.Ev REPLY )
+as array of characters (wide characters if the
+.Ic utf8\-mode
+option is enacted, octets otherwise).
+.It Fl d Ar x
+Use the first byte of
+.Ar x ,
+.Dv NUL
+if empty, instead of the ASCII newline character as input line delimiter.
+.It Fl N Ar z
+Instead of reading till end-of-line, read exactly
+.Ar z
+bytes; less if EOF or a timeout occurs.
+.It Fl n Ar z
+Instead of reading till end-of-line, read up to
+.Ar z
+bytes but return as soon as any bytes are read, e.g.\& from a
+slow terminal device, or if EOF or a timeout occurs.
+.It Fl p
+Read from the currently active co-process, see
+.Sx Co-processes
+above for details on this.
+.It Fl u Ns Op Ar n
+Read from the file descriptor
+.Ar n
+(defaults to 0, i.e.\& standard input).
+The argument must immediately follow the option character.
+.It Fl t Ar n
+Interrupt reading after
+.Ar n
+seconds (specified as positive decimal value with an optional fractional part).
+.It Fl r
+Normally, the ASCII backslash character escapes the special
+meaning of the following character and is stripped from the input;
+.Ic read
+does not stop when encountering a backslash-newline sequence and
+does not store that newline in the result.
+This option enables raw mode, in which backslashes are not processed.
+.It Fl s
+The input line is saved to the history.
+.El
+.Pp
+If the input is a terminal, both the
+.Fl N
+and
+.Fl n
+options set it into raw mode;
+they read an entire file if \-1 is passed as
+.Ar z
+argument.
+.Pp
+The first parameter may have a question mark and a string appended to it, in
+which case the string is used as a prompt (printed to standard error before
+any input is read) if the input is a
+.Xr tty 4
+(e.g.\&
+.Ic read nfoo?\*(aqnumber of foos: \*(aq ) .
+.Pp
+If no input is read or a timeout occurred,
+.Ic read
+exits with a non-zero status.
+.Pp
+Another handy set of tricks:
+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.
+Similarily, when using the
+.Fl a
+option, use of the
+.Fl r
+option might be prudent; the same applies for:
+.Bd -literal -offset indent
+find . \-type f \-print0 \*(Ba \e
+    while IFS= read \-d \*(aq\*(aq \-r filename; do
+	print \-r \-\- "found <${filename#./}>"
+done
+.Ed
+.Pp
+The inner loop will be executed in a subshell and variable changes
+cannot be propagated if executed in a pipeline:
+.Bd -literal -offset indent
+bar \*(Ba baz \*(Ba while read foo; do ...; done
+.Ed
+.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
+.It Xo
+.Ic readonly
+.Op Fl p
+.Oo Ar parameter
+.Op Ns = Ns Ar value
+.Ar ... Oc
+.Xc
+Sets the read-only attribute of the named parameters.
+If values are given,
+parameters are set to them before setting the attribute.
+Once a parameter is
+made read-only, it cannot be unset and its value cannot be changed.
+.Pp
+If no parameters are specified, the names of all parameters with the read-only
+attribute are printed one per line, unless the
+.Fl p
+option is used, in which case
+.Ic readonly
+commands defining all read-only parameters, including their values, are
+printed.
+.Pp
+.It Xo
+.Ic realpath
+.Op Fl \-
+.Ar name
+.Xc
+Prints the resolved absolute pathname corresponding to
+.Ar name .
+If
+.Ar name
+ends with a slash
+.Pq Sq / ,
+it's also checked for existence and whether it is a directory; otherwise,
+.Ic realpath
+returns 0 if the pathname either exists or can be created immediately,
+i.e. all but the last component exist and are directories.
+.Pp
+.It Xo
+.Ic rename
+.Op Fl \-
+.Ar from to
+.Xc
+Renames the file
+.Ar from
+to
+.Ar to .
+Both must be complete pathnames and on the same device.
+This builtin is intended for emergency situations where
+.Pa /bin/mv
+becomes unusable, and directly calls
+.Xr rename 2 .
+.Pp
+.It Ic return Op Ar status
+Returns from a function or
+.Ic .\&
+script, with exit status
+.Ar status .
+If no
+.Ar status
+is given, the exit status of the last executed command is used.
+If used outside of a function or
+.Ic .\&
+script, it has the same effect as
+.Ic exit .
+Note that
+.Nm
+treats both profile and
+.Ev ENV
+files as
+.Ic .\&
+scripts, while the original Korn shell only treats profiles as
+.Ic .\&
+scripts.
+.Pp
+.It Xo
+.Ic set Op Ic +\-abCefhiklmnprsUuvXx
+.Op Ic +\-o Ar option
+.Op Ic +\-A Ar name
+.Op Fl \-
+.Op Ar arg ...
+.Xc
+The
+.Ic set
+command can be used to set
+.Pq Ic \-
+or clear
+.Pq Ic +
+shell options, set the positional parameters, or set an array parameter.
+Options can be changed using the
+.Cm +\-o Ar option
+syntax, where
+.Ar option
+is the long name of an option, or using the
+.Cm +\- Ns Ar letter
+syntax, where
+.Ar letter
+is the option's single letter name (not all options have a single letter name).
+The following table lists both option letters (if they exist) and long names
+along with a description of what the option does:
+.Bl -tag -width 3n
+.It Fl A Ar name
+Sets the elements of the array parameter
+.Ar name
+to
+.Ar arg ...
+If
+.Fl A
+is used, the array is reset (i.e. emptied) first; if
+.Ic +A
+is used, the first N elements are set (where N is the number of arguments);
+the rest are left untouched.
+.Pp
+An alternative syntax for the command
+.Ic set \-A foo \-\- a b c
+which is compatible to
+.Tn GNU
+.Nm bash
+and also supported by
+.At
+.Nm ksh93
+is:
+.Ic foo=(a b c); foo+=(d e)
+.Pp
+Another
+.At
+.Nm ksh93
+and
+.Tn GNU
+.Nm bash
+extension allows specifying the indices used for
+.Ar arg ...
+.Pq from the above example, Ic a b c
+like this:
+.Ic set \-A foo \-\- [0]=a [1]=b [2]=c
+or
+.Ic foo=([0]=a [1]=b [2]=c)
+which can also be written
+.Ic foo=([0]=a b c)
+because indices are incremented automatically.
+.It Fl a \*(Ba Fl o Ic allexport
+All new parameters are created with the export attribute.
+.It Fl b \*(Ba Fl o Ic notify
+Print job notification messages asynchronously, instead of just before the
+prompt.
+Only used if job control is enabled
+.Pq Fl m .
+.It Fl C \*(Ba Fl o Ic noclobber
+Prevent \*(Gt redirection from overwriting existing files.
+Instead, \*(Gt\*(Ba must be used to force an overwrite.
+.It Fl e \*(Ba Fl o Ic errexit
+Exit (after executing the
+.Dv ERR
+trap) as soon as an error occurs or a command fails (i.e. exits with a
+non-zero status).
+This does not apply to commands whose exit status is
+explicitly tested by a shell construct such as
+.Ic if ,
+.Ic until ,
+.Ic while ,
+.Ic && ,
+.Ic \*(Ba\*(Ba ,
+or
+.Ic !\&
+statements.
+.It Fl f \*(Ba Fl o Ic noglob
+Do not expand file name patterns.
+.It Fl h \*(Ba Fl o Ic trackall
+Create tracked aliases for all executed commands (see
+.Sx Aliases
+above).
+Enabled by default for non-interactive shells.
+.It Fl i \*(Ba Fl o Ic interactive
+The shell is an interactive shell.
+This option can only be used when the shell is invoked.
+See above for a description of what this means.
+.It Fl k \*(Ba Fl o Ic keyword
+Parameter assignments are recognised anywhere in a command.
+.It Fl l \*(Ba Fl o Ic login
+The shell is a login shell.
+This option can only be used when the shell is invoked.
+See above for a description of what this means.
+.It Fl m \*(Ba Fl o Ic monitor
+Enable job control (default for interactive shells).
+.It Fl n \*(Ba Fl o Ic noexec
+Do not execute any commands.
+Useful for checking the syntax of scripts
+(ignored if interactive).
+.It Fl p \*(Ba Fl o Ic privileged
+The shell is a privileged shell.
+It is set automatically if, when the shell starts,
+the real UID or GID does not match
+the effective UID (EUID) or GID (EGID), respectively.
+See above for a description of what this means.
+.It Fl r \*(Ba Fl o Ic restricted
+The shell is a restricted shell.
+This option can only be used when the shell is invoked.
+See above for a description of what this means.
+.It Fl s \*(Ba Fl o Ic stdin
+If used when the shell is invoked, commands are read from standard input.
+Set automatically if the shell is invoked with no arguments.
+.Pp
+When
+.Fl s
+is used with the
+.Ic set
+command it causes the specified arguments to be sorted before assigning them to
+the positional parameters (or to array
+.Ar name ,
+if
+.Fl A
+is used).
+.It Fl U \*(Ba Fl o Ic utf8\-mode
+Enable UTF-8 support in the
+.Sx Emacs editing mode
+and internal string handling functions.
+This flag is disabled by default, but can be enabled by setting it on the
+shell command line; is enabled automatically for interactive shells if
+requested at compile time, your system supports
+.Fn setlocale LC_CTYPE \&""
+and optionally
+.Fn nl_langinfo CODESET ,
+or the
+.Ev LC_ALL ,
+.Ev LC_CTYPE ,
+or
+.Ev LANG
+environment variables,
+and at least one of these returns something that matches
+.Dq UTF\-8
+or
+.Dq utf8
+case-insensitively; for direct builtin calls depending on the
+aforementioned environment variables; or for stdin or scripts,
+if the input begins with a UTF-8 Byte Order Mark.
+.It Fl u \*(Ba Fl o Ic nounset
+Referencing of an unset parameter, other than
+.Dq $@
+or
+.Dq $* ,
+is treated as an error, unless one of the
+.Ql \- ,
+.Ql + ,
+or
+.Ql =
+modifiers is used.
+.It Fl v \*(Ba Fl o Ic verbose
+Write shell input to standard error as it is read.
+.It Fl X \*(Ba Fl o Ic markdirs
+Mark directories with a trailing
+.Ql /
+during file name generation.
+.It Fl x \*(Ba Fl o Ic xtrace
+Print commands and parameter assignments when they are executed, preceded by
+the value of
+.Ev PS4 .
+.It Fl o Ic bgnice
+Background jobs are run with lower priority.
+.It Fl o Ic braceexpand
+Enable brace expansion (a.k.a. alternation).
+This is enabled by default.
+If disabled, tilde expansion after an equals sign is disabled as a side effect.
+.It Fl o Ic emacs
+Enable BRL emacs-like command-line editing (interactive shells only); see
+.Sx Emacs editing mode .
+.It Fl o Ic gmacs
+Enable gmacs-like command-line editing (interactive shells only).
+Currently identical to emacs editing except that transpose\-chars (\*(haT) acts
+slightly differently.
+.It Fl o Ic ignoreeof
+The shell will not (easily) exit when end-of-file is read;
+.Ic exit
+must be used.
+To avoid infinite loops, the shell will exit if
+.Dv EOF
+is read 13 times in a row.
+.It Fl o Ic nohup
+Do not kill running jobs with a
+.Dv SIGHUP
+signal when a login shell exits.
+Currently set by default, but this may
+change in the future to be compatible with
+.At
+.Nm ksh ,
+which
+doesn't have this option, but does send the
+.Dv SIGHUP
+signal.
+.It Fl o Ic nolog
+No effect.
+In the original Korn shell, this prevents function definitions from
+being stored in the history file.
+.It Fl o Ic physical
+Causes the
+.Ic cd
+and
+.Ic pwd
+commands to use
+.Dq physical
+(i.e. the filesystem's)
+.Sq ..
+directories instead of
+.Dq logical
+directories (i.e. the shell handles
+.Sq .. ,
+which allows the user to be oblivious of symbolic links to directories).
+Clear by default.
+Note that setting this option does not affect the current value of the
+.Ev PWD
+parameter; only the
+.Ic cd
+command changes
+.Ev PWD .
+See the
+.Ic cd
+and
+.Ic pwd
+commands above for more details.
+.It Fl o Ic posix
+Enable a somewhat more
+.Px
+ish mode.
+As a side effect, setting this flag turns off
+.Ic braceexpand
+mode, which can be turned back on manually, and
+.Ic sh
+mode.
+.It Fl o Ic sh
+Enable
+.Pa /bin/sh
+.Pq kludge
+mode.
+Automatically enabled if the basename of the shell invocation begins with
+.Dq sh
+and this autodetection feature is compiled in
+.Pq not in MirBSD .
+As a side effect, setting this flag turns off
+.Ic braceexpand
+mode, which can be turned back on manually, and
+.Ic posix
+mode.
+.It Fl o Ic vi
+Enable
+.Xr vi 1 Ns -like
+command-line editing (interactive shells only).
+.It Fl o Ic vi\-esccomplete
+In vi command-line editing, do command and file name completion when escape
+(\*(ha[) is entered in command mode.
+.It Fl o Ic vi\-tabcomplete
+In vi command-line editing, do command and file name completion when tab (\*(haI)
+is entered in insert mode.
+This is the default.
+.It Fl o Ic viraw
+No effect.
+In the original Korn shell, unless
+.Ic viraw
+was set, the vi command-line mode would let the
+.Xr tty 4
+driver do the work until ESC (\*(ha[) was entered.
+.Nm
+is always in viraw mode.
+.El
+.Pp
+These options can also be used upon invocation of the shell.
+The current set of
+options (with single letter names) can be found in the parameter
+.Sq $\- .
+.Ic set Fl o
+with no option name will list all the options and whether each is on or off;
+.Ic set +o
+will print the long names of all options that are currently on.
+.Pp
+Remaining arguments, if any, are positional parameters and are assigned, in
+order, to the positional parameters (i.e. $1, $2, etc.).
+If options end with
+.Ql \-\-
+and there are no remaining arguments, all positional parameters are cleared.
+If no options or arguments are given, the values of all names are printed.
+For unknown historical reasons, a lone
+.Ql \-
+option is treated specially \*(en it clears both the
+.Fl v
+and
+.Fl x
+options.
+.Pp
+.It Ic shift Op Ar number
+The positional parameters
+.Ar number Ns +1 ,
+.Ar number Ns +2 ,
+etc. are renamed to
+.Sq 1 ,
+.Sq 2 ,
+etc.
+.Ar number
+defaults to 1.
+.Pp
+.It Ic sleep Ar seconds
+Suspends execution for a minimum of the
+.Ar seconds
+specified as positive decimal value with an optional fractional part.
+Signal delivery may continue execution earlier.
+.Pp
+.It Ic source Ar file Op Ar arg ...
+Like
+.Ic \&. Po Do dot Dc Pc ,
+except that the current working directory is appended to the
+.Ev PATH
+in GNU
+.Nm bash
+and
+.Nm mksh .
+In
+.Nm ksh93
+and
+.Nm mksh ,
+this is implemented as a shell alias instead of a builtin.
+.Pp
+.It Ic test Ar expression
+.It Ic \&[ Ar expression Ic \&]
+.Ic test
+evaluates the
+.Ar expression
+and returns zero status if true, 1 if false, or greater than 1 if there
+was an error.
+It is normally used as the condition command of
+.Ic if
+and
+.Ic while
+statements.
+Symbolic links are followed for all
+.Ar file
+expressions except
+.Fl h
+and
+.Fl L .
+.Pp
+The following basic expressions are available:
+.Bl -tag -width 17n
+.It Fl a Ar file
+.Ar file
+exists.
+.It Fl b Ar file
+.Ar file
+is a block special device.
+.It Fl c Ar file
+.Ar file
+is a character special device.
+.It Fl d Ar file
+.Ar file
+is a directory.
+.It Fl e Ar file
+.Ar file
+exists.
+.It Fl f Ar file
+.Ar file
+is a regular file.
+.It Fl G Ar file
+.Ar file Ns 's
+group is the shell's effective group ID.
+.It Fl g Ar file
+.Ar file Ns 's
+mode has the setgid bit set.
+.It Fl H Ar file
+.Ar file
+is a context dependent directory (only useful on HP-UX).
+.It Fl h Ar file
+.Ar file
+is a symbolic link.
+.It Fl k Ar file
+.Ar file Ns 's
+mode has the
+.Xr sticky 8
+bit set.
+.It Fl L Ar file
+.Ar file
+is a symbolic link.
+.It Fl O Ar file
+.Ar file Ns 's
+owner is the shell's effective user ID.
+.It Fl o Ar option
+Shell
+.Ar option
+is set (see the
+.Ic set
+command above for a list of options).
+As a non-standard extension, if the option starts with a
+.Ql \&! ,
+the test is negated; the test always fails if
+.Ar option
+doesn't exist (so [ \-o foo \-o \-o !foo ] returns true if and only if option
+.Ar foo
+exists).
+The same can be achieved with [ \-o ?foo ] like in
+.At
+.Nm ksh93 .
+.Ar option
+can also be the short flag led by either
+.Ql \-
+or
+.Ql +
+.Pq no logical negation ,
+for example
+.Ql \-x
+or
+.Ql +x
+instead of
+.Ql xtrace .
+.It Fl p Ar file
+.Ar file
+is a named pipe.
+.It Fl r Ar file
+.Ar file
+exists and is readable.
+.It Fl S Ar file
+.Ar file
+is a
+.Xr unix 4 Ns -domain
+socket.
+.It Fl s Ar file
+.Ar file
+is not empty.
+.It Fl t Ar fd
+File descriptor
+.Ar fd
+is a
+.Xr tty 4
+device.
+.It Fl u Ar file
+.Ar file Ns 's
+mode has the setuid bit set.
+.It Fl w Ar file
+.Ar file
+exists and is writable.
+.It Fl x Ar file
+.Ar file
+exists and is executable.
+.It Ar file1 Fl nt Ar file2
+.Ar file1
+is newer than
+.Ar file2
+or
+.Ar file1
+exists and
+.Ar file2
+does not.
+.It Ar file1 Fl ot Ar file2
+.Ar file1
+is older than
+.Ar file2
+or
+.Ar file2
+exists and
+.Ar file1
+does not.
+.It Ar file1 Fl ef Ar file2
+.Ar file1
+is the same file as
+.Ar file2 .
+.It Ar string
+.Ar string
+has non-zero length.
+.It Fl n Ar string
+.Ar string
+is not empty.
+.It Fl z Ar string
+.Ar string
+is empty.
+.It Ar string No = Ar string
+Strings are equal.
+.It Ar string No == Ar string
+Strings are equal.
+.It Ar string No \*(Gt Ar string
+First string operand is greater than second string operand.
+.It Ar string No \*(Lt Ar string
+First string operand is less than second string operand.
+.It Ar string No != Ar string
+Strings are not equal.
+.It Ar number Fl eq Ar number
+Numbers compare equal.
+.It Ar number Fl ne Ar number
+Numbers compare not equal.
+.It Ar number Fl ge Ar number
+Numbers compare greater than or equal.
+.It Ar number Fl gt Ar number
+Numbers compare greater than.
+.It Ar number Fl le Ar number
+Numbers compare less than or equal.
+.It Ar number Fl \&lt Ar number
+Numbers compare less than.
+.El
+.Pp
+The above basic expressions, in which unary operators have precedence over
+binary operators, may be combined with the following operators (listed in
+increasing order of precedence):
+.Bd -literal -offset indent
+expr \-o expr		Logical OR.
+expr \-a expr		Logical AND.
+! expr			Logical NOT.
+( expr )		Grouping.
+.Ed
+.Pp
+Note that a number actually may be an arithmetic expression, such as
+a mathematical term or the name of an integer variable:
+.Bd -literal -offset indent
+x=1; [ "x" \-eq 1 ]	evaluates to true
+.Ed
+.Pp
+Note that some special rules are applied (courtesy of POSIX)
+if the number of
+arguments to
+.Ic test
+or
+.Ic \&[ ... \&]
+is less than five: if leading
+.Ql \&!
+arguments can be stripped such that only one argument remains then a string
+length test is performed (again, even if the argument is a unary operator); if
+leading
+.Ql \&!
+arguments can be stripped such that three arguments remain and the second
+argument is a binary operator, then the binary operation is performed (even
+if the first argument is a unary operator, including an unstripped
+.Ql \&! ) .
+.Pp
+.Sy Note :
+A common mistake is to use
+.Dq if \&[ $foo = bar \&]
+which fails if parameter
+.Dq foo
+is
+.Dv NULL
+or unset, if it has embedded spaces (i.e.\&
+.Ev IFS
+octets), or if it is a unary operator like
+.Sq \&!
+or
+.Sq Fl n .
+Use tests like
+.Dq if \&[ x\&"$foo\&" = x"bar" \&]
+instead, or the double-bracket operator
+.Dq if \&[[ $foo = bar \&]]
+or, to avoid pattern matching (see
+.Ic \&[[
+above):
+.Dq if \&[[ $foo = "$bar" \&]]
+.Pp
+.It Xo
+.Ic time
+.Op Fl p
+.Op Ar pipeline
+.Xc
+If a
+.Ar pipeline
+is given, the times used to execute the pipeline are reported.
+If no pipeline
+is given, then the user and system time used by the shell itself, and all the
+commands it has run since it was started, are reported.
+The times reported are the real time (elapsed time from start to finish),
+the user CPU time (time spent running in user mode), and the system CPU time
+(time spent running in kernel mode).
+Times are reported to standard error; the format of the output is:
+.Pp
+.Dl "0m0.00s real     0m0.00s user     0m0.00s system"
+.Pp
+If the
+.Fl p
+option is given the output is slightly longer:
+.Bd -literal -offset indent
+real     0.00
+user     0.00
+sys      0.00
+.Ed
+.Pp
+It is an error to specify the
+.Fl p
+option unless
+.Ar pipeline
+is a simple command.
+.Pp
+Simple redirections of standard error do not affect the output of the
+.Ic time
+command:
+.Pp
+.Dl $ time sleep 1 2\*(Gtafile
+.Dl $ { time sleep 1; } 2\*(Gtafile
+.Pp
+Times for the first command do not go to
+.Dq afile ,
+but those of the second command do.
+.Pp
+.It Ic times
+Print the accumulated user and system times used both by the shell
+and by processes that the shell started which have exited.
+The format of the output is:
+.Bd -literal -offset indent
+0m0.00s 0m0.00s
+0m0.00s 0m0.00s
+.Ed
+.Pp
+.It Ic trap Op Ar handler signal ...
+Sets a trap handler that is to be executed when any of the specified signals are
+received.
+.Ar handler
+is either a
+.Dv NULL
+string, indicating the signals are to be ignored, a minus sign
+.Pq Sq \- ,
+indicating that the default action is to be taken for the signals (see
+.Xr signal 3 ) ,
+or a string containing shell commands to be evaluated and executed at the first
+opportunity (i.e. when the current command completes, or before printing the
+next
+.Ev PS1
+prompt) after receipt of one of the signals.
+.Ar signal
+is the name of a signal (e.g.\&
+.Dv PIPE
+or
+.Dv ALRM )
+or the number of the signal (see the
+.Ic kill \-l
+command above).
+.Pp
+There are two special signals:
+.Dv EXIT
+(also known as 0) which is executed when the shell is about to exit, and
+.Dv ERR ,
+which is executed after an error occurs (an error is something that would cause
+the shell to exit if the
+.Fl e
+or
+.Ic errexit
+option were set \*(en see the
+.Ic set
+command above).
+.Dv EXIT
+handlers are executed in the environment of the last executed command.
+Note
+that for non-interactive shells, the trap handler cannot be changed for signals
+that were ignored when the shell started.
+.Pp
+With no arguments,
+.Ic trap
+lists, as a series of
+.Ic trap
+commands, the current state of the traps that have been set since the shell
+started.
+Note that the output of
+.Ic trap
+cannot be usefully piped to another process (an artifact of the fact that
+traps are cleared when subprocesses are created).
+.Pp
+The original Korn shell's
+.Dv DEBUG
+trap and the handling of
+.Dv ERR
+and
+.Dv EXIT
+traps in functions are not yet implemented.
+.Pp
+.It Ic true
+A command that exits with a zero value.
+.Pp
+.It Xo
+.Ic global
+.Oo Op Ic +\-alpnrtUux
+.Op Fl L Ns Op Ar n
+.Op Fl R Ns Op Ar n
+.Op Fl Z Ns Op Ar n
+.Op Fl i Ns Op Ar n
+.No \*(Ba Fl f Op Fl tux Oc
+.Oo Ar name
+.Op Ns = Ns Ar value
+.Ar ... Oc
+.Xc
+.It Xo
+.Ic typeset
+.Oo Op Ic +\-alpnrtUux
+.Op Fl LRZ Ns Op Ar n
+.Op Fl i Ns Op Ar n
+.No \*(Ba Fl f Op Fl tux Oc
+.Oo Ar name
+.Op Ns = Ns Ar value
+.Ar ... Oc
+.Xc
+Display or set parameter attributes.
+With no
+.Ar name
+arguments, parameter attributes are displayed; if no options are used, the
+current attributes of all parameters are printed as
+.Ic typeset
+commands; if an option is given (or
+.Ql \-
+with no option letter), all parameters and their values with the specified
+attributes are printed; if options are introduced with
+.Ql + ,
+parameter values are not printed.
+.Pp
+If
+.Ar name
+arguments are given, the attributes of the named parameters are set
+.Pq Ic \-
+or cleared
+.Pq Ic + .
+Values for parameters may optionally be specified.
+For
+.Ar name Ns \&[*] ,
+the change affects the entire array, and no value may be specified.
+.Pp
+If
+.Ic typeset
+is used inside a function, any parameters specified are localised.
+This is not done by the otherwise identical
+.Ic global .
+.Pp
+When
+.Fl f
+is used,
+.Ic typeset
+operates on the attributes of functions.
+As with parameters, if no
+.Ar name
+arguments are given,
+functions are listed with their values (i.e. definitions) unless
+options are introduced with
+.Ql + ,
+in which case only the function names are reported.
+.Bl -tag -width Ds
+.It Fl a
+Indexed array attribute.
+.It Fl f
+Function mode.
+Display or set functions and their attributes, instead of parameters.
+.It Fl i Ns Op Ar n
+Integer attribute.
+.Ar n
+specifies the base to use when displaying the integer (if not specified, the
+base given in the first assignment is used).
+Parameters with this attribute may
+be assigned values containing arithmetic expressions.
+.It Fl L Ns Op Ar n
+Left justify attribute.
+.Ar n
+specifies the field width.
+If
+.Ar n
+is not specified, the current width of a parameter (or the width of its first
+assigned value) is used.
+Leading whitespace (and zeros, if used with the
+.Fl Z
+option) is stripped.
+If necessary, values are either truncated or space padded
+to fit the field width.
+.It Fl l
+Lower case attribute.
+All upper case characters in values are converted to lower case.
+(In the original Korn shell, this parameter meant
+.Dq long integer
+when used with the
+.Fl i
+option.)
+.It Fl n
+Create a bound variable (name reference): any access to the variable
+.Ar name
+will access the variable
+.Ar value
+in the current scope (this is different from
+.At
+.Nm ksh93 ! )
+instead.
+Also different from
+.At
+.Nm ksh93
+is that
+.Ar value
+is lazily evaluated at the time
+.Ar name
+is accessed.
+This can be used by functions to access variables whose names are
+passed as parametres, instead of using
+.Ic eval .
+.It Fl p
+Print complete
+.Ic typeset
+commands that can be used to re-create the attributes and values of
+parameters.
+.It Fl R Ns Op Ar n
+Right justify attribute.
+.Ar n
+specifies the field width.
+If
+.Ar n
+is not specified, the current width of a parameter (or the width of its first
+assigned value) is used.
+Trailing whitespace is stripped.
+If necessary, values are either stripped of leading characters or space
+padded to make them fit the field width.
+.It Fl r
+Read-only attribute.
+Parameters with this attribute may not be assigned to or unset.
+Once this attribute is set, it cannot be turned off.
+.It Fl t
+Tag attribute.
+Has no meaning to the shell; provided for application use.
+.Pp
+For functions,
+.Fl t
+is the trace attribute.
+When functions with the trace attribute are executed, the
+.Ic xtrace
+.Pq Fl x
+shell option is temporarily turned on.
+.It Fl U
+Unsigned integer attribute.
+Integers are printed as unsigned values (combine with the
+.Fl i
+option).
+This option is not in the original Korn shell.
+.It Fl u
+Upper case attribute.
+All lower case characters in values are converted to upper case.
+(In the original Korn shell, this parameter meant
+.Dq unsigned integer
+when used with the
+.Fl i
+option which meant upper case letters would never be used for bases greater
+than 10.
+See the
+.Fl U
+option.)
+.Pp
+For functions,
+.Fl u
+is the undefined attribute.
+See
+.Sx Functions
+above for the implications of this.
+.It Fl x
+Export attribute.
+Parameters (or functions) are placed in the environment of
+any executed commands.
+Exported functions are not yet implemented.
+.It Fl Z Ns Op Ar n
+Zero fill attribute.
+If not combined with
+.Fl L ,
+this is the same as
+.Fl R ,
+except zero padding is used instead of space padding.
+For integers, the number instead of the base is padded.
+.El
+.Pp
+If any of the
+.\" long integer ,
+.Fl i ,
+.Fl L ,
+.Fl l ,
+.Fl R ,
+.Fl U ,
+.Fl u ,
+or
+.Fl Z
+options are changed, all others from this set are cleared,
+unless they are also given on the same command line.
+.Pp
+.It Xo
+.Ic ulimit
+.Op Fl aBCcdefHiLlMmnOPpqrSsTtVvw
+.Op Ar value
+.Xc
+Display or set process limits.
+If no options are used, the file size limit
+.Pq Fl f
+is assumed.
+.Ar value ,
+if specified, may be either an arithmetic expression or the word
+.Dq unlimited .
+The limits affect the shell and any processes created by the shell after a
+limit is imposed.
+Note that some systems may not allow limits to be increased
+once they are set.
+Also note that the types of limits available are system
+dependent \*(en some systems have only the
+.Fl f
+limit.
+.Bl -tag -width 5n
+.It Fl a
+Display all limits; unless
+.Fl H
+is used, soft limits are displayed.
+.It Fl B Ar n
+Set the socket buffer size to
+.Ar n
+kibibytes.
+.It Fl C Ar n
+Set the number of cached threads to
+.Ar n .
+.It Fl c Ar n
+Impose a size limit of
+.Ar n
+blocks on the size of core dumps.
+.It Fl d Ar n
+Impose a size limit of
+.Ar n
+kibibytes on the size of the data area.
+.It Fl e Ar n
+Set the maximum niceness to
+.Ar n .
+.It Fl f Ar n
+Impose a size limit of
+.Ar n
+blocks on files written by the shell and its child processes (files of any
+size may be read).
+.It Fl H
+Set the hard limit only (the default is to set both hard and soft limits).
+.It Fl i Ar n
+Set the number of pending signals to
+.Ar n .
+.It Fl L Ar n
+Control flocks; documentation is missing.
+.It Fl l Ar n
+Impose a limit of
+.Ar n
+kibibytes on the amount of locked (wired) physical memory.
+.It Fl M Ar n
+Set the AIO locked memory to
+.Ar n
+kibibytes.
+.It Fl m Ar n
+Impose a limit of
+.Ar n
+kibibytes on the amount of physical memory used.
+.It Fl n Ar n
+Impose a limit of
+.Ar n
+file descriptors that can be open at once.
+.It Fl O Ar n
+Set the number of AIO operations to
+.Ar n .
+.It Fl P Ar n
+Limit the number of threads per process to
+.Ar n .
+.It Fl p Ar n
+Impose a limit of
+.Ar n
+processes that can be run by the user at any one time.
+.It Fl q Ar n
+Limit the size of
+.Tn POSIX
+message queues to
+.Ar n
+bytes.
+.It Fl r Ar n
+Set the maximum real-time priority to
+.Ar n .
+.It Fl S
+Set the soft limit only (the default is to set both hard and soft limits).
+.It Fl s Ar n
+Impose a size limit of
+.Ar n
+kibibytes on the size of the stack area.
+.It Fl T Ar n
+Impose a time limit of
+.Ar n
+real seconds to be used by each process.
+.It Fl t Ar n
+Impose a time limit of
+.Ar n
+CPU seconds spent in user mode to be used by each process.
+.It Fl V Ar n
+Set the number of vnode monitors on Haiku to
+.Ar n .
+.It Fl v Ar n
+Impose a limit of
+.Ar n
+kibibytes on the amount of virtual memory (address space) used.
+.It Fl w Ar n
+Impose a limit of
+.Ar n
+kibibytes on the amount of swap space used.
+.El
+.Pp
+As far as
+.Ic ulimit
+is concerned, a block is 512 bytes.
+.Pp
+.It Xo
+.Ic umask
+.Op Fl S
+.Op Ar mask
+.Xc
+Display or set the file permission creation mask, or umask (see
+.Xr umask 2 ) .
+If the
+.Fl S
+option is used, the mask displayed or set is symbolic; otherwise, it is an
+octal number.
+.Pp
+Symbolic masks are like those used by
+.Xr chmod 1 .
+When used, they describe what permissions may be made available (as opposed to
+octal masks in which a set bit means the corresponding bit is to be cleared).
+For example,
+.Dq ug=rwx,o=
+sets the mask so files will not be readable, writable, or executable by
+.Dq others ,
+and is equivalent (on most systems) to the octal mask
+.Dq 007 .
+.Pp
+.It Xo
+.Ic unalias
+.Op Fl adt
+.Op Ar name ...
+.Xc
+The aliases for the given names are removed.
+If the
+.Fl a
+option is used, all aliases are removed.
+If the
+.Fl t
+or
+.Fl d
+options are used, the indicated operations are carried out on tracked or
+directory aliases, respectively.
+.Pp
+.It Xo
+.Ic unset
+.Op Fl fv
+.Ar parameter ...
+.Xc
+Unset the named parameters
+.Po
+.Fl v ,
+the default
+.Pc
+or functions
+.Pq Fl f .
+With
+.Ar parameter Ns \&[*] ,
+attributes are kept, only values are unset.
+.Pp
+The exit status is non-zero if any of the parameters have the read-only
+attribute set, zero otherwise.
+.Pp
+.It Ic wait Op Ar job ...
+Wait for the specified job(s) to finish.
+The exit status of
+.Ic wait
+is that of the last specified job; if the last job is killed by a signal, the
+exit status is 128 + the number of the signal (see
+.Ic kill \-l Ar exit-status
+above); if the last specified job can't be found (because it never existed, or
+had already finished), the exit status of
+.Ic wait
+is 127.
+See
+.Sx Job control
+below for the format of
+.Ar job .
+.Ic wait
+will return if a signal for which a trap has been set is received, or if a
+.Dv SIGHUP ,
+.Dv SIGINT ,
+or
+.Dv SIGQUIT
+signal is received.
+.Pp
+If no jobs are specified,
+.Ic wait
+waits for all currently running jobs (if any) to finish and exits with a zero
+status.
+If job monitoring is enabled, the completion status of jobs is printed
+(this is not the case when jobs are explicitly specified).
+.Pp
+.It Xo
+.Ic whence
+.Op Fl pv
+.Op Ar name ...
+.Xc
+For each
+.Ar name ,
+the type of command is listed (reserved word, built-in, alias,
+function, tracked alias, or executable).
+If the
+.Fl p
+option is used, a path search is performed even if
+.Ar name
+is a reserved word, alias, etc.
+Without the
+.Fl v
+option,
+.Ic whence
+is similar to
+.Ic command Fl v
+except that
+.Ic whence
+will find reserved words and won't print aliases as alias commands.
+With the
+.Fl v
+option,
+.Ic whence
+is the same as
+.Ic command Fl V .
+Note that for
+.Ic whence ,
+the
+.Fl p
+option does not affect the search path used, as it does for
+.Ic command .
+If the type of one or more of the names could not be determined, the exit
+status is non-zero.
+.El
+.Ss Job control
+Job control refers to the shell's ability to monitor and control jobs which
+are processes or groups of processes created for commands or pipelines.
+At a minimum, the shell keeps track of the status of the background (i.e.\&
+asynchronous) jobs that currently exist; this information can be displayed
+using the
+.Ic jobs
+commands.
+If job control is fully enabled (using
+.Ic set \-m
+or
+.Ic set \-o monitor ) ,
+as it is for interactive shells, the processes of a job are placed in their
+own process group.
+Foreground jobs can be stopped by typing the suspend
+character from the terminal (normally \*(haZ), jobs can be restarted in either the
+foreground or background using the
+.Ic fg
+and
+.Ic bg
+commands, and the state of the terminal is saved or restored when a foreground
+job is stopped or restarted, respectively.
+.Pp
+Note that only commands that create processes (e.g. asynchronous commands,
+subshell commands, and non-built-in, non-function commands) can be stopped;
+commands like
+.Ic read
+cannot be.
+.Pp
+When a job is created, it is assigned a job number.
+For interactive shells, this number is printed inside
+.Dq \&[..] ,
+followed by the process IDs of the processes in the job when an asynchronous
+command is run.
+A job may be referred to in the
+.Ic bg ,
+.Ic fg ,
+.Ic jobs ,
+.Ic kill ,
+and
+.Ic wait
+commands either by the process ID of the last process in the command pipeline
+(as stored in the
+.Ic $!\&
+parameter) or by prefixing the job number with a percent
+sign
+.Pq Sq % .
+Other percent sequences can also be used to refer to jobs:
+.Bl -tag -width "%+ x %% x %XX"
+.It %+ \*(Ba %% \*(Ba %
+The most recently stopped job, or, if there are no stopped jobs, the oldest
+running job.
+.It %\-
+The job that would be the
+.Ic %+
+job if the latter did not exist.
+.It % Ns Ar n
+The job with job number
+.Ar n .
+.It %? Ns Ar string
+The job with its command containing the string
+.Ar string
+(an error occurs if multiple jobs are matched).
+.It % Ns Ar string
+The job with its command starting with the string
+.Ar string
+(an error occurs if multiple jobs are matched).
+.El
+.Pp
+When a job changes state (e.g. a background job finishes or foreground job is
+stopped), the shell prints the following status information:
+.Pp
+.D1 [ Ns Ar number ] Ar flag status command
+.Pp
+where...
+.Bl -tag -width "command"
+.It Ar number
+is the job number of the job;
+.It Ar flag
+is the
+.Ql +
+or
+.Ql \-
+character if the job is the
+.Ic %+
+or
+.Ic %\-
+job, respectively, or space if it is neither;
+.It Ar status
+indicates the current state of the job and can be:
+.Bl -tag -width "RunningXX"
+.It Done Op Ar number
+The job exited.
+.Ar number
+is the exit status of the job which is omitted if the status is zero.
+.It Running
+The job has neither stopped nor exited (note that running does not necessarily
+mean consuming CPU time \*(en
+the process could be blocked waiting for some event).
+.It Stopped Op Ar signal
+The job was stopped by the indicated
+.Ar signal
+(if no signal is given, the job was stopped by
+.Dv SIGTSTP ) .
+.It Ar signal-description Op Dq core dumped
+The job was killed by a signal (e.g. memory fault, hangup); use
+.Ic kill \-l
+for a list of signal descriptions.
+The
+.Dq core dumped
+message indicates the process created a core file.
+.El
+.It Ar command
+is the command that created the process.
+If there are multiple processes in
+the job, each process will have a line showing its
+.Ar command
+and possibly its
+.Ar status ,
+if it is different from the status of the previous process.
+.El
+.Pp
+When an attempt is made to exit the shell while there are jobs in the stopped
+state, the shell warns the user that there are stopped jobs and does not exit.
+If another attempt is immediately made to exit the shell, the stopped jobs are
+sent a
+.Dv SIGHUP
+signal and the shell exits.
+Similarly, if the
+.Ic nohup
+option is not set and there are running jobs when an attempt is made to exit
+a login shell, the shell warns the user and does not exit.
+If another attempt
+is immediately made to exit the shell, the running jobs are sent a
+.Dv SIGHUP
+signal and the shell exits.
+.Ss Interactive input line editing
+The shell supports three modes of reading command lines from a
+.Xr tty 4
+in an interactive session, controlled by the
+.Ic emacs ,
+.Ic gmacs ,
+and
+.Ic vi
+options (at most one of these can be set at once).
+The default is
+.Ic emacs .
+Editing modes can be set explicitly using the
+.Ic set
+built-in.
+If none of these options are enabled,
+the shell simply reads lines using the normal
+.Xr tty 4
+driver.
+If the
+.Ic emacs
+or
+.Ic gmacs
+option is set, the shell allows emacs-like editing of the command; similarly,
+if the
+.Ic vi
+option is set, the shell allows vi-like editing of the command.
+These modes are described in detail in the following sections.
+.Pp
+In these editing modes, if a line is longer than the screen width (see the
+.Ev COLUMNS
+parameter),
+a
+.Ql \*(Gt ,
+.Ql + ,
+or
+.Ql \*(Lt
+character is displayed in the last column indicating that there are more
+characters after, before and after, or before the current position,
+respectively.
+The line is scrolled horizontally as necessary.
+.Pp
+Completed lines are pushed into the history, unless they begin with an
+IFS octet or IFS white space, or are the same as the previous line.
+.Ss Emacs editing mode
+When the
+.Ic emacs
+option is set, interactive input line editing is enabled.
+Warning: This mode is
+slightly different from the emacs mode in the original Korn shell.
+In this mode, various editing commands
+(typically bound to one or more control characters) cause immediate actions
+without waiting for a newline.
+Several editing commands are bound to particular
+control characters when the shell is invoked; these bindings can be changed
+using the
+.Ic bind
+command.
+.Pp
+The following is a list of available editing commands.
+Each description starts with the name of the command,
+suffixed with a colon;
+an
+.Op Ar n
+(if the command can be prefixed with a count); and any keys the command is
+bound to by default, written using caret notation
+e.g. the ASCII ESC character is written as \*(ha[.
+These control sequences are not case sensitive.
+A count prefix for a command is entered using the sequence
+.Pf \*(ha[ Ns Ar n ,
+where
+.Ar n
+is a sequence of 1 or more digits.
+Unless otherwise specified, if a count is
+omitted, it defaults to 1.
+.Pp
+Note that editing command names are used only with the
+.Ic bind
+command.
+Furthermore, many editing commands are useful only on terminals with
+a visible cursor.
+The default bindings were chosen to resemble corresponding
+Emacs key bindings.
+The user's
+.Xr tty 4
+characters (e.g.\&
+.Dv ERASE )
+are bound to
+reasonable substitutes and override the default bindings.
+.Bl -tag -width Ds
+.It abort: \*(haC, \*(haG
+Abort the current command, empty the line buffer and
+set the exit state to interrupted.
+.It auto\-insert: Op Ar n
+Simply causes the character to appear as literal input.
+Most ordinary characters are bound to this.
+.It Xo backward\-char:
+.Op Ar n
+.No \*(haB , \*(haXD , ANSI-CurLeft
+.Xc
+Moves the cursor backward
+.Ar n
+characters.
+.It Xo backward\-word:
+.Op Ar n
+.No \*(ha[b , ANSI-Ctrl-CurLeft , ANSI-Alt-CurLeft
+.Xc
+Moves the cursor backward to the beginning of the word; words consist of
+alphanumerics, underscore
+.Pq Sq _ ,
+and dollar sign
+.Pq Sq $
+characters.
+.It beginning\-of\-history: \*(ha[\*(Lt
+Moves to the beginning of the history.
+.It beginning\-of\-line: \*(haA, ANSI-Home
+Moves the cursor to the beginning of the edited input line.
+.It Xo capitalise\-word:
+.Op Ar n
+.No \*(ha[C , \*(ha[c
+.Xc
+Uppercase the first character in the next
+.Ar n
+words, leaving the cursor past the end of the last word.
+.It clear\-screen: \*(ha[\*(haL
+Prints a compile-time configurable sequence to clear the screen and home
+the cursor, redraws the entire prompt and the currently edited input line.
+The default sequence works for almost all standard terminals.
+.It comment: \*(ha[#
+If the current line does not begin with a comment character, one is added at
+the beginning of the line and the line is entered (as if return had been
+pressed); otherwise, the existing comment characters are removed and the cursor
+is placed at the beginning of the line.
+.It complete: \*(ha[\*(ha[
+Automatically completes as much as is unique of the command name or the file
+name containing the cursor.
+If the entire remaining command or file name is
+unique, a space is printed after its completion, unless it is a directory name
+in which case
+.Ql /
+is appended.
+If there is no command or file name with the current partial word
+as its prefix, a bell character is output (usually causing a beep to be
+sounded).
+.It complete\-command: \*(haX\*(ha[
+Automatically completes as much as is unique of the command name having the
+partial word up to the cursor as its prefix, as in the
+.Ic complete
+command above.
+.It complete\-file: \*(ha[\*(haX
+Automatically completes as much as is unique of the file name having the
+partial word up to the cursor as its prefix, as in the
+.Ic complete
+command described above.
+.It complete\-list: \*(haI, \*(ha[=
+Complete as much as is possible of the current word,
+and list the possible completions for it.
+If only one completion is possible,
+match as in the
+.Ic complete
+command above.
+Note that \*(haI is usually generated by the TAB (tabulator) key.
+.It Xo delete\-char\-backward:
+.Op Ar n
+.No ERASE , \*(ha? , \*(haH
+.Xc
+Deletes
+.Ar n
+characters before the cursor.
+.It Xo delete\-char\-forward:
+.Op Ar n
+.No ANSI-Del
+.Xc
+Deletes
+.Ar n
+characters after the cursor.
+.It Xo delete\-word\-backward:
+.Op Ar n
+.No WERASE , \*(ha[\*(ha? , \*(ha[\*(haH , \*(ha[h
+.Xc
+Deletes
+.Ar n
+words before the cursor.
+.It Xo delete\-word\-forward:
+.Op Ar n
+.No \*(ha[d
+.Xc
+Deletes characters after the cursor up to the end of
+.Ar n
+words.
+.It Xo down\-history:
+.Op Ar n
+.No \*(haN , \*(haXB , ANSI-CurDown
+.Xc
+Scrolls the history buffer forward
+.Ar n
+lines (later).
+Each input line originally starts just after the last entry
+in the history buffer, so
+.Ic down\-history
+is not useful until either
+.Ic search\-history ,
+.Ic search\-history\-up
+or
+.Ic up\-history
+has been performed.
+.It Xo downcase\-word:
+.Op Ar n
+.No \*(ha[L , \*(ha[l
+.Xc
+Lowercases the next
+.Ar n
+words.
+.It Xo edit\-line:
+.Op Ar n
+.No \*(haXe
+.Xc
+Edit line
+.Ar n
+or the current line, if not specified, interactively.
+The actual command executed is
+.Ic fc \-e ${VISUAL:\-${EDITOR:\-vi}} Ar n .
+.It end\-of\-history: \*(ha[\*(Gt
+Moves to the end of the history.
+.It end\-of\-line: \*(haE, ANSI-End
+Moves the cursor to the end of the input line.
+.It eot: \*(ha_
+Acts as an end-of-file; this is useful because edit-mode input disables
+normal terminal input canonicalization.
+.It Xo eot\-or\-delete:
+.Op Ar n
+.No \*(haD
+.Xc
+Acts as
+.Ic eot
+if alone on a line; otherwise acts as
+.Ic delete\-char\-forward .
+.It error: (not bound)
+Error (ring the bell).
+.It exchange\-point\-and\-mark: \*(haX\*(haX
+Places the cursor where the mark is and sets the mark to where the cursor was.
+.It expand\-file: \*(ha[*
+Appends a
+.Ql *
+to the current word and replaces the word with the result of performing file
+globbing on the word.
+If no files match the pattern, the bell is rung.
+.It Xo forward\-char:
+.Op Ar n
+.No \*(haF , \*(haXC , ANSI-CurRight
+.Xc
+Moves the cursor forward
+.Ar n
+characters.
+.It Xo forward\-word:
+.Op Ar n
+.No \*(ha[f , ANSI-Ctrl-CurRight , ANSI-Alt-CurRight
+.Xc
+Moves the cursor forward to the end of the
+.Ar n Ns th
+word.
+.It Xo goto\-history:
+.Op Ar n
+.No \*(ha[g
+.Xc
+Goes to history number
+.Ar n .
+.It kill\-line: KILL
+Deletes the entire input line.
+.It kill\-region: \*(haW
+Deletes the input between the cursor and the mark.
+.It Xo kill\-to\-eol:
+.Op Ar n
+.No \*(haK
+.Xc
+Deletes the input from the cursor to the end of the line if
+.Ar n
+is not specified; otherwise deletes characters between the cursor and column
+.Ar n .
+.It list: \*(ha[?
+Prints a sorted, columnated list of command names or file names (if any) that
+can complete the partial word containing the cursor.
+Directory names have
+.Ql /
+appended to them.
+.It list\-command: \*(haX?
+Prints a sorted, columnated list of command names (if any) that can complete
+the partial word containing the cursor.
+.It list\-file: \*(haX\*(haY
+Prints a sorted, columnated list of file names (if any) that can complete the
+partial word containing the cursor.
+File type indicators are appended as described under
+.Ic list
+above.
+.It newline: \*(haJ , \*(haM
+Causes the current input line to be processed by the shell.
+The current cursor position may be anywhere on the line.
+.It newline\-and\-next: \*(haO
+Causes the current input line to be processed by the shell, and the next line
+from history becomes the current line.
+This is only useful after an
+.Ic up\-history ,
+.Ic search\-history
+or
+.Ic search\-history\-up .
+.It no\-op: QUIT
+This does nothing.
+.It prefix\-1: \*(ha[
+Introduces a 2-character command sequence.
+.It prefix\-2: \*(haX , \*(ha[[ , \*(ha[O
+Introduces a 2-character command sequence.
+.It Xo prev\-hist\-word:
+.Op Ar n
+.No \*(ha[. , \*(ha[_
+.Xc
+The last word, or, if given, the
+.Ar n Ns th
+word (zero-based) of the previous (on repeated execution, second-last,
+third-last, etc.) command is inserted at the cursor.
+Use of this editing command trashes the mark.
+.It quote: \*(ha\*(ha , \*(haV
+The following character is taken literally rather than as an editing command.
+.It redraw: \*(haL
+Reprints the last line of the prompt string and the current input line
+on a new line.
+.It Xo search\-character\-backward:
+.Op Ar n
+.No \*(ha[\*(ha]
+.Xc
+Search backward in the current line for the
+.Ar n Ns th
+occurrence of the next character typed.
+.It Xo search\-character\-forward:
+.Op Ar n
+.No \*(ha]
+.Xc
+Search forward in the current line for the
+.Ar n Ns th
+occurrence of the next character typed.
+.It search\-history: \*(haR
+Enter incremental search mode.
+The internal history list is searched
+backwards for commands matching the input.
+An initial
+.Ql \*(ha
+in the search string anchors the search.
+The escape key will leave search mode.
+Other commands, including sequences of escape as
+.Ic prefix\-1
+followed by a
+.Ic prefix\-1
+or
+.Ic prefix\-2
+key will be executed after leaving search mode.
+The
+.Ic abort Pq \*(haG
+command will restore the input line before search started.
+Successive
+.Ic search\-history
+commands continue searching backward to the next previous occurrence of the
+pattern.
+The history buffer retains only a finite number of lines; the oldest
+are discarded as necessary.
+.It search\-history\-up: ANSI-PgUp
+Search backwards through the history buffer for commands whose beginning match
+the portion of the input line before the cursor.
+When used on an empty line, this has the same effect as
+.Ic up\-history .
+.It search\-history\-down: ANSI-PgDn
+Search forwards through the history buffer for commands whose beginning match
+the portion of the input line before the cursor.
+When used on an empty line, this has the same effect as
+.Ic down\-history .
+This is only useful after an
+.Ic up\-history ,
+.Ic search\-history
+or
+.Ic search\-history\-up .
+.It set\-mark\-command: \*(ha[ Ns Aq space
+Set the mark at the cursor position.
+.It transpose\-chars: \*(haT
+If at the end of line, or if the
+.Ic gmacs
+option is set, this exchanges the two previous characters; otherwise, it
+exchanges the previous and current characters and moves the cursor one
+character to the right.
+.It Xo up\-history:
+.Op Ar n
+.No \*(haP , \*(haXA , ANSI-CurUp
+.Xc
+Scrolls the history buffer backward
+.Ar n
+lines (earlier).
+.It Xo upcase\-word:
+.Op Ar n
+.No \*(ha[U , \*(ha[u
+.Xc
+Uppercase the next
+.Ar n
+words.
+.It version: \*(ha[\*(haV
+Display the version of
+.Nm mksh .
+The current edit buffer is restored as soon as a key is pressed.
+The restoring keypress is processed, unless it is a space.
+.It yank: \*(haY
+Inserts the most recently killed text string at the current cursor position.
+.It yank\-pop: \*(ha[y
+Immediately after a
+.Ic yank ,
+replaces the inserted text string with the next previously killed text string.
+.El
+.Ss Vi editing mode
+.Em Note:
+The vi command-line editing mode is orphaned, yet still functional.
+.Pp
+The vi command-line editor in
+.Nm
+has basically the same commands as the
+.Xr vi 1
+editor with the following exceptions:
+.Bl -bullet
+.It
+You start out in insert mode.
+.It
+There are file name and command completion commands:
+=, \e, *, \*(haX, \*(haE, \*(haF, and, optionally,
+.Aq tab
+and
+.Aq esc .
+.It
+The
+.Ic _
+command is different (in
+.Nm mksh ,
+it is the last argument command; in
+.Xr vi 1
+it goes to the start of the current line).
+.It
+The
+.Ic /
+and
+.Ic G
+commands move in the opposite direction to the
+.Ic j
+command.
+.It
+Commands which don't make sense in a single line editor are not available
+(e.g. screen movement commands and
+.Xr ex 1 Ns -style
+colon
+.Pq Ic \&:
+commands).
+.El
+.Pp
+Like
+.Xr vi 1 ,
+there are two modes:
+.Dq insert
+mode and
+.Dq command
+mode.
+In insert mode, most characters are simply put in the buffer at the
+current cursor position as they are typed; however, some characters are
+treated specially.
+In particular, the following characters are taken from current
+.Xr tty 4
+settings
+(see
+.Xr stty 1 )
+and have their usual meaning (normal values are in parentheses): kill (\*(haU),
+erase (\*(ha?), werase (\*(haW), eof (\*(haD), intr (\*(haC), and quit (\*(ha\e).
+In addition to
+the above, the following characters are also treated specially in insert mode:
+.Bl -tag -width XJXXXXM
+.It \*(haE
+Command and file name enumeration (see below).
+.It \*(haF
+Command and file name completion (see below).
+If used twice in a row, the
+list of possible completions is displayed; if used a third time, the completion
+is undone.
+.It \*(haH
+Erases previous character.
+.It \*(haJ \*(Ba \*(haM
+End of line.
+The current line is read, parsed, and executed by the shell.
+.It \*(haV
+Literal next.
+The next character typed is not treated specially (can be used
+to insert the characters being described here).
+.It \*(haX
+Command and file name expansion (see below).
+.It Aq esc
+Puts the editor in command mode (see below).
+.It Aq tab
+Optional file name and command completion (see
+.Ic \*(haF
+above), enabled with
+.Ic set \-o vi\-tabcomplete .
+.El
+.Pp
+In command mode, each character is interpreted as a command.
+Characters that
+don't correspond to commands, are illegal combinations of commands, or are
+commands that can't be carried out, all cause beeps.
+In the following command descriptions, an
+.Op Ar n
+indicates the command may be prefixed by a number (e.g.\&
+.Ic 10l
+moves right 10 characters); if no number prefix is used,
+.Ar n
+is assumed to be 1 unless otherwise specified.
+The term
+.Dq current position
+refers to the position between the cursor and the character preceding the
+cursor.
+A
+.Dq word
+is a sequence of letters, digits, and underscore characters or a sequence of
+non-letter, non-digit, non-underscore, and non-whitespace characters (e.g.\&
+.Dq ab2*&\*(ha
+contains two words) and a
+.Dq big-word
+is a sequence of non-whitespace characters.
+.Pp
+Special
+.Nm
+vi commands:
+.Pp
+The following commands are not in, or are different from, the normal vi file
+editor:
+.Bl -tag -width 10n
+.It Xo
+.Oo Ar n Oc Ns _
+.Xc
+Insert a space followed by the
+.Ar n Ns th
+big-word from the last command in the history at the current position and enter
+insert mode; if
+.Ar n
+is not specified, the last word is inserted.
+.It #
+Insert the comment character
+.Pq Sq #
+at the start of the current line and return the line to the shell (equivalent
+to
+.Ic I#\*(haJ ) .
+.It Xo
+.Oo Ar n Oc Ns g
+.Xc
+Like
+.Ic G ,
+except if
+.Ar n
+is not specified, it goes to the most recent remembered line.
+.It Xo
+.Oo Ar n Oc Ns v
+.Xc
+Edit line
+.Ar n
+using the
+.Xr vi 1
+editor; if
+.Ar n
+is not specified, the current line is edited.
+The actual command executed is
+.Ic fc \-e ${VISUAL:\-${EDITOR:\-vi}} Ar n .
+.It * and \*(haX
+Command or file name expansion is applied to the current big-word (with an
+appended
+.Ql *
+if the word contains no file globbing characters) \*(en the big-word is replaced
+with the resulting words.
+If the current big-word is the first on the line
+or follows one of the characters
+.Ql \&; ,
+.Ql \*(Ba ,
+.Ql & ,
+.Ql \&( ,
+or
+.Ql \&) ,
+and does not contain a slash
+.Pq Sq / ,
+then command expansion is done; otherwise file name expansion is done.
+Command expansion will match the big-word against all aliases, functions, and
+built-in commands as well as any executable files found by searching the
+directories in the
+.Ev PATH
+parameter.
+File name expansion matches the big-word against the files in the
+current directory.
+After expansion, the cursor is placed just past the last
+word and the editor is in insert mode.
+.It Xo
+.Oo Ar n Oc Ns \e ,
+.Oo Ar n Oc Ns \*(haF ,
+.Oo Ar n Oc Ns Aq tab ,
+.No and
+.Oo Ar n Oc Ns Aq esc
+.Xc
+Command/file name completion.
+Replace the current big-word with the
+longest unique match obtained after performing command and file name expansion.
+.Aq tab
+is only recognised if the
+.Ic vi\-tabcomplete
+option is set, while
+.Aq esc
+is only recognised if the
+.Ic vi\-esccomplete
+option is set (see
+.Ic set \-o ) .
+If
+.Ar n
+is specified, the
+.Ar n Ns th
+possible completion is selected (as reported by the command/file name
+enumeration command).
+.It = and \*(haE
+Command/file name enumeration.
+List all the commands or files that match the current big-word.
+.It \*(haV
+Display the version of
+.Nm mksh .
+The current edit buffer is restored as soon as a key is pressed.
+The restoring keypress is ignored.
+.It @ Ns Ar c
+Macro expansion.
+Execute the commands found in the alias
+.Ar c .
+.El
+.Pp
+Intra-line movement commands:
+.Bl -tag -width Ds
+.It Xo
+.Oo Ar n Oc Ns h and
+.Oo Ar n Oc Ns \*(haH
+.Xc
+Move left
+.Ar n
+characters.
+.It Xo
+.Oo Ar n Oc Ns l and
+.Oo Ar n Oc Ns Aq space
+.Xc
+Move right
+.Ar n
+characters.
+.It 0
+Move to column 0.
+.It \*(ha
+Move to the first non-whitespace character.
+.It Xo
+.Oo Ar n Oc Ns \*(Ba
+.Xc
+Move to column
+.Ar n .
+.It $
+Move to the last character.
+.It Xo
+.Oo Ar n Oc Ns b
+.Xc
+Move back
+.Ar n
+words.
+.It Xo
+.Oo Ar n Oc Ns B
+.Xc
+Move back
+.Ar n
+big-words.
+.It Xo
+.Oo Ar n Oc Ns e
+.Xc
+Move forward to the end of the word,
+.Ar n
+times.
+.It Xo
+.Oo Ar n Oc Ns E
+.Xc
+Move forward to the end of the big-word,
+.Ar n
+times.
+.It Xo
+.Oo Ar n Oc Ns w
+.Xc
+Move forward
+.Ar n
+words.
+.It Xo
+.Oo Ar n Oc Ns W
+.Xc
+Move forward
+.Ar n
+big-words.
+.It %
+Find match.
+The editor looks forward for the nearest parenthesis, bracket, or
+brace and then moves the cursor to the matching parenthesis, bracket, or brace.
+.It Xo
+.Oo Ar n Oc Ns f Ns Ar c
+.Xc
+Move forward to the
+.Ar n Ns th
+occurrence of the character
+.Ar c .
+.It Xo
+.Oo Ar n Oc Ns F Ns Ar c
+.Xc
+Move backward to the
+.Ar n Ns th
+occurrence of the character
+.Ar c .
+.It Xo
+.Oo Ar n Oc Ns t Ns Ar c
+.Xc
+Move forward to just before the
+.Ar n Ns th
+occurrence of the character
+.Ar c .
+.It Xo
+.Oo Ar n Oc Ns T Ns Ar c
+.Xc
+Move backward to just before the
+.Ar n Ns th
+occurrence of the character
+.Ar c .
+.It Xo
+.Oo Ar n Oc Ns \&;
+.Xc
+Repeats the last
+.Ic f , F , t ,
+or
+.Ic T
+command.
+.It Xo
+.Oo Ar n Oc Ns \&,
+.Xc
+Repeats the last
+.Ic f , F , t ,
+or
+.Ic T
+command, but moves in the opposite direction.
+.El
+.Pp
+Inter-line movement commands:
+.Bl -tag -width Ds
+.It Xo
+.Oo Ar n Oc Ns j ,
+.Oo Ar n Oc Ns + ,
+.No and
+.Oo Ar n Oc Ns \*(haN
+.Xc
+Move to the
+.Ar n Ns th
+next line in the history.
+.It Xo
+.Oo Ar n Oc Ns k ,
+.Oo Ar n Oc Ns \- ,
+.No and
+.Oo Ar n Oc Ns \*(haP
+.Xc
+Move to the
+.Ar n Ns th
+previous line in the history.
+.It Xo
+.Oo Ar n Oc Ns G
+.Xc
+Move to line
+.Ar n
+in the history; if
+.Ar n
+is not specified, the number of the first remembered line is used.
+.It Xo
+.Oo Ar n Oc Ns g
+.Xc
+Like
+.Ic G ,
+except if
+.Ar n
+is not specified, it goes to the most recent remembered line.
+.It Xo
+.Oo Ar n Oc Ns / Ns Ar string
+.Xc
+Search backward through the history for the
+.Ar n Ns th
+line containing
+.Ar string ;
+if
+.Ar string
+starts with
+.Ql \*(ha ,
+the remainder of the string must appear at the start of the history line for
+it to match.
+.It Xo
+.Oo Ar n Oc Ns \&? Ns Ar string
+.Xc
+Same as
+.Ic / ,
+except it searches forward through the history.
+.It Xo
+.Oo Ar n Oc Ns n
+.Xc
+Search for the
+.Ar n Ns th
+occurrence of the last search string;
+the direction of the search is the same as the last search.
+.It Xo
+.Oo Ar n Oc Ns N
+.Xc
+Search for the
+.Ar n Ns th
+occurrence of the last search string;
+the direction of the search is the opposite of the last search.
+.El
+.Pp
+Edit commands
+.Bl -tag -width Ds
+.It Xo
+.Oo Ar n Oc Ns a
+.Xc
+Append text
+.Ar n
+times; goes into insert mode just after the current position.
+The append is
+only replicated if command mode is re-entered i.e.\&
+.Aq esc
+is used.
+.It Xo
+.Oo Ar n Oc Ns A
+.Xc
+Same as
+.Ic a ,
+except it appends at the end of the line.
+.It Xo
+.Oo Ar n Oc Ns i
+.Xc
+Insert text
+.Ar n
+times; goes into insert mode at the current position.
+The insertion is only
+replicated if command mode is re-entered i.e.\&
+.Aq esc
+is used.
+.It Xo
+.Oo Ar n Oc Ns I
+.Xc
+Same as
+.Ic i ,
+except the insertion is done just before the first non-blank character.
+.It Xo
+.Oo Ar n Oc Ns s
+.Xc
+Substitute the next
+.Ar n
+characters (i.e. delete the characters and go into insert mode).
+.It S
+Substitute whole line.
+All characters from the first non-blank character to the
+end of the line are deleted and insert mode is entered.
+.It Xo
+.Oo Ar n Oc Ns c Ns Ar move-cmd
+.Xc
+Change from the current position to the position resulting from
+.Ar n move-cmd Ns s
+(i.e. delete the indicated region and go into insert mode); if
+.Ar move-cmd
+is
+.Ic c ,
+the line starting from the first non-blank character is changed.
+.It C
+Change from the current position to the end of the line (i.e. delete to the
+end of the line and go into insert mode).
+.It Xo
+.Oo Ar n Oc Ns x
+.Xc
+Delete the next
+.Ar n
+characters.
+.It Xo
+.Oo Ar n Oc Ns X
+.Xc
+Delete the previous
+.Ar n
+characters.
+.It D
+Delete to the end of the line.
+.It Xo
+.Oo Ar n Oc Ns d Ns Ar move-cmd
+.Xc
+Delete from the current position to the position resulting from
+.Ar n move-cmd Ns s ;
+.Ar move-cmd
+is a movement command (see above) or
+.Ic d ,
+in which case the current line is deleted.
+.It Xo
+.Oo Ar n Oc Ns r Ns Ar c
+.Xc
+Replace the next
+.Ar n
+characters with the character
+.Ar c .
+.It Xo
+.Oo Ar n Oc Ns R
+.Xc
+Replace.
+Enter insert mode but overwrite existing characters instead of
+inserting before existing characters.
+The replacement is repeated
+.Ar n
+times.
+.It Xo
+.Oo Ar n Oc Ns \*(TI
+.Xc
+Change the case of the next
+.Ar n
+characters.
+.It Xo
+.Oo Ar n Oc Ns y Ns Ar move-cmd
+.Xc
+Yank from the current position to the position resulting from
+.Ar n move-cmd Ns s
+into the yank buffer; if
+.Ar move-cmd
+is
+.Ic y ,
+the whole line is yanked.
+.It Y
+Yank from the current position to the end of the line.
+.It Xo
+.Oo Ar n Oc Ns p
+.Xc
+Paste the contents of the yank buffer just after the current position,
+.Ar n
+times.
+.It Xo
+.Oo Ar n Oc Ns P
+.Xc
+Same as
+.Ic p ,
+except the buffer is pasted at the current position.
+.El
+.Pp
+Miscellaneous vi commands
+.Bl -tag -width Ds
+.It \*(haJ and \*(haM
+The current line is read, parsed, and executed by the shell.
+.It \*(haL and \*(haR
+Redraw the current line.
+.It Xo
+.Oo Ar n Oc Ns \&.
+.Xc
+Redo the last edit command
+.Ar n
+times.
+.It u
+Undo the last edit command.
+.It U
+Undo all changes that have been made to the current line.
+.It Ar intr No and Ar quit
+The interrupt and quit terminal characters cause the current line to be
+deleted and a new prompt to be printed.
+.El
+.Sh FILES
+.Bl -tag -width XetcXsuid_profile -compact
+.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);
+AOSP Android builds use
+.Pa /system/etc/mkshrc .
+.It Pa \*(TI/.profile
+User profile (non-privileged login shells); see
+.Sx Startup files
+near the top of this manual.
+.It Pa /etc/profile
+System profile (login shells); see
+.Sx Startup files.
+.It Pa /etc/shells
+Shell database.
+.It Pa /etc/suid_profile
+Suid profile (privileged shells); see
+.Sx Startup files.
+.El
+.Pp
+Note: On Android,
+.Pa /system/etc/
+contains the system and suid profile.
+.Sh SEE ALSO
+.Xr awk 1 ,
+.Xr cat 1 ,
+.Xr ed 1 ,
+.Xr getopt 1 ,
+.Xr printf 1 ,
+.Xr sed 1 ,
+.Xr sh 1 ,
+.Xr stty 1 ,
+.Xr dup 2 ,
+.Xr execve 2 ,
+.Xr getgid 2 ,
+.Xr getuid 2 ,
+.Xr mknod 2 ,
+.Xr mkfifo 2 ,
+.Xr open 2 ,
+.Xr pipe 2 ,
+.Xr rename 2 ,
+.Xr wait 2 ,
+.Xr getopt 3 ,
+.Xr nl_langinfo 3 ,
+.Xr setlocale 3 ,
+.Xr signal 3 ,
+.Xr system 3 ,
+.Xr tty 4 ,
+.Xr shells 5 ,
+.Xr environ 7 ,
+.Xr script 7 ,
+.Xr utf\-8 7 ,
+.Xr mknod 8
+.Pp
+.Pa http://docsrv.sco.com:507/en/man/html.C/sh.C.html
+.Rs
+.%A Morris Bolsky
+.%B "The KornShell Command and Programming Language"
+.%D 1989
+.%I "Prentice Hall PTR"
+.%P "xvi\ +\ 356 pages"
+.%O "ISBN 978\-0\-13\-516972\-8 (0\-13\-516972\-0)"
+.Re
+.Rs
+.%A Morris I. Bolsky
+.%A David G. Korn
+.%B "The New KornShell Command and Programming Language (2nd Edition)"
+.%D 1995
+.%I "Prentice Hall PTR"
+.%P "xvi\ +\ 400 pages"
+.%O "ISBN 978\-0\-13\-182700\-4 (0\-13\-182700\-6)"
+.Re
+.Rs
+.%A Stephen G. Kochan
+.%A Patrick H. Wood
+.%B "\\*(tNUNIX\\*(sP Shell Programming"
+.%V "Revised Edition"
+.%D 1990
+.%I "Hayden"
+.%P "xi\ +\ 490 pages"
+.%O "ISBN 978\-0\-672\-48448\-3 (0\-672\-48448\-X)"
+.Re
+.Rs
+.%A "IEEE Inc."
+.%B "\\*(tNIEEE\\*(sP Standard for Information Technology \*(en Portable Operating System Interface (POSIX)"
+.%V "Part 2: Shell and Utilities"
+.%D 1993
+.%I "IEEE Press"
+.%P "xvii\ +\ 1195 pages"
+.%O "ISBN 978\-1\-55937\-255\-8 (1\-55937\-255\-9)"
+.Re
+.Rs
+.%A Bill Rosenblatt
+.%B "Learning the Korn Shell"
+.%D 1993
+.%I "O'Reilly"
+.%P "360 pages"
+.%O "ISBN 978\-1\-56592\-054\-5 (1\-56592\-054\-6)"
+.Re
+.Rs
+.%A Bill Rosenblatt
+.%A Arnold Robbins
+.%B "Learning the Korn Shell, Second Edition"
+.%D 2002
+.%I "O'Reilly"
+.%P "432 pages"
+.%O "ISBN 978\-0\-596\-00195\-7 (0\-596\-00195\-9)"
+.Re
+.Rs
+.%A Barry Rosenberg
+.%B "KornShell Programming Tutorial"
+.%D 1991
+.%I "Addison-Wesley Professional"
+.%P "xxi\ +\ 324 pages"
+.%O "ISBN 978\-0\-201\-56324\-5 (0\-201\-56324\-X)"
+.Re
+.Sh AUTHORS
+.Nm "The MirBSD Korn Shell"
+is developed by
+.An Thorsten Glaser Aq tg@mirbsd.org
+and currently maintained as part of The MirOS Project.
+This shell is based upon the Public Domain Korn SHell.
+The developer of mksh recognises the efforts of the pdksh authors,
+who had dedicated their work into Public Domain, our users, and
+all contributors, such as the Debian and OpenBSD projects.
+.\"
+.\" Charles Forsyth, author of the (Public Domain) Bourne Shell clone,
+.\" which mksh is derived from, agreed to the following:
+.\"
+.\" In countries where the Public Domain status of the work may not be
+.\" valid, its primary author hereby grants a copyright licence to the
+.\" general public to deal in the work without restriction and permis-
+.\" sion to sublicence derivates under the terms of any (OSI approved)
+.\" Open Source licence.
+.\"
+See the documentation, CVS, and web site for details.
+.Sh CAVEATS
+.Nm
+only supports the Unicode BMP (Basic Multilingual Plane).
+It has a different scope model from
+.At
+.Nm ksh ,
+which leads to subtile differences in semantics for identical builtins.
+.Pp
+The parts of a pipeline, like below, are executed in subshells.
+Thus, variable assignments inside them fail.
+Use co-processes instead.
+.Bd -literal -offset indent
+foo \*(Ba bar \*(Ba read baz            # will not change $baz
+foo \*(Ba bar \*(Ba& read \-p baz        # will, however, do so
+.Ed
+.Sh BUGS
+Suspending (using \*(haZ) pipelines like the one below will only suspend
+the currently running part of the pipeline; in this example,
+.Dq fubar
+is immediately printed on suspension (but not later after an
+.Ic fg ) .
+.Bd -literal -offset indent
+$ /bin/sleep 666 && echo fubar
+.Ed
+.Pp
+This document attempts to describe
+.Nm mksh\ R40+CVS
+and up,
+compiled without any options impacting functionality, such as
+.Dv MKSH_SMALL ,
+for an operating environment supporting all of its advanced needs.
+Please report bugs in
+.Nm
+to the
+.Mx
+mailing list at
+.Aq miros\-discuss@mirbsd.org
+or in the
+.Li \&#\&!/bin/mksh
+.Pq or Li \&#ksh
+IRC channel at
+.Pa irc.freenode.net:6667 .
diff --git a/src/sh.h b/src/sh.h
index 11588c9..740518c 100644
--- a/src/sh.h
+++ b/src/sh.h
@@ -9,28 +9,28 @@
 /*	$OpenBSD: tty.h,v 1.5 2004/12/20 11:34:26 otto Exp $	*/
 
 /*-
- * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ * Copyright © 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
  *	Thorsten Glaser <tg@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
  * are retained or reproduced in an accompanying document, permission
- * is granted to deal in this work without restriction, including un-
+ * is granted to deal in this work without restriction, including un‐
  * limited rights to use, publicly perform, distribute, sell, modify,
  * merge, give away, or sublicence.
  *
- * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
  * the utmost extent permitted by applicable law, neither express nor
  * implied; without malicious intent or gross negligence. In no event
  * may a licensor, author or contributor be held liable for indirect,
  * direct, other damage, loss, or other issues arising in any way out
  * of dealing in the work, even if advised of the possibility of such
  * damage or existence of a defect, except proven that it results out
- * of said person's immediate fault when using the work as intended.
+ * of said person’s immediate fault when using the work as intended.
  */
 
 #ifdef __dietlibc__
 /* XXX imake style */
-#define _BSD_SOURCE	/* live, BSD, live! */
+#define _BSD_SOURCE	/* live, BSD, live❣ */
 #endif
 
 #if HAVE_SYS_PARAM_H
@@ -68,9 +68,6 @@
 #include <setjmp.h>
 #include <signal.h>
 #include <stdarg.h>
-#if HAVE_STDBOOL_H
-#include <stdbool.h>
-#endif
 #include <stddef.h>
 #if HAVE_STDINT_H
 #include <stdint.h>
@@ -93,12 +90,12 @@
 
 #undef __attribute__
 #if HAVE_ATTRIBUTE_BOUNDED
-#define MKSH_A_BOUNDED(x,y,z)	__attribute__((bounded (x, y, z)))
+#define MKSH_A_BOUNDED(x,y,z)	__attribute__((__bounded__ (x, y, z)))
 #else
 #define MKSH_A_BOUNDED(x,y,z)	/* nothing */
 #endif
 #if HAVE_ATTRIBUTE_FORMAT
-#define MKSH_A_FORMAT(x,y,z)	__attribute__((format (x, y, z)))
+#define MKSH_A_FORMAT(x,y,z)	__attribute__((__format__ (x, y, z)))
 #else
 #define MKSH_A_FORMAT(x,y,z)	/* nothing */
 #endif
@@ -108,17 +105,17 @@
 #define MKSH_A_NONNULL(a)	/* nothing */
 #endif
 #if HAVE_ATTRIBUTE_NORETURN
-#define MKSH_A_NORETURN		__attribute__((noreturn))
+#define MKSH_A_NORETURN		__attribute__((__noreturn__))
 #else
 #define MKSH_A_NORETURN		/* nothing */
 #endif
 #if HAVE_ATTRIBUTE_UNUSED
-#define MKSH_A_UNUSED		__attribute__((unused))
+#define MKSH_A_UNUSED		__attribute__((__unused__))
 #else
 #define MKSH_A_UNUSED		/* nothing */
 #endif
 #if HAVE_ATTRIBUTE_USED
-#define MKSH_A_USED		__attribute__((used))
+#define MKSH_A_USED		__attribute__((__used__))
 #else
 #define MKSH_A_USED		/* nothing */
 #endif
@@ -141,18 +138,22 @@
 #undef __SCCSID
 #define __IDSTRING_CONCAT(l,p)		__LINTED__ ## l ## _ ## p
 #define __IDSTRING_EXPAND(l,p)		__IDSTRING_CONCAT(l,p)
+#ifdef MKSH_DONT_EMIT_IDSTRING
+#define __IDSTRING(prefix, string)	/* nothing */
+#else
 #define __IDSTRING(prefix, string)				\
 	static const char __IDSTRING_EXPAND(__LINE__,prefix) []	\
 	    MKSH_A_USED = "@(""#)" #prefix ": " string
+#endif
 #define __COPYRIGHT(x)		__IDSTRING(copyright,x)
 #define __RCSID(x)		__IDSTRING(rcsid,x)
 #define __SCCSID(x)		__IDSTRING(sccsid,x)
 #endif
 
 #ifdef EXTERN
-__RCSID("$MirOS: src/bin/mksh/sh.h,v 1.405 2010/08/24 15:19:54 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/sh.h,v 1.495 2011/10/07 19:51:44 tg Exp $");
 #endif
-#define MKSH_VERSION "R39 2010/08/24"
+#define MKSH_VERSION "R40 2011/10/07"
 
 #ifndef MKSH_INCLUDES_ONLY
 
@@ -163,8 +164,8 @@
 #undef RUSAGE_SELF
 #undef RUSAGE_CHILDREN
 #define rusage mksh_rusage
-#define RUSAGE_SELF	0
-#define RUSAGE_CHILDREN	-1
+#define RUSAGE_SELF		0
+#define RUSAGE_CHILDREN		-1
 
 struct rusage {
 	struct timeval ru_utime;
@@ -181,13 +182,6 @@
 typedef void (*sig_t)(int);
 #endif
 
-#if !HAVE_STDBOOL_H
-/* kludge, but enough for mksh */
-typedef int bool;
-#define false 0
-#define true 1
-#endif
-
 #if !HAVE_CAN_INTTYPES
 #if !HAVE_CAN_UCBINTS
 typedef signed int int32_t;
@@ -264,6 +258,9 @@
 #ifndef S_ISSOCK
 #define S_ISSOCK(m)	((m & 0170000) == 0140000)
 #endif
+#if !defined(S_ISCDF) && defined(S_CDF)
+#define S_ISCDF(m)	(S_ISDIR(m) && ((m) & S_CDF))
+#endif
 #ifndef DEFFILEMODE
 #define DEFFILEMODE	(S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)
 #endif
@@ -301,11 +298,6 @@
 extern int revoke(const char *);
 #endif
 
-#if !HAVE_SETMODE
-mode_t getmode(const void *, mode_t);
-void *setmode(const char *);
-#endif
-
 #ifdef __ultrix
 /* XXX imake style */
 int strcasecmp(const char *, const char *);
@@ -341,23 +333,35 @@
 
 /* some useful #defines */
 #ifdef EXTERN
-# define I__(i) = i
+# define E_INIT(i) = i
 #else
-# define I__(i)
+# define E_INIT(i)
 # define EXTERN extern
 # define EXTERN_DEFINED
 #endif
 
+/* define bit in flag */
+#define BIT(i)		(1 << (i))
 #define NELEM(a)	(sizeof(a) / sizeof((a)[0]))
-#define BIT(i)		(1 << (i))	/* define bit in flag */
-
-/* Table flag type - needs > 16 and < 32 bits */
-typedef int32_t Tflag;
 
 /* arithmetics types */
 typedef int32_t mksh_ari_t;
 typedef uint32_t mksh_uari_t;
 
+/* boolean type (no <stdbool.h> deliberately) */
+typedef unsigned char mksh_bool;
+#undef bool
+/* false MUST equal 0 */
+#undef false
+#undef true
+/* access macros for boolean type */
+#define bool		mksh_bool
+/* values must have identity mapping between mksh_bool and short */
+#define false		0
+#define true		1
+/* make any-type into bool or short */
+#define tobool(cond)	((cond) ? true : false)
+
 /* these shall be smaller than 100 */
 #ifdef MKSH_CONSERVATIVE_FDS
 #define NUFILE		32	/* Number of user-accessible files */
@@ -367,7 +371,8 @@
 #define FDBASE		24	/* First file usable by Shell */
 #endif
 
-/* Make MAGIC a char that might be printed to make bugs more obvious, but
+/*
+ * Make MAGIC a char that might be printed to make bugs more obvious, but
  * not a char that is used often. Also, can't use the high bit as it causes
  * portability problems (calling strchr(x, 0x80|'x') is error prone).
  */
@@ -378,11 +383,11 @@
 #define LINE		4096	/* input line size */
 
 EXTERN const char *safe_prompt; /* safe prompt if PS1 substitution fails */
-EXTERN const char initvsn[] I__("KSH_VERSION=@(#)MIRBSD KSH " MKSH_VERSION);
+EXTERN const char initvsn[] E_INIT("KSH_VERSION=@(#)MIRBSD KSH " MKSH_VERSION);
 #define KSH_VERSION	(initvsn + /* "KSH_VERSION=@(#)" */ 16)
 
-EXTERN const char digits_uc[] I__("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
-EXTERN const char digits_lc[] I__("0123456789abcdefghijklmnopqrstuvwxyz");
+EXTERN const char digits_uc[] E_INIT("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+EXTERN const char digits_lc[] E_INIT("0123456789abcdefghijklmnopqrstuvwxyz");
 
 /*
  * Evil hack for const correctness due to API brokenness
@@ -471,7 +476,7 @@
 #endif
 
 #if HAVE_STRCASESTR
-#define stristr(b,l)	((const char *)strcasestr((b), (l)))
+#define stristr(b,l)		((const char *)strcasestr((b), (l)))
 #endif
 
 #ifdef MKSH_SMALL
@@ -490,10 +495,43 @@
 #define MKSH_S_NOVI		0
 #endif
 
+#if defined(MKSH_NOPROSPECTOFWORK) && !defined(MKSH_UNEMPLOYED)
+#define MKSH_UNEMPLOYED		1
+#endif
+
 /*
  * simple grouping allocator
  */
 
+
+/* 0. OS API: where to get memory from and how to free it (grouped) */
+
+/* malloc(3)/realloc(3) -> free(3) for use by the memory allocator */
+#define malloc_osi(sz)		malloc(sz)
+#define realloc_osi(p,sz)	realloc((p), (sz))
+#define free_osimalloc(p)	free(p)
+
+/* malloc(3)/realloc(3) -> free(3) for use by mksh code */
+#define malloc_osfunc(sz)	malloc(sz)
+#define realloc_osfunc(p,sz)	realloc((p), (sz))
+#define free_osfunc(p)		free(p)
+
+#if HAVE_MKNOD
+/* setmode(3) -> free(3) */
+#define free_ossetmode(p)	free(p)
+#endif
+
+#if !HAVE_MKSTEMP
+/* tempnam(3) -> free(3) */
+#define free_ostempnam(p)	free(p)
+#endif
+
+#ifdef NO_PATH_MAX
+/* GNU libc: get_current_dir_name(3) -> free(3) */
+#define free_gnu_gcdn(p)	free(p)
+#endif
+
+
 /* 1. internal structure */
 struct lalloc {
 	struct lalloc *next;
@@ -520,14 +558,14 @@
 	FNFLAGS		/* (place holder: how many flags are there) */
 };
 
-#define Flag(f)	(kshstate_v.shell_flags_[(int)(f)])
+#define Flag(f)	(shell_flags[(int)(f)])
 #define UTFMODE	Flag(FUNICODE)
 
 /*
  * parsing & execution environment
  */
 extern struct env {
-	ALLOC_ITEM __alloc_i;	/* internal, do not touch */
+	ALLOC_ITEM alloc_INT;	/* internal, do not touch */
 	Area area;		/* temporary allocation area */
 	struct env *oenv;	/* link to previous environment */
 	struct block *loc;	/* local variables and functions */
@@ -570,42 +608,32 @@
 #define LSHELL	8	/* return to interactive shell() */
 #define LAEXPR	9	/* error in arithmetic expression */
 
-/*
- * some kind of global shell state, for change_random() mostly
- */
+/* sort of shell global state */
+EXTERN pid_t procpid;		/* PID of executing process */
+EXTERN int exstat;		/* exit status */
+EXTERN int subst_exstat;	/* exit status of last $(..)/`..` */
+EXTERN struct tbl *vp_pipest;	/* global PIPESTATUS array */
+EXTERN short trap_exstat;	/* exit status before running a trap */
+EXTERN uint8_t trap_nested;	/* running nested traps */
+EXTERN uint8_t shell_flags[FNFLAGS];
+EXTERN const char *kshname;	/* $0 */
+EXTERN struct {
+	uid_t kshuid_v;		/* real UID of shell */
+	uid_t ksheuid_v;	/* effective UID of shell */
+	gid_t kshgid_v;		/* real GID of shell */
+	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 */
+	pid_t kshpid_v;		/* $$, shell PID */
+} rndsetupstate;
 
-EXTERN struct mksh_kshstate_v {
-	/* for change_random */
-	struct timeval cr_tv;	/* timestamp */
-	const void *cr_dp;	/* argument address */
-	size_t cr_dsz;		/* argument length */
-	uint32_t lcg_state_;	/* previous LCG state */
-	/* global state */
-	pid_t procpid_;		/* PID of executing process */
-	int exstat_;		/* exit status */
-	int subst_exstat_;	/* exit status of last $(..)/`..` */
-	struct env env_;	/* top-level parsing & execution env. */
-	uint8_t shell_flags_[FNFLAGS];
-} kshstate_v;
-EXTERN struct mksh_kshstate_f {
-	const char *kshname_;	/* $0 */
-	pid_t kshpid_;		/* $$, shell PID */
-	pid_t kshpgrp_;		/* process group of shell */
-	uid_t ksheuid_;		/* effective UID of shell */
-	pid_t kshppid_;		/* PID of parent of shell */
-	uint32_t h;		/* some kind of hash */
-} kshstate_f;
-#define kshname		kshstate_f.kshname_
-#define kshpid		kshstate_f.kshpid_
-#define procpid		kshstate_v.procpid_
-#define kshpgrp		kshstate_f.kshpgrp_
-#define ksheuid		kshstate_f.ksheuid_
-#define kshppid		kshstate_f.kshppid_
-#define exstat		kshstate_v.exstat_
-#define subst_exstat	kshstate_v.subst_exstat_
-
-/* evil hack: return hash(kshstate_f concat (kshstate_f'.h:=hash(arg))) */
-uint32_t evilhash(const char *);
+#define kshpid		rndsetupstate.kshpid_v
+#define kshpgrp		rndsetupstate.kshpgrp_v
+#define kshuid		rndsetupstate.kshuid_v
+#define ksheuid		rndsetupstate.ksheuid_v
+#define kshgid		rndsetupstate.kshgid_v
+#define kshegid		rndsetupstate.kshegid_v
+#define kshppid		rndsetupstate.kshppid_v
 
 
 /* option processing */
@@ -623,16 +651,34 @@
 };
 extern const struct shoption options[];
 
-/* null value for variable; comparision pointer for unset */
-EXTERN char null[] I__("");
+/* null value for variable; comparison pointer for unset */
+EXTERN char null[] E_INIT("");
 /* helpers for string pooling */
-#define T_synerr "syntax error"
-EXTERN const char r_fc_e_[] I__("r=fc -e -");
-#define fc_e_		(r_fc_e_ + 2)		/* "fc -e -" */
-#define fc_e_n		7			/* strlen(fc_e_) */
-EXTERN const char T_local_typeset[] I__("local=typeset");
-#define T__typeset	(T_local_typeset + 5)	/* "=typeset" */
-#define T_typeset	(T_local_typeset + 6)	/* "typeset" */
+EXTERN const char Tintovfl[] E_INIT("integer overflow %zu %c %zu prevented");
+EXTERN const char Toomem[] E_INIT("can't allocate %lu data bytes");
+#if defined(__GNUC__)
+/* trust this to have string pooling; -Wformat bitches otherwise */
+#define Tsynerr		"syntax error"
+#else
+EXTERN const char Tsynerr[] E_INIT("syntax error");
+#endif
+EXTERN const char Tselect[] E_INIT("select");
+EXTERN const char Tr_fc_e_dash[] E_INIT("r=fc -e -");
+#define Tfc_e_dash	(Tr_fc_e_dash + 2)	/* "fc -e -" */
+#define Zfc_e_dash	7			/* strlen(Tfc_e_dash) */
+EXTERN const char Tlocal_typeset[] E_INIT("local=typeset");
+#define T_typeset	(Tlocal_typeset + 5)	/* "=typeset" */
+#define Ttypeset	(Tlocal_typeset + 6)	/* "typeset" */
+EXTERN const char Tpalias[] E_INIT("+alias");
+#define Talias		(Tpalias + 1)		/* "alias" */
+EXTERN const char Tpunalias[] E_INIT("+unalias");
+#define Tunalias	(Tpunalias + 1)		/* "unalias" */
+EXTERN const char Tsgset[] E_INIT("*=set");
+#define Tset		(Tsgset + 2)		/* "set" */
+EXTERN const char Tgbuiltin[] E_INIT("=builtin");
+#define Tbuiltin	(Tgbuiltin + 1)		/* "builtin" */
+EXTERN const char T_function[] E_INIT(" function");
+#define Tfunction	(T_function + 1)	/* "function" */
 
 enum temp_type {
 	TT_HEREDOC_EXP,	/* expanded heredoc */
@@ -655,7 +701,7 @@
 #define shl_spare	(&shf_iob[0])	/* for c_read()/c_print() */
 #define shl_stdout	(&shf_iob[1])
 #define shl_out		(&shf_iob[2])
-EXTERN int shl_stdout_ok;
+EXTERN bool shl_stdout_ok;
 
 /*
  * trap handlers
@@ -693,17 +739,17 @@
 #define SS_USER		BIT(4)	/* user is doing the set (ie, trap command) */
 #define SS_SHTRAP	BIT(5)	/* trap for internal use (ALRM, CHLD, WINCH) */
 
-#define SIGEXIT_	0	/* for trap EXIT */
-#define SIGERR_		NSIG	/* for trap ERR */
+#define ksh_SIGEXIT	0	/* for trap EXIT */
+#define ksh_SIGERR	NSIG	/* for trap ERR */
 
 EXTERN volatile sig_atomic_t trap;	/* traps pending? */
 EXTERN volatile sig_atomic_t intrsig;	/* pending trap interrupts command */
-EXTERN volatile sig_atomic_t fatal_trap;/* received a fatal signal */
+EXTERN volatile sig_atomic_t fatal_trap; /* received a fatal signal */
 extern	Trap	sigtraps[NSIG+1];
 
 /* got_winch = 1 when we need to re-adjust the window size */
 #ifdef SIGWINCH
-EXTERN volatile sig_atomic_t got_winch I__(1);
+EXTERN volatile sig_atomic_t got_winch E_INIT(1);
 #else
 #define got_winch	true
 #endif
@@ -718,7 +764,7 @@
 	TMOUT_LEAVING		/* have timed out */
 };
 EXTERN unsigned int ksh_tmout;
-EXTERN enum tmout_enum ksh_tmout_state I__(TMOUT_EXECUTING);
+EXTERN enum tmout_enum ksh_tmout_state E_INIT(TMOUT_EXECUTING);
 
 /* For "You have stopped jobs" message */
 EXTERN int really_exit;
@@ -738,13 +784,13 @@
 
 extern unsigned char chtypes[];
 
-#define ctype(c, t)	!!( ((t) == C_SUBOP2) ?				\
+#define ctype(c, t)	tobool( ((t) == C_SUBOP2) ?			\
 			    (((c) == '#' || (c) == '%') ? 1 : 0) :	\
-			    (chtypes[(unsigned char)(c)]&(t)) )
+			    (chtypes[(unsigned char)(c)] & (t)) )
 #define ksh_isalphx(c)	ctype((c), C_ALPHA)
 #define ksh_isalnux(c)	ctype((c), C_ALPHA | C_DIGIT)
 
-EXTERN int ifs0 I__(' ');	/* for "$*" */
+EXTERN int ifs0 E_INIT(' ');	/* for "$*" */
 
 /* Argument parsing for built-in commands and getopts command */
 
@@ -758,14 +804,18 @@
 #define GI_PLUS		BIT(1)	/* an option started with +... */
 #define GI_MINUSMINUS	BIT(2)	/* arguments were ended with -- */
 
+/* in case some OS defines these */
+#undef optarg
+#undef optind
+
 typedef struct {
-	const char	*optarg;
-	int		optind;
-	int		uoptind;/* what user sees in $OPTIND */
-	int		flags;	/* see GF_* */
-	int		info;	/* see GI_* */
-	unsigned int	p;	/* 0 or index into argv[optind - 1] */
-	char		buf[2];	/* for bad option OPTARG value */
+	const char *optarg;
+	int optind;
+	int uoptind;		/* what user sees in $OPTIND */
+	int flags;		/* see GF_* */
+	int info;		/* see GI_* */
+	unsigned int p;		/* 0 or index into argv[optind - 1] */
+	char buf[2];		/* for bad option OPTARG value */
 } Getopt;
 
 EXTERN Getopt builtin_opt;	/* for shell builtin commands */
@@ -773,7 +823,9 @@
 
 /* This for co-processes */
 
-typedef int32_t Coproc_id; /* something that won't (realisticly) wrap */
+/* something that won't (realisticly) wrap */
+typedef int32_t Coproc_id;
+
 struct coproc {
 	void *job;	/* 0 or job of co-process using input pipe */
 	int read;	/* pipe from co-process's stdout */
@@ -784,27 +836,31 @@
 };
 EXTERN struct coproc coproc;
 
-/* Used in jobs.c and by coprocess stuff in exec.c */
+#ifndef MKSH_NOPROSPECTOFWORK
+/* used in jobs.c and by coprocess stuff in exec.c and select() calls */
 EXTERN sigset_t		sm_default, sm_sigchld;
+#endif
 
 /* name of called builtin function (used by error functions) */
 EXTERN const char *builtin_argv0;
-EXTERN Tflag builtin_flag;	/* flags of called builtin (SPEC_BI, etc.) */
+/* flags of called builtin (SPEC_BI, etc.) */
+EXTERN uint32_t builtin_flag;
 
-/* current working directory, and size of memory allocated for same */
+/* current working directory */
 EXTERN char	*current_wd;
-EXTERN size_t	current_wd_size;
 
-/* Minimum required space to work with on a line - if the prompt leaves less
- * space than this on a line, the prompt is truncated.
+/*
+ * Minimum required space to work with on a line - if the prompt leaves
+ * less space than this on a line, the prompt is truncated.
  */
 #define MIN_EDIT_SPACE	7
-/* Minimum allowed value for x_cols: 2 for prompt, 3 for " < " at end of line
+/*
+ * Minimum allowed value for x_cols: 2 for prompt, 3 for " < " at end of line
  */
 #define MIN_COLS	(2 + MIN_EDIT_SPACE + 3)
 #define MIN_LINS	3
-EXTERN mksh_ari_t x_cols I__(80);	/* tty columns */
-EXTERN mksh_ari_t x_lins I__(-1);	/* tty lines */
+EXTERN mksh_ari_t x_cols E_INIT(80);	/* tty columns */
+EXTERN mksh_ari_t x_lins E_INIT(-1);	/* tty lines */
 
 /* These to avoid bracket matching problems */
 #define OPAREN	'('
@@ -814,8 +870,19 @@
 #define OBRACE	'{'
 #define CBRACE	'}'
 
+
 /* Determine the location of the system (common) profile */
-#define KSH_SYSTEM_PROFILE "/etc/profile"
+
+/* This is deliberately not configurable via CPPFLAGS */
+#if defined(ANDROID)
+#define MKSH_ETC_LOCATION	"/system/etc"
+#else
+#define MKSH_ETC_LOCATION	"/etc"
+#endif
+
+#define MKSH_SYSTEM_PROFILE	MKSH_ETC_LOCATION "/profile"
+#define MKSH_SUID_PROFILE	MKSH_ETC_LOCATION "/suid_profile"
+
 
 /* Used by v_evaluate() and setstr() to control action when error occurs */
 #define KSH_UNWIND_ERROR	0	/* unwind the stack (longjmp) */
@@ -825,24 +892,19 @@
  * Shell file I/O routines
  */
 
-#define SHF_BSIZE	512
+#define SHF_BSIZE		512
 
-#define shf_fileno(shf)	((shf)->fd)
+#define shf_fileno(shf)		((shf)->fd)
 #define shf_setfileno(shf,nfd)	((shf)->fd = (nfd))
-#ifdef MKSH_SMALL
-int shf_getc(struct shf *);
-int shf_putc(int, struct shf *);
-#else
-#define shf_getc(shf)		((shf)->rnleft > 0 ? \
+#define shf_getc_(shf)		((shf)->rnleft > 0 ? \
 				    (shf)->rnleft--, *(shf)->rp++ : \
 				    shf_getchar(shf))
-#define shf_putc(c, shf)	((shf)->wnleft == 0 ? \
+#define shf_putc_(c, shf)	((shf)->wnleft == 0 ? \
 				    shf_putchar((c), (shf)) : \
 				    ((shf)->wnleft--, *(shf)->wp++ = (c)))
-#endif
 #define shf_eof(shf)		((shf)->flags & SHF_EOF)
 #define shf_error(shf)		((shf)->flags & SHF_ERROR)
-#define shf_errno(shf)		((shf)->errno_)
+#define shf_errno(shf)		((shf)->errnosv)
 #define shf_clearerr(shf)	((shf)->flags &= ~(SHF_EOF | SHF_ERROR))
 
 /* Flags passed to shf_*open() */
@@ -872,14 +934,14 @@
 	unsigned char *rp;	/* read: current position in buffer */
 	unsigned char *wp;	/* write: current position in buffer */
 	unsigned char *buf;	/* buffer */
+	ssize_t bsize;		/* actual size of buf */
+	ssize_t rbsize;		/* size of buffer (1 if SHF_UNBUF) */
+	ssize_t rnleft;		/* read: how much data left in buffer */
+	ssize_t wbsize;		/* size of buffer (0 if SHF_UNBUF) */
+	ssize_t wnleft;		/* write: how much space left in buffer */
 	int flags;		/* see SHF_* */
-	int rbsize;		/* size of buffer (1 if SHF_UNBUF) */
-	int rnleft;		/* read: how much data left in buffer */
-	int wbsize;		/* size of buffer (0 if SHF_UNBUF) */
-	int wnleft;		/* write: how much space left in buffer */
 	int fd;			/* file descriptor */
-	int errno_;		/* saved value of errno after error */
-	int bsize;		/* actual size of buf */
+	int errnosv;		/* saved value of errno after error */
 };
 
 extern struct shf shf_iob[];
@@ -887,34 +949,44 @@
 struct table {
 	Area *areap;		/* area to allocate entries */
 	struct tbl **tbls;	/* hashed table items */
-	short size, nfree;	/* hash size (always 2^^n), free entries */
+	size_t nfree;		/* free table entries */
+	uint8_t tshift;		/* table size (2^tshift) */
 };
 
-struct tbl {			/* table item */
-	Area *areap;		/* area to allocate from */
+/* table item */
+struct tbl {
+	/* Area to allocate from */
+	Area *areap;
+	/* value */
 	union {
-		char *s;		/* string */
-		mksh_ari_t i;		/* integer */
-		mksh_uari_t u;		/* unsigned integer */
-		int (*f)(const char **);/* int function */
-		struct op *t;		/* "function" tree */
-	} val;			/* value */
+		char *s;			/* string */
+		mksh_ari_t i;			/* integer */
+		mksh_uari_t u;			/* unsigned integer */
+		int (*f)(const char **);	/* built-in command */
+		struct op *t;			/* "function" tree */
+	} val;
 	union {
 		struct tbl *array;	/* array values */
 		const char *fpath;	/* temporary path to undef function */
 	} u;
 	union {
-		int field;	/* field with for -L/-R/-Z */
-		int errno_;	/* CEXEC/CTALIAS */
+		int field;		/* field with for -L/-R/-Z */
+		int errnov;		/* CEXEC/CTALIAS */
 	} u2;
-	int type;		/* command type (see below), base (if INTEGER),
-				 * or offset from val.s of value (if EXPORT) */
-	Tflag flag;		/* flags */
 	union {
 		uint32_t hval;		/* hash(name) */
 		uint32_t index;		/* index for an array */
 	} ua;
-	char name[4];		/* name -- variable length */
+	/*
+	 * command type (see below), base (if INTEGER),
+	 * offset from val.s of value (if EXPORT)
+	 */
+	int type;
+	/* flags (see below) */
+	uint32_t flag;
+
+	/* actually longer: name (variable length) */
+	char name[4];
 };
 
 /* common flag bits */
@@ -936,7 +1008,7 @@
 #define LCASEV		BIT(17)	/* convert to lower case */
 #define UCASEV_AL	BIT(18) /* convert to upper case / autoload function */
 #define INT_U		BIT(19)	/* unsigned integer */
-#define INT_L		BIT(20)	/* long integer (no-op) */
+#define INT_L		BIT(20)	/* long integer (no-op but used as magic) */
 #define IMPORT		BIT(21)	/* flag to typeset(): no arrays, must have = */
 #define LOCAL_COPY	BIT(22)	/* with LOCAL - copy attrs from existing var */
 #define EXPRINEVAL	BIT(23)	/* contents currently being evaluated */
@@ -950,8 +1022,10 @@
 #define FKSH		BIT(11)	/* function defined with function x (vs x()) */
 #define SPEC_BI		BIT(12)	/* a POSIX special builtin */
 #define REG_BI		BIT(13)	/* a POSIX regular builtin */
-/* Attributes that can be set by the user (used to decide if an unset param
- * should be repoted by set/typeset). Does not include ARRAY or LOCAL.
+/*
+ * Attributes that can be set by the user (used to decide if an unset
+ * param should be repoted by set/typeset). Does not include ARRAY or
+ * LOCAL.
  */
 #define USERATTRIB	(EXPORT|INTEGER|RDONLY|LJUST|RJUST|ZEROFIL|\
 			    LCASEV|UCASEV_AL|INT_U|INT_L)
@@ -959,6 +1033,12 @@
 #define arrayindex(vp)	((unsigned long)((vp)->flag & AINDEX ? \
 			    (vp)->ua.index : 0))
 
+EXTERN enum {
+	SRF_NOP,
+	SRF_ENABLE,
+	SRF_DISABLE
+} set_refflag E_INIT(SRF_NOP);
+
 /* command types */
 #define CNONE		0	/* undefined */
 #define CSHELL		1	/* built-in */
@@ -981,13 +1061,13 @@
 #define AF_ARGV_ALLOC	0x1	/* argv[] array allocated */
 #define AF_ARGS_ALLOCED	0x2	/* argument strings allocated */
 #define AI_ARGV(a, i)	((i) == 0 ? (a).argv[0] : (a).argv[(i) - (a).skip])
-#define AI_ARGC(a)	((a).argc_ - (a).skip)
+#define AI_ARGC(a)	((a).ai_argc - (a).skip)
 
 /* Argument info. Used for $#, $* for shell, functions, includes, etc. */
 struct arg_info {
 	const char **argv;
 	int flags;	/* AF_* */
-	int argc_;
+	int ai_argc;
 	int skip;	/* first arg is argv[0], second is argv[1 + skip] */
 };
 
@@ -1063,9 +1143,14 @@
 					 */
 	int lineno;			/* TCOM/TFUNC: LINENO for this */
 	short type;			/* operation type, see below */
-	union { /* WARNING: newtp(), tcopy() use evalflags = 0 to clear union */
-		short evalflags;	/* TCOM: arg expansion eval() flags */
-		short ksh_func;		/* TFUNC: function x (vs x()) */
+	/* WARNING: newtp(), tcopy() use evalflags = 0 to clear union */
+	union {
+		/* TCOM: arg expansion eval() flags */
+		short evalflags;
+		/* TFUNC: function x (vs x()) */
+		short ksh_func;
+		/* TPAT: termination character */
+		char charflag;
 	} u;
 };
 
@@ -1115,27 +1200,29 @@
  * IO redirection
  */
 struct ioword {
-	int	unit;	/* unit affected */
-	int	flag;	/* action (below) */
-	char	*name;	/* file name (unused if heredoc) */
-	char	*delim;	/* delimiter for <<,<<- */
-	char	*heredoc;/* content of heredoc */
+	int	unit;		/* unit affected */
+	int	flag;		/* action (below) */
+	char	*name;		/* file name (unused if heredoc) */
+	char	*delim;		/* delimiter for <<,<<- */
+	char	*heredoc;	/* content of heredoc */
 };
 
 /* ioword.flag - type of redirection */
-#define IOTYPE	0xF	/* type: bits 0:3 */
-#define IOREAD	0x1	/* < */
-#define IOWRITE	0x2	/* > */
-#define IORDWR	0x3	/* <>: todo */
-#define IOHERE	0x4	/* << (here file) */
-#define IOCAT	0x5	/* >> */
-#define IODUP	0x6	/* <&/>& */
-#define IOEVAL	BIT(4)	/* expand in << */
-#define IOSKIP	BIT(5)	/* <<-, skip ^\t* */
-#define IOCLOB	BIT(6)	/* >|, override -o noclobber */
-#define IORDUP	BIT(7)	/* x<&y (as opposed to x>&y) */
-#define IONAMEXP BIT(8)	/* name has been expanded */
-#define IOBASH	BIT(9)	/* &> etc. */
+#define IOTYPE		0xF	/* type: bits 0:3 */
+#define IOREAD		0x1	/* < */
+#define IOWRITE		0x2	/* > */
+#define IORDWR		0x3	/* <>: todo */
+#define IOHERE		0x4	/* << (here file) */
+#define IOCAT		0x5	/* >> */
+#define IODUP		0x6	/* <&/>& */
+#define IOEVAL		BIT(4)	/* expand in << */
+#define IOSKIP		BIT(5)	/* <<-, skip ^\t* */
+#define IOCLOB		BIT(6)	/* >|, override -o noclobber */
+#define IORDUP		BIT(7)	/* x<&y (as opposed to x>&y) */
+#define IONAMEXP	BIT(8)	/* name has been expanded */
+#define IOBASH		BIT(9)	/* &> etc. */
+#define IOHERESTR	BIT(10)	/* <<< (here string) */
+#define IONDELIM	BIT(11)	/* null delimiter (<<) */
 
 /* execute/exchild flags */
 #define XEXEC	BIT(0)		/* execute without forking */
@@ -1150,6 +1237,7 @@
 #define XERROK	BIT(8)		/* non-zero exit ok (for set -e) */
 #define XCOPROC BIT(9)		/* starting a co-process */
 #define XTIME	BIT(10)		/* timing TCOM command */
+#define XPIPEST	BIT(11)		/* want PIPESTATUS */
 
 /*
  * flags to control expansion of words (assumed by t->evalflags to fit
@@ -1161,9 +1249,9 @@
 #define DOTILDE	BIT(3)		/* normal ~ expansion (first char) */
 #define DONTRUNCOMMAND BIT(4)	/* do not run $(command) things */
 #define DOASNTILDE BIT(5)	/* assignment ~ expansion (after =, :) */
-#define DOBRACE_ BIT(6)		/* used by expand(): do brace expansion */
-#define DOMAGIC_ BIT(7)		/* used by expand(): string contains MAGIC */
-#define DOTEMP_	BIT(8)		/* ditto : in word part of ${..[%#=?]..} */
+#define DOBRACE BIT(6)		/* used by expand(): do brace expansion */
+#define DOMAGIC BIT(7)		/* used by expand(): string contains MAGIC */
+#define DOTEMP	BIT(8)		/* dito: in word part of ${..[%#=?]..} */
 #define DOVACHECK BIT(9)	/* var assign check (for typeset, set, etc) */
 #define DOMARKDIRS BIT(10)	/* force markdirs behaviour */
 
@@ -1180,7 +1268,7 @@
 #define DB_BE	4	/* an inserted -BE */
 #define DB_PAT	5	/* a pattern argument */
 
-#define X_EXTRA	8	/* this many extra bytes in X string */
+#define X_EXTRA	20	/* this many extra bytes in X string */
 
 typedef struct XString {
 	char *end, *beg;	/* end, begin of string */
@@ -1207,7 +1295,7 @@
 
 /* check if there are at least n bytes left */
 #define XcheckN(xs, xp, n) do {					\
-	int more = ((xp) + (n)) - (xs).end;			\
+	ssize_t more = ((xp) + (n)) - (xs).end;			\
 	if (more > 0)						\
 		(xp) = Xcheck_grow_(&(xs), (xp), more);		\
 } while (/* CONSTCOND */ 0)
@@ -1230,7 +1318,7 @@
 #define Xsavepos(xs, xp)	((xp) - (xs).beg)
 #define Xrestpos(xs, xp, n)	((xs).beg + (n))
 
-char *Xcheck_grow_(XString *, const char *, unsigned int);
+char *Xcheck_grow_(XString *, const char *, size_t);
 
 /*
  * expandable vector of generic pointers
@@ -1242,17 +1330,17 @@
 } XPtrV;
 
 #define XPinit(x, n) do {					\
-	void **vp__;						\
-	vp__ = alloc((n) * sizeof(void *), ATEMP);		\
-	(x).cur = (x).beg = vp__;				\
-	(x).end = vp__ + (n);					\
+	void **XPinit_vp;					\
+	XPinit_vp = alloc2((n), sizeof(void *), ATEMP);		\
+	(x).cur = (x).beg = XPinit_vp;				\
+	(x).end = XPinit_vp + (n);				\
 } while (/* CONSTCOND */ 0)
 
 #define XPput(x, p) do {					\
 	if ((x).cur >= (x).end) {				\
 		size_t n = XPsize(x);				\
-		(x).beg = aresize((x).beg,			\
-		    n * 2 * sizeof(void *), ATEMP);		\
+		(x).beg = aresize2((x).beg,			\
+		    n, 2 * sizeof(void *), ATEMP);		\
 		(x).cur = (x).beg + n;				\
 		(x).end = (x).cur + n;				\
 	}							\
@@ -1261,7 +1349,7 @@
 
 #define XPptrv(x)	((x).beg)
 #define XPsize(x)	((x).cur - (x).beg)
-#define XPclose(x)	aresize((x).beg, XPsize(x) * sizeof(void *), ATEMP)
+#define XPclose(x)	aresize2((x).beg, XPsize(x), sizeof(void *), ATEMP)
 #define XPfree(x)	afree((x).beg, ATEMP)
 
 #define IDENT	64
@@ -1304,8 +1392,7 @@
 #define SF_ALIAS	BIT(1)	/* faking space at end of alias */
 #define SF_ALIASEND	BIT(2)	/* faking space at end of alias */
 #define SF_TTY		BIT(3)	/* type == SSTDIN & it is a tty */
-#define SF_FIRST	BIT(4)	/* initial state (to ignore UTF-8 BOM) */
-#define SF_HASALIAS	BIT(5)	/* u.tblp valid (SALIAS, SEOF) */
+#define SF_HASALIAS	BIT(4)	/* u.tblp valid (SALIAS, SEOF) */
 
 typedef union {
 	int i;
@@ -1341,6 +1428,8 @@
 #define BANG		278	/* ! */
 #define DBRACKET	279	/* [[ .. ]] */
 #define COPROC		280	/* |& */
+#define BRKEV		281	/* ;| */
+#define BRKFT		282	/* ;& */
 #define YYERRCODE	300
 
 /* flags to yylex */
@@ -1356,18 +1445,17 @@
 #define HEREDELIM	BIT(9)	/* parsing <<,<<- delimiter */
 #define LQCHAR		BIT(10)	/* source string contains QCHAR */
 #define HEREDOC		BIT(11)	/* parsing a here document */
-#define LETARRAY	BIT(12)	/* copy expression inside =( ) */
 
-#define HERES	10		/* max << in line */
+#define HERES		10	/* max number of << in line */
 
 #undef CTRL
 #define	CTRL(x)		((x) == '?' ? 0x7F : (x) & 0x1F)	/* ASCII */
 #define	UNCTRL(x)	((x) ^ 0x40)				/* ASCII */
 
 EXTERN Source *source;		/* yyparse/yylex source */
-EXTERN YYSTYPE	yylval;		/* result from yylex */
-EXTERN struct ioword *heres [HERES], **herep;
-EXTERN char	ident [IDENT+1];
+EXTERN YYSTYPE yylval;		/* result from yylex */
+EXTERN struct ioword *heres[HERES], **herep;
+EXTERN char ident[IDENT+1];
 
 #define HISTORYSIZE	500	/* size of saved history */
 
@@ -1378,12 +1466,75 @@
 /* user and system time of last j_waitjed job */
 EXTERN struct timeval j_usrtime, j_systime;
 
+#define notoktomul(fac1, fac2)	(((fac1) != 0) && ((fac2) != 0) && \
+				    ((SIZE_MAX / (fac1)) < (fac2)))
+#define notoktoadd(val, cnst)	((val) > (SIZE_MAX - (cnst)))
+#define checkoktoadd(val, cnst) do {					\
+	if (notoktoadd((val), (cnst)))					\
+		internal_errorf(Tintovfl, (size_t)(val),		\
+		    '+', (size_t)(cnst));				\
+} while (/* CONSTCOND */ 0)
+
+
+/* NZAT/NZAAT hashes based on Bob Jenkins' one-at-a-time hash */
+
+/* From: src/kern/include/nzat.h,v 1.2 2011/07/18 00:35:40 tg Exp $ */
+
+#define NZATInit(h) do {					\
+	(h) = 0;						\
+} while (/* CONSTCOND */ 0)
+
+#define NZATUpdateByte(h,b) do {				\
+	(h) += (uint8_t)(b);					\
+	++(h);							\
+	(h) += (h) << 10;					\
+	(h) ^= (h) >> 6;					\
+} while (/* CONSTCOND */ 0)
+
+#define NZATUpdateMem(h,p,z) do {				\
+	register const uint8_t *NZATUpdateMem_p;		\
+	register size_t NZATUpdateMem_z = (z);			\
+								\
+	NZATUpdateMem_p = (const void *)(p);			\
+	while (NZATUpdateMem_z--)				\
+		NZATUpdateByte((h), *NZATUpdateMem_p++);	\
+} while (/* CONSTCOND */ 0)
+
+#define NZATUpdateString(h,s) do {				\
+	register const char *NZATUpdateString_s;		\
+	register uint8_t NZATUpdateString_c;			\
+								\
+	NZATUpdateString_s = (const void *)(s);			\
+	while ((NZATUpdateString_c = *NZATUpdateString_s++))	\
+		NZATUpdateByte((h), NZATUpdateString_c);	\
+} while (/* CONSTCOND */ 0)
+
+/* not zero after termination */
+#define NZATFinish(h) do {					\
+	if ((h) == 0)						\
+		++(h);						\
+	else							\
+		NZAATFinish(h);					\
+} while (/* CONSTCOND */ 0)
+
+/* NULs zählen an allen Teilen */
+#define NZAATFinish(h) do {					\
+	(h) += (h) << 10;					\
+	(h) ^= (h) >> 6;					\
+	(h) += (h) << 3;					\
+	(h) ^= (h) >> 11;					\
+	(h) += (h) << 15;					\
+} while (/* CONSTCOND */ 0)
+
+
 /* lalloc.c */
 void ainit(Area *);
 void afreeall(Area *);
 /* these cannot fail and can take NULL (not for ap) */
-#define alloc(n, ap)	aresize(NULL, (n), (ap))
+#define alloc(n, ap)		aresize(NULL, (n), (ap))
+#define alloc2(m, n, ap)	aresize2(NULL, (m), (n), (ap))
 void *aresize(void *, size_t, Area *);
+void *aresize2(void *, size_t, size_t, Area *);
 void afree(void *, Area *);	/* can take NULL */
 /* edit.c */
 #ifndef MKSH_SMALL
@@ -1392,6 +1543,7 @@
 int x_bind(const char *, const char *, bool);
 #endif
 void x_init(void);
+void x_mkraw(int, struct termios *, bool);
 int x_read(char *, size_t);
 /* eval.c */
 char *substitute(const char *, int);
@@ -1406,11 +1558,10 @@
 int shcomexec(const char **);
 struct tbl *findfunc(const char *, uint32_t, bool);
 int define(const char *, struct op *);
-void builtin(const char *, int (*)(const char **));
+const char *builtin(const char *, int (*)(const char **));
 struct tbl *findcom(const char *, int);
-void flushcom(int);
-const char *search(const char *, const char *, int, int *);
-int search_access(const char *, int, int *);
+void flushcom(bool);
+const char *search_path(const char *, const char *, int, int *);
 int pr_menu(const char * const *);
 int pr_list(char * const *);
 /* expr.c */
@@ -1420,15 +1571,15 @@
 size_t utf_mbtowc(unsigned int *, const char *);
 size_t utf_wctomb(char *, unsigned int);
 int utf_widthadj(const char *, const char **);
-int utf_mbswidth(const char *);
+size_t utf_mbswidth(const char *);
 const char *utf_skipcols(const char *, int);
 size_t utf_ptradj(const char *);
 #ifndef MKSH_mirbsd_wcwidth
 int utf_wcwidth(unsigned int);
 #endif
+int ksh_access(const char *, int);
 /* funcs.c */
 int c_hash(const char **);
-int c_cd(const char **);
 int c_pwd(const char **);
 int c_print(const char **);
 #ifdef MKSH_PRINTF_BUILTIN
@@ -1448,7 +1599,6 @@
 void getopts_reset(int);
 int c_getopts(const char **);
 int c_bind(const char **);
-int c_label(const char **);
 int c_shift(const char **);
 int c_umask(const char **);
 int c_dot(const char **);
@@ -1465,13 +1615,16 @@
 int timex(struct op *, int, volatile int *);
 void timex_hook(struct op *, char ** volatile *);
 int c_exec(const char **);
-int c_builtin(const char **);
+/* dummy function (just need pointer value), special case in comexec() */
+#define c_builtin shcomexec
 int c_test(const char **);
 #if HAVE_MKNOD
 int c_mknod(const char **);
 #endif
 int c_realpath(const char **);
 int c_rename(const char **);
+int c_cat(const char **);
+int c_sleep(const char **);
 /* histrap.c */
 void init_histvec(void);
 void hist_init(Source *);
@@ -1490,7 +1643,6 @@
 char **histpos(void);
 int histnum(int);
 int findhist(int, int, const char *, int);
-int findhistrel(const char *);
 char **hist_get_newest(bool);
 void inittraps(void);
 void alarm_init(void);
@@ -1500,7 +1652,7 @@
 int fatal_trap_check(void);
 int trap_pending(void);
 void runtraps(int intr);
-void runtrap(Trap *);
+void runtrap(Trap *, bool);
 void cleartraps(void);
 void restoresigs(void);
 void settrap(Trap *, const char *);
@@ -1523,7 +1675,6 @@
 int j_resume(const char *, int);
 #endif
 int j_jobs(const char *, int, int);
-int j_njobs(void);
 void j_notify(void);
 pid_t j_async(void);
 int j_stopped_running(void);
@@ -1531,7 +1682,7 @@
 int yylex(int);
 void yyerror(const char *, ...)
     MKSH_A_NORETURN
-    MKSH_A_FORMAT(printf, 1, 2);
+    MKSH_A_FORMAT(__printf__, 1, 2);
 Source *pushs(int, Area *);
 void set_prompt(int, Source *);
 void pprompt(const char *, int);
@@ -1547,25 +1698,27 @@
 void cleanup_proc_env(void);
 void errorf(const char *, ...)
     MKSH_A_NORETURN
-    MKSH_A_FORMAT(printf, 1, 2);
+    MKSH_A_FORMAT(__printf__, 1, 2);
+void errorfx(int, const char *, ...)
+    MKSH_A_NORETURN
+    MKSH_A_FORMAT(__printf__, 2, 3);
 void warningf(bool, const char *, ...)
-    MKSH_A_FORMAT(printf, 2, 3);
+    MKSH_A_FORMAT(__printf__, 2, 3);
 void bi_errorf(const char *, ...)
-    MKSH_A_FORMAT(printf, 1, 2);
+    MKSH_A_FORMAT(__printf__, 1, 2);
 #define errorfz()	errorf("\1")
+#define errorfxz(rc)	errorfx((rc), "\1")
 #define bi_errorfz()	bi_errorf("\1")
-void internal_verrorf(const char *, va_list)
-    MKSH_A_FORMAT(printf, 1, 0);
 void internal_errorf(const char *, ...)
     MKSH_A_NORETURN
-    MKSH_A_FORMAT(printf, 1, 2);
+    MKSH_A_FORMAT(__printf__, 1, 2);
 void internal_warningf(const char *, ...)
-    MKSH_A_FORMAT(printf, 1, 2);
+    MKSH_A_FORMAT(__printf__, 1, 2);
 void error_prefix(bool);
 void shellf(const char *, ...)
-    MKSH_A_FORMAT(printf, 1, 2);
+    MKSH_A_FORMAT(__printf__, 1, 2);
 void shprintf(const char *, ...)
-    MKSH_A_FORMAT(printf, 1, 2);
+    MKSH_A_FORMAT(__printf__, 1, 2);
 int can_seek(int);
 void initio(void);
 int ksh_dup2(int, int, bool);
@@ -1581,11 +1734,10 @@
 int coproc_getfd(int, const char **);
 void coproc_cleanup(int);
 struct temp *maketemp(Area *, Temp_type, struct temp **);
-#define hash(s) oaathash_full((const uint8_t *)(s))
-uint32_t oaathash_full(register const uint8_t *);
-uint32_t hashmem(const void *, size_t);
-void ktinit(struct table *, Area *, size_t);
-struct tbl *ktsearch(struct table *, const char *, uint32_t);
+void ktinit(Area *, struct table *, uint8_t);
+struct tbl *ktscan(struct table *, const char *, uint32_t, struct tbl ***);
+/* table, name (key) to search for, hash(n) */
+#define ktsearch(tp, s, h) ktscan((tp), (s), (h), NULL)
 struct tbl *ktenter(struct table *, const char *, uint32_t);
 #define ktdelete(p)	do { p->flag = 0; } while (/* CONSTCOND */ 0)
 void ktwalk(struct tstate *, struct table *);
@@ -1602,22 +1754,23 @@
 int bi_getn(const char *, int *);
 int gmatchx(const char *, const char *, bool);
 int has_globbing(const char *, const char *);
-const unsigned char *pat_scan(const unsigned char *, const unsigned char *, int);
 int xstrcmp(const void *, const void *);
 void ksh_getopt_reset(Getopt *, int);
 int ksh_getopt(const char **, Getopt *, const char *);
 void print_value_quoted(const char *);
+char *quote_value(const char *);
 void print_columns(struct shf *, int,
-    char *(*)(char *, int, int, const void *),
-    const void *, int, int, bool);
+    char *(*)(char *, size_t, int, const void *),
+    const void *, size_t, size_t, bool);
 void strip_nuls(char *, int);
-int blocking_read(int, char *, int)
-    MKSH_A_BOUNDED(buffer, 2, 3);
+ssize_t blocking_read(int, char *, size_t)
+    MKSH_A_BOUNDED(__buffer__, 2, 3);
 int reset_nonblock(int);
-char *ksh_get_wd(size_t *);
-int make_path(const char *, const char *, char **, XString *, int *);
+char *ksh_get_wd(void);
+char *do_realpath(const char *);
 void simplify_path(char *);
-void set_current_wd(char *);
+void set_current_wd(const char *);
+int c_cd(const char **);
 #ifdef MKSH_SMALL
 char *strdup_(const char *, Area *);
 char *strndup_(const char *, size_t, Area *);
@@ -1627,38 +1780,56 @@
 struct shf *shf_open(const char *, int, int, int);
 struct shf *shf_fdopen(int, int, struct shf *);
 struct shf *shf_reopen(int, int, struct shf *);
-struct shf *shf_sopen(char *, int, int, struct shf *);
+struct shf *shf_sopen(char *, ssize_t, int, struct shf *);
 int shf_close(struct shf *);
 int shf_fdclose(struct shf *);
 char *shf_sclose(struct shf *);
 int shf_flush(struct shf *);
-int shf_read(char *, int, struct shf *);
-char *shf_getse(char *, int, struct shf *);
+ssize_t shf_read(char *, ssize_t, struct shf *);
+char *shf_getse(char *, ssize_t, struct shf *);
 int shf_getchar(struct shf *s);
 int shf_ungetc(int, struct shf *);
+#ifdef MKSH_SMALL
+int shf_getc(struct shf *);
+int shf_putc(int, struct shf *);
+#else
+#define shf_getc shf_getc_
+#define shf_putc shf_putc_
+#endif
 int shf_putchar(int, struct shf *);
-int shf_puts(const char *, struct shf *);
-int shf_write(const char *, int, struct shf *);
-int shf_fprintf(struct shf *, const char *, ...)
-    MKSH_A_FORMAT(printf, 2, 3);
-int shf_snprintf(char *, int, const char *, ...)
-    MKSH_A_FORMAT(printf, 3, 4)
-    MKSH_A_BOUNDED(string, 1, 2);
+ssize_t shf_puts(const char *, struct shf *);
+ssize_t shf_write(const char *, ssize_t, struct shf *);
+ssize_t shf_fprintf(struct shf *, const char *, ...)
+    MKSH_A_FORMAT(__printf__, 2, 3);
+ssize_t shf_snprintf(char *, ssize_t, const char *, ...)
+    MKSH_A_FORMAT(__printf__, 3, 4)
+    MKSH_A_BOUNDED(__string__, 1, 2);
 char *shf_smprintf(const char *, ...)
-    MKSH_A_FORMAT(printf, 1, 2);
-int shf_vfprintf(struct shf *, const char *, va_list)
-    MKSH_A_FORMAT(printf, 2, 0);
+    MKSH_A_FORMAT(__printf__, 1, 2);
+ssize_t shf_vfprintf(struct shf *, const char *, va_list)
+    MKSH_A_FORMAT(__printf__, 2, 0);
 /* syn.c */
 void initkeywords(void);
-struct op *compile(Source *);
+struct op *compile(Source *, bool);
+bool parse_usec(const char *, struct timeval *);
+char *yyrecursive(void);
 /* tree.c */
-int fptreef(struct shf *, int, const char *, ...);
-char *snptreef(char *, int, const char *, ...);
+void fptreef(struct shf *, int, const char *, ...);
+char *snptreef(char *, ssize_t, const char *, ...);
 struct op *tcopy(struct op *, Area *);
 char *wdcopy(const char *, Area *);
 const char *wdscan(const char *, int);
-char *wdstrip(const char *, bool, bool);
+#define WDS_TPUTS	BIT(0)		/* tputS (dumpwdvar) mode */
+#define WDS_KEEPQ	BIT(1)		/* keep quote characters */
+#define WDS_MAGIC	BIT(2)		/* make MAGIC */
+char *wdstrip(const char *, int);
 void tfree(struct op *, Area *);
+void dumpchar(struct shf *, int);
+void dumptree(struct shf *, struct op *);
+void dumpwdvar(struct shf *, const char *);
+void vistree(char *, size_t, struct op *)
+    MKSH_A_BOUNDED(__string__, 1, 2);
+void fpFUNCTf(struct shf *, int, bool, const char *, struct op *);
 /* var.c */
 void newblock(void);
 void popblock(void);
@@ -1669,22 +1840,26 @@
 int setstr(struct tbl *, const char *, int);
 struct tbl *setint_v(struct tbl *, struct tbl *, bool);
 void setint(struct tbl *, mksh_ari_t);
-struct tbl *typeset(const char *, Tflag, Tflag, int, int)
-    MKSH_A_NONNULL((nonnull (1)));
+void setint_n(struct tbl *, mksh_ari_t);
+struct tbl *typeset(const char *, uint32_t, uint32_t, int, int)
+    MKSH_A_NONNULL((__nonnull__ (1)));
 void unset(struct tbl *, int);
 const char *skip_varname(const char *, int);
-const char *skip_wdvarname(const char *, int);
-int is_wdvarname(const char *, int);
+const char *skip_wdvarname(const char *, bool);
+int is_wdvarname(const char *, bool);
 int is_wdvarassign(const char *);
+struct tbl *arraysearch(struct tbl *, uint32_t);
 char **makenv(void);
-void change_random(const void *, size_t);
 void change_winsz(void);
-int array_ref_len(const char *);
+size_t array_ref_len(const char *);
 char *arrayname(const char *);
 mksh_uari_t set_array(const char *, bool, const char **);
+uint32_t hash(const void *);
+void rndset(long);
 
 enum Test_op {
-	TO_NONOP = 0,	/* non-operator */
+	/* non-operator */
+	TO_NONOP = 0,
 	/* unary operators */
 	TO_STNZE, TO_STZER, TO_OPTION,
 	TO_FILAXST,
@@ -1718,15 +1893,15 @@
 
 typedef struct test_env {
 	union {
-		const char **wp;/* used by ptest_* */
-		XPtrV *av;	/* used by dbtestp_* */
+		const char **wp;	/* used by ptest_* */
+		XPtrV *av;		/* used by dbtestp_* */
 	} pos;
-	const char **wp_end;	/* used by ptest_* */
+	const char **wp_end;		/* used by ptest_* */
 	Test_op (*isa)(struct test_env *, Test_meta);
 	const char *(*getopnd) (struct test_env *, Test_op, bool);
 	int (*eval)(struct test_env *, Test_op, const char *, const char *, bool);
 	void (*error)(struct test_env *, int, const char *);
-	int flags;		/* TEF_* */
+	int flags;			/* TEF_* */
 } Test_env;
 
 extern const char *const dbtest_tokens[];
@@ -1735,8 +1910,8 @@
 int test_eval(Test_env *, Test_op, const char *, const char *, bool);
 int test_parse(Test_env *);
 
-EXTERN int tty_fd I__(-1);	/* dup'd tty file descriptor */
-EXTERN int tty_devtty;		/* true if tty_fd is from /dev/tty */
+EXTERN int tty_fd E_INIT(-1);	/* dup'd tty file descriptor */
+EXTERN bool tty_devtty;		/* true if tty_fd is from /dev/tty */
 EXTERN struct termios tty_state;	/* saved tty state */
 
 extern void tty_init(bool, bool);
@@ -1747,6 +1922,6 @@
 # undef EXTERN_DEFINED
 # undef EXTERN
 #endif
-#undef I__
+#undef E_INIT
 
 #endif /* !MKSH_INCLUDES_ONLY */
diff --git a/src/sh_flags.h b/src/sh_flags.h
index aa5481e..a850220 100644
--- a/src/sh_flags.h
+++ b/src/sh_flags.h
@@ -1,5 +1,5 @@
 #if defined(SHFLAGS_DEFNS)
-__RCSID("$MirOS: src/bin/mksh/sh_flags.h,v 1.7 2010/07/13 13:07:58 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/sh_flags.h,v 1.9 2011/06/12 15:37:10 tg Exp $");
 #define FN(sname,cname,ochar,flags)	/* nothing */
 #elif defined(SHFLAGS_ENUMS)
 #define FN(sname,cname,ochar,flags)	cname,
@@ -21,9 +21,6 @@
 /* -a	all new parameters are created with the export attribute */
 F0("allexport", FEXPORT, 'a', OF_ANY)
 
-/* ./.	backwards compat: dummy, emits a warning */
-FN("arc4random", FARC4RANDOM, 0, OF_ANY)
-
 #if HAVE_NICE
 /* ./.	bgnice */
 FN("bgnice", FBGNICE, 0, OF_ANY)
@@ -135,6 +132,9 @@
  * anonymous flags: used internally by shell only (not visible to user)
  */
 
+/* ./.	direct builtin call (divined from argv[0] multi-call binary) */
+FN(NULL, FAS_BUILTIN, 0, OF_INTERNAL)
+
 /* ./.	(internal) initial shell was interactive */
 FN(NULL, FTALKING_I, 0, OF_INTERNAL)
 
diff --git a/src/shf.c b/src/shf.c
index 0962752..7592e2b 100644
--- a/src/shf.c
+++ b/src/shf.c
@@ -1,7 +1,7 @@
 /*	$OpenBSD: shf.c,v 1.15 2006/04/02 00:48:33 deraadt Exp $	*/
 
 /*-
- * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2011
  *	Thorsten Glaser <tg@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -18,11 +18,13 @@
  * of dealing in the work, even if advised of the possibility of such
  * damage or existence of a defect, except proven that it results out
  * of said person's immediate fault when using the work as intended.
+ *-
+ * Use %zX instead of %p and floating point isn't supported at all.
  */
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/shf.c,v 1.36 2010/07/19 22:41:04 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/shf.c,v 1.44 2011/09/07 15:24:20 tg Exp $");
 
 /* flags to shf_emptybuf() */
 #define EB_READSW	0x01	/* about to switch to reading */
@@ -37,7 +39,8 @@
 static int shf_fillbuf(struct shf *);
 static int shf_emptybuf(struct shf *, int);
 
-/* Open a file. First three args are for open(), last arg is flags for
+/*
+ * Open a file. First three args are for open(), last arg is flags for
  * this package. Returns NULL if file could not be opened, or if a dup
  * fails.
  */
@@ -45,7 +48,9 @@
 shf_open(const char *name, int oflags, int mode, int sflags)
 {
 	struct shf *shf;
-	int bsize = sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE;
+	ssize_t bsize =
+	    /* at most 512 */
+	    sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE;
 	int fd;
 
 	/* Done before open so if alloca fails, fd won't be lost. */
@@ -79,11 +84,11 @@
 	return (shf_reopen(fd, sflags, shf));
 }
 
-/* Set up the shf structure for a file descriptor. Doesn't fail. */
-struct shf *
-shf_fdopen(int fd, int sflags, struct shf *shf)
+/* helper function for shf_fdopen and shf_reopen */
+static void
+shf_open_hlp(int fd, int *sflagsp, const char *where)
 {
-	int bsize = sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE;
+	int sflags = *sflagsp;
 
 	/* use fcntl() to figure out correct read/write flags */
 	if (sflags & SHF_GETFL) {
@@ -105,11 +110,22 @@
 				break;
 			}
 		}
+		*sflagsp = sflags;
 	}
 
 	if (!(sflags & (SHF_RD | SHF_WR)))
-		internal_errorf("shf_fdopen: missing read/write");
+		internal_errorf("%s: %s", where, "missing read/write");
+}
 
+/* Set up the shf structure for a file descriptor. Doesn't fail. */
+struct shf *
+shf_fdopen(int fd, int sflags, struct shf *shf)
+{
+	ssize_t bsize =
+	    /* at most 512 */
+	    sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE;
+
+	shf_open_hlp(fd, &sflags, "shf_fdopen");
 	if (shf) {
 		if (bsize) {
 			shf->buf = alloc(bsize, ATEMP);
@@ -129,7 +145,7 @@
 	shf->wnleft = 0; /* force call to shf_emptybuf() */
 	shf->wbsize = sflags & SHF_UNBUF ? 0 : bsize;
 	shf->flags = sflags;
-	shf->errno_ = 0;
+	shf->errnosv = 0;
 	shf->bsize = bsize;
 	if (sflags & SHF_CLEXEC)
 		fcntl(fd, F_SETFD, FD_CLOEXEC);
@@ -140,34 +156,13 @@
 struct shf *
 shf_reopen(int fd, int sflags, struct shf *shf)
 {
-	int bsize = sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE;
+	ssize_t bsize =
+	    /* at most 512 */
+	    sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE;
 
-	/* use fcntl() to figure out correct read/write flags */
-	if (sflags & SHF_GETFL) {
-		int flags = fcntl(fd, F_GETFL, 0);
-
-		if (flags < 0)
-			/* will get an error on first read/write */
-			sflags |= SHF_RDWR;
-		else {
-			switch (flags & O_ACCMODE) {
-			case O_RDONLY:
-				sflags |= SHF_RD;
-				break;
-			case O_WRONLY:
-				sflags |= SHF_WR;
-				break;
-			case O_RDWR:
-				sflags |= SHF_RDWR;
-				break;
-			}
-		}
-	}
-
-	if (!(sflags & (SHF_RD | SHF_WR)))
-		internal_errorf("shf_reopen: missing read/write");
+	shf_open_hlp(fd, &sflags, "shf_reopen");
 	if (!shf || !shf->buf || shf->bsize < bsize)
-		internal_errorf("shf_reopen: bad shf/buf/bsize");
+		internal_errorf("%s: %s", "shf_reopen", "bad shf/buf/bsize");
 
 	/* assumes shf->buf and shf->bsize already set up */
 	shf->fd = fd;
@@ -177,26 +172,27 @@
 	shf->wnleft = 0; /* force call to shf_emptybuf() */
 	shf->wbsize = sflags & SHF_UNBUF ? 0 : bsize;
 	shf->flags = (shf->flags & (SHF_ALLOCS | SHF_ALLOCB)) | sflags;
-	shf->errno_ = 0;
+	shf->errnosv = 0;
 	if (sflags & SHF_CLEXEC)
 		fcntl(fd, F_SETFD, FD_CLOEXEC);
 	return (shf);
 }
 
-/* Open a string for reading or writing. If reading, bsize is the number
+/*
+ * Open a string for reading or writing. If reading, bsize is the number
  * of bytes that can be read. If writing, bsize is the maximum number of
- * bytes that can be written. If shf is not null, it is filled in and
- * returned, if it is null, shf is allocated. If writing and buf is null
+ * bytes that can be written. If shf is not NULL, it is filled in and
+ * returned, if it is NULL, shf is allocated. If writing and buf is NULL
  * and SHF_DYNAMIC is set, the buffer is allocated (if bsize > 0, it is
  * used for the initial size). Doesn't fail.
- * When writing, a byte is reserved for a trailing null - see shf_sclose().
+ * When writing, a byte is reserved for a trailing NUL - see shf_sclose().
  */
 struct shf *
-shf_sopen(char *buf, int bsize, int sflags, struct shf *shf)
+shf_sopen(char *buf, ssize_t bsize, int sflags, struct shf *shf)
 {
 	/* can't have a read+write string */
 	if (!(!(sflags & SHF_RD) ^ !(sflags & SHF_WR)))
-		internal_errorf("shf_sopen: flags 0x%x", sflags);
+		internal_errorf("%s: flags 0x%X", "shf_sopen", sflags);
 
 	if (!shf) {
 		shf = alloc(sizeof(struct shf), ATEMP);
@@ -216,7 +212,7 @@
 	shf->wnleft = bsize - 1;	/* space for a '\0' */
 	shf->wbsize = bsize;
 	shf->flags = sflags | SHF_STRING;
-	shf->errno_ = 0;
+	shf->errnosv = 0;
 	shf->bsize = bsize;
 
 	return (shf);
@@ -260,7 +256,8 @@
 	return (ret);
 }
 
-/* Close a string - if it was opened for writing, it is null terminated;
+/*
+ * Close a string - if it was opened for writing, it is NUL terminated;
  * returns a pointer to the string and frees shf if it was allocated
  * (does not free string if it was allocated).
  */
@@ -269,7 +266,7 @@
 {
 	unsigned char *s = shf->buf;
 
-	/* null terminate */
+	/* NUL terminate */
 	if (shf->flags & SHF_WR) {
 		shf->wnleft++;
 		shf_putc('\0', shf);
@@ -279,7 +276,8 @@
 	return ((char *)s);
 }
 
-/* Un-read what has been read but not examined, or write what has been
+/*
+ * Un-read what has been read but not examined, or write what has been
  * buffered. Returns 0 for success, EOF for (write) error.
  */
 int
@@ -289,10 +287,10 @@
 		return ((shf->flags & SHF_WR) ? EOF : 0);
 
 	if (shf->fd < 0)
-		internal_errorf("shf_flush: no fd");
+		internal_errorf("%s: %s", "shf_flush", "no fd");
 
 	if (shf->flags & SHF_ERROR) {
-		errno = shf->errno_;
+		errno = shf->errnosv;
 		return (EOF);
 	}
 
@@ -310,7 +308,8 @@
 	return (0);
 }
 
-/* Write out any buffered data. If currently reading, flushes the read
+/*
+ * Write out any buffered data. If currently reading, flushes the read
  * buffer. Returns 0 for success, EOF for (write) error.
  */
 static int
@@ -319,15 +318,16 @@
 	int ret = 0;
 
 	if (!(shf->flags & SHF_STRING) && shf->fd < 0)
-		internal_errorf("shf_emptybuf: no fd");
+		internal_errorf("%s: %s", "shf_emptybuf", "no fd");
 
 	if (shf->flags & SHF_ERROR) {
-		errno = shf->errno_;
+		errno = shf->errnosv;
 		return (EOF);
 	}
 
 	if (shf->flags & SHF_READING) {
-		if (flags & EB_READSW) /* doesn't happen */
+		if (flags & EB_READSW)
+			/* doesn't happen */
 			return (0);
 		ret = shf_flush(shf);
 		shf->flags &= ~SHF_READING;
@@ -335,25 +335,26 @@
 	if (shf->flags & SHF_STRING) {
 		unsigned char *nbuf;
 
-		/* Note that we assume SHF_ALLOCS is not set if SHF_ALLOCB
-		 * is set... (changing the shf pointer could cause problems)
+		/*
+		 * Note that we assume SHF_ALLOCS is not set if
+		 * SHF_ALLOCB is set... (changing the shf pointer could
+		 * cause problems)
 		 */
 		if (!(flags & EB_GROW) || !(shf->flags & SHF_DYNAMIC) ||
 		    !(shf->flags & SHF_ALLOCB))
 			return (EOF);
 		/* allocate more space for buffer */
-		nbuf = aresize(shf->buf, 2 * shf->wbsize, shf->areap);
+		nbuf = aresize2(shf->buf, 2, shf->wbsize, shf->areap);
 		shf->rp = nbuf + (shf->rp - shf->buf);
 		shf->wp = nbuf + (shf->wp - shf->buf);
 		shf->rbsize += shf->wbsize;
 		shf->wnleft += shf->wbsize;
-		shf->wbsize *= 2;
+		shf->wbsize <<= 1;
 		shf->buf = nbuf;
 	} else {
 		if (shf->flags & SHF_WRITING) {
-			int ntowrite = shf->wp - shf->buf;
+			ssize_t n, ntowrite = shf->wp - shf->buf;
 			unsigned char *buf = shf->buf;
-			int n;
 
 			while (ntowrite > 0) {
 				n = write(shf->fd, buf, ntowrite);
@@ -362,11 +363,13 @@
 					    !(shf->flags & SHF_INTERRUPT))
 						continue;
 					shf->flags |= SHF_ERROR;
-					shf->errno_ = errno;
+					shf->errnosv = errno;
 					shf->wnleft = 0;
 					if (buf != shf->buf) {
-						/* allow a second flush
-						 * to work */
+						/*
+						 * allow a second flush
+						 * to work
+						 */
 						memmove(shf->buf, buf,
 						    ntowrite);
 						shf->wp = shf->buf + ntowrite;
@@ -395,15 +398,17 @@
 static int
 shf_fillbuf(struct shf *shf)
 {
+	ssize_t n;
+
 	if (shf->flags & SHF_STRING)
 		return (0);
 
 	if (shf->fd < 0)
-		internal_errorf("shf_fillbuf: no fd");
+		internal_errorf("%s: %s", "shf_fillbuf", "no fd");
 
 	if (shf->flags & (SHF_EOF | SHF_ERROR)) {
 		if (shf->flags & SHF_ERROR)
-			errno = shf->errno_;
+			errno = shf->errnosv;
 		return (EOF);
 	}
 
@@ -413,42 +418,39 @@
 	shf->flags |= SHF_READING;
 
 	shf->rp = shf->buf;
-	while (1) {
-		shf->rnleft = blocking_read(shf->fd, (char *) shf->buf,
-		    shf->rbsize);
-		if (shf->rnleft < 0 && errno == EINTR &&
-		    !(shf->flags & SHF_INTERRUPT))
+	while (/* CONSTCOND */ 1) {
+		n = blocking_read(shf->fd, (char *)shf->buf, shf->rbsize);
+		if (n < 0 && errno == EINTR && !(shf->flags & SHF_INTERRUPT))
 			continue;
 		break;
 	}
-	if (shf->rnleft <= 0) {
-		if (shf->rnleft < 0) {
-			shf->flags |= SHF_ERROR;
-			shf->errno_ = errno;
-			shf->rnleft = 0;
-			shf->rp = shf->buf;
-			return (EOF);
-		}
-		shf->flags |= SHF_EOF;
+	if (n < 0) {
+		shf->flags |= SHF_ERROR;
+		shf->errnosv = errno;
+		shf->rnleft = 0;
+		shf->rp = shf->buf;
+		return (EOF);
 	}
+	if ((shf->rnleft = n) == 0)
+		shf->flags |= SHF_EOF;
 	return (0);
 }
 
-/* Read a buffer from shf. Returns the number of bytes read into buf,
- * if no bytes were read, returns 0 if end of file was seen, EOF if
- * a read error occurred.
+/*
+ * Read a buffer from shf. Returns the number of bytes read into buf, if
+ * no bytes were read, returns 0 if end of file was seen, EOF if a read
+ * error occurred.
  */
-int
-shf_read(char *buf, int bsize, struct shf *shf)
+ssize_t
+shf_read(char *buf, ssize_t bsize, struct shf *shf)
 {
-	int orig_bsize = bsize;
-	int ncopy;
+	ssize_t ncopy, orig_bsize = bsize;
 
 	if (!(shf->flags & SHF_RD))
-		internal_errorf("shf_read: flags %x", shf->flags);
+		internal_errorf("%s: flags 0x%X", "shf_read", shf->flags);
 
 	if (bsize <= 0)
-		internal_errorf("shf_read: bsize %d", bsize);
+		internal_errorf("%s: %s %zd", "shf_write", "bsize", bsize);
 
 	while (bsize > 0) {
 		if (shf->rnleft == 0 &&
@@ -468,24 +470,27 @@
 	    orig_bsize - bsize);
 }
 
-/* Read up to a newline or EOF. The newline is put in buf; buf is always
- * null terminated. Returns NULL on read error or if nothing was read before
- * end of file, returns a pointer to the null byte in buf otherwise.
+/*
+ * Read up to a newline or EOF. The newline is put in buf; buf is always
+ * NUL terminated. Returns NULL on read error or if nothing was read
+ * before end of file, returns a pointer to the NUL byte in buf
+ * otherwise.
  */
 char *
-shf_getse(char *buf, int bsize, struct shf *shf)
+shf_getse(char *buf, ssize_t bsize, struct shf *shf)
 {
 	unsigned char *end;
-	int ncopy;
+	ssize_t ncopy;
 	char *orig_buf = buf;
 
 	if (!(shf->flags & SHF_RD))
-		internal_errorf("shf_getse: flags %x", shf->flags);
+		internal_errorf("%s: flags 0x%X", "shf_getse", shf->flags);
 
 	if (bsize <= 0)
 		return (NULL);
 
-	--bsize;	/* save room for null */
+	/* save room for NUL */
+	--bsize;	
 	do {
 		if (shf->rnleft == 0) {
 			if (shf_fillbuf(shf) == EOF)
@@ -495,7 +500,7 @@
 				return (buf == orig_buf ? NULL : buf);
 			}
 		}
-		end = (unsigned char *)memchr((char *) shf->rp, '\n',
+		end = (unsigned char *)memchr((char *)shf->rp, '\n',
 		    shf->rnleft);
 		ncopy = end ? end - shf->rp + 1 : shf->rnleft;
 		if (ncopy > bsize)
@@ -515,7 +520,7 @@
 shf_getchar(struct shf *shf)
 {
 	if (!(shf->flags & SHF_RD))
-		internal_errorf("shf_getchar: flags %x", shf->flags);
+		internal_errorf("%s: flags 0x%X", "shf_getchar", shf->flags);
 
 	if (shf->rnleft == 0 && (shf_fillbuf(shf) == EOF || shf->rnleft == 0))
 		return (EOF);
@@ -523,14 +528,15 @@
 	return (*shf->rp++);
 }
 
-/* Put a character back in the input stream. Returns the character if
+/*
+ * Put a character back in the input stream. Returns the character if
  * successful, EOF if there is no room.
  */
 int
 shf_ungetc(int c, struct shf *shf)
 {
 	if (!(shf->flags & SHF_RD))
-		internal_errorf("shf_ungetc: flags %x", shf->flags);
+		internal_errorf("%s: flags 0x%X", "shf_ungetc", shf->flags);
 
 	if ((shf->flags & SHF_ERROR) || c == EOF ||
 	    (shf->rp == shf->buf && shf->rnleft))
@@ -542,8 +548,9 @@
 	if (shf->rp == shf->buf)
 		shf->rp = shf->buf + shf->rbsize;
 	if (shf->flags & SHF_STRING) {
-		/* Can unget what was read, but not something different - we
-		 * don't want to modify a string.
+		/*
+		 * Can unget what was read, but not something different;
+		 * we don't want to modify a string.
 		 */
 		if (shf->rp[-1] != c)
 			return (EOF);
@@ -558,26 +565,27 @@
 	return (c);
 }
 
-/* Write a character. Returns the character if successful, EOF if
- * the char could not be written.
+/*
+ * Write a character. Returns the character if successful, EOF if the
+ * char could not be written.
  */
 int
 shf_putchar(int c, struct shf *shf)
 {
 	if (!(shf->flags & SHF_WR))
-		internal_errorf("shf_putchar: flags %x", shf->flags);
+		internal_errorf("%s: flags 0x%X", "shf_putchar", shf->flags);
 
 	if (c == EOF)
 		return (EOF);
 
 	if (shf->flags & SHF_UNBUF) {
 		unsigned char cc = (unsigned char)c;
-		int n;
+		ssize_t n;
 
 		if (shf->fd < 0)
-			internal_errorf("shf_putchar: no fd");
+			internal_errorf("%s: %s", "shf_putchar", "no fd");
 		if (shf->flags & SHF_ERROR) {
-			errno = shf->errno_;
+			errno = shf->errnosv;
 			return (EOF);
 		}
 		while ((n = write(shf->fd, &cc, 1)) != 1)
@@ -586,7 +594,7 @@
 				    !(shf->flags & SHF_INTERRUPT))
 					continue;
 				shf->flags |= SHF_ERROR;
-				shf->errno_ = errno;
+				shf->errnosv = errno;
 				return (EOF);
 			}
 	} else {
@@ -600,10 +608,11 @@
 	return (c);
 }
 
-/* Write a string. Returns the length of the string if successful, EOF if
- * the string could not be written.
+/*
+ * Write a string. Returns the length of the string if successful, EOF
+ * if the string could not be written.
  */
-int
+ssize_t
 shf_puts(const char *s, struct shf *shf)
 {
 	if (!s)
@@ -613,16 +622,16 @@
 }
 
 /* Write a buffer. Returns nbytes if successful, EOF if there is an error. */
-int
-shf_write(const char *buf, int nbytes, struct shf *shf)
+ssize_t
+shf_write(const char *buf, ssize_t nbytes, struct shf *shf)
 {
-	int n, ncopy, orig_nbytes = nbytes;
+	ssize_t n, ncopy, orig_nbytes = nbytes;
 
 	if (!(shf->flags & SHF_WR))
-		internal_errorf("shf_write: flags %x", shf->flags);
+		internal_errorf("%s: flags 0x%X", "shf_write", shf->flags);
 
 	if (nbytes < 0)
-		internal_errorf("shf_write: nbytes %d", nbytes);
+		internal_errorf("%s: %s %zd", "shf_write", "nbytes", nbytes);
 
 	/* Don't buffer if buffer is empty and we're writting a large amount. */
 	if ((ncopy = shf->wnleft) &&
@@ -659,7 +668,7 @@
 						    !(shf->flags & SHF_INTERRUPT))
 							continue;
 						shf->flags |= SHF_ERROR;
-						shf->errno_ = errno;
+						shf->errnosv = errno;
 						shf->wnleft = 0;
 						/*
 						 * Note: fwrite(3) returns 0
@@ -684,11 +693,11 @@
 	return (orig_nbytes);
 }
 
-int
+ssize_t
 shf_fprintf(struct shf *shf, const char *fmt, ...)
 {
 	va_list args;
-	int n;
+	ssize_t n;
 
 	va_start(args, fmt);
 	n = shf_vfprintf(shf, fmt, args);
@@ -697,21 +706,23 @@
 	return (n);
 }
 
-int
-shf_snprintf(char *buf, int bsize, const char *fmt, ...)
+ssize_t
+shf_snprintf(char *buf, ssize_t bsize, const char *fmt, ...)
 {
 	struct shf shf;
 	va_list args;
-	int n;
+	ssize_t n;
 
 	if (!buf || bsize <= 0)
-		internal_errorf("shf_snprintf: buf %p, bsize %d", buf, bsize);
+		internal_errorf("shf_snprintf: buf %zX, bsize %zd",
+		    (size_t)buf, bsize);
 
 	shf_sopen(buf, bsize, SHF_WR, &shf);
 	va_start(args, fmt);
 	n = shf_vfprintf(&shf, fmt, args);
 	va_end(args);
-	shf_sclose(&shf); /* null terminates */
+	/* NUL terminates */
+	shf_sclose(&shf); 
 	return (n);
 }
 
@@ -725,20 +736,11 @@
 	va_start(args, fmt);
 	shf_vfprintf(&shf, fmt, args);
 	va_end(args);
-	return (shf_sclose(&shf)); /* null terminates */
+	/* NUL terminates */
+	return (shf_sclose(&shf));
 }
 
-#undef FP			/* if you want floating point stuff */
-
-#ifndef DMAXEXP
-# define DMAXEXP	128	/* should be big enough */
-#endif
-
 #define BUF_SIZE	128
-/* must be > MAX(DMAXEXP, log10(pow(2, DSIGNIF))) + ceil(log10(DMAXEXP)) + 8
- * (I think); since it's hard to express as a constant, just use a large buffer
- */
-#define FPBUF_SIZE	(DMAXEXP+16)
 
 #define	FL_HASH		0x001	/* '#' seen */
 #define FL_PLUS		0x002	/* '+' seen */
@@ -750,19 +752,23 @@
 #define FL_DOT		0x080	/* '.' seen */
 #define FL_UPPER	0x100	/* format character was uppercase */
 #define FL_NUMBER	0x200	/* a number was formated %[douxefg] */
+#define FL_SIZET	0x400	/* 'z' seen */
+#define FM_SIZES	0x430	/* h/l/z mask */
 
-
-int
+ssize_t
 shf_vfprintf(struct shf *shf, const char *fmt, va_list args)
 {
 	const char *s;
 	char c, *cp;
-	int tmp = 0, field, precision, len, flags;
+	int tmp = 0, flags;
+	ssize_t field, precision, len;
 	unsigned long lnum;
 	/* %#o produces the longest output */
 	char numbuf[(8 * sizeof(long) + 2) / 3 + 1];
 	/* this stuff for dealing with the buffer */
-	int nwritten = 0;
+	ssize_t nwritten = 0;
+
+#define VA(type) va_arg(args, type)
 
 	if (!fmt)
 		return (0);
@@ -774,13 +780,14 @@
 			continue;
 		}
 		/*
-		 * This will accept flags/fields in any order - not
-		 * just the order specified in printf(3), but this is
-		 * the way _doprnt() seems to work (on bsd and sysV).
-		 * The only restriction is that the format character must
-		 * come last :-).
+		 * This will accept flags/fields in any order - not just
+		 * the order specified in printf(3), but this is the way
+		 * _doprnt() seems to work (on BSD and SYSV). The only
+		 * restriction is that the format character must come
+		 * last :-).
 		 */
-		flags = field = precision = 0;
+		flags = 0;
+		field = precision = 0;
 		for ( ; (c = *fmt++) ; ) {
 			switch (c) {
 			case '#':
@@ -810,7 +817,7 @@
 				continue;
 
 			case '*':
-				tmp = va_arg(args, int);
+				tmp = VA(int);
 				if (flags & FL_DOT)
 					precision = tmp;
 				else if ((field = tmp) < 0) {
@@ -820,19 +827,27 @@
 				continue;
 
 			case 'l':
+				flags &= ~FM_SIZES;
 				flags |= FL_LONG;
 				continue;
 
 			case 'h':
+				flags &= ~FM_SIZES;
 				flags |= FL_SHORT;
 				continue;
+
+			case 'z':
+				flags &= ~FM_SIZES;
+				flags |= FL_SIZET;
+				continue;
 			}
 			if (ksh_isdigit(c)) {
 				tmp = c - '0';
 				while (c = *fmt++, ksh_isdigit(c))
 					tmp = tmp * 10 + c - '0';
 				--fmt;
-				if (tmp < 0)		/* overflow? */
+				if (tmp < 0)
+					/* overflow? */
 					tmp = 0;
 				if (flags & FL_DOT)
 					precision = tmp;
@@ -846,7 +861,8 @@
 		if (precision < 0)
 			precision = 0;
 
-		if (!c)		/* nasty format */
+		if (!c)
+			/* nasty format */
 			break;
 
 		if (c >= 'A' && c <= 'Z') {
@@ -855,33 +871,34 @@
 		}
 
 		switch (c) {
-		case 'p': /* pointer */
-			flags &= ~(FL_LONG | FL_SHORT);
-			flags |= (sizeof(char *) > sizeof(int)) ?
-			    /* hope it fits.. */ FL_LONG : 0;
-			/* aaahhh... */
 		case 'd':
 		case 'i':
+			if (flags & FL_SIZET)
+				lnum = (long)VA(ssize_t);
+			else if (flags & FL_LONG)
+				lnum = VA(long);
+			else if (flags & FL_SHORT)
+				lnum = (long)(short)VA(int);
+			else
+				lnum = (long)VA(int);
+			goto integral;
+
 		case 'o':
 		case 'u':
 		case 'x':
+			if (flags & FL_SIZET)
+				lnum = VA(size_t);
+			else if (flags & FL_LONG)
+				lnum = VA(unsigned long);
+			else if (flags & FL_SHORT)
+				lnum = (unsigned long)(unsigned short)VA(int);
+			else
+				lnum = (unsigned long)VA(unsigned int);
+
+ integral:
 			flags |= FL_NUMBER;
 			cp = numbuf + sizeof(numbuf);
-			/*-
-			 * XXX any better way to do this?
-			 * XXX hopefully the compiler optimises this out
-			 *
-			 * For shorts, we want sign extend for %d but not
-			 * for %[oxu] - on 16 bit machines it doesn't matter.
-			 * Assumes C compiler has converted shorts to ints
-			 * before pushing them. XXX optimise this -tg
-			 */
-			if (flags & FL_LONG)
-				lnum = va_arg(args, unsigned long);
-			else if ((sizeof(int) < sizeof(long)) && (c == 'd'))
-				lnum = (long)va_arg(args, int);
-			else
-				lnum = va_arg(args, unsigned int);
+
 			switch (c) {
 			case 'd':
 			case 'i':
@@ -917,7 +934,6 @@
 					*--cp = '0';
 				break;
 
-			case 'p':
 			case 'x': {
 				const char *digits = (flags & FL_UPPER) ?
 				    digits_uc : digits_lc;
@@ -938,19 +954,20 @@
 					field = precision;
 					flags |= FL_ZERO;
 				} else
-					precision = len; /* no loss */
+					/* no loss */
+					precision = len;
 			}
 			break;
 
 		case 's':
-			if (!(s = va_arg(args, const char *)))
+			if ((s = VA(const char *)) == NULL)
 				s = "(null)";
 			len = utf_mbswidth(s);
 			break;
 
 		case 'c':
 			flags &= ~FL_DOT;
-			numbuf[0] = (char)(va_arg(args, int));
+			numbuf[0] = (char)(VA(int));
 			s = numbuf;
 			len = 1;
 			break;
@@ -1029,14 +1046,12 @@
 int
 shf_getc(struct shf *shf)
 {
-	return ((shf)->rnleft > 0 ? (shf)->rnleft--, *(shf)->rp++ :
-	    shf_getchar(shf));
+	return (shf_getc_(shf));
 }
 
 int
 shf_putc(int c, struct shf *shf)
 {
-	return ((shf)->wnleft == 0 ? shf_putchar((c), (shf)) :
-	    ((shf)->wnleft--, *(shf)->wp++ = (c)));
+	return (shf_putc_(c, shf));
 }
 #endif
diff --git a/src/strlcpy.c b/src/strlcpy.c
new file mode 100644
index 0000000..53f9130
--- /dev/null
+++ b/src/strlcpy.c
@@ -0,0 +1,52 @@
+/*-
+ * Copyright (c) 2006, 2008, 2009
+ *	Thorsten Glaser <tg@mirbsd.org>
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "sh.h"
+
+__RCSID("$MirOS: src/bin/mksh/strlcpy.c,v 1.7 2009/06/10 18:12:50 tg Rel $");
+
+/*
+ * Copy src to string dst of size siz. At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz == 0).
+ * Returns strlen(src); if retval >= siz, truncation occurred.
+ */
+size_t
+strlcpy(char *dst, const char *src, size_t siz)
+{
+	const char *s = src;
+
+	if (siz == 0)
+		goto traverse_src;
+
+	/* copy as many chars as will fit */
+	while (--siz && (*dst++ = *s++))
+		;
+
+	/* not enough room in dst */
+	if (siz == 0) {
+		/* safe to NUL-terminate dst since we copied <= siz-1 chars */
+		*dst = '\0';
+ traverse_src:
+		/* traverse rest of src */
+		while (*s++)
+			;
+	}
+
+	/* count does not include NUL */
+	return ((size_t)(s - src - 1));
+}
diff --git a/src/syn.c b/src/syn.c
index 64b2867..b2a3f8b 100644
--- a/src/syn.c
+++ b/src/syn.c
@@ -1,7 +1,7 @@
 /*	$OpenBSD: syn.c,v 1.28 2008/07/23 16:34:38 jaredy Exp $	*/
 
 /*-
- * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2011
  *	Thorsten Glaser <tg@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -22,7 +22,10 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/syn.c,v 1.49 2010/07/17 22:09:39 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/syn.c,v 1.69 2011/09/07 15:24:21 tg Exp $");
+
+extern short subshell_nesting_level;
+extern void yyskiputf8bom(void);
 
 struct nesting_state {
 	int start_token;	/* token than began nesting (eg, FOR) */
@@ -32,7 +35,7 @@
 static void yyparse(void);
 static struct op *pipeline(int);
 static struct op *andor(void);
-static struct op *c_list(int);
+static struct op *c_list(bool);
 static struct ioword *synio(int);
 static struct op *nested(int, int, int);
 static struct op *get_command(int);
@@ -59,14 +62,14 @@
 static struct op *outtree;		/* yyparse output */
 static struct nesting_state nesting;	/* \n changed to ; */
 
-static int reject;		/* token(cf) gets symbol again */
-static int symbol;		/* yylex value */
+static bool reject;			/* token(cf) gets symbol again */
+static int symbol;			/* yylex value */
 
-#define REJECT		(reject = 1)
-#define ACCEPT		(reject = 0)
+#define REJECT		(reject = true)
+#define ACCEPT		(reject = false)
 #define token(cf)	((reject) ? (ACCEPT, symbol) : (symbol = yylex(cf)))
 #define tpeek(cf)	((reject) ? (symbol) : (REJECT, symbol = yylex(cf)))
-#define musthave(c,cf)	do { if (token(cf) != (c)) syntaxerr(NULL); } while (0)
+#define musthave(c,cf)	do { if (token(cf) != (c)) syntaxerr(NULL); } while (/* CONSTCOND */ 0)
 
 static void
 yyparse(void)
@@ -122,20 +125,23 @@
 }
 
 static struct op *
-c_list(int multi)
+c_list(bool multi)
 {
 	struct op *t = NULL, *p, *tl = NULL;
-	int c, have_sep;
+	int c;
+	bool have_sep;
 
-	while (1) {
+	while (/* CONSTCOND */ 1) {
 		p = andor();
-		/* Token has always been read/rejected at this point, so
+		/*
+		 * Token has always been read/rejected at this point, so
 		 * we don't worry about what flags to pass token()
 		 */
 		c = token(0);
-		have_sep = 1;
+		have_sep = true;
 		if (c == '\n' && (multi || inalias(source))) {
-			if (!p) /* ignore blank lines */
+			if (!p)
+				/* ignore blank lines */
 				continue;
 		} else if (!p)
 			break;
@@ -143,7 +149,7 @@
 			p = block(c == '&' ? TASYNC : TCOPROC,
 			    p, NOBLOCK, NOWORDS);
 		else if (c != ';')
-			have_sep = 0;
+			have_sep = false;
 		if (!t)
 			t = p;
 		else if (!tl)
@@ -161,7 +167,7 @@
 synio(int cf)
 {
 	struct ioword *iop;
-	static struct ioword *nextiop = NULL;
+	static struct ioword *nextiop;
 	bool ishere;
 
 	if (nextiop != NULL) {
@@ -174,14 +180,18 @@
 		return (NULL);
 	ACCEPT;
 	iop = yylval.iop;
-	ishere = (iop->flag&IOTYPE) == IOHERE;
+	if (iop->flag & IONDELIM)
+		goto gotnulldelim;
+	ishere = (iop->flag & IOTYPE) == IOHERE;
 	musthave(LWORD, ishere ? HEREDELIM : 0);
 	if (ishere) {
 		iop->delim = yylval.cp;
-		if (*ident != 0) /* unquoted */
+		if (*ident != 0)
+			/* unquoted */
+ gotnulldelim:
 			iop->flag |= IOEVAL;
 		if (herep > &heres[HERES - 1])
-			yyerror("too many <<s\n");
+			yyerror("too many %ss\n", "<<");
 		*herep++ = iop;
 	} else
 		iop->name = yylval.cp;
@@ -231,7 +241,8 @@
 	XPtrV args, vars;
 	struct nesting_state old_nesting;
 
-	iops = alloc((NUFILE + 1) * sizeof(struct ioword *), ATEMP);
+	/* NUFILE is small enough to leave this addition unchecked */
+	iops = alloc2((NUFILE + 1), sizeof(struct ioword *), ATEMP);
 	XPinit(args, 16);
 	XPinit(vars, 16);
 
@@ -242,7 +253,8 @@
 		afree(iops, ATEMP);
 		XPfree(args);
 		XPfree(vars);
-		return (NULL); /* empty line */
+		/* empty line */
+		return (NULL);
 
 	case LWORD:
 	case REDIR:
@@ -250,21 +262,23 @@
 		syniocf &= ~(KEYWORD|ALIAS);
 		t = newtp(TCOM);
 		t->lineno = source->line;
-		while (1) {
+		while (/* CONSTCOND */ 1) {
 			cf = (t->u.evalflags ? ARRAYVAR : 0) |
 			    (XPsize(args) == 0 ? ALIAS|VARASN : CMDWORD);
 			switch (tpeek(cf)) {
 			case REDIR:
 				while ((iop = synio(cf)) != NULL) {
 					if (iopn >= NUFILE)
-						yyerror("too many redirections\n");
+						yyerror("too many %ss\n",
+						    "redirection");
 					iops[iopn++] = iop;
 				}
 				break;
 
 			case LWORD:
 				ACCEPT;
-				/* the iopn == 0 and XPsize(vars) == 0 are
+				/*
+				 * the iopn == 0 and XPsize(vars) == 0 are
 				 * dubious but AT&T ksh acts this way
 				 */
 				if (iopn == 0 && XPsize(vars) == 0 &&
@@ -279,7 +293,13 @@
 				break;
 
 			case '(':
-				/* Check for "> foo (echo hi)" which AT&T ksh
+#ifndef MKSH_SMALL
+				if ((XPsize(args) == 0 || Flag(FKEYWORD)) &&
+				    XPsize(vars) == 1 && is_wdvarassign(yylval.cp))
+					goto is_wdarrassign;
+#endif
+				/*
+				 * Check for "> foo (echo hi)" which AT&T ksh
 				 * allows (not POSIX, but not disallowed)
 				 */
 				afree(t, ATEMP);
@@ -287,55 +307,50 @@
 					ACCEPT;
 					goto Subshell;
 				}
-#ifndef MKSH_SMALL
-				if ((XPsize(args) == 0 || Flag(FKEYWORD)) &&
-				    XPsize(vars) == 1 && is_wdvarassign(yylval.cp))
-					goto is_wdarrassign;
-#endif
-				/* Must be a function */
+
+				/* must be a function */
 				if (iopn != 0 || XPsize(args) != 1 ||
 				    XPsize(vars) != 0)
 					syntaxerr(NULL);
 				ACCEPT;
-				/*(*/
-				musthave(')', 0);
+				musthave(/*(*/')', 0);
 				t = function_body(XPptrv(args)[0], false);
 				goto Leave;
 #ifndef MKSH_SMALL
  is_wdarrassign:
 			{
 				static const char set_cmd0[] = {
-					CHAR, 'e', CHAR, 'v',
-					CHAR, 'a', CHAR, 'l', EOS
+					CHAR, 's', CHAR, 'e',
+					CHAR, 't', EOS
 				};
 				static const char set_cmd1[] = {
-					CHAR, 's', CHAR, 'e',
-					CHAR, 't', CHAR, ' ',
 					CHAR, '-', CHAR, 'A', EOS
 				};
 				static const char set_cmd2[] = {
 					CHAR, '-', CHAR, '-', EOS
 				};
 				char *tcp;
-				XPfree(vars);
-				XPinit(vars, 16);
-				/*
-				 * we know (or rather hope) that yylval.cp
-				 * contains a string "varname="
-				 */
-				tcp = wdcopy(yylval.cp, ATEMP);
-				tcp[wdscan(tcp, EOS) - tcp - 3] = EOS;
-				/* now make an array assignment command */
-				t = newtp(TCOM);
-				t->lineno = source->line;
+
 				ACCEPT;
+
+				/* manipulate the vars string */
+				tcp = *(--vars.cur);
+				/* 'varname=' -> 'varname' */
+				tcp[wdscan(tcp, EOS) - tcp - 3] = EOS;
+
+				/* construct new args strings */
 				XPput(args, wdcopy(set_cmd0, ATEMP));
 				XPput(args, wdcopy(set_cmd1, ATEMP));
 				XPput(args, tcp);
 				XPput(args, wdcopy(set_cmd2, ATEMP));
-				musthave(LWORD,LETARRAY);
-				XPput(args, yylval.cp);
-				break;
+
+				/* slurp in words till closing paren */
+				while (token(CONTIN) == LWORD)
+					XPput(args, yylval.cp);
+				if (symbol != /*(*/ ')')
+					syntaxerr(NULL);
+
+				goto Leave;
 			}
 #endif
 
@@ -348,7 +363,9 @@
 
 	case '(':
  Subshell:
+		++subshell_nesting_level;
 		t = nested(TPAREN, '(', ')');
+		--subshell_nesting_level;
 		break;
 
 	case '{': /*}*/
@@ -362,13 +379,13 @@
 			CHAR, 't', EOS
 		};
 
-		/* Leave KEYWORD in syniocf (allow if (( 1 )) then ...) */
+		/* leave KEYWORD in syniocf (allow if (( 1 )) then ...) */
 		lno = source->line;
 		ACCEPT;
 		switch (token(LETEXPR)) {
 		case LWORD:
 			break;
-		case '(':	/* ) */
+		case '(': /*)*/
 			goto Subshell;
 		default:
 			syntaxerr(NULL);
@@ -381,7 +398,7 @@
 	}
 
 	case DBRACKET: /* [[ .. ]] */
-		/* Leave KEYWORD in syniocf (allow if [[ -n 1 ]] then ...) */
+		/* leave KEYWORD in syniocf (allow if [[ -n 1 ]] then ...) */
 		t = newtp(TDBRACKET);
 		ACCEPT;
 		{
@@ -403,8 +420,8 @@
 		t = newtp((c == FOR) ? TFOR : TSELECT);
 		musthave(LWORD, ARRAYVAR);
 		if (!is_wdvarname(yylval.cp, true))
-			yyerror("%s: bad identifier\n",
-			    c == FOR ? "for" : "select");
+			yyerror("%s: %s\n", c == FOR ? "for" : Tselect,
+			    "bad identifier");
 		strdupx(t->str, ident, ATEMP);
 		nesting_push(&old_nesting, c);
 		t->vars = wordlist();
@@ -452,7 +469,8 @@
 		t = pipeline(0);
 		if (t) {
 			t->str = alloc(2, ATEMP);
-			t->str[0] = '\0';	/* TF_* flags */
+			/* TF_* flags */
+			t->str[0] = '\0';
 			t->str[1] = '\0';
 		}
 		t = block(TTIME, t, NOBLOCK, NOWORDS);
@@ -466,7 +484,7 @@
 
 	while ((iop = synio(syniocf)) != NULL) {
 		if (iopn >= NUFILE)
-			yyerror("too many redirections\n");
+			yyerror("too many %ss\n", "redirection");
 		iops[iopn++] = iop;
 	}
 
@@ -475,7 +493,7 @@
 		t->ioact = NULL;
 	} else {
 		iops[iopn++] = NULL;
-		iops = aresize(iops, iopn * sizeof(struct ioword *), ATEMP);
+		iops = aresize2(iops, iopn, sizeof(struct ioword *), ATEMP);
 		t->ioact = iops;
 	}
 
@@ -499,7 +517,8 @@
 	struct op *list;
 
 	c = token(CONTIN|KEYWORD|ALIAS);
-	/* A {...} can be used instead of do...done for for/select loops
+	/*
+	 * A {...} can be used instead of do...done for for/select loops
 	 * but not for while/until loops - we don't need to check if it
 	 * is a while loop because it would have been parsed as part of
 	 * the conditional command list...
@@ -567,7 +586,8 @@
 	else
 		syntaxerr(NULL);
 	t = tl = NULL;
-	while ((tpeek(CONTIN|KEYWORD|ESACONLY)) != c) { /* no ALIAS here */
+	/* no ALIAS here */
+	while ((tpeek(CONTIN|KEYWORD|ESACONLY)) != c) {
 		struct op *tc = casepart(c);
 		if (tl == NULL)
 			t = tl = tc, tl->right = NULL;
@@ -601,53 +621,62 @@
 	t->left = c_list(true);
 	/* Note: POSIX requires the ;; */
 	if ((tpeek(CONTIN|KEYWORD|ALIAS)) != endtok)
-		musthave(BREAK, CONTIN|KEYWORD|ALIAS);
+		switch (symbol) {
+		default:
+			syntaxerr(NULL);
+		case BREAK:
+		case BRKEV:
+		case BRKFT:
+			t->u.charflag =
+			    (symbol == BRKEV) ? '|' :
+			    (symbol == BRKFT) ? '&' : ';';
+			ACCEPT;
+		}
 	return (t);
 }
 
 static struct op *
 function_body(char *name,
-    bool ksh_func)		/* function foo { ... } vs foo() { .. } */
+    /* function foo { ... } vs foo() { .. } */
+    bool ksh_func)
 {
 	char *sname, *p;
 	struct op *t;
 	bool old_func_parse;
 
-	sname = wdstrip(name, false, false);
-	/* Check for valid characters in name. POSIX and AT&T ksh93 say only
-	 * allow [a-zA-Z_0-9] but this allows more as old pdkshs have
-	 * allowed more (the following were never allowed:
+	sname = wdstrip(name, 0);
+	/*-
+	 * Check for valid characters in name. POSIX and AT&T ksh93 say
+	 * only allow [a-zA-Z_0-9] but this allows more as old pdkshs
+	 * have allowed more; the following were never allowed:
 	 *	NUL TAB NL SP " $ & ' ( ) ; < = > \ ` |
 	 * C_QUOTE covers all but adds # * ? [ ]
 	 */
 	for (p = sname; *p; p++)
 		if (ctype(*p, C_QUOTE))
-			yyerror("%s: invalid function name\n", sname);
+			yyerror("%s: %s\n", sname, "invalid function name");
 
-	/* Note that POSIX allows only compound statements after foo(), sh and
-	 * AT&T ksh allow any command, go with the later since it shouldn't
-	 * break anything. However, for function foo, AT&T ksh only accepts
-	 * an open-brace.
+	/*
+	 * Note that POSIX allows only compound statements after foo(),
+	 * sh and AT&T ksh allow any command, go with the later since it
+	 * shouldn't break anything. However, for function foo, AT&T ksh
+	 * only accepts an open-brace.
 	 */
 	if (ksh_func) {
-		if (tpeek(CONTIN|KEYWORD|ALIAS) == '(' /* ) */) {
-			struct tbl *tp;
-
+		if (tpeek(CONTIN|KEYWORD|ALIAS) == '(' /*)*/) {
 			/* function foo () { */
 			ACCEPT;
 			musthave(')', 0);
 			/* degrade to POSIX function */
 			ksh_func = false;
-			if ((tp = ktsearch(&aliases, sname, hash(sname))))
-				ktdelete(tp);
 		}
-		musthave('{', CONTIN|KEYWORD|ALIAS); /* } */
+		musthave('{' /*}*/, CONTIN|KEYWORD|ALIAS);
 		REJECT;
 	}
 
 	t = newtp(TFUNCT);
 	t->str = sname;
-	t->u.ksh_func = ksh_func;
+	t->u.ksh_func = tobool(ksh_func);
 	t->lineno = source->line;
 
 	old_func_parse = e->flags & EF_FUNC_PARSE;
@@ -655,12 +684,13 @@
 	if ((t->left = get_command(CONTIN)) == NULL) {
 		char *tv;
 		/*
-		 * Probably something like foo() followed by eof or ;.
+		 * Probably something like foo() followed by EOF or ';'.
 		 * This is accepted by sh and ksh88.
 		 * To make "typeset -f foo" work reliably (so its output can
 		 * be used as input), we pretend there is a colon here.
 		 */
 		t->left = newtp(TCOM);
+		/* (2 * sizeof(char *)) is small enough */
 		t->left->args = alloc(2 * sizeof(char *), ATEMP);
 		t->left->args[0] = tv = alloc(3, ATEMP);
 		tv[0] = CHAR;
@@ -686,7 +716,8 @@
 	XPinit(args, 16);
 	/* POSIX does not do alias expansion here... */
 	if ((c = token(CONTIN|KEYWORD|ALIAS)) != IN) {
-		if (c != ';') /* non-POSIX, but AT&T ksh accepts a ; here */
+		if (c != ';')
+			/* non-POSIX, but AT&T ksh accepts a ; here */
 			REJECT;
 		return (NULL);
 	}
@@ -733,13 +764,13 @@
 	{ "case",	CASE,	true },
 	{ "esac",	ESAC,	true },
 	{ "for",	FOR,	true },
-	{ "select",	SELECT,	true },
+	{ Tselect,	SELECT,	true },
 	{ "while",	WHILE,	true },
 	{ "until",	UNTIL,	true },
 	{ "do",		DO,	true },
 	{ "done",	DONE,	true },
 	{ "in",		IN,	true },
-	{ "function",	FUNCTION, true },
+	{ Tfunction,	FUNCTION, true },
 	{ "time",	TIME,	true },
 	{ "{",		'{',	true },
 	{ "}",		'}',	true },
@@ -749,6 +780,8 @@
 	{ "&&",		LOGAND,	false },
 	{ "||",		LOGOR,	false },
 	{ ";;",		BREAK,	false },
+	{ ";|",		BRKEV,	false },
+	{ ";&",		BRKFT,	false },
 	{ "((",		MDPAREN, false },
 	{ "|&",		COPROC,	false },
 	/* and some special cases... */
@@ -762,8 +795,9 @@
 	struct tokeninfo const *tt;
 	struct tbl *p;
 
-	ktinit(&keywords, APERM,
-	    /* must be 80% of 2^n (currently 20 keywords) */ 32);
+	ktinit(APERM, &keywords,
+	    /* currently 28 keywords -> 80% of 64 (2^6) */
+	    6);
 	for (tt = tokentab; tt->name; tt++) {
 		if (tt->reserved) {
 			p = ktenter(&keywords, tt->name, hash(tt->name));
@@ -777,7 +811,8 @@
 static void
 syntaxerr(const char *what)
 {
-	char redir[6];	/* 2<<- is the longest redirection, I think */
+	/* 2<<- is the longest redirection, I think */
+	char redir[6];
 	const char *s;
 	struct tokeninfo const *tt;
 	int c;
@@ -796,7 +831,7 @@
 			goto Again;
 		}
 		/* don't quote the EOF */
-		yyerror("%s: unexpected EOF\n", T_synerr);
+		yyerror("%s: %s %s\n", Tsynerr, "unexpected", "EOF");
 		/* NOTREACHED */
 
 	case LWORD:
@@ -823,7 +858,7 @@
 			s = redir;
 		}
 	}
-	yyerror("%s: '%s' %s\n", T_synerr, s, what);
+	yyerror("%s: '%s' %s\n", Tsynerr, s, what);
 }
 
 static void
@@ -857,17 +892,20 @@
 }
 
 struct op *
-compile(Source *s)
+compile(Source *s, bool skiputf8bom)
 {
 	nesting.start_token = 0;
 	nesting.start_line = 0;
 	herep = heres;
 	source = s;
+	if (skiputf8bom)
+		yyskiputf8bom();
 	yyparse();
 	return (outtree);
 }
 
-/* This kludge exists to take care of sh/AT&T ksh oddity in which
+/*-
+ * This kludge exists to take care of sh/AT&T ksh oddity in which
  * the arguments of alias/export/readonly/typeset have no field
  * splitting, file globbing, or (normal) tilde expansion done.
  * AT&T ksh seems to do something similar to this since
@@ -882,10 +920,10 @@
 {
 	if (!*s)
 		return (0);
-	return ((strcmp(s, "alias") == 0) ||
+	return ((strcmp(s, Talias) == 0) ||
 	    (strcmp(s, "export") == 0) ||
 	    (strcmp(s, "readonly") == 0) ||
-	    (strcmp(s, T_typeset) == 0));
+	    (strcmp(s, Ttypeset) == 0));
 }
 
 /* Check if we are in the middle of reading an alias */
@@ -899,7 +937,8 @@
 }
 
 
-/* Order important - indexed by Test_meta values
+/*
+ * Order important - indexed by Test_meta values
  * Note that ||, &&, ( and ) can't appear in as unquoted strings
  * in normal shell input, so these can be interpreted unambiguously
  * in the evaluation pass.
@@ -952,7 +991,8 @@
 			    db_lthan : db_gthan, ATEMP);
 		} else if (uqword && (ret = test_isop(meta, ident)))
 			save = yylval.cp;
-	} else /* meta == TM_END */
+	} else
+		/* meta == TM_END */
 		ret = (uqword && !strcmp(yylval.cp,
 		    db_close)) ? TO_NONNULL : TO_NONOP;
 	if (ret != TO_NONOP) {
@@ -1002,3 +1042,96 @@
 	}
 	syntaxerr(msg);
 }
+
+#if HAVE_SELECT
+
+#ifndef EOVERFLOW
+#ifdef ERANGE
+#define EOVERFLOW	ERANGE
+#else
+#define EOVERFLOW	EINVAL
+#endif
+#endif
+
+bool
+parse_usec(const char *s, struct timeval *tv)
+{
+	struct timeval tt;
+	int i;
+
+	tv->tv_sec = 0;
+	/* parse integral part */
+	while (ksh_isdigit(*s)) {
+		tt.tv_sec = tv->tv_sec * 10 + (*s++ - '0');
+		if (tt.tv_sec / 10 != tv->tv_sec) {
+			errno = EOVERFLOW;
+			return (true);
+		}
+		tv->tv_sec = tt.tv_sec;
+	}
+
+	tv->tv_usec = 0;
+	if (!*s)
+		/* no decimal fraction */
+		return (false);
+	else if (*s++ != '.') {
+		/* junk after integral part */
+		errno = EINVAL;
+		return (true);
+	}
+
+	/* parse decimal fraction */
+	i = 100000;
+	while (ksh_isdigit(*s)) {
+		tv->tv_usec += i * (*s++ - '0');
+		if (i == 1)
+			break;
+		i /= 10;
+	}
+	/* check for junk after fractional part */
+	while (ksh_isdigit(*s))
+		++s;
+	if (*s) {
+		errno = EINVAL;
+		return (true);
+	}
+
+	/* end of input string reached, no errors */
+	return (false);
+}
+#endif
+
+/*
+ * Helper function called from within lex.c:yylex() to parse
+ * a COMSUB recursively using the main shell parser and lexer
+ */
+char *
+yyrecursive(void)
+{
+	struct op *t;
+	char *cp;
+	bool old_reject;
+	int old_symbol;
+	struct ioword **old_herep;
+
+	/* tell the lexer to accept a closing parenthesis as EOD */
+	++subshell_nesting_level;
+
+	/* push reject state, parse recursively, pop reject state */
+	old_reject = reject;
+	old_symbol = symbol;
+	ACCEPT;
+	old_herep = herep;
+	/* we use TPAREN as a helper container here */
+	t = nested(TPAREN, '(', ')');
+	herep = old_herep;
+	reject = old_reject;
+	symbol = old_symbol;
+
+	/* t->left because nested(TPAREN, ...) hides our goodies there */
+	cp = snptreef(NULL, 0, "%T", t->left);
+	tfree(t, ATEMP);
+
+	--subshell_nesting_level;
+	return (cp);
+}
diff --git a/src/tree.c b/src/tree.c
index aa861db..9ade37b 100644
--- a/src/tree.c
+++ b/src/tree.c
@@ -1,7 +1,7 @@
 /*	$OpenBSD: tree.c,v 1.19 2008/08/11 21:50:35 jaredy Exp $	*/
 
 /*-
- * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
  *	Thorsten Glaser <tg@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -22,19 +22,20 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/tree.c,v 1.30 2010/02/25 20:18:19 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/tree.c,v 1.51 2011/09/07 15:24:21 tg Exp $");
 
-#define INDENT	4
+#define INDENT	8
 
-#define tputc(c, shf) shf_putchar(c, shf);
 static void ptree(struct op *, int, struct shf *);
 static void pioact(struct shf *, int, struct ioword *);
-static void tputC(int, struct shf *);
-static void tputS(char *, struct shf *);
+static const char *wdvarput(struct shf *, const char *, int, int);
 static void vfptreef(struct shf *, int, const char *, va_list);
 static struct ioword **iocopy(struct ioword **, Area *);
 static void iofree(struct ioword **, Area *);
 
+/* "foo& ; bar" and "foo |& ; bar" are invalid */
+static bool prevent_semicolon;
+
 /*
  * print a command tree
  */
@@ -44,22 +45,26 @@
 	const char **w;
 	struct ioword **ioact;
 	struct op *t1;
+	int i;
 
  Chain:
 	if (t == NULL)
 		return;
 	switch (t->type) {
 	case TCOM:
-		if (t->vars)
-			for (w = (const char **)t->vars; *w != NULL; )
+		if (t->vars) {
+			w = (const char **)t->vars;
+			while (*w)
 				fptreef(shf, indent, "%S ", *w++);
-		else
+		} else
 			shf_puts("#no-vars# ", shf);
-		if (t->args)
-			for (w = t->args; *w != NULL; )
+		if (t->args) {
+			w = t->args;
+			while (*w)
 				fptreef(shf, indent, "%S ", *w++);
-		else
+		} else
 			shf_puts("#no-args# ", shf);
+		prevent_semicolon = false;
 		break;
 	case TEXEC:
 		t = t->left;
@@ -78,30 +83,28 @@
 	case TOR:
 	case TAND:
 		fptreef(shf, indent, "%T%s %T",
-		    t->left, (t->type==TOR) ? "||" : "&&", t->right);
+		    t->left, (t->type == TOR) ? "||" : "&&", t->right);
 		break;
 	case TBANG:
 		shf_puts("! ", shf);
+		prevent_semicolon = false;
 		t = t->right;
 		goto Chain;
-	case TDBRACKET: {
-		int i;
-
+	case TDBRACKET:
+		w = t->args;
 		shf_puts("[[", shf);
-		for (i = 0; t->args[i]; i++)
-			fptreef(shf, indent, " %S", t->args[i]);
+		while (*w)
+			fptreef(shf, indent, " %S", *w++);
 		shf_puts(" ]] ", shf);
 		break;
-	}
 	case TSELECT:
-		fptreef(shf, indent, "select %s ", t->str);
-		/* FALLTHROUGH */
 	case TFOR:
-		if (t->type == TFOR)
-			fptreef(shf, indent, "for %s ", t->str);
+		fptreef(shf, indent, "%s %s ",
+		    (t->type == TFOR) ? "for" : Tselect, t->str);
 		if (t->vars != NULL) {
 			shf_puts("in ", shf);
-			for (w = (const char **)t->vars; *w; )
+			w = (const char **)t->vars;
+			while (*w)
 				fptreef(shf, indent, "%S ", *w++);
 			fptreef(shf, indent, "%;");
 		}
@@ -112,34 +115,43 @@
 		fptreef(shf, indent, "case %S in", t->str);
 		for (t1 = t->left; t1 != NULL; t1 = t1->right) {
 			fptreef(shf, indent, "%N(");
-			for (w = (const char **)t1->vars; *w != NULL; w++)
+			w = (const char **)t1->vars;
+			while (*w) {
 				fptreef(shf, indent, "%S%c", *w,
 				    (w[1] != NULL) ? '|' : ')');
-			fptreef(shf, indent + INDENT, "%;%T%N;;", t1->left);
+				++w;
+			}
+			fptreef(shf, indent + INDENT, "%N%T%N;%c", t1->left,
+			    t1->u.charflag);
 		}
 		fptreef(shf, indent, "%Nesac ");
 		break;
-	case TIF:
+#ifndef MKSH_NO_DEPRECATED_WARNING
 	case TELIF:
-		/* 3 == strlen("if ") */
-		fptreef(shf, indent + 3, "if %T", t->left);
-		for (;;) {
+		internal_errorf("TELIF in tree.c:ptree() unexpected");
+		/* FALLTHROUGH */
+#endif
+	case TIF:
+		i = 2;
+		goto process_TIF;
+		do {
+			t = t->right;
+			i = 0;
+			fptreef(shf, indent, "%;");
+ process_TIF:
+			/* 5 == strlen("elif ") */
+			fptreef(shf, indent + 5 - i, "elif %T" + i, t->left);
 			t = t->right;
 			if (t->left != NULL) {
 				fptreef(shf, indent, "%;");
-				fptreef(shf, indent + INDENT, "then%N%T",
-				    t->left);
+				fptreef(shf, indent + INDENT, "%s%N%T",
+				    "then", t->left);
 			}
-			if (t->right == NULL || t->right->type != TELIF)
-				break;
-			t = t->right;
-			fptreef(shf, indent, "%;");
-			/* 5 == strlen("elif ") */
-			fptreef(shf, indent + 5, "elif %T", t->left);
-		}
+		} while (t->right && t->right->type == TELIF);
 		if (t->right != NULL) {
 			fptreef(shf, indent, "%;");
-			fptreef(shf, indent + INDENT, "else%;%T", t->right);
+			fptreef(shf, indent + INDENT, "%s%N%T",
+			    "else", t->right);
 		}
 		fptreef(shf, indent, "%;fi ");
 		break;
@@ -147,60 +159,63 @@
 	case TUNTIL:
 		/* 6 == strlen("while"/"until") */
 		fptreef(shf, indent + 6, "%s %T",
-		    (t->type==TWHILE) ? "while" : "until",
+		    (t->type == TWHILE) ? "while" : "until",
 		    t->left);
-		fptreef(shf, indent, "%;do");
-		fptreef(shf, indent + INDENT, "%;%T", t->right);
+		fptreef(shf, indent, "%;");
+		fptreef(shf, indent + INDENT, "do%N%T", t->right);
 		fptreef(shf, indent, "%;done ");
 		break;
 	case TBRACE:
-		fptreef(shf, indent + INDENT, "{%;%T", t->left);
+		fptreef(shf, indent + INDENT, "{%N%T", t->left);
 		fptreef(shf, indent, "%;} ");
 		break;
 	case TCOPROC:
 		fptreef(shf, indent, "%T|& ", t->left);
+		prevent_semicolon = true;
 		break;
 	case TASYNC:
 		fptreef(shf, indent, "%T& ", t->left);
+		prevent_semicolon = true;
 		break;
 	case TFUNCT:
-		fptreef(shf, indent,
-		    t->u.ksh_func ? "function %s %T" : "%s() %T",
-		    t->str, t->left);
+		fpFUNCTf(shf, indent, tobool(t->u.ksh_func), t->str, t->left);
 		break;
 	case TTIME:
-		fptreef(shf, indent, "time %T", t->left);
+		fptreef(shf, indent, "%s %T", "time", t->left);
 		break;
 	default:
 		shf_puts("<botch>", shf);
+		prevent_semicolon = false;
 		break;
 	}
 	if ((ioact = t->ioact) != NULL) {
-		int	need_nl = 0;
+		bool need_nl = false;
 
 		while (*ioact != NULL)
 			pioact(shf, indent, *ioact++);
 		/* Print here documents after everything else... */
-		for (ioact = t->ioact; *ioact != NULL; ) {
+		ioact = t->ioact;
+		while (*ioact != NULL) {
 			struct ioword *iop = *ioact++;
 
-			/* heredoc is 0 when tracing (set -x) */
-			if ((iop->flag & IOTYPE) == IOHERE && iop->heredoc &&
-			    /* iop->delim[1] == '<' means here string */
-			    (!iop->delim || iop->delim[1] != '<')) {
-				tputc('\n', shf);
+			/* heredoc is NULL when tracing (set -x) */
+			if ((iop->flag & (IOTYPE | IOHERESTR)) == IOHERE &&
+			    iop->heredoc) {
+				shf_putc('\n', shf);
 				shf_puts(iop->heredoc, shf);
 				fptreef(shf, indent, "%s",
+				    iop->flag & IONDELIM ? "<<" :
 				    evalstr(iop->delim, 0));
-				need_nl = 1;
+				need_nl = true;
 			}
 		}
-		/* Last delimiter must be followed by a newline (this often
-		 * leads to an extra blank line, but its not worth worrying
-		 * about)
+		/*
+		 * Last delimiter must be followed by a newline (this
+		 * often leads to an extra blank line, but it's not
+		 * worth worrying about)
 		 */
 		if (need_nl)
-			tputc('\n', shf);
+			shf_putc('\n', shf);
 	}
 }
 
@@ -220,123 +235,136 @@
 
 	switch (type) {
 	case IOREAD:
-		shf_puts("< ", shf);
+		shf_puts("<", shf);
 		break;
 	case IOHERE:
 		shf_puts(flag & IOSKIP ? "<<-" : "<<", shf);
 		break;
 	case IOCAT:
-		shf_puts(">> ", shf);
+		shf_puts(">>", shf);
 		break;
 	case IOWRITE:
-		shf_puts(flag & IOCLOB ? ">| " : "> ", shf);
+		shf_puts(flag & IOCLOB ? ">|" : ">", shf);
 		break;
 	case IORDWR:
-		shf_puts("<> ", shf);
+		shf_puts("<>", shf);
 		break;
 	case IODUP:
 		shf_puts(flag & IORDUP ? "<&" : ">&", shf);
 		break;
 	}
-	/* name/delim are 0 when printing syntax errors */
+	/* name/delim are NULL when printing syntax errors */
 	if (type == IOHERE) {
 		if (iop->delim)
-			fptreef(shf, indent, "%s%S ",
-			    /* here string */ iop->delim[1] == '<' ? "" : " ",
-			    iop->delim);
+			fptreef(shf, indent, "%S ", iop->delim);
 		else
-			tputc(' ', shf);
+			shf_putc(' ', shf);
 	} else if (iop->name)
 		fptreef(shf, indent, (iop->flag & IONAMEXP) ? "%s " : "%S ",
 		    iop->name);
+	prevent_semicolon = false;
 }
 
-
-/*
- * variants of fputc, fputs for ptreef and snptreef
- */
-static void
-tputC(int c, struct shf *shf)
+/* variant of fputs for ptreef and wdstrip */
+static const char *
+wdvarput(struct shf *shf, const char *wp, int quotelevel, int opmode)
 {
-	if ((c&0x60) == 0) {		/* C0|C1 */
-		tputc((c&0x80) ? '$' : '^', shf);
-		tputc(((c&0x7F)|0x40), shf);
-	} else if ((c&0x7F) == 0x7F) {	/* DEL */
-		tputc((c&0x80) ? '$' : '^', shf);
-		tputc('?', shf);
-	} else
-		tputc(c, shf);
-}
+	int c;
 
-static void
-tputS(char *wp, struct shf *shf)
-{
-	int c, quotelevel = 0;
-
-	/* problems:
+	/*-
+	 * problems:
 	 *	`...` -> $(...)
 	 *	'foo' -> "foo"
+	 *	x${foo:-"hi"} -> x${foo:-hi} unless WDS_TPUTS
+	 *	x${foo:-'hi'} -> x${foo:-hi} unless WDS_KEEPQ
 	 * could change encoding to:
 	 *	OQUOTE ["'] ... CQUOTE ["']
 	 *	COMSUB [(`] ...\0	(handle $ ` \ and maybe " in `...` case)
 	 */
-	while (1)
+	while (/* CONSTCOND */ 1)
 		switch (*wp++) {
 		case EOS:
-			return;
+			return (--wp);
 		case ADELIM:
 		case CHAR:
-			tputC(*wp++, shf);
-			break;
-		case QCHAR:
 			c = *wp++;
-			if (!quotelevel || (c == '"' || c == '`' || c == '$'))
-				tputc('\\', shf);
-			tputC(c, shf);
+			if ((opmode & WDS_MAGIC) &&
+			    (ISMAGIC(c) || c == '[' || c == NOT ||
+			    c == '-' || c == ']' || c == '*' || c == '?'))
+				shf_putc(MAGIC, shf);
+			shf_putc(c, shf);
 			break;
+		case QCHAR: {
+			bool doq;
+
+			c = *wp++;
+			doq = (c == '"' || c == '`' || c == '$' || c == '\\');
+			if (opmode & WDS_TPUTS) {
+				if (quotelevel == 0)
+					doq = true;
+			} else {
+				if (!(opmode & WDS_KEEPQ))
+					doq = false;
+			}
+			if (doq)
+				shf_putc('\\', shf);
+			shf_putc(c, shf);
+			break;
+		}
 		case COMSUB:
 			shf_puts("$(", shf);
-			while (*wp != 0)
-				tputC(*wp++, shf);
-			tputc(')', shf);
-			wp++;
+			while ((c = *wp++) != 0)
+				shf_putc(c, shf);
+			shf_putc(')', shf);
 			break;
 		case EXPRSUB:
 			shf_puts("$((", shf);
-			while (*wp != 0)
-				tputC(*wp++, shf);
+			while ((c = *wp++) != 0)
+				shf_putc(c, shf);
 			shf_puts("))", shf);
-			wp++;
 			break;
 		case OQUOTE:
-			quotelevel++;
-			tputc('"', shf);
+			if (opmode & WDS_TPUTS) {
+				quotelevel++;
+				shf_putc('"', shf);
+			}
 			break;
 		case CQUOTE:
-			if (quotelevel)
-				quotelevel--;
-			tputc('"', shf);
+			if (opmode & WDS_TPUTS) {
+				if (quotelevel)
+					quotelevel--;
+				shf_putc('"', shf);
+			}
 			break;
 		case OSUBST:
-			tputc('$', shf);
+			shf_putc('$', shf);
 			if (*wp++ == '{')
-				tputc('{', shf);
+				shf_putc('{', shf);
 			while ((c = *wp++) != 0)
-				tputC(c, shf);
+				shf_putc(c, shf);
+			wp = wdvarput(shf, wp, 0, opmode);
 			break;
 		case CSUBST:
 			if (*wp++ == '}')
-				tputc('}', shf);
-			break;
+				shf_putc('}', shf);
+			return (wp);
 		case OPAT:
-			tputc(*wp++, shf);
-			tputc('(', shf);
+			if (opmode & WDS_MAGIC) {
+				shf_putc(MAGIC, shf);
+				shf_putchar(*wp++ | 0x80, shf);
+			} else {
+				shf_putchar(*wp++, shf);
+				shf_putc('(', shf);
+			}
 			break;
 		case SPAT:
-			tputc('|', shf);
-			break;
+			c = '|';
+			if (0)
 		case CPAT:
-			tputc(')', shf);
+				c = /*(*/ ')';
+			if (opmode & WDS_MAGIC)
+				shf_putc(MAGIC, shf);
+			shf_putc(c, shf);
 			break;
 		}
 }
@@ -346,21 +374,19 @@
  * variable args with an ANSI compiler
  */
 /* VARARGS */
-int
+void
 fptreef(struct shf *shf, int indent, const char *fmt, ...)
 {
 	va_list va;
 
 	va_start(va, fmt);
-
 	vfptreef(shf, indent, fmt, va);
 	va_end(va);
-	return (0);
 }
 
 /* VARARGS */
 char *
-snptreef(char *s, int n, const char *fmt, ...)
+snptreef(char *s, ssize_t n, const char *fmt, ...)
 {
 	va_list va;
 	struct shf shf;
@@ -371,7 +397,8 @@
 	vfptreef(&shf, 0, fmt, va);
 	va_end(va);
 
-	return (shf_sclose(&shf)); /* null terminates */
+	/* shf_sclose NUL terminates */
+	return (shf_sclose(&shf));
 }
 
 static void
@@ -383,48 +410,63 @@
 		if (c == '%') {
 			switch ((c = *fmt++)) {
 			case 'c':
-				tputc(va_arg(va, int), shf);
+				/* character (octet, probably) */
+				shf_putchar(va_arg(va, int), shf);
 				break;
 			case 's':
+				/* string */
 				shf_puts(va_arg(va, char *), shf);
 				break;
-			case 'S':	/* word */
-				tputS(va_arg(va, char *), shf);
+			case 'S':
+				/* word */
+				wdvarput(shf, va_arg(va, char *), 0, WDS_TPUTS);
 				break;
-			case 'd':	/* decimal */
+			case 'd':
+				/* signed decimal */
 				shf_fprintf(shf, "%d", va_arg(va, int));
 				break;
-			case 'u':	/* decimal */
+			case 'u':
+				/* unsigned decimal */
 				shf_fprintf(shf, "%u", va_arg(va, unsigned int));
 				break;
-			case 'T':	/* format tree */
+			case 'T':
+				/* format tree */
 				ptree(va_arg(va, struct op *), indent, shf);
-				break;
-			case ';':	/* newline or ; */
-			case 'N':	/* newline or space */
+				goto dont_trash_prevent_semicolon;
+			case ';':
+				/* newline or ; */
+			case 'N':
+				/* newline or space */
 				if (shf->flags & SHF_STRING) {
-					if (c == ';')
-						tputc(';', shf);
-					tputc(' ', shf);
+					if (c == ';' && !prevent_semicolon)
+						shf_putc(';', shf);
+					shf_putc(' ', shf);
 				} else {
 					int i;
 
-					tputc('\n', shf);
-					for (i = indent; i >= 8; i -= 8)
-						tputc('\t', shf);
-					for (; i > 0; --i)
-						tputc(' ', shf);
+					shf_putc('\n', shf);
+					i = indent;
+					while (i >= 8) {
+						shf_putc('\t', shf);
+						i -= 8;
+					}
+					while (i--)
+						shf_putc(' ', shf);
 				}
 				break;
 			case 'R':
+				/* I/O redirection */
 				pioact(shf, indent, va_arg(va, struct ioword *));
 				break;
 			default:
-				tputc(c, shf);
+				shf_putc(c, shf);
 				break;
 			}
 		} else
-			tputc(c, shf);
+			shf_putc(c, shf);
+		prevent_semicolon = false;
+ dont_trash_prevent_semicolon:
+		;
 	}
 }
 
@@ -454,11 +496,13 @@
 	if (t->vars == NULL)
 		r->vars = NULL;
 	else {
-		for (tw = (const char **)t->vars; *tw++ != NULL; )
-			;
-		rw = r->vars = alloc((tw - (const char **)t->vars + 1) *
+		tw = (const char **)t->vars;
+		while (*tw)
+			++tw;
+		rw = r->vars = alloc2(tw - (const char **)t->vars + 1,
 		    sizeof(*tw), ap);
-		for (tw = (const char **)t->vars; *tw != NULL; )
+		tw = (const char **)t->vars;
+		while (*tw)
 			*rw++ = wdcopy(*tw++, ap);
 		*rw = NULL;
 	}
@@ -466,11 +510,13 @@
 	if (t->args == NULL)
 		r->args = NULL;
 	else {
-		for (tw = t->args; *tw++ != NULL; )
-			;
-		r->args = (const char **)(rw = alloc((tw - t->args + 1) *
+		tw = t->args;
+		while (*tw)
+			++tw;
+		r->args = (const char **)(rw = alloc2(tw - t->args + 1,
 		    sizeof(*tw), ap));
-		for (tw = t->args; *tw != NULL; )
+		tw = t->args;
+		while (*tw)
 			*rw++ = wdcopy(*tw++, ap);
 		*rw = NULL;
 	}
@@ -487,7 +533,9 @@
 char *
 wdcopy(const char *wp, Area *ap)
 {
-	size_t len = wdscan(wp, EOS) - wp;
+	size_t len;
+
+	len = wdscan(wp, EOS) - wp;
 	return (memcpy(alloc(len, ap), wp, len));
 }
 
@@ -497,7 +545,7 @@
 {
 	int nest = 0;
 
-	while (1)
+	while (/* CONSTCOND */ 1)
 		switch (*wp++) {
 		case EOS:
 			return (wp);
@@ -546,88 +594,19 @@
 		}
 }
 
-/* return a copy of wp without any of the mark up characters and
- * with quote characters (" ' \) stripped.
- * (string is allocated from ATEMP)
+/*
+ * return a copy of wp without any of the mark up characters and with
+ * quote characters (" ' \) stripped. (string is allocated from ATEMP)
  */
 char *
-wdstrip(const char *wp, bool keepq, bool make_magic)
+wdstrip(const char *wp, int opmode)
 {
 	struct shf shf;
-	int c;
 
 	shf_sopen(NULL, 32, SHF_WR | SHF_DYNAMIC, &shf);
-
-	/* problems:
-	 *	`...` -> $(...)
-	 *	x${foo:-"hi"} -> x${foo:-hi}
-	 *	x${foo:-'hi'} -> x${foo:-hi} unless keepq
-	 */
-	while (1)
-		switch (*wp++) {
-		case EOS:
-			return (shf_sclose(&shf)); /* null terminates */
-		case ADELIM:
-		case CHAR:
-			c = *wp++;
-			if (make_magic && (ISMAGIC(c) || c == '[' || c == NOT ||
-			    c == '-' || c == ']' || c == '*' || c == '?'))
-				shf_putchar(MAGIC, &shf);
-			shf_putchar(c, &shf);
-			break;
-		case QCHAR:
-			c = *wp++;
-			if (keepq && (c == '"' || c == '`' || c == '$' || c == '\\'))
-				shf_putchar('\\', &shf);
-			shf_putchar(c, &shf);
-			break;
-		case COMSUB:
-			shf_puts("$(", &shf);
-			while (*wp != 0)
-				shf_putchar(*wp++, &shf);
-			shf_putchar(')', &shf);
-			break;
-		case EXPRSUB:
-			shf_puts("$((", &shf);
-			while (*wp != 0)
-				shf_putchar(*wp++, &shf);
-			shf_puts("))", &shf);
-			break;
-		case OQUOTE:
-			break;
-		case CQUOTE:
-			break;
-		case OSUBST:
-			shf_putchar('$', &shf);
-			if (*wp++ == '{')
-			    shf_putchar('{', &shf);
-			while ((c = *wp++) != 0)
-				shf_putchar(c, &shf);
-			break;
-		case CSUBST:
-			if (*wp++ == '}')
-				shf_putchar('}', &shf);
-			break;
-		case OPAT:
-			if (make_magic) {
-				shf_putchar(MAGIC, &shf);
-				shf_putchar(*wp++ | 0x80, &shf);
-			} else {
-				shf_putchar(*wp++, &shf);
-				shf_putchar('(', &shf);
-			}
-			break;
-		case SPAT:
-			if (make_magic)
-				shf_putchar(MAGIC, &shf);
-			shf_putchar('|', &shf);
-			break;
-		case CPAT:
-			if (make_magic)
-				shf_putchar(MAGIC, &shf);
-			shf_putchar(')', &shf);
-			break;
-		}
+	wdvarput(&shf, wp, 0, opmode);
+	/* shf_sclose NUL terminates */
+	return (shf_sclose(&shf));
 }
 
 static struct ioword **
@@ -636,9 +615,10 @@
 	struct ioword **ior;
 	int i;
 
-	for (ior = iow; *ior++ != NULL; )
-		;
-	ior = alloc((ior - iow + 1) * sizeof(struct ioword *), ap);
+	ior = iow;
+	while (*ior)
+		++ior;
+	ior = alloc2(ior - iow + 1, sizeof(struct ioword *), ap);
 
 	for (i = 0; iow[i] != NULL; i++) {
 		struct ioword *p, *q;
@@ -680,8 +660,9 @@
 	}
 
 	if (t->args != NULL) {
+		/*XXX we assume the caller is right */
 		union mksh_ccphack cw;
-		/* XXX we assume the caller is right */
+
 		cw.ro = t->args;
 		for (w = cw.rw; *w != NULL; w++)
 			afree(*w, ap);
@@ -703,7 +684,8 @@
 	struct ioword **iop;
 	struct ioword *p;
 
-	for (iop = iow; (p = *iop++) != NULL; ) {
+	iop = iow;
+	while ((p = *iop++) != NULL) {
 		if (p->name != NULL)
 			afree(p->name, ap);
 		if (p->delim != NULL)
@@ -714,3 +696,315 @@
 	}
 	afree(iow, ap);
 }
+
+void
+fpFUNCTf(struct shf *shf, int i, bool isksh, const char *k, struct op *v)
+{
+	if (isksh)
+		fptreef(shf, i, "%s %s %T", Tfunction, k, v);
+	else
+		fptreef(shf, i, "%s() %T", k, v);
+}
+
+
+/* for jobs.c */
+void
+vistree(char *dst, size_t sz, struct op *t)
+{
+	int c;
+	char *cp, *buf;
+
+	buf = alloc(sz, ATEMP);
+	snptreef(buf, sz, "%T", t);
+	cp = buf;
+	while ((c = *cp++)) {
+		if (((c & 0x60) == 0) || ((c & 0x7F) == 0x7F)) {
+			/* C0 or C1 control character or DEL */
+			if (!--sz)
+				break;
+			*dst++ = (c & 0x80) ? '$' : '^';
+			c = (c & 0x7F) ^ 0x40;
+		}
+		if (!--sz)
+			break;
+		*dst++ = c;
+	}
+	*dst = '\0';
+	afree(buf, ATEMP);
+}
+
+#ifdef DEBUG
+void
+dumpchar(struct shf *shf, int c)
+{
+	if (((c & 0x60) == 0) || ((c & 0x7F) == 0x7F)) {
+		/* C0 or C1 control character or DEL */
+		shf_putc((c & 0x80) ? '$' : '^', shf);
+		c = (c & 0x7F) ^ 0x40;
+	}
+	shf_putc(c, shf);
+}
+
+/* see: wdvarput */
+static const char *
+dumpwdvar_(struct shf *shf, const char *wp, int quotelevel)
+{
+	int c;
+
+	while (/* CONSTCOND */ 1) {
+		switch(*wp++) {
+		case EOS:
+			shf_puts("EOS", shf);
+			return (--wp);
+		case ADELIM:
+			shf_puts("ADELIM=", shf);
+			if (0)
+		case CHAR:
+				shf_puts("CHAR=", shf);
+			dumpchar(shf, *wp++);
+			break;
+		case QCHAR:
+			shf_puts("QCHAR<", shf);
+			c = *wp++;
+			if (quotelevel == 0 ||
+			    (c == '"' || c == '`' || c == '$' || c == '\\'))
+				shf_putc('\\', shf);
+			dumpchar(shf, c);
+			goto closeandout;
+		case COMSUB:
+			shf_puts("COMSUB<", shf);
+ dumpsub:
+			while ((c = *wp++) != 0)
+				dumpchar(shf, c);
+ closeandout:
+			shf_putc('>', shf);
+			break;
+		case EXPRSUB:
+			shf_puts("EXPRSUB<", shf);
+			goto dumpsub;
+		case OQUOTE:
+			shf_fprintf(shf, "OQUOTE{%d", ++quotelevel);
+			break;
+		case CQUOTE:
+			shf_fprintf(shf, "%d}CQUOTE", quotelevel);
+			if (quotelevel)
+				quotelevel--;
+			else
+				shf_puts("(err)", shf);
+			break;
+		case OSUBST:
+			shf_puts("OSUBST(", shf);
+			dumpchar(shf, *wp++);
+			shf_puts(")[", shf);
+			while ((c = *wp++) != 0)
+				dumpchar(shf, c);
+			shf_putc('|', shf);
+			wp = dumpwdvar_(shf, wp, 0);
+			break;
+		case CSUBST:
+			shf_puts("]CSUBST(", shf);
+			dumpchar(shf, *wp++);
+			shf_putc(')', shf);
+			return (wp);
+		case OPAT:
+			shf_puts("OPAT=", shf);
+			dumpchar(shf, *wp++);
+			break;
+		case SPAT:
+			shf_puts("SPAT", shf);
+			break;
+		case CPAT:
+			shf_puts("CPAT", shf);
+			break;
+		default:
+			shf_fprintf(shf, "INVAL<%u>", (uint8_t)wp[-1]);
+			break;
+		}
+		shf_putc(' ', shf);
+	}
+}
+void
+dumpwdvar(struct shf *shf, const char *wp)
+{
+	dumpwdvar_(shf, wp, 0);
+}
+
+void
+dumptree(struct shf *shf, struct op *t)
+{
+	int i;
+	const char **w, *name;
+	struct op *t1;
+	static int nesting = 0;
+
+	for (i = 0; i < nesting; ++i)
+		shf_putc('\t', shf);
+	++nesting;
+	shf_puts("{tree:" /*}*/, shf);
+	if (t == NULL) {
+		name = "(null)";
+		goto out;
+	}
+	switch (t->type) {
+#define OPEN(x) case x: name = #x; shf_puts(" {" #x ":", shf); /*}*/
+
+	OPEN(TCOM)
+		if (t->vars) {
+			i = 0;
+			w = (const char **)t->vars;
+			while (*w) {
+				shf_putc('\n', shf);
+				for (int j = 0; j < nesting; ++j)
+					shf_putc('\t', shf);
+				shf_fprintf(shf, " var%d<", i++);
+				dumpwdvar(shf, *w++);
+				shf_putc('>', shf);
+			}
+		} else
+			shf_puts(" #no-vars#", shf);
+		if (t->args) {
+			i = 0;
+			w = t->args;
+			while (*w) {
+				shf_putc('\n', shf);
+				for (int j = 0; j < nesting; ++j)
+					shf_putc('\t', shf);
+				shf_fprintf(shf, " arg%d<", i++);
+				dumpwdvar(shf, *w++);
+				shf_putc('>', shf);
+			}
+		} else
+			shf_puts(" #no-args#", shf);
+		break;
+	OPEN(TEXEC)
+ dumpleftandout:
+		t = t->left;
+ dumpandout:
+		shf_putc('\n', shf);
+		dumptree(shf, t);
+		break;
+	OPEN(TPAREN)
+		goto dumpleftandout;
+	OPEN(TPIPE)
+ dumpleftmidrightandout:
+		shf_putc('\n', shf);
+		dumptree(shf, t->left);
+/* middumprightandout: (unused) */
+		shf_fprintf(shf, "/%s:", name);
+ dumprightandout:
+		t = t->right;
+		goto dumpandout;
+	OPEN(TLIST)
+		goto dumpleftmidrightandout;
+	OPEN(TOR)
+		goto dumpleftmidrightandout;
+	OPEN(TAND)
+		goto dumpleftmidrightandout;
+	OPEN(TBANG)
+		goto dumprightandout;
+	OPEN(TDBRACKET)
+		i = 0;
+		w = t->args;
+		while (*w) {
+			shf_putc('\n', shf);
+			for (int j = 0; j < nesting; ++j)
+				shf_putc('\t', shf);
+			shf_fprintf(shf, " arg%d<", i++);
+			dumpwdvar(shf, *w++);
+			shf_putc('>', shf);
+		}
+		break;
+	OPEN(TFOR)
+ dumpfor:
+		shf_fprintf(shf, " str<%s>", t->str);
+		if (t->vars != NULL) {
+			i = 0;
+			w = (const char **)t->vars;
+			while (*w) {
+				shf_putc('\n', shf);
+				for (int j = 0; j < nesting; ++j)
+					shf_putc('\t', shf);
+				shf_fprintf(shf, " var%d<", i++);
+				dumpwdvar(shf, *w++);
+				shf_putc('>', shf);
+			}
+		}
+		goto dumpleftandout;
+	OPEN(TSELECT)
+		goto dumpfor;
+	OPEN(TCASE)
+		shf_fprintf(shf, " str<%s>", t->str);
+		i = 0;
+		for (t1 = t->left; t1 != NULL; t1 = t1->right) {
+			shf_putc('\n', shf);
+			for (int j = 0; j < nesting; ++j)
+				shf_putc('\t', shf);
+			shf_fprintf(shf, " sub%d[(", i);
+			w = (const char **)t1->vars;
+			while (*w) {
+				dumpwdvar(shf, *w);
+				if (w[1] != NULL)
+					shf_putc('|', shf);
+				++w;
+			}
+			shf_putc(')', shf);
+			shf_putc('\n', shf);
+			dumptree(shf, t1->left);
+			shf_fprintf(shf, " ;%c/%d]", t1->u.charflag, i++);
+		}
+		break;
+	OPEN(TWHILE)
+		goto dumpleftmidrightandout;
+	OPEN(TUNTIL)
+		goto dumpleftmidrightandout;
+	OPEN(TBRACE)
+		goto dumpleftandout;
+	OPEN(TCOPROC)
+		goto dumpleftandout;
+	OPEN(TASYNC)
+		goto dumpleftandout;
+	OPEN(TFUNCT)
+		shf_fprintf(shf, " str<%s> ksh<%s>", t->str,
+		    t->u.ksh_func ? "yes" : "no");
+		goto dumpleftandout;
+	OPEN(TTIME)
+		goto dumpleftandout;
+	OPEN(TIF)
+ dumpif:
+		shf_putc('\n', shf);
+		dumptree(shf, t->left);
+		t = t->right;
+		if (t->left != NULL) {
+			shf_puts(" /TTHEN:\n", shf);
+			dumptree(shf, t->left);
+		}
+		if (t->right && t->right->type == TELIF) {
+			shf_puts(" /TELIF:", shf);
+			t = t->right;
+			goto dumpif;
+		}
+		if (t->right != NULL) {
+			shf_puts(" /TELSE:\n", shf);
+			dumptree(shf, t->right);
+		}
+		break;
+	OPEN(TEOF)
+ dumpunexpected:
+		shf_puts("unexpected", shf);
+		break;
+	OPEN(TELIF)
+		goto dumpunexpected;
+	OPEN(TPAT)
+		goto dumpunexpected;
+	default:
+		name = "TINVALID";
+		shf_fprintf(shf, "{T<%d>:" /*}*/, t->type);
+		goto dumpunexpected;
+
+#undef OPEN
+	}
+ out:
+	shf_fprintf(shf, /*{*/ " /%s}\n", name);
+	--nesting;
+}
+#endif
diff --git a/src/var.c b/src/var.c
index 4e9729e..315294e 100644
--- a/src/var.c
+++ b/src/var.c
@@ -1,7 +1,7 @@
 /*	$OpenBSD: var.c,v 1.34 2007/10/15 02:16:35 deraadt Exp $	*/
 
 /*-
- * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
  *	Thorsten Glaser <tg@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -26,9 +26,9 @@
 #include <sys/sysctl.h>
 #endif
 
-__RCSID("$MirOS: src/bin/mksh/var.c,v 1.110 2010/07/25 11:35:43 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/var.c,v 1.132 2011/09/07 15:24:22 tg Exp $");
 
-/*
+/*-
  * Variables
  *
  * WARNING: unreadable code, needs a rewrite
@@ -37,8 +37,11 @@
  * otherwise, (val.s + type) contains string value.
  * if (flag&EXPORT), val.s contains "name=value" for E-Z exporting.
  */
+
 static struct tbl vtemp;
 static struct table specials;
+static uint32_t lcg_state = 5381;
+
 static char *formatstr(struct tbl *, const char *);
 static void exportprep(struct tbl *, const char *);
 static int special(const char *);
@@ -47,14 +50,7 @@
 static void setspec(struct tbl *);
 static void unsetspec(struct tbl *);
 static int getint(struct tbl *, mksh_ari_t *, bool);
-static mksh_ari_t intval(struct tbl *);
-static struct tbl *arraysearch(struct tbl *, uint32_t);
 static const char *array_index_calc(const char *, bool *, uint32_t *);
-static uint32_t oaathash_update(register uint32_t, register const uint8_t *,
-    register size_t);
-static uint32_t oaathash_finalise(register uint32_t);
-
-uint8_t set_refflag = 0;
 
 /*
  * create a new block for function calls and simple commands
@@ -68,7 +64,8 @@
 
 	l = alloc(sizeof(struct block), ATEMP);
 	l->flags = 0;
-	ainit(&l->area); /* todo: could use e->area (l->area => l->areap) */
+	/* TODO: could use e->area (l->area => l->areap) */
+	ainit(&l->area);
 	if (!e->loc) {
 		l->argc = 0;
 		l->argv = empty;
@@ -77,8 +74,8 @@
 		l->argv = e->loc->argv;
 	}
 	l->exit = l->error = NULL;
-	ktinit(&l->vars, &l->area, 0);
-	ktinit(&l->funs, &l->area, 0);
+	ktinit(&l->area, &l->vars, 0);
+	ktinit(&l->area, &l->funs, 0);
 	l->next = e->loc;
 	e->loc = l;
 }
@@ -89,12 +86,15 @@
 void
 popblock(void)
 {
+	ssize_t i;
 	struct block *l = e->loc;
 	struct tbl *vp, **vpp = l->vars.tbls, *vq;
-	int i;
 
-	e->loc = l->next;	/* pop block */
-	for (i = l->vars.size; --i >= 0; )
+	/* pop block */
+	e->loc = l->next;
+
+	i = 1 << (l->vars.tshift);
+	while (--i >= 0)
 		if ((vp = *vpp++) != NULL && (vp->flag&SPECIAL)) {
 			if ((vq = global(vp->name))->flag & ISSET)
 				setspec(vq);
@@ -117,6 +117,7 @@
 	V_MAX
 };
 
+/* this is biased with -1 relative to VARSPEC_ENUMS */
 static const char * const initvar_names[] = {
 #define VARSPEC_ITEMS
 #include "var_spec.h"
@@ -128,8 +129,9 @@
 	int i = 0;
 	struct tbl *tp;
 
-	ktinit(&specials, APERM,
-	    /* must be 80% of 2^n (currently 12 specials) */ 16);
+	ktinit(APERM, &specials,
+	    /* currently 12 specials -> 80% of 16 (2^4) */
+	    4);
 	while (i < V_MAX - 1) {
 		tp = ktenter(&specials, initvar_names[i],
 		    hash(initvar_names[i]));
@@ -138,32 +140,49 @@
 	}
 }
 
-/* Used to calculate an array index for global()/local(). Sets *arrayp to
- * true if this is an array, sets *valp to the array index, returns
+/* common code for several functions below */
+static struct block *
+varsearch(struct block *l, struct tbl **vpp, const char *vn, uint32_t h)
+{
+	register struct tbl *vp;
+
+	if (l) {
+ varsearch_loop:
+		if ((vp = ktsearch(&l->vars, vn, h)) != NULL)
+			goto varsearch_out;
+		if (l->next != NULL) {
+			l = l->next;
+			goto varsearch_loop;
+		}
+	}
+	vp = NULL;
+ varsearch_out:
+	*vpp = vp;
+	return (l);
+}
+
+/*
+ * Used to calculate an array index for global()/local(). Sets *arrayp
+ * to true if this is an array, sets *valp to the array index, returns
  * the basename of the array.
  */
 static const char *
 array_index_calc(const char *n, bool *arrayp, uint32_t *valp)
 {
 	const char *p;
-	int len;
+	size_t len;
 	char *ap = NULL;
 
 	*arrayp = false;
  redo_from_ref:
 	p = skip_varname(n, false);
-	if (!set_refflag && (p != n) && ksh_isalphx(n[0])) {
-		struct block *l = e->loc;
+	if (set_refflag == SRF_NOP && (p != n) && ksh_isalphx(n[0])) {
 		struct tbl *vp;
 		char *vn;
-		uint32_t h;
 
 		strndupx(vn, n, p - n, ATEMP);
-		h = hash(vn);
 		/* check if this is a reference */
-		do {
-			vp = ktsearch(&l->vars, vn, h);
-		} while (!vp && (l = l->next));
+		varsearch(e->loc, &vp, vn, hash(vn));
 		afree(vn, ATEMP);
 		if (vp && (vp->flag & (DEFINED|ASSOC|ARRAY)) ==
 		    (DEFINED|ASSOC)) {
@@ -181,7 +200,7 @@
 		char *sub, *tmp;
 		mksh_ari_t rval;
 
-		/* Calculate the value of the subscript */
+		/* calculate the value of the subscript */
 		*arrayp = true;
 		strndupx(tmp, p + 1, len - 2, ATEMP);
 		sub = substitute(tmp, 0);
@@ -236,7 +255,7 @@
 			vp->val.i = kshpid;
 			break;
 		case '!':
-			/* If no job, expand to nothing */
+			/* if no job, expand to nothing */
 			if ((vp->val.i = j_async()) == 0)
 				vp->flag &= ~(ISSET|INTEGER);
 			break;
@@ -255,17 +274,9 @@
 		}
 		return (vp);
 	}
-	for (l = e->loc; ; l = l->next) {
-		vp = ktsearch(&l->vars, n, h);
-		if (vp != NULL) {
-			if (array)
-				return (arraysearch(vp, val));
-			else
-				return (vp);
-		}
-		if (l->next == NULL)
-			break;
-	}
+	l = varsearch(e->loc, &vp, n, h);
+	if (vp != NULL)
+		return (array ? arraysearch(vp, val) : vp);
 	vp = ktenter(&l->vars, n, h);
 	if (array)
 		vp = arraysearch(vp, val);
@@ -286,7 +297,7 @@
 	bool array;
 	uint32_t h, val;
 
-	/* Check to see if this is an array */
+	/* check to see if this is an array */
 	n = array_index_calc(n, &array, &val);
 	h = hash(n);
 	if (!ksh_isalphx(*n)) {
@@ -298,12 +309,10 @@
 	}
 	vp = ktenter(&l->vars, n, h);
 	if (copy && !(vp->flag & DEFINED)) {
-		struct block *ll = l;
-		struct tbl *vq = NULL;
+		struct tbl *vq;
 
-		while ((ll = ll->next) && !(vq = ktsearch(&ll->vars, n, h)))
-			;
-		if (vq) {
+		varsearch(l->next, &vq, n, h);
+		if (vq != NULL) {
 			vp->flag |= vq->flag &
 			    (EXPORT | INTEGER | RDONLY | LJUST | RJUST |
 			    ZEROFIL | LCASEV | UCASEV_AL | INT_U | INT_L);
@@ -329,18 +338,23 @@
 	if ((vp->flag&SPECIAL))
 		getspec(vp);
 	if (!(vp->flag&ISSET))
-		s = null;		/* special to dollar() */
-	else if (!(vp->flag&INTEGER))	/* string source */
+		/* special to dollar() */
+		s = null;
+	else if (!(vp->flag&INTEGER))
+		/* string source */
 		s = vp->val.s + vp->type;
-	else {				/* integer source */
-		/* worst case number length is when base=2 */
-		/* 1 (minus) + 2 (base, up to 36) + 1 ('#') + number of bits
-		 * in the mksh_uari_t + 1 (NUL) */
+	else {
+		/* integer source */
+		mksh_uari_t n;
+		int base;
+		/**
+		 * worst case number length is when base == 2:
+		 *	1 (minus) + 2 (base, up to 36) + 1 ('#') +
+		 *	number of bits in the mksh_uari_t + 1 (NUL)
+		 */
 		char strbuf[1 + 2 + 1 + 8 * sizeof(mksh_uari_t) + 1];
 		const char *digits = (vp->flag & UCASEV_AL) ?
 		    digits_uc : digits_lc;
-		mksh_uari_t n;
-		int base;
 
 		s = strbuf + sizeof(strbuf);
 		if (vp->flag & INT_U)
@@ -349,6 +363,8 @@
 			n = (vp->val.i < 0) ? -vp->val.i : vp->val.i;
 		base = (vp->type == 0) ? 10 : vp->type;
 
+		if (base == 1 && n == 0)
+			base = 2;
 		if (base == 1) {
 			size_t sz = 1;
 
@@ -375,7 +391,8 @@
 			if (!(vp->flag & INT_U) && vp->val.i < 0)
 				*--s = '-';
 		}
-		if (vp->flag & (RJUST|LJUST)) /* case already dealt with */
+		if (vp->flag & (RJUST|LJUST))
+			/* case already dealt with */
 			s = formatstr(vp, s);
 		else
 			strdupx(s, s, ATEMP);
@@ -383,20 +400,6 @@
 	return (s);
 }
 
-/* get variable integer value, with error checking */
-static mksh_ari_t
-intval(struct tbl *vp)
-{
-	mksh_ari_t num;
-	int base;
-
-	base = getint(vp, &num, false);
-	if (base == -1)
-		/* XXX check calls - is error here ok by POSIX? */
-		errorf("%s: bad number", str_val(vp));
-	return (num);
-}
-
 /* set variable to string value */
 int
 setstr(struct tbl *vq, const char *s, int error_ok)
@@ -406,12 +409,13 @@
 
 	error_ok &= ~0x4;
 	if ((vq->flag & RDONLY) && !no_ro_check) {
-		warningf(true, "%s: is read only", vq->name);
+		warningf(true, "%s: %s", vq->name, "is read only");
 		if (!error_ok)
-			errorfz();
+			errorfxz(2);
 		return (0);
 	}
-	if (!(vq->flag&INTEGER)) { /* string dest */
+	if (!(vq->flag&INTEGER)) {
+		/* string dest */
 		if ((vq->flag&ALLOC)) {
 			/* debugging */
 			if (s >= vq->val.s &&
@@ -431,7 +435,8 @@
 			strdupx(vq->val.s, s, vq->areap);
 			vq->flag |= ALLOC;
 		}
-	} else {		/* integer dest */
+	} else {
+		/* integer dest */
 		if (!v_evaluate(vq, s, error_ok, true))
 			return (0);
 	}
@@ -479,8 +484,6 @@
 		return (vp->type);
 	}
 	s = vp->val.s + vp->type;
-	if (s == NULL)	/* redundant given initial test */
-		s = null;
 	base = 10;
 	num = 0;
 	neg = 0;
@@ -541,7 +544,8 @@
 	return (base);
 }
 
-/* convert variable vq to integer variable, setting its value from vp
+/*
+ * convert variable vq to integer variable, setting its value from vp
  * (vq and vp may be the same)
  */
 struct tbl *
@@ -552,17 +556,26 @@
 
 	if ((base = getint(vp, &num, arith)) == -1)
 		return (NULL);
+	setint_n(vq, num);
+	if (vq->type == 0)
+		/* default base */
+		vq->type = base;
+	return (vq);
+}
+
+/* convert variable vq to integer variable, setting its value to num */
+void
+setint_n(struct tbl *vq, mksh_ari_t num)
+{
 	if (!(vq->flag & INTEGER) && (vq->flag & ALLOC)) {
 		vq->flag &= ~ALLOC;
+		vq->type = 0;
 		afree(vq->val.s, vq->areap);
 	}
 	vq->val.i = num;
-	if (vq->type == 0) /* default base */
-		vq->type = base;
 	vq->flag |= ISSET|INTEGER;
 	if (vq->flag&SPECIAL)
 		setspec(vq);
-	return (vq);
 }
 
 static char *
@@ -572,10 +585,11 @@
 	char *p, *q;
 	size_t psiz;
 
-	olen = utf_mbswidth(s);
+	olen = (int)utf_mbswidth(s);
 
 	if (vp->flag & (RJUST|LJUST)) {
-		if (!vp->u2.field)	/* default field width */
+		if (!vp->u2.field)
+			/* default field width */
 			vp->u2.field = olen;
 		nlen = vp->u2.field;
 	} else
@@ -649,33 +663,38 @@
 {
 	char *xp;
 	char *op = (vp->flag&ALLOC) ? vp->val.s : NULL;
-	int namelen = strlen(vp->name);
-	int vallen = strlen(val) + 1;
+	size_t namelen, vallen;
+
+	namelen = strlen(vp->name);
+	vallen = strlen(val) + 1;
 
 	vp->flag |= ALLOC;
+	/* 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++ = '=';
-	vp->type = xp - vp->val.s; /* offset to value */
+	/* offset to value */
+	vp->type = xp - vp->val.s;
 	memcpy(xp, val, vallen);
 	if (op != NULL)
 		afree(op, vp->areap);
 }
 
 /*
- * lookup variable (according to (set&LOCAL)),
- * set its attributes (INTEGER, RDONLY, EXPORT, TRACE, LJUST, RJUST, ZEROFIL,
- * LCASEV, UCASEV_AL), and optionally set its value if an assignment.
+ * lookup variable (according to (set&LOCAL)), set its attributes
+ * (INTEGER, RDONLY, EXPORT, TRACE, LJUST, RJUST, ZEROFIL, LCASEV,
+ * UCASEV_AL), and optionally set its value if an assignment.
  */
 struct tbl *
-typeset(const char *var, Tflag set, Tflag clr, int field, int base)
+typeset(const char *var, uint32_t set, uint32_t clr, int field, int base)
 {
 	struct tbl *vp;
 	struct tbl *vpbase, *t;
 	char *tvar;
 	const char *val;
-	int len;
+	size_t len;
+	bool vappend = false;
 
 	/* check for valid variable name, search for value */
 	val = skip_varname(var, false);
@@ -684,55 +703,80 @@
 	mkssert(var != NULL);
 	mkssert(*var != 0);
 	if (*val == '[') {
-		if (set_refflag)
-			errorf("%s: reference variable cannot be an array",
-			    var);
+		if (set_refflag != SRF_NOP)
+			errorf("%s: %s", var,
+			    "reference variable can't be an array");
 		len = array_ref_len(val);
 		if (len == 0)
 			return (NULL);
-		/* IMPORT is only used when the shell starts up and is
+		/*
+		 * IMPORT is only used when the shell starts up and is
 		 * setting up its environment. Allow only simple array
-		 * references at this time since parameter/command substitution
-		 * is preformed on the [expression] which would be a major
-		 * security hole.
+		 * references at this time since parameter/command
+		 * substitution is preformed on the [expression] which
+		 * would be a major security hole.
 		 */
 		if (set & IMPORT) {
-			int i;
+			size_t i;
+
 			for (i = 1; i < len - 1; i++)
 				if (!ksh_isdigit(val[i]))
 					return (NULL);
 		}
 		val += len;
 	}
-	if (*val == '=')
-		strndupx(tvar, var, val++ - var, ATEMP);
-	else {
-		/* Importing from original environment: must have an = */
+	if (val[0] == '=' || (val[0] == '+' && val[1] == '=')) {
+		strndupx(tvar, var, val - var, ATEMP);
+		if (*val++ == '+') {
+			++val;
+			vappend = true;
+		}
+	} else {
+		/* importing from original environment: must have an = */
 		if (set & IMPORT)
 			return (NULL);
 		strdupx(tvar, var, ATEMP);
 		val = NULL;
-		/* handle foo[*] ⇒ foo (whole array) mapping for R39b */
+		/* handle foo[*] => foo (whole array) mapping for R39b */
 		len = strlen(tvar);
-		if (len > 3 && tvar[len-3] == '[' && tvar[len-2] == '*' &&
-		    tvar[len-1] == ']')
-			tvar[len-3] = '\0';
+		if (len > 3 && tvar[len - 3] == '[' && tvar[len - 2] == '*' &&
+		    tvar[len - 1] == ']')
+			tvar[len - 3] = '\0';
 	}
 
-	/* Prevent typeset from creating a local PATH/ENV/SHELL */
+	if (set_refflag == SRF_ENABLE) {
+		const char *qval;
+
+		/* bail out on 'nameref foo+=bar' */
+		if (vappend)
+			errorfz();
+		/* find value if variable already exists */
+		if ((qval = val) == NULL) {
+			varsearch(e->loc, &vp, tvar, hash(tvar));
+			if (vp != NULL)
+				qval = str_val(vp);
+		}
+		/* silently ignore 'nameref foo=foo' */
+		if (qval != NULL && !strcmp(qval, tvar)) {
+			afree(tvar, ATEMP);
+			return (&vtemp);
+		}
+	}
+
+	/* prevent typeset from creating a local PATH/ENV/SHELL */
 	if (Flag(FRESTRICTED) && (strcmp(tvar, "PATH") == 0 ||
 	    strcmp(tvar, "ENV") == 0 || strcmp(tvar, "SHELL") == 0))
-		errorf("%s: restricted", tvar);
+		errorf("%s: %s", tvar, "restricted");
 
-	vp = (set&LOCAL) ? local(tvar, (set & LOCAL_COPY) ? true : false) :
+	vp = (set&LOCAL) ? local(tvar, tobool(set & LOCAL_COPY)) :
 	    global(tvar);
-	if (set_refflag == 2 && (vp->flag & (ARRAY|ASSOC)) == ASSOC)
+	if (set_refflag == SRF_DISABLE && (vp->flag & (ARRAY|ASSOC)) == ASSOC)
 		vp->flag &= ~ASSOC;
-	else if (set_refflag == 1) {
+	else if (set_refflag == SRF_ENABLE) {
 		if (vp->flag & ARRAY) {
 			struct tbl *a, *tmp;
 
-			/* Free up entire array */
+			/* free up entire array */
 			for (a = vp->u.array; a; ) {
 				tmp = a;
 				a = a->u.array;
@@ -750,21 +794,24 @@
 
 	vpbase = (vp->flag & ARRAY) ? global(arrayname(var)) : vp;
 
-	/* only allow export flag to be set. AT&T ksh allows any attribute to
-	 * be changed which means it can be truncated or modified (-L/-R/-Z/-i)
+	/*
+	 * only allow export flag to be set; AT&T ksh allows any
+	 * attribute to be changed which means it can be truncated or
+	 * modified (-L/-R/-Z/-i)
 	 */
 	if ((vpbase->flag&RDONLY) &&
 	    (val || clr || (set & ~EXPORT)))
 		/* XXX check calls - is error here ok by POSIX? */
-		errorf("%s: is read only", tvar);
+		errorfx(2, "%s: %s", tvar, "is read only");
 	afree(tvar, ATEMP);
 
 	/* most calls are with set/clr == 0 */
 	if (set | clr) {
 		bool ok = true;
 
-		/* XXX if x[0] isn't set, there will be problems: need to have
-		 * one copy of attributes for arrays...
+		/*
+		 * XXX if x[0] isn't set, there will be problems: need
+		 * to have one copy of attributes for arrays...
 		 */
 		for (t = vpbase; t; t = t->u.array) {
 			bool fake_assign;
@@ -791,8 +838,9 @@
 				t->flag &= ~ALLOC;
 			}
 			t->flag = (t->flag | set) & ~clr;
-			/* Don't change base if assignment is to be done,
-			 * in case assignment fails.
+			/*
+			 * Don't change base if assignment is to be
+			 * done, in case assignment fails.
 			 */
 			if ((set & INTEGER) && base > 0 && (!val || t != vp))
 				t->type = base;
@@ -800,9 +848,11 @@
 				t->u2.field = field;
 			if (fake_assign) {
 				if (!setstr(t, s, KSH_RETURN_ERROR)) {
-					/* Somewhat arbitrary action here:
-					 * zap contents of variable, but keep
-					 * the flag settings.
+					/*
+					 * Somewhat arbitrary action
+					 * here: zap contents of
+					 * variable, but keep the flag
+					 * settings.
 					 */
 					ok = false;
 					if (t->flag & INTEGER)
@@ -823,15 +873,26 @@
 	}
 
 	if (val != NULL) {
+		char *tval;
+
+		if (vappend) {
+			tval = shf_smprintf("%s%s", str_val(vp), val);
+			val = tval;
+		} else
+			tval = NULL;
+
 		if (vp->flag&INTEGER) {
 			/* do not zero base before assignment */
 			setstr(vp, val, KSH_UNWIND_ERROR | 0x4);
-			/* Done after assignment to override default */
+			/* done after assignment to override default */
 			if (base > 0)
 				vp->type = base;
 		} else
 			/* setstr can't fail (readonly check already done) */
 			setstr(vp, val, KSH_RETURN_ERROR | 0x4);
+
+		if (tval != NULL)
+			afree(tval, ATEMP);
 	}
 
 	/* only x[0] is ever exported, so use vpbase */
@@ -855,7 +916,7 @@
 	if ((vp->flag & ARRAY) && (flags & 1)) {
 		struct tbl *a, *tmp;
 
-		/* Free up entire array */
+		/* free up entire array */
 		for (a = vp->u.array; a; ) {
 			tmp = a;
 			a = a->u.array;
@@ -869,20 +930,22 @@
 		vp->flag &= ~(ALLOC|ISSET);
 		return;
 	}
-	/* If foo[0] is being unset, the remainder of the array is kept... */
+	/* if foo[0] is being unset, the remainder of the array is kept... */
 	vp->flag &= SPECIAL | ((flags & 1) ? 0 : ARRAY|DEFINED);
 	if (vp->flag & SPECIAL)
-		unsetspec(vp);	/* responsible for 'unspecial'ing var */
+		/* responsible for 'unspecial'ing var */
+		unsetspec(vp);
 }
 
-/* return a pointer to the first char past a legal variable name (returns the
- * argument if there is no legal name, returns a pointer to the terminating
- * NUL if whole string is legal).
+/*
+ * Return a pointer to the first char past a legal variable name
+ * (returns the argument if there is no legal name, returns a pointer to
+ * the terminating NUL if whole string is legal).
  */
 const char *
 skip_varname(const char *s, int aok)
 {
-	int alen;
+	size_t alen;
 
 	if (s && ksh_isalphx(*s)) {
 		while (*++s && ksh_isalnux(*s))
@@ -896,7 +959,8 @@
 /* Return a pointer to the first character past any legal variable name */
 const char *
 skip_wdvarname(const char *s,
-    int aok)				/* skip array de-reference? */
+    /* skip array de-reference? */
+    bool aok)
 {
 	if (s[0] == CHAR && ksh_isalphx(s[1])) {
 		do {
@@ -908,7 +972,7 @@
 			char c;
 			int depth = 0;
 
-			while (1) {
+			while (/* CONSTCOND */ 1) {
 				if (p[0] != CHAR)
 					break;
 				c = p[1];
@@ -927,7 +991,7 @@
 
 /* Check if coded string s is a variable name */
 int
-is_wdvarname(const char *s, int aok)
+is_wdvarname(const char *s, bool aok)
 {
 	const char *p = skip_wdvarname(s, aok);
 
@@ -940,7 +1004,8 @@
 {
 	const char *p = skip_wdvarname(s, true);
 
-	return (p != s && p[0] == CHAR && p[1] == '=');
+	return (p != s && p[0] == CHAR &&
+	    (p[1] == '=' || (p[1] == '+' && p[2] == CHAR && p[3] == '=')));
 }
 
 /*
@@ -949,14 +1014,16 @@
 char **
 makenv(void)
 {
+	ssize_t i;
 	struct block *l;
 	XPtrV denv;
 	struct tbl *vp, **vpp;
-	int i;
 
 	XPinit(denv, 64);
-	for (l = e->loc; l != NULL; l = l->next)
-		for (vpp = l->vars.tbls, i = l->vars.size; --i >= 0; )
+	for (l = e->loc; l != NULL; l = l->next) {
+		vpp = l->vars.tbls;
+		i = 1 << (l->vars.tshift);
+		while (--i >= 0)
 			if ((vp = *vpp++) != NULL &&
 			    (vp->flag&(ISSET|EXPORT)) == (ISSET|EXPORT)) {
 				struct block *l2;
@@ -979,90 +1046,11 @@
 				}
 				XPput(denv, vp->val.s);
 			}
+	}
 	XPput(denv, NULL);
 	return ((char **)XPclose(denv));
 }
 
-/* Bob Jenkins' one-at-a-time hash */
-static uint32_t
-oaathash_update(register uint32_t h, register const uint8_t *cp,
-    register size_t n)
-{
-	while (n--) {
-		h += *cp++;
-		h += h << 10;
-		h ^= h >> 6;
-	}
-
-	return (h);
-}
-
-static uint32_t
-oaathash_finalise(register uint32_t h)
-{
-	h += h << 3;
-	h ^= h >> 11;
-	h += h << 15;
-
-	return (h);
-}
-
-uint32_t
-oaathash_full(register const uint8_t *bp)
-{
-	register uint32_t h = 0;
-	register uint8_t c;
-
-	while ((c = *bp++)) {
-		h += c;
-		h += h << 10;
-		h ^= h >> 6;
-	}
-
-	return (oaathash_finalise(h));
-}
-
-void
-change_random(const void *vp, size_t n)
-{
-	register uint32_t h = 0x100;
-#if defined(__OpenBSD__)
-	int mib[2];
-	uint8_t k[3];
-	size_t klen;
-#endif
-
-	kshstate_v.cr_dp = vp;
-	kshstate_v.cr_dsz = n;
-	gettimeofday(&kshstate_v.cr_tv, NULL);
-	h = oaathash_update(oaathash_update(h, (void *)&kshstate_v,
-	    sizeof(kshstate_v)), vp, n);
-	kshstate_v.lcg_state_ = oaathash_finalise(h);
-
-#if defined(__OpenBSD__)
-	/* OpenBSD, MirBSD: proper kernel entropy comes at zero cost */
-
-	mib[0] = CTL_KERN;
-	mib[1] = KERN_ARND;
-	klen = sizeof(k);
-	sysctl(mib, 2, k, &klen, &kshstate_v.lcg_state_,
-	    sizeof(kshstate_v.lcg_state_));
-	/* we ignore failures and take in k anyway */
-	h = oaathash_update(h, k, sizeof(k));
-	kshstate_v.lcg_state_ = oaathash_finalise(h);
-#elif defined(MKSH_A4PB)
-	/* forced by the user to use arc4random_pushb(3) • Cygwin? */
-	{
-		uint32_t prv;
-
-		prv = arc4random_pushb(&kshstate_v.lcg_state_,
-		    sizeof(kshstate_v.lcg_state_));
-		h = oaathash_update(h, &prv, sizeof(prv));
-	}
-	kshstate_v.lcg_state_ = oaathash_finalise(h);
-#endif
-}
-
 /*
  * handle special variables with side effects - PATH, SECONDS.
  */
@@ -1117,8 +1105,7 @@
 		 * this is the same Linear Congruential PRNG as Borland
 		 * C/C++ allegedly uses in its built-in rand() function
 		 */
-		i = ((kshstate_v.lcg_state_ =
-		    22695477 * kshstate_v.lcg_state_ + 1) >> 16) & 0x7FFF;
+		i = ((lcg_state = 22695477 * lcg_state + 1) >> 16) & 0x7FFF;
 		break;
 	case V_HISTSIZE:
 		i = histsize;
@@ -1146,7 +1133,7 @@
 		return;
 	}
 	vp->flag &= ~SPECIAL;
-	setint(vp, i);
+	setint_n(vp, i);
 	vp->flag |= SPECIAL;
 }
 
@@ -1163,7 +1150,8 @@
 			afree(path, APERM);
 		s = str_val(vp);
 		strdupx(path, s, APERM);
-		flushcom(1);	/* clear tracked aliases */
+		/* clear tracked aliases */
+		flushcom(true);
 		return;
 	case V_IFS:
 		setctypes(s = str_val(vp), C_IFS);
@@ -1174,28 +1162,25 @@
 			afree(tmpdir, APERM);
 			tmpdir = NULL;
 		}
-		/* Use tmpdir iff it is an absolute path, is writable and
-		 * searchable and is a directory...
+		/*
+		 * Use tmpdir iff it is an absolute path, is writable
+		 * and searchable and is a directory...
 		 */
 		{
 			struct stat statb;
 
 			s = str_val(vp);
+			/* LINTED use of access */
 			if (s[0] == '/' && access(s, W_OK|X_OK) == 0 &&
 			    stat(s, &statb) == 0 && S_ISDIR(statb.st_mode))
 				strdupx(tmpdir, s, APERM);
 		}
-		break;
+		return;
 #if HAVE_PERSISTENT_HISTORY
 	case V_HISTFILE:
 		sethistfile(str_val(vp));
-		break;
+		return;
 #endif
-	case V_TMOUT:
-		/* AT&T ksh seems to do this (only listen if integer) */
-		if (vp->flag & INTEGER)
-			ksh_tmout = vp->val.i >= 0 ? vp->val.i : 0;
-		break;
 
 	/* common sub-cases */
 	case V_OPTIND:
@@ -1205,8 +1190,14 @@
 	case V_RANDOM:
 	case V_SECONDS:
 	case V_LINENO:
+	case V_TMOUT:
 		vp->flag &= ~SPECIAL;
-		i = intval(vp);
+		if (getint(vp, &i, false) == -1) {
+			s = str_val(vp);
+			if (st != V_RANDOM)
+				errorf("%s: %s: %s", vp->name, "bad number", s);
+			i = hash(s);
+		}
 		vp->flag |= SPECIAL;
 		break;
 	default:
@@ -1236,7 +1227,7 @@
 		 * mksh R39d+ no longer has the traditional repeatability
 		 * of $RANDOM sequences, but always retains state
 		 */
-		change_random(&i, sizeof(i));
+		rndset((long)i);
 		break;
 	case V_SECONDS:
 		{
@@ -1250,6 +1241,9 @@
 		/* The -1 is because line numbering starts at 1. */
 		user_lineno = (unsigned int)i - current_lineno - 1;
 		break;
+	case V_TMOUT:
+		ksh_tmout = i >= 0 ? i : 0;
+		break;
 	}
 }
 
@@ -1261,7 +1255,8 @@
 		if (path)
 			afree(path, APERM);
 		strdupx(path, def_path, APERM);
-		flushcom(1);	/* clear tracked aliases */
+		/* clear tracked aliases */
+		flushcom(true);
 		break;
 	case V_IFS:
 		setctypes(" \t\n", C_IFS);
@@ -1277,7 +1272,8 @@
 	case V_LINENO:
 	case V_RANDOM:
 	case V_SECONDS:
-	case V_TMOUT:		/* AT&T ksh leaves previous value in place */
+	case V_TMOUT:
+		/* AT&T ksh leaves previous value in place */
 		unspecial(vp->name);
 		break;
 
@@ -1295,14 +1291,14 @@
  * Search for (and possibly create) a table entry starting with
  * vp, indexed by val.
  */
-static struct tbl *
+struct tbl *
 arraysearch(struct tbl *vp, uint32_t val)
 {
 	struct tbl *prev, *curr, *news;
 	size_t len;
 
 	vp->flag = (vp->flag | (ARRAY|DEFINED)) & ~ASSOC;
-	/* The table entry is always [0] */
+	/* the table entry is always [0] */
 	if (val == 0)
 		return (vp);
 	prev = vp;
@@ -1317,9 +1313,10 @@
 		news = curr;
 	} else
 		news = NULL;
-	len = strlen(vp->name) + 1;
 	if (!news) {
-		news = alloc(offsetof(struct tbl, name[0]) + len, vp->areap);
+		len = strlen(vp->name);
+		checkoktoadd(len, 1 + offsetof(struct tbl, name[0]));
+		news = alloc(offsetof(struct tbl, name[0]) + ++len, vp->areap);
 		memcpy(news->name, vp->name, len);
 	}
 	news->flag = (vp->flag & ~(ALLOC|DEFINED|ISSET|SPECIAL)) | AINDEX;
@@ -1328,22 +1325,24 @@
 	news->u2.field = vp->u2.field;
 	news->ua.index = val;
 
-	if (curr != news) {		/* not reusing old array entry */
+	if (curr != news) {
+		/* not reusing old array entry */
 		prev->u.array = news;
 		news->u.array = curr;
 	}
 	return (news);
 }
 
-/* Return the length of an array reference (eg, [1+2]) - cp is assumed
- * to point to the open bracket. Returns 0 if there is no matching closing
- * bracket.
+/*
+ * Return the length of an array reference (eg, [1+2]) - cp is assumed
+ * to point to the open bracket. Returns 0 if there is no matching
+ * closing bracket.
  */
-int
+size_t
 array_ref_len(const char *cp)
 {
 	const char *s = cp;
-	int c;
+	char c;
 	int depth = 0;
 
 	while ((c = *s++) && (c != ']' || --depth))
@@ -1377,32 +1376,50 @@
 set_array(const char *var, bool reset, const char **vals)
 {
 	struct tbl *vp, *vq;
-	mksh_uari_t i;
+	mksh_uari_t i = 0, j = 0;
 	const char *ccp;
 #ifndef MKSH_SMALL
-	char *cp;
-	mksh_uari_t j;
+	char *cp = NULL;
+	size_t n;
 #endif
 
 	/* to get local array, use "typeset foo; set -A foo" */
-	vp = global(var);
+#ifndef MKSH_SMALL
+	n = strlen(var);
+	if (n > 0 && var[n - 1] == '+') {
+		/* append mode */
+		reset = false;
+		strndupx(cp, var, n - 1, ATEMP);
+	}
+#define CPORVAR	(cp ? cp : var)
+#else
+#define CPORVAR	var
+#endif
+	vp = global(CPORVAR);
 
 	/* Note: AT&T ksh allows set -A but not set +A of a read-only var */
 	if ((vp->flag&RDONLY))
-		errorf("%s: is read only", var);
+		errorfx(2, "%s: %s", CPORVAR, "is read only");
 	/* This code is quite non-optimal */
 	if (reset)
 		/* trash existing values and attributes */
 		unset(vp, 1);
-	/* todo: would be nice for assignment to completely succeed or
+	/*
+	 * TODO: would be nice for assignment to completely succeed or
 	 * completely fail. Only really effects integer arrays:
 	 * evaluation of some of vals[] may fail...
 	 */
-	i = 0;
 #ifndef MKSH_SMALL
-	j = 0;
-#else
-#define j i
+	if (cp != NULL) {
+		/* find out where to set when appending */
+		for (vq = vp; vq; vq = vq->u.array) {
+			if (!(vq->flag & ISSET))
+				continue;
+			if (arrayindex(vq) >= j)
+				j = arrayindex(vq) + 1;
+		}
+		afree(cp, ATEMP);
+	}
 #endif
 	while ((ccp = vals[i])) {
 #ifndef MKSH_SMALL
@@ -1432,9 +1449,7 @@
 		/* would be nice to deal with errors here... (see above) */
 		setstr(vq, ccp, KSH_RETURN_ERROR);
 		i++;
-#ifndef MKSH_SMALL
 		j++;
-#endif
 	}
 
 	return (i);
@@ -1448,7 +1463,7 @@
 #ifdef TIOCGWINSZ
 		if (tty_fd < 0)
 			/* non-FTALKING, try to get an fd anyway */
-			tty_init(false, false);
+			tty_init(true, false);
 #endif
 		x_cols = -1;
 	}
@@ -1479,12 +1494,42 @@
 }
 
 uint32_t
-evilhash(const char *s)
+hash(const void *s)
 {
-	register uint32_t h = 0x100;
+	register uint32_t h;
 
-	h = oaathash_update(h, (void *)&kshstate_f, sizeof(kshstate_f));
-	kshstate_f.h = oaathash_full((const uint8_t *)s);
-	return (oaathash_finalise(oaathash_update(h,
-	    (void *)&kshstate_f.h, sizeof(kshstate_f.h))));
+	NZATInit(h);
+	NZATUpdateString(h, s);
+	NZATFinish(h);
+	return (h);
+}
+
+void
+rndset(long v)
+{
+	register uint32_t h;
+
+	NZATInit(h);
+	NZATUpdateMem(h, &lcg_state, sizeof(lcg_state));
+	NZATUpdateMem(h, &v, sizeof(v));
+
+#if defined(arc4random_pushb_fast) || defined(MKSH_A4PB)
+	/*
+	 * either we have very chap entropy get and push available,
+	 * with malloc() pulling in this code already anyway, or the
+	 * user requested us to use the old functions
+	 */
+	lcg_state = h;
+	NZAATFinish(lcg_state);
+#if defined(arc4random_pushb_fast)
+	arc4random_pushb_fast(&lcg_state, sizeof(lcg_state));
+	lcg_state = arc4random();
+#else
+	lcg_state = arc4random_pushb(&lcg_state, sizeof(lcg_state));
+#endif
+	NZATUpdateMem(h, &lcg_state, sizeof(lcg_state));
+#endif
+
+	NZAATFinish(h);
+	lcg_state = h;
 }
diff --git a/src/var_spec.h b/src/var_spec.h
index 4035cc9..b3bef4b 100644
--- a/src/var_spec.h
+++ b/src/var_spec.h
@@ -1,5 +1,5 @@
 #if defined(VARSPEC_DEFNS)
-__RCSID("$MirOS: src/bin/mksh/var_spec.h,v 1.1 2009/09/26 03:40:03 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/var_spec.h,v 1.2 2011/06/05 19:58:21 tg Exp $");
 #define FN(name)			/* nothing */
 #elif defined(VARSPEC_ENUMS)
 #define FN(name)			V_##name,
@@ -13,6 +13,8 @@
 #define F0 FN
 #endif
 
+/* NOTE: F0 are skipped for the ITEMS array, only FN generate names */
+
 /* 0 is always V_NONE */
 F0(NONE)