Merge "Upgrade to mksh R55."
diff --git a/Android.mk b/Android.mk
index 427aea9..5e7c633 100644
--- a/Android.mk
+++ b/Android.mk
@@ -83,6 +83,6 @@
     -DHAVE_SETGROUPS=1 -DHAVE_STRERROR=1 -DHAVE_STRSIGNAL=0 \
     -DHAVE_STRLCPY=1 -DHAVE_FLOCK_DECL=1 -DHAVE_REVOKE_DECL=1 \
     -DHAVE_SYS_ERRLIST_DECL=0 -DHAVE_SYS_SIGLIST_DECL=1 \
-    -DHAVE_PERSISTENT_HISTORY=0 -DMKSH_BUILD_R=541
+    -DHAVE_PERSISTENT_HISTORY=0 -DMKSH_BUILD_R=551
 
 include $(BUILD_EXECUTABLE)
diff --git a/src/Build.sh b/src/Build.sh
old mode 100755
new mode 100644
index d0ff130..ca88a06
--- a/src/Build.sh
+++ b/src/Build.sh
@@ -1,8 +1,8 @@
 #!/bin/sh
-srcversion='$MirOS: src/bin/mksh/Build.sh,v 1.707 2016/11/11 23:31:29 tg Exp $'
+srcversion='$MirOS: src/bin/mksh/Build.sh,v 1.716 2017/04/12 18:33:22 tg Exp $'
 #-
 # Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
-#		2011, 2012, 2013, 2014, 2015, 2016
+#		2011, 2012, 2013, 2014, 2015, 2016, 2017
 #	mirabilos <m@mirbsd.org>
 #
 # Provided that these terms and disclaimer and all copyright notices
@@ -495,6 +495,7 @@
 last=
 tfn=
 legacy=0
+textmode=0
 
 for i
 do
@@ -551,6 +552,12 @@
 	:-r)
 		r=1
 		;;
+	:-T)
+		textmode=1
+		;;
+	:+T)
+		textmode=0
+		;;
 	:-t)
 		last=t
 		;;
@@ -586,16 +593,21 @@
 rmf a.exe* a.out* conftest.c conftest.exe* *core core.* ${tfn}* *.bc *.dbg \
     *.ll *.o *.gen *.cat1 Rebuild.sh lft no signames.inc test.sh x vv.out
 
-SRCS="lalloc.c eval.c exec.c expr.c funcs.c histrap.c jobs.c"
+SRCS="lalloc.c edit.c eval.c exec.c expr.c funcs.c histrap.c jobs.c"
 SRCS="$SRCS lex.c main.c misc.c shf.c syn.c tree.c var.c"
 
 if test $legacy = 0; then
-	SRCS="$SRCS edit.c"
 	check_categories="$check_categories shell:legacy-no int:32"
 else
 	check_categories="$check_categories shell:legacy-yes"
 	add_cppflags -DMKSH_LEGACY_MODE
-	HAVE_PERSISTENT_HISTORY=0
+fi
+
+if test $textmode = 0; then
+	check_categories="$check_categories shell:textmode-no shell:binmode-yes"
+else
+	check_categories="$check_categories shell:textmode-yes shell:binmode-no"
+	add_cppflags -DMKSH_WITH_TEXTMODE
 fi
 
 if test x"$srcdir" = x"."; then
@@ -766,7 +778,6 @@
 	add_cppflags -DMKSH__NO_SETEUGID
 	oswarn=' and will currently not work'
 	add_cppflags -DMKSH_UNEMPLOYED
-	add_cppflags -DMKSH_NOPROSPECTOFWORK
 	# these taken from Harvey-OS github and need re-checking
 	add_cppflags -D_setjmp=setjmp -D_longjmp=longjmp
 	: "${HAVE_CAN_NO_EH_FRAME=0}"
@@ -849,16 +860,39 @@
 	: "${HAVE_SETLOCALE_CTYPE=0}"
 	;;
 OS/2)
+	add_cppflags -DMKSH_ASSUME_UTF8=0; HAVE_ISSET_MKSH_ASSUME_UTF8=1
 	HAVE_TERMIOS_H=0
 	HAVE_MKNOD=0	# setmode() incompatible
-	oswarn="; it is currently being ported, get it from"
-	oswarn="$oswarn${nl}https://github.com/komh/mksh-os2 in the meanwhile"
+	oswarn="; it is being ported"
 	check_categories="$check_categories nosymlink"
 	: "${CC=gcc}"
 	: "${SIZE=: size}"
+	SRCS="$SRCS os2.c"
 	add_cppflags -DMKSH_UNEMPLOYED
 	add_cppflags -DMKSH_NOPROSPECTOFWORK
 	add_cppflags -DMKSH_NO_LIMITS
+	add_cppflags -DMKSH_DOSPATH
+	if test $textmode = 0; then
+		x='dis'
+		y='standard OS/2 tools'
+	else
+		x='en'
+		y='standard Unix mksh and other tools'
+	fi
+	echo >&2 "
+OS/2 Note: mksh can be built with or without 'textmode'.
+Without 'textmode' it will behave like a standard Unix utility,
+compatible to mksh on all other platforms, using only ASCII LF
+(0x0A) as line ending character. This is supported by the mksh
+upstream developer.
+With 'textmode', mksh will be modified to behave more like other
+OS/2 utilities, supporting ASCII CR+LF (0x0D 0x0A) as line ending
+at the cost of deviation from standard mksh. This is supported by
+the mksh-os2 porter.
+
+] You are currently compiling with textmode ${x}abled, introducing
+] incompatibilities with $y.
+"
 	;;
 OSF1)
 	HAVE_SIG_T=0	# incompatible
@@ -2152,68 +2186,6 @@
 ac_testdone
 ac_cppflags
 
-save_CFLAGS=$CFLAGS
-ac_testn compile_time_asserts_$$ '' 'whether compile-time assertions pass' <<-'EOF'
-	#define MKSH_INCLUDES_ONLY
-	#include "sh.h"
-	#ifndef CHAR_BIT
-	#define CHAR_BIT 8	/* defuse this test on really legacy systems */
-	#endif
-	struct ctasserts {
-	#define cta(name, assertion) char name[(assertion) ? 1 : -1]
-/* this one should be defined by the standard */
-cta(char_is_1_char, (sizeof(char) == 1) && (sizeof(signed char) == 1) &&
-    (sizeof(unsigned char) == 1));
-cta(char_is_8_bits, ((CHAR_BIT) == 8) && ((int)(unsigned char)0xFF == 0xFF) &&
-    ((int)(unsigned char)0x100 == 0) && ((int)(unsigned char)(int)-1 == 0xFF));
-/* the next assertion is probably not really needed */
-cta(short_is_2_char, sizeof(short) == 2);
-cta(short_size_no_matter_of_signedness, sizeof(short) == sizeof(unsigned short));
-/* the next assertion is probably not really needed */
-cta(int_is_4_char, sizeof(int) == 4);
-cta(int_size_no_matter_of_signedness, sizeof(int) == sizeof(unsigned int));
-
-cta(long_ge_int, sizeof(long) >= sizeof(int));
-cta(long_size_no_matter_of_signedness, sizeof(long) == sizeof(unsigned long));
-
-#ifndef MKSH_LEGACY_MODE
-/* the next assertion is probably not really needed */
-cta(ari_is_4_char, sizeof(mksh_ari_t) == 4);
-/* but this is */
-cta(ari_has_31_bit, 0 < (mksh_ari_t)(((((mksh_ari_t)1 << 15) << 15) - 1) * 2 + 1));
-/* the next assertion is probably not really needed */
-cta(uari_is_4_char, sizeof(mksh_uari_t) == 4);
-/* but the next three are; we REQUIRE unsigned integer wraparound */
-cta(uari_has_31_bit, 0 < (mksh_uari_t)(((((mksh_uari_t)1 << 15) << 15) - 1) * 2 + 1));
-cta(uari_has_32_bit, 0 < (mksh_uari_t)(((((mksh_uari_t)1 << 15) << 15) - 1) * 4 + 3));
-cta(uari_wrap_32_bit,
-    (mksh_uari_t)(((((mksh_uari_t)1 << 15) << 15) - 1) * 4 + 3) >
-    (mksh_uari_t)(((((mksh_uari_t)1 << 15) << 15) - 1) * 4 + 4));
-#define NUM 22
-#else
-#define NUM 16
-#endif
-/* these are always required */
-cta(ari_is_signed, (mksh_ari_t)-1 < (mksh_ari_t)0);
-cta(uari_is_unsigned, (mksh_uari_t)-1 > (mksh_uari_t)0);
-/* we require these to have the precisely same size and assume 2s complement */
-cta(ari_size_no_matter_of_signedness, sizeof(mksh_ari_t) == sizeof(mksh_uari_t));
-
-cta(sizet_size_no_matter_of_signedness, sizeof(ssize_t) == sizeof(size_t));
-cta(sizet_voidptr_same_size, sizeof(size_t) == sizeof(void *));
-cta(sizet_funcptr_same_size, sizeof(size_t) == sizeof(void (*)(void)));
-/* our formatting routines assume this */
-cta(ptr_fits_in_long, sizeof(size_t) <= sizeof(long));
-cta(ari_fits_in_long, sizeof(mksh_ari_t) <= sizeof(long));
-/* for struct alignment people */
-		char padding[64 - NUM];
-	};
-char ctasserts_dblcheck[sizeof(struct ctasserts) == 64 ? 1 : -1];
-	int main(void) { return (sizeof(ctasserts_dblcheck) + isatty(0)); }
-EOF
-CFLAGS=$save_CFLAGS
-eval test 1 = \$HAVE_COMPILE_TIME_ASSERTS_$$ || exit 1
-
 #
 # extra checks for legacy mksh
 #
@@ -2367,7 +2339,7 @@
 addsrcs USE_PRINTF_BUILTIN printf.c
 test 1 = "$USE_PRINTF_BUILTIN" && add_cppflags -DMKSH_PRINTF_BUILTIN
 test 1 = "$HAVE_CAN_VERB" && CFLAGS="$CFLAGS -verbose"
-add_cppflags -DMKSH_BUILD_R=541
+add_cppflags -DMKSH_BUILD_R=551
 
 $e $bi$me: Finished configuration testing, now producing output.$ao
 
diff --git a/src/check.t b/src/check.t
index 1b03af6..93c614f 100644
--- a/src/check.t
+++ b/src/check.t
Binary files differ
diff --git a/src/dot.mkshrc b/src/dot.mkshrc
index 13a1f54..af55d7d 100644
--- a/src/dot.mkshrc
+++ b/src/dot.mkshrc
@@ -1,8 +1,8 @@
 # $Id$
-# $MirOS: src/bin/mksh/dot.mkshrc,v 1.108 2016/07/26 22:03:41 tg Exp $
+# $MirOS: src/bin/mksh/dot.mkshrc,v 1.114 2017/03/19 22:31:26 tg Exp $
 #-
 # Copyright (c) 2002, 2003, 2004, 2006, 2007, 2008, 2009, 2010,
-#		2011, 2012, 2013, 2014, 2015, 2016
+#		2011, 2012, 2013, 2014, 2015, 2016, 2017
 #	mirabilos <m@mirbsd.org>
 #
 # Provided that these terms and disclaimer and all copyright notices
@@ -22,65 +22,100 @@
 #-
 # ${ENV:-~/.mkshrc}: mksh initialisation file for interactive shells
 
-# catch non-mksh (including lksh) trying to run this file
+# catch non-mksh, non-lksh, trying to run this file
 case ${KSH_VERSION:-} in
-*MIRBSD\ KSH*) ;;
-*) return 0 ;;
+*LEGACY\ KSH*|*MIRBSD\ KSH*) ;;
+*) \return 0 ;;
 esac
 
-PS1='#'; (( USER_ID )) && PS1='$'; \: "${TERM:=vt100}${HOSTNAME:=$(\ulimit -c \
-    0; hostname 2>/dev/null)}${EDITOR:=/bin/ed}${USER:=$(\ulimit -c 0; id -un \
-    2>/dev/null || \echo \?)}${MKSH:=$(\builtin whence -p mksh)}"
-HOSTNAME=${HOSTNAME%%*([	 ]).*}; HOSTNAME=${HOSTNAME##*([	 ])}
-[[ $HOSTNAME = ?(ip6-)localhost?(6) ]] && HOSTNAME=
-\: "${HOSTNAME:=nil}${MKSH:=/bin/mksh}"; \export EDITOR HOSTNAME MKSH TERM USER
-PS4='[$EPOCHREALTIME] '; PS1=$'\001\r''${|
-	\typeset e=$?
+# give MidnightBSD's laffer1 a bit of csh feeling
+function setenv {
+	if (( $# )); then
+		\\builtin eval '\\builtin export "$1"="${2:-}"'
+	else
+		\\builtin typeset -x
+	fi
+}
+
+# pager (not control character safe)
+smores() (
+	\\builtin set +m
+	\\builtin cat "$@" |&
+	\\builtin trap "rv=\$?; \\\\builtin kill $! >/dev/null 2>&1; \\\\builtin exit \$rv" EXIT
+	while IFS= \\builtin read -pr line; do
+		llen=${%line}
+		(( llen == -1 )) && llen=${#line}
+		(( llen = llen ? (llen + COLUMNS - 1) / COLUMNS : 1 ))
+		if (( (curlin += llen) >= LINES )); then
+			\\builtin print -nr -- $'\e[7m--more--\e[0m'
+			\\builtin read -u1 || \\builtin exit $?
+			[[ $REPLY = [Qq]* ]] && \\builtin exit 0
+			curlin=$llen
+		fi
+		\\builtin print -r -- "$line"
+	done
+)
+
+\\builtin alias ls=ls l='ls -F' la='l -a' ll='l -l' lo='l -alo'
+\: "${HOSTNAME:=$(\\builtin ulimit -c 0; \\builtin print -r -- $(hostname \
+    2>/dev/null))}${EDITOR:=/bin/ed}${TERM:=vt100}${USER:=$(\\builtin ulimit \
+    -c 0; id -un 2>/dev/null)}${USER:=?}"
+[[ $HOSTNAME = ?(?(ip6-)localhost?(6)) ]] && HOSTNAME=nil; \\builtin unalias ls
+\\builtin export EDITOR HOSTNAME TERM USER
+
+# minimal support for lksh users
+if [[ $KSH_VERSION = *LEGACY\ KSH* ]]; then
+	PS1='$USER@${HOSTNAME%%.*}:$PWD>'
+	\\builtin return 0
+fi
+
+# mksh-specific from here
+\: "${MKSH:=$(\\builtin whence -p mksh)}${MKSH:=/bin/mksh}"
+\\builtin export MKSH
+
+PS4='[$EPOCHREALTIME] '; PS1='#'; (( USER_ID )) && PS1='$'; PS1=$'\001\r''${|
+	\\builtin typeset e=$?
 
 	(( e )) && REPLY+="$e|"
 	REPLY+=${USER}@${HOSTNAME%%.*}:
 
-	\typeset d=${PWD:-?}/ p=~; [[ $p = ?(*/) ]] || d=${d/#$p\//\~/}
-	d=${d%/}; \typeset m=${%d} n p=...; (( m > 0 )) || m=${#d}
+	\\builtin typeset d=${PWD:-?}/ p=~; [[ $p = ?(*/) ]] || d=${d/#$p\//\~/}
+	d=${d%/}; \\builtin typeset m=${%d} n p=...; (( m > 0 )) || m=${#d}
 	(( m > (n = (COLUMNS/3 < 7 ? 7 : COLUMNS/3)) )) && d=${d:(-n)} || p=
 	REPLY+=$p$d
 
-	\return $e
+	\\builtin return $e
 } '"$PS1 "
-\alias ls=ls
-\unalias ls
-\alias l='ls -F'
-\alias la='l -a'
-\alias ll='l -l'
-\alias lo='l -alo'
-\alias doch='sudo mksh -c "$(\builtin fc -ln -1)"'
-\command -v rot13 >/dev/null || \alias rot13='tr \
+\\builtin alias doch='sudo mksh -c "$(\\builtin fc -ln -1)"'
+\\builtin command -v rot13 >/dev/null || \\builtin alias rot13='tr \
     abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ \
     nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM'
-if \command -v hd >/dev/null; then \:; elif \command -v hexdump >/dev/null; then
+if \\builtin command -v hd >/dev/null; then
+	\:
+elif \\builtin command -v hexdump >/dev/null; then
 	function hd {
 		hexdump -e '"%08.8_ax  " 8/1 "%02X " " - " 8/1 "%02X "' \
 		    -e '"  |" "%_p"' -e '"|\n"' "$@"
 	}
 else
 	function hd {
-		\typeset -Uui16 -Z11 pos=0
-		\typeset -Uui16 -Z5 hv=2147483647
-		\typeset dasc line i
-		\set +U
+		\\builtin typeset -Uui16 -Z11 pos=0
+		\\builtin typeset -Uui16 -Z5 hv=2147483647
+		\\builtin typeset dasc line i
+		\\builtin set +U
 
-		\cat "$@" | if \read -arN -1 line; then
-			\typeset -i1 'line[*]'
+		\\builtin cat "$@" | if \\builtin read -arN -1 line; then
+			\\builtin typeset -i1 'line[*]'
 			i=0
 			while (( i < ${#line[*]} )); do
 				hv=${line[i++]}
 				if (( (pos & 15) == 0 )); then
 					(( pos )) && \
-					    \builtin print -r -- "$dasc|"
-					\builtin print -n "${pos#16#}  "
+					    \\builtin print -r -- "$dasc|"
+					\\builtin print -nr "${pos#16#}  "
 					dasc=' |'
 				fi
-				\builtin print -n "${hv#16#} "
+				\\builtin print -nr "${hv#16#} "
 				#XXX EBCDIC, but we need [[:print:]] to fix this
 				if (( (hv < 32) || (hv > 126) )); then
 					dasc+=.
@@ -88,69 +123,69 @@
 					dasc+=${line[i-1]#1#}
 				fi
 				(( (pos++ & 15) == 7 )) && \
-				    \builtin print -n -- '- '
+				    \\builtin print -nr -- '- '
 			done
 			while (( pos & 15 )); do
-				\builtin print -n '   '
+				\\builtin print -nr '   '
 				(( (pos++ & 15) == 7 )) && \
-				    \builtin print -n -- '- '
+				    \\builtin print -nr -- '- '
 			done
-			(( hv == 2147483647 )) || \builtin print -r -- "$dasc|"
+			(( hv == 2147483647 )) || \\builtin print -r -- "$dasc|"
 		fi
 	}
 fi
 
 # Berkeley C shell compatible dirs, popd, and pushd functions
 # Z shell compatible chpwd() hook, used to update DIRSTACK[0]
-DIRSTACKBASE=$(\builtin realpath ~/. 2>/dev/null || \
-    \builtin print -nr -- "${HOME:-/}")
-set -A DIRSTACK
+DIRSTACKBASE=$(\\builtin realpath ~/. 2>/dev/null || \
+    \\builtin print -nr -- "${HOME:-/}")
+\\builtin set -A DIRSTACK
 function chpwd {
-	DIRSTACK[0]=$(\builtin realpath . 2>/dev/null || \
-	    \builtin print -r -- "$PWD")
+	DIRSTACK[0]=$(\\builtin realpath . 2>/dev/null || \
+	    \\builtin print -nr -- "$PWD")
 	[[ $DIRSTACKBASE = ?(*/) ]] || \
 	    DIRSTACK[0]=${DIRSTACK[0]/#$DIRSTACKBASE/\~}
 	\:
 }
 \chpwd .
 cd() {
-	\builtin cd "$@" || \return $?
+	\\builtin cd "$@" || \\builtin return $?
 	\chpwd "$@"
 }
 function cd_csh {
-	\typeset d t=${1/#\~/$DIRSTACKBASE}
+	\\builtin typeset d t=${1/#\~/$DIRSTACKBASE}
 
-	if ! d=$(\builtin cd "$t" 2>&1); then
-		\builtin print -u2 "${1}: ${d##*cd: $t: }."
-		\return 1
+	if ! d=$(\\builtin cd "$t" 2>&1); then
+		\\builtin print -ru2 "${1}: ${d##*cd: $t: }."
+		\\builtin return 1
 	fi
 	\cd "$t"
 }
 function dirs {
-	\typeset d dwidth
-	\typeset -i fl=0 fv=0 fn=0 cpos=0
+	\\builtin typeset d dwidth
+	\\builtin typeset -i fl=0 fv=0 fn=0 cpos=0
 
-	while \getopts ":lvn" d; do
+	while \\builtin getopts ":lvn" d; do
 		case $d {
 		(l)	fl=1 ;;
 		(v)	fv=1 ;;
 		(n)	fn=1 ;;
-		(*)	\builtin print -u2 'Usage: dirs [-lvn].'
-			\return 1 ;;
+		(*)	\\builtin print -ru2 'Usage: dirs [-lvn].'
+			\\builtin return 1 ;;
 		}
 	done
-	\shift $((OPTIND - 1))
+	\\builtin shift $((OPTIND - 1))
 	if (( $# > 0 )); then
-		\builtin print -u2 'Usage: dirs [-lvn].'
-		\return 1
+		\\builtin print -ru2 'Usage: dirs [-lvn].'
+		\\builtin return 1
 	fi
 	if (( fv )); then
 		fv=0
 		while (( fv < ${#DIRSTACK[*]} )); do
 			d=${DIRSTACK[fv]}
 			(( fl )) && d=${d/#\~/$DIRSTACKBASE}
-			\builtin print -r -- "$fv	$d"
-			\builtin let fv++
+			\\builtin print -r -- "$fv	$d"
+			(( ++fv ))
 		done
 	else
 		fv=0
@@ -160,136 +195,117 @@
 			(( dwidth = (${%d} > 0 ? ${%d} : ${#d}) ))
 			if (( fn && (cpos += dwidth + 1) >= 79 && \
 			    dwidth < 80 )); then
-				\builtin print
+				\\builtin print
 				(( cpos = dwidth + 1 ))
 			fi
-			\builtin print -nr -- "$d "
-			\builtin let fv++
+			\\builtin print -nr -- "$d "
+			(( ++fv ))
 		done
-		\builtin print
+		\\builtin print
 	fi
-	\return 0
+	\\builtin return 0
 }
 function popd {
-	\typeset d fa
-	\typeset -i n=1
+	\\builtin typeset d fa
+	\\builtin typeset -i n=1
 
-	while \getopts ":0123456789lvn" d; do
+	while \\builtin getopts ":0123456789lvn" d; do
 		case $d {
 		(l|v|n)	fa+=" -$d" ;;
 		(+*)	n=2
-			\break ;;
-		(*)	\builtin print -u2 'Usage: popd [-lvn] [+<n>].'
-			\return 1 ;;
+			\\builtin break ;;
+		(*)	\\builtin print -ru2 'Usage: popd [-lvn] [+<n>].'
+			\\builtin return 1 ;;
 		}
 	done
-	\shift $((OPTIND - n))
+	\\builtin shift $((OPTIND - n))
 	n=0
 	if (( $# > 1 )); then
-		\builtin print -u2 popd: Too many arguments.
-		\return 1
+		\\builtin print -ru2 popd: Too many arguments.
+		\\builtin return 1
 	elif [[ $1 = ++([0-9]) && $1 != +0 ]]; then
 		if (( (n = ${1#+}) >= ${#DIRSTACK[*]} )); then
-			\builtin print -u2 popd: Directory stack not that deep.
-			\return 1
+			\\builtin print -ru2 popd: Directory stack not that deep.
+			\\builtin return 1
 		fi
 	elif [[ -n $1 ]]; then
-		\builtin print -u2 popd: Bad directory.
-		\return 1
+		\\builtin print -ru2 popd: Bad directory.
+		\\builtin return 1
 	fi
 	if (( ${#DIRSTACK[*]} < 2 )); then
-		\builtin print -u2 popd: Directory stack empty.
-		\return 1
+		\\builtin print -ru2 popd: Directory stack empty.
+		\\builtin return 1
 	fi
-	\unset DIRSTACK[n]
-	\set -A DIRSTACK -- "${DIRSTACK[@]}"
-	\cd_csh "${DIRSTACK[0]}" || \return 1
+	\\builtin unset DIRSTACK[n]
+	\\builtin set -A DIRSTACK -- "${DIRSTACK[@]}"
+	\cd_csh "${DIRSTACK[0]}" || \\builtin return 1
 	\dirs $fa
 }
 function pushd {
-	\typeset d fa
-	\typeset -i n=1
+	\\builtin typeset d fa
+	\\builtin typeset -i n=1
 
-	while \getopts ":0123456789lvn" d; do
+	while \\builtin getopts ":0123456789lvn" d; do
 		case $d {
 		(l|v|n)	fa+=" -$d" ;;
 		(+*)	n=2
-			\break ;;
-		(*)	\builtin print -u2 'Usage: pushd [-lvn] [<dir>|+<n>].'
-			\return 1 ;;
+			\\builtin break ;;
+		(*)	\\builtin print -ru2 'Usage: pushd [-lvn] [<dir>|+<n>].'
+			\\builtin return 1 ;;
 		}
 	done
-	\shift $((OPTIND - n))
+	\\builtin shift $((OPTIND - n))
 	if (( $# == 0 )); then
 		if (( ${#DIRSTACK[*]} < 2 )); then
-			\builtin print -u2 pushd: No other directory.
-			\return 1
+			\\builtin print -ru2 pushd: No other directory.
+			\\builtin return 1
 		fi
 		d=${DIRSTACK[1]}
 		DIRSTACK[1]=${DIRSTACK[0]}
-		\cd_csh "$d" || \return 1
+		\cd_csh "$d" || \\builtin return 1
 	elif (( $# > 1 )); then
-		\builtin print -u2 pushd: Too many arguments.
-		\return 1
+		\\builtin print -ru2 pushd: Too many arguments.
+		\\builtin return 1
 	elif [[ $1 = ++([0-9]) && $1 != +0 ]]; then
 		if (( (n = ${1#+}) >= ${#DIRSTACK[*]} )); then
-			\builtin print -u2 pushd: Directory stack not that deep.
-			\return 1
+			\\builtin print -ru2 pushd: Directory stack not that deep.
+			\\builtin return 1
 		fi
 		while (( n-- )); do
 			d=${DIRSTACK[0]}
-			\unset DIRSTACK[0]
-			\set -A DIRSTACK -- "${DIRSTACK[@]}" "$d"
+			\\builtin unset DIRSTACK[0]
+			\\builtin set -A DIRSTACK -- "${DIRSTACK[@]}" "$d"
 		done
-		\cd_csh "${DIRSTACK[0]}" || \return 1
+		\cd_csh "${DIRSTACK[0]}" || \\builtin return 1
 	else
-		\set -A DIRSTACK -- placeholder "${DIRSTACK[@]}"
-		\cd_csh "$1" || \return 1
+		\\builtin set -A DIRSTACK -- placeholder "${DIRSTACK[@]}"
+		\cd_csh "$1" || \\builtin return 1
 	fi
 	\dirs $fa
 }
 
-# pager (not control character safe)
-smores() (
-	\set +m
-	\cat "$@" |&
-	\trap "rv=\$?; 'kill' $! >/dev/null 2>&1; 'exit' \$rv" EXIT
-	while IFS= \read -pr line; do
-		llen=${%line}
-		(( llen == -1 )) && llen=${#line}
-		(( llen = llen ? (llen + COLUMNS - 1) / COLUMNS : 1 ))
-		if (( (curlin += llen) >= LINES )); then
-			\builtin print -n -- '\e[7m--more--\e[0m'
-			\read -u1 || \exit $?
-			[[ $REPLY = [Qq]* ]] && \exit 0
-			curlin=$llen
-		fi
-		\builtin print -r -- "$line"
-	done
-)
-
 # base64 encoder and decoder, RFC compliant, NUL safe, not EBCDIC safe
 function Lb64decode {
-	\set +U
-	\typeset c s="$*" t
-	[[ -n $s ]] || { s=$(\cat; \builtin print x); s=${s%x}; }
-	\typeset -i i=0 j=0 n=${#s} p=0 v x
-	\typeset -i16 o
+	\\builtin set +U
+	\\builtin typeset c s="$*" t
+	[[ -n $s ]] || { s=$(\\builtin cat; \\builtin print x); s=${s%x}; }
+	\\builtin typeset -i i=0 j=0 n=${#s} p=0 v x
+	\\builtin typeset -i16 o
 
 	while (( i < n )); do
 		c=${s:(i++):1}
 		case $c {
-		(=)	\break ;;
+		(=)	\\builtin break ;;
 		([A-Z])	(( v = 1#$c - 65 )) ;;
 		([a-z])	(( v = 1#$c - 71 )) ;;
 		([0-9])	(( v = 1#$c + 4 )) ;;
 		(+)	v=62 ;;
 		(/)	v=63 ;;
-		(*)	\continue ;;
+		(*)	\\builtin continue ;;
 		}
 		(( x = (x << 6) | v ))
 		case $((p++)) {
-		(0)	\continue ;;
+		(0)	\\builtin continue ;;
 		(1)	(( o = (x >> 4) & 255 )) ;;
 		(2)	(( o = (x >> 2) & 255 )) ;;
 		(3)	(( o = x & 255 ))
@@ -297,63 +313,60 @@
 			;;
 		}
 		t+=\\x${o#16#}
-		(( ++j & 4095 )) && \continue
-		\builtin print -n $t
+		(( ++j & 4095 )) && \\builtin continue
+		\\builtin print -n $t
 		t=
 	done
-	\builtin print -n $t
+	\\builtin print -n $t
 }
-
-\set -A Lb64encode_tbl -- 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 {
-	\set +U
-	\typeset c s t
+	\\builtin set +U
+	\\builtin typeset c s t table
+	\\builtin set -A table -- 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 + /
 	if (( $# )); then
-		\read -raN-1 s <<<"$*"
-		\unset s[${#s[*]}-1]
+		\\builtin read -raN-1 s <<<"$*"
+		\\builtin unset s[${#s[*]}-1]
 	else
-		\read -raN-1 s
+		\\builtin read -raN-1 s
 	fi
-	\typeset -i i=0 n=${#s[*]} j v
+	\\builtin typeset -i i=0 n=${#s[*]} 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+=${Lb64encode_tbl[v >> 18]}${Lb64encode_tbl[v >> 12 & 63]}
-		c=${Lb64encode_tbl[v >> 6 & 63]}
+		(( v |= s[i++] << 8 ))
+		(( v |= s[i++] ))
+		t+=${table[v >> 18]}${table[v >> 12 & 63]}
+		c=${table[v >> 6 & 63]}
 		if (( i <= n )); then
-			t+=$c${Lb64encode_tbl[v & 63]}
+			t+=$c${table[v & 63]}
 		elif (( i == n + 1 )); then
 			t+=$c=
 		else
 			t+===
 		fi
 		if (( ${#t} == 76 || i >= n )); then
-			\builtin print $t
+			\\builtin print -r $t
 			t=
 		fi
 	done
 }
 
 # Better Avalanche for the Jenkins Hash
-\typeset -Z11 -Uui16 Lbafh_v
+\\builtin typeset -Z11 -Uui16 Lbafh_v
 function Lbafh_init {
 	Lbafh_v=0
 }
 function Lbafh_add {
-	\set +U
-	\typeset s
+	\\builtin set +U
+	\\builtin typeset s
 	if (( $# )); then
-		\read -raN-1 s <<<"$*"
-		\unset s[${#s[*]}-1]
+		\\builtin read -raN-1 s <<<"$*"
+		\\builtin unset s[${#s[*]}-1]
 	else
-		\read -raN-1 s
+		\\builtin read -raN-1 s
 	fi
-	\typeset -i i=0 n=${#s[*]}
+	\\builtin typeset -i i=0 n=${#s[*]}
 
 	while (( i < n )); do
 		((# Lbafh_v = (Lbafh_v + s[i++] + 1) * 1025 ))
@@ -361,7 +374,7 @@
 	done
 }
 function Lbafh_finish {
-	\typeset -Ui t
+	\\builtin typeset -Ui t
 
 	((# t = (((Lbafh_v >> 7) & 0x01010101) * 0x1B) ^ \
 	    ((Lbafh_v << 1) & 0xFEFEFEFE) ))
@@ -373,42 +386,33 @@
 # 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 {
-	\set -o noglob
-	\cat "$@" | while \read _line; do
+	\\builtin set -o noglob
+	\\builtin cat "$@" | while \\builtin read _line; do
 		_line=${_line%%#*}
-		[[ -n $_line ]] && \builtin print -r -- $_line
+		[[ -n $_line ]] && \\builtin print -r -- $_line
 	done
 }
 
-# give MidnightBSD's laffer1 a bit of csh feeling
-function setenv {
-	if (( $# )); then
-		\eval '\export "$1"="${2:-}"'
-	else
-		\typeset -x
-	fi
-}
-
 # toggle built-in aliases and utilities, and aliases and functions from mkshrc
 function enable {
-	\typeset doprnt=0 mode=1 x y z rv=0
-	\typeset b_alias i_alias i_func nalias=0 nfunc=0 i_all
-	\set -A b_alias
-	\set -A i_alias
-	\set -A i_func
+	\\builtin typeset doprnt=0 mode=1 x y z rv=0
+	\\builtin typeset b_alias i_alias i_func nalias=0 nfunc=0 i_all
+	\\builtin set -A b_alias
+	\\builtin set -A i_alias
+	\\builtin set -A i_func
 
 	# accumulate mksh built-in aliases, in ASCIIbetical order
-	i_alias[nalias]=autoload; b_alias[nalias++]='\typeset -fu'
-	i_alias[nalias]=functions; b_alias[nalias++]='\typeset -f'
-	i_alias[nalias]=hash; b_alias[nalias++]='\builtin alias -t'
-	i_alias[nalias]=history; b_alias[nalias++]='\builtin fc -l'
-	i_alias[nalias]=integer; b_alias[nalias++]='\typeset -i'
-	i_alias[nalias]=local; b_alias[nalias++]='\typeset'
-	i_alias[nalias]=login; b_alias[nalias++]='\exec login'
-	i_alias[nalias]=nameref; b_alias[nalias++]='\typeset -n'
+	i_alias[nalias]=autoload; b_alias[nalias++]='\\builtin typeset -fu'
+	i_alias[nalias]=functions; b_alias[nalias++]='\\builtin typeset -f'
+	i_alias[nalias]=hash; b_alias[nalias++]='\\builtin alias -t'
+	i_alias[nalias]=history; b_alias[nalias++]='\\builtin fc -l'
+	i_alias[nalias]=integer; b_alias[nalias++]='\\builtin typeset -i'
+	i_alias[nalias]=local; b_alias[nalias++]='\\builtin typeset'
+	i_alias[nalias]=login; b_alias[nalias++]='\\builtin exec login'
+	i_alias[nalias]=nameref; b_alias[nalias++]='\\builtin typeset -n'
 	i_alias[nalias]=nohup; b_alias[nalias++]='nohup '
-	i_alias[nalias]=r; b_alias[nalias++]='\builtin fc -e -'
-	i_alias[nalias]=type; b_alias[nalias++]='\builtin whence -v'
+	i_alias[nalias]=r; b_alias[nalias++]='\\builtin fc -e -'
+	i_alias[nalias]=type; b_alias[nalias++]='\\builtin whence -v'
 
 	# accumulate mksh built-in utilities, in definition order, even ifndef
 	i_func[nfunc++]=.
@@ -416,6 +420,7 @@
 	i_func[nfunc++]='['
 	i_func[nfunc++]=alias
 	i_func[nfunc++]=break
+	# \\builtin cannot, by design, be overridden
 	i_func[nfunc++]=builtin
 	i_func[nfunc++]=cat
 	i_func[nfunc++]=cd
@@ -434,7 +439,6 @@
 	i_func[nfunc++]=jobs
 	i_func[nfunc++]=kill
 	i_func[nfunc++]=let
-	i_func[nfunc++]='let]'
 	i_func[nfunc++]=print
 	i_func[nfunc++]=pwd
 	i_func[nfunc++]=read
@@ -471,11 +475,13 @@
 	i_alias[nalias]=la; b_alias[nalias++]='l -a'
 	i_alias[nalias]=ll; b_alias[nalias++]='l -l'
 	i_alias[nalias]=lo; b_alias[nalias++]='l -alo'
-	i_alias[nalias]=doch; b_alias[nalias++]='sudo mksh -c "$(\builtin fc -ln -1)"'
+	i_alias[nalias]=doch; b_alias[nalias++]='sudo mksh -c "$(\\builtin fc -ln -1)"'
 	i_alias[nalias]=rot13; b_alias[nalias++]='tr abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM'
-	i_alias[nalias]=cls; b_alias[nalias++]='\builtin print -n \\ec'
+	i_alias[nalias]=cls; b_alias[nalias++]='\\builtin print -n \\ec'
 
 	# accumulate functions from dot.mkshrc, in definition order
+	i_func[nfunc++]=setenv
+	i_func[nfunc++]=smores
 	i_func[nfunc++]=hd
 	i_func[nfunc++]=chpwd
 	i_func[nfunc++]=cd
@@ -483,21 +489,19 @@
 	i_func[nfunc++]=dirs
 	i_func[nfunc++]=popd
 	i_func[nfunc++]=pushd
-	i_func[nfunc++]=smores
 	i_func[nfunc++]=Lb64decode
 	i_func[nfunc++]=Lb64encode
 	i_func[nfunc++]=Lbafh_init
 	i_func[nfunc++]=Lbafh_add
 	i_func[nfunc++]=Lbafh_finish
 	i_func[nfunc++]=Lstripcom
-	i_func[nfunc++]=setenv
 	i_func[nfunc++]=enable
 
 	# collect all identifiers, sorted ASCIIbetically
-	\set -sA i_all -- "${i_alias[@]}" "${i_func[@]}"
+	\\builtin set -sA i_all -- "${i_alias[@]}" "${i_func[@]}"
 
 	# handle options, we don't do dynamic loading
-	while \getopts "adf:nps" x; do
+	while \\builtin getopts "adf:nps" x; do
 		case $x {
 		(a)
 			mode=-1
@@ -506,8 +510,8 @@
 			# deliberately causing an error, like bash-static
 			;|
 		(f)
-			\builtin print -u2 enable: dynamic loading not available
-			\return 2
+			\\builtin print -ru2 enable: dynamic loading not available
+			\\builtin return 2
 			;;
 		(n)
 			mode=0
@@ -516,88 +520,89 @@
 			doprnt=1
 			;;
 		(s)
-			\set -sA i_all -- . : break continue eval exec exit \
-			    export readonly return set shift times trap unset
+			\\builtin set -sA i_all -- . : break continue eval \
+			    exec exit export readonly return set shift times \
+			    trap unset
 			;;
 		(*)
-			\builtin print -u2 enable: usage: \
+			\\builtin print -ru2 enable: usage: \
 			    "enable [-adnps] [-f filename] [name ...]"
 			return 2
 			;;
 		}
 	done
-	\shift $((OPTIND - 1))
+	\\builtin shift $((OPTIND - 1))
 
 	# display builtins enabled/disabled/all/special?
 	if (( doprnt || ($# == 0) )); then
 		for x in "${i_all[@]}"; do
-			y=$(\alias "$x") || y=
-			[[ $y = "$x='\\builtin whence -p $x >/dev/null || (\\builtin print mksh: $x: not found; exit 127) && \$(\\builtin whence -p $x)'" ]]; z=$?
+			y=$(\\builtin alias "$x") || y=
+			[[ $y = "$x='\\\\builtin whence -p $x >/dev/null || (\\\\builtin print -r mksh: $x: not found; \\\\builtin exit 127) && \$(\\\\builtin whence -p $x)'" ]]; z=$?
 			case $mode:$z {
 			(-1:0|0:0)
-				\print -r -- "enable -n $x"
+				\\builtin print -r -- "enable -n $x"
 				;;
 			(-1:1|1:1)
-				\print -r -- "enable $x"
+				\\builtin print -r -- "enable $x"
 				;;
 			}
 		done
-		\return 0
+		\\builtin return 0
 	fi
 
 	for x in "$@"; do
 		z=0
 		for y in "${i_alias[@]}" "${i_func[@]}"; do
-			[[ $x = "$y" ]] || \continue
+			[[ $x = "$y" ]] || \\builtin continue
 			z=1
-			\break
+			\\builtin break
 		done
 		if (( !z )); then
-			\builtin print -ru2 enable: "$x": not a shell builtin
+			\\builtin print -ru2 enable: "$x": not a shell builtin
 			rv=1
-			\continue
+			\\builtin continue
 		fi
 		if (( !mode )); then
 			# disable this
-			\alias "$x=\\builtin whence -p $x >/dev/null || (\\builtin print mksh: $x: not found; exit 127) && \$(\\builtin whence -p $x)"
+			\\builtin alias "$x=\\\\builtin whence -p $x >/dev/null || (\\\\builtin print -r mksh: $x: not found; \\\\builtin exit 127) && \$(\\\\builtin whence -p $x)"
 		else
 			# find out if this is an alias or not, first
 			z=0
 			y=-1
 			while (( ++y < nalias )); do
-				[[ $x = "${i_alias[y]}" ]] || \continue
+				[[ $x = "${i_alias[y]}" ]] || \\builtin continue
 				z=1
-				\break
+				\\builtin break
 			done
 			if (( z )); then
 				# re-enable the original alias body
-				\alias "$x=${b_alias[y]}"
+				\\builtin alias "$x=${b_alias[y]}"
 			else
 				# re-enable the original utility/function
-				\unalias "$x"
+				\\builtin unalias "$x"
 			fi
 		fi
 	done
-	\return $rv
+	\\builtin return $rv
 }
 
 \: place customisations below this line
 
 for p in ~/.etc/bin ~/bin; do
-	[[ -d $p/. ]] || \continue
-	#XXX OS/2
-	[[ :$PATH: = *:$p:* ]] || PATH=$p:$PATH
+	[[ -d $p/. ]] || \\builtin continue
+	[[ $PATHSEP$PATH$PATHSEP = *"$PATHSEP$p$PATHSEP"* ]] || \
+	    PATH=$p$PATHSEP$PATH
 done
 
-\export SHELL=$MKSH MANWIDTH=80 LESSHISTFILE=-
-\alias cls='\builtin print -n \\ec'
+\\builtin export SHELL=$MKSH MANWIDTH=80 LESSHISTFILE=-
+\\builtin alias cls='\\builtin print -n \\ec'
 
-#\unset LANGUAGE LC_ADDRESS LC_ALL LC_COLLATE LC_IDENTIFICATION LC_MONETARY \
+#\\builtin 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
-#\export LANG=C LC_CTYPE=$p LC_MEASUREMENT=$p LC_MESSAGES=$p LC_PAPER=$p
-#\set -U
+#\\builtin export LANG=C LC_CTYPE=$p LC_MEASUREMENT=$p LC_MESSAGES=$p LC_PAPER=$p
+#\\builtin set -U
 
-\unset p
+\\builtin unset p
 
 \: place customisations above this line
diff --git a/src/edit.c b/src/edit.c
index 4ed5ca3..58eaf7f 100644
--- a/src/edit.c
+++ b/src/edit.c
@@ -5,7 +5,7 @@
 
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
- *		 2011, 2012, 2013, 2014, 2015, 2016
+ *		 2011, 2012, 2013, 2014, 2015, 2016, 2017
  *	mirabilos <m@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -28,7 +28,7 @@
 
 #ifndef MKSH_NO_CMDLINE_EDITING
 
-__RCSID("$MirOS: src/bin/mksh/edit.c,v 1.312 2016/11/11 23:48:28 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/edit.c,v 1.321 2017/04/12 16:46:20 tg Exp $");
 
 /*
  * in later versions we might use libtermcap for this, but since external
@@ -145,6 +145,9 @@
 static int
 x_getc(void)
 {
+#ifdef __OS2__
+	return (_read_kbd(0, 1, 0));
+#else
 	char c;
 	ssize_t n;
 
@@ -166,6 +169,7 @@
 			x_mode(true);
 		}
 	return ((n == 1) ? (int)(unsigned char)c : -1);
+#endif
 }
 
 static void
@@ -339,7 +343,7 @@
 	 * and if so, discern "~foo/bar" and "~/baz" from "~blah";
 	 * if we have a directory part (the former), try to expand
 	 */
-	if (*s == '~' && (cp = mksh_sdirsep(s)) != NULL) {
+	if (*s == '~' && (cp = /* not sdirsep */ strchr(s, '/')) != NULL) {
 		/* ok, so split into "~foo"/"bar" or "~"/"baz" */
 		*cp++ = 0;
 		/* try to expand the tilde */
@@ -658,7 +662,7 @@
 			}
 		}
 
-		if (*toglob == '~' && !mksh_vdirsep(toglob)) {
+		if (*toglob == '~' && /* not vdirsep */ !vstrchr(toglob, '/')) {
 			/* neither for '~foo' (but '~foo/bar') */
 			*flagsp |= XCF_IS_NOSPACE;
 			goto dont_add_glob;
@@ -949,6 +953,7 @@
 static char **x_histp;		/* history position */
 static int x_nextcmd;		/* for newline-and-next */
 static char **x_histncp;	/* saved x_histp for " */
+static char **x_histmcp;	/* saved x_histp for " */
 static char *xmp;		/* mark pointer */
 static unsigned char x_last_command;
 static unsigned char (*x_tab)[X_TABSZ];	/* key definition */
@@ -1163,6 +1168,7 @@
 x_modified(void)
 {
 	if (!modified) {
+		x_histmcp = x_histp;
 		x_histp = histptr + 1;
 		modified = 1;
 	}
@@ -1239,7 +1245,7 @@
 	xlp_valid = true;
 	xmp = NULL;
 	x_curprefix = 0;
-	x_histp = histptr + 1;
+	x_histmcp = x_histp = histptr + 1;
 	x_last_command = XFUNC_error;
 
 	x_init_prompt(true);
@@ -1780,12 +1786,11 @@
 static int
 x_end_of_text(int c MKSH_A_UNUSED)
 {
-	unsigned char tmp;
-	char *cp = (void *)&tmp;
+	unsigned char tmp[1], *cp = tmp;
 
-	tmp = isedchar(edchars.eof) ? (unsigned char)edchars.eof :
+	*tmp = isedchar(edchars.eof) ? (unsigned char)edchars.eof :
 	    (unsigned char)CTRL('D');
-	x_zotc3(&cp);
+	x_zotc3((char **)&cp);
 	x_putc('\r');
 	x_putc('\n');
 	x_flush();
@@ -1862,9 +1867,11 @@
 static int
 x_nl_next_com(int c MKSH_A_UNUSED)
 {
-	if (!x_histncp || (x_histp != x_histncp && x_histp != histptr + 1))
+	if (!modified)
+		x_histmcp = x_histp;
+	if (!x_histncp || (x_histmcp != x_histncp && x_histmcp != histptr + 1))
 		/* fresh start of ^O */
-		x_histncp = x_histp;
+		x_histncp = x_histmcp;
 	x_nextcmd = source->line - (histptr - x_histncp) + 1;
 	return (x_newline('\n'));
 }
@@ -1922,8 +1929,10 @@
 				offset = -1;
 				break;
 			}
-			if (p > pat)
-				*--p = '\0';
+			if (p > pat) {
+				p = x_bs0(p - 1, pat);
+				*p = '\0';
+			}
 			if (p == pat)
 				offset = -1;
 			else
@@ -2933,8 +2942,12 @@
 			char *cp2;
 
 			width = utf_widthadj(*cp, (const char **)&cp2);
-			while (*cp < cp2)
-				x_putcf(*(*cp)++);
+			if (cp2 == *cp + 1) {
+				(*cp)++;
+				shf_puts("\xEF\xBF\xBD", shl_out);
+			} else
+				while (*cp < cp2)
+					x_putcf(*(*cp)++);
 		} else {
 			(*cp)++;
 			x_putc(c);
@@ -3162,6 +3175,8 @@
 		x_ins(cp);
 		*rcp = ch;
 	}
+	if (!modified)
+		x_histmcp = x_histp;
 	modified = m + 1;
 	return (KSTD);
 }
@@ -3466,7 +3481,7 @@
 static struct edstate	ebuf;
 static struct edstate	undobuf;
 
-static struct edstate	*es;		/* current editor state */
+static struct edstate	*vs;		/* current Vi editing mode state */
 static struct edstate	*undo;
 
 static char *ibuf;			/* input buffer */
@@ -3530,7 +3545,7 @@
 	undobuf.linelen = ebuf.linelen = 0;
 	undobuf.cursor = ebuf.cursor = 0;
 	undobuf.winleft = ebuf.winleft = 0;
-	es = &ebuf;
+	vs = &ebuf;
 	undo = &undobuf;
 
 	x_init_prompt(true);
@@ -3581,7 +3596,7 @@
 				unwind(LSHELL);
 			} else if (isched(c, edchars.eof) &&
 			    state != VVERSION) {
-				if (es->linelen == 0) {
+				if (vs->linelen == 0) {
 					x_vi_zotc(c);
 					c = -1;
 					break;
@@ -3598,15 +3613,15 @@
 	x_putc('\n');
 	x_flush();
 
-	if (c == -1 || (ssize_t)LINE <= es->linelen)
+	if (c == -1 || (ssize_t)LINE <= vs->linelen)
 		return (-1);
 
-	if (es->cbuf != buf)
-		memcpy(buf, es->cbuf, es->linelen);
+	if (vs->cbuf != buf)
+		memcpy(buf, vs->cbuf, vs->linelen);
 
-	buf[es->linelen++] = '\n';
+	buf[vs->linelen++] = '\n';
 
-	return (es->linelen);
+	return (vs->linelen);
 }
 
 static int
@@ -3643,7 +3658,7 @@
 				break;
 			case 0:
 				if (state == VLIT) {
-					es->cursor--;
+					vs->cursor--;
 					refresh(0);
 				} else
 					refresh(insert != 0);
@@ -3665,8 +3680,8 @@
 				state = nextstate(ch);
 				if (state == VSEARCH) {
 					save_cbuf();
-					es->cursor = 0;
-					es->linelen = 0;
+					vs->cursor = 0;
+					vs->linelen = 0;
 					if (putbuf(ch == '/' ? "/" : "?", 1,
 					    false) != 0)
 						return (-1);
@@ -3674,8 +3689,8 @@
 				}
 				if (state == VVERSION) {
 					save_cbuf();
-					es->cursor = 0;
-					es->linelen = 0;
+					vs->cursor = 0;
+					vs->linelen = 0;
 					putbuf(KSH_VERSION,
 					    strlen(KSH_VERSION), false);
 					refresh(0);
@@ -3686,10 +3701,10 @@
 
 	case VLIT:
 		if (is_bad(ch)) {
-			del_range(es->cursor, es->cursor + 1);
+			del_range(vs->cursor, vs->cursor + 1);
 			vi_error();
 		} else
-			es->cbuf[es->cursor++] = ch;
+			vs->cbuf[vs->cursor++] = ch;
 		refresh(1);
 		state = VNORMAL;
 		break;
@@ -3772,8 +3787,8 @@
 		} else if (isched(ch, edchars.erase) || ch == CTRL('h')) {
 			if (srchlen != 0) {
 				srchlen--;
-				es->linelen -= char_len(locpat[srchlen]);
-				es->cursor = es->linelen;
+				vs->linelen -= char_len(locpat[srchlen]);
+				vs->cursor = vs->linelen;
 				refresh(0);
 				return (0);
 			}
@@ -3782,8 +3797,8 @@
 			refresh(0);
 		} else if (isched(ch, edchars.kill)) {
 			srchlen = 0;
-			es->linelen = 1;
-			es->cursor = 1;
+			vs->linelen = 1;
+			vs->cursor = 1;
 			refresh(0);
 			return (0);
 		} else if (isched(ch, edchars.werase)) {
@@ -3793,16 +3808,16 @@
 			new_es.cursor = srchlen;
 			new_es.cbuf = locpat;
 
-			save_es = es;
-			es = &new_es;
+			save_es = vs;
+			vs = &new_es;
 			n = backword(1);
-			es = save_es;
+			vs = save_es;
 
 			i = (unsigned)srchlen;
 			while (--i >= n)
-				es->linelen -= char_len(locpat[i]);
+				vs->linelen -= char_len(locpat[i]);
 			srchlen = (int)n;
-			es->cursor = es->linelen;
+			vs->cursor = vs->linelen;
 			refresh(0);
 			return (0);
 		} else {
@@ -3811,17 +3826,17 @@
 			else {
 				locpat[srchlen++] = ch;
 				if (ISCTRL(ch) && /* but not C1 */ ch < 0x80) {
-					if ((size_t)es->linelen + 2 >
-					    (size_t)es->cbufsize)
+					if ((size_t)vs->linelen + 2 >
+					    (size_t)vs->cbufsize)
 						vi_error();
-					es->cbuf[es->linelen++] = '^';
-					es->cbuf[es->linelen++] = UNCTRL(ch);
+					vs->cbuf[vs->linelen++] = '^';
+					vs->cbuf[vs->linelen++] = UNCTRL(ch);
 				} else {
-					if (es->linelen >= es->cbufsize)
+					if (vs->linelen >= vs->cbufsize)
 						vi_error();
-					es->cbuf[es->linelen++] = ch;
+					vs->cbuf[vs->linelen++] = ch;
 				}
-				es->cursor = es->linelen;
+				vs->cursor = vs->linelen;
 				refresh(0);
 			}
 			return (0);
@@ -3834,18 +3849,18 @@
 		switch (ch) {
 		case 'A':
 			/* the cursor may not be at the BOL */
-			if (!es->cursor)
+			if (!vs->cursor)
 				break;
 			/* nor further in the line than we can search for */
-			if ((size_t)es->cursor >= sizeof(srchpat) - 1)
-				es->cursor = sizeof(srchpat) - 2;
+			if ((size_t)vs->cursor >= sizeof(srchpat) - 1)
+				vs->cursor = sizeof(srchpat) - 2;
 			/* anchor the search pattern */
 			srchpat[0] = '^';
 			/* take the current line up to the cursor */
-			memmove(srchpat + 1, es->cbuf, es->cursor);
-			srchpat[es->cursor + 1] = '\0';
+			memmove(srchpat + 1, vs->cbuf, vs->cursor);
+			srchpat[vs->cursor + 1] = '\0';
 			/* set a magic flag */
-			argc1 = 2 + (int)es->cursor;
+			argc1 = 2 + (int)vs->cursor;
 			/* and emulate a backwards history search */
 			lastsearch = '/';
 			*curcmd = 'n';
@@ -3942,52 +3957,52 @@
 
 	if (isched(ch, edchars.erase) || ch == CTRL('h')) {
 		if (insert == REPLACE) {
-			if (es->cursor == undo->cursor) {
+			if (vs->cursor == undo->cursor) {
 				vi_error();
 				return (0);
 			}
 			if (inslen > 0)
 				inslen--;
-			es->cursor--;
-			if (es->cursor >= undo->linelen)
-				es->linelen--;
+			vs->cursor--;
+			if (vs->cursor >= undo->linelen)
+				vs->linelen--;
 			else
-				es->cbuf[es->cursor] = undo->cbuf[es->cursor];
+				vs->cbuf[vs->cursor] = undo->cbuf[vs->cursor];
 		} else {
-			if (es->cursor == 0)
+			if (vs->cursor == 0)
 				return (0);
 			if (inslen > 0)
 				inslen--;
-			es->cursor--;
-			es->linelen--;
-			memmove(&es->cbuf[es->cursor], &es->cbuf[es->cursor + 1],
-			    es->linelen - es->cursor + 1);
+			vs->cursor--;
+			vs->linelen--;
+			memmove(&vs->cbuf[vs->cursor], &vs->cbuf[vs->cursor + 1],
+			    vs->linelen - vs->cursor + 1);
 		}
 		expanded = NONE;
 		return (0);
 	}
 	if (isched(ch, edchars.kill)) {
-		if (es->cursor != 0) {
+		if (vs->cursor != 0) {
 			inslen = 0;
-			memmove(es->cbuf, &es->cbuf[es->cursor],
-			    es->linelen - es->cursor);
-			es->linelen -= es->cursor;
-			es->cursor = 0;
+			memmove(vs->cbuf, &vs->cbuf[vs->cursor],
+			    vs->linelen - vs->cursor);
+			vs->linelen -= vs->cursor;
+			vs->cursor = 0;
 		}
 		expanded = NONE;
 		return (0);
 	}
 	if (isched(ch, edchars.werase)) {
-		if (es->cursor != 0) {
+		if (vs->cursor != 0) {
 			tcursor = backword(1);
-			memmove(&es->cbuf[tcursor], &es->cbuf[es->cursor],
-			    es->linelen - es->cursor);
-			es->linelen -= es->cursor - tcursor;
-			if (inslen < es->cursor - tcursor)
+			memmove(&vs->cbuf[tcursor], &vs->cbuf[vs->cursor],
+			    vs->linelen - vs->cursor);
+			vs->linelen -= vs->cursor - tcursor;
+			if (inslen < vs->cursor - tcursor)
 				inslen = 0;
 			else
-				inslen -= es->cursor - tcursor;
-			es->cursor = tcursor;
+				inslen -= vs->cursor - tcursor;
+			vs->cursor = tcursor;
 		}
 		expanded = NONE;
 		return (0);
@@ -4034,7 +4049,7 @@
 		break;
 
 	case CTRL('e'):
-		print_expansions(es, 0);
+		print_expansions(vs, 0);
 		break;
 
 	case CTRL('i'):
@@ -4046,17 +4061,17 @@
 	/* end nonstandard vi commands } */
 
 	default:
-		if (es->linelen >= es->cbufsize - 1)
+		if (vs->linelen >= vs->cbufsize - 1)
 			return (-1);
 		ibuf[inslen++] = ch;
 		if (insert == INSERT) {
-			memmove(&es->cbuf[es->cursor + 1], &es->cbuf[es->cursor],
-			    es->linelen - es->cursor);
-			es->linelen++;
+			memmove(&vs->cbuf[vs->cursor + 1], &vs->cbuf[vs->cursor],
+			    vs->linelen - vs->cursor);
+			vs->linelen++;
 		}
-		es->cbuf[es->cursor++] = ch;
-		if (insert == REPLACE && es->cursor > es->linelen)
-			es->linelen++;
+		vs->cbuf[vs->cursor++] = ch;
+		if (insert == REPLACE && vs->cursor > vs->linelen)
+			vs->linelen++;
 		expanded = NONE;
 	}
 	return (0);
@@ -4075,18 +4090,18 @@
 
 	if (is_move(*cmd)) {
 		if ((cur = domove(argcnt, cmd, 0)) >= 0) {
-			if (cur == es->linelen && cur != 0)
+			if (cur == vs->linelen && cur != 0)
 				cur--;
-			es->cursor = cur;
+			vs->cursor = cur;
 		} else
 			return (-1);
 	} else {
 		/* Don't save state in middle of macro.. */
 		if (is_undoable(*cmd) && !macro.p) {
-			undo->winleft = es->winleft;
-			memmove(undo->cbuf, es->cbuf, es->linelen);
-			undo->linelen = es->linelen;
-			undo->cursor = es->cursor;
+			undo->winleft = vs->winleft;
+			memmove(undo->cbuf, vs->cbuf, vs->linelen);
+			undo->linelen = vs->linelen;
+			undo->cursor = vs->cursor;
 			lastac = argcnt;
 			memmove(lastcmd, cmd, MAXVICMD);
 		}
@@ -4141,8 +4156,8 @@
 		case 'a':
 			modified = 1;
 			hnum = hlast;
-			if (es->linelen != 0)
-				es->cursor++;
+			if (vs->linelen != 0)
+				vs->cursor++;
 			insert = INSERT;
 			break;
 
@@ -4150,13 +4165,13 @@
 			modified = 1;
 			hnum = hlast;
 			del_range(0, 0);
-			es->cursor = es->linelen;
+			vs->cursor = vs->linelen;
 			insert = INSERT;
 			break;
 
 		case 'S':
-			es->cursor = domove(1, "^", 1);
-			del_range(es->cursor, es->linelen);
+			vs->cursor = domove(1, "^", 1);
+			del_range(vs->cursor, vs->linelen);
 			modified = 1;
 			hnum = hlast;
 			insert = INSERT;
@@ -4172,7 +4187,7 @@
 		case 'y':
 			if (*cmd == cmd[1]) {
 				c1 = *cmd == 'c' ? domove(1, "^", 1) : 0;
-				c2 = es->linelen;
+				c2 = vs->linelen;
 			} else if (!is_move(cmd[1]))
 				return (-1);
 			else {
@@ -4180,18 +4195,18 @@
 					return (-1);
 				if (*cmd == 'c' &&
 				    (cmd[1] == 'w' || cmd[1] == 'W') &&
-				    !ksh_isspace(es->cbuf[es->cursor])) {
+				    !ksh_isspace(vs->cbuf[vs->cursor])) {
 					do {
 						--ncursor;
-					} while (ksh_isspace(es->cbuf[ncursor]));
+					} while (ksh_isspace(vs->cbuf[ncursor]));
 					ncursor++;
 				}
-				if (ncursor > es->cursor) {
-					c1 = es->cursor;
+				if (ncursor > vs->cursor) {
+					c1 = vs->cursor;
 					c2 = ncursor;
 				} else {
 					c1 = ncursor;
-					c2 = es->cursor;
+					c2 = vs->cursor;
 					if (cmd[1] == '%')
 						c2++;
 				}
@@ -4200,7 +4215,7 @@
 				yank_range(c1, c2);
 			if (*cmd != 'y') {
 				del_range(c1, c2);
-				es->cursor = c1;
+				vs->cursor = c1;
 			}
 			if (*cmd == 'c') {
 				modified = 1;
@@ -4212,13 +4227,13 @@
 		case 'p':
 			modified = 1;
 			hnum = hlast;
-			if (es->linelen != 0)
-				es->cursor++;
+			if (vs->linelen != 0)
+				vs->cursor++;
 			while (putbuf(ybuf, yanklen, false) == 0 &&
 			    --argcnt > 0)
 				;
-			if (es->cursor != 0)
-				es->cursor--;
+			if (vs->cursor != 0)
+				vs->cursor--;
 			if (argcnt != 0)
 				return (-1);
 			break;
@@ -4230,8 +4245,8 @@
 			while (putbuf(ybuf, yanklen, false) == 0 &&
 			    --argcnt > 0)
 				any = 1;
-			if (any && es->cursor != 0)
-				es->cursor--;
+			if (any && vs->cursor != 0)
+				vs->cursor--;
 			if (argcnt != 0)
 				return (-1);
 			break;
@@ -4239,15 +4254,15 @@
 		case 'C':
 			modified = 1;
 			hnum = hlast;
-			del_range(es->cursor, es->linelen);
+			del_range(vs->cursor, vs->linelen);
 			insert = INSERT;
 			break;
 
 		case 'D':
-			yank_range(es->cursor, es->linelen);
-			del_range(es->cursor, es->linelen);
-			if (es->cursor != 0)
-				es->cursor--;
+			yank_range(vs->cursor, vs->linelen);
+			del_range(vs->cursor, vs->linelen);
+			if (vs->cursor != 0)
+				vs->cursor--;
 			break;
 
 		case 'g':
@@ -4276,7 +4291,7 @@
 		case 'I':
 			modified = 1;
 			hnum = hlast;
-			es->cursor = domove(1, "^", 1);
+			vs->cursor = domove(1, "^", 1);
 			insert = INSERT;
 			break;
 
@@ -4303,7 +4318,7 @@
 			break;
 
 		case 'r':
-			if (es->linelen == 0)
+			if (vs->linelen == 0)
 				return (-1);
 			modified = 1;
 			hnum = hlast;
@@ -4312,11 +4327,11 @@
 			else {
 				int n;
 
-				if (es->cursor + argcnt > es->linelen)
+				if (vs->cursor + argcnt > vs->linelen)
 					return (-1);
 				for (n = 0; n < argcnt; ++n)
-					es->cbuf[es->cursor + n] = cmd[1];
-				es->cursor += n - 1;
+					vs->cbuf[vs->cursor + n] = cmd[1];
+				vs->cursor += n - 1;
 			}
 			break;
 
@@ -4327,66 +4342,66 @@
 			break;
 
 		case 's':
-			if (es->linelen == 0)
+			if (vs->linelen == 0)
 				return (-1);
 			modified = 1;
 			hnum = hlast;
-			if (es->cursor + argcnt > es->linelen)
-				argcnt = es->linelen - es->cursor;
-			del_range(es->cursor, es->cursor + argcnt);
+			if (vs->cursor + argcnt > vs->linelen)
+				argcnt = vs->linelen - vs->cursor;
+			del_range(vs->cursor, vs->cursor + argcnt);
 			insert = INSERT;
 			break;
 
 		case 'v':
 			if (!argcnt) {
-				if (es->linelen == 0)
+				if (vs->linelen == 0)
 					return (-1);
 				if (modified) {
-					es->cbuf[es->linelen] = '\0';
-					histsave(&source->line, es->cbuf,
+					vs->cbuf[vs->linelen] = '\0';
+					histsave(&source->line, vs->cbuf,
 					    HIST_STORE, true);
 				} else
 					argcnt = source->line + 1 -
 					    (hlast - hnum);
 			}
 			if (argcnt)
-				shf_snprintf(es->cbuf, es->cbufsize, Tf_sd,
+				shf_snprintf(vs->cbuf, vs->cbufsize, Tf_sd,
 				    "fc -e ${VISUAL:-${EDITOR:-vi}} --",
 				    argcnt);
 			else
-				strlcpy(es->cbuf,
+				strlcpy(vs->cbuf,
 				    "fc -e ${VISUAL:-${EDITOR:-vi}} --",
-				    es->cbufsize);
-			es->linelen = strlen(es->cbuf);
+				    vs->cbufsize);
+			vs->linelen = strlen(vs->cbuf);
 			return (2);
 
 		case 'x':
-			if (es->linelen == 0)
+			if (vs->linelen == 0)
 				return (-1);
 			modified = 1;
 			hnum = hlast;
-			if (es->cursor + argcnt > es->linelen)
-				argcnt = es->linelen - es->cursor;
-			yank_range(es->cursor, es->cursor + argcnt);
-			del_range(es->cursor, es->cursor + argcnt);
+			if (vs->cursor + argcnt > vs->linelen)
+				argcnt = vs->linelen - vs->cursor;
+			yank_range(vs->cursor, vs->cursor + argcnt);
+			del_range(vs->cursor, vs->cursor + argcnt);
 			break;
 
 		case 'X':
-			if (es->cursor > 0) {
+			if (vs->cursor > 0) {
 				modified = 1;
 				hnum = hlast;
-				if (es->cursor < argcnt)
-					argcnt = es->cursor;
-				yank_range(es->cursor - argcnt, es->cursor);
-				del_range(es->cursor - argcnt, es->cursor);
-				es->cursor -= argcnt;
+				if (vs->cursor < argcnt)
+					argcnt = vs->cursor;
+				yank_range(vs->cursor - argcnt, vs->cursor);
+				del_range(vs->cursor - argcnt, vs->cursor);
+				vs->cursor -= argcnt;
 			} else
 				return (-1);
 			break;
 
 		case 'u':
-			t = es;
-			es = undo;
+			t = vs;
+			vs = undo;
 			undo = t;
 			break;
 
@@ -4434,7 +4449,7 @@
 			}
 			if (argcnt >= 2) {
 				/* flag from cursor-up command */
-				es->cursor = argcnt - 2;
+				vs->cursor = argcnt - 2;
 				return (0);
 			}
 			break;
@@ -4475,16 +4490,16 @@
 				}
 				modified = 1;
 				hnum = hlast;
-				if (es->cursor != es->linelen)
-					es->cursor++;
+				if (vs->cursor != vs->linelen)
+					vs->cursor++;
 				while (*p && !issp(*p)) {
 					argcnt++;
 					p++;
 				}
 				if (putbuf(T1space, 1, false) != 0 ||
 				    putbuf(sp, argcnt, false) != 0) {
-					if (es->cursor != 0)
-						es->cursor--;
+					if (vs->cursor != 0)
+						vs->cursor--;
 					return (-1);
 				}
 				insert = INSERT;
@@ -4496,10 +4511,10 @@
 				char *p;
 				int i;
 
-				if (es->linelen == 0)
+				if (vs->linelen == 0)
 					return (-1);
 				for (i = 0; i < argcnt; i++) {
-					p = &es->cbuf[es->cursor];
+					p = &vs->cbuf[vs->cursor];
 					if (ksh_islower(*p)) {
 						modified = 1;
 						hnum = hlast;
@@ -4509,18 +4524,18 @@
 						hnum = hlast;
 						*p = ksh_tolower(*p);
 					}
-					if (es->cursor < es->linelen - 1)
-						es->cursor++;
+					if (vs->cursor < vs->linelen - 1)
+						vs->cursor++;
 				}
 				break;
 			}
 
 		case '#':
 			{
-				int ret = x_do_comment(es->cbuf, es->cbufsize,
-				    &es->linelen);
+				int ret = x_do_comment(vs->cbuf, vs->cbufsize,
+				    &vs->linelen);
 				if (ret >= 0)
-					es->cursor = 0;
+					vs->cursor = 0;
 				return (ret);
 			}
 
@@ -4528,7 +4543,7 @@
 		case '=':
 		/* Nonstandard vi/ksh */
 		case CTRL('e'):
-			print_expansions(es, 1);
+			print_expansions(vs, 1);
 			break;
 
 
@@ -4564,13 +4579,13 @@
 		case '[':
 		case 'O':
 			state = VPREFIX2;
-			if (es->linelen != 0)
-				es->cursor++;
+			if (vs->linelen != 0)
+				vs->cursor++;
 			insert = INSERT;
 			return (0);
 		}
-		if (insert == 0 && es->cursor != 0 && es->cursor >= es->linelen)
-			es->cursor--;
+		if (insert == 0 && vs->cursor != 0 && vs->cursor >= vs->linelen)
+			vs->cursor--;
 	}
 	return (0);
 }
@@ -4583,30 +4598,30 @@
 
 	switch (*cmd) {
 	case 'b':
-		if (!sub && es->cursor == 0)
+		if (!sub && vs->cursor == 0)
 			return (-1);
 		ncursor = backword(argcnt);
 		break;
 
 	case 'B':
-		if (!sub && es->cursor == 0)
+		if (!sub && vs->cursor == 0)
 			return (-1);
 		ncursor = Backword(argcnt);
 		break;
 
 	case 'e':
-		if (!sub && es->cursor + 1 >= es->linelen)
+		if (!sub && vs->cursor + 1 >= vs->linelen)
 			return (-1);
 		ncursor = endword(argcnt);
-		if (sub && ncursor < es->linelen)
+		if (sub && ncursor < vs->linelen)
 			ncursor++;
 		break;
 
 	case 'E':
-		if (!sub && es->cursor + 1 >= es->linelen)
+		if (!sub && vs->cursor + 1 >= vs->linelen)
 			return (-1);
 		ncursor = Endword(argcnt);
-		if (sub && ncursor < es->linelen)
+		if (sub && ncursor < vs->linelen)
 			ncursor++;
 		break;
 
@@ -4634,32 +4649,32 @@
 
 	case 'h':
 	case CTRL('h'):
-		if (!sub && es->cursor == 0)
+		if (!sub && vs->cursor == 0)
 			return (-1);
-		ncursor = es->cursor - argcnt;
+		ncursor = vs->cursor - argcnt;
 		if (ncursor < 0)
 			ncursor = 0;
 		break;
 
 	case ' ':
 	case 'l':
-		if (!sub && es->cursor + 1 >= es->linelen)
+		if (!sub && vs->cursor + 1 >= vs->linelen)
 			return (-1);
-		if (es->linelen != 0) {
-			ncursor = es->cursor + argcnt;
-			if (ncursor > es->linelen)
-				ncursor = es->linelen;
+		if (vs->linelen != 0) {
+			ncursor = vs->cursor + argcnt;
+			if (ncursor > vs->linelen)
+				ncursor = vs->linelen;
 		}
 		break;
 
 	case 'w':
-		if (!sub && es->cursor + 1 >= es->linelen)
+		if (!sub && vs->cursor + 1 >= vs->linelen)
 			return (-1);
 		ncursor = forwword(argcnt);
 		break;
 
 	case 'W':
-		if (!sub && es->cursor + 1 >= es->linelen)
+		if (!sub && vs->cursor + 1 >= vs->linelen)
 			return (-1);
 		ncursor = Forwword(argcnt);
 		break;
@@ -4670,43 +4685,43 @@
 
 	case '^':
 		ncursor = 0;
-		while (ncursor < es->linelen - 1 &&
-		    ksh_isspace(es->cbuf[ncursor]))
+		while (ncursor < vs->linelen - 1 &&
+		    ksh_isspace(vs->cbuf[ncursor]))
 			ncursor++;
 		break;
 
 	case '|':
 		ncursor = argcnt;
-		if (ncursor > es->linelen)
-			ncursor = es->linelen;
+		if (ncursor > vs->linelen)
+			ncursor = vs->linelen;
 		if (ncursor)
 			ncursor--;
 		break;
 
 	case '$':
-		if (es->linelen != 0)
-			ncursor = es->linelen;
+		if (vs->linelen != 0)
+			ncursor = vs->linelen;
 		else
 			ncursor = 0;
 		break;
 
 	case '%':
-		ncursor = es->cursor;
-		while (ncursor < es->linelen &&
-		    (i = bracktype(es->cbuf[ncursor])) == 0)
+		ncursor = vs->cursor;
+		while (ncursor < vs->linelen &&
+		    (i = bracktype(vs->cbuf[ncursor])) == 0)
 			ncursor++;
-		if (ncursor == es->linelen)
+		if (ncursor == vs->linelen)
 			return (-1);
 		bcount = 1;
 		do {
 			if (i > 0) {
-				if (++ncursor >= es->linelen)
+				if (++ncursor >= vs->linelen)
 					return (-1);
 			} else {
 				if (--ncursor < 0)
 					return (-1);
 			}
-			t = bracktype(es->cbuf[ncursor]);
+			t = bracktype(vs->cbuf[ncursor]);
 			if (t == i)
 				bcount++;
 			else if (t == -i)
@@ -4728,8 +4743,8 @@
 	while (count-- > 0)
 		if (putbuf(ibuf, inslen, tobool(insert == REPLACE)) != 0)
 			return (-1);
-	if (es->cursor > 0)
-		es->cursor--;
+	if (vs->cursor > 0)
+		vs->cursor--;
 	insert = 0;
 	return (0);
 }
@@ -4739,7 +4754,7 @@
 {
 	yanklen = b - a;
 	if (yanklen != 0)
-		memmove(ybuf, &es->cbuf[a], yanklen);
+		memmove(ybuf, &vs->cbuf[a], yanklen);
 }
 
 static int
@@ -4777,17 +4792,17 @@
 static void
 save_cbuf(void)
 {
-	memmove(holdbufp, es->cbuf, es->linelen);
-	holdlen = es->linelen;
+	memmove(holdbufp, vs->cbuf, vs->linelen);
+	holdlen = vs->linelen;
 	holdbufp[holdlen] = '\0';
 }
 
 static void
 restore_cbuf(void)
 {
-	es->cursor = 0;
-	es->linelen = holdlen;
-	memmove(es->cbuf, holdbufp, holdlen);
+	vs->cursor = 0;
+	vs->linelen = holdlen;
+	memmove(vs->cbuf, holdbufp, holdlen);
 }
 
 /* return a new edstate */
@@ -4838,28 +4853,28 @@
 	if (len == 0)
 		return (0);
 	if (repl) {
-		if (es->cursor + len >= es->cbufsize)
+		if (vs->cursor + len >= vs->cbufsize)
 			return (-1);
-		if (es->cursor + len > es->linelen)
-			es->linelen = es->cursor + len;
+		if (vs->cursor + len > vs->linelen)
+			vs->linelen = vs->cursor + len;
 	} else {
-		if (es->linelen + len >= es->cbufsize)
+		if (vs->linelen + len >= vs->cbufsize)
 			return (-1);
-		memmove(&es->cbuf[es->cursor + len], &es->cbuf[es->cursor],
-		    es->linelen - es->cursor);
-		es->linelen += len;
+		memmove(&vs->cbuf[vs->cursor + len], &vs->cbuf[vs->cursor],
+		    vs->linelen - vs->cursor);
+		vs->linelen += len;
 	}
-	memmove(&es->cbuf[es->cursor], buf, len);
-	es->cursor += len;
+	memmove(&vs->cbuf[vs->cursor], buf, len);
+	vs->cursor += len;
 	return (0);
 }
 
 static void
 del_range(int a, int b)
 {
-	if (es->linelen != b)
-		memmove(&es->cbuf[a], &es->cbuf[b], es->linelen - b);
-	es->linelen -= b - a;
+	if (vs->linelen != b)
+		memmove(&vs->cbuf[a], &vs->cbuf[b], vs->linelen - b);
+	vs->linelen -= b - a;
 }
 
 static int
@@ -4867,19 +4882,19 @@
 {
 	int ncursor;
 
-	if (es->linelen == 0)
+	if (vs->linelen == 0)
 		return (-1);
-	ncursor = es->cursor;
+	ncursor = vs->cursor;
 	while (cnt--) {
 		do {
 			if (forw) {
-				if (++ncursor == es->linelen)
+				if (++ncursor == vs->linelen)
 					return (-1);
 			} else {
 				if (--ncursor < 0)
 					return (-1);
 			}
-		} while (es->cbuf[ncursor] != ch);
+		} while (vs->cbuf[ncursor] != ch);
 	}
 	if (!incl) {
 		if (forw)
@@ -4895,19 +4910,19 @@
 {
 	int ncursor;
 
-	ncursor = es->cursor;
-	while (ncursor < es->linelen && argcnt--) {
-		if (ksh_isalnux(es->cbuf[ncursor]))
-			while (ncursor < es->linelen &&
-			    ksh_isalnux(es->cbuf[ncursor]))
+	ncursor = vs->cursor;
+	while (ncursor < vs->linelen && argcnt--) {
+		if (ksh_isalnux(vs->cbuf[ncursor]))
+			while (ncursor < vs->linelen &&
+			    ksh_isalnux(vs->cbuf[ncursor]))
 				ncursor++;
-		else if (!ksh_isspace(es->cbuf[ncursor]))
-			while (ncursor < es->linelen &&
-			    !ksh_isalnux(es->cbuf[ncursor]) &&
-			    !ksh_isspace(es->cbuf[ncursor]))
+		else if (!ksh_isspace(vs->cbuf[ncursor]))
+			while (ncursor < vs->linelen &&
+			    !ksh_isalnux(vs->cbuf[ncursor]) &&
+			    !ksh_isspace(vs->cbuf[ncursor]))
 				ncursor++;
-		while (ncursor < es->linelen &&
-		    ksh_isspace(es->cbuf[ncursor]))
+		while (ncursor < vs->linelen &&
+		    ksh_isspace(vs->cbuf[ncursor]))
 			ncursor++;
 	}
 	return (ncursor);
@@ -4918,19 +4933,19 @@
 {
 	int ncursor;
 
-	ncursor = es->cursor;
+	ncursor = vs->cursor;
 	while (ncursor > 0 && argcnt--) {
-		while (--ncursor > 0 && ksh_isspace(es->cbuf[ncursor]))
+		while (--ncursor > 0 && ksh_isspace(vs->cbuf[ncursor]))
 			;
 		if (ncursor > 0) {
-			if (ksh_isalnux(es->cbuf[ncursor]))
+			if (ksh_isalnux(vs->cbuf[ncursor]))
 				while (--ncursor >= 0 &&
-				    ksh_isalnux(es->cbuf[ncursor]))
+				    ksh_isalnux(vs->cbuf[ncursor]))
 					;
 			else
 				while (--ncursor >= 0 &&
-				    !ksh_isalnux(es->cbuf[ncursor]) &&
-				    !ksh_isspace(es->cbuf[ncursor]))
+				    !ksh_isalnux(vs->cbuf[ncursor]) &&
+				    !ksh_isspace(vs->cbuf[ncursor]))
 					;
 			ncursor++;
 		}
@@ -4943,20 +4958,20 @@
 {
 	int ncursor;
 
-	ncursor = es->cursor;
-	while (ncursor < es->linelen && argcnt--) {
-		while (++ncursor < es->linelen - 1 &&
-		    ksh_isspace(es->cbuf[ncursor]))
+	ncursor = vs->cursor;
+	while (ncursor < vs->linelen && argcnt--) {
+		while (++ncursor < vs->linelen - 1 &&
+		    ksh_isspace(vs->cbuf[ncursor]))
 			;
-		if (ncursor < es->linelen - 1) {
-			if (ksh_isalnux(es->cbuf[ncursor]))
-				while (++ncursor < es->linelen &&
-				    ksh_isalnux(es->cbuf[ncursor]))
+		if (ncursor < vs->linelen - 1) {
+			if (ksh_isalnux(vs->cbuf[ncursor]))
+				while (++ncursor < vs->linelen &&
+				    ksh_isalnux(vs->cbuf[ncursor]))
 					;
 			else
-				while (++ncursor < es->linelen &&
-				    !ksh_isalnux(es->cbuf[ncursor]) &&
-				    !ksh_isspace(es->cbuf[ncursor]))
+				while (++ncursor < vs->linelen &&
+				    !ksh_isalnux(vs->cbuf[ncursor]) &&
+				    !ksh_isspace(vs->cbuf[ncursor]))
 					;
 			ncursor--;
 		}
@@ -4969,13 +4984,13 @@
 {
 	int ncursor;
 
-	ncursor = es->cursor;
-	while (ncursor < es->linelen && argcnt--) {
-		while (ncursor < es->linelen &&
-		    !ksh_isspace(es->cbuf[ncursor]))
+	ncursor = vs->cursor;
+	while (ncursor < vs->linelen && argcnt--) {
+		while (ncursor < vs->linelen &&
+		    !ksh_isspace(vs->cbuf[ncursor]))
 			ncursor++;
-		while (ncursor < es->linelen &&
-		    ksh_isspace(es->cbuf[ncursor]))
+		while (ncursor < vs->linelen &&
+		    ksh_isspace(vs->cbuf[ncursor]))
 			ncursor++;
 	}
 	return (ncursor);
@@ -4986,11 +5001,11 @@
 {
 	int ncursor;
 
-	ncursor = es->cursor;
+	ncursor = vs->cursor;
 	while (ncursor > 0 && argcnt--) {
-		while (--ncursor >= 0 && ksh_isspace(es->cbuf[ncursor]))
+		while (--ncursor >= 0 && ksh_isspace(vs->cbuf[ncursor]))
 			;
-		while (ncursor >= 0 && !ksh_isspace(es->cbuf[ncursor]))
+		while (ncursor >= 0 && !ksh_isspace(vs->cbuf[ncursor]))
 			ncursor--;
 		ncursor++;
 	}
@@ -5002,14 +5017,14 @@
 {
 	int ncursor;
 
-	ncursor = es->cursor;
-	while (ncursor < es->linelen - 1 && argcnt--) {
-		while (++ncursor < es->linelen - 1 &&
-		    ksh_isspace(es->cbuf[ncursor]))
+	ncursor = vs->cursor;
+	while (ncursor < vs->linelen - 1 && argcnt--) {
+		while (++ncursor < vs->linelen - 1 &&
+		    ksh_isspace(vs->cbuf[ncursor]))
 			;
-		if (ncursor < es->linelen - 1) {
-			while (++ncursor < es->linelen &&
-			    !ksh_isspace(es->cbuf[ncursor]))
+		if (ncursor < vs->linelen - 1) {
+			while (++ncursor < vs->linelen &&
+			    !ksh_isspace(vs->cbuf[ncursor]))
 				;
 			ncursor--;
 		}
@@ -5036,10 +5051,10 @@
 	}
 	if (save)
 		save_cbuf();
-	if ((es->linelen = strlen(hptr)) >= es->cbufsize)
-		es->linelen = es->cbufsize - 1;
-	memmove(es->cbuf, hptr, es->linelen);
-	es->cursor = 0;
+	if ((vs->linelen = strlen(hptr)) >= vs->cbufsize)
+		vs->linelen = vs->cbufsize - 1;
+	memmove(vs->cbuf, hptr, vs->linelen);
+	vs->cursor = 0;
 	ohnum = n;
 	return (0);
 }
@@ -5070,10 +5085,10 @@
 		save_cbuf();
 	histnum(hist);
 	hptr = *histpos();
-	if ((es->linelen = strlen(hptr)) >= es->cbufsize)
-		es->linelen = es->cbufsize - 1;
-	memmove(es->cbuf, hptr, es->linelen);
-	es->cursor = 0;
+	if ((vs->linelen = strlen(hptr)) >= vs->cbufsize)
+		vs->linelen = vs->cbufsize - 1;
+	memmove(vs->cbuf, hptr, vs->linelen);
+	vs->cursor = 0;
 	return (hist);
 }
 
@@ -5108,12 +5123,12 @@
 {
 	int cur, col;
 
-	if (es->cursor < es->winleft)
+	if (vs->cursor < vs->winleft)
 		return (1);
 	col = 0;
-	cur = es->winleft;
-	while (cur < es->cursor)
-		col = newcol((unsigned char)es->cbuf[cur++], col);
+	cur = vs->winleft;
+	while (cur < vs->cursor)
+		col = newcol((unsigned char)vs->cbuf[cur++], col);
 	if (col >= winwidth)
 		return (1);
 	return (0);
@@ -5128,19 +5143,19 @@
 
 	holdcur1 = holdcur2 = tcur = 0;
 	holdcol1 = holdcol2 = tcol = 0;
-	while (tcur < es->cursor) {
+	while (tcur < vs->cursor) {
 		if (tcol - holdcol2 > winwidth / 2) {
 			holdcur1 = holdcur2;
 			holdcol1 = holdcol2;
 			holdcur2 = tcur;
 			holdcol2 = tcol;
 		}
-		tcol = newcol((unsigned char)es->cbuf[tcur++], tcol);
+		tcol = newcol((unsigned char)vs->cbuf[tcur++], tcol);
 	}
 	while (tcol - holdcol1 > winwidth / 2)
-		holdcol1 = newcol((unsigned char)es->cbuf[holdcur1++],
+		holdcol1 = newcol((unsigned char)vs->cbuf[holdcur1++],
 		    holdcol1);
-	es->winleft = holdcur1;
+	vs->winleft = holdcur1;
 }
 
 static int
@@ -5161,13 +5176,13 @@
 	int moreright;
 
 	col = 0;
-	cur = es->winleft;
+	cur = vs->winleft;
 	moreright = 0;
 	twb1 = wb1;
-	while (col < winwidth && cur < es->linelen) {
-		if (cur == es->cursor && leftside)
+	while (col < winwidth && cur < vs->linelen) {
+		if (cur == vs->cursor && leftside)
 			ncol = col + pwidth;
-		if ((ch = es->cbuf[cur]) == '\t')
+		if ((ch = vs->cbuf[cur]) == '\t')
 			do {
 				*twb1++ = ' ';
 			} while (++col < winwidth && (col & 7) != 0);
@@ -5183,11 +5198,11 @@
 				col++;
 			}
 		}
-		if (cur == es->cursor && !leftside)
+		if (cur == vs->cursor && !leftside)
 			ncol = col + pwidth - 1;
 		cur++;
 	}
-	if (cur == es->cursor)
+	if (cur == vs->cursor)
 		ncol = col + pwidth;
 	if (col < winwidth) {
 		while (col < winwidth) {
@@ -5213,13 +5228,13 @@
 		twb2++;
 		col++;
 	}
-	if (es->winleft > 0 && moreright)
+	if (vs->winleft > 0 && moreright)
 		/*
 		 * POSIX says to use * for this but that is a globbing
 		 * character and may confuse people; + is more innocuous
 		 */
 		mc = '+';
-	else if (es->winleft > 0)
+	else if (vs->winleft > 0)
 		mc = '<';
 	else if (moreright)
 		mc = '>';
@@ -5267,7 +5282,7 @@
 
 	/* Undo previous expansion */
 	if (cmd == 0 && expanded == EXPAND && buf) {
-		restore_edstate(es, buf);
+		restore_edstate(vs, buf);
 		buf = 0;
 		expanded = NONE;
 		return (0);
@@ -5278,17 +5293,17 @@
 	}
 
 	i = XCF_COMMAND_FILE | XCF_FULLPATH;
-	nwords = x_cf_glob(&i, es->cbuf, es->linelen, es->cursor,
+	nwords = x_cf_glob(&i, vs->cbuf, vs->linelen, vs->cursor,
 	    &start, &end, &words);
 	if (nwords == 0) {
 		vi_error();
 		return (-1);
 	}
 
-	buf = save_edstate(es);
+	buf = save_edstate(vs);
 	expanded = EXPAND;
 	del_range(start, end);
-	es->cursor = start;
+	vs->cursor = start;
 	i = 0;
 	while (i < nwords) {
 		if (x_escape(words[i], strlen(words[i]), x_vi_putbuf) != 0) {
@@ -5302,7 +5317,7 @@
 	}
 	i = buf->cursor - end;
 	if (rval == 0 && i > 0)
-		es->cursor += i;
+		vs->cursor += i;
 	modified = 1;
 	hnum = hlast;
 	insert = INSERT;
@@ -5328,7 +5343,7 @@
 		return (0);
 	}
 	if (cmd == 0 && expanded == PRINT && buf) {
-		restore_edstate(es, buf);
+		restore_edstate(vs, buf);
 		buf = 0;
 		expanded = NONE;
 		return (0);
@@ -5345,7 +5360,7 @@
 	flags = XCF_COMMAND_FILE;
 	if (count)
 		flags |= XCF_FULLPATH;
-	nwords = x_cf_glob(&flags, es->cbuf, es->linelen, es->cursor,
+	nwords = x_cf_glob(&flags, vs->cbuf, vs->linelen, vs->cursor,
 	    &start, &end, &words);
 	if (nwords == 0) {
 		vi_error();
@@ -5390,9 +5405,9 @@
 		is_unique = nwords == 1;
 	}
 
-	buf = save_edstate(es);
+	buf = save_edstate(vs);
 	del_range(start, end);
-	es->cursor = start;
+	vs->cursor = start;
 
 	/*
 	 * escape all shell-sensitive characters and put the result into
@@ -5544,12 +5559,13 @@
 	if (!kshsetjmp(e->jbuf)) {
 		char *wds = alloc(len + 3, ATEMP);
 
-		wds[0] = FUNSUB;
+		wds[0] = FUNASUB;
 		memcpy(wds + 1, cmd, len);
 		wds[len + 1] = '\0';
 		wds[len + 2] = EOS;
 
 		cp = evalstr(wds, DOSCALAR);
+		afree(wds, ATEMP);
 		strdupx(cp, cp, AEDIT);
 	} else
 		cp = NULL;
diff --git a/src/eval.c b/src/eval.c
index a9980c7..23894d6 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -2,7 +2,7 @@
 
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
- *		 2011, 2012, 2013, 2014, 2015, 2016
+ *		 2011, 2012, 2013, 2014, 2015, 2016, 2017
  *	mirabilos <m@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -23,7 +23,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/eval.c,v 1.194 2016/11/11 23:31:34 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/eval.c,v 1.201 2017/04/06 01:59:54 tg Exp $");
 
 /*
  * string expansion
@@ -301,25 +301,36 @@
 					word = IFS_WORD;
 				quote = st->quotew;
 				continue;
+			case COMASUB:
 			case COMSUB:
+			case FUNASUB:
 			case FUNSUB:
 			case VALSUB:
 				tilde_ok = 0;
 				if (f & DONTRUNCOMMAND) {
 					word = IFS_WORD;
 					*dp++ = '$';
-					*dp++ = c == COMSUB ? '(' : '{';
-					if (c != COMSUB)
-						*dp++ = c == FUNSUB ? ' ' : '|';
+					switch (c) {
+					case COMASUB:
+					case COMSUB:
+						*dp++ = '(';
+						c = ')';
+						break;
+					case FUNASUB:
+					case FUNSUB:
+					case VALSUB:
+						*dp++ = '{';
+						*dp++ = c == VALSUB ? '|' : ' ';
+						c = '}';
+						break;
+					}
 					while (*sp != '\0') {
 						Xcheck(ds, dp);
 						*dp++ = *sp++;
 					}
-					if (c != COMSUB) {
+					if (c == '}')
 						*dp++ = ';';
-						*dp++ = '}';
-					} else
-						*dp++ = ')';
+					*dp++ = c;
 				} else {
 					type = comsub(&x, sp, c);
 					if (type != XBASE && (f & DOBLANK))
@@ -625,13 +636,12 @@
 						break;
 					case '=':
 						/*
-						 * Enabling tilde expansion
-						 * after :s here is
-						 * non-standard ksh, but is
-						 * consistent with rules for
-						 * other assignments. Not
-						 * sure what POSIX thinks of
-						 * this.
+						 * Tilde expansion for string
+						 * variables in POSIX mode is
+						 * governed by Austinbug 351.
+						 * In non-POSIX mode historic
+						 * ksh behaviour (enable it!)
+						 * us followed.
 						 * Not doing tilde expansion
 						 * for integer variables is a
 						 * non-POSIX thing - makes
@@ -640,7 +650,7 @@
 						 */
 						if (!(x.var->flag & INTEGER))
 							f |= DOASNTILDE | DOTILDE;
-						f |= DOTEMP;
+						f |= DOTEMP | DOSCALAR;
 						/*
 						 * These will be done after the
 						 * value has been assigned.
@@ -880,10 +890,30 @@
 				c = '\n';
 				--newlines;
 			} else {
-				while ((c = shf_getc(x.u.shf)) == 0 || c == '\n')
+				while ((c = shf_getc(x.u.shf)) == 0 ||
+#ifdef MKSH_WITH_TEXTMODE
+				       c == '\r' ||
+#endif
+				       c == '\n') {
+#ifdef MKSH_WITH_TEXTMODE
+					if (c == '\r') {
+						c = shf_getc(x.u.shf);
+						switch (c) {
+						case '\n':
+							break;
+						default:
+							shf_ungetc(c, x.u.shf);
+							/* FALLTHROUGH */
+						case -1:
+							c = '\r';
+							break;
+						}
+					}
+#endif
 					if (c == '\n')
 						/* save newlines */
 						newlines++;
+				}
 				if (newlines && c != -1) {
 					shf_ungetc(c, x.u.shf);
 					c = '\n';
@@ -1197,7 +1227,7 @@
 	} else if (ctype(c, C_SUBOP1)) {
 		slen += 2;
 		stype |= c;
-	} else if (ctype(c, C_SUBOP2)) {
+	} else if (ksh_issubop2(c)) {
 		/* Note: ksh88 allows :%, :%%, etc */
 		slen += 2;
 		stype = c;
@@ -1207,12 +1237,16 @@
 		}
 	} else if (c == '@') {
 		/* @x where x is command char */
-		slen += 2;
-		stype |= 0x100;
-		if (word[slen] == CHAR) {
-			stype |= word[slen + 1];
-			slen += 2;
+		switch (c = word[slen + 2] == CHAR ? word[slen + 3] : 0) {
+		case '#':
+		case '/':
+		case 'Q':
+			break;
+		default:
+			return (-1);
 		}
+		stype |= 0x100 | c;
+		slen += 4;
 	} else if (stype)
 		/* : is not ok */
 		return (-1);
@@ -1301,7 +1335,7 @@
 
 	c = stype & 0x7F;
 	/* test the compiler's code generator */
-	if (((stype < 0x100) && (ctype(c, C_SUBOP2) ||
+	if (((stype < 0x100) && (ksh_issubop2(c) ||
 	    (((stype & 0x80) ? *xp->str == '\0' : xp->str == null) &&
 	    (state != XARG || (ifs0 || xp->split ?
 	    (xp->u.strv[0] == NULL) : !hasnonempty(xp->u.strv))) ?
@@ -1311,7 +1345,7 @@
 		/* expand word instead of variable value */
 		state = XBASE;
 	if (Flag(FNOUNSET) && xp->str == null && !zero_ok &&
-	    (ctype(c, C_SUBOP2) || (state != XBASE && c != '+')))
+	    (ksh_issubop2(c) || (state != XBASE && c != '+')))
 		errorf(Tf_parm, sp);
 	*stypep = stype;
 	*slenp = slen;
@@ -1322,17 +1356,28 @@
  * Run the command in $(...) and read its output.
  */
 static int
-comsub(Expand *xp, const char *cp, int fn MKSH_A_UNUSED)
+comsub(Expand *xp, const char *cp, int fn)
 {
 	Source *s, *sold;
 	struct op *t;
 	struct shf *shf;
+	bool doalias = false;
 	uint8_t old_utfmode = UTFMODE;
 
+	switch (fn) {
+	case COMASUB:
+		fn = COMSUB;
+		if (0)
+			/* FALLTHROUGH */
+	case FUNASUB:
+		  fn = FUNSUB;
+		doalias = true;
+	}
+
 	s = pushs(SSTRING, ATEMP);
 	s->start = s->str = cp;
 	sold = source;
-	t = compile(s, true);
+	t = compile(s, true, doalias);
 	afree(s, ATEMP);
 	source = sold;
 
@@ -1713,7 +1758,7 @@
 
 	Xinit(ts, tp, 16, ATEMP);
 	/* : only for DOASNTILDE form */
-	while (p[0] == CHAR && !mksh_cdirsep(p[1]) &&
+	while (p[0] == CHAR && /* not cdirsep */ p[1] != '/' &&
 	    (!isassign || p[1] != ':')) {
 		Xcheck(ts, tp);
 		*tp++ = p[1];
diff --git a/src/exec.c b/src/exec.c
index 882b628..6307bce 100644
--- a/src/exec.c
+++ b/src/exec.c
@@ -2,7 +2,7 @@
 
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
- *		 2011, 2012, 2013, 2014, 2015, 2016
+ *		 2011, 2012, 2013, 2014, 2015, 2016, 2017
  *	mirabilos <m@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -23,7 +23,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/exec.c,v 1.186 2016/11/11 23:31:34 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/exec.c,v 1.196 2017/04/12 16:46:21 tg Exp $");
 
 #ifndef MKSH_DEFAULT_EXECSHELL
 #define MKSH_DEFAULT_EXECSHELL	MKSH_UNIXROOT "/bin/sh"
@@ -376,9 +376,8 @@
 		if (t->right == NULL)
 			/* 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);
+		rv = execute(execute(t->left, XERROK, NULL) == 0 ?
+		    t->right->left : t->right->right, flags & XERROK, xerrok);
 		break;
 
 	case TCASE:
@@ -806,7 +805,7 @@
 			/* NOTREACHED */
 		default:
 			quitenv(NULL);
-			internal_errorf(Tf_sd, "CFUNC", i);
+			internal_errorf(Tunexpected_type, Tunwind, Tfunction, i);
 		}
 		break;
 	}
@@ -889,6 +888,9 @@
 		unsigned short m;
 		ssize_t n;
 
+#if defined(__OS2__) && defined(MKSH_WITH_TEXTMODE)
+		setmode(fd, O_TEXT);
+#endif
 		/* read first couple of octets from file */
 		n = read(fd, buf, sizeof(buf) - 1);
 		close(fd);
@@ -944,6 +946,17 @@
 			if (*cp)
 				*tp->args-- = (char *)cp;
 		}
+#ifdef __OS2__
+		/*
+		 * Search shell/interpreter name without directory in PATH
+		 * if specified path does not exist
+		 */
+		if (mksh_vdirsep(sh) && !search_path(sh, path, X_OK, NULL)) {
+			cp = search_path(_getname(sh), path, X_OK, NULL);
+			if (cp)
+				sh = cp;
+		}
+#endif
 		goto nomagic;
  noshebang:
 		m = buf[0] << 8 | buf[1];
@@ -964,6 +977,19 @@
 		    buf[4] == 'Z') || (m == /* 7zip */ 0x377A) ||
 		    (m == /* gzip */ 0x1F8B) || (m == /* .Z */ 0x1F9D))
 			errorf("%s: not executable: magic %04X", tp->str, m);
+#ifdef __OS2__
+		cp = _getext(tp->str);
+		if (cp && (!stricmp(cp, ".cmd") || !stricmp(cp, ".bat"))) {
+			/* execute .cmd and .bat with OS2_SHELL, usually CMD.EXE */
+			sh = str_val(global("OS2_SHELL"));
+			*tp->args-- = "/c";
+			/* convert slahes to backslashes */
+			for (cp = tp->str; *cp; cp++) {
+				if (*cp == '/')
+					*cp = '\\';
+			}
+		}
+#endif
  nomagic:
 		;
 	}
@@ -978,13 +1004,17 @@
 	errorf(Tf_sD_sD_s, tp->str, sh, cstrerror(errno));
 }
 
+/* actual 'builtin' built-in utility call is handled in comexec() */
 int
-shcomexec(const char **wp)
+c_builtin(const char **wp)
 {
-	struct tbl *tp;
+	return (call_builtin(get_builtin(*wp), wp, Tbuiltin, false));
+}
 
-	tp = ktsearch(&builtins, *wp, hash(*wp));
-	return (call_builtin(tp, wp, "shcomexec", false));
+struct tbl *
+get_builtin(const char *s)
+{
+	return (s && *s ? ktsearch(&builtins, s, hash(s)) : NULL);
 }
 
 /*
@@ -1090,6 +1120,14 @@
 		/* external utility overrides built-in utility, with flags */
 		flag |= LOW_BI;
 		break;
+	case '-':
+		/* is declaration utility if argv[1] is one (POSIX: command) */
+		flag |= DECL_FWDR;
+		break;
+	case '^':
+		/* is declaration utility (POSIX: export, readonly) */
+		flag |= DECL_UTIL;
+		break;
 	default:
 		goto flags_seen;
 	}
@@ -1123,7 +1161,11 @@
 	char *fpath;
 	union mksh_cchack npath;
 
-	if (mksh_vdirsep(name)) {
+	if (mksh_vdirsep(name)
+#ifdef MKSH_DOSPATH
+	    && (strcmp(name, T_builtin) != 0)
+#endif
+	    ) {
 		insert = 0;
 		/* prevent FPATH search below */
 		flags &= ~FC_FUNC;
@@ -1242,7 +1284,7 @@
 	}
 #ifdef __OS2__
 	/* treat all files as executable on OS/2 */
-	sb.st_mode &= S_IXUSR | S_IXGRP | S_IXOTH;
+	sb.st_mode |= S_IXUSR | S_IXGRP | S_IXOTH;
 #endif
 	if (mode == X_OK && (!S_ISREG(sb.st_mode) ||
 	    !(sb.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))))
@@ -1251,6 +1293,13 @@
 	return (0);
 }
 
+#ifdef __OS2__
+/* check if path is something we want to find, adding executable extensions */
+#define search_access(fn, mode)	access_ex((search_access), (fn), (mode))
+#else
+#define search_access(fn, mode)	(search_access)((fn), (mode))
+#endif
+
 /*
  * search for command with PATH
  */
@@ -1272,7 +1321,11 @@
  search_path_ok:
 			if (errnop)
 				*errnop = 0;
+#ifndef __OS2__
 			return (name);
+#else
+			return (real_exec_name(name));
+#endif
 		}
 		goto search_path_err;
 	}
@@ -1289,6 +1342,10 @@
 			XcheckN(xs, xp, p - sp);
 			memcpy(xp, sp, p - sp);
 			xp += p - sp;
+#ifdef __OS2__
+			if (xp > Xstring(xs, xp) && mksh_cdirsep(xp[-1]))
+				xp--;
+#endif
 			*xp++ = '/';
 		}
 		sp = p;
@@ -1319,9 +1376,7 @@
 	if (!tp)
 		internal_errorf(Tf_sD_s, where, wp[0]);
 	builtin_argv0 = wp[0];
-	builtin_spec = tobool(!resetspec &&
-	    /*XXX odd use of KEEPASN */
-	    ((tp->flag & SPEC_BI) || (Flag(FPOSIX) && (tp->flag & KEEPASN))));
+	builtin_spec = tobool(!resetspec && (tp->flag & SPEC_BI));
 	shf_reopen(1, SHF_WR, shl_stdout);
 	shl_stdout_ok = true;
 	ksh_getopt_reset(&builtin_opt, GF_ERROR);
@@ -1450,8 +1505,11 @@
 		/* herein() may already have printed message */
 		if (u == -1) {
 			u = errno;
-			warningf(true, Tf_cant,
+			warningf(true, Tf_cant_ss_s,
+#if 0
+			    /* can't happen */
 			    iotype == IODUP ? "dup" :
+#endif
 			    (iotype == IOREAD || iotype == IOHERE) ?
 			    Topen : Tcreate, cp, cstrerror(u));
 		}
diff --git a/src/expr.c b/src/expr.c
index f259b0c..124dc17 100644
--- a/src/expr.c
+++ b/src/expr.c
@@ -2,7 +2,7 @@
 
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
- *		 2011, 2012, 2013, 2014, 2016
+ *		 2011, 2012, 2013, 2014, 2016, 2017
  *	mirabilos <m@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -23,7 +23,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/expr.c,v 1.90 2016/11/07 16:58:48 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/expr.c,v 1.93 2017/04/02 16:47:41 tg Exp $");
 
 #define EXPRTOK_DEFNS
 #include "exprtok.h"
@@ -203,7 +203,7 @@
 
 	case ET_BADLIT:
 		warningf(true, Tf_sD_s_qs, es->expression,
-		    "bad number", str);
+		    Tbadnum, str);
 		break;
 
 	case ET_RECURSIVE:
@@ -572,8 +572,9 @@
 	if (c == '\0')
 		es->tok = END;
 	else if (ksh_isalphx(c)) {
-		for (; ksh_isalnux(c); c = *cp)
-			cp++;
+		do {
+			c = *++cp;
+		} while (ksh_isalnux(c));
 		if (c == '[') {
 			size_t len;
 
@@ -856,6 +857,9 @@
 int
 ksh_access(const char *fn, int mode)
 {
+#ifdef __OS2__
+	return (access_ex(access, fn, mode));
+#else
 	int rv;
 	struct stat sb;
 
@@ -865,6 +869,7 @@
 		rv = -1;
 
 	return (rv);
+#endif
 }
 
 #ifndef MIRBSD_BOOTFLOPPY
diff --git a/src/funcs.c b/src/funcs.c
index b76fbfc..930462d 100644
--- a/src/funcs.c
+++ b/src/funcs.c
@@ -5,7 +5,7 @@
 
 /*-
  * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
- *		 2010, 2011, 2012, 2013, 2014, 2015, 2016
+ *		 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017
  *	mirabilos <m@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -38,7 +38,7 @@
 #endif
 #endif
 
-__RCSID("$MirOS: src/bin/mksh/funcs.c,v 1.319 2016/11/11 23:48:29 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/funcs.c,v 1.340 2017/04/12 17:46:29 tg Exp $");
 
 #if HAVE_KILLPG
 /*
@@ -73,7 +73,7 @@
 	int rv;
 
 	if (!(rv = getn(as, ai)))
-		bi_errorf(Tf_sD_s, as, "bad number");
+		bi_errorf(Tf_sD_s, Tbadnum, as);
 	return (rv);
 }
 
@@ -92,6 +92,7 @@
 /*
  * A leading = means assignments before command are kept.
  * A leading * means a POSIX special builtin.
+ * A leading ^ means declaration utility, - forwarder.
  */
 const struct builtin mkshbuiltins[] = {
 	{Tsgdot, c_dot},
@@ -99,33 +100,34 @@
 	{Tbracket, c_test},
 	/* no =: AT&T manual wrong */
 	{Talias, c_alias},
-	{"*=break", c_brkcont},
-	{Tgbuiltin, c_builtin},
+	{Tsgbreak, c_brkcont},
+	{T__builtin, c_builtin},
+	{Tbuiltin, c_builtin},
 #if !defined(__ANDROID__)
 	{Tbcat, c_cat},
 #endif
 	{Tcd, c_cd},
 	/* dash compatibility hack */
 	{"chdir", c_cd},
-	{Tcommand, c_command},
-	{"*=continue", c_brkcont},
+	{T_command, c_command},
+	{Tsgcontinue, c_brkcont},
 	{"echo", c_print},
 	{"*=eval", c_eval},
 	{"*=exec", c_exec},
 	{"*=exit", c_exitreturn},
-	{Tsgexport, c_typeset},
+	{Tdsgexport, c_typeset},
 	{Tfalse, c_false},
 	{"fc", c_fc},
 	{Tgetopts, c_getopts},
-	{"=global", c_typeset},
+	/* deprecated, replaced by typeset -g */
+	{"^=global", c_typeset},
 	{Tjobs, c_jobs},
 	{"kill", c_kill},
 	{"let", c_let},
-	{"let]", c_let},
 	{"print", c_print},
 	{"pwd", c_pwd},
 	{Tread, c_read},
-	{Tsgreadonly, c_typeset},
+	{Tdsgreadonly, c_typeset},
 #if !defined(__ANDROID__)
 	{"!realpath", c_realpath},
 #endif
@@ -133,7 +135,7 @@
 	{"*=return", c_exitreturn},
 	{Tsgset, c_set},
 	{"*=shift", c_shift},
-	{"=source", c_dot},
+	{Tgsource, c_dot},
 #if !defined(MKSH_UNEMPLOYED) && HAVE_GETSID
 	{Tsuspend, c_suspend},
 #endif
@@ -141,12 +143,12 @@
 	{"*=times", c_times},
 	{"*=trap", c_trap},
 	{Ttrue, c_true},
-	{Tgtypeset, c_typeset},
+	{Tdgtypeset, c_typeset},
 	{"ulimit", c_ulimit},
 	{"umask", c_umask},
 	{Tunalias, c_unalias},
 	{"*=unset", c_unset},
-	{"=wait", c_wait},
+	{"wait", c_wait},
 	{"whence", c_whence},
 #ifndef MKSH_UNEMPLOYED
 	{Tbg, c_fgbg},
@@ -193,8 +195,8 @@
 	{"-f",	TO_FILREG },
 	{"-G",	TO_FILGID },
 	{"-g",	TO_FILSETG },
-	{"-h",	TO_FILSYM },
 	{"-H",	TO_FILCDF },
+	{"-h",	TO_FILSYM },
 	{"-k",	TO_FILSTCK },
 	{"-L",	TO_FILSYM },
 	{"-n",	TO_STNZE },
@@ -202,10 +204,11 @@
 	{"-o",	TO_OPTION },
 	{"-p",	TO_FILFIFO },
 	{"-r",	TO_FILRD },
-	{"-s",	TO_FILGZ },
 	{"-S",	TO_FILSOCK },
+	{"-s",	TO_FILGZ },
 	{"-t",	TO_FILTT },
 	{"-u",	TO_FILSETU },
+	{"-v",	TO_ISSET },
 	{"-w",	TO_FILWR },
 	{"-x",	TO_FILEX },
 	{"-z",	TO_STZER },
@@ -313,8 +316,6 @@
 		bool hist;
 		/* print words as wide characters? */
 		bool chars;
-		/* print a "--" argument? */
-		bool pminusminus;
 		/* writing to a coprocess (SIGPIPE blocked)? */
 		bool coproc;
 		bool copipe;
@@ -325,47 +326,39 @@
 	po.ws = ' ';
 	po.ls = '\n';
 	po.nl = true;
-	po.exp = true;
 
 	if (wp[0][0] == 'e') {
 		/* "echo" builtin */
-		++wp;
-#ifdef MKSH_MIDNIGHTBSD01ASH_COMPAT
-		if (Flag(FSH)) {
-			/*
-			 * MidnightBSD /bin/sh needs a BSD echo, that is,
-			 * one that supports -e but does not enable it by
-			 * default
-			 */
-			po.exp = false;
-		}
-#endif
 		if (Flag(FPOSIX) ||
 #ifndef MKSH_MIDNIGHTBSD01ASH_COMPAT
 		    Flag(FSH) ||
 #endif
 		    Flag(FAS_BUILTIN)) {
-			/* Debian Policy 10.4 compliant "echo" builtin */
+			/* BSD "echo" cmd, Debian Policy 10.4 compliant */
+			++wp;
+ bsd_echo:
 			if (*wp && !strcmp(*wp, "-n")) {
-				/* recognise "-n" only as the first arg */
 				po.nl = false;
 				++wp;
 			}
-			/* print everything as-is */
 			po.exp = false;
 		} else {
-			bool new_exp = po.exp, new_nl = po.nl;
+			bool new_exp, new_nl = true;
 
-			/**
-			 * a compromise between sysV and BSD echo commands:
-			 * escape sequences are enabled by default, and -n,
-			 * -e and -E are recognised if they appear in argu-
-			 * ments with no illegal options (ie, echo -nq will
-			 * print -nq).
-			 * Different from sysV echo since options are reco-
-			 * gnised, different from BSD echo since escape se-
-			 * quences are enabled by default.
+			/*-
+			 * compromise between various historic echos: only
+			 * recognise -Een if they appear in arguments with
+			 * no illegal options; e.g. echo -nq outputs '-nq'
 			 */
+#ifdef MKSH_MIDNIGHTBSD01ASH_COMPAT
+			/* MidnightBSD /bin/sh needs -e supported but off */
+			if (Flag(FSH))
+				new_exp = false;
+			else
+#endif
+			/* otherwise compromise on -e enabled by default */
+			  new_exp = true;
+			goto print_tradparse_beg;
 
  print_tradparse_arg:
 			if ((s = *wp) && *s++ == '-' && *s) {
@@ -381,6 +374,7 @@
 					new_nl = false;
 					goto print_tradparse_ch;
 				case '\0':
+ print_tradparse_beg:
 					po.exp = new_exp;
 					po.nl = new_nl;
 					++wp;
@@ -390,10 +384,10 @@
 		}
 	} else {
 		/* "print" builtin */
-		const char *opts = "AclNnpRrsu,";
+		const char *opts = "AcelNnpRrsu,";
 		const char *emsg;
 
-		po.pminusminus = false;
+		po.exp = true;
 
 		while ((c = ksh_getopt(wp, &builtin_opt, opts)) != -1)
 			switch (c) {
@@ -423,11 +417,9 @@
 				}
 				break;
 			case 'R':
-				/* fake BSD echo command */
-				po.pminusminus = true;
-				po.exp = false;
-				opts = "en";
-				break;
+				/* fake BSD echo but don't reset other flags */
+				wp += builtin_opt.optind;
+				goto bsd_echo;
 			case 'r':
 				po.exp = false;
 				break;
@@ -451,8 +443,7 @@
 			if (wp[builtin_opt.optind] &&
 			    ksh_isdash(wp[builtin_opt.optind]))
 				builtin_opt.optind++;
-			} else if (po.pminusminus)
-				builtin_opt.optind--;
+		}
 		wp += builtin_opt.optind;
 	}
 
@@ -747,7 +738,7 @@
 			break;
 #ifndef MKSH_SMALL
 		default:
-			bi_errorf("%s is of unknown type %d", id, tp->type);
+			bi_errorf(Tunexpected_type, id, Tcommand, tp->type);
 			return (1);
 #endif
 		}
@@ -757,380 +748,15 @@
 	return (rv);
 }
 
-/* typeset, global, export, and readonly */
-static void c_typeset_vardump(struct tbl *, uint32_t, int, bool, bool);
-static void c_typeset_vardump_recursive(struct block *, uint32_t, int, bool,
-    bool);
-int
-c_typeset(const char **wp)
+bool
+valid_alias_name(const char *cp)
 {
-	struct tbl *vp, **p;
-	uint32_t fset = 0, fclr = 0, flag;
-	int thing = 0, field = 0, base = 0, i;
-	struct block *l;
-	const char *opts;
-	const char *fieldstr = NULL, *basestr = NULL;
-	bool localv = false, func = false, pflag = false, istset = true;
-	enum namerefflag new_refflag = SRF_NOP;
-
-	switch (**wp) {
-
-	/* export */
-	case 'e':
-		fset |= EXPORT;
-		istset = false;
-		break;
-
-	/* readonly */
-	case 'r':
-		fset |= RDONLY;
-		istset = false;
-		break;
-
-	/* set */
-	case 's':
-		/* called with 'typeset -' */
-		break;
-
-	/* typeset */
-	case 't':
-		localv = true;
-		break;
-	}
-
-	/* see comment below regarding possible opions */
-	opts = istset ? "L#R#UZ#afi#lnprtux" : "p";
-
-	builtin_opt.flags |= GF_PLUSOPT;
-	/*
-	 * 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
-	 * does not allow the number to be specified as a separate argument
-	 * Here, the number must follow the RLZi option, but is optional
-	 * (see the # kludge in ksh_getopt()).
-	 */
-	while ((i = ksh_getopt(wp, &builtin_opt, opts)) != -1) {
-		flag = 0;
-		switch (i) {
-		case 'L':
-			flag = LJUST;
-			fieldstr = builtin_opt.optarg;
-			break;
-		case 'R':
-			flag = RJUST;
-			fieldstr = builtin_opt.optarg;
-			break;
-		case 'U':
-			/*
-			 * 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
-			 */
-			flag = INT_U;
-			break;
-		case 'Z':
-			flag = ZEROFIL;
-			fieldstr = builtin_opt.optarg;
-			break;
-		case 'a':
-			/*
-			 * this is supposed to set (-a) or unset (+a) the
-			 * indexed array attribute; it does nothing on an
-			 * existing regular string or indexed array though
-			 */
-			break;
-		case 'f':
-			func = true;
-			break;
-		case 'i':
-			flag = INTEGER;
-			basestr = builtin_opt.optarg;
-			break;
-		case 'l':
-			flag = LCASEV;
-			break;
-		case 'n':
-			new_refflag = (builtin_opt.info & GI_PLUS) ?
-			    SRF_DISABLE : SRF_ENABLE;
-			break;
-		/* export, readonly: POSIX -p flag */
-		case 'p':
-			/* typeset: show values as well */
-			pflag = true;
-			if (istset)
-				continue;
-			break;
-		case 'r':
-			flag = RDONLY;
-			break;
-		case 't':
-			flag = TRACE;
-			break;
-		case 'u':
-			/* upper case / autoload */
-			flag = UCASEV_AL;
-			break;
-		case 'x':
-			flag = EXPORT;
-			break;
-		case '?':
-			return (1);
-		}
-		if (builtin_opt.info & GI_PLUS) {
-			fclr |= flag;
-			fset &= ~flag;
-			thing = '+';
-		} else {
-			fset |= flag;
-			fclr &= ~flag;
-			thing = '-';
-		}
-	}
-
-	if (fieldstr && !bi_getn(fieldstr, &field))
-		return (1);
-	if (basestr) {
-		if (!getn(basestr, &base)) {
-			bi_errorf(Tf_sD_s, "bad integer base", basestr);
-			return (1);
-		}
-		if (base < 1 || base > 36)
-			base = 10;
-	}
-
-	if (!(builtin_opt.info & GI_MINUSMINUS) && wp[builtin_opt.optind] &&
-	    (wp[builtin_opt.optind][0] == '-' ||
-	    wp[builtin_opt.optind][0] == '+') &&
-	    wp[builtin_opt.optind][1] == '\0') {
-		thing = wp[builtin_opt.optind][0];
-		builtin_opt.optind++;
-	}
-
-	if (func && (((fset|fclr) & ~(TRACE|UCASEV_AL|EXPORT)) ||
-	    new_refflag != SRF_NOP)) {
-		bi_errorf("only -t, -u and -x options may be used with -f");
-		return (1);
-	}
-	if (wp[builtin_opt.optind]) {
-		/*
-		 * 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 */
-			fset &= ~UCASEV_AL;
-		if (fset & LJUST)
-			/* LJUST has priority over RJUST */
-			fset &= ~RJUST;
-		if ((fset & (ZEROFIL|LJUST)) == ZEROFIL) {
-			/* -Z implies -ZR */
-			fset |= RJUST;
-			fclr &= ~RJUST;
-		}
-		/*
-		 * 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)) || new_refflag != SRF_NOP)
-			fclr |= ~fset & (LJUST | RJUST | ZEROFIL | UCASEV_AL |
-			    LCASEV | INTEGER | INT_U | INT_L);
-	}
-	if (new_refflag != SRF_NOP) {
-		fclr &= ~(ARRAY | ASSOC);
-		fset &= ~(ARRAY | ASSOC);
-		fclr |= EXPORT;
-		fset |= ASSOC;
-		if (new_refflag == SRF_DISABLE)
-			fclr |= ASSOC;
-	}
-
-	/* set variables and attributes */
-	if (wp[builtin_opt.optind] &&
-	    /* not "typeset -p varname" */
-	    !(!func && pflag && !(fset | fclr))) {
-		int rv = 0;
-		struct tbl *f;
-
-		if (localv && !func)
-			fset |= LOCAL;
-		for (i = builtin_opt.optind; wp[i]; i++) {
-			if (func) {
-				f = findfunc(wp[i], hash(wp[i]),
-				    tobool(fset & UCASEV_AL));
-				if (!f) {
-					/* AT&T ksh does ++rv: bogus */
-					rv = 1;
-					continue;
-				}
-				if (fset | fclr) {
-					f->flag |= fset;
-					f->flag &= ~fclr;
-				} 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(Tf_sD_s, wp[i], Tnot_ident);
-				return (1);
-			}
-		}
-		return (rv);
-	}
-
-	/* list variables and attributes */
-
-	/* 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 == '-')
-					fpFUNCTf(shl_stdout, 0,
-					    tobool(vp->flag & FKSH),
-					    vp->name, vp->val.t);
-				else
-					shf_puts(vp->name, shl_stdout);
-				shf_putc('\n', shl_stdout);
-			}
-		}
-	} else if (wp[builtin_opt.optind]) {
-		for (i = builtin_opt.optind; wp[i]; i++) {
-			varsearch(e->loc, &vp, wp[i], hash(wp[i]));
-			c_typeset_vardump(vp, flag, thing, pflag, istset);
-		}
-	} else
-		c_typeset_vardump_recursive(e->loc, flag, thing, pflag, istset);
-	return (0);
-}
-
-static void
-c_typeset_vardump_recursive(struct block *l, uint32_t flag, int thing,
-    bool pflag, bool istset)
-{
-	struct tbl **blockvars, *vp;
-
-	if (l->next)
-		c_typeset_vardump_recursive(l->next, flag, thing, pflag, istset);
-	blockvars = ktsort(&l->vars);
-	while ((vp = *blockvars++))
-		c_typeset_vardump(vp, flag, thing, pflag, istset);
-	/*XXX doesn’t this leak? */
-}
-
-static void
-c_typeset_vardump(struct tbl *vp, uint32_t flag, int thing, bool pflag,
-    bool istset)
-{
-	struct tbl *tvp;
-	int any_set = 0;
-	char *s;
-
-	if (!vp)
-		return;
-
-	/*
-	 * See if the parameter is set (for arrays, if any
-	 * element is set).
-	 */
-	for (tvp = vp; tvp; tvp = tvp->u.array)
-		if (tvp->flag & ISSET) {
-			any_set = 1;
-			break;
-		}
-
-	/*
-	 * Check attributes - note that all array elements
-	 * have (should have?) the same attributes, so checking
-	 * the first is sufficient.
-	 *
-	 * Report an unset param only if the user has
-	 * explicitly given it some attribute (like export);
-	 * otherwise, after "echo $FOO", we would report FOO...
-	 */
-	if (!any_set && !(vp->flag & USERATTRIB))
-		return;
-	if (flag && (vp->flag & flag) == 0)
-		return;
-	if (!(vp->flag & ARRAY))
-		/* optimise later conditionals */
-		any_set = 0;
-	do {
-		/*
-		 * Ignore array elements that aren't set unless there
-		 * are no set elements, in which case the first is
-		 * reported on
-		 */
-		if (any_set && !(vp->flag & ISSET))
-			continue;
-		/* no arguments */
-		if (!thing && !flag) {
-			if (any_set == 1) {
-				shprintf(Tf_s_s_sN, Tset, "-A", vp->name);
-				any_set = 2;
-			}
-			/*
-			 * AT&T ksh prints things like export, integer,
-			 * leftadj, zerofill, etc., but POSIX says must
-			 * be suitable for re-entry...
-			 */
-			shprintf(Tf_s_s, Ttypeset, "");
-			if (((vp->flag & (ARRAY | ASSOC)) == ASSOC))
-				shprintf(Tf__c_, 'n');
-			if ((vp->flag & INTEGER))
-				shprintf(Tf__c_, 'i');
-			if ((vp->flag & EXPORT))
-				shprintf(Tf__c_, 'x');
-			if ((vp->flag & RDONLY))
-				shprintf(Tf__c_, 'r');
-			if ((vp->flag & TRACE))
-				shprintf(Tf__c_, '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))
-				shprintf(Tf__c_, 'Z');
-			if ((vp->flag & LCASEV))
-				shprintf(Tf__c_, 'l');
-			if ((vp->flag & UCASEV_AL))
-				shprintf(Tf__c_, 'u');
-			if ((vp->flag & INT_U))
-				shprintf(Tf__c_, 'U');
-		} else if (pflag) {
-			shprintf(Tf_s_s, istset ? Ttypeset :
-			    (flag & EXPORT) ? Texport : Treadonly, "");
-		}
-		if (any_set)
-			shprintf("%s[%lu]", vp->name, arrayindex(vp));
+	while (*cp)
+		if (!ksh_isalias(*cp))
+			return (false);
 		else
-			shf_puts(vp->name, shl_stdout);
-		if ((!thing && !flag && pflag) ||
-		    (thing == '-' && (vp->flag & ISSET))) {
-			s = str_val(vp);
-			shf_putc('=', shl_stdout);
-			/* AT&T ksh can't have justified integers... */
-			if ((vp->flag & (INTEGER | LJUST | RJUST)) == INTEGER)
-				shf_puts(s, shl_stdout);
-			else
-				print_value_quoted(shl_stdout, s);
-		}
-		shf_putc('\n', shl_stdout);
-
-		/*
-		 * Only report first 'element' of an array with
-		 * no set elements.
-		 */
-		if (!any_set)
-			return;
-	} while ((vp = vp->u.array));
+			++cp;
+	return (true);
 }
 
 int
@@ -1231,6 +857,11 @@
 			strndupx(xalias, alias, val++ - alias, ATEMP);
 			alias = xalias;
 		}
+		if (!valid_alias_name(alias) || *alias == '-') {
+			bi_errorf(Tinvname, alias, Talias);
+			afree(xalias, ATEMP);
+			return (1);
+		}
 		h = hash(alias);
 		if (val == NULL && !tflag && !xflag) {
 			ap = ktsearch(t, alias, h);
@@ -1639,7 +1270,7 @@
 	if (user_opt.optarg == NULL)
 		unset(voptarg, 1);
 	else
-		/* This can't fail (have cleared readonly/integer) */
+		/* this can't fail (haing cleared readonly/integer) */
 		setstr(voptarg, user_opt.optarg, KSH_RETURN_ERROR);
 
 	rv = 0;
@@ -1737,7 +1368,7 @@
 		/* nothing to do */
 		return (0);
 	} else if (n < 0) {
-		bi_errorf(Tf_sD_s, arg, "bad number");
+		bi_errorf(Tf_sD_s, Tbadnum, arg);
 		return (1);
 	}
 	if (l->argc < n) {
@@ -1798,7 +1429,7 @@
 				++cp;
 			}
 			if (*cp) {
-				bi_errorf("bad number");
+				bi_errorf(Tbadnum);
 				return (1);
 			}
 		} else {
@@ -1982,6 +1613,10 @@
 #else
 #define c_read_opts "Aad:N:n:prsu,"
 #endif
+#if defined(__OS2__) && defined(MKSH_WITH_TEXTMODE)
+	int saved_mode;
+	int saved_errno;
+#endif
 
 	while ((c = ksh_getopt(wp, &builtin_opt, c_read_opts)) != -1)
 	switch (c) {
@@ -2110,7 +1745,15 @@
 	}
 #endif
 
+#if defined(__OS2__) && defined(MKSH_WITH_TEXTMODE)
+	saved_mode = setmode(fd, O_TEXT);
+#endif
 	if ((bytesread = blocking_read(fd, xp, bytesleft)) == (size_t)-1) {
+#if defined(__OS2__) && defined(MKSH_WITH_TEXTMODE)
+		saved_errno = errno;
+		setmode(fd, saved_mode);
+		errno = saved_errno;
+#endif
 		if (errno == EINTR) {
 			/* check whether the signal would normally kill */
 			if (!fatal_trap_check()) {
@@ -2125,6 +1768,9 @@
 		rv = 2;
 		goto c_read_out;
 	}
+#if defined(__OS2__) && defined(MKSH_WITH_TEXTMODE)
+	setmode(fd, saved_mode);
+#endif
 
 	switch (readmode) {
 	case READALL:
@@ -2386,6 +2032,7 @@
 		return (1);
 	s = pushs(SWORDS, ATEMP);
 	s->u.strv = wp + builtin_opt.optind;
+	s->line = current_lineno;
 
 	/*-
 	 * The following code handles the case where the command is
@@ -2423,7 +2070,7 @@
 
 	savef = Flag(FERREXIT);
 	Flag(FERREXIT) |= 0x80;
-	rv = shell(s, false);
+	rv = shell(s, 2);
 	Flag(FERREXIT) = savef;
 	source = saves;
 	afree(s, ATEMP);
@@ -2559,7 +2206,7 @@
 		 * scripts, but don't generate an error (ie, keep going).
 		 */
 		if ((unsigned int)n == quit) {
-			warningf(true, "%s: can't %s", wp[0], wp[0]);
+			warningf(true, Tf_cant_s, wp[0], wp[0]);
 			return (0);
 		}
 		/*
@@ -2827,7 +2474,6 @@
 		for (i = 0; i < NUFILE; i++) {
 			if (e->savefd[i] > 0)
 				close(e->savefd[i]);
-#ifndef MKSH_LEGACY_MODE
 			/*
 			 * keep all file descriptors > 2 private for ksh,
 			 * but not for POSIX or legacy/kludge sh
@@ -2835,14 +2481,13 @@
 			if (!Flag(FPOSIX) && !Flag(FSH) && i > 2 &&
 			    e->savefd[i])
 				fcntl(i, F_SETFD, FD_CLOEXEC);
-#endif
 		}
 		e->savefd = NULL;
 	}
 	return (0);
 }
 
-#if HAVE_MKNOD
+#if HAVE_MKNOD && !defined(__OS2__)
 int
 c_mknod(const char **wp)
 {
@@ -2939,14 +2584,13 @@
 		| "(" oexpr ")"
 		;
 
-	unary-operator ::= "-a"|"-r"|"-w"|"-x"|"-e"|"-f"|"-d"|"-c"|"-b"|"-p"|
-			   "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|
-			   "-L"|"-h"|"-S"|"-H";
+	unary-operator ::= "-a"|"-b"|"-c"|"-d"|"-e"|"-f"|"-G"|"-g"|"-H"|"-h"|
+			   "-k"|"-L"|"-n"|"-O"|"-o"|"-p"|"-r"|"-S"|"-s"|"-t"|
+			   "-u"|"-v"|"-w"|"-x"|"-z";
 
-	binary-operator ::= "="|"=="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
-			    "-nt"|"-ot"|"-ef"|
-			    "<"|">"	# rules used for [[ ... ]] expressions
-			    ;
+	binary-operator ::= "="|"=="|"!="|"<"|">"|"-eq"|"-ne"|"-gt"|"-ge"|
+			    "-lt"|"-le"|"-ef"|"-nt"|"-ot";
+
 	operand ::= <anything>
 */
 
@@ -3099,6 +2743,14 @@
 	return (TO_NONOP);
 }
 
+#ifdef __OS2__
+#define test_access(name, mode) access_ex(access, (name), (mode))
+#define test_stat(name, buffer) stat_ex((name), (buffer))
+#else
+#define test_access(name, mode) access((name), (mode))
+#define test_stat(name, buffer) stat((name), (buffer))
+#endif
+
 int
 test_eval(Test_env *te, Test_op op, const char *opnd1, const char *opnd2,
     bool do_eval)
@@ -3107,6 +2759,7 @@
 	size_t k;
 	struct stat b1, b2;
 	mksh_ari_t v1, v2;
+	struct tbl *vp;
 
 	if (!do_eval)
 		return (0);
@@ -3153,6 +2806,10 @@
 	case TO_STZER:
 		return (*opnd1 == '\0');
 
+	/* -v */
+	case TO_ISSET:
+		return ((vp = isglobal(opnd1, false)) && (vp->flag & ISSET));
+
 	/* -o */
 	case TO_OPTION:
 		if ((i = *opnd1) == '!' || i == '?')
@@ -3164,12 +2821,12 @@
 	/* -r */
 	case TO_FILRD:
 		/* LINTED use of access */
-		return (access(opnd1, R_OK) == 0);
+		return (test_access(opnd1, R_OK) == 0);
 
 	/* -w */
 	case TO_FILWR:
 		/* LINTED use of access */
-		return (access(opnd1, W_OK) == 0);
+		return (test_access(opnd1, W_OK) == 0);
 
 	/* -x */
 	case TO_FILEX:
@@ -3179,11 +2836,11 @@
 	case TO_FILAXST:
 	/* -e */
 	case TO_FILEXST:
-		return (stat(opnd1, &b1) == 0);
+		return (test_stat(opnd1, &b1) == 0);
 
-	/* -r */
+	/* -f */
 	case TO_FILREG:
-		return (stat(opnd1, &b1) == 0 && S_ISREG(b1.st_mode));
+		return (test_stat(opnd1, &b1) == 0 && S_ISREG(b1.st_mode));
 
 	/* -d */
 	case TO_FILID:
@@ -3282,7 +2939,7 @@
 	 * Binary Operators
 	 */
 
-	/* = */
+	/* =, == */
 	case TO_STEQL:
 		if (te->flags & TEF_DBRACKET) {
 			if ((i = gmatchx(opnd1, opnd2, false)))
@@ -3668,7 +3325,7 @@
 	if (!all)
 		print_ulimit(rlimits[i], how);
 	else for (i = 0; i < NELEM(rlimits); ++i) {
-		shprintf("%-20s ", rlimits[i]->name);
+		shprintf("-%c: %-20s  ", rlimits[i]->optchar, rlimits[i]->name);
 		print_ulimit(rlimits[i], how);
 	}
 	return (0);
diff --git a/src/histrap.c b/src/histrap.c
index 56723ff..26dd521 100644
--- a/src/histrap.c
+++ b/src/histrap.c
@@ -27,7 +27,7 @@
 #include <sys/file.h>
 #endif
 
-__RCSID("$MirOS: src/bin/mksh/histrap.c,v 1.159 2016/11/11 18:44:32 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/histrap.c,v 1.160 2017/04/08 01:07:16 tg Exp $");
 
 Trap sigtraps[ksh_NSIG + 1];
 static struct sigaction Sigact_ign;
@@ -829,7 +829,7 @@
 			goto retry;
 		}
 		if (hs != hist_init_retry)
-			bi_errorf(Tf_cant,
+			bi_errorf(Tf_cant_ss_s,
 			    "unlink HISTFILE", hname, cstrerror(errno));
 		histfsize = 0;
 		return;
@@ -1033,8 +1033,8 @@
 			if (!strcmp(sigtraps[i].name, "EXIT") ||
 			    !strcmp(sigtraps[i].name, "ERR")) {
 #ifndef MKSH_SMALL
-				internal_warningf("ignoring invalid signal name %s",
-				    sigtraps[i].name);
+				internal_warningf(Tinvname, sigtraps[i].name,
+				    "signal");
 #endif
 				sigtraps[i].name = null;
 			}
diff --git a/src/lex.c b/src/lex.c
index 741bb93..78c2ee7 100644
--- a/src/lex.c
+++ b/src/lex.c
@@ -2,7 +2,7 @@
 
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
- *		 2011, 2012, 2013, 2014, 2015, 2016
+ *		 2011, 2012, 2013, 2014, 2015, 2016, 2017
  *	mirabilos <m@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -23,7 +23,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/lex.c,v 1.228 2016/08/01 21:38:03 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/lex.c,v 1.234 2017/04/06 01:59:55 tg Exp $");
 
 /*
  * states while lexing word
@@ -489,7 +489,7 @@
 					 * If this is a trim operation,
 					 * treat (,|,) specially in STBRACE.
 					 */
-					if (ctype(c, C_SUBOP2)) {
+					if (ksh_issubop2(c)) {
 						ungetsc(c);
 						if (Flag(FSH))
 							PUSH_STATE(STBRACEBOURNE);
@@ -532,7 +532,7 @@
 			case '`':
  subst_gravis:
 				PUSH_STATE(SBQUOTE);
-				*wp++ = COMSUB;
+				*wp++ = COMASUB;
 				/*
 				 * We need to know whether we are within double
 				 * quotes in order to translate \" to " within
@@ -885,7 +885,7 @@
 	Xcheck(ws, wp);
 	if (statep != &states[1])
 		/* XXX figure out what is missing */
-		yyerror("no closing quote\n");
+		yyerror("no closing quote");
 
 	/* This done to avoid tests for SHEREDELIM wherever SBASE tested */
 	if (state == SHEREDELIM)
@@ -893,9 +893,7 @@
 
 	dp = Xstring(ws, wp);
 	if (state == SBASE && (
-#ifndef MKSH_LEGACY_MODE
 	    (c == '&' && !Flag(FSH) && !Flag(FPOSIX)) ||
-#endif
 	    c == '<' || c == '>') && ((c2 = Xlength(ws, wp)) == 0 ||
 	    (c2 == 2 && dp[0] == CHAR && ksh_isdigit(dp[1])))) {
 		struct ioword *iop = alloc(sizeof(struct ioword), ATEMP);
@@ -1016,15 +1014,12 @@
 	while ((dp - ident) < IDENT && (c = *sp++) == CHAR)
 		*dp++ = *sp++;
 	if (c != EOS)
-		/* word is not unquoted */
+		/* word is not unquoted, or space ran out */
 		dp = ident;
 	/* make sure the ident array stays NUL padded */
 	memset(dp, 0, (ident + IDENT) - dp + 1);
 
-	if (!(cf & (KEYWORD | ALIAS)))
-		return (LWORD);
-
-	if (*ident != '\0') {
+	if (*ident != '\0' && (cf & (KEYWORD | ALIAS))) {
 		struct tbl *p;
 		uint32_t h = hash(ident);
 
@@ -1068,6 +1063,7 @@
 				s->start = s->str = p->val.s;
 				s->u.tblp = p;
 				s->flags |= SF_HASALIAS;
+				s->line = source->line;
 				s->next = source;
 				if (source->type == SEOF) {
 					/* prevent infinite recursion at EOS */
@@ -1079,9 +1075,12 @@
 				goto Again;
 			}
 		}
-	} else if (cf & ALIAS) {
+	} else if (*ident == '\0') {
 		/* retain typeset et al. even when quoted */
-		if (assign_command((dp = wdstrip(yylval.cp, 0)), true))
+		struct tbl *tt = get_builtin((dp = wdstrip(yylval.cp, 0)));
+		uint32_t flag = tt ? tt->flag : 0;
+
+		if (flag & (DECL_UTIL | DECL_FWDR))
 			strlcpy(ident, dp, sizeof(ident));
 		afree(dp, ATEMP);
 	}
@@ -1204,6 +1203,7 @@
 	error_prefix(true);
 	va_start(va, fmt);
 	shf_vfprintf(shl_out, fmt, va);
+	shf_putc('\n', shl_out);
 	va_end(va);
 	errorfz();
 }
@@ -1625,7 +1625,7 @@
 					char *tmp, *p;
 
 					if (!arraysub(&tmp))
-						yyerror("missing ]\n");
+						yyerror("missing ]");
 					*wp++ = c;
 					for (p = tmp; *p; ) {
 						Xcheck(*wsp, wp);
diff --git a/src/lksh.1 b/src/lksh.1
index a9f8be7..c3a82cb 100644
--- a/src/lksh.1
+++ b/src/lksh.1
@@ -1,6 +1,6 @@
-.\" $MirOS: src/bin/mksh/lksh.1,v 1.20 2016/11/11 23:31:35 tg Exp $
+.\" $MirOS: src/bin/mksh/lksh.1,v 1.23 2017/04/02 13:38:02 tg Exp $
 .\"-
-.\" Copyright (c) 2008, 2009, 2010, 2012, 2013, 2015, 2016
+.\" Copyright (c) 2008, 2009, 2010, 2012, 2013, 2015, 2016, 2017
 .\"	mirabilos <m@mirbsd.org>
 .\"
 .\" Provided that these terms and disclaimer and all copyright notices
@@ -74,7 +74,7 @@
 .\" with -mandoc, it might implement .Mx itself, but we want to
 .\" use our own definition. And .Dd must come *first*, always.
 .\"
-.Dd $Mdocdate: November 11 2016 $
+.Dd $Mdocdate: April 2 2017 $
 .\"
 .\" Check which macro package we use, and do other -mdoc setup.
 .\"
@@ -173,37 +173,34 @@
 refer to its manual page for details on the scripting language.
 It is recommended to port scripts to
 .Nm mksh
-instead of relying on legacy or idiotic POSIX-mandated behaviour,
+instead of relying on legacy or objectionable POSIX-mandated behaviour,
 since the MirBSD Korn Shell scripting language is much more consistent.
 .Pp
+Do not use
+.Nm
+as an interactive or login shell; use
+.Nm mksh
+instead.
+.Pp
 Note that it's strongly recommended to invoke
 .Nm
-with at least the
+with
 .Fl o Ic posix
-option, if not both that
-.Em and Fl o Ic sh ,
 to fully enjoy better compatibility to the
 .Tn POSIX
 standard (which is probably why you use
 .Nm
 over
 .Nm mksh
-in the first place) or legacy scripts, respectively.
+in the first place);
+.Fl o Ic sh
+(possibly additionally to the above) may be needed for some legacy scripts.
 .Sh LEGACY MODE
 .Nm
 currently has the following differences from
 .Nm mksh :
 .Bl -bullet
 .It
-.\"XXX TODO: remove (some systems may wish to have lksh as ksh)
-There is no explicit support for interactive use,
-nor any command line editing or history code.
-Hence,
-.Nm
-is not suitable as a user's login shell, either; use
-.Nm mksh
-instead.
-.It
 The
 .Ev KSH_VERSION
 string identifies
@@ -241,25 +238,11 @@
 The compiler is permitted to delete all data and crash the system
 if Undefined Behaviour occurs (see above for an example).
 .It
-.\"XXX TODO: move this to FPOSIX
 The rotation arithmetic operators are not available.
 .It
 The shift arithmetic operators take all bits of the second operand into
 account; if they exceed permitted precision, the result is unspecified.
 .It
-.\"XXX TODO: move this to FPOSIX
-The
-.Tn GNU
-.Nm bash
-extension &\*(Gt to redirect stdout and stderr in one go is not parsed.
-.It
-.\"XXX TODO: drop along with allowing interactivity
-The
-.Nm mksh
-command line option
-.Fl T
-is not available.
-.It
 Unless
 .Ic set -o posix
 is active,
@@ -275,19 +258,6 @@
 .Xr getopt 1
 command.
 .It
-.\"XXX TODO: move to FPOSIX/FSH
-Unlike
-.At
-.Nm ksh ,
-.Nm mksh
-in
-.Fl o Ic posix
-or
-.Fl o Ic sh
-mode and
-.Nm lksh
-do not keep file descriptors \*(Gt 2 private from sub-processes.
-.It
 Functions defined with the
 .Ic function
 reserved word share the shell options
@@ -297,16 +267,10 @@
 .Sh SEE ALSO
 .Xr mksh 1
 .Pp
-.Pa https://www.mirbsd.org/mksh.htm
+.Pa http://www.mirbsd.org/mksh.htm
 .Pp
-.Pa https://www.mirbsd.org/ksh\-chan.htm
+.Pa http://www.mirbsd.org/ksh\-chan.htm
 .Sh CAVEATS
-The distinction between the shell variants
-.Pq Nm lksh / Nm mksh
-and shell flags
-.Pq Fl o Ic posix / Ic sh
-will be reworked for an upcoming release.
-.Pp
 To use
 .Nm
 as
@@ -315,16 +279,22 @@
 .Ic set -o posix
 by default if called as
 .Nm sh
+.Pq adding Dv \-DMKSH_BINSHPOSIX to Dv CPPFLAGS
 is highly recommended for better standards compliance.
+.Pp
 For better compatibility with legacy scripts, such as many
 .Tn Debian
 maintainer scripts, Upstart and SYSV init scripts, and other
-unfixed scripts, using the compile-time options for enabling
+unfixed scripts, also adding the
+.Dv \-DMKSH_BINSHREDUCED
+compile-time option to enable
 .Em both
 .Ic set -o posix -o sh
 when the shell is run as
-.Nm sh
-is recommended.
+.Nm sh ,
+as well as integrating the optional disrecommended
+.Xr printf 1
+builtin, might be necessary.
 .Pp
 .Nm
 tries to make a cross between a legacy bourne/posix compatibl-ish
@@ -332,14 +302,6 @@
 .Dq legacy
 is not exactly specified.
 .Pp
-The
-.Ic set
-built-in command does not currently have all options one would expect
-from a full-blown
-.Nm mksh
-or
-.Nm pdksh .
-.Pp
 Talk to the
 .Mx
 development team using the mailing list at
diff --git a/src/main.c b/src/main.c
index ebbadd9..a556d5d 100644
--- a/src/main.c
+++ b/src/main.c
@@ -5,7 +5,7 @@
 
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
- *		 2011, 2012, 2013, 2014, 2015, 2016
+ *		 2011, 2012, 2013, 2014, 2015, 2016, 2017
  *	mirabilos <m@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -34,7 +34,7 @@
 #include <locale.h>
 #endif
 
-__RCSID("$MirOS: src/bin/mksh/main.c,v 1.322 2016/11/11 23:48:30 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/main.c,v 1.332 2017/04/12 16:01:45 tg Exp $");
 
 extern char **environ;
 
@@ -56,8 +56,6 @@
 static void x_sigwinch(int);
 #endif
 
-static const char initifs[] = "IFS= \t\n";
-
 static const char initsubs[] =
     "${PS2=> }"
     "${PS3=#? }"
@@ -71,18 +69,18 @@
 	Ttypeset, "-x", "HOME", TPATH, TSHELL, NULL,
 	Ttypeset, "-i10", "COLUMNS", "LINES", "SECONDS", "TMOUT", NULL,
 	Talias,
-	"integer=\\typeset -i",
-	"local=\\typeset",
+	"integer=\\\\builtin typeset -i",
+	"local=\\\\builtin typeset",
 	/* not "alias -t --": hash -r needs to work */
-	"hash=\\builtin alias -t",
-	"type=\\builtin whence -v",
-	"autoload=\\typeset -fu",
-	"functions=\\typeset -f",
-	"history=\\builtin fc -l",
-	"nameref=\\typeset -n",
+	"hash=\\\\builtin alias -t",
+	"type=\\\\builtin whence -v",
+	"autoload=\\\\builtin typeset -fu",
+	"functions=\\\\builtin typeset -f",
+	"history=\\\\builtin fc -l",
+	"nameref=\\\\builtin typeset -n",
 	"nohup=nohup ",
-	"r=\\builtin fc -e -",
-	"login=\\exec login",
+	"r=\\\\builtin fc -e -",
+	"login=\\\\builtin exec login",
 	NULL,
 	 /* this is what AT&T ksh seems to track, with the addition of emacs */
 	Talias, "-tU",
@@ -101,6 +99,51 @@
 static struct env env;
 struct env *e = &env;
 
+/* compile-time assertions */
+#define cta(name, expr) struct cta_ ## name { char t[(expr) ? 1 : -1]; }
+
+/* this one should be defined by the standard */
+cta(char_is_1_char, (sizeof(char) == 1) && (sizeof(signed char) == 1) &&
+    (sizeof(unsigned char) == 1));
+cta(char_is_8_bits, ((CHAR_BIT) == 8) && ((int)(unsigned char)0xFF == 0xFF) &&
+    ((int)(unsigned char)0x100 == 0) && ((int)(unsigned char)(int)-1 == 0xFF));
+/* the next assertion is probably not really needed */
+cta(short_is_2_char, sizeof(short) == 2);
+cta(short_size_no_matter_of_signedness, sizeof(short) == sizeof(unsigned short));
+/* the next assertion is probably not really needed */
+cta(int_is_4_char, sizeof(int) == 4);
+cta(int_size_no_matter_of_signedness, sizeof(int) == sizeof(unsigned int));
+
+cta(long_ge_int, sizeof(long) >= sizeof(int));
+cta(long_size_no_matter_of_signedness, sizeof(long) == sizeof(unsigned long));
+
+#ifndef MKSH_LEGACY_MODE
+/* the next assertion is probably not really needed */
+cta(ari_is_4_char, sizeof(mksh_ari_t) == 4);
+/* but this is */
+cta(ari_has_31_bit, 0 < (mksh_ari_t)(((((mksh_ari_t)1 << 15) << 15) - 1) * 2 + 1));
+/* the next assertion is probably not really needed */
+cta(uari_is_4_char, sizeof(mksh_uari_t) == 4);
+/* but the next three are; we REQUIRE unsigned integer wraparound */
+cta(uari_has_31_bit, 0 < (mksh_uari_t)(((((mksh_uari_t)1 << 15) << 15) - 1) * 2 + 1));
+cta(uari_has_32_bit, 0 < (mksh_uari_t)(((((mksh_uari_t)1 << 15) << 15) - 1) * 4 + 3));
+cta(uari_wrap_32_bit,
+    (mksh_uari_t)(((((mksh_uari_t)1 << 15) << 15) - 1) * 4 + 3) >
+    (mksh_uari_t)(((((mksh_uari_t)1 << 15) << 15) - 1) * 4 + 4));
+#endif
+/* these are always required */
+cta(ari_is_signed, (mksh_ari_t)-1 < (mksh_ari_t)0);
+cta(uari_is_unsigned, (mksh_uari_t)-1 > (mksh_uari_t)0);
+/* we require these to have the precisely same size and assume 2s complement */
+cta(ari_size_no_matter_of_signedness, sizeof(mksh_ari_t) == sizeof(mksh_uari_t));
+
+cta(sizet_size_no_matter_of_signedness, sizeof(ssize_t) == sizeof(size_t));
+cta(sizet_voidptr_same_size, sizeof(size_t) == sizeof(void *));
+cta(sizet_funcptr_same_size, sizeof(size_t) == sizeof(void (*)(void)));
+/* our formatting routines assume this */
+cta(ptr_fits_in_long, sizeof(size_t) <= sizeof(long));
+cta(ari_fits_in_long, sizeof(mksh_ari_t) <= sizeof(long));
+
 static mksh_uari_t
 rndsetup(void)
 {
@@ -197,6 +240,8 @@
 	for (i = 0; i < 3; ++i)
 		if (!isatty(i))
 			setmode(i, O_BINARY);
+
+	os2_init(&argc, &argv);
 #endif
 
 	/* do things like getpgrp() et al. */
@@ -363,9 +408,10 @@
 	}
 
 	/* for security */
-	typeset(initifs, 0, 0, 0, 0);
+	typeset("IFS= \t\n", 0, 0, 0, 0);
 
 	/* assign default shell variable values */
+	typeset("PATHSEP=" MKSH_PATHSEPS, 0, 0, 0, 0);
 	substitute(initsubs, 0);
 
 	/* Figure out the current working directory and set $PWD */
@@ -382,7 +428,7 @@
 		setstr(vp, current_wd, KSH_RETURN_ERROR);
 
 	for (wp = initcoms; *wp != NULL; wp++) {
-		shcomexec(wp);
+		c_builtin(wp);
 		while (*wp != NULL)
 			wp++;
 	}
@@ -468,7 +514,9 @@
 		 * to search for it. This changes the behaviour of a
 		 * simple "mksh foo", but can't be helped.
 		 */
-		s->file = search_path(argv[argi++], path, X_OK, NULL);
+		s->file = argv[argi++];
+		if (search_access(s->file, X_OK) != 0)
+			s->file = search_path(s->file, path, X_OK, NULL);
 		if (!s->file || !*s->file)
 			s->file = argv[argi - 1];
 #else
@@ -627,7 +675,7 @@
 	}
 
 	if (restricted_shell) {
-		shcomexec(restr_com);
+		c_builtin(restr_com);
 		/* After typeset command... */
 		Flag(FRESTRICTED) = 1;
 	}
@@ -657,9 +705,9 @@
 
 	if ((rv = main_init(argc, argv, &s, &l)) == 0) {
 		if (Flag(FAS_BUILTIN)) {
-			rv = shcomexec(l->argv);
+			rv = c_builtin(l->argv);
 		} else {
-			shell(s, true);
+			shell(s, 0);
 			/* NOTREACHED */
 		}
 	}
@@ -712,7 +760,7 @@
 			unwind(i);
 			/* NOTREACHED */
 		default:
-			internal_errorf("include %d", i);
+			internal_errorf(Tunexpected_type, Tunwind, Tsource, i);
 			/* NOTREACHED */
 		}
 	}
@@ -723,7 +771,7 @@
 	s = pushs(SFILE, ATEMP);
 	s->u.shf = shf;
 	strdupx(s->file, name, ATEMP);
-	i = shell(s, false);
+	i = shell(s, 1);
 	quitenv(s->u.shf);
 	if (old_argv) {
 		e->loc->argv = old_argv;
@@ -743,7 +791,7 @@
 	s = pushs(SSTRING, ATEMP);
 	s->start = s->str = comm;
 	s->line = line;
-	rv = shell(s, false);
+	rv = shell(s, 1);
 	source = sold;
 	return (rv);
 }
@@ -752,22 +800,33 @@
  * run the commands from the input source, returning status.
  */
 int
-shell(Source * volatile s, volatile bool toplevel)
+shell(Source * volatile s, volatile int level)
 {
 	struct op *t;
 	volatile bool wastty = tobool(s->flags & SF_TTY);
 	volatile uint8_t attempts = 13;
-	volatile bool interactive = Flag(FTALKING) && toplevel;
+	volatile bool interactive = (level == 0) && Flag(FTALKING);
 	volatile bool sfirst = true;
 	Source *volatile old_source = source;
 	int i;
 
-	newenv(E_PARSE);
+	newenv(level == 2 ? E_EVAL : E_PARSE);
 	if (interactive)
 		really_exit = false;
 	switch ((i = kshsetjmp(e->jbuf))) {
 	case 0:
 		break;
+	case LBREAK:
+	case LCONTIN:
+		if (level != 2) {
+			source = old_source;
+			quitenv(NULL);
+			internal_errorf(Tf_cant_s, Tshell,
+			    i == LBREAK ? Tbreak : Tcontinue);
+			/* NOTREACHED */
+		}
+		/* assert: interactive == false */
+		/* FALLTHROUGH */
 	case LINTR:
 		/* we get here if SIGINT not caught or ignored */
 	case LERROR:
@@ -807,7 +866,7 @@
 	default:
 		source = old_source;
 		quitenv(NULL);
-		internal_errorf("shell %d", i);
+		internal_errorf(Tunexpected_type, Tunwind, Tshell, i);
 		/* NOTREACHED */
 	}
 	while (/* CONSTCOND */ 1) {
@@ -824,7 +883,7 @@
 			j_notify();
 			set_prompt(PS1, s);
 		}
-		t = compile(s, sfirst);
+		t = compile(s, sfirst, true);
 		if (interactive)
 			histsave(&s->line, NULL, HIST_FLUSH, true);
 		sfirst = false;
@@ -845,7 +904,7 @@
 				 * immediately after the last command
 				 * executed.
 				 */
-				if (toplevel)
+				if (level == 0)
 					unwind(LEXIT);
 				break;
 			}
@@ -908,6 +967,7 @@
 		case E_INCL:
 		case E_LOOP:
 		case E_ERRH:
+		case E_EVAL:
 			kshlongjmp(e->jbuf, i);
 			/* NOTREACHED */
 		case E_NONE:
@@ -1271,12 +1331,10 @@
 	    VWARNINGF_BUILTIN, fmt, va);
 	va_end(va);
 
-	/*
-	 * POSIX special builtins and ksh special builtins cause
-	 * non-interactive shells to exit. XXX may not want LERROR here
-	 */
+	/* POSIX special builtins cause non-interactive shells to exit */
 	if (builtin_spec) {
 		builtin_argv0 = NULL;
+		/* may not want to use LERROR here */
 		unwind(LERROR);
 	}
 }
@@ -1379,19 +1437,19 @@
 #ifdef DF
 	if ((lfp = getenv("SDMKSH_PATH")) == NULL) {
 		if ((lfp = getenv("HOME")) == NULL || !mksh_abspath(lfp))
-			errorf("cannot get home directory");
+			errorf("can't get home directory");
 		lfp = shf_smprintf(Tf_sSs, lfp, "mksh-dbg.txt");
 	}
 
 	if ((shl_dbg_fd = open(lfp, O_WRONLY | O_APPEND | O_CREAT, 0600)) < 0)
-		errorf("cannot open debug output file %s", lfp);
+		errorf("can't open debug output file %s", lfp);
 	if (shl_dbg_fd < FDBASE) {
 		int nfd;
 
 		nfd = fcntl(shl_dbg_fd, F_DUPFD, FDBASE);
 		close(shl_dbg_fd);
 		if ((shl_dbg_fd = nfd) == -1)
-			errorf("cannot dup debug output file");
+			errorf("can't dup debug output file");
 	}
 	fcntl(shl_dbg_fd, F_SETFD, FD_CLOEXEC);
 	shf_fdopen(shl_dbg_fd, SHF_WR, shl_dbg);
@@ -1407,7 +1465,7 @@
 	int rv;
 
 	if (((rv = dup2(ofd, nfd)) < 0) && !errok && (errno != EBADF))
-		errorf("too many files open in shell");
+		errorf(Ttoo_many_files);
 
 #ifdef __ultrix
 	/*XXX imake style */
@@ -1431,7 +1489,7 @@
 	    (errno == EBADF || errno == EPERM))
 		return (-1);
 	if (nfd < 0 || nfd > SHRT_MAX)
-		errorf("too many files open in shell");
+		errorf(Ttoo_many_files);
 	fcntl(nfd, F_SETFD, FD_CLOEXEC);
 	return ((short)nfd);
 }
@@ -1464,6 +1522,10 @@
 	pv[1] = savefd(lpv[1]);
 	if (pv[1] != lpv[1])
 		close(lpv[1]);
+#ifdef __OS2__
+	setmode(pv[0], O_BINARY);
+	setmode(pv[1], O_BINARY);
+#endif
 }
 
 void
diff --git a/src/misc.c b/src/misc.c
index 647d1d1..6957c22 100644
--- a/src/misc.c
+++ b/src/misc.c
@@ -3,7 +3,7 @@
 
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
- *		 2011, 2012, 2013, 2014, 2015, 2016
+ *		 2011, 2012, 2013, 2014, 2015, 2016, 2017
  *	mirabilos <m@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -30,7 +30,7 @@
 #include <grp.h>
 #endif
 
-__RCSID("$MirOS: src/bin/mksh/misc.c,v 1.249 2016/11/11 23:31:35 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/misc.c,v 1.255 2017/04/12 16:46:22 tg Exp $");
 
 #define KSH_CHVT_FLAG
 #ifdef MKSH_SMALL
@@ -40,10 +40,6 @@
 #define KSH_CHVT_CODE
 #define KSH_CHVT_FLAG
 #endif
-#ifdef MKSH_LEGACY_MODE
-#undef KSH_CHVT_CODE
-#undef KSH_CHVT_FLAG
-#endif
 
 /* type bits for unsigned char */
 unsigned char chtypes[UCHAR_MAX + 1];
@@ -93,11 +89,10 @@
 void
 initctypes(void)
 {
-	setctypes(letters_uc, C_ALPHA);
-	setctypes(letters_lc, C_ALPHA);
-	chtypes['_'] |= C_ALPHA;
+	setctypes(letters_uc, C_ALPHX);
+	setctypes(letters_lc, C_ALPHX);
+	chtypes['_'] |= C_ALPHX;
 	setctypes("0123456789", C_DIGIT);
-	/* \0 added automatically */
 	setctypes(TC_LEX1, C_LEX1);
 	setctypes("*@#!$-?", C_VAR1);
 	setctypes(TC_IFSWS, C_IFSWS);
@@ -506,7 +501,7 @@
 	if (arrayset) {
 		const char *ccp = NULL;
 
-		if (*array)
+		if (array && *array)
 			ccp = skip_varname(array, false);
 		if (!ccp || !(!ccp[0] || (ccp[0] == '+' && !ccp[1]))) {
 			bi_errorf(Tf_sD_s, array, Tnot_ident);
@@ -1541,12 +1536,11 @@
 				/* symlink target is an absolute path */
 				xp = Xstring(xs, xp);
  beginning_of_a_pathname:
-				/* assert: (ip == ipath)[0] == '/' */
+				/* assert: mksh_cdirsep((ip == ipath)[0]) */
 				/* assert: xp == xs.beg => start of path */
 
 				/* exactly two leading slashes? (SUSv4 3.266) */
-				/* @komh do NOT use mksh_cdirsep() here */
-				if (ip[1] == '/' && ip[2] != '/') {
+				if (ip[1] == ip[0] && !mksh_cdirsep(ip[2])) {
 					/* keep them, e.g. for UNC pathnames */
 					Xput(xs, xp, '/');
 				}
@@ -1711,15 +1705,13 @@
 	case 0:
 		return;
 	case '/':
-		/* exactly two leading slashes? (SUSv4 3.266) */
-		/* @komh no mksh_cdirsep() here! */
-		if (p[1] == '/' && p[2] != '/')
-			/* keep them, e.g. for UNC pathnames */
-			++p;
-#ifdef __OS2__
-		/* FALLTHROUGH */
+#ifdef MKSH_DOSPATH
 	case '\\':
 #endif
+		/* exactly two leading slashes? (SUSv4 3.266) */
+		if (p[1] == p[0] && !mksh_cdirsep(p[2]))
+			/* keep them, e.g. for UNC pathnames */
+			++p;
 		needslash = true;
 		break;
 	default:
@@ -2157,7 +2149,7 @@
 int
 unbksl(bool cstyle, int (*fg)(void), void (*fp)(int))
 {
-	int wc, i, c, fc;
+	int wc, i, c, fc, n;
 
 	fc = (*fg)();
 	switch (fc) {
@@ -2245,7 +2237,8 @@
 		 *	four (U: eight) digits; convert to Unicode
 		 */
 		wc = 0;
-		while (i--) {
+		n = 0;
+		while (n < i || i == -1) {
 			wc <<= 4;
 			if ((c = (*fg)()) >= ord('0') && c <= ord('9'))
 				wc += ksh_numdig(c);
@@ -2258,7 +2251,10 @@
 				(*fp)(c);
 				break;
 			}
+			++n;
 		}
+		if (!n)
+			goto unknown_escape;
 		if ((cstyle && wc > 0xFF) || fc != 'x')
 			/* Unicode marker */
 			wc += 0x100;
diff --git a/src/mksh.1 b/src/mksh.1
index 0de2b8f..6a2609a 100644
--- a/src/mksh.1
+++ b/src/mksh.1
@@ -1,8 +1,8 @@
-.\" $MirOS: src/bin/mksh/mksh.1,v 1.420 2016/11/11 23:31:36 tg Exp $
+.\" $MirOS: src/bin/mksh/mksh.1,v 1.442 2017/04/12 18:30:58 tg Exp $
 .\" $OpenBSD: ksh.1,v 1.160 2015/07/04 13:27:04 feinerer Exp $
 .\"-
 .\" Copyright © 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
-.\"		2010, 2011, 2012, 2013, 2014, 2015, 2016
+.\"		2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017
 .\"	mirabilos <m@mirbsd.org>
 .\"
 .\" Provided that these terms and disclaimer and all copyright notices
@@ -76,7 +76,7 @@
 .\" with -mandoc, it might implement .Mx itself, but we want to
 .\" use our own definition. And .Dd must come *first*, always.
 .\"
-.Dd $Mdocdate: November 11 2016 $
+.Dd $Mdocdate: April 12 2017 $
 .\"
 .\" Check which macro package we use, and do other -mdoc setup.
 .\"
@@ -186,23 +186,8 @@
 into account all information is first and foremost presented with
 .Nm
 in mind and should be taken as such.
-.Ss I'm an Android user, so what's mksh?
-.Nm mksh
-is a
-.Ux
-shell / command interpreter, similar to
-.Nm COMMAND.COM
-or
-.Nm CMD.EXE ,
-which has been included with
-.Tn Android Open Source Project
-for a while now.
-Basically, it's a program that runs in a terminal (console window),
-takes user input and runs commands or scripts, which it can also
-be asked to do by other programs, even in the background.
-Any privilege pop-ups you might be encountering are thus not
-.Nm mksh
-issues but questions by some other program utilising it.
+.Ss I use Android, OS/2, etc. so what...?
+Please see the FAQ at the end of this document.
 .Ss Invocation
 Most builtins can be called directly, for example if a link points from its
 name to the shell; not all make sense, have been tested or work at all though.
@@ -1130,17 +1115,17 @@
 .Pp
 The following command aliases are defined automatically by the shell:
 .Bd -literal -offset indent
-autoload=\*(aq\etypeset \-fu\*(aq
-functions=\*(aq\etypeset \-f\*(aq
-hash=\*(aq\ebuiltin alias \-t\*(aq
-history=\*(aq\ebuiltin fc \-l\*(aq
-integer=\*(aq\etypeset \-i\*(aq
-local=\*(aq\etypeset\*(aq
-login=\*(aq\eexec login\*(aq
-nameref=\*(aq\etypeset \-n\*(aq
+autoload=\*(aq\e\ebuiltin typeset \-fu\*(aq
+functions=\*(aq\e\ebuiltin typeset \-f\*(aq
+hash=\*(aq\e\ebuiltin alias \-t\*(aq
+history=\*(aq\e\ebuiltin fc \-l\*(aq
+integer=\*(aq\e\ebuiltin typeset \-i\*(aq
+local=\*(aq\e\ebuiltin typeset\*(aq
+login=\*(aq\e\ebuiltin exec login\*(aq
+nameref=\*(aq\e\ebuiltin typeset \-n\*(aq
 nohup=\*(aqnohup \*(aq
-r=\*(aq\ebuiltin fc \-e \-\*(aq
-type=\*(aq\ebuiltin whence \-v\*(aq
+r=\*(aq\e\ebuiltin fc \-e \-\*(aq
+type=\*(aq\e\ebuiltin whence \-v\*(aq
 .Ed
 .Pp
 Tracked aliases allow the shell to remember where it found a particular
@@ -2035,9 +2020,11 @@
 .Dq Li \&.
 command (see below).
 An empty string resulting from a leading or trailing
-colon, or two adjacent colons, is treated as a
+(semi)colon, or two adjacent ones, is treated as a
 .Dq Li \&.
 (the current directory).
+.It Ev PATHSEP
+A colon (semicolon on OS/2), for the user's convenience.
 .It Ev PGRP
 The process ID of the shell's process group leader.
 .It Ev PIPESTATUS
@@ -2127,7 +2114,7 @@
 .Nm
 now also supports the following form:
 .Bd -literal -offset indent
-PS1=$'\e1\er\e1\ee[7m\e1$PWD\e1\ee[0m\e1\*(Gt '
+PS1=$\*(aq\e1\er\e1\ee[7m\e1$PWD\e1\ee[0m\e1\*(Gt \*(aq
 .Ed
 .It Ev PS2
 Secondary prompt string, by default
@@ -2185,9 +2172,18 @@
 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
+Tilde expansion, which is done in parallel with parameter substitution,
+is applied to words starting with an unquoted
 .Ql \*(TI .
+In parameter assignments (such as those preceding a simple-command or those
+occurring in the arguments of a declaration utility), tilde expansion is done
+after any assignment (i.e. after the equals sign) or after an unquoted colon
+.Pq Ql \&: ;
+login names are also delimited by colons.
+The Korn shell, except in POSIX mode, always expands tildes after unquoted
+equals signs, not just in assignment context (see below), and enables tab
+completion for tildes after all unquoted colons during command line editing.
+.Pp
 The characters following the tilde, up to the first
 .Ql / ,
 if any, are assumed to be a login name.
@@ -2208,21 +2204,6 @@
 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 Ql \&: ;
-login names are also delimited by colons.
-.Pp
 The home directory of previously expanded login names are cached and re-used.
 The
 .Ic alias Fl d
@@ -2586,7 +2567,7 @@
 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
+.Dl $ cat /foo/bar 2\*(Gt&1 \*(Gt/dev/null \*(Ba pr \-n \-t
 .Pp
 File descriptors created by I/O redirections are private to the shell.
 .Ss Arithmetic expressions
@@ -2946,6 +2927,12 @@
 A function can be made to finish immediately using the
 .Ic return
 command; this may also be used to explicitly specify the exit status.
+Note that when called in a subshell,
+.Ic return
+will only exit that subshell and will not cause the original shell to exit
+a running function (see the
+.Ic while Ns Li \&... Ns Ic read
+loop FAQ below).
 .Pp
 Functions defined with the
 .Ic function
@@ -3022,18 +3009,18 @@
 .Nm
 commands keeping assignments:
 .Pp
-.Ic builtin , global , source , typeset ,
-.Ic wait
+.Ic global , source , typeset
 .Pp
 Builtins that are not special:
 .Pp
 .Ic [ , alias , bg , bind ,
-.Ic cat , cd , command , echo ,
-.Ic false , fc , fg , getopts ,
-.Ic jobs , kill , let , print ,
-.Ic pwd , read , realpath , rename ,
-.Ic sleep , suspend , test , true ,
-.Ic ulimit , umask , unalias , whence
+.Ic builtin , cat , cd , command ,
+.Ic echo , false , fc , fg ,
+.Ic getopts , jobs , kill , let ,
+.Ic print , pwd , read , realpath ,
+.Ic rename , sleep , suspend , test ,
+.Ic true , ulimit , umask , unalias ,
+.Ic wait , whence
 .Pp
 Once the type of command has been determined, any command-line parameter
 assignments are performed and exported for the duration of the command.
@@ -3082,6 +3069,8 @@
 Any name with a value defines an alias (see
 .Sx Aliases
 above).
+.Li \&[A\-Za\-z0\-9_!%,@\-]
+are valid in names except they may not begin with a hyphen-minus.
 .Pp
 When listing aliases, one of two formats is used.
 Normally, aliases are listed as
@@ -3217,6 +3206,18 @@
 .Ar command .
 .Pp
 .It Xo
+.Ic \ebuiltin
+.Ar command Op Ar arg ...
+.Xc
+Same as
+.Ic builtin .
+Additionally acts as declaration utility forwarder, i.e. this is a
+declaration utility (see
+.Sx Tilde expansion )
+.No iff Ar command
+is a declaration utility.
+.Pp
+.It Xo
 .Ic cat
 .Op Fl u
 .Op Ar
@@ -3346,6 +3347,7 @@
 and secondly, special built-in commands lose their specialness
 (i.e. redirection and utility errors do not cause the shell to
 exit, and command assignments are not permanent).
+The declaration utility property is not reset.
 .Pp
 If the
 .Fl p
@@ -3421,8 +3423,10 @@
 .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
+option is set or this is a direct builtin call or
+.Ic print
+.Fl R ,
+only the first argument is treated as an option, and only if it is exactly
 .Dq Li \-n .
 Backslash interpretation is disabled.
 .Pp
@@ -3463,7 +3467,7 @@
 it does pass these file descriptors on.
 .Pp
 .It Ic exit Op Ar status
-The shell exits with the specified exit status.
+The shell or subshell exits with the specified exit status.
 If
 .Ar status
 is not specified, the exit status is the current value of the
@@ -3478,6 +3482,7 @@
 Sets the export attribute of the named parameters.
 Exported parameters are passed in the environment to executed commands.
 If values are specified, the named parameters are also assigned.
+This is a declaration utility.
 .Pp
 If no parameters are specified, all parameters with the export attribute
 set are printed one per line; either their names, or, if a
@@ -3632,9 +3637,22 @@
 .Ev OPTIND
 may lead to unexpected results.
 .Pp
-.It global Ar ...
+.It Xo
+.Ic global
+.Op Ic +\-aglpnrtUux
+.Oo Fl L Ns Op Ar n
+.No \*(Ba Fl R Ns Op Ar n
+.No \*(Ba Fl Z Ns Op Ar n Oc
+.Op Fl i Ns Op Ar n
+.Oo Ar name
+.Op Ns = Ns Ar value
+.Ar ... Oc
+.Xc
 See
-.Ic typeset .
+.Ic typeset Fl g .
+.No Deprecated , Em will
+be removed from a future version of
+.Nm .
 .Pp
 .It Xo
 .Ic hash
@@ -3712,12 +3730,8 @@
 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 Ic let]
-Internally used alias for
-.Ic let .
+is syntactic sugar for:
+.Dl "{ \e\ebuiltin let \*(aq" Ns Ar expr Ns "\*(aq; }"
 .Pp
 .It Xo
 .Ic mknod
@@ -3757,13 +3771,13 @@
 .Pp
 .It Xo
 .Ic print
-.Oo Fl AclNnprsu Ns Oo Ar n Oc \*(Ba
-.Fl R Op Fl en Oc
+.Oo Fl AcelNnprsu Ns Oo Ar n Oc \*(Ba
+.Fl R Op Fl n Oc
 .Op Ar argument ...
 .Xc
 Print the specified argument(s) on the standard output,
 separated by spaces, terminated with a newline.
-The C escapes mentioned in
+The escapes mentioned in
 .Sx Backslash expansion
 above, as well as
 .Dq Li \ec ,
@@ -3789,6 +3803,9 @@
 built-in utility and the
 .Ic select
 statement do.
+.It Fl e
+Restore backslash expansion after a previous
+.Fl r .
 .It Fl l
 Change the output word separator to newline.
 .It Fl N
@@ -3803,7 +3820,7 @@
 Inhibit backslash expansion.
 .It Fl s
 Print to the history file instead of standard output.
-.It Fl u Op Ar n
+.It Fl u Ns Op Ar n
 Print to the file descriptor
 .Ar n Pq defaults to 1 if omitted
 instead of standard output.
@@ -3811,31 +3828,13 @@
 .Pp
 The
 .Fl R
-option is used to emulate, to some degree, the
+option mostly emulates 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
-.Xr printf 1 ,
-utility, except it uses the same
-.Sx Backslash expansion
-and I/O code and does not handle floating point as the rest of
-.Nm mksh .
-An external utility is preferred over the builtin.
-This is not normally part of
-.Nm mksh ;
-however, distributors may have added this as builtin as a speed hack.
-Do not use in new code.
+command which does not expand backslashes and interprets
+its first argument as option only if it is exactly
+.Dq Li \-n
+.Pq to suppress the trailing newline .
 .Pp
 .It Ic pwd Op Fl LP
 Print the present working directory.
@@ -3970,40 +3969,6 @@
 .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.
-Similarly, 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 \-pr filename; do
-	print \-r \-\- "found \*(Lt${filename#./}\*(Gt"
-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
@@ -4012,6 +3977,7 @@
 .Ar ... Oc
 .Xc
 Sets the read-only attribute of the named parameters.
+This is a declaration utility.
 If values are given,
 parameters are set to them before setting the attribute.
 Once a parameter is
@@ -4266,7 +4232,6 @@
 .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 .
@@ -4507,34 +4472,6 @@
 .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
-.Dq Li \-x
-or
-.Dq Li +x
-instead of
-.Dq Li xtrace .
 .It Fl p Ar file
 .Ar file
 is a named pipe
@@ -4596,6 +4533,38 @@
 .It Fl z Ar string
 .Ar string
 is empty.
+.It Fl v Ar name
+The shell parameter
+.Ar name
+is set.
+.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
+.Dq Li \-x
+or
+.Dq Li +x
+instead of
+.Dq Li xtrace .
 .It Ar string No = Ar string
 Strings are equal.
 .It Ar string No == Ar string
@@ -4800,28 +4769,23 @@
 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
+.Ic typeset
+.Op Ic +\-aglpnrtUux
+.Oo Fl L Ns Op Ar n
+.No \*(Ba Fl R Ns Op Ar n
+.No \*(Ba Fl Z Ns Op Ar n Oc
 .Op Fl i Ns Op Ar n
-.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
+.Fl f Op Fl tux
+.Op Ar name ...
 .Xc
 Display or set parameter attributes.
+This is a declaration utility.
 With no
 .Ar name
 arguments, parameter attributes are displayed; if no options are used, the
@@ -4837,27 +4801,16 @@
 If
 .Ar name
 arguments are given, the attributes of the named parameters are set
-.Pq Ic \-
+.Pq Ic \&\-
 or cleared
-.Pq Ic + .
+.Pq Ic \&+ ;
+inside a function, this will cause the parameters to be created
+(with no value) in the local scope (but see
+.Fl g ) .
 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 .
-.Em Note :
-This means that
-.Nm No 's Ic global
-command is
-.Em not
-equivalent to other programming languages' as it does not allow a
-function called from another function to access a parameter at truly
-global scope, but only prevents putting an accessed one into local scope.
+the change affects all elements of the array, and no value may be specified.
 .Pp
 When
 .Fl f
@@ -4877,6 +4830,9 @@
 .It Fl f
 Function mode.
 Display or set functions and their attributes, instead of parameters.
+.It Fl g
+Do not cause named parameters to be created in
+the local scope when called inside a function.
 .It Fl i Ns Op Ar n
 Integer attribute.
 .Ar n
@@ -5028,7 +4984,7 @@
 Also note that the types of limits available are system
 dependent \*(en some systems have only the
 .Fl f
-limit.
+limit, or not even that, or can set only the soft limits
 .Bl -tag -width 5n
 .It Fl a
 Display all limits; unless
@@ -5395,11 +5351,11 @@
 or opinionated differences can be disabled by using this mode; these are:
 .Bl -bullet
 .It
-The GNU
+The incompatible GNU
 .Nm bash
 I/O redirection
 .Ic &\*(Gt Ns Ar file
-is no longer supported.
+is not supported.
 .It
 File descriptors created by I/O redirections are inherited by
 child processes.
@@ -5409,20 +5365,34 @@
 The
 .Nm echo
 builtin does not interpret backslashes and only supports the exact option
-.Dq Li \-n .
+.Fl n .
 .It
-\&... (list is incomplete and may change for R54)
+Alias expansion with a trailing space only reruns on command words.
+.It
+Tilde expansion follows POSIX instead of Korn shell rules.
+.It
+The exit status of
+.Ic fg
+is always 0.
+.It
+.Ic kill
+.Fl l
+only lists signal names, all in one line.
+.It
+.Ic getopts
+does not accept options with a leading
+.Ql + .
 .El
 .Ss SH mode
 Compatibility mode; intended for use with legacy scripts that
 cannot easily be fixed; the changes are as follows:
 .Bl -bullet
 .It
-The GNU
+The incompatible GNU
 .Nm bash
 I/O redirection
 .Ic &\*(Gt Ns Ar file
-is no longer supported.
+is not supported.
 .It
 File descriptors created by I/O redirections are inherited by
 child processes.
@@ -5430,7 +5400,9 @@
 The
 .Nm echo
 builtin does not interpret backslashes and only supports the exact option
-.Dq Li \-n .
+.Fl n ,
+unless built with
+.Ev \-DMKSH_MIDNIGHTBSD01ASH_COMPAT .
 .It
 The substitution operations
 .Sm off
@@ -5460,7 +5432,16 @@
 .Xc
 wrongly do not require a parenthesis to be escaped and do not parse extglobs.
 .It
-\&... (list is incomplete and may change for R54)
+The getopt construct from
+.Xr lksh 1
+passes through the errorlevel.
+.It
+.Nm sh
+.Fl c
+eats a leading
+.Fl \-
+if built with
+.Ev \-DMKSH_MIDNIGHTBSD01ASH_COMPAT .
 .El
 .Ss Interactive input line editing
 The shell supports three modes of reading command lines from a
@@ -5744,9 +5725,6 @@
 .No KILL Pq \*(haU
 .Xc
 Deletes the entire input line.
-If Ctrl-U should only delete the line up to the cursor, use:
-.Pp
-.D1 $ bind \-m \*(haU='\*(ha[0\*(haK'
 .It kill\-region: \*(haW
 Deletes the input between the cursor and the mark.
 .It Xo kill\-to\-eol:
@@ -6514,7 +6492,7 @@
 .Xr utf\-8 7 ,
 .Xr mknod 8
 .Pp
-.Pa https://www.mirbsd.org/ksh\-chan.htm
+.Pa http://www.mirbsd.org/ksh\-chan.htm
 .Rs
 .%A Morris Bolsky
 .%B "The KornShell Command and Programming Language"
@@ -6608,9 +6586,17 @@
 contributors including our users, to improve the shell is appreciated.
 See the documentation, web site and CVS for details.
 .Pp
+.Nm mksh\-os2
+is developed by
+.An KO Myung-Hun Aq Mt komh@chollian.net .
+.Pp
+.Nm mksh\-w32
+is developed by
+.An Michael Langguth Aq Mt lan@scalaris.com .
+.Pp
 The BSD daemon is Copyright \(co Marshall Kirk McKusick.
 The complete legalese is at:
-.Pa https://www.mirbsd.org/TaC\-mksh.txt
+.Pa http://www.mirbsd.org/TaC\-mksh.txt
 .\"
 .\" This boils down to: feel free to use mksh.ico as application icon
 .\" or shortcut for mksh or mksh/Win32 or OS/2; distro patches are ok
@@ -6625,24 +6611,6 @@
 .\" to protect it or lose it, which McKusick almost did.
 .\"
 .Sh CAVEATS
-.Nm
-has a different scope model from
-.At
-.Nm ksh ,
-which leads to subtle differences in semantics for identical builtins.
-This can cause issues with a
-.Ic nameref
-to suddenly point to a local variable by accident; fixing this is hard.
-.Pp
-The parts of a pipeline, like below, are executed in subshells.
-Thus, variable assignments inside them are not visible in the
-surrounding execution environment.
-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
-.Pp
 .Nm mksh
 provides a consistent 32-bit integer arithmetic implementation, both
 signed and unsigned, with sign of the result of a remainder operation
@@ -6687,6 +6655,8 @@
 esac
 .Ed
 In near future, (Unicode) locale tracking will be implemented though.
+.Pp
+See also the FAQ below.
 .Sh BUGS
 Suspending (using \*(haZ) pipelines like the one below will only suspend
 the currently running part of the pipeline; in this example,
@@ -6710,7 +6680,7 @@
 .Xr memmove 3 .
 .Pp
 This document attempts to describe
-.Nm mksh\ R54
+.Nm mksh\ R55
 and up,
 .\" with vendor patches from insert-your-name-here,
 compiled without any options impacting functionality, such as
@@ -6727,9 +6697,8 @@
 Please report bugs in
 .Nm
 to the
-.Mx
-mailing list at
 .Aq Mt miros\-mksh@mirbsd.org
+mailing list
 or in the
 .Li \&#\&!/bin/mksh
 .Pq or Li \&#ksh
@@ -6738,3 +6707,177 @@
 .Pq Port 6697 SSL, 6667 unencrypted ,
 or at:
 .Pa https://launchpad.net/mksh
+.Sh FREQUENTLY ASKED QUESTIONS
+This FAQ attempts to document some of the questions users of
+.Nm
+or readers of this manual page may encounter.
+.Ss I'm an Android user, so what's mksh?
+.Nm mksh
+is a
+.Ux
+shell / command interpreter, similar to
+.Nm COMMAND.COM
+or
+.Nm CMD.EXE ,
+which has been included with
+.Tn Android Open Source Project
+for a while now.
+Basically, it's a program that runs in a terminal (console window),
+takes user input and runs commands or scripts, which it can also
+be asked to do by other programs, even in the background.
+Any privilege pop-ups you might be encountering are thus not
+.Nm mksh
+issues but questions by some other program utilising it.
+.Ss "I'm an OS/2 user, what do I need to know?"
+Unlike the native command prompt, the current working directory is,
+for security reasons common on Unix systems which the shell is designed for,
+not in the search path at all; if you really need this, run the command
+.Li PATH=.$PATHSEP$PATH
+or add that to a suitable initialisation file.
+.Pp
+There are two different newline modes for mksh-os2: standard (Unix) mode,
+in which only LF (0A hex) is supported as line separator, and "textmode",
+which also accepts ASCII newlines (CR+LF), like most other tools on OS/2,
+but creating an incompatibility with standard
+.Nm .
+If you compiled mksh from source, you will get the standard Unix mode unless
+.Fl T
+is added during compilation; you will most likely have gotten this shell
+through komh's port on Hobbes, or from his OS/2 Factory on eComStation
+Korea, which uses "textmode", though.
+Most OS/2 users will want to use "textmode" unless they need absolute
+compatibility with Unix
+.Nm .
+.Ss "How do I start mksh on a specific terminal?"
+Normally:
+.Dl mksh \-T/dev/tty2
+.Pp
+However, if you want for it to return (e.g. for an embedded
+system rescue shell), use this on your real console device instead:
+.Dl mksh \-T!/dev/ttyACM0
+.Pp
+.Nm
+can also daemonise (send to the background):
+.Dl mksh \-T\- \-c \*(aqexec cdio lock\*(aq
+.Ss "POSIX says..."
+Run the shell in POSIX mode (and possibly
+.Nm lksh
+instead of
+.Nm mksh ) :
+.Dl set \-o posix
+.Ss "My prompt from <some other shell> does not work!"
+Contact us on the mailing list or on IRC, we'll convert it for you.
+.Ss "Something is going wrong with my while...read loop"
+Most likely, you've encountered the problem in which the shell runs
+all parts of a pipeline as subshell.
+The inner loop will be executed in a subshell and variable changes
+cannot be propagated if run in a pipeline:
+.Bd -literal -offset indent
+bar \*(Ba baz \*(Ba while read foo; do ...; done
+.Ed
+.Pp
+Note that
+.Ic exit
+in the inner loop will only exit the subshell and not the original shell.
+Likewise, if the code is inside a function,
+.Ic return
+in the inner loop will only exit the subshell and won't terminate the function.
+.Pp
+Use co-processes instead:
+.Bd -literal -offset indent
+bar \*(Ba baz \*(Ba&
+while read \-p foo; do ...; done
+exec 3\*(Gt&p; exec 3\*(Gt&\-
+.Ed
+.Pp
+If
+.Ic read
+is run in a loop such as
+.Ic while read foo; do ...; done
+then leading whitespace will be removed (IFS) and backslashes processed.
+You might want to use
+.Ic while IFS= read \-r foo; do ...; done
+for pristine I/O.
+Similarly, when using the
+.Fl a
+option, use of the
+.Fl r
+option might be prudent
+.Pq Dq Li read \-raN\-1 arr \*(Ltfile ;
+the same applies for NUL-terminated lines:
+.Bd -literal -offset indent
+find . \-type f \-print0 \*(Ba& \e
+    while IFS= read \-d \*(aq\*(aq \-pr filename; do
+	print \-r \-\- "found \*(Lt${filename#./}\*(Gt"
+done
+.Ed
+.Pp
+.Ss "What differences in function-local scopes are there?"
+.Nm
+has a different scope model from
+.At
+.Nm ksh ,
+which leads to subtle differences in semantics for identical builtins.
+This can cause issues with a
+.Ic nameref
+to suddenly point to a local variable by accident.
+.Pp
+.Tn GNU
+.Nm bash
+allows unsetting local variables; in
+.Nm ,
+doing so in a function allows back access to the global variable
+(actually the one in the next scope up) with the same name.
+The following code, when run before the function definitions, changes
+the behaviour of
+.Ic unset
+to behave like other shells (the alias can be removed after the definitions):
+.Bd -literal -offset indent
+case ${KSH_VERSION:\-} in
+*MIRBSD\ KSH*\*(Ba*LEGACY\ KSH*)
+	function unset_compat {
+		\e\ebuiltin typeset unset_compat_x
+
+		for unset_compat_x in "$@"; do
+			eval "\e\e\e\ebuiltin unset $unset_compat_x[*]"
+		done
+	}
+	\e\ebuiltin alias unset=unset_compat
+	;;
+esac
+.Ed
+.Pp
+When a local variable is created (e.g. using
+.Ic local ,
+.Ic typeset ,
+.Ic integer ,
+.Ic \e\ebuiltin typeset )
+it does not, like in other shells, inherit the value from the global
+(next scope up) variable with the same name; it is rather created
+without any value (unset but defined).
+.Ss "I get an error in this regex comparison"
+Use extglobs instead of regexes:
+.Dl "[[ foo =~ (foo\*(Babar).*baz ]]	# becomes"
+.Dl "[[ foo = *@(foo\*(Babar)*baz* ]]	# instead"
+.Ss "Are there any extensions to avoid?"
+.Tn GNU
+.Nm bash
+supports
+.Dq Li &\*(Gt
+.Pq and Dq Li \*(Ba&
+to redirect both stdout and stderr in one go, but this breaks POSIX
+and Korn Shell syntax; use POSIX redirections instead:
+.Dl "foo \*(Ba& bar \*(Ba& baz &\*(Gtlog			     # GNU bash"
+.Dl "foo 2\*(Gt&1 \*(Ba bar 2\*(Gt&1 \*(Ba baz \*(Gtlog 2\*(Gt&1	# POSIX"
+.Ss "\*(haL (Ctrl-L) does not clear the screen"
+Use \*(ha[\*(haL (Escape+Ctrl-L) or rebind it:
+.Dl bind \*(aq\*(haL=clear-screen\*(aq
+.Ss "\*(haU (Ctrl-U) clears the entire line"
+If it should only delete the line up to the cursor, use:
+.Dl bind \-m \*(haU=\*(aq\*(ha[0\*(haK\*(aq
+.Ss "Cursor Up behaves differently from zsh"
+Some shells make Cursor Up search in the history only for
+commands starting with what was already entered.
+.Nm
+separates the shortcuts: Cursor Up goes up one command
+and PgUp searches the history as described above.
diff --git a/src/os2.c b/src/os2.c
new file mode 100644
index 0000000..5d39630
--- /dev/null
+++ b/src/os2.c
@@ -0,0 +1,557 @@
+/*-
+ * Copyright (c) 2015
+ *	KO Myung-Hun <komh@chollian.net>
+ *
+ * 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.
+ */
+
+#define INCL_DOS
+#include <os2.h>
+
+#include "sh.h"
+
+#include <klibc/startup.h>
+#include <io.h>
+#include <unistd.h>
+#include <process.h>
+
+__RCSID("$MirOS: src/bin/mksh/os2.c,v 1.1 2017/04/02 15:00:44 tg Exp $");
+
+static char *remove_trailing_dots(char *);
+static int access_stat_ex(int (*)(), const char *, void *);
+static int test_exec_exist(const char *, char *);
+static void response(int *, const char ***);
+static char *make_response_file(char * const *);
+static void env_slashify(void);
+static void add_temp(const char *);
+static void cleanup_temps(void);
+static void cleanup(void);
+
+#define RPUT(x) do {					\
+	if (new_argc >= new_alloc) {			\
+		new_alloc += 20;			\
+		if (!(new_argv = realloc(new_argv,	\
+		    new_alloc * sizeof(char *))))	\
+			goto exit_out_of_memory;	\
+	}						\
+	new_argv[new_argc++] = (x);			\
+} while (/* CONSTCOND */ 0)
+
+#define KLIBC_ARG_RESPONSE_EXCLUDE	\
+	(__KLIBC_ARG_DQUOTE | __KLIBC_ARG_WILDCARD | __KLIBC_ARG_SHELL)
+
+static void
+response(int *argcp, const char ***argvp)
+{
+	int i, old_argc, new_argc, new_alloc = 0;
+	const char **old_argv, **new_argv;
+	char *line, *l, *p;
+	FILE *f;
+
+	old_argc = *argcp;
+	old_argv = *argvp;
+	for (i = 1; i < old_argc; ++i)
+		if (old_argv[i] &&
+		    !(old_argv[i][-1] & KLIBC_ARG_RESPONSE_EXCLUDE) &&
+		    old_argv[i][0] == '@')
+			break;
+
+	if (i >= old_argc)
+		/* do nothing */
+		return;
+
+	new_argv = NULL;
+	new_argc = 0;
+	for (i = 0; i < old_argc; ++i) {
+		if (i == 0 || !old_argv[i] ||
+		    (old_argv[i][-1] & KLIBC_ARG_RESPONSE_EXCLUDE) ||
+		    old_argv[i][0] != '@' ||
+		    !(f = fopen(old_argv[i] + 1, "rt")))
+			RPUT(old_argv[i]);
+		else {
+			long filesize;
+
+			fseek(f, 0, SEEK_END);
+			filesize = ftell(f);
+			fseek(f, 0, SEEK_SET);
+
+			line = malloc(filesize + /* type */ 1 + /* NUL */ 1);
+			if (!line) {
+ exit_out_of_memory:
+				fputs("Out of memory while reading response file\n", stderr);
+				exit(255);
+			}
+
+			line[0] = __KLIBC_ARG_NONZERO | __KLIBC_ARG_RESPONSE;
+			l = line + 1;
+			while (fgets(l, (filesize + 1) - (l - (line + 1)), f)) {
+				p = strchr(l, '\n');
+				if (p) {
+					/*
+					 * if a line ends with a backslash,
+					 * concatenate with the next line
+					 */
+					if (p > l && p[-1] == '\\') {
+						char *p1;
+						int count = 0;
+
+						for (p1 = p - 1; p1 >= l &&
+						    *p1 == '\\'; p1--)
+							count++;
+
+						if (count & 1) {
+							l = p + 1;
+
+							continue;
+						}
+					}
+
+					*p = 0;
+				}
+				p = strdup(line);
+				if (!p)
+					goto exit_out_of_memory;
+
+				RPUT(p + 1);
+
+				l = line + 1;
+			}
+
+			free(line);
+
+			if (ferror(f)) {
+				fputs("Cannot read response file\n", stderr);
+				exit(255);
+			}
+
+			fclose(f);
+		}
+	}
+
+	RPUT(NULL);
+	--new_argc;
+
+	*argcp = new_argc;
+	*argvp = new_argv;
+}
+
+static void
+init_extlibpath(void)
+{
+	const char *vars[] = {
+		"BEGINLIBPATH",
+		"ENDLIBPATH",
+		"LIBPATHSTRICT",
+		NULL
+	};
+	char val[512];
+	int flag;
+
+	for (flag = 0; vars[flag]; flag++) {
+		DosQueryExtLIBPATH(val, flag + 1);
+		if (val[0])
+			setenv(vars[flag], val, 1);
+	}
+}
+
+/*
+ * Convert backslashes of environmental variables to forward slahes.
+ * A backslash may be used as an escaped character when doing 'echo'.
+ * This leads to an unexpected behavior.
+ */
+static void
+env_slashify(void)
+{
+	/*
+	 * PATH and TMPDIR are used by OS/2 as well. That is, they may
+	 * have backslashes as a directory separator.
+	 * BEGINLIBPATH and ENDLIBPATH are special variables on OS/2.
+	 */
+	const char *var_list[] = {
+		"PATH",
+		"TMPDIR",
+		"BEGINLIBPATH",
+		"ENDLIBPATH",
+		NULL
+	};
+	const char **var;
+	char *value;
+
+	for (var = var_list; *var; var++) {
+		value = getenv(*var);
+
+		if (value)
+			_fnslashify(value);
+	}
+}
+
+void
+os2_init(int *argcp, const char ***argvp)
+{
+	response(argcp, argvp);
+
+	init_extlibpath();
+	env_slashify();
+
+	if (!isatty(STDIN_FILENO))
+		setmode(STDIN_FILENO, O_BINARY);
+	if (!isatty(STDOUT_FILENO))
+		setmode(STDOUT_FILENO, O_BINARY);
+	if (!isatty(STDERR_FILENO))
+		setmode(STDERR_FILENO, O_BINARY);
+
+	atexit(cleanup);
+}
+
+void
+setextlibpath(const char *name, const char *val)
+{
+	int flag;
+	char *p, *cp;
+
+	if (!strcmp(name, "BEGINLIBPATH"))
+		flag = BEGIN_LIBPATH;
+	else if (!strcmp(name, "ENDLIBPATH"))
+		flag = END_LIBPATH;
+	else if (!strcmp(name, "LIBPATHSTRICT"))
+		flag = LIBPATHSTRICT;
+	else
+		return;
+
+	/* convert slashes to backslashes */
+	strdupx(cp, val, ATEMP);
+	for (p = cp; *p; p++) {
+		if (*p == '/')
+			*p = '\\';
+	}
+
+	DosSetExtLIBPATH(cp, flag);
+
+	afree(cp, ATEMP);
+}
+
+/* remove trailing dots */
+static char *
+remove_trailing_dots(char *name)
+{
+	char *p;
+
+	for (p = name + strlen(name); --p > name && *p == '.'; )
+		/* nothing */;
+
+	if (*p != '.' && *p != '/' && *p != '\\' && *p != ':')
+		p[1] = '\0';
+
+	return (name);
+}
+
+#define REMOVE_TRAILING_DOTS(name)	\
+	remove_trailing_dots(memcpy(alloca(strlen(name) + 1), name, strlen(name) + 1))
+
+/* alias of stat() */
+extern int _std_stat(const char *, struct stat *);
+
+/* replacement for stat() of kLIBC which fails if there are trailing dots */
+int
+stat(const char *name, struct stat *buffer)
+{
+	return (_std_stat(REMOVE_TRAILING_DOTS(name), buffer));
+}
+
+/* alias of access() */
+extern int _std_access(const char *, int);
+
+/* replacement for access() of kLIBC which fails if there are trailing dots */
+int
+access(const char *name, int mode)
+{
+	/*
+	 * On OS/2 kLIBC, X_OK is set only for executable files.
+	 * This prevents scripts from being executed.
+	 */
+	if (mode & X_OK)
+		mode = (mode & ~X_OK) | R_OK;
+
+	return (_std_access(REMOVE_TRAILING_DOTS(name), mode));
+}
+
+#define MAX_X_SUFFIX_LEN	4
+
+static const char *x_suffix_list[] =
+    { "", ".ksh", ".exe", ".sh", ".cmd", ".com", ".bat", NULL };
+
+/* call fn() by appending executable extensions */
+static int
+access_stat_ex(int (*fn)(), const char *name, void *arg)
+{
+	char *x_name;
+	const char **x_suffix;
+	int rc = -1;
+	size_t x_namelen = strlen(name) + MAX_X_SUFFIX_LEN + 1;
+
+	/* otherwise, try to append executable suffixes */
+	x_name = alloc(x_namelen, ATEMP);
+
+	for (x_suffix = x_suffix_list; rc && *x_suffix; x_suffix++) {
+		strlcpy(x_name, name, x_namelen);
+		strlcat(x_name, *x_suffix, x_namelen);
+
+		rc = fn(x_name, arg);
+	}
+
+	afree(x_name, ATEMP);
+
+	return (rc);
+}
+
+/* access()/search_access() version */
+int
+access_ex(int (*fn)(const char *, int), const char *name, int mode)
+{
+	/*XXX this smells fishy --mirabilos */
+	return (access_stat_ex(fn, name, (void *)mode));
+}
+
+/* stat() version */
+int
+stat_ex(const char *name, struct stat *buffer)
+{
+	return (access_stat_ex(stat, name, buffer));
+}
+
+static int
+test_exec_exist(const char *name, char *real_name)
+{
+	struct stat sb;
+
+	if (stat(name, &sb) < 0 || !S_ISREG(sb.st_mode))
+		return (-1);
+
+	/* safe due to calculations in real_exec_name() */
+	memcpy(real_name, name, strlen(name) + 1);
+
+	return (0);
+}
+
+const char *
+real_exec_name(const char *name)
+{
+	char x_name[strlen(name) + MAX_X_SUFFIX_LEN + 1];
+	const char *real_name = name;
+
+	if (access_stat_ex(test_exec_exist, real_name, x_name) != -1)
+		/*XXX memory leak */
+		strdupx(real_name, x_name, ATEMP);
+
+	return (real_name);
+}
+
+/* OS/2 can process a command line up to 32 KiB */
+#define MAX_CMD_LINE_LEN 32768
+
+/* make a response file to pass a very long command line */
+static char *
+make_response_file(char * const *argv)
+{
+	char rsp_name_arg[] = "@mksh-rsp-XXXXXX";
+	char *rsp_name = &rsp_name_arg[1];
+	int arg_len = 0;
+	int i;
+
+	for (i = 0; argv[i]; i++)
+		arg_len += strlen(argv[i]) + 1;
+
+	/*
+	 * If a length of command line is longer than MAX_CMD_LINE_LEN, then
+	 * use a response file. OS/2 cannot process a command line longer
+	 * than 32K. Of course, a response file cannot be recognised by a
+	 * normal OS/2 program, that is, neither non-EMX or non-kLIBC. But
+	 * it cannot accept a command line longer than 32K in itself. So
+	 * using a response file in this case, is an acceptable solution.
+	 */
+	if (arg_len > MAX_CMD_LINE_LEN) {
+		int fd;
+		char *result;
+
+		if ((fd = mkstemp(rsp_name)) == -1)
+			return (NULL);
+
+		/* write all the arguments except a 0th program name */
+		for (i = 1; argv[i]; i++) {
+			write(fd, argv[i], strlen(argv[i]));
+			write(fd, "\n", 1);
+		}
+
+		close(fd);
+		add_temp(rsp_name);
+		strdupx(result, rsp_name_arg, ATEMP);
+		return (result);
+	}
+
+	return (NULL);
+}
+
+/* alias of execve() */
+extern int _std_execve(const char *, char * const *, char * const *);
+
+/* replacement for execve() of kLIBC */
+int
+execve(const char *name, char * const *argv, char * const *envp)
+{
+	const char *exec_name;
+	FILE *fp;
+	char sign[2];
+	char *rsp_argv[3];
+	char *rsp_name_arg;
+	int pid;
+	int status;
+	int fd;
+	int rc;
+
+	/*
+	 * #! /bin/sh : append .exe
+	 * extproc sh : search sh.exe in PATH
+	 */
+	exec_name = search_path(name, path, X_OK, NULL);
+	if (!exec_name) {
+		errno = ENOENT;
+		return (-1);
+	}
+
+	/*-
+	 * kLIBC execve() has problems when executing scripts.
+	 * 1. it fails to execute a script if a directory whose name
+	 *    is same as an interpreter exists in a current directory.
+	 * 2. it fails to execute a script not starting with sharpbang.
+	 * 3. it fails to execute a batch file if COMSPEC is set to a shell
+	 *    incompatible with cmd.exe, such as /bin/sh.
+	 * And ksh process scripts more well, so let ksh process scripts.
+	 */
+	errno = 0;
+	if (!(fp = fopen(exec_name, "rb")))
+		errno = ENOEXEC;
+
+	if (!errno && fread(sign, 1, sizeof(sign), fp) != sizeof(sign))
+		errno = ENOEXEC;
+
+	if (fp && fclose(fp))
+		errno = ENOEXEC;
+
+	if (!errno &&
+	    !((sign[0] == 'M' && sign[1] == 'Z') ||
+	      (sign[0] == 'N' && sign[1] == 'E') ||
+	      (sign[0] == 'L' && sign[1] == 'X')))
+		errno = ENOEXEC;
+
+	if (errno == ENOEXEC)
+		return (-1);
+
+	rsp_name_arg = make_response_file(argv);
+
+	if (rsp_name_arg) {
+		rsp_argv[0] = argv[0];
+		rsp_argv[1] = rsp_name_arg;
+		rsp_argv[2] = NULL;
+
+		argv = rsp_argv;
+	}
+
+	pid = spawnve(P_NOWAIT, exec_name, argv, envp);
+
+	afree(rsp_name_arg, ATEMP);
+
+	if (pid == -1) {
+		cleanup_temps();
+
+		return (-1);
+	}
+
+	/* close all opened handles */
+	for (fd = 0; fd < NUFILE; fd++) {
+		if (fcntl(fd, F_GETFD) == -1)
+			continue;
+
+		close(fd);
+	}
+
+	while ((rc = waitpid(pid, &status, 0)) < 0 && errno == EINTR)
+		/* nothing */;
+
+	cleanup_temps();
+
+	/* Is this possible? And is this right? */
+	if (rc == -1)
+		return (-1);
+
+	if (WIFSIGNALED(status))
+		_exit(ksh_sigmask(WTERMSIG(status)));
+
+	_exit(WEXITSTATUS(status));
+}
+
+static struct temp *templist = NULL;
+
+static void
+add_temp(const char *name)
+{
+	struct temp *tp;
+
+	tp = alloc(offsetof(struct temp, tffn[0]) + strlen(name) + 1, APERM);
+	memcpy(tp->tffn, name, strlen(name) + 1);
+	tp->next = templist;
+	templist = tp;
+}
+
+/* alias of unlink() */
+extern int _std_unlink(const char *);
+
+/*
+ * Replacement for unlink() of kLIBC not supporting to remove files used by
+ * another processes.
+ */
+int
+unlink(const char *name)
+{
+	int rc;
+
+	rc = _std_unlink(name);
+	if (rc == -1 && errno != ENOENT)
+		add_temp(name);
+
+	return (rc);
+}
+
+static void
+cleanup_temps(void)
+{
+	struct temp *tp;
+	struct temp **tpnext;
+
+	for (tpnext = &templist, tp = templist; tp; tp = *tpnext) {
+		if (_std_unlink(tp->tffn) == 0 || errno == ENOENT) {
+			*tpnext = tp->next;
+			afree(tp, APERM);
+		} else {
+			tpnext = &tp->next;
+		}
+	}
+}
+
+static void
+cleanup(void)
+{
+	cleanup_temps();
+}
diff --git a/src/sh.h b/src/sh.h
index c9b9078..5b36378 100644
--- a/src/sh.h
+++ b/src/sh.h
@@ -10,7 +10,7 @@
 
 /*-
  * Copyright © 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
- *	       2011, 2012, 2013, 2014, 2015, 2016
+ *	       2011, 2012, 2013, 2014, 2015, 2016, 2017
  *	mirabilos <m@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -175,9 +175,9 @@
 #endif
 
 #ifdef EXTERN
-__RCSID("$MirOS: src/bin/mksh/sh.h,v 1.791 2016/11/11 23:31:38 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/sh.h,v 1.808 2017/04/12 17:38:46 tg Exp $");
 #endif
-#define MKSH_VERSION "R54 2016/11/11"
+#define MKSH_VERSION "R55 2017/04/12"
 
 /* arithmetic types: C implementation */
 #if !HAVE_CAN_INTTYPES
@@ -392,13 +392,20 @@
 #endif
 
 #ifdef __OS2__
+#define MKSH_UNIXROOT	"/@unixroot"
+#else
+#define MKSH_UNIXROOT	""
+#endif
+
+#ifdef MKSH_DOSPATH
+#ifndef __GNUC__
+# error GCC extensions needed later on
+#endif
 #define MKSH_PATHSEPS	";"
 #define MKSH_PATHSEPC	';'
-#define MKSH_UNIXROOT	"/@unixroot"
 #else
 #define MKSH_PATHSEPS	":"
 #define MKSH_PATHSEPC	':'
-#define MKSH_UNIXROOT	""
 #endif
 
 #if !HAVE_FLOCK_DECL
@@ -505,12 +512,20 @@
 EXTERN const char *safe_prompt; /* safe prompt if PS1 substitution fails */
 
 #ifdef MKSH_LEGACY_MODE
-#define KSH_VERSIONNAME	"LEGACY"
+#define KSH_VERSIONNAME_ISLEGACY	"LEGACY"
 #else
-#define KSH_VERSIONNAME	"MIRBSD"
+#define KSH_VERSIONNAME_ISLEGACY	"MIRBSD"
 #endif
-EXTERN const char initvsn[] E_INIT("KSH_VERSION=@(#)" KSH_VERSIONNAME \
-    " KSH " MKSH_VERSION);
+#ifdef MKSH_WITH_TEXTMODE
+#define KSH_VERSIONNAME_TEXTMODE	" +TEXTMODE"
+#else
+#define KSH_VERSIONNAME_TEXTMODE	""
+#endif
+#ifndef KSH_VERSIONNAME_VENDOR_EXT
+#define KSH_VERSIONNAME_VENDOR_EXT	""
+#endif
+EXTERN const char initvsn[] E_INIT("KSH_VERSION=@(#)" KSH_VERSIONNAME_ISLEGACY \
+    " KSH " MKSH_VERSION KSH_VERSIONNAME_TEXTMODE KSH_VERSIONNAME_VENDOR_EXT);
 #define KSH_VERSION	(initvsn + /* "KSH_VERSION=@(#)" */ 16)
 
 EXTERN const char digits_uc[] E_INIT("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
@@ -578,7 +593,7 @@
 #define mkssert(e)	do { } while (/* CONSTCOND */ 0)
 #endif
 
-#if (!defined(MKSH_BUILDMAKEFILE4BSD) && !defined(MKSH_BUILDSH)) || (MKSH_BUILD_R != 541)
+#if (!defined(MKSH_BUILDMAKEFILE4BSD) && !defined(MKSH_BUILDSH)) || (MKSH_BUILD_R != 551)
 #error Must run Build.sh to compile this.
 extern void thiswillneverbedefinedIhope(void);
 int
@@ -630,14 +645,6 @@
 } while (/* CONSTCOND */ 0)
 #endif
 
-#ifdef MKSH_LEGACY_MODE
-#ifndef MKSH_NO_CMDLINE_EDITING
-#define MKSH_NO_CMDLINE_EDITING	/* defined */
-#endif
-#undef MKSH_S_NOVI
-#define MKSH_S_NOVI		1
-#endif
-
 #ifdef MKSH_SMALL
 #ifndef MKSH_NOPWNAM
 #define MKSH_NOPWNAM		/* defined */
@@ -772,6 +779,7 @@
 #define E_LOOP	5	/* executing for/while # */
 #define E_ERRH	6	/* general error handler # */
 #define E_GONE	7	/* hidden in child */
+#define E_EVAL	8	/* running eval # */
 /* # indicates env has valid jbuf (see unwind()) */
 
 /* struct env.flag values */
@@ -855,8 +863,8 @@
 
 #ifndef HAVE_STRING_POOLING /* helpers for pooled strings */
 EXTERN const char T4spaces[] E_INIT("    ");
-#define T1space (T4spaces + 3)
-EXTERN const char Tcolsp[] E_INIT(": ");
+#define T1space (Treal_sp2 + 5)
+#define Tcolsp (Tf_sD_ + 2)
 EXTERN const char TC_LEX1[] E_INIT("|&;<>() \t\n");
 #define TC_IFSWS (TC_LEX1 + 7)
 EXTERN const char TFCEDIT_dollaru[] E_INIT("${FCEDIT:-/bin/ed} $_");
@@ -865,15 +873,19 @@
 EXTERN const char Taugo[] E_INIT("augo");
 EXTERN const char Tbracket[] E_INIT("[");
 #define Tdot (Tsgdot + 2)
-EXTERN const char Talias[] E_INIT("alias");
-EXTERN const char Tbadsubst[] E_INIT("bad substitution");
+#define Talias (Tunalias + 2)
+EXTERN const char Tbadnum[] E_INIT("bad number");
+#define Tbadsubst (Tfg_badsubst + 10)
 EXTERN const char Tbg[] E_INIT("bg");
 EXTERN const char Tbad_bsize[] E_INIT("bad shf/buf/bsize");
 #define Tbsize (Tbad_bsize + 12)
 EXTERN const char Tbad_sig_ss[] E_INIT("%s: bad signal '%s'");
 #define Tbad_sig_s (Tbad_sig_ss + 4)
-EXTERN const char Tgbuiltin[] E_INIT("=builtin");
-#define Tbuiltin (Tgbuiltin + 1)
+EXTERN const char Tsgbreak[] E_INIT("*=break");
+#define Tbreak (Tsgbreak + 2)
+EXTERN const char T__builtin[] E_INIT("-\\builtin");
+#define T_builtin (T__builtin + 1)
+#define Tbuiltin (T__builtin + 2)
 EXTERN const char Toomem[] E_INIT("can't allocate %zu data bytes");
 EXTERN const char Tcant_cd[] E_INIT("restricted shell - can't cd");
 EXTERN const char Tcant_find[] E_INIT("can't find");
@@ -882,31 +894,35 @@
 EXTERN const char Tbcat[] E_INIT("!cat");
 #define Tcat (Tbcat + 1)
 #define Tcd (Tcant_cd + 25)
-EXTERN const char Tcommand[] E_INIT("command");
+#define T_command (T_funny_command + 9)
+#define Tcommand (T_funny_command + 10)
+EXTERN const char Tsgcontinue[] E_INIT("*=continue");
+#define Tcontinue (Tsgcontinue + 2)
 EXTERN const char Tcreate[] E_INIT("create");
 EXTERN const char TELIF_unexpected[] E_INIT("TELIF unexpected");
 EXTERN const char TEXECSHELL[] E_INIT("EXECSHELL");
-EXTERN const char Tsgexport[] E_INIT("*=export");
-#define Texport (Tsgexport + 2)
+EXTERN const char Tdsgexport[] E_INIT("^*=export");
+#define Texport (Tdsgexport + 3)
 #ifdef __OS2__
 EXTERN const char Textproc[] E_INIT("extproc");
 #endif
 EXTERN const char Tfalse[] E_INIT("false");
 EXTERN const char Tfg[] E_INIT("fg");
 EXTERN const char Tfg_badsubst[] E_INIT("fileglob: bad substitution");
-EXTERN const char Tfile[] E_INIT("file");
+#define Tfile (Tfile_fd + 20)
 EXTERN const char Tfile_fd[] E_INIT("function definition file");
 EXTERN const char TFPATH[] E_INIT("FPATH");
 EXTERN const char T_function[] E_INIT(" function");
 #define Tfunction (T_function + 1)
-EXTERN const char T_funny_command[] E_INIT("funny $() command");
+EXTERN const char T_funny_command[] E_INIT("funny $()-command");
 EXTERN const char Tgetopts[] E_INIT("getopts");
-EXTERN const char Thistory[] E_INIT("history");
+#define Thistory (Tnot_in_history + 7)
 EXTERN const char Tintovfl[] E_INIT("integer overflow %zu %c %zu prevented");
+EXTERN const char Tinvname[] E_INIT("%s: invalid %s name");
 EXTERN const char Tjobs[] E_INIT("jobs");
 EXTERN const char Tjob_not_started[] E_INIT("job not started");
 EXTERN const char Tmksh[] E_INIT("mksh");
-EXTERN const char Tname[] E_INIT("name");
+#define Tname (Tinvname + 15)
 EXTERN const char Tno_args[] E_INIT("missing argument");
 EXTERN const char Tno_OLDPWD[] E_INIT("no OLDPWD");
 EXTERN const char Tnot_ident[] E_INIT("is not an identifier");
@@ -917,77 +933,84 @@
 #define TOLDPWD (Tno_OLDPWD + 3)
 #define Topen (Tcant_open + 6)
 #define TPATH (TFPATH + 1)
-EXTERN const char Tpv[] E_INIT("pv");
+#define Tpv (TpVv + 1)
 EXTERN const char TpVv[] E_INIT("Vpv");
 #define TPWD (Tno_OLDPWD + 6)
-EXTERN const char Tread[] E_INIT("read");
-EXTERN const char Tsgreadonly[] E_INIT("*=readonly");
-#define Treadonly (Tsgreadonly + 2)
+#define Tread (Tshf_read + 4)
+EXTERN const char Tdsgreadonly[] E_INIT("^*=readonly");
+#define Treadonly (Tdsgreadonly + 3)
 EXTERN const char Tredirection_dup[] E_INIT("can't finish (dup) redirection");
 #define Tredirection (Tredirection_dup + 19)
-EXTERN const char Treal_sp1[] E_INIT("real ");
+#define Treal_sp1 (Treal_sp2 + 1)
 EXTERN const char Treal_sp2[] E_INIT(" real ");
 EXTERN const char Treq_arg[] E_INIT("requires an argument");
 EXTERN const char Tselect[] E_INIT("select");
 EXTERN const char Tsgset[] E_INIT("*=set");
-#define Tset (Tsgset + 2)
+#define Tset (Tf_parm + 18)
 #define Tsh (Tmksh + 2)
 #define TSHELL (TEXECSHELL + 4)
+#define Tshell (Ttoo_many_files + 23)
 EXTERN const char Tshf_read[] E_INIT("shf_read");
 EXTERN const char Tshf_write[] E_INIT("shf_write");
+EXTERN const char Tgsource[] E_INIT("=source");
+#define Tsource (Tgsource + 1)
 EXTERN const char Tj_suspend[] E_INIT("j_suspend");
 #define Tsuspend (Tj_suspend + 2)
 EXTERN const char Tsynerr[] E_INIT("syntax error");
 EXTERN const char Ttime[] E_INIT("time");
 EXTERN const char Ttoo_many_args[] E_INIT("too many arguments");
+EXTERN const char Ttoo_many_files[] E_INIT("too many open files in shell");
 EXTERN const char Ttrue[] E_INIT("true");
 EXTERN const char Ttty_fd_dupof[] E_INIT("dup of tty fd");
 #define Ttty_fd (Ttty_fd_dupof + 7)
-EXTERN const char Tgtypeset[] E_INIT("=typeset");
-#define Ttypeset (Tgtypeset + 1)
+EXTERN const char Tdgtypeset[] E_INIT("^=typeset");
+#define Ttypeset (Tdgtypeset + 2)
 #define Tugo (Taugo + 1)
 EXTERN const char Tunalias[] E_INIT("unalias");
 #define Tunexpected (TELIF_unexpected + 6)
+EXTERN const char Tunexpected_type[] E_INIT("%s: unexpected %s type %d");
 EXTERN const char Tunknown_option[] E_INIT("unknown option");
-EXTERN const char Tuser_sp1[] E_INIT("user ");
+EXTERN const char Tunwind[] E_INIT("unwind");
+#define Tuser_sp1 (Tuser_sp2 + 1)
 EXTERN const char Tuser_sp2[] E_INIT(" user ");
 #define Twrite (Tshf_write + 4)
 EXTERN const char Tf__S[] E_INIT(" %S");
-EXTERN const char Tf__d[] E_INIT(" %d");
+#define Tf__d (Tunexpected_type + 22)
 EXTERN const char Tf__ss[] E_INIT(" %s%s");
-EXTERN const char Tf__sN[] E_INIT(" %s\n");
+#define Tf__sN (Tf_s_s_sN + 5)
 EXTERN const char Tf_sSs[] E_INIT("%s/%s");
-EXTERN const char Tf_T[] E_INIT("%T");
+#define Tf_T (Tf_s_T + 3)
 EXTERN const char Tf_dN[] E_INIT("%d\n");
 EXTERN const char Tf_s_[] E_INIT("%s ");
 EXTERN const char Tf_s_T[] E_INIT("%s %T");
 EXTERN const char Tf_s_s_sN[] E_INIT("%s %s %s\n");
-EXTERN const char Tf_s_s[] E_INIT("%s %s");
-EXTERN const char Tf_s_sD_s[] E_INIT("%s %s: %s");
+#define Tf_s_s (Tf_sD_s_s + 4)
+#define Tf_s_sD_s (Tf_cant_ss_s + 6)
 EXTERN const char Tf_optfoo[] E_INIT("%s%s-%c: %s");
 EXTERN const char Tf_sD_[] E_INIT("%s: ");
 EXTERN const char Tf_szs[] E_INIT("%s: %zd %s");
 EXTERN const char Tf_parm[] E_INIT("%s: parameter not set");
 EXTERN const char Tf_coproc[] E_INIT("-p: %s");
-EXTERN const char Tf_cant[] E_INIT("can't %s %s: %s");
-EXTERN const char Tf_heredoc[] E_INIT("here document '%s' unclosed\n");
+EXTERN const char Tf_cant_s[] E_INIT("%s: can't %s");
+EXTERN const char Tf_cant_ss_s[] E_INIT("can't %s %s: %s");
+EXTERN const char Tf_heredoc[] E_INIT("here document '%s' unclosed");
 #if HAVE_MKNOD
 EXTERN const char Tf_nonnum[] E_INIT("non-numeric %s %s '%s'");
 #endif
 EXTERN const char Tf_S_[] E_INIT("%S ");
 #define Tf_S (Tf__S + 1)
-EXTERN const char Tf_lu[] E_INIT("%lu");
+#define Tf_lu (Tf_toolarge + 17)
 EXTERN const char Tf_toolarge[] E_INIT("%s %s too large: %lu");
 EXTERN const char Tf_ldfailed[] E_INIT("%s %s(%d, %ld) failed: %s");
-#define Tf_ss (Tf__ss + 1)
+#define Tf_ss (Tf_sss + 2)
 EXTERN const char Tf_sss[] E_INIT("%s%s%s");
 EXTERN const char Tf_sD_s_sD_s[] E_INIT("%s: %s %s: %s");
-EXTERN const char Tf_toomany[] E_INIT("too many %ss\n");
+EXTERN const char Tf_toomany[] E_INIT("too many %ss");
 EXTERN const char Tf_sd[] E_INIT("%s %d");
-#define Tf_s (Tf__ss + 3)
+#define Tf_s (Tf_temp + 28)
 EXTERN const char Tft_end[] E_INIT("%;");
 EXTERN const char Tft_R[] E_INIT("%R");
-#define Tf_d (Tf__d + 1)
+#define Tf_d (Tunexpected_type + 23)
 EXTERN const char Tf_sD_s_qs[] E_INIT("%s: %s '%s'");
 EXTERN const char Tf_ro[] E_INIT("read-only: %s");
 EXTERN const char Tf_flags[] E_INIT("%s: flags 0x%X");
@@ -996,8 +1019,8 @@
 EXTERN const char Tf_sD_sD_s[] E_INIT("%s: %s: %s");
 EXTERN const char Tf__c_[] E_INIT("-%c ");
 EXTERN const char Tf_sD_s_s[] E_INIT("%s: %s %s");
-#define Tf_sN (Tf__sN + 1)
-#define Tf_sD_s (Tf_s_sD_s + 3)
+#define Tf_sN (Tf_s_s_sN + 6)
+#define Tf_sD_s (Tf_temp + 24)
 EXTERN const char T_devtty[] E_INIT("/dev/tty");
 #else /* helpers for string pooling */
 #define T4spaces "    "
@@ -1012,13 +1035,17 @@
 #define Tbracket "["
 #define Tdot "."
 #define Talias "alias"
+#define Tbadnum "bad number"
 #define Tbadsubst "bad substitution"
 #define Tbg "bg"
 #define Tbad_bsize "bad shf/buf/bsize"
 #define Tbsize "bsize"
 #define Tbad_sig_ss "%s: bad signal '%s'"
 #define Tbad_sig_s "bad signal '%s'"
-#define Tgbuiltin "=builtin"
+#define Tsgbreak "*=break"
+#define Tbreak "break"
+#define T__builtin "-\\builtin"
+#define T_builtin "\\builtin"
 #define Tbuiltin "builtin"
 #define Toomem "can't allocate %zu data bytes"
 #define Tcant_cd "restricted shell - can't cd"
@@ -1028,11 +1055,14 @@
 #define Tbcat "!cat"
 #define Tcat "cat"
 #define Tcd "cd"
+#define T_command "-command"
 #define Tcommand "command"
+#define Tsgcontinue "*=continue"
+#define Tcontinue "continue"
 #define Tcreate "create"
 #define TELIF_unexpected "TELIF unexpected"
 #define TEXECSHELL "EXECSHELL"
-#define Tsgexport "*=export"
+#define Tdsgexport "^*=export"
 #define Texport "export"
 #ifdef __OS2__
 #define Textproc "extproc"
@@ -1045,10 +1075,11 @@
 #define TFPATH "FPATH"
 #define T_function " function"
 #define Tfunction "function"
-#define T_funny_command "funny $() command"
+#define T_funny_command "funny $()-command"
 #define Tgetopts "getopts"
 #define Thistory "history"
 #define Tintovfl "integer overflow %zu %c %zu prevented"
+#define Tinvname "%s: invalid %s name"
 #define Tjobs "jobs"
 #define Tjob_not_started "job not started"
 #define Tmksh "mksh"
@@ -1067,7 +1098,7 @@
 #define TpVv "Vpv"
 #define TPWD "PWD"
 #define Tread "read"
-#define Tsgreadonly "*=readonly"
+#define Tdsgreadonly "^*=readonly"
 #define Treadonly "readonly"
 #define Tredirection_dup "can't finish (dup) redirection"
 #define Tredirection "redirection"
@@ -1079,22 +1110,28 @@
 #define Tset "set"
 #define Tsh "sh"
 #define TSHELL "SHELL"
+#define Tshell "shell"
 #define Tshf_read "shf_read"
 #define Tshf_write "shf_write"
+#define Tgsource "=source"
+#define Tsource "source"
 #define Tj_suspend "j_suspend"
 #define Tsuspend "suspend"
 #define Tsynerr "syntax error"
 #define Ttime "time"
 #define Ttoo_many_args "too many arguments"
+#define Ttoo_many_files "too many open files in shell"
 #define Ttrue "true"
 #define Ttty_fd_dupof "dup of tty fd"
 #define Ttty_fd "tty fd"
-#define Tgtypeset "=typeset"
+#define Tdgtypeset "^=typeset"
 #define Ttypeset "typeset"
 #define Tugo "ugo"
 #define Tunalias "unalias"
 #define Tunexpected "unexpected"
+#define Tunexpected_type "%s: unexpected %s type %d"
 #define Tunknown_option "unknown option"
+#define Tunwind "unwind"
 #define Tuser_sp1 "user "
 #define Tuser_sp2 " user "
 #define Twrite "write"
@@ -1115,8 +1152,9 @@
 #define Tf_szs "%s: %zd %s"
 #define Tf_parm "%s: parameter not set"
 #define Tf_coproc "-p: %s"
-#define Tf_cant "can't %s %s: %s"
-#define Tf_heredoc "here document '%s' unclosed\n"
+#define Tf_cant_s "%s: can't %s"
+#define Tf_cant_ss_s "can't %s %s: %s"
+#define Tf_heredoc "here document '%s' unclosed"
 #if HAVE_MKNOD
 #define Tf_nonnum "non-numeric %s %s '%s'"
 #endif
@@ -1128,7 +1166,7 @@
 #define Tf_ss "%s%s"
 #define Tf_sss "%s%s%s"
 #define Tf_sD_s_sD_s "%s: %s %s: %s"
-#define Tf_toomany "too many %ss\n"
+#define Tf_toomany "too many %ss"
 #define Tf_sd "%s %d"
 #define Tf_s "%s"
 #define Tft_end "%;"
@@ -1247,7 +1285,7 @@
 /*
  * fast character classes
  */
-#define C_ALPHA	 BIT(0)		/* a-z_A-Z */
+#define C_ALPHX	 BIT(0)		/* A-Za-z_ */
 #define C_DIGIT	 BIT(1)		/* 0-9 */
 #define C_LEX1	 BIT(2)		/* \t \n\0|&;<>() */
 #define C_VAR1	 BIT(3)		/* *@#!$-? */
@@ -1255,17 +1293,19 @@
 #define C_SUBOP1 BIT(5)		/* "=-+?" */
 #define C_QUOTE	 BIT(6)		/* \t\n "#$&'()*;<=>?[\]`| (needing quoting) */
 #define C_IFS	 BIT(7)		/* $IFS */
-#define C_SUBOP2 BIT(8)		/* "#%" (magic, see below) */
 
 extern unsigned char chtypes[];
 
-#define ctype(c, t)	tobool( ((t) == C_SUBOP2) ?			\
-			    (((c) == '#' || (c) == '%') ? 1 : 0) :	\
-			    (chtypes[(unsigned char)(c)] & (t)) )
+#define ctype(c, t)	tobool(chtypes[(unsigned char)(c)] & (t))
 #define ord(c)		((int)(unsigned char)(c))
-#define ksh_isalphx(c)	ctype((c), C_ALPHA)
-#define ksh_isalnux(c)	ctype((c), C_ALPHA | C_DIGIT)
-#define ksh_isdigit(c)	(((c) >= '0') && ((c) <= '9'))
+#define ksh_issubop2(c)	tobool((c) == ord('#') || (c) == ord('%'))
+#define ksh_isalias(c)	(ctype((c), C_ALPHX | C_DIGIT) || (c) == ord('!') || \
+			    (c) == ord('%') || (c) == ord(',') || \
+			    (c) == ord('@') || (c) == ord('-'))
+#define ksh_isalpha(c)	(ctype((c), C_ALPHX) && (c) != ord('_'))
+#define ksh_isalphx(c)	ctype((c), C_ALPHX)
+#define ksh_isalnux(c)	ctype((c), C_ALPHX | C_DIGIT)
+#define ksh_isdigit(c)	ctype((c), C_DIGIT)
 #define ksh_islower(c)	(((c) >= 'a') && ((c) <= 'z'))
 #define ksh_isupper(c)	(((c) >= 'A') && ((c) <= 'Z'))
 #define ksh_tolower(c)	(ksh_isupper(c) ? (c) - 'A' + 'a' : (c))
@@ -1330,7 +1370,7 @@
 
 /* name of called builtin function (used by error functions) */
 EXTERN const char *builtin_argv0;
-/* is called builtin SPEC_BI? (also KEEPASN, odd use though) */
+/* is called builtin a POSIX special builtin? (error functions only) */
 EXTERN bool builtin_spec;
 
 /* current working directory */
@@ -1463,7 +1503,7 @@
 };
 
 EXTERN struct tbl *vtemp;
-/* set by global() and local() */
+/* set by isglobal(), global() and local() */
 EXTERN bool last_lookup_was_array;
 
 /* common flag bits */
@@ -1500,6 +1540,8 @@
 #define SPEC_BI		BIT(12)	/* a POSIX special builtin */
 #define LOWER_BI	BIT(13)	/* (with LOW_BI) override even w/o flags */
 #define LOW_BI		BIT(14)	/* external utility overrides built-in one */
+#define DECL_UTIL	BIT(15)	/* is declaration utility */
+#define DECL_FWDR	BIT(16) /* is declaration utility forwarder */
 
 /*
  * Attributes that can be set by the user (used to decide if an unset
@@ -1672,6 +1714,8 @@
 #define ADELIM	12	/* arbitrary delimiter: ${foo:2:3} ${foo/bar/baz} */
 #define FUNSUB	14	/* ${ foo;} substitution (NUL terminated) */
 #define VALSUB	15	/* ${|foo;} substitution (NUL terminated) */
+#define COMASUB	16	/* `…` substitution (COMSUB but expand aliases) */
+#define FUNASUB	17	/* function substitution but expand aliases */
 
 /*
  * IO redirection
@@ -2019,7 +2063,8 @@
 char *do_tilde(char *);
 /* exec.c */
 int execute(struct op * volatile, volatile int, volatile int * volatile);
-int shcomexec(const char **);
+int c_builtin(const char **);
+struct tbl *get_builtin(const char *);
 struct tbl *findfunc(const char *, uint32_t, bool);
 int define(const char *, struct op *);
 const char *builtin(const char *, int (*)(const char **));
@@ -2057,6 +2102,7 @@
 int c_whence(const char **);
 int c_command(const char **);
 int c_typeset(const char **);
+bool valid_alias_name(const char *);
 int c_alias(const char **);
 int c_unalias(const char **);
 int c_let(const char **);
@@ -2086,8 +2132,6 @@
 int timex(struct op *, int, volatile int *);
 void timex_hook(struct op *, char ** volatile *);
 int c_exec(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 **);
@@ -2170,7 +2214,7 @@
 /* main.c */
 int include(const char *, int, const char **, bool);
 int command(const char *, int);
-int shell(Source * volatile, volatile bool);
+int shell(Source * volatile, volatile int);
 /* argument MUST NOT be 0 */
 void unwind(int) MKSH_A_NORETURN;
 void newenv(int);
@@ -2262,6 +2306,14 @@
 char *strndup_i(const char *, size_t, Area *);
 #endif
 int unbksl(bool, int (*)(void), void (*)(int));
+#ifdef __OS2__
+/* os2.c */
+void os2_init(int *, const char ***);
+void setextlibpath(const char *, const char *);
+int access_ex(int (*)(const char *, int), const char *, int);
+int stat_ex(const char *, struct stat *);
+const char *real_exec_name(const char *);
+#endif
 /* shf.c */
 struct shf *shf_open(const char *, int, int, int);
 struct shf *shf_fdopen(int, int, struct shf *);
@@ -2295,9 +2347,8 @@
 ssize_t shf_vfprintf(struct shf *, const char *, va_list)
     MKSH_A_FORMAT(__printf__, 2, 0);
 /* syn.c */
-int assign_command(const char *, bool) MKSH_A_PURE;
 void initkeywords(void);
-struct op *compile(Source *, bool);
+struct op *compile(Source *, bool, bool);
 bool parse_usec(const char *, struct timeval *);
 char *yyrecursive(int);
 void yyrecursive_pop(bool);
@@ -2323,6 +2374,7 @@
 void initvar(void);
 struct block *varsearch(struct block *, struct tbl **, const char *, uint32_t);
 struct tbl *global(const char *);
+struct tbl *isglobal(const char *, bool);
 struct tbl *local(const char *, bool);
 char *str_val(struct tbl *);
 int setstr(struct tbl *, const char *, int);
@@ -2352,7 +2404,7 @@
 	/* non-operator */
 	TO_NONOP = 0,
 	/* unary operators */
-	TO_STNZE, TO_STZER, TO_OPTION,
+	TO_STNZE, TO_STZER, TO_ISSET, TO_OPTION,
 	TO_FILAXST,
 	TO_FILEXST,
 	TO_FILREG, TO_FILBDEV, TO_FILCDEV, TO_FILSYM, TO_FILFIFO, TO_FILSOCK,
@@ -2410,9 +2462,6 @@
 extern int tty_init_fd(void);	/* initialise tty_fd, tty_devtty */
 
 #ifdef __OS2__
-#ifndef __GNUC__
-# error oops?
-#endif
 #define binopen2(path,flags)		__extension__({			\
 	int binopen2_fd = open((path), (flags) | O_BINARY);		\
 	if (binopen2_fd >= 0)						\
@@ -2425,24 +2474,31 @@
 		setmode(binopen3_fd, O_BINARY);				\
 	(binopen3_fd);							\
 })
+#else
+#define binopen2(path,flags)		open((path), (flags) | O_BINARY)
+#define binopen3(path,flags,mode)	open((path), (flags) | O_BINARY, (mode))
+#endif
+
+#ifdef MKSH_DOSPATH
 #define mksh_abspath(s)			__extension__({			\
 	const char *mksh_abspath_s = (s);				\
 	(mksh_cdirsep(mksh_abspath_s[0]) ||				\
-	    (ksh_isalphx(mksh_abspath_s[0]) &&				\
+	    (ksh_isalpha(mksh_abspath_s[0]) &&				\
 	    mksh_abspath_s[1] == ':'));					\
 })
 #define mksh_cdirsep(c)			__extension__({			\
 	char mksh_cdirsep_c = (c);					\
 	(mksh_cdirsep_c == '/' || mksh_cdirsep_c == '\\');		\
 })
-/*
- * I've seen mksh_sdirsep(s) and mksh_vdirsep(s) but need to think
- * more about the OS/2 port (and, possibly, toy with it) before I
- * can merge this upstream, but good job so far @komh, thanks!
- */
+#define mksh_sdirsep(s)			__extension__({			\
+	const char *mksh_sdirsep_s = (s);				\
+	((char *)((ksh_isalphx(mksh_sdirsep_s[0]) &&			\
+	    mksh_sdirsep_s[1] == ':' &&					\
+	    !mksh_cdirsep(mksh_sdirsep_s[2])) ?				\
+	    (mksh_sdirsep_s + 1) : strpbrk(mksh_sdirsep_s, "/\\")));	\
+})
+#define mksh_vdirsep(s)			(mksh_sdirsep((s)) != NULL)
 #else
-#define binopen2(path,flags)		open((path), (flags) | O_BINARY)
-#define binopen3(path,flags,mode)	open((path), (flags) | O_BINARY, (mode))
 #define mksh_abspath(s)			((s)[0] == '/')
 #define mksh_cdirsep(c)			((c) == '/')
 #define mksh_sdirsep(s)			strchr((s), '/')
diff --git a/src/sh_flags.gen b/src/sh_flags.gen
index bcbc729..24b3359 100644
--- a/src/sh_flags.gen
+++ b/src/sh_flags.gen
@@ -1,6 +1,6 @@
 /* +++ GENERATED FILE +++ DO NOT EDIT +++ */
 /*-
- * Copyright (c) 2013, 2014, 2015
+ * Copyright (c) 2013, 2014, 2015, 2017
  *	mirabilos <m@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -21,7 +21,7 @@
 
 #ifndef SHFLAGS_OPTCS
 #if defined(SHFLAGS_DEFNS)
-__RCSID("$MirOS: src/bin/mksh/sh_flags.opt,v 1.4 2015/12/12 21:08:44 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/sh_flags.opt,v 1.5 2017/02/18 02:33:15 tg Exp $");
 #elif defined(SHFLAGS_ENUMS)
 #define FN(sname,cname,flags,ochar)	cname,
 #define F0(sname,cname,flags,ochar)	cname = 0,
@@ -36,11 +36,11 @@
 FN("bgnice", FBGNICE, OF_ANY, 0)
 #endif
 FN("braceexpand", FBRACEEXPAND, OF_ANY, 0)
-#if !defined(MKSH_NO_CMDLINE_EDITING) || defined(MKSH_LEGACY_MODE)
+#ifndef MKSH_NO_CMDLINE_EDITING
 FN("emacs", FEMACS, OF_ANY, 0)
 #endif
 FN("errexit", FERREXIT, OF_ANY, 'e')
-#if !defined(MKSH_NO_CMDLINE_EDITING) || defined(MKSH_LEGACY_MODE)
+#ifndef MKSH_NO_CMDLINE_EDITING
 FN("gmacs", FGMACS, OF_ANY, 0)
 #endif
 FN("ignoreeof", FIGNOREEOF, OF_ANY, 0)
@@ -79,16 +79,16 @@
 FN("trackall", FTRACKALL, OF_ANY, 'h')
 FN("utf8-mode", FUNICODE, OF_ANY, 'U')
 FN("verbose", FVERBOSE, OF_ANY, 'v')
-#if !defined(MKSH_NO_CMDLINE_EDITING) || defined(MKSH_LEGACY_MODE)
+#ifndef MKSH_NO_CMDLINE_EDITING
 FN("vi", FVI, OF_ANY, 0)
 #endif
-#if !defined(MKSH_NO_CMDLINE_EDITING) || defined(MKSH_LEGACY_MODE)
+#ifndef MKSH_NO_CMDLINE_EDITING
 FN("vi-esccomplete", FVIESCCOMPLETE, OF_ANY, 0)
 #endif
-#if !defined(MKSH_NO_CMDLINE_EDITING) || defined(MKSH_LEGACY_MODE)
+#ifndef MKSH_NO_CMDLINE_EDITING
 FN("vi-tabcomplete", FVITABCOMPLETE, OF_ANY, 0)
 #endif
-#if !defined(MKSH_NO_CMDLINE_EDITING) || defined(MKSH_LEGACY_MODE)
+#ifndef MKSH_NO_CMDLINE_EDITING
 FN("viraw", FVIRAW, OF_ANY, 0)
 #endif
 FN("xtrace", FXTRACE, OF_ANY, 'x')
diff --git a/src/sh_flags.opt b/src/sh_flags.opt
index 9f364d5..795d198 100644
--- a/src/sh_flags.opt
+++ b/src/sh_flags.opt
@@ -1,5 +1,5 @@
 /*-
- * Copyright (c) 2013, 2014, 2015
+ * Copyright (c) 2013, 2014, 2015, 2017
  *	mirabilos <m@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -19,7 +19,7 @@
  */
 
 @SHFLAGS_DEFNS
-__RCSID("$MirOS: src/bin/mksh/sh_flags.opt,v 1.4 2015/12/12 21:08:44 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/sh_flags.opt,v 1.5 2017/02/18 02:33:15 tg Exp $");
 @SHFLAGS_ENUMS
 #define FN(sname,cname,flags,ochar)	cname,
 #define F0(sname,cname,flags,ochar)	cname = 0,
@@ -52,7 +52,7 @@
 FN("braceexpand", FBRACEEXPAND, OF_ANY
 
 /* ./.	Emacs command line editing mode */
->|!defined(MKSH_NO_CMDLINE_EDITING) || defined(MKSH_LEGACY_MODE)
+>|!MKSH_NO_CMDLINE_EDITING
 FN("emacs", FEMACS, OF_ANY
 
 /* -e	quit on error */
@@ -60,7 +60,7 @@
 FN("errexit", FERREXIT, OF_ANY
 
 /* ./.	Emacs command line editing mode, gmacs variant */
->|!defined(MKSH_NO_CMDLINE_EDITING) || defined(MKSH_LEGACY_MODE)
+>|!MKSH_NO_CMDLINE_EDITING
 FN("gmacs", FGMACS, OF_ANY
 
 /* ./.	reading EOF does not exit */
@@ -160,19 +160,19 @@
 FN("verbose", FVERBOSE, OF_ANY
 
 /* ./.	Vi command line editing mode */
->|!defined(MKSH_NO_CMDLINE_EDITING) || defined(MKSH_LEGACY_MODE)
+>|!MKSH_NO_CMDLINE_EDITING
 FN("vi", FVI, OF_ANY
 
 /* ./.	enable ESC as file name completion character (non-standard) */
->|!defined(MKSH_NO_CMDLINE_EDITING) || defined(MKSH_LEGACY_MODE)
+>|!MKSH_NO_CMDLINE_EDITING
 FN("vi-esccomplete", FVIESCCOMPLETE, OF_ANY
 
 /* ./.	enable Tab as file name completion character (non-standard) */
->|!defined(MKSH_NO_CMDLINE_EDITING) || defined(MKSH_LEGACY_MODE)
+>|!MKSH_NO_CMDLINE_EDITING
 FN("vi-tabcomplete", FVITABCOMPLETE, OF_ANY
 
 /* ./.	always read in raw mode (no effect) */
->|!defined(MKSH_NO_CMDLINE_EDITING) || defined(MKSH_LEGACY_MODE)
+>|!MKSH_NO_CMDLINE_EDITING
 FN("viraw", FVIRAW, OF_ANY
 
 /* -x	execution trace (display commands as they are run) */
diff --git a/src/shf.c b/src/shf.c
index ace0ab6..09cc7c3 100644
--- a/src/shf.c
+++ b/src/shf.c
@@ -2,7 +2,7 @@
 
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2011,
- *		 2012, 2013, 2015, 2016
+ *		 2012, 2013, 2015, 2016, 2017
  *	mirabilos <m@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -25,7 +25,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/shf.c,v 1.76 2016/07/25 00:04:47 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/shf.c,v 1.79 2017/04/12 17:08:49 tg Exp $");
 
 /* flags to shf_emptybuf() */
 #define EB_READSW	0x01	/* about to switch to reading */
@@ -289,29 +289,31 @@
 int
 shf_flush(struct shf *shf)
 {
+	int rv = 0;
+
 	if (shf->flags & SHF_STRING)
-		return ((shf->flags & SHF_WR) ? -1 : 0);
-
-	if (shf->fd < 0)
+		rv = (shf->flags & SHF_WR) ? -1 : 0;
+	else if (shf->fd < 0)
 		internal_errorf(Tf_sD_s, "shf_flush", "no fd");
-
-	if (shf->flags & SHF_ERROR) {
+	else if (shf->flags & SHF_ERROR) {
 		errno = shf->errnosv;
-		return (-1);
-	}
-
-	if (shf->flags & SHF_READING) {
+		rv = -1;
+	} else if (shf->flags & SHF_READING) {
 		shf->flags &= ~(SHF_EOF | SHF_READING);
 		if (shf->rnleft > 0) {
-			lseek(shf->fd, (off_t)-shf->rnleft, SEEK_CUR);
+			if (lseek(shf->fd, (off_t)-shf->rnleft,
+			    SEEK_CUR) == -1) {
+				shf->flags |= SHF_ERROR;
+				shf->errnosv = errno;
+				rv = -1;
+			}
 			shf->rnleft = 0;
 			shf->rp = shf->buf;
 		}
-		return (0);
 	} else if (shf->flags & SHF_WRITING)
-		return (shf_emptybuf(shf, 0));
+		rv = shf_emptybuf(shf, 0);
 
-	return (0);
+	return (rv);
 }
 
 /*
@@ -518,7 +520,23 @@
 		shf->rnleft -= ncopy;
 		buf += ncopy;
 		bsize -= ncopy;
+#ifdef MKSH_WITH_TEXTMODE
+		if (end && buf > orig_buf + 1 && buf[-2] == '\r') {
+			buf--;
+			bsize++;
+			buf[-1] = '\n';
+		}
+#endif
 	} while (!end && bsize);
+#ifdef MKSH_WITH_TEXTMODE
+	if (!bsize && buf[-1] == '\r') {
+		int c = shf_getc(shf);
+		if (c == '\n')
+			buf[-1] = '\n';
+		else if (c != -1)
+			shf_ungetc(c, shf);
+	}
+#endif
 	*buf = '\0';
 	return (buf);
 }
diff --git a/src/syn.c b/src/syn.c
index 87b7c75..0454488 100644
--- a/src/syn.c
+++ b/src/syn.c
@@ -2,7 +2,7 @@
 
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009,
- *		 2011, 2012, 2013, 2014, 2015, 2016
+ *		 2011, 2012, 2013, 2014, 2015, 2016, 2017
  *	mirabilos <m@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -23,7 +23,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/syn.c,v 1.115 2016/09/01 12:59:12 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/syn.c,v 1.120 2017/04/06 01:59:57 tg Exp $");
 
 struct nesting_state {
 	int start_token;	/* token than began nesting (eg, FOR) */
@@ -35,25 +35,24 @@
 	struct yyrecursive_state *next;
 	struct ioword **old_herep;
 	int old_symbol;
-	int old_salias;
 	int old_nesting_type;
 	bool old_reject;
 };
 
-static void yyparse(void);
-static struct op *pipeline(int);
-static struct op *andor(void);
-static struct op *c_list(bool);
+static void yyparse(bool);
+static struct op *pipeline(int, int);
+static struct op *andor(int);
+static struct op *c_list(int, bool);
 static struct ioword *synio(int);
-static struct op *nested(int, int, int);
-static struct op *get_command(int);
-static struct op *dogroup(void);
-static struct op *thenpart(void);
-static struct op *elsepart(void);
-static struct op *caselist(void);
-static struct op *casepart(int);
-static struct op *function_body(char *, bool);
-static char **wordlist(void);
+static struct op *nested(int, int, int, int);
+static struct op *get_command(int, int);
+static struct op *dogroup(int);
+static struct op *thenpart(int);
+static struct op *elsepart(int);
+static struct op *caselist(int);
+static struct op *casepart(int, int);
+static struct op *function_body(char *, int, bool);
+static char **wordlist(int);
 static struct op *block(int, struct op *, struct op *);
 static struct op *newtp(int);
 static void syntaxerr(const char *) MKSH_A_NORETURN;
@@ -71,7 +70,6 @@
 
 static bool reject;			/* token(cf) gets symbol again */
 static int symbol;			/* yylex value */
-static int sALIAS = ALIAS;		/* 0 in yyrecursive */
 
 #define REJECT		(reject = true)
 #define ACCEPT		(reject = false)
@@ -83,13 +81,13 @@
 static const char Tesac[] = "esac";
 
 static void
-yyparse(void)
+yyparse(bool doalias)
 {
 	int c;
 
 	ACCEPT;
 
-	outtree = c_list(source->type == SSTRING);
+	outtree = c_list(doalias ? ALIAS : 0, source->type == SSTRING);
 	c = tpeek(0);
 	if (c == 0 && !outtree)
 		outtree = newtp(TEOF);
@@ -98,14 +96,14 @@
 }
 
 static struct op *
-pipeline(int cf)
+pipeline(int cf, int sALIAS)
 {
 	struct op *t, *p, *tl = NULL;
 
-	t = get_command(cf);
+	t = get_command(cf, sALIAS);
 	if (t != NULL) {
 		while (token(0) == '|') {
-			if ((p = get_command(CONTIN)) == NULL)
+			if ((p = get_command(CONTIN, sALIAS)) == NULL)
 				syntaxerr(NULL);
 			if (tl == NULL)
 				t = tl = block(TPIPE, t, p);
@@ -118,15 +116,15 @@
 }
 
 static struct op *
-andor(void)
+andor(int sALIAS)
 {
 	struct op *t, *p;
 	int c;
 
-	t = pipeline(0);
+	t = pipeline(0, sALIAS);
 	if (t != NULL) {
 		while ((c = token(0)) == LOGAND || c == LOGOR) {
-			if ((p = pipeline(CONTIN)) == NULL)
+			if ((p = pipeline(CONTIN, sALIAS)) == NULL)
 				syntaxerr(NULL);
 			t = block(c == LOGAND? TAND: TOR, t, p);
 		}
@@ -136,14 +134,14 @@
 }
 
 static struct op *
-c_list(bool multi)
+c_list(int sALIAS, bool multi)
 {
 	struct op *t = NULL, *p, *tl = NULL;
 	int c;
 	bool have_sep;
 
 	while (/* CONSTCOND */ 1) {
-		p = andor();
+		p = andor(sALIAS);
 		/*
 		 * Token has always been read/rejected at this point, so
 		 * we don't worry about what flags to pass token()
@@ -232,23 +230,27 @@
 }
 
 static struct op *
-nested(int type, int smark, int emark)
+nested(int type, int smark, int emark, int sALIAS)
 {
 	struct op *t;
 	struct nesting_state old_nesting;
 
 	nesting_push(&old_nesting, smark);
-	t = c_list(true);
+	t = c_list(sALIAS, true);
 	musthave(emark, KEYWORD|sALIAS);
 	nesting_pop(&old_nesting);
 	return (block(type, t, NULL));
 }
 
+static const char builtin_cmd[] = {
+	QCHAR, '\\', CHAR, 'b', CHAR, 'u', CHAR, 'i',
+	CHAR, 'l', CHAR, 't', CHAR, 'i', CHAR, 'n', EOS
+};
 static const char let_cmd[] = {
-	QCHAR, 'l', CHAR, 'e', CHAR, 't', CHAR, ']', EOS
+	CHAR, 'l', CHAR, 'e', CHAR, 't', EOS
 };
 static const char setA_cmd0[] = {
-	QCHAR, 's', CHAR, 'e', CHAR, 't', EOS
+	CHAR, 's', CHAR, 'e', CHAR, 't', EOS
 };
 static const char setA_cmd1[] = {
 	CHAR, '-', CHAR, 'A', EOS
@@ -258,7 +260,7 @@
 };
 
 static struct op *
-get_command(int cf)
+get_command(int cf, int sALIAS)
 {
 	struct op *t;
 	int c, iopn = 0, syniocf, lno;
@@ -289,11 +291,11 @@
 		t->lineno = source->line;
 		goto get_command_start;
 		while (/* CONSTCOND */ 1) {
-			bool check_assign_cmd;
+			bool check_decl_utility;
 
 			if (XPsize(args) == 0) {
  get_command_start:
-				check_assign_cmd = true;
+				check_decl_utility = true;
 				cf = sALIAS | CMDASN;
 			} else if (t->u.evalflags)
 				cf = CMDWORD | CMDASN;
@@ -311,16 +313,15 @@
 
 			case LWORD:
 				ACCEPT;
-				/*
-				 * the iopn == 0 and XPsize(vars) == 0 are
-				 * dubious but AT&T ksh acts this way
-				 */
-				if (iopn == 0 && XPsize(vars) == 0 &&
-				    check_assign_cmd) {
-					if (assign_command(ident, false))
+				if (check_decl_utility) {
+					struct tbl *tt = get_builtin(ident);
+					uint32_t flag;
+
+					flag = tt ? tt->flag : 0;
+					if (flag & DECL_UTIL)
 						t->u.evalflags = DOVACHECK;
-					else if (strcmp(ident, Tcommand) != 0)
-						check_assign_cmd = false;
+					if (!(flag & DECL_FWDR))
+						check_decl_utility = false;
 				}
 				if ((XPsize(args) == 0 || Flag(FKEYWORD)) &&
 				    is_wdvarassign(yylval.cp))
@@ -343,6 +344,7 @@
 					tcp[wdscan(tcp, EOS) - tcp - 3] = EOS;
 
 					/* construct new args strings */
+					XPput(args, wdcopy(builtin_cmd, ATEMP));
 					XPput(args, wdcopy(setA_cmd0, ATEMP));
 					XPput(args, wdcopy(setA_cmd1, ATEMP));
 					XPput(args, tcp);
@@ -372,7 +374,8 @@
 						syntaxerr(NULL);
 					ACCEPT;
 					musthave(/*(*/')', 0);
-					t = function_body(XPptrv(args)[0], false);
+					t = function_body(XPptrv(args)[0],
+					    sALIAS, false);
 				}
 				goto Leave;
 
@@ -388,13 +391,13 @@
  Subshell:
 		subshell_nesting_type_saved = subshell_nesting_type;
 		subshell_nesting_type = ')';
-		t = nested(TPAREN, '(', ')');
+		t = nested(TPAREN, '(', ')', sALIAS);
 		subshell_nesting_type = subshell_nesting_type_saved;
 		break;
 	    }
 
 	case '{': /*}*/
-		t = nested(TBRACE, '{', '}');
+		t = nested(TBRACE, '{', '}', sALIAS);
 		break;
 
 	case MDPAREN:
@@ -412,6 +415,7 @@
 		}
 		t = newtp(TCOM);
 		t->lineno = lno;
+		XPput(args, wdcopy(builtin_cmd, ATEMP));
 		XPput(args, wdcopy(let_cmd, ATEMP));
 		XPput(args, yylval.cp);
 		break;
@@ -439,12 +443,12 @@
 		t = newtp((c == FOR) ? TFOR : TSELECT);
 		musthave(LWORD, CMDASN);
 		if (!is_wdvarname(yylval.cp, true))
-			yyerror("%s: bad identifier\n",
+			yyerror("%s: bad identifier",
 			    c == FOR ? "for" : Tselect);
 		strdupx(t->str, ident, ATEMP);
 		nesting_push(&old_nesting, c);
-		t->vars = wordlist();
-		t->left = dogroup();
+		t->vars = wordlist(sALIAS);
+		t->left = dogroup(sALIAS);
 		nesting_pop(&old_nesting);
 		break;
 
@@ -452,8 +456,8 @@
 	case UNTIL:
 		nesting_push(&old_nesting, c);
 		t = newtp((c == WHILE) ? TWHILE : TUNTIL);
-		t->left = c_list(true);
-		t->right = dogroup();
+		t->left = c_list(sALIAS, true);
+		t->right = dogroup(sALIAS);
 		nesting_pop(&old_nesting);
 		break;
 
@@ -462,22 +466,22 @@
 		musthave(LWORD, 0);
 		t->str = yylval.cp;
 		nesting_push(&old_nesting, c);
-		t->left = caselist();
+		t->left = caselist(sALIAS);
 		nesting_pop(&old_nesting);
 		break;
 
 	case IF:
 		nesting_push(&old_nesting, c);
 		t = newtp(TIF);
-		t->left = c_list(true);
-		t->right = thenpart();
+		t->left = c_list(sALIAS, true);
+		t->right = thenpart(sALIAS);
 		musthave(FI, KEYWORD|sALIAS);
 		nesting_pop(&old_nesting);
 		break;
 
 	case BANG:
 		syniocf &= ~(KEYWORD|sALIAS);
-		t = pipeline(0);
+		t = pipeline(0, sALIAS);
 		if (t == NULL)
 			syntaxerr(NULL);
 		t = block(TBANG, NULL, t);
@@ -485,7 +489,7 @@
 
 	case TIME:
 		syniocf &= ~(KEYWORD|sALIAS);
-		t = pipeline(0);
+		t = pipeline(0, sALIAS);
 		if (t && t->type == TCOM) {
 			t->str = alloc(2, ATEMP);
 			/* TF_* flags */
@@ -497,7 +501,7 @@
 
 	case FUNCTION:
 		musthave(LWORD, 0);
-		t = function_body(yylval.cp, true);
+		t = function_body(yylval.cp, sALIAS, true);
 		break;
 	}
 
@@ -536,7 +540,7 @@
 }
 
 static struct op *
-dogroup(void)
+dogroup(int sALIAS)
 {
 	int c;
 	struct op *list;
@@ -554,40 +558,40 @@
 		c = '}';
 	else
 		syntaxerr(NULL);
-	list = c_list(true);
+	list = c_list(sALIAS, true);
 	musthave(c, KEYWORD|sALIAS);
 	return (list);
 }
 
 static struct op *
-thenpart(void)
+thenpart(int sALIAS)
 {
 	struct op *t;
 
 	musthave(THEN, KEYWORD|sALIAS);
 	t = newtp(0);
-	t->left = c_list(true);
+	t->left = c_list(sALIAS, true);
 	if (t->left == NULL)
 		syntaxerr(NULL);
-	t->right = elsepart();
+	t->right = elsepart(sALIAS);
 	return (t);
 }
 
 static struct op *
-elsepart(void)
+elsepart(int sALIAS)
 {
 	struct op *t;
 
 	switch (token(KEYWORD|sALIAS|CMDASN)) {
 	case ELSE:
-		if ((t = c_list(true)) == NULL)
+		if ((t = c_list(sALIAS, true)) == NULL)
 			syntaxerr(NULL);
 		return (t);
 
 	case ELIF:
 		t = newtp(TELIF);
-		t->left = c_list(true);
-		t->right = thenpart();
+		t->left = c_list(sALIAS, true);
+		t->right = thenpart(sALIAS);
 		return (t);
 
 	default:
@@ -597,7 +601,7 @@
 }
 
 static struct op *
-caselist(void)
+caselist(int sALIAS)
 {
 	struct op *t, *tl;
 	int c;
@@ -613,7 +617,7 @@
 	t = tl = NULL;
 	/* no ALIAS here */
 	while ((tpeek(CONTIN|KEYWORD|ESACONLY)) != c) {
-		struct op *tc = casepart(c);
+		struct op *tc = casepart(c, sALIAS);
 		if (tl == NULL)
 			t = tl = tc, tl->right = NULL;
 		else
@@ -624,7 +628,7 @@
 }
 
 static struct op *
-casepart(int endtok)
+casepart(int endtok, int sALIAS)
 {
 	struct op *t;
 	XPtrV ptns;
@@ -656,7 +660,7 @@
 	t->vars = (char **)XPclose(ptns);
 	musthave(')', 0);
 
-	t->left = c_list(true);
+	t->left = c_list(sALIAS, true);
 
 	/* initialise to default for ;; or omitted */
 	t->u.charflag = ';';
@@ -680,7 +684,7 @@
 }
 
 static struct op *
-function_body(char *name,
+function_body(char *name, int sALIAS,
     /* function foo { ... } vs foo() { .. } */
     bool ksh_func)
 {
@@ -697,7 +701,7 @@
 	 */
 	for (p = sname; *p; p++)
 		if (ctype(*p, C_QUOTE))
-			yyerror("%s: invalid function name\n", sname);
+			yyerror(Tinvname, sname, Tfunction);
 
 	/*
 	 * Note that POSIX allows only compound statements after foo(),
@@ -722,7 +726,7 @@
 	t->u.ksh_func = tobool(ksh_func);
 	t->lineno = source->line;
 
-	if ((t->left = get_command(CONTIN)) == NULL) {
+	if ((t->left = get_command(CONTIN, sALIAS)) == NULL) {
 		char *tv;
 		/*
 		 * Probably something like foo() followed by EOF or ';'.
@@ -747,7 +751,7 @@
 }
 
 static char **
-wordlist(void)
+wordlist(int sALIAS)
 {
 	int c;
 	XPtrV args;
@@ -864,7 +868,7 @@
 			goto Again;
 		}
 		/* don't quote the EOF */
-		yyerror("%s: unexpected EOF\n", Tsynerr);
+		yyerror("%s: unexpected EOF", Tsynerr);
 		/* NOTREACHED */
 
 	case LWORD:
@@ -891,7 +895,7 @@
 			s = redir;
 		}
 	}
-	yyerror("%s: '%s' %s\n", Tsynerr, s, what);
+	yyerror(Tf_sD_s_qs, Tsynerr, what, s);
 }
 
 static void
@@ -925,7 +929,7 @@
 }
 
 struct op *
-compile(Source *s, bool skiputf8bom)
+compile(Source *s, bool skiputf8bom, bool doalias)
 {
 	nesting.start_token = 0;
 	nesting.start_line = 0;
@@ -933,33 +937,10 @@
 	source = s;
 	if (skiputf8bom)
 		yyskiputf8bom();
-	yyparse();
+	yyparse(doalias);
 	return (outtree);
 }
 
-/*-
- * 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
- *	$ touch a=a; typeset a=[ab]; echo "$a"
- *	a=[ab]
- *	$ x=typeset; $x a=[ab]; echo "$a"
- *	a=a
- *	$
- */
-int
-assign_command(const char *s, bool docommand)
-{
-	if (!*s)
-		return (0);
-	return ((strcmp(s, Talias) == 0) ||
-	    (strcmp(s, Texport) == 0) ||
-	    (strcmp(s, Treadonly) == 0) ||
-	    (docommand && (strcmp(s, Tcommand) == 0)) ||
-	    (strcmp(s, Ttypeset) == 0));
-}
-
 /* Check if we are in the middle of reading an alias */
 static int
 inalias(struct source *s)
@@ -1144,7 +1125,7 @@
  * a COMSUB recursively using the main shell parser and lexer
  */
 char *
-yyrecursive(int subtype MKSH_A_UNUSED)
+yyrecursive(int subtype)
 {
 	struct op *t;
 	char *cp;
@@ -1172,12 +1153,10 @@
 	memcpy(ys->old_heres, heres, sizeof(heres));
 	ys->old_herep = herep;
 	herep = heres;
-	ys->old_salias = sALIAS;
-	sALIAS = 0;
 	ys->next = e->yyrecursive_statep;
 	e->yyrecursive_statep = ys;
 	/* we use TPAREN as a helper container here */
-	t = nested(TPAREN, stok, etok);
+	t = nested(TPAREN, stok, etok, ALIAS);
 	yyrecursive_pop(false);
 
 	/* t->left because nested(TPAREN, ...) hides our goodies there */
@@ -1197,7 +1176,6 @@
 		return;
 	e->yyrecursive_statep = ys->next;
 
-	sALIAS = ys->old_salias;
 	memcpy(heres, ys->old_heres, sizeof(heres));
 	herep = ys->old_herep;
 	reject = ys->old_reject;
diff --git a/src/tree.c b/src/tree.c
index ff15077..1fd8f2a 100644
--- a/src/tree.c
+++ b/src/tree.c
@@ -2,7 +2,7 @@
 
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
- *		 2011, 2012, 2013, 2015, 2016
+ *		 2011, 2012, 2013, 2015, 2016, 2017
  *	mirabilos <m@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -23,7 +23,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/tree.c,v 1.86 2016/07/25 00:04:48 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/tree.c,v 1.89 2017/04/12 16:46:23 tg Exp $");
 
 #define INDENT	8
 
@@ -58,7 +58,7 @@
 	case TCOM:
 		prevent_semicolon = false;
 		/* special-case 'var=<<EOF' (cf. exec.c:execute) */
-		if (
+		if (t->args &&
 		    /* we have zero arguments, i.e. no program to run */
 		    t->args[0] == NULL &&
 		    /* we have exactly one variable assignment */
@@ -86,6 +86,15 @@
 			shf_puts("#no-vars# ", shf);
 		if (t->args) {
 			w = t->args;
+			if (*w && **w == CHAR) {
+				char *cp = wdstrip(*w++, WDS_TPUTS);
+
+				if (valid_alias_name(cp))
+					shf_putc('\\', shf);
+				shf_puts(cp, shf);
+				shf_putc(' ', shf);
+				afree(cp, ATEMP);
+			}
 			while (*w)
 				fptreef(shf, indent, Tf_S_, *w++);
 		} else
@@ -352,14 +361,18 @@
 				}
 			shf_putc(c, shf);
 			break;
+		case COMASUB:
 		case COMSUB:
 			shf_puts("$(", shf);
 			cs = ")";
+			if (*wp == '(' /*)*/)
+				shf_putc(' ', shf);
  pSUB:
 			while ((c = *wp++) != 0)
 				shf_putc(c, shf);
 			shf_puts(cs, shf);
 			break;
+		case FUNASUB:
 		case FUNSUB:
 			c = ' ';
 			if (0)
@@ -409,8 +422,9 @@
 		case SPAT:
 			c = '|';
 			if (0)
+				/* FALLTHROUGH */
 		case CPAT:
-				c = /*(*/ ')';
+			  c = /*(*/ ')';
 			shf_putc(c, shf);
 			break;
 		}
@@ -606,7 +620,9 @@
 		case QCHAR:
 			wp++;
 			break;
+		case COMASUB:
 		case COMSUB:
+		case FUNASUB:
 		case FUNSUB:
 		case VALSUB:
 		case EXPRSUB:
@@ -832,8 +848,9 @@
 			}
 			shf_puts("ADELIM=", shf);
 			if (0)
+				/* FALLTHROUGH */
 		case CHAR:
-				shf_puts("CHAR=", shf);
+			  shf_puts("CHAR=", shf);
 			dumpchar(shf, *wp++);
 			break;
 		case QCHAR:
@@ -844,6 +861,9 @@
 				shf_putc('\\', shf);
 			dumpchar(shf, c);
 			goto closeandout;
+		case COMASUB:
+			shf_puts("COMASUB<", shf);
+			goto dumpsub;
 		case COMSUB:
 			shf_puts("COMSUB<", shf);
  dumpsub:
@@ -852,6 +872,9 @@
  closeandout:
 			shf_putc('>', shf);
 			break;
+		case FUNASUB:
+			shf_puts("FUNASUB<", shf);
+			goto dumpsub;
 		case FUNSUB:
 			shf_puts("FUNSUB<", shf);
 			goto dumpsub;
diff --git a/src/var.c b/src/var.c
index 64de0f9..b83977f 100644
--- a/src/var.c
+++ b/src/var.c
@@ -2,7 +2,7 @@
 
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
- *		 2011, 2012, 2013, 2014, 2015, 2016
+ *		 2011, 2012, 2013, 2014, 2015, 2016, 2017
  *	mirabilos <m@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -28,7 +28,7 @@
 #include <sys/sysctl.h>
 #endif
 
-__RCSID("$MirOS: src/bin/mksh/var.c,v 1.209 2016/11/11 23:31:39 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/var.c,v 1.214 2017/04/02 16:47:43 tg Exp $");
 
 /*-
  * Variables
@@ -45,6 +45,9 @@
 /* may only be set by typeset() just before call to array_index_calc() */
 static enum namerefflag innermost_refflag = SRF_NOP;
 
+static void c_typeset_vardump(struct tbl *, uint32_t, int, int, bool, bool);
+static void c_typeset_vardump_recursive(struct block *, uint32_t, int, bool,
+    bool);
 static char *formatstr(struct tbl *, const char *);
 static void exportprep(struct tbl *, const char *);
 static int special(const char *);
@@ -225,6 +228,13 @@
 struct tbl *
 global(const char *n)
 {
+	return (isglobal(n, true));
+}
+
+/* search for variable; if not found, return NULL or create globally */
+struct tbl *
+isglobal(const char *n, bool docreate)
+{
 	struct tbl *vp;
 	union mksh_cchack vname;
 	struct block *l = e->loc;
@@ -291,17 +301,19 @@
 		goto out;
 	}
 	l = varsearch(e->loc, &vp, vn, h);
+	if (vp == NULL && docreate)
+		vp = ktenter(&l->vars, vn, h);
+	else
+		docreate = false;
 	if (vp != NULL) {
 		if (array)
 			vp = arraysearch(vp, val);
-		goto out;
+		if (docreate) {
+			vp->flag |= DEFINED;
+			if (special(vn))
+				vp->flag |= SPECIAL;
+		}
 	}
-	vp = ktenter(&l->vars, vn, h);
-	if (array)
-		vp = arraysearch(vp, val);
-	vp->flag |= DEFINED;
-	if (special(vn))
-		vp->flag |= SPECIAL;
  out:
 	last_lookup_was_array = array;
 	if (vn != n)
@@ -1053,8 +1065,9 @@
 	size_t alen;
 
 	if (s && ksh_isalphx(*s)) {
-		while (*++s && ksh_isalnux(*s))
-			;
+		do {
+			++s;
+		} while (ksh_isalnux(*s));
 		if (aok && *s == '[' && (alen = array_ref_len(s)))
 			s += alen;
 	}
@@ -1346,7 +1359,7 @@
 		if (getint(vp, &num, false) == -1) {
 			s = str_val(vp);
 			if (st != V_RANDOM)
-				errorf(Tf_sD_sD_s, vp->name, "bad number", s);
+				errorf(Tf_sD_sD_s, vp->name, Tbadnum, s);
 			num.u = hash(s);
 		}
 		vp->flag |= SPECIAL;
@@ -1770,3 +1783,381 @@
 	vp->flag = DEFINED | RDONLY;
 	setstr(vp, istr, 0x4);
 }
+
+/* typeset, global(deprecated), export, and readonly */
+int
+c_typeset(const char **wp)
+{
+	struct tbl *vp, **p;
+	uint32_t fset = 0, fclr = 0, flag;
+	int thing = 0, field = 0, base = 0, i;
+	struct block *l;
+	const char *opts;
+	const char *fieldstr = NULL, *basestr = NULL;
+	bool localv = false, func = false, pflag = false, istset = true;
+	enum namerefflag new_refflag = SRF_NOP;
+
+	switch (**wp) {
+
+	/* export */
+	case 'e':
+		fset |= EXPORT;
+		istset = false;
+		break;
+
+	/* readonly */
+	case 'r':
+		fset |= RDONLY;
+		istset = false;
+		break;
+
+	/* set */
+	case 's':
+		/* called with 'typeset -' */
+		break;
+
+	/* typeset */
+	case 't':
+		localv = true;
+		break;
+	}
+
+	/* see comment below regarding possible opions */
+	opts = istset ? "L#R#UZ#afgi#lnprtux" : "p";
+
+	builtin_opt.flags |= GF_PLUSOPT;
+	/*
+	 * 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
+	 * does not allow the number to be specified as a separate argument
+	 * Here, the number must follow the RLZi option, but is optional
+	 * (see the # kludge in ksh_getopt()).
+	 */
+	while ((i = ksh_getopt(wp, &builtin_opt, opts)) != -1) {
+		flag = 0;
+		switch (i) {
+		case 'L':
+			flag = LJUST;
+			fieldstr = builtin_opt.optarg;
+			break;
+		case 'R':
+			flag = RJUST;
+			fieldstr = builtin_opt.optarg;
+			break;
+		case 'U':
+			/*
+			 * 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
+			 */
+			flag = INT_U;
+			break;
+		case 'Z':
+			flag = ZEROFIL;
+			fieldstr = builtin_opt.optarg;
+			break;
+		case 'a':
+			/*
+			 * this is supposed to set (-a) or unset (+a) the
+			 * indexed array attribute; it does nothing on an
+			 * existing regular string or indexed array though
+			 */
+			break;
+		case 'f':
+			func = true;
+			break;
+		case 'g':
+			localv = (builtin_opt.info & GI_PLUS) ? true : false;
+			break;
+		case 'i':
+			flag = INTEGER;
+			basestr = builtin_opt.optarg;
+			break;
+		case 'l':
+			flag = LCASEV;
+			break;
+		case 'n':
+			new_refflag = (builtin_opt.info & GI_PLUS) ?
+			    SRF_DISABLE : SRF_ENABLE;
+			break;
+		/* export, readonly: POSIX -p flag */
+		case 'p':
+			/* typeset: show values as well */
+			pflag = true;
+			if (istset)
+				continue;
+			break;
+		case 'r':
+			flag = RDONLY;
+			break;
+		case 't':
+			flag = TRACE;
+			break;
+		case 'u':
+			/* upper case / autoload */
+			flag = UCASEV_AL;
+			break;
+		case 'x':
+			flag = EXPORT;
+			break;
+		case '?':
+			return (1);
+		}
+		if (builtin_opt.info & GI_PLUS) {
+			fclr |= flag;
+			fset &= ~flag;
+			thing = '+';
+		} else {
+			fset |= flag;
+			fclr &= ~flag;
+			thing = '-';
+		}
+	}
+
+	if (fieldstr && !getn(fieldstr, &field)) {
+		bi_errorf(Tf_sD_s, Tbadnum, fieldstr);
+		return (1);
+	}
+	if (basestr) {
+		if (!getn(basestr, &base)) {
+			bi_errorf(Tf_sD_s, "bad integer base", basestr);
+			return (1);
+		}
+		if (base < 1 || base > 36)
+			base = 10;
+	}
+
+	if (!(builtin_opt.info & GI_MINUSMINUS) && wp[builtin_opt.optind] &&
+	    (wp[builtin_opt.optind][0] == '-' ||
+	    wp[builtin_opt.optind][0] == '+') &&
+	    wp[builtin_opt.optind][1] == '\0') {
+		thing = wp[builtin_opt.optind][0];
+		builtin_opt.optind++;
+	}
+
+	if (func && (((fset|fclr) & ~(TRACE|UCASEV_AL|EXPORT)) ||
+	    new_refflag != SRF_NOP)) {
+		bi_errorf("only -t, -u and -x options may be used with -f");
+		return (1);
+	}
+	if (wp[builtin_opt.optind]) {
+		/*
+		 * 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 */
+			fset &= ~UCASEV_AL;
+		if (fset & LJUST)
+			/* LJUST has priority over RJUST */
+			fset &= ~RJUST;
+		if ((fset & (ZEROFIL|LJUST)) == ZEROFIL) {
+			/* -Z implies -ZR */
+			fset |= RJUST;
+			fclr &= ~RJUST;
+		}
+		/*
+		 * 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)) || new_refflag != SRF_NOP)
+			fclr |= ~fset & (LJUST | RJUST | ZEROFIL | UCASEV_AL |
+			    LCASEV | INTEGER | INT_U | INT_L);
+	}
+	if (new_refflag != SRF_NOP) {
+		fclr &= ~(ARRAY | ASSOC);
+		fset &= ~(ARRAY | ASSOC);
+		fclr |= EXPORT;
+		fset |= ASSOC;
+		if (new_refflag == SRF_DISABLE)
+			fclr |= ASSOC;
+	}
+
+	/* set variables and attributes */
+	if (wp[builtin_opt.optind] &&
+	    /* not "typeset -p varname" */
+	    !(!func && pflag && !(fset | fclr))) {
+		int rv = 0;
+		struct tbl *f;
+
+		if (localv && !func)
+			fset |= LOCAL;
+		for (i = builtin_opt.optind; wp[i]; i++) {
+			if (func) {
+				f = findfunc(wp[i], hash(wp[i]),
+				    tobool(fset & UCASEV_AL));
+				if (!f) {
+					/* AT&T ksh does ++rv: bogus */
+					rv = 1;
+					continue;
+				}
+				if (fset | fclr) {
+					f->flag |= fset;
+					f->flag &= ~fclr;
+				} 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(Tf_sD_s, wp[i], Tnot_ident);
+				return (1);
+			}
+		}
+		return (rv);
+	}
+
+	/* list variables and attributes */
+
+	/* 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 == '-')
+					fpFUNCTf(shl_stdout, 0,
+					    tobool(vp->flag & FKSH),
+					    vp->name, vp->val.t);
+				else
+					shf_puts(vp->name, shl_stdout);
+				shf_putc('\n', shl_stdout);
+			}
+		}
+	} else if (wp[builtin_opt.optind]) {
+		for (i = builtin_opt.optind; wp[i]; i++) {
+			vp = isglobal(wp[i], false);
+			c_typeset_vardump(vp, flag, thing,
+			    last_lookup_was_array ? 4 : 0, pflag, istset);
+		}
+	} else
+		c_typeset_vardump_recursive(e->loc, flag, thing, pflag, istset);
+	return (0);
+}
+
+static void
+c_typeset_vardump_recursive(struct block *l, uint32_t flag, int thing,
+    bool pflag, bool istset)
+{
+	struct tbl **blockvars, *vp;
+
+	if (l->next)
+		c_typeset_vardump_recursive(l->next, flag, thing, pflag, istset);
+	blockvars = ktsort(&l->vars);
+	while ((vp = *blockvars++))
+		c_typeset_vardump(vp, flag, thing, 0, pflag, istset);
+	/*XXX doesn’t this leak? */
+}
+
+static void
+c_typeset_vardump(struct tbl *vp, uint32_t flag, int thing, int any_set,
+    bool pflag, bool istset)
+{
+	struct tbl *tvp;
+	char *s;
+
+	if (!vp)
+		return;
+
+	/*
+	 * See if the parameter is set (for arrays, if any
+	 * element is set).
+	 */
+	for (tvp = vp; tvp; tvp = tvp->u.array)
+		if (tvp->flag & ISSET) {
+			any_set |= 1;
+			break;
+		}
+
+	/*
+	 * Check attributes - note that all array elements
+	 * have (should have?) the same attributes, so checking
+	 * the first is sufficient.
+	 *
+	 * Report an unset param only if the user has
+	 * explicitly given it some attribute (like export);
+	 * otherwise, after "echo $FOO", we would report FOO...
+	 */
+	if (!any_set && !(vp->flag & USERATTRIB))
+		return;
+	if (flag && (vp->flag & flag) == 0)
+		return;
+	if (!(vp->flag & ARRAY))
+		/* optimise later conditionals */
+		any_set = 0;
+	do {
+		/*
+		 * Ignore array elements that aren't set unless there
+		 * are no set elements, in which case the first is
+		 * reported on
+		 */
+		if (any_set && !(vp->flag & ISSET))
+			continue;
+		/* no arguments */
+		if (!thing && !flag) {
+			if (any_set == 1) {
+				shprintf(Tf_s_s_sN, Tset, "-A", vp->name);
+				any_set = 2;
+			}
+			/*
+			 * AT&T ksh prints things like export, integer,
+			 * leftadj, zerofill, etc., but POSIX says must
+			 * be suitable for re-entry...
+			 */
+			shprintf(Tf_s_s, Ttypeset, "");
+			if (((vp->flag & (ARRAY | ASSOC)) == ASSOC))
+				shprintf(Tf__c_, 'n');
+			if ((vp->flag & INTEGER))
+				shprintf(Tf__c_, 'i');
+			if ((vp->flag & EXPORT))
+				shprintf(Tf__c_, 'x');
+			if ((vp->flag & RDONLY))
+				shprintf(Tf__c_, 'r');
+			if ((vp->flag & TRACE))
+				shprintf(Tf__c_, '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))
+				shprintf(Tf__c_, 'Z');
+			if ((vp->flag & LCASEV))
+				shprintf(Tf__c_, 'l');
+			if ((vp->flag & UCASEV_AL))
+				shprintf(Tf__c_, 'u');
+			if ((vp->flag & INT_U))
+				shprintf(Tf__c_, 'U');
+		} else if (pflag) {
+			shprintf(Tf_s_s, istset ? Ttypeset :
+			    (flag & EXPORT) ? Texport : Treadonly, "");
+		}
+		if (any_set)
+			shprintf("%s[%lu]", vp->name, arrayindex(vp));
+		else
+			shf_puts(vp->name, shl_stdout);
+		if ((!thing && !flag && pflag) ||
+		    (thing == '-' && (vp->flag & ISSET))) {
+			s = str_val(vp);
+			shf_putc('=', shl_stdout);
+			/* AT&T ksh can't have justified integers... */
+			if ((vp->flag & (INTEGER | LJUST | RJUST)) == INTEGER)
+				shf_puts(s, shl_stdout);
+			else
+				print_value_quoted(shl_stdout, s);
+		}
+		shf_putc('\n', shl_stdout);
+
+		/*
+		 * Only report first 'element' of an array with
+		 * no set elements.
+		 */
+		if (!any_set)
+			return;
+	} while (!(any_set & 4) && (vp = vp->u.array));
+}