OSDN Git Service

Updated mksh to ToT as of 12 October 2011.
authorGeremy Condra <gcondra@google.com>
Thu, 13 Oct 2011 01:17:24 +0000 (18:17 -0700)
committerGeremy Condra <gcondra@google.com>
Thu, 13 Oct 2011 01:17:24 +0000 (18:17 -0700)
This includes several security fixes and brings us in
line with upstream, who has included fixes for a
number of issues originally reported by the Android
team.

Change-Id: I1e0f3adf292b86fa7679b3364a774e5b6004beb8

25 files changed:
src/Build.sh
src/Makefile [new file with mode: 0644]
src/check.pl
src/check.t
src/dot.mkshrc [new file with mode: 0644]
src/edit.c
src/eval.c
src/exec.c
src/expr.c
src/funcs.c
src/histrap.c
src/jobs.c
src/lalloc.c
src/lex.c
src/main.c
src/misc.c
src/mksh.1 [new file with mode: 0644]
src/sh.h
src/sh_flags.h
src/shf.c
src/strlcpy.c [new file with mode: 0644]
src/syn.c
src/tree.c
src/var.c
src/var_spec.h

index c98b1ca..6561cf6 100644 (file)
@@ -1,7 +1,7 @@
 #!/bin/sh
-srcversion='$MirOS: src/bin/mksh/Build.sh,v 1.459 2010/08/24 15:46:06 tg Exp $'
+srcversion='$MirOS: src/bin/mksh/Build.sh,v 1.488 2011/10/07 19:51:41 tg Exp $'
 #-
-# Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+# Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
 #      Thorsten Glaser <tg@mirbsd.org>
 #
 # Provided that these terms and disclaimer and all copyright notices
@@ -30,6 +30,8 @@ srcversion='$MirOS: src/bin/mksh/Build.sh,v 1.459 2010/08/24 15:46:06 tg Exp $'
 #                      MKSH_NOPWNAM MKSH_NO_LIMITS MKSH_SMALL MKSH_S_NOVI
 #                      MKSH_UNEMPLOYED MKSH_DEFAULT_EXECSHELL MKSHRC_PATH
 #                      MKSH_DEFAULT_TMPDIR MKSH_CLRTOEOL_STRING MKSH_A4PB
+#                      MKSH_NO_DEPRECATED_WARNING MKSH_DONT_EMIT_IDSTRING
+#                      MKSH_NOPROSPECTOFWORK MKSH_NO_EXTERNAL_CAT
 
 LC_ALL=C
 export LC_ALL
@@ -204,11 +206,15 @@ EOF
        test x"$fv" = x"1"
 }
 
+add_cppflags() {
+       CPPFLAGS="$CPPFLAGS $*"
+}
+
 ac_cppflags() {
        test x"$1" = x"" || fu=$1
        fv=$2
        test x"$2" = x"" && eval fv=\$HAVE_$fu
-       CPPFLAGS="$CPPFLAGS -DHAVE_$fu=$fv"
+       add_cppflags -DHAVE_$fu=$fv
 }
 
 ac_test() {
@@ -216,7 +222,7 @@ ac_test() {
        ac_cppflags
 }
 
-# ac_flags [-] add varname flags [text]
+# ac_flags [-] add varname cflags [text] [ldflags]
 ac_flags() {
        if test x"$1" = x"-"; then
                shift
@@ -228,9 +234,14 @@ ac_flags() {
        vn=$2
        f=$3
        ft=$4
+       fl=$5
        test x"$ft" = x"" && ft="if $f can be used"
        save_CFLAGS=$CFLAGS
        CFLAGS="$CFLAGS $f"
+       if test -n "$fl"; then
+               save_LDFLAGS=$LDFLAGS
+               LDFLAGS="$LDFLAGS $fl"
+       fi
        if test 1 = $hf; then
                ac_testn can_$vn '' "$ft"
        else
@@ -240,6 +251,9 @@ ac_flags() {
                EOF
        fi
        eval fv=\$HAVE_CAN_`upper $vn`
+       if test -n "$fl"; then
+               test 11 = $fa$fv || LDFLAGS=$save_LDFLAGS
+       fi
        test 11 = $fa$fv || CFLAGS=$save_CFLAGS
 }
 
@@ -287,7 +301,7 @@ rmf a.exe* a.out* conftest.c *core lft mksh* no *.bc *.ll *.o \
     Rebuild.sh signames.inc test.sh x vv.out
 
 curdir=`pwd` srcdir=`dirname "$0"` check_categories=
-test -n "$dirname" || dirname=.
+test -n "$srcdir" || srcdir=.
 dstversion=`sed -n '/define MKSH_VERSION/s/^.*"\(.*\)".*$/\1/p' $srcdir/sh.h`
 
 e=echo
@@ -301,7 +315,7 @@ last=
 for i
 do
        case $last:$i in
-       c:combine|c:dragonegg|c:llvm)
+       c:combine|c:dragonegg|c:llvm|c:lto)
                cm=$i
                last=
                ;;
@@ -316,28 +330,14 @@ do
        :-c)
                last=c
                ;;
-       :-combine)
-               cm=combine
-               echo "$me: Warning: '$i' is deprecated, use '-c combine' instead!" >&2
-               ;;
        :-g)
                # checker, debug, valgrind build
-               CPPFLAGS="$CPPFLAGS -DDEBUG"
+               add_cppflags -DDEBUG
                CFLAGS="$CFLAGS -g3 -fno-builtin"
                ;;
        :-j)
                pm=1
                ;;
-       :-llvm)
-               cm=llvm
-               optflags=-std-compile-opts
-               echo "$me: Warning: '$i' is deprecated, use '-c llvm -O' instead!" >&2
-               ;;
-       :-llvm=*)
-               cm=llvm
-               optflags=`echo "x$i" | sed 's/^x-llvm=//'`
-               echo "$me: Warning: '$i' is deprecated, use '-c llvm -o $llvm' instead!" >&2
-               ;;
        :-M)
                cm=makefile
                ;;
@@ -383,12 +383,22 @@ else
 fi
 
 test x"$TARGET_OS" = x"" && TARGET_OS=`uname -s 2>/dev/null || uname`
+if test x"$TARGET_OS" = x""; then
+       echo "$me: Set TARGET_OS, your uname is broken!" >&2
+       exit 1
+fi
 oswarn=
 ccpc=-Wc,
 ccpl=-Wl,
 tsts=
 ccpr='|| for _f in ${tcfn}*; do test x"${_f}" = x"mksh.1" || rm -f "${_f}"; done'
 
+# Evil hack
+if test x"$TARGET_OS" = x"Android"; then
+       check_categories="$check_categories android"
+       TARGET_OS=Linux
+fi
+
 # Configuration depending on OS revision, on OSes that need them
 case $TARGET_OS in
 QNX)
@@ -399,7 +409,7 @@ esac
 # Configuration depending on OS name
 case $TARGET_OS in
 AIX)
-       CPPFLAGS="$CPPFLAGS -D_ALL_SOURCE"
+       add_cppflags -D_ALL_SOURCE
        : ${HAVE_SETLOCALE_CTYPE=0}
        ;;
 BeOS)
@@ -417,22 +427,34 @@ DragonFly)
        ;;
 FreeBSD)
        ;;
+FreeMiNT)
+       oswarn="; it has minor issues"
+       add_cppflags -D_GNU_SOURCE
+       : ${HAVE_SETLOCALE_CTYPE=0}
+       ;;
 GNU)
+       case $CC in
+       *tendracc*) ;;
+       *) add_cppflags -D_GNU_SOURCE ;;
+       esac
        # define NO_PATH_MAX to use Hurd-only functions
-       CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE -DNO_PATH_MAX"
+       add_cppflags -DNO_PATH_MAX
        ;;
 GNU/kFreeBSD)
-       CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE"
+       case $CC in
+       *tendracc*) ;;
+       *) add_cppflags -D_GNU_SOURCE ;;
+       esac
        ;;
 Haiku)
-       CPPFLAGS="$CPPFLAGS -DMKSH_ASSUME_UTF8"
+       add_cppflags -DMKSH_ASSUME_UTF8
        ;;
 HP-UX)
        ;;
 Interix)
        ccpc='-X '
        ccpl='-Y '
-       CPPFLAGS="$CPPFLAGS -D_ALL_SOURCE"
+       add_cppflags -D_ALL_SOURCE
        : ${LIBS='-lcrypt'}
        : ${HAVE_SETLOCALE_CTYPE=0}
        ;;
@@ -440,19 +462,29 @@ IRIX*)
        : ${HAVE_SETLOCALE_CTYPE=0}
        ;;
 Linux)
-       CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE"
+       case $CC in
+       *tendracc*) ;;
+       *) add_cppflags -D_GNU_SOURCE ;;
+       esac
+       add_cppflags -DSETUID_CAN_FAIL_WITH_EAGAIN
        : ${HAVE_REVOKE=0}
        ;;
 MidnightBSD)
        ;;
 Minix)
-       CPPFLAGS="$CPPFLAGS -DMKSH_UNEMPLOYED -DMKSH_CONSERVATIVE_FDS"
-       CPPFLAGS="$CPPFLAGS -D_POSIX_SOURCE -D_POSIX_1_SOURCE=2 -D_MINIX"
+       add_cppflags -DMKSH_UNEMPLOYED
+       add_cppflags -DMKSH_CONSERVATIVE_FDS
+       add_cppflags -DMKSH_NO_LIMITS
+       add_cppflags -D_POSIX_SOURCE -D_POSIX_1_SOURCE=2 -D_MINIX
        oldish_ed=no-stderr-ed          # /usr/bin/ed(!) is broken
        : ${HAVE_SETLOCALE_CTYPE=0}
        ;;
 MirBSD)
        ;;
+MSYS_*)
+       # probably same as CYGWIN* – need to test; from RT|Chatzilla
+       oswarn='but will probably work'
+       ;;
 NetBSD)
        ;;
 OpenBSD)
@@ -460,15 +492,20 @@ OpenBSD)
        ;;
 OSF1)
        HAVE_SIG_T=0    # incompatible
-       CPPFLAGS="$CPPFLAGS -D_OSF_SOURCE -D_POSIX_C_SOURCE=200112L"
-       CPPFLAGS="$CPPFLAGS -D_XOPEN_SOURCE=600 -D_XOPEN_SOURCE_EXTENDED"
+       add_cppflags -D_OSF_SOURCE
+       add_cppflags -D_POSIX_C_SOURCE=200112L
+       add_cppflags -D_XOPEN_SOURCE=600
+       add_cppflags -D_XOPEN_SOURCE_EXTENDED
        : ${HAVE_SETLOCALE_CTYPE=0}
        ;;
 Plan9)
-       CPPFLAGS="$CPPFLAGS -D_POSIX_SOURCE -D_LIMITS_EXTENSION"
-       CPPFLAGS="$CPPFLAGS -D_BSD_EXTENSION -D_SUSV2_SOURCE"
+       add_cppflags -D_POSIX_SOURCE
+       add_cppflags -D_LIMITS_EXTENSION
+       add_cppflags -D_BSD_EXTENSION
+       add_cppflags -D_SUSV2_SOURCE
+       add_cppflags -DMKSH_ASSUME_UTF8
        oswarn=' and will currently not work'
-       CPPFLAGS="$CPPFLAGS -DMKSH_ASSUME_UTF8 -DMKSH_UNEMPLOYED"
+       add_cppflags -DMKSH_UNEMPLOYED
        ;;
 PW32*)
        HAVE_SIG_T=0    # incompatible
@@ -476,7 +513,7 @@ PW32*)
        : ${HAVE_SETLOCALE_CTYPE=0}
        ;;
 QNX)
-       CPPFLAGS="$CPPFLAGS -D__NO_EXT_QNX"
+       add_cppflags -D__NO_EXT_QNX
        case $TARGET_OSREV in
        [012345].*|6.[0123].*|6.4.[01])
                oldish_ed=no-stderr-ed          # oldish /bin/ed is broken
@@ -485,15 +522,16 @@ QNX)
        : ${HAVE_SETLOCALE_CTYPE=0}
        ;;
 SunOS)
-       CPPFLAGS="$CPPFLAGS -D_BSD_SOURCE -D__EXTENSIONS__"
+       add_cppflags -D_BSD_SOURCE
+       add_cppflags -D__EXTENSIONS__
        ;;
 syllable)
-       CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE"
+       add_cppflags -D_GNU_SOURCE
        oswarn=' and will currently not work'
        ;;
 ULTRIX)
        : ${CC=cc -YPOSIX}
-       CPPFLAGS="$CPPFLAGS -Dssize_t=int"
+       add_cppflags -Dssize_t=int
        : ${HAVE_SETLOCALE_CTYPE=0}
        ;;
 UWIN*)
@@ -509,6 +547,8 @@ UWIN*)
        ;;
 esac
 
+: ${HAVE_MKNOD=0}
+
 : ${CC=cc} ${NROFF=nroff}
 test 0 = $r && echo | $NROFF -v 2>&1 | grep GNU >/dev/null 2>&1 && \
     NROFF="$NROFF -c"
@@ -516,6 +556,10 @@ test 0 = $r && echo | $NROFF -v 2>&1 | grep GNU >/dev/null 2>&1 && \
 # this aids me in tracing FTBFSen without access to the buildd
 $e "Hi from$ao $bi$srcversion$ao on:"
 case $TARGET_OS in
+AIX)
+       vv '|' "oslevel >&2"
+       vv '|' "uname -a >&2"
+       ;;
 Darwin)
        vv '|' "hwprefs machine_type os_type os_class >&2"
        vv '|' "uname -a >&2"
@@ -735,6 +779,7 @@ watcom)
     own risk, please report success/failure to the developers.'
        ;;
 xlc)
+       vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -qversion"
        vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -qversion=verbose"
        vv '|' "ld -V"
        ;;
@@ -866,9 +911,46 @@ if test $ct = gcc; then
        ac_flags 1 fnostrictaliasing -fno-strict-aliasing
        ac_flags 1 fstackprotectorall -fstack-protector-all
        ac_flags 1 fwrapv -fwrapv
-       test $cm = combine && ac_flags 0 combine \
-           '-fwhole-program --combine' \
-           'if gcc supports -fwhole-program --combine'
+       test $cm = dragonegg && case " $CC $CFLAGS $LDFLAGS " in
+       *\ -fplugin=*dragonegg*) ;;
+       *) ac_flags 1 fplugin_dragonegg -fplugin=dragonegg ;;
+       esac
+       if test $cm = lto; then
+               fv=0
+               checks='1 2 3 4 5 6 7 8'
+       elif test $cm = combine; then
+               fv=0
+               checks='7 8'
+       else
+               fv=1
+       fi
+       test $fv = 1 || for what in $checks; do
+               test $fv = 1 && break
+               case $what in
+               1)      t_cflags='-flto=jobserver'
+                       t_ldflags='-fuse-linker-plugin'
+                       t_use=1 t_name=fltojs_lp ;;
+               2)      t_cflags='-flto=jobserver' t_ldflags=''
+                       t_use=1 t_name=fltojs_nn ;;
+               3)      t_cflags='-flto=jobserver'
+                       t_ldflags='-fno-use-linker-plugin -fwhole-program'
+                       t_use=1 t_name=fltojs_np ;;
+               4)      t_cflags='-flto'
+                       t_ldflags='-fuse-linker-plugin'
+                       t_use=1 t_name=fltons_lp ;;
+               5)      t_cflags='-flto' t_ldflags=''
+                       t_use=1 t_name=fltons_nn ;;
+               6)      t_cflags='-flto'
+                       t_ldflags='-fno-use-linker-plugin -fwhole-program'
+                       t_use=1 t_name=fltons_np ;;
+               7)      t_cflags='-fwhole-program --combine' t_ldflags=''
+                       t_use=0 t_name=combine cm=combine ;;
+               8)      fv=1 cm=normal ;;
+               esac
+               test $fv = 1 && break
+               ac_flags $t_use $t_name "$t_cflags" \
+                   "if gcc supports $t_cflags $t_ldflags" "$t_ldflags"
+       done
        i=1
 elif test $ct = icc; then
        ac_flags 1 fnobuiltinsetmode -fno-builtin-setmode
@@ -933,8 +1015,8 @@ if test 1 = $i; then
            ac_flags 1 stdc99 -std=c99 'for support of ISO C99'
        ac_flags 1 wall -Wall
 fi
-phase=x
 
+phase=x
 # The following tests run with -Werror or similar (all compilers) if possible
 NOWARN=$DOWARN
 test $ct = pcc && phase=u
@@ -942,76 +1024,77 @@ test $ct = pcc && phase=u
 #
 # Compiler: check for stuff that only generates warnings
 #
-ac_test attribute_bounded '' 'for __attribute__((bounded))' <<-'EOF'
-       #if defined(__GNUC__) && (__GNUC__ < 2)
-       /* force a failure: gcc 1.42 has a false positive here */
+ac_test attribute_bounded '' 'for __attribute__((__bounded__))' <<-'EOF'
+       #if defined(__TenDRA__) || (defined(__GNUC__) && (__GNUC__ < 2))
+       /* force a failure: TenDRA and gcc 1.42 have false positive here */
        int main(void) { return (thiswillneverbedefinedIhope()); }
        #else
        #include <string.h>
        #undef __attribute__
        int xcopy(const void *, void *, size_t)
-           __attribute__((bounded (buffer, 1, 3)))
-           __attribute__((bounded (buffer, 2, 3)));
+           __attribute__((__bounded__ (__buffer__, 1, 3)))
+           __attribute__((__bounded__ (__buffer__, 2, 3)));
        int main(int ac, char *av[]) { return (xcopy(av[0], av[--ac], 1)); }
        int xcopy(const void *s, void *d, size_t n) {
                memmove(d, s, n); return ((int)n);
        }
        #endif
 EOF
-ac_test attribute_format '' 'for __attribute__((format))' <<-'EOF'
-       #if defined(__GNUC__) && (__GNUC__ < 2)
-       /* force a failure: gcc 1.42 has a false positive here */
+ac_test attribute_format '' 'for __attribute__((__format__))' <<-'EOF'
+       #if defined(__TenDRA__) || (defined(__GNUC__) && (__GNUC__ < 2))
+       /* force a failure: TenDRA and gcc 1.42 have false positive here */
        int main(void) { return (thiswillneverbedefinedIhope()); }
        #else
+       #define fprintf printfoo
        #include <stdio.h>
        #undef __attribute__
-       #undef printf
-       extern int printf(const char *format, ...)
-           __attribute__((format (printf, 1, 2)));
-       int main(int ac, char **av) { return (printf("%s%d", *av, ac)); }
+       #undef fprintf
+       extern int fprintf(FILE *, const char *format, ...)
+           __attribute__((__format__ (__printf__, 2, 3)));
+       int main(int ac, char **av) { return (fprintf(stderr, "%s%d", *av, ac)); }
        #endif
 EOF
-ac_test attribute_nonnull '' 'for __attribute__((nonnull))' <<-'EOF'
-       #if defined(__GNUC__) && (__GNUC__ < 2)
-       /* force a failure: gcc 1.42 has a false positive here */
+ac_test attribute_nonnull '' 'for __attribute__((__nonnull__))' <<-'EOF'
+       #if defined(__TenDRA__) || (defined(__GNUC__) && (__GNUC__ < 2))
+       /* force a failure: TenDRA and gcc 1.42 have false positive here */
        int main(void) { return (thiswillneverbedefinedIhope()); }
        #else
-       int foo(char *s1, char *s2) __attribute__((nonnull));
-       int bar(char *s1, char *s2) __attribute__((nonnull (1, 2)));
-       int baz(char *s) __attribute__((nonnull (1)));
+       int foo(char *s1, char *s2) __attribute__((__nonnull__));
+       int bar(char *s1, char *s2) __attribute__((__nonnull__ (1, 2)));
+       int baz(char *s) __attribute__((__nonnull__ (1)));
        int foo(char *s1, char *s2) { return (bar(s2, s1)); }
        int bar(char *s1, char *s2) { return (baz(s1) - baz(s2)); }
        int baz(char *s) { return (*s); }
        int main(int ac, char **av) { return (ac == foo(av[0], av[ac-1])); }
        #endif
 EOF
-ac_test attribute_noreturn '' 'for __attribute__((noreturn))' <<-'EOF'
-       #if defined(__GNUC__) && (__GNUC__ < 2)
-       /* force a failure: gcc 1.42 has a false positive here */
+ac_test attribute_noreturn '' 'for __attribute__((__noreturn__))' <<-'EOF'
+       #if defined(__TenDRA__) || (defined(__GNUC__) && (__GNUC__ < 2))
+       /* force a failure: TenDRA and gcc 1.42 have false positive here */
        int main(void) { return (thiswillneverbedefinedIhope()); }
        #else
        #include <stdlib.h>
        #undef __attribute__
-       void fnord(void) __attribute__((noreturn));
+       void fnord(void) __attribute__((__noreturn__));
        int main(void) { fnord(); }
        void fnord(void) { exit(0); }
        #endif
 EOF
-ac_test attribute_unused '' 'for __attribute__((unused))' <<-'EOF'
-       #if defined(__GNUC__) && (__GNUC__ < 2)
-       /* force a failure: gcc 1.42 has a false positive here */
+ac_test attribute_unused '' 'for __attribute__((__unused__))' <<-'EOF'
+       #if defined(__TenDRA__) || (defined(__GNUC__) && (__GNUC__ < 2))
+       /* force a failure: TenDRA and gcc 1.42 have false positive here */
        int main(void) { return (thiswillneverbedefinedIhope()); }
        #else
-       int main(int ac __attribute__((unused)), char **av
-           __attribute__((unused))) { return (0); }
+       int main(int ac __attribute__((__unused__)), char **av
+           __attribute__((__unused__))) { return (0); }
        #endif
 EOF
-ac_test attribute_used '' 'for __attribute__((used))' <<-'EOF'
-       #if defined(__GNUC__) && (__GNUC__ < 2)
-       /* force a failure: gcc 1.42 has a false positive here */
+ac_test attribute_used '' 'for __attribute__((__used__))' <<-'EOF'
+       #if defined(__TenDRA__) || (defined(__GNUC__) && (__GNUC__ < 2))
+       /* force a failure: TenDRA and gcc 1.42 have false positive here */
        int main(void) { return (thiswillneverbedefinedIhope()); }
        #else
-       static const char fnord[] __attribute__((used)) = "42";
+       static const char fnord[] __attribute__((__used__)) = "42";
        int main(void) { return (0); }
        #endif
 EOF
@@ -1043,43 +1126,93 @@ if ac_ifcpp 'ifdef MKSH_SMALL' isset_MKSH_SMALL '' \
                ;;
        esac
 
-       : ${HAVE_MKNOD=0}
        : ${HAVE_NICE=0}
-       : ${HAVE_REVOKE=0}
        : ${HAVE_PERSISTENT_HISTORY=0}
-       check_categories=$check_categories,smksh
+       check_categories="$check_categories smksh"
        HAVE_ISSET_MKSH_CONSERVATIVE_FDS=1      # from sh.h
 fi
 ac_ifcpp 'ifdef MKSH_BINSHREDUCED' isset_MKSH_BINSHREDUCED '' \
     "if a reduced-feature sh is requested" && \
-    check_categories=$check_categories,binsh
+    check_categories="$check_categories binsh"
 ac_ifcpp 'ifdef MKSH_UNEMPLOYED' isset_MKSH_UNEMPLOYED '' \
     "if mksh will be built without job control" && \
-    check_categories=$check_categories,arge
+    check_categories="$check_categories arge"
+ac_ifcpp 'ifdef MKSH_NOPROSPECTOFWORK' isset_MKSH_NOPROSPECTOFWORK '' \
+    "if mksh will be built without job signals" && \
+    check_categories="$check_categories arge nojsig"
 ac_ifcpp 'ifdef MKSH_ASSUME_UTF8' isset_MKSH_ASSUME_UTF8 '' \
     'if the default UTF-8 mode is specified' && : ${HAVE_SETLOCALE_CTYPE=0}
 ac_ifcpp 'ifdef MKSH_CONSERVATIVE_FDS' isset_MKSH_CONSERVATIVE_FDS '' \
     'if traditional/conservative fd use is requested' && \
-    check_categories=$check_categories,convfds
+    check_categories="$check_categories convfds"
 
 #
 # Environment: headers
 #
-ac_header sys/param.h
+ac_header sys/bsdtypes.h
+ac_header sys/file.h sys/types.h
 ac_header sys/mkdev.h sys/types.h
 ac_header sys/mman.h sys/types.h
+ac_header sys/param.h
+ac_header sys/select.h sys/types.h
 ac_header sys/sysmacros.h
+ac_header bstring.h
 ac_header grp.h sys/types.h
 ac_header libgen.h
 ac_header libutil.h sys/types.h
 ac_header paths.h
-ac_header stdbool.h
 ac_header stdint.h stdarg.h
-ac_header strings.h sys/types.h
+# include strings.h only if compatible with string.h
+ac_header strings.h sys/types.h string.h
 ac_header ulimit.h sys/types.h
 ac_header values.h
 
 #
+# check whether whatever we use for the final link will succeed
+#
+if test $cm = makefile; then
+       : nothing to check
+else
+       HAVE_LINK_WORKS=x
+       ac_testinit link_works '' 'checking if the final link command may succeed'
+       fv=1
+       cat >conftest.c <<-'EOF'
+               #define EXTERN
+               #define MKSH_INCLUDES_ONLY
+               #include "sh.h"
+               __RCSID("$MirOS: src/bin/mksh/Build.sh,v 1.488 2011/10/07 19:51:41 tg Exp $");
+               int main(void) { printf("Hello, World!\n"); return (0); }
+EOF
+       case $cm in
+       llvm)
+               v "$CC $CFLAGS $CPPFLAGS $NOWARN -emit-llvm -c conftest.c" || fv=0
+               rmf mksh.s
+               test $fv = 0 || v "llvm-link -o - conftest.o | opt $optflags | llc -o mksh.s" || fv=0
+               test $fv = 0 || v "$CC $CFLAGS $LDFLAGS -o $tcfn mksh.s $LIBS $ccpr"
+               ;;
+       dragonegg)
+               v "$CC $CFLAGS $CPPFLAGS $NOWARN -S -flto conftest.c" || fv=0
+               test $fv = 0 || v "mv conftest.s conftest.ll"
+               test $fv = 0 || v "llvm-as conftest.ll" || fv=0
+               rmf mksh.s
+               test $fv = 0 || v "llvm-link -o - conftest.bc | opt $optflags | llc -o mksh.s" || fv=0
+               test $fv = 0 || v "$CC $CFLAGS $LDFLAGS -o $tcfn mksh.s $LIBS $ccpr"
+               ;;
+       combine)
+               v "$CC $CFLAGS $CPPFLAGS $LDFLAGS -fwhole-program --combine $NOWARN -o $tcfn conftest.c $LIBS $ccpr"
+               ;;
+       lto|normal)
+               cm=normal
+               v "$CC $CFLAGS $CPPFLAGS $NOWARN -c conftest.c" || fv=0
+               test $fv = 0 || v "$CC $CFLAGS $LDFLAGS -o $tcfn conftest.o $LIBS $ccpr"
+               ;;
+       esac
+       test -f $tcfn || fv=0
+       ac_testdone
+       test $fv = 1 || exit 1
+fi
+
+#
 # Environment: definitions
 #
 echo '#include <sys/types.h>
@@ -1090,10 +1223,11 @@ int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 &&
 int main(void) { return (0); }' >lft.c
 ac_testn can_lfs '' "for large file support" <lft.c
 save_CPPFLAGS=$CPPFLAGS
-CPPFLAGS="$CPPFLAGS -D_FILE_OFFSET_BITS=64"
+add_cppflags -D_FILE_OFFSET_BITS=64
 ac_testn can_lfs_sus '!' can_lfs 0 "... with -D_FILE_OFFSET_BITS=64" <lft.c
 if test 0 = $HAVE_CAN_LFS_SUS; then
-       CPPFLAGS="$save_CPPFLAGS -D_LARGE_FILES=1"
+       CPPFLAGS=$save_CPPFLAGS
+       add_cppflags -D_LARGE_FILES=1
        ac_testn can_lfs_aix '!' can_lfs 0 "... with -D_LARGE_FILES=1" <lft.c
        test 1 = $HAVE_CAN_LFS_AIX || CPPFLAGS=$save_CPPFLAGS
 fi
@@ -1136,17 +1270,17 @@ ac_testn sig_t <<-'EOF'
        #include <sys/types.h>
        #include <signal.h>
        #include <stddef.h>
-       int main(void) { return ((int)(ptrdiff_t)(sig_t)kill(0,0)); }
+       int main(void) { return ((int)(ptrdiff_t)(sig_t)(ptrdiff_t)kill(0,0)); }
 EOF
 
 ac_testn sighandler_t '!' sig_t 0 <<-'EOF'
        #include <sys/types.h>
        #include <signal.h>
        #include <stddef.h>
-       int main(void) { return ((int)(ptrdiff_t)(sighandler_t)kill(0,0)); }
+       int main(void) { return ((int)(ptrdiff_t)(sighandler_t)(ptrdiff_t)kill(0,0)); }
 EOF
 if test 1 = $HAVE_SIGHANDLER_T; then
-       CPPFLAGS="$CPPFLAGS -Dsig_t=sighandler_t"
+       add_cppflags -Dsig_t=sighandler_t
        HAVE_SIG_T=1
 fi
 
@@ -1154,14 +1288,14 @@ ac_testn __sighandler_t '!' sig_t 0 <<-'EOF'
        #include <sys/types.h>
        #include <signal.h>
        #include <stddef.h>
-       int main(void) { return ((int)(ptrdiff_t)(__sighandler_t)kill(0,0)); }
+       int main(void) { return ((int)(ptrdiff_t)(__sighandler_t)(ptrdiff_t)kill(0,0)); }
 EOF
 if test 1 = $HAVE___SIGHANDLER_T; then
-       CPPFLAGS="$CPPFLAGS -Dsig_t=__sighandler_t"
+       add_cppflags -Dsig_t=__sighandler_t
        HAVE_SIG_T=1
 fi
 
-test 1 = $HAVE_SIG_T || CPPFLAGS="$CPPFLAGS -Dsig_t=nosig_t"
+test 1 = $HAVE_SIG_T || add_cppflags -Dsig_t=nosig_t
 ac_cppflags SIG_T
 
 #
@@ -1179,9 +1313,10 @@ for what in name list; do
                extern const char *const _sys_sig${what}[];
                int main(void) { return (_sys_sig${what}[0][0]); }
        EOF
-       if eval "test 1 = \$HAVE__SYS_SIG$uwhat"; then
-               CPPFLAGS="$CPPFLAGS -Dsys_sig$what=_sys_sig$what"
-               eval "HAVE_SYS_SIG$uwhat=1"
+       eval uwhat_v=\$HAVE__SYS_SIG$uwhat
+       if test 1 = "$uwhat_v"; then
+               add_cppflags -Dsys_sig$what=_sys_sig$what
+               eval HAVE_SYS_SIG$uwhat=1
        fi
        ac_cppflags SYS_SIG$uwhat
 done
@@ -1197,8 +1332,12 @@ EOF
 #
 ac_testn flock_ex '' 'flock and mmap' <<-'EOF'
        #include <sys/types.h>
+       #if HAVE_SYS_FILE_H
        #include <sys/file.h>
+       #endif
+       #if HAVE_SYS_MMAN_H
        #include <sys/mman.h>
+       #endif
        #include <fcntl.h>
        #include <stdlib.h>
        int main(void) { return ((void *)mmap(NULL, (size_t)flock(0, LOCK_EX),
@@ -1264,18 +1403,30 @@ ac_test langinfo_codeset setlocale_ctype 0 'nl_langinfo(CODESET)' <<-'EOF'
        int main(void) { return ((int)(ptrdiff_t)(void *)nl_langinfo(CODESET)); }
 EOF
 
-ac_test setmode mknod 1 <<-'EOF'
-       /* XXX imake style */
-       /* XXX conditions correct? */
-       #if defined(__MSVCRT__) || defined(__CYGWIN__)
-       /* force a failure: Win32 setmode() is not what we want... */
-       int main(void) { return (thiswillneverbedefinedIhope()); }
-       #else
+ac_test select <<-'EOF'
        #include <sys/types.h>
-       #include <unistd.h>
-       int main(int ac, char *av[]) { return (getmode(setmode(av[0]),
-           (mode_t)ac)); }
+       #include <sys/time.h>
+       #if HAVE_SYS_BSDTYPES_H
+       #include <sys/bsdtypes.h>
        #endif
+       #if HAVE_SYS_SELECT_H
+       #include <sys/select.h>
+       #endif
+       #if HAVE_BSTRING_H
+       #include <bstring.h>
+       #endif
+       #include <stddef.h>
+       #include <stdlib.h>
+       #include <string.h>
+       #if HAVE_STRINGS_H
+       #include <strings.h>
+       #endif
+       #include <unistd.h>
+       int main(void) {
+               struct timeval tv = { 1, 200000 };
+               fd_set fds; FD_ZERO(&fds); FD_SET(0, &fds);
+               return (select(FD_SETSIZE, &fds, NULL, NULL, &tv));
+       }
 EOF
 
 ac_test setresugid <<-'EOF'
@@ -1340,7 +1491,7 @@ CC=$save_CC; LDFLAGS=$save_LDFLAGS; LIBS=$save_LIBS
 #
 fd='if to use persistent history'
 ac_cache PERSISTENT_HISTORY || test 0 = $HAVE_FLOCK_EX || fv=1
-test 1 = $fv || check_categories=$check_categories,no-histfile
+test 1 = $fv || check_categories="$check_categories no-histfile"
 ac_testdone
 ac_cppflags
 
@@ -1365,7 +1516,7 @@ $e ... done.
 # the character count to standard output; cope for that
 echo wq >x
 ed x <x 2>/dev/null | grep 3 >/dev/null 2>&1 && \
-    check_categories=$check_categories,$oldish_ed
+    check_categories="$check_categories $oldish_ed"
 rmf x vv.out
 
 if test 0 = $HAVE_SYS_SIGNAME; then
@@ -1407,7 +1558,7 @@ mksh_cfg: NSIG' >conftest.c
                vq "$CPP $CFLAGS $CPPFLAGS $NOWARN conftest.c" | \
                    grep mksh_cfg: | \
                    sed 's/^mksh_cfg:[   ]*\([0-9x]*\).*$/\1:'$name/
-       done | grep -v '^:' | while IFS=: read nr name; do
+       done | grep -v '^:' | sed 's/:/ /g' | while read nr name; do
                test $printf = echo || nr=`printf %d "$nr" 2>/dev/null`
                test $nr -gt 0 && test $nr -le $NSIG || continue
                case $sigseen in
@@ -1422,11 +1573,9 @@ mksh_cfg: NSIG' >conftest.c
        $e done.
 fi
 
-addsrcs '!' HAVE_SETMODE setmode.c
 addsrcs '!' HAVE_STRLCPY strlcpy.c
 addsrcs USE_PRINTF_BUILTIN printf.c
-test 1 = "$USE_PRINTF_BUILTIN" && CPPFLAGS="$CPPFLAGS -DMKSH_PRINTF_BUILTIN"
-test 0 = "$HAVE_SETMODE" && CPPFLAGS="$CPPFLAGS -DHAVE_CONFIG_H -DCONFIG_H_FILENAME=\\\"sh.h\\\""
+test 1 = "$USE_PRINTF_BUILTIN" && add_cppflags -DMKSH_PRINTF_BUILTIN
 test 1 = "$HAVE_CAN_VERB" && CFLAGS="$CFLAGS -verbose"
 
 $e $bi$me: Finished configuration testing, now producing output.$ao
@@ -1441,7 +1590,42 @@ esac
 cat >>test.sh <<-EOF
        LC_ALL=C PATH='$PATH'; export LC_ALL PATH
        test -n "\$KSH_VERSION" || exit 1
-       check_categories=$check_categories
+       set -A check_categories -- $check_categories
+       pflag='$curdir/mksh'
+       sflag='$srcdir/check.t'
+       usee=0 Pflag=0 uset=0 vflag=0 xflag=0
+       while getopts "C:e:Pp:s:t:v" ch; do case \$ch {
+       (C)     check_categories[\${#check_categories[*]}]=\$OPTARG ;;
+       (e)     usee=1; eflag=\$OPTARG ;;
+       (P)     Pflag=1 ;;
+       (p)     pflag=\$OPTARG ;;
+       (s)     sflag=\$OPTARG ;;
+       (t)     uset=1; tflag=\$OPTARG ;;
+       (v)     vflag=1 ;;
+       (*)     xflag=1 ;;
+       }
+       done
+       shift \$((OPTIND - 1))
+       set -A args -- '$srcdir/check.pl' -p "\$pflag" -s "\$sflag"
+       x=
+       for y in "\${check_categories[@]}"; do
+               x=\$x,\$y
+       done
+       if [[ -n \$x ]]; then
+               args[\${#args[*]}]=-C
+               args[\${#args[*]}]=\${x#,}
+       fi
+       if (( usee )); then
+               args[\${#args[*]}]=-e
+               args[\${#args[*]}]=\$eflag
+       fi
+       (( Pflag )) && args[\${#args[*]}]=-P
+       if (( uset )); then
+               args[\${#args[*]}]=-t
+               args[\${#args[*]}]=\$tflag
+       fi
+       (( vflag )) && args[\${#args[*]}]=-v
+       (( xflag )) && args[\${#args[*]}]=-x    # force usage by synerr
        print Testing mksh for conformance:
        fgrep MirOS: '$srcdir/check.t'
        fgrep MIRBSD '$srcdir/check.t'
@@ -1451,14 +1635,13 @@ cat >>test.sh <<-EOF
        cstr="\$cstr"'print \$os . ", Perl version " . \$];'
        for perli in \$PERL perl5 perl no; do
                [[ \$perli = no ]] && exit 1
-               perlos=\$(\$perli -e "\$cstr") 2>&- || continue
+               perlos=\$(\$perli -e "\$cstr") 2>/dev/null || continue
                print "Perl interpreter '\$perli' running on '\$perlos'"
                [[ -n \$perlos ]] && break
        done
-       exec \$perli '$srcdir/check.pl' -s '$srcdir/check.t' -p '$curdir/mksh' \${check_categories:+-C} \${check_categories#,} \$*$tsts
+       exec \$perli "\${args[@]}" "\$@"$tsts
 EOF
 chmod 755 test.sh
-test $HAVE_CAN_COMBINE$cm = 0combine && cm=normal
 if test $cm = llvm; then
        emitbc="-emit-llvm -c"
 elif test $cm = dragonegg; then
diff --git a/src/Makefile b/src/Makefile
new file mode 100644 (file)
index 0000000..03ca8bf
--- /dev/null
@@ -0,0 +1,97 @@
+# $MirOS: src/bin/mksh/Makefile,v 1.88 2011/10/07 19:51:17 tg Exp $
+#-
+# Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
+#      Thorsten Glaser <tg@mirbsd.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un-
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person's immediate fault when using the work as intended.
+#-
+# use CPPFLAGS=-DDEBUG __CRAZY=Yes to check for certain more stuff
+
+.include <bsd.own.mk>
+
+PROG=          mksh
+SRCS=          edit.c eval.c exec.c expr.c funcs.c histrap.c jobs.c \
+               lalloc.c lex.c main.c misc.c shf.c syn.c tree.c var.c
+.if !make(test-build)
+CPPFLAGS+=     -DMKSH_ASSUME_UTF8 \
+               -DHAVE_ATTRIBUTE_BOUNDED=1 -DHAVE_ATTRIBUTE_FORMAT=1 \
+               -DHAVE_ATTRIBUTE_NONNULL=1 -DHAVE_ATTRIBUTE_NORETURN=1 \
+               -DHAVE_ATTRIBUTE_UNUSED=1 -DHAVE_ATTRIBUTE_USED=1 \
+               -DHAVE_SYS_BSDTYPES_H=0 -DHAVE_SYS_FILE_H=1 \
+               -DHAVE_SYS_MKDEV_H=0 -DHAVE_SYS_MMAN_H=1 -DHAVE_SYS_PARAM_H=1 \
+               -DHAVE_SYS_SELECT_H=1 -DHAVE_SYS_SYSMACROS_H=0 \
+               -DHAVE_BSTRING_H=0 -DHAVE_GRP_H=1 -DHAVE_LIBGEN_H=1 \
+               -DHAVE_LIBUTIL_H=0 -DHAVE_PATHS_H=1 -DHAVE_STDINT_H=1 \
+               -DHAVE_STRINGS_H=1 -DHAVE_ULIMIT_H=0 -DHAVE_VALUES_H=0 \
+               -DHAVE_CAN_INTTYPES=1 -DHAVE_CAN_UCBINTS=1 \
+               -DHAVE_CAN_INT8TYPE=1 -DHAVE_CAN_UCBINT8=1 -DHAVE_RLIM_T=1 \
+               -DHAVE_SIG_T=1 -DHAVE_SYS_SIGNAME=1 -DHAVE_SYS_SIGLIST=1 \
+               -DHAVE_STRSIGNAL=0 -DHAVE_GETRUSAGE=1 -DHAVE_KILLPG=1 \
+               -DHAVE_MKNOD=0 -DHAVE_MKSTEMP=1 -DHAVE_NICE=1 -DHAVE_REVOKE=1 \
+               -DHAVE_SETLOCALE_CTYPE=0 -DHAVE_LANGINFO_CODESET=0 \
+               -DHAVE_SELECT=1 -DHAVE_SETRESUGID=1 -DHAVE_SETGROUPS=1 \
+               -DHAVE_STRCASESTR=1 -DHAVE_STRLCPY=1 -DHAVE_FLOCK_DECL=1 \
+               -DHAVE_REVOKE_DECL=1 -DHAVE_SYS_SIGLIST_DECL=1 \
+               -DHAVE_PERSISTENT_HISTORY=1
+COPTS+=                -std=gnu99 -Wall
+.endif
+
+USE_PRINTF_BUILTIN?=   0
+.if ${USE_PRINTF_BUILTIN} == 1
+.PATH: ${BSDSRCDIR}/usr.bin/printf
+SRCS+=         printf.c
+CPPFLAGS+=     -DMKSH_PRINTF_BUILTIN
+.endif
+
+MANLINKS=      [ false pwd sh sleep test true
+BINLINKS=      ${MANLINKS} echo domainname kill
+.for _i in ${BINLINKS}
+LINKS+=                ${BINDIR}/${PROG} ${BINDIR}/${_i}
+.endfor
+.for _i in ${MANLINKS}
+MLINKS+=       ${PROG}.1 ${_i}.1
+.endfor
+
+regress: ${PROG} check.pl check.t
+       -rm -rf regress-dir
+       mkdir -p regress-dir
+       echo export FNORD=666 >regress-dir/.mkshrc
+       HOME=$$(realpath regress-dir) perl ${.CURDIR}/check.pl \
+           -s ${.CURDIR}/check.t -v -p ./${PROG}
+
+test-build: .PHONY
+       -rm -rf build-dir
+       mkdir -p build-dir
+.if ${USE_PRINTF_BUILTIN} == 1
+       cp ${BSDSRCDIR}/usr.bin/printf/printf.c build-dir/
+.endif
+       cd build-dir; env CC=${CC:Q} CFLAGS=${CFLAGS:M*:Q} \
+           CPPFLAGS=${CPPFLAGS:M*:Q} LDFLAGS=${LDFLAGS:M*:Q} \
+           LIBS= NOWARN=-Wno-error TARGET_OS= CPP= /bin/sh \
+           ${.CURDIR}/Build.sh -Q -r && ./test.sh -v
+
+cleandir: clean-extra
+
+clean-extra: .PHONY
+       -rm -rf build-dir regress-dir printf.o printf.ln
+
+distribution:
+       sed 's!\$$I''d\([:$$]\)!$$M''irSecuCron\1!g' \
+           ${.CURDIR}/dot.mkshrc >${DESTDIR}/etc/skel/.mkshrc
+       chown ${BINOWN}:${CONFGRP} ${DESTDIR}/etc/skel/.mkshrc
+       chmod 0644 ${DESTDIR}/etc/skel/.mkshrc
+
+.include <bsd.prog.mk>
index e793e95..7dfab36 100644 (file)
@@ -1,7 +1,7 @@
-# $MirOS: src/bin/mksh/check.pl,v 1.23 2009/06/10 18:12:43 tg Rel $
+# $MirOS: src/bin/mksh/check.pl,v 1.27 2011/05/29 02:18:47 tg Exp $
 # $OpenBSD: th,v 1.13 2006/05/18 21:27:23 miod Exp $
 #-
-# Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009
+# Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2011
 #      Thorsten Glaser <tg@mirbsd.org>
 #
 # Provided that these terms and disclaimer and all copyright notices
@@ -71,7 +71,8 @@
 #                                      environment. Programs are run with
 #                                      the following minimal environment:
 #                                          HOME, LD_LIBRARY_PATH, LOCPATH,
-#                                          LOGNAME, PATH, SHELL, USER
+#                                          LOGNAME, PATH, SHELL, UNIXMODE,
+#                                          USER
 #                                      (values taken from the environment of
 #                                      the test harness).
 #                                      ENV is set to /nonexistant.
 #                                      One category os:XXX is predefined
 #                                      (XXX is the operating system name,
 #                                      eg, linux, dec_osf).
+#      need-ctty                       'yes' if the test needs a ctty, run
+#                                      with -C regress:no-ctty to disable.
 # Flag meanings:
 #      r       tag is required (eg, a test must have a name tag).
 #      m       value can be multiple lines. Lines must be prefixed with
@@ -156,19 +159,19 @@ $os = defined $^O ? $^O : 'unknown';
 ($prog = $0) =~ s#.*/##;
 
 $Usage = <<EOF ;
-Usage: $prog [-s test-set] [-C category] [-p prog] [-v] [-e e=v] name ...
-       -p p    Use p as the program to test
+Usage: $prog [-Pv] [-C cat] [-e e=v] [-p prog] [-s fn] [-t tmo] name ...
        -C c    Specify the comma separated list of categories the program
                belongs to (see category field).
+       -e e=v  Set the environment variable e to v for all tests
+               (if no =v is given, the current value is used)
+               Only one -e option can be given at the moment, sadly.
+       -P      program (-p) string has multiple words, and the program is in
+               the path (kludge option)
+       -p p    Use p as the program to test
        -s s    Read tests from file s; if s is a directory, it is recursively
                scaned for test files (which end in .t).
        -t t    Use t as default time limit for tests (default is unlimited)
-       -P      program (-p) string has multiple words, and the program is in
-               the path (kludge option)
        -v      Verbose mode: print reason test failed.
-       -e e=v  Set the environment variable e to v for all tests
-               (if no =v is given, the current value is used)
-               Only one -e option can be given at the moment, sadly.
        name    specifies the name of the test(s) to run; if none are
                specified, all tests are run.
 EOF
@@ -193,6 +196,8 @@ EOF
        'expected-stderr',              'm',
        'expected-stderr-pattern',      'm',
        'category',                     'm',
+       'need-ctty',                    '',
+       'need-pass',                    '',
        );
 # Filled in by read_test()
 %internal_test_fields = (
@@ -213,13 +218,14 @@ $tempe = "/tmp/rte$$";
 $tempdir = "/tmp/rtd$$";
 
 $nfailed = 0;
+$nifailed = 0;
 $nxfailed = 0;
 $npassed = 0;
 $nxpassed = 0;
 
 %known_tests = ();
 
-if (!getopts('C:p:Ps:t:ve:')) {
+if (!getopts('C:e:Pp:s:t:v')) {
     print STDERR $Usage;
     exit 1;
 }
@@ -253,7 +259,7 @@ $all_tests = @ARGV == 0;
 # Set up a very minimal environment
 %new_env = ();
 foreach $env (('HOME', 'LD_LIBRARY_PATH', 'LOCPATH', 'LOGNAME',
-  'PATH', 'SHELL', 'USER')) {
+  'PATH', 'SHELL', 'UNIXMODE', 'USER')) {
     $new_env{$env} = $ENV{$env} if defined $ENV{$env};
 }
 $new_env{'ENV'} = '/nonexistant';
@@ -300,12 +306,13 @@ if (-d $test_set) {
 }
 &cleanup_exit() if !defined $ret;
 
-$tot_failed = $nfailed + $nxfailed;
+$tot_failed = $nfailed + $nifailed + $nxfailed;
 $tot_passed = $npassed + $nxpassed;
 if ($tot_failed || $tot_passed) {
     print "Total failed: $tot_failed";
+    print " ($nifailed ignored)" if $nifailed;
     print " ($nxfailed unexpected)" if $nxfailed;
-    print " (as expected)" if $nfailed && !$nxfailed;
+    print " (as expected)" if $nfailed && !$nxfailed && !$nifailed;
     print "\nTotal passed: $tot_passed";
     print " ($nxpassed unexpected)" if $nxpassed;
     print "\n";
@@ -319,7 +326,11 @@ cleanup_exit
     local($sig, $exitcode) = ('', 1);
 
     if ($_[0] eq 'ok') {
-       $exitcode = 0;
+       unless ($nxfailed) {
+               $exitcode = 0;
+       } else {
+               $exitcode = 1;
+       }
     } elsif ($_[0] ne '') {
        $sig = $_[0];
     }
@@ -616,8 +627,13 @@ run_test
 
     if ($failed) {
        if (!$test{'expected-fail'}) {
-           print "FAIL $name\n";
-           $nxfailed++;
+           if ($test{'need-pass'}) {
+               print "FAIL $name\n";
+               $nxfailed++;
+           } else {
+               print "FAIL $name (ignored)\n";
+               $nifailed++;
+           }
        } else {
            print "fail $name (as expected)\n";
            $nfailed++;
@@ -642,6 +658,7 @@ category_check
     local(*test) = @_;
     local($c);
 
+    return 0 if ($test{'need-ctty'} && defined $categories{'regress:no-ctty'});
     return 1 if (!defined $test{'category'});
     local($ok) = 0;
     foreach $c (split(',', $test{'category'})) {
@@ -1064,6 +1081,26 @@ read_test
     } else {
        $test{'expected-fail'} = 0;
     }
+    if (defined $test{'need-ctty'}) {
+       if ($test{'need-ctty'} !~ /^(yes|no)$/) {
+           print STDERR
+             "$prog:$test{':long-name'}: bad value for need-ctty field\n";
+           return undef;
+       }
+       $test{'need-ctty'} = $1 eq 'yes';
+    } else {
+       $test{'need-ctty'} = 0;
+    }
+    if (defined $test{'need-pass'}) {
+       if ($test{'need-pass'} !~ /^(yes|no)$/) {
+           print STDERR
+             "$prog:$test{':long-name'}: bad value for need-pass field\n";
+           return undef;
+       }
+       $test{'need-pass'} = $1 eq 'yes';
+    } else {
+       $test{'need-pass'} = 1;
+    }
     if (defined $test{'arguments'}) {
        local($firstc) = substr($test{'arguments'}, 0, 1);
 
index c8e8caf..8df403a 100644 (file)
@@ -1,9 +1,9 @@
-# $MirOS: src/bin/mksh/check.t,v 1.388 2010/08/24 15:47:44 tg Exp $
+# $MirOS: src/bin/mksh/check.t,v 1.483 2011/10/07 19:51:42 tg Exp $
 # $OpenBSD: bksl-nl.t,v 1.2 2001/01/28 23:04:56 niklas Exp $
 # $OpenBSD: history.t,v 1.5 2001/01/28 23:04:56 niklas Exp $
 # $OpenBSD: read.t,v 1.3 2003/03/10 03:48:16 david Exp $
 #-
-# Copyright © 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+# Copyright © 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
 #      Thorsten Glaser <tg@mirbsd.org>
 #
 # Provided that these terms and disclaimer and all copyright notices
@@ -25,7 +25,7 @@
 # http://www.research.att.com/~gsf/public/ifs.sh
 
 expected-stdout:
-       @(#)MIRBSD KSH R39 2010/08/24
+       @(#)MIRBSD KSH R40 2011/10/07
 description:
        Check version of shell.
 stdin:
@@ -65,6 +65,16 @@ category: disabled
 stdin:
        set
 ---
+name: selftest-direct-builtin-call
+description:
+       Check that direct builtin calls work
+stdin:
+       ln -s "$__progname" cat
+       ln -s "$__progname" echo
+       ./echo -c 'echo  foo' | ./cat -u
+expected-stdout:
+       -c echo  foo
+---
 name: alias-1
 description:
        Check that recursion is detected/avoided in aliases.
@@ -346,7 +356,7 @@ expected-stdout:
 ---
 name: bksl-nl-ign-1
 description:
-       Check that \newline is not collasped after #
+       Check that \newline is not collapsed after #
 stdin:
        echo hi #there \
        echo folks
@@ -356,7 +366,7 @@ expected-stdout:
 ---
 name: bksl-nl-ign-2
 description:
-       Check that \newline is not collasped inside single quotes
+       Check that \newline is not collapsed inside single quotes
 stdin:
        echo 'hi \
        there'
@@ -368,7 +378,7 @@ expected-stdout:
 ---
 name: bksl-nl-ign-3
 description:
-       Check that \newline is not collasped inside single quotes
+       Check that \newline is not collapsed inside single quotes
 stdin:
        cat << \EOF
        hi \
@@ -418,7 +428,7 @@ expected-stdout:
 #
 name: bksl-nl-1
 description:
-       Check that \newline is collasped before, in the middle of, and
+       Check that \newline is collapsed before, in the middle of, and
        after words
 stdin:
                        \
@@ -430,7 +440,7 @@ expected-stdout:
 ---
 name: bksl-nl-2
 description:
-       Check that \newline is collasped in $ sequences
+       Check that \newline is collapsed in $ sequences
        (ksh93 fails this)
 stdin:
        a=12
@@ -454,7 +464,7 @@ expected-stdout:
 ---
 name: bksl-nl-3
 description:
-       Check that \newline is collasped in $(..) and `...` sequences
+       Check that \newline is collapsed in $(..) and `...` sequences
        (ksh93 fails this)
 stdin:
        echo $\
@@ -479,7 +489,7 @@ expected-stdout:
 ---
 name: bksl-nl-4
 description:
-       Check that \newline is collasped in $((..)) sequences
+       Check that \newline is collapsed in $((..)) sequences
        (ksh93 fails this)
 stdin:
        echo $\
@@ -501,7 +511,7 @@ expected-stdout:
 ---
 name: bksl-nl-5
 description:
-       Check that \newline is collasped in double quoted strings
+       Check that \newline is collapsed in double quoted strings
 stdin:
        echo "\
        hi"
@@ -516,7 +526,7 @@ expected-stdout:
 ---
 name: bksl-nl-6
 description:
-       Check that \newline is collasped in here document delimiters
+       Check that \newline is collapsed in here document delimiters
        (ksh93 fails second part of this)
 stdin:
        a=12
@@ -539,7 +549,7 @@ expected-stdout:
 ---
 name: bksl-nl-7
 description:
-       Check that \newline is collasped in double-quoted here-document
+       Check that \newline is collapsed in double-quoted here-document
        delimiter.
 stdin:
        a=12
@@ -558,7 +568,7 @@ expected-stdout:
 ---
 name: bksl-nl-8
 description:
-       Check that \newline is collasped in various 2+ character tokens
+       Check that \newline is collapsed in various 2+ character tokens
        delimiter.
        (ksh93 fails this)
 stdin:
@@ -995,6 +1005,45 @@ expected-stdout:
          1 /bin
          0 /tmp
 ---
+name: cd-pe
+description:
+       Check package for cd -Pe
+need-pass: no
+# the mv command fails on Cygwin
+category: !os:cygwin
+file-setup: file 644 "x"
+       mkdir noread noread/target noread/target/subdir
+       ln -s noread link
+       chmod 311 noread
+       cd -P$1 .
+       echo 0=$?
+       bwd=$PWD
+       cd -P$1 link/target
+       echo 1=$?,${PWD#$bwd/}
+       epwd=$($TSHELL -c pwd 2>/dev/null)
+       # This unexpectedly succeeds on GNU/Linux and MidnightBSD
+       #echo pwd=$?,$epwd
+       # expect:       pwd=1,
+       mv ../../noread ../../renamed
+       cd -P$1 subdir
+       echo 2=$?,${PWD#$bwd/}
+       cd $bwd
+       chmod 755 renamed
+       rm -rf noread link renamed
+stdin:
+       export TSHELL="$__progname"
+       "$__progname" x
+       echo "now with -e:"
+       "$__progname" x e
+expected-stdout:
+       0=0
+       1=0,noread/target
+       2=0,noread/target/subdir
+       now with -e:
+       0=0
+       1=0,noread/target
+       2=1,noread/target/subdir
+---
 name: env-prompt
 description:
        Check that prompt not printed when processing ENV
@@ -1003,6 +1052,7 @@ file-setup: file 644 "foo"
        XXX=_
        PS1=X
        false && echo hmmm
+need-ctty: yes
 arguments: !-i!
 stdin:
        echo hi${XXX}there
@@ -1360,6 +1410,56 @@ expected-stdout:
        9 EQAL brac foo x c x} baz
        9 QSTN brac foo x c x} baz
 ---
+name: expand-threecolons-dblq
+description:
+       Check for a particular thing that used to segfault
+stdin:
+       TEST=1234
+       echo "${TEST:1:2:3}"
+       echo $? but still living
+expected-stderr-pattern:
+       /bad substitution/
+expected-exit: 1
+---
+name: expand-threecolons-unq
+description:
+       Check for a particular thing that used to not error out
+stdin:
+       TEST=1234
+       echo ${TEST:1:2:3}
+       echo $? but still living
+expected-stderr-pattern:
+       /bad substitution/
+expected-exit: 1
+---
+name: expand-weird-1
+description:
+       Check corner case of trim expansion vs. $# vs. ${#var}
+stdin:
+       set 1 2 3 4 5 6 7 8 9 10 11
+       echo ${#}       # value of $#
+       echo ${##}      # length of $#
+       echo ${##1}     # $# trimmed 1
+       set 1 2 3 4 5 6 7 8 9 10 11 12
+       echo ${##1}
+expected-stdout:
+       11
+       2
+       1
+       2
+---
+name: expand-weird-2
+description:
+       Check corner case of ${var?} vs. ${#var}
+stdin:
+       (exit 0)
+       echo $? = ${#?} .
+       (exit 111)
+       echo $? = ${#?} .
+expected-stdout:
+       0 = 1 .
+       111 = 3 .
+---
 name: eglob-bad-1
 description:
        Check that globbing isn't done when glob has syntax error
@@ -1495,6 +1595,27 @@ expected-stdout:
        3: abcdef
        4: cdef
 ---
+name: eglob-trim-3
+description:
+       Check eglobbing works in trims, for Korn Shell
+       Ensure eglobbing does not work for reduced-feature /bin/sh
+stdin:
+       set +o sh
+       x=foobar
+       y=foobaz
+       z=fooba\?
+       echo "<${x%bar|baz},${y%bar|baz},${z%\?}>"
+       echo "<${x%ba(r|z)},${y%ba(r|z)}>"
+       set -o sh
+       echo "<${x%bar|baz},${y%bar|baz},${z%\?}>"
+       z='foo(bar'
+       echo "<${z%(*}>"
+expected-stdout:
+       <foo,foo,fooba>
+       <foo,foo>
+       <foobar,foobaz,fooba>
+       <foo>
+---
 name: eglob-substrpl-1
 description:
        Check eglobbing works in substs... and they work at all
@@ -1742,6 +1863,8 @@ expected-stdout:
 name: glob-bad-2
 description:
        Check that symbolic links aren't stat()'d
+# breaks on FreeMiNT (cannot unlink dangling symlinks)
+category: !os:mint
 file-setup: dir 755 "dir"
 file-setup: symlink 644 "dir/abc"
        non-existent-file
@@ -1787,7 +1910,8 @@ name: glob-range-3
 description:
        Check that globbing matches the right things...
 # breaks on Mac OSX (HFS+ non-standard Unicode canonical decomposition)
-category: !os:darwin
+# breaks on Cygwin 1.7 (files are now UTF-16 or something)
+category: !os:cygwin,!os:darwin
 file-setup: file 644 "aÂc"
 stdin:
        echo a[Á-Ú]*
@@ -2007,6 +2131,230 @@ stdin:
 expected-stdout:
        one
 ---
+name: heredoc-9e
+description:
+       Check here string related regression with multiple iops
+stdin:
+       echo $(tr r z <<<'bar' 2>&-)
+expected-stdout:
+       baz
+---
+name: heredoc-10
+description:
+       Check direct here document assignment
+stdin:
+       x=u
+       va=<<EOF
+       =a $x \x40=
+       EOF
+       vb=<<'EOF'
+       =b $x \x40=
+       EOF
+       function foo {
+               vc=<<-EOF
+                       =c $x \x40=
+               EOF
+       }
+       typeset -f foo
+       foo
+       # rather nonsensical, but…
+       vd=<<<"=d $x \x40="
+       ve=<<<'=e $x \x40='
+       vf=<<<$'=f $x \x40='
+       # now check
+       print -r -- "| va={$va} vb={$vb} vc={$vc} vd={$vd} ve={$ve} vf={$vf} |"
+expected-stdout:
+       function foo {
+               vc= <<-EOF 
+       =c $x \x40=
+       EOF
+       
+       } 
+       | va={=a u \x40=
+       } vb={=b $x \x40=
+       } vc={=c u \x40=
+       } vd={=d u \x40=
+       } ve={=e $x \x40=
+       } vf={=f $x @=
+       } |
+---
+name: heredoc-11
+description:
+       Check here documents with no or empty delimiter
+stdin:
+       x=u
+       va=<<
+       =a $x \x40=
+       <<
+       vb=<<''
+       =b $x \x40=
+       
+       function foo {
+               vc=<<-
+                       =c $x \x40=
+               <<
+               vd=<<-''
+                       =d $x \x40=
+       
+       }
+       typeset -f foo
+       foo
+       print -r -- "| va={$va} vb={$vb} vc={$vc} vd={$vd} |"
+expected-stdout:
+       function foo {
+               vc= <<- 
+       =c $x \x40=
+       <<
+       
+               vd= <<-"" 
+       =d $x \x40=
+       
+       
+       } 
+       | va={=a u \x40=
+       } vb={=b $x \x40=
+       } vc={=c u \x40=
+       } vd={=d $x \x40=
+       } |
+---
+name: heredoc-comsub-1
+description:
+       Tests for here documents in COMSUB, taken from Austin ML
+stdin:
+       text=$(cat <<EOF
+       here is the text
+       EOF)
+       echo = $text =
+expected-stdout:
+       = here is the text =
+---
+name: heredoc-comsub-2
+description:
+       Tests for here documents in COMSUB, taken from Austin ML
+stdin:
+       unbalanced=$(cat <<EOF
+       this paren ) is a problem
+       EOF)
+       echo = $unbalanced =
+expected-stdout:
+       = this paren ) is a problem =
+---
+name: heredoc-comsub-3
+description:
+       Tests for here documents in COMSUB, taken from Austin ML
+stdin:
+       balanced=$(cat <<EOF
+       these parens ( ) are not a problem
+       EOF)
+       echo = $balanced =
+expected-stdout:
+       = these parens ( ) are not a problem =
+---
+name: heredoc-comsub-4
+description:
+       Tests for here documents in COMSUB, taken from Austin ML
+stdin:
+       balanced=$(cat <<EOF
+       these parens \( ) are a problem
+       EOF)
+       echo = $balanced =
+expected-stdout:
+       = these parens \( ) are a problem =
+---
+name: heredoc-subshell-1
+description:
+       Tests for here documents in subshells, taken from Austin ML
+stdin:
+       (cat <<EOF
+       some text
+       EOF)
+       echo end
+expected-stdout:
+       some text
+       end
+---
+name: heredoc-subshell-2
+description:
+       Tests for here documents in subshells, taken from Austin ML
+stdin:
+       (cat <<EOF
+       some text
+       EOF
+       )
+       echo end
+expected-stdout:
+       some text
+       end
+---
+name: heredoc-subshell-3
+description:
+       Tests for here documents in subshells, taken from Austin ML
+stdin:
+       (cat <<EOF; )
+       some text
+       EOF
+       echo end
+expected-stdout:
+       some text
+       end
+---
+name: heredoc-weird-1
+description:
+       Tests for here documents, taken from Austin ML
+       Documents current state in mksh, *NOT* necessarily correct!
+stdin:
+       cat <<END
+       hello
+       END\
+       END
+       END
+       echo end
+expected-stdout:
+       hello
+       ENDEND
+       end
+---
+name: heredoc-weird-2
+description:
+       Tests for here documents, taken from Austin ML
+stdin:
+       cat <<'    END    '
+       hello
+           END    
+       echo end
+expected-stdout:
+       hello
+       end
+---
+name: heredoc-weird-4
+description:
+       Tests for here documents, taken from Austin ML
+       Documents current state in mksh, *NOT* necessarily correct!
+stdin:
+       cat <<END
+       hello\
+       END
+       END
+       echo end
+expected-stdout:
+       helloEND
+       end
+---
+name: heredoc-weird-5
+description:
+       Tests for here documents, taken from Austin ML
+       Documents current state in mksh, *NOT* necessarily correct!
+stdin:
+       cat <<END
+       hello
+       \END
+       END
+       echo end
+expected-stdout:
+       hello
+       \END
+       end
+---
 name: heredoc-quoting-unsubst
 description:
        Check for correct handling of quoted characters in
@@ -2188,6 +2536,7 @@ description:
        late. Heredoc in function, backgrounded call to function.
        This check can fail on slow machines (<100 MHz), or Cygwin,
        that's normal.
+need-pass: no
 stdin:
        TMPDIR=$PWD
        # Background eval so main shell doesn't do parsing
@@ -2215,6 +2564,7 @@ expected-stdout:
 name: history-basic
 description:
        See if we can test history at all
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2231,6 +2581,7 @@ expected-stderr-pattern:
 name: history-dups
 description:
        Verify duplicates and spaces are not entered
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2251,6 +2602,7 @@ expected-stderr-pattern:
 name: history-unlink
 description:
        Check if broken HISTFILEs do not cause trouble
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=foo/hist.file!
 file-setup: file 644 "Env"
@@ -2268,11 +2620,12 @@ expected-stdout:
        hi
        1       echo hi
 expected-stderr-pattern:
-       /(.*cannot unlink HISTFILE.*\n)?X*$/
+       /(.*can't unlink HISTFILE.*\n)?X*$/
 ---
 name: history-e-minus-1
 description:
        Check if more recent command is executed
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2292,6 +2645,7 @@ name: history-e-minus-2
 description:
        Check that repeated command is printed before command
        is re-executed.
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2311,6 +2665,7 @@ description:
        fc -e - fails when there is no history
        (ksh93 has a bug that causes this to fail)
        (ksh88 loops on this)
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2326,6 +2681,7 @@ expected-stderr-pattern:
 name: history-e-minus-4
 description:
        Check if "fc -e -" command output goes to stdout.
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2344,6 +2700,7 @@ expected-stderr-pattern:
 name: history-e-minus-5
 description:
        fc is replaced in history by new command.
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2369,6 +2726,7 @@ name: history-list-1
 description:
        List lists correct range
        (ksh88 fails 'cause it lists the fc command)
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2392,6 +2750,7 @@ description:
        Lists oldest history if given pre-historic number
        (ksh93 has a bug that causes this to fail)
        (ksh88 fails 'cause it lists the fc command)
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2414,6 +2773,7 @@ expected-stderr-pattern:
 name: history-list-3
 description:
        Can give number 'options' to fc
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2437,6 +2797,7 @@ expected-stderr-pattern:
 name: history-list-4
 description:
        -1 refers to previous command
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2459,6 +2820,7 @@ expected-stderr-pattern:
 name: history-list-5
 description:
        List command stays in history
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2485,6 +2847,7 @@ name: history-list-6
 description:
        HISTSIZE limits about of history kept.
        (ksh88 fails 'cause it lists the fc command)
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!HISTSIZE=3!
 file-setup: file 644 "Env"
@@ -2510,6 +2873,7 @@ expected-stderr-pattern:
 name: history-list-7
 description:
        fc allows too old/new errors in range specification
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!HISTSIZE=3!
 file-setup: file 644 "Env"
@@ -2536,6 +2900,7 @@ expected-stderr-pattern:
 name: history-list-r-1
 description:
        test -r flag in history
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2562,6 +2927,7 @@ expected-stderr-pattern:
 name: history-list-r-2
 description:
        If first is newer than last, -r is implied.
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2588,6 +2954,7 @@ expected-stderr-pattern:
 name: history-list-r-3
 description:
        If first is newer than last, -r is cancelled.
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2614,6 +2981,7 @@ expected-stderr-pattern:
 name: history-subst-1
 description:
        Basic substitution
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2632,6 +3000,7 @@ expected-stderr-pattern:
 name: history-subst-2
 description:
        Does subst find previous command?
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2650,6 +3019,7 @@ expected-stderr-pattern:
 name: history-subst-3
 description:
        Does subst find previous command when no arguments given
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2669,6 +3039,7 @@ name: history-subst-4
 description:
        Global substitutions work
        (ksh88 and ksh93 do not have -g option)
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2686,6 +3057,7 @@ name: history-subst-5
 description:
        Make sure searches don't find current (fc) command
        (ksh88/ksh93 don't have the ? prefix thing so they fail this test)
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2707,6 +3079,8 @@ description:
        that prints no prompts). This is for oldish ed(1) which write
        the character count to stdout.
 category: stdout-ed
+need-ctty: yes
+need-pass: no
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2729,6 +3103,8 @@ name: history-ed-2-old
 description:
        Correct command is edited when number given
 category: stdout-ed
+need-ctty: yes
+need-pass: no
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2760,6 +3136,8 @@ description:
        (NOTE: adjusted for COMPLEX HISTORY compile time option)
        (ksh88 fails 'cause it lists the fc command)
 category: stdout-ed
+need-ctty: yes
+need-pass: no
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2791,6 +3169,8 @@ description:
        Basic (ed) editing works (assumes you have generic ed editor
        that prints no prompts). This is for newish ed(1) and stderr.
 category: !no-stderr-ed
+need-ctty: yes
+need-pass: no
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2811,6 +3191,8 @@ name: history-ed-2
 description:
        Correct command is edited when number given
 category: !no-stderr-ed
+need-ctty: yes
+need-pass: no
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2838,6 +3220,8 @@ description:
        Newly created multi line commands show up as single command
        in history.
 category: !no-stderr-ed
+need-ctty: yes
+need-pass: no
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -2943,23 +3327,6 @@ stdin:
 expected-stdout:
         <:>
 ---
-name: IFS-space-colon-3
-description:
-       Simple test, IFS=<white-space>:
-       pdksh fails both of these tests
-       not sure whether #2 is correct
-stdin:
-       showargs() { for i; do echo -n " <$i>"; done; echo; }
-       IFS="$IFS:"
-       x=
-       set --
-       showargs "$x$@" 1
-       showargs "$@$x" 2
-expected-fail: yes
-expected-stdout:
-        <> <1>
-        <> <2>
----
 name: IFS-space-colon-4
 description:
        Simple test, IFS=<white-space>:
@@ -3051,6 +3418,7 @@ description:
        Syntax errors in expressions and effects on bases
        (interactive so errors don't cause exits)
        (ksh88 fails this test - shell exits, even with -i)
+need-ctty: yes
 arguments: !-i!
 stdin:
        PS1= # minimise prompt hassles
@@ -3346,6 +3714,26 @@ expected-stdout:
        line <6>
 expected-exit: 1
 ---
+name: unknown-trap
+description:
+       Ensure unknown traps are not a syntax error
+stdin:
+       (
+       trap "echo trap 1 executed" UNKNOWNSIGNAL || echo "foo"
+       echo =1
+       trap "echo trap 2 executed" UNKNOWNSIGNAL EXIT 999999 FNORD
+       echo = $?
+       ) 2>&1 | sed "s\ 1^${__progname}: <stdin>\[[0-9]*]\ 1PROG\ 1"
+expected-stdout:
+       PROG: trap: bad signal 'UNKNOWNSIGNAL'
+       foo
+       =1
+       PROG: trap: bad signal 'UNKNOWNSIGNAL'
+       PROG: trap: bad signal '999999'
+       PROG: trap: bad signal 'FNORD'
+       = 3
+       trap 2 executed
+---
 name: read-IFS-1
 description:
        Simple test, default IFS
@@ -3372,6 +3760,74 @@ stdin:
 expected-stdout:
        [abc]
 ---
+name: read-regress-1
+description:
+       Check a regression of read
+file-setup: file 644 "foo"
+       foo bar
+       baz
+       blah
+stdin:
+       while read a b c; do
+               read d
+               break
+       done <foo
+       echo "<$a|$b|$c><$d>"
+expected-stdout:
+       <foo|bar|><baz>
+---
+name: read-delim-1
+description:
+       Check read with delimiters
+stdin:
+       emit() {
+               printf 'foo bar\tbaz\nblah \0blub\tblech\nmyok meck \0'
+       }
+       emit | while IFS= read -d "" foo; do print -r -- "<$foo>"; done
+       emit | while read -d "" foo; do print -r -- "<$foo>"; done
+       emit | while read -d "eh?" foo; do print -r -- "<$foo>"; done
+expected-stdout:
+       <foo bar        baz
+       blah >
+       <blub   blech
+       myok meck >
+       <foo bar        baz
+       blah>
+       <blub   blech
+       myok meck>
+       <foo bar        baz
+       blah blub       bl>
+       <ch
+       myok m>
+---
+name: read-ext-1
+description:
+       Check read with number of bytes specified, and -A
+stdin:
+       print 'foo\nbar' >x1
+       print -n x >x2
+       print 'foo\\ bar baz' >x3
+       x1a=u; read x1a <x1
+       x1b=u; read -N-1 x1b <x1
+       x2a=u; read x2a <x2; r2a=$?
+       x2b=u; read -N2 x2c <x2; r2b=$?
+       x2c=u; read -n2 x2c <x2; r2c=$?
+       x3a=u; read -A x3a <x3
+       print -r "x1a=<$x1a>"
+       print -r "x1b=<$x1b>"
+       print -r "x2a=$r2a<$x2a>"
+       print -r "x2b=$r2b<$x2b>"
+       print -r "x2c=$r2c<$x2c>"
+       print -r "x3a=<${x3a[0]}|${x3a[1]}|${x3a[2]}>"
+expected-stdout:
+       x1a=<foo>
+       x1b=<foo
+       bar>
+       x2a=1<x>
+       x2b=1<u>
+       x2c=0<x>
+       x3a=<foo bar|baz|>
+---
 name: regression-1
 description:
        Lex array code had problems with this.
@@ -3782,6 +4238,9 @@ name: regression-33
 description:
        Does umask print a leading 0 when umask is 3 digits?
 stdin:
+       # on MiNT, the first umask call seems to fail
+       umask 022
+       # now, the test proper
        umask 222
        umask
 expected-stdout:
@@ -4006,6 +4465,7 @@ file-setup: file 644 "env"
        PS1=Y
        PS2=X
 env-setup: !ENV=./env!
+need-ctty: yes
 arguments: !-i!
 stdin:
        alias foo='echo hi ; '
@@ -4036,6 +4496,7 @@ file-setup: file 644 "env"
 file-setup: file 644 "abc"
        stuff
 env-setup: !ENV=./env!
+need-ctty: yes
 arguments: !-i!
 stdin:
        sed 's/^/X /' < ab*
@@ -4369,6 +4830,78 @@ stdin:
                time
        }
 ---
+name: regression-65
+description:
+       check for a regression with sleep builtin and signal mask
+category: !nojsig
+time-limit: 3
+stdin:
+       sleep 1
+       echo blub |&
+       while read -p line; do :; done
+       echo ok
+expected-stdout:
+       ok
+---
+name: readonly-0
+description:
+       Ensure readonly is honoured for assignments and unset
+stdin:
+       "$__progname" -c 'u=x; echo $? $u .' || echo aborted, $?
+       echo =
+       "$__progname" -c 'readonly u; u=x; echo $? $u .' || echo aborted, $?
+       echo =
+       "$__progname" -c 'u=x; readonly u; unset u; echo $? $u .' || echo aborted, $?
+expected-stdout:
+       0 x .
+       =
+       aborted, 2
+       =
+       1 x .
+expected-stderr-pattern:
+       /read *only/
+---
+name: readonly-1
+description:
+       http://austingroupbugs.net/view.php?id=367 for export
+stdin:
+       "$__progname" -c 'readonly foo; export foo=a; echo $?' || echo aborted, $?
+expected-stdout:
+       aborted, 2
+expected-stderr-pattern:
+       /read *only/
+---
+name: readonly-2a
+description:
+       Check that getopts works as intended, for readonly-2b to be valid
+stdin:
+       "$__progname" -c 'set -- -a b; getopts a c; echo $? $c .; getopts a c; echo $? $c .' || echo aborted, $?
+expected-stdout:
+       0 a .
+       1 ? .
+---
+name: readonly-2b
+description:
+       http://austingroupbugs.net/view.php?id=367 for getopts
+stdin:
+       "$__progname" -c 'readonly c; set -- -a b; getopts a c; echo $? $c .' || echo aborted, $?
+expected-stdout:
+       2 .
+expected-stderr-pattern:
+       /read *only/
+---
+name: readonly-3
+description:
+       http://austingroupbugs.net/view.php?id=367 for read
+stdin:
+       echo x | "$__progname" -c 'read s; echo $? $s .' || echo aborted, $?
+       echo y | "$__progname" -c 'readonly s; read s; echo $? $s .' || echo aborted, $?
+expected-stdout:
+       0 x .
+       2 .
+expected-stderr-pattern:
+       /read *only/
+---
 name: syntax-1
 description:
        Check that lone ampersand is a syntax error
@@ -4567,6 +5100,7 @@ expected-stdout:
 name: xxx-exec-1
 description:
        Check that exec exits for built-ins
+need-ctty: yes
 arguments: !-i!
 stdin:
        exec echo hi
@@ -4614,6 +5148,7 @@ expected-stdout:
 name: xxx-status-1
 description:
        Check that blank lines don't clear $?
+need-ctty: yes
 arguments: !-i!
 stdin:
        (exit 1)
@@ -4702,12 +5237,12 @@ description:
        Check some "exit on error" conditions
 stdin:
        set -ex
-       /usr/bin/env false && echo something
+       env false && echo something
        echo END
 expected-stdout:
        END
 expected-stderr:
-       + /usr/bin/env false
+       + env false
        + echo END
 ---
 name: exit-err-2
@@ -4715,15 +5250,15 @@ description:
        Check some "exit on error" edge conditions (POSIXly)
 stdin:
        set -ex
-       if /usr/bin/env true; then
-               /usr/bin/env false && echo something
+       if env true; then
+               env false && echo something
        fi
        echo END
 expected-stdout:
        END
 expected-stderr:
-       + /usr/bin/env true
-       + /usr/bin/env false
+       + env true
+       + env false
        + echo END
 ---
 name: exit-err-3
@@ -4845,6 +5380,16 @@ expected-stdout:
        E 0
        F 0
 ---
+name: exit-trap-1
+description:
+       Check that "exit" with no arguments behaves SUSv4 conformant.
+stdin:
+       trap 'echo hi; exit' EXIT
+       exit 9
+expected-stdout:
+       hi
+expected-exit: 9
+---
 name: test-stlt-1
 description:
        Check that test also can handle string1 < string2 etc.
@@ -4981,6 +5526,7 @@ description:
        Part 2: verify mkshrc can be read (interactive shells)
 file-setup: file 644 ".mkshrc"
        FNORD=42
+need-ctty: yes
 arguments: !-i!
 env-setup: !HOME=.!ENV=!PS1=!
 stdin:
@@ -5104,6 +5650,7 @@ expected-stdout:
 name: pipeline-2
 description:
        check that co-processes work with TCOMs, TPIPEs and TPARENs
+category: !nojsig
 stdin:
        "$__progname" -c 'i=100; echo hi |& while read -p line; do echo "$((i++)) $line"; done'
        "$__progname" -c 'i=200; echo hi | cat |& while read -p line; do echo "$((i++)) $line"; done'
@@ -5113,10 +5660,33 @@ expected-stdout:
        200 hi
        300 hi
 ---
+name: pipeline-3
+description:
+       Check that PIPESTATUS does what it's supposed to
+stdin:
+       echo 1 $PIPESTATUS .
+       echo 2 ${PIPESTATUS[0]} .
+       echo 3 ${PIPESTATUS[1]} .
+       (echo x; exit 12) | (cat; exit 23) | (cat; exit 42)
+       echo 5 $? , $PIPESTATUS , ${PIPESTATUS[0]} , ${PIPESTATUS[1]} , ${PIPESTATUS[2]} , ${PIPESTATUS[3]} .
+       echo 6 ${PIPESTATUS[0]} .
+       set | fgrep PIPESTATUS
+       echo 8 $(set | fgrep PIPESTATUS) .
+expected-stdout:
+       1 0 .
+       2 0 .
+       3 .
+       x
+       5 42 , 12 , 12 , 23 , 42 , .
+       6 0 .
+       PIPESTATUS[0]=0
+       8 PIPESTATUS[0]=0 PIPESTATUS[1]=0 .
+---
 name: persist-history-1
 description:
        Check if persistent history saving works
 category: !no-histfile
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!HISTFILE=hist.file!
 file-setup: file 644 "Env"
@@ -5128,6 +5698,40 @@ expected-stdout-pattern:
 expected-stderr-pattern:
        /^X*$/
 ---
+name: typeset-1
+description:
+       Check that global does what typeset is supposed to do
+stdin:
+       set -A arrfoo 65
+       foo() {
+               global -Uui16 arrfoo[*]
+       }
+       echo before ${arrfoo[0]} .
+       foo
+       echo after ${arrfoo[0]} .
+       set -A arrbar 65
+       bar() {
+               echo inside before ${arrbar[0]} .
+               arrbar[0]=97
+               echo inside changed ${arrbar[0]} .
+               global -Uui16 arrbar[*]
+               echo inside typeset ${arrbar[0]} .
+               arrbar[0]=48
+               echo inside changed ${arrbar[0]} .
+       }
+       echo before ${arrbar[0]} .
+       bar
+       echo after ${arrbar[0]} .
+expected-stdout:
+       before 65 .
+       after 16#41 .
+       before 65 .
+       inside before 65 .
+       inside changed 97 .
+       inside typeset 16#61 .
+       inside changed 16#30 .
+       after 16#30 .
+---
 name: typeset-padding-1
 description:
        Check if left/right justification works as per TFM
@@ -5208,7 +5812,7 @@ expected-stdout:
        mit
        ohne
        =
-       : mit
+       : ohne
 ---
 name: utf8bom-2
 description:
@@ -5216,6 +5820,7 @@ description:
        XXX if the OS can already execute them, we lose
        note: cygwin execve(2) doesn't return to us with ENOEXEC, we lose
        note: Ultrix perl5 t4 returns 65280 (exit-code 255) and no text
+need-pass: no
 category: !os:cygwin,!os:uwin-nt,!os:ultrix,!smksh
 env-setup: !FOO=BAR!
 stdin:
@@ -5239,12 +5844,17 @@ expected-stderr-pattern:
 name: utf8bom-3
 description:
        Reading the UTF-8 BOM should enable the utf8-mode flag
+       (temporarily for COMSUBs)
 stdin:
        "$__progname" -c ':; if [[ $- = *U* ]]; then echo 1 on; else echo 1 off; fi'
        "$__progname" -c ':; if [[ $- = *U* ]]; then echo 2 on; else echo 2 off; fi'
+       "$__progname" -c 'if [[ $- = *U* ]]; then echo 3 on; else echo 3 off; fi; x=$(:; if [[ $- = *U* ]]; then echo 4 on; else echo 4 off; fi); echo $x; if [[ $- = *U* ]]; then echo 5 on; else echo 5 off; fi'
 expected-stdout:
        1 off
        2 on
+       3 off
+       4 on
+       5 off
 ---
 name: utf8opt-1a
 description:
@@ -5281,7 +5891,9 @@ description:
        -DMKSH_ASSUME_UTF8=1 => not expected, please investigate
        -UMKSH_ASSUME_UTF8 => not expected, but if your OS is old,
         try passing HAVE_SETLOCALE_CTYPE=0 to Build.sh
+need-pass: no
 category: !os:hpux
+need-ctty: yes
 arguments: !-i!
 env-setup: !PS1=!PS2=!LC_CTYPE=en_US.UTF-8!
 stdin:
@@ -5300,6 +5912,7 @@ description:
        Check that the utf8-mode flag is set at interactive startup
        Expected failure if -DMKSH_ASSUME_UTF8=0
 category: os:hpux
+need-ctty: yes
 arguments: !-i!
 env-setup: !PS1=!PS2=!LC_CTYPE=en_US.utf8!
 stdin:
@@ -5313,29 +5926,40 @@ expected-stdout:
 expected-stderr-pattern:
        /(# )*/
 ---
-name: utf8opt-3
+name: utf8opt-3a
 description:
        Ensure ±U on the command line is honoured
-       (this test may pass falsely depending on CPPFLAGS)
+       (these two tests may pass falsely depending on CPPFLAGS)
 stdin:
        export i=0
        code='if [[ $- = *U* ]]; then echo $i on; else echo $i off; fi'
        let i++; "$__progname" -U -c "$code"
        let i++; "$__progname" +U -c "$code"
+       echo $((++i)) done
+expected-stdout:
+       1 on
+       2 off
+       3 done
+---
+name: utf8opt-3b
+description:
+       Ensure ±U on the command line is honoured, interactive shells
+need-ctty: yes
+stdin:
+       export i=0
+       code='if [[ $- = *U* ]]; then echo $i on; else echo $i off; fi'
        let i++; "$__progname" -U -ic "$code"
        let i++; "$__progname" +U -ic "$code"
        echo $((++i)) done
 expected-stdout:
        1 on
        2 off
-       3 on
-       4 off
-       5 done
+       3 done
 ---
 name: aliases-1
 description:
        Check if built-in shell aliases are okay
-category: !arge
+category: !android,!arge
 stdin:
        alias
        typeset -f
@@ -5351,13 +5975,14 @@ expected-stdout:
        nohup='nohup '
        r='fc -e -'
        source='PATH=$PATH:. command .'
+       stop='kill -STOP'
        suspend='kill -STOP $$'
        type='whence -v'
 ---
 name: aliases-1-hartz4
 description:
        Check if built-in shell aliases are okay
-category: arge
+category: android,arge
 stdin:
        alias
        typeset -f
@@ -5391,7 +6016,6 @@ name: aliases-3a
 description:
        Check if running as sh disables built-in aliases (except a few)
 category: disabled
-arguments: !-o!sh!
 stdin:
        cp "$__progname" sh
        ./sh -c 'alias; typeset -f'
@@ -5403,7 +6027,7 @@ expected-stdout:
 name: aliases-2b
 description:
        Check if “set -o sh” does not influence built-in aliases
-category: !arge
+category: !android,!arge
 arguments: !-o!sh!
 stdin:
        alias
@@ -5420,14 +6044,14 @@ expected-stdout:
        nohup='nohup '
        r='fc -e -'
        source='PATH=$PATH:. command .'
+       stop='kill -STOP'
        suspend='kill -STOP $$'
        type='whence -v'
 ---
 name: aliases-3b
 description:
        Check if running as sh does not influence built-in aliases
-category: !arge
-arguments: !-o!sh!
+category: !android,!arge
 stdin:
        cp "$__progname" sh
        ./sh -c 'alias; typeset -f'
@@ -5444,13 +6068,14 @@ expected-stdout:
        nohup='nohup '
        r='fc -e -'
        source='PATH=$PATH:. command .'
+       stop='kill -STOP'
        suspend='kill -STOP $$'
        type='whence -v'
 ---
 name: aliases-2b-hartz4
 description:
        Check if “set -o sh” does not influence built-in aliases
-category: arge
+category: android,arge
 arguments: !-o!sh!
 stdin:
        alias
@@ -5472,8 +6097,7 @@ expected-stdout:
 name: aliases-3b-hartz4
 description:
        Check if running as sh does not influence built-in aliases
-category: arge
-arguments: !-o!sh!
+category: android,arge
 stdin:
        cp "$__progname" sh
        ./sh -c 'alias; typeset -f'
@@ -5528,6 +6152,17 @@ stdin:
 expected-stdout:
        makro
 ---
+name: aliases-funcdef-4
+description:
+       Functions should only take over if actually being defined
+stdin:
+       alias local
+       :|| local() { :; }
+       alias local
+expected-stdout:
+       local=typeset
+       local=typeset
+---
 name: arrays-1
 description:
        Check if Korn Shell arrays work as expected
@@ -5538,7 +6173,7 @@ stdin:
 expected-stdout:
        5|a|$v|c d|$v|b|
 ---
-name: arrays-2
+name: arrays-2a
 description:
        Check if bash-style arrays work as expected
 category: !smksh
@@ -5549,6 +6184,33 @@ stdin:
 expected-stdout:
        5|a|$v|c d|$v|b|
 ---
+name: arrays-2b
+description:
+       Check if bash-style arrays work as expected, with newlines
+category: !smksh
+stdin:
+       test -n "$ZSH_VERSION" && setopt KSH_ARRAYS
+       v="e f"
+       foo=(a
+               bc
+               d \$v "$v" '$v' g
+       )
+       printf '%s|' "${#foo[*]}" "${foo[0]}" "${foo[1]}" "${foo[2]}" "${foo[3]}" "${foo[4]}" "${foo[5]}" "${foo[6]}"; echo
+       foo=(a\
+               bc
+               d \$v "$v" '$v' g
+       )
+       printf '%s|' "${#foo[*]}" "${foo[0]}" "${foo[1]}" "${foo[2]}" "${foo[3]}" "${foo[4]}" "${foo[5]}" "${foo[6]}"; echo
+       foo=(a\
+       bc\\
+               d \$v "$v" '$v'
+       g)
+       printf '%s|' "${#foo[*]}" "${foo[0]}" "${foo[1]}" "${foo[2]}" "${foo[3]}" "${foo[4]}" "${foo[5]}" "${foo[6]}"; echo
+expected-stdout:
+       7|a|bc|d|$v|e f|$v|g|
+       7|a|bc|d|$v|e f|$v|g|
+       6|abc\|d|$v|e f|$v|g||
+---
 name: arrays-3
 description:
        Check if array bounds are uint32_t
@@ -5743,6 +6405,29 @@ expected-stdout:
        2g 009 .
        2h 00001 00002 .
 ---
+name: arrays-9a
+description:
+       Check that we can concatenate arrays
+category: !smksh
+stdin:
+       unset foo; foo=(bar); foo+=(baz); echo 1 ${!foo[*]} : ${foo[*]} .
+       unset foo; foo=(foo bar); foo+=(baz); echo 2 ${!foo[*]} : ${foo[*]} .
+       unset foo; foo=([2]=foo [0]=bar); foo+=(baz [5]=quux); echo 3 ${!foo[*]} : ${foo[*]} .
+expected-stdout:
+       1 0 1 : bar baz .
+       2 0 1 2 : foo bar baz .
+       3 0 2 3 5 : bar foo baz quux .
+---
+name: arrays-9b
+description:
+       Check that we can concatenate parameters too
+stdin:
+       unset foo; foo=bar; foo+=baz; echo 1 $foo .
+       unset foo; typeset -i16 foo=10; foo+=20; echo 2 $foo .
+expected-stdout:
+       1 barbaz .
+       2 16#a20 .
+---
 name: varexpand-substr-1
 description:
        Check if bash-style substring expansion works
@@ -5865,6 +6550,17 @@ expected-stdout:
        c we
        d we
 ---
+name: varexpand-special-hash
+description:
+       Check special ${var@x} expansion for x=hash
+stdin:
+       typeset -i8 foo=10
+       bar=baz
+       unset baz
+       print ${foo@#} ${bar@#} ${baz@#} .
+expected-stdout:
+       D50219A0 20E5DB5B 00000001 .
+---
 name: varexpand-null-1
 description:
        Ensure empty strings expand emptily
@@ -5924,8 +6620,9 @@ stdin:
            '\U\V\W\X\Y\Z\[\\\]\^\_\`\a\b  \d\e\f\g\h\i\j\k\l\m\n\o\p' \
            '\q\r\s\t\u\v\w\x\y\z\{\|\}\~' '\u20acd' '\U20acd' '\x123' \
            '\0x' '\0123' '\01234' | {
+               # integer-base-one-3As
                typeset -Uui16 -Z11 pos=0
-               typeset -Uui16 -Z5 hv
+               typeset -Uui16 -Z5 hv=2147483647
                typeset -i1 wc=0x0A
                dasc=
                nl=${wc#1#}
@@ -5948,13 +6645,11 @@ stdin:
                                line=${line:1}
                        done
                done
-               if (( (pos & 15) != 1 )); then
-                       while (( pos & 15 )); do
-                               print -n '   '
-                               (( (pos++ & 15) == 7 )) && print -n -- '- '
-                       done
-                       print "$dasc|"
-               fi
+               while (( pos & 15 )); do
+                       print -n '   '
+                       (( (pos++ & 15) == 7 )) && print -n -- '- '
+               done
+               (( hv == 2147483647 )) || print "$dasc|"
        }
 expected-stdout:
        00000000  5C 20 5C 21 5C 22 5C 23 - 5C 24 5C 25 5C 26 5C 27  |\ \!\"\#\$\%\&\'|
@@ -5971,6 +6666,22 @@ expected-stdout:
        000000B0  E2 82 AC 64 20 EF BF BD - 20 12 33 20 78 20 53 20  |...d ... .3 x S |
        000000C0  53 34 0A                -                          |S4.|
 ---
+name: dollar-doublequoted-strings
+description:
+       Check that a $ preceding "…" is ignored
+stdin:
+       echo $"Localise me!"
+       cat <<<$"Me too!"
+       V=X
+       aol=aol
+       cat <<-$"aol"
+               I do not take a $V for a V!
+       aol
+expected-stdout:
+       Localise me!
+       Me too!
+       I do not take a $V for a V!
+---
 name: dollar-quoted-strings
 description:
        Check backslash expansion by $'…' strings
@@ -5982,8 +6693,9 @@ stdin:
            $'\u20acd' $'\U20acd' $'\x123' $'fn\x0rd' $'\0234' $'\234' \
            $'\2345' $'\ca' $'\c!' $'\c?' $'\c€' $'a\
        b' | {
+               # integer-base-one-3As
                typeset -Uui16 -Z11 pos=0
-               typeset -Uui16 -Z5 hv
+               typeset -Uui16 -Z5 hv=2147483647
                typeset -i1 wc=0x0A
                dasc=
                nl=${wc#1#}
@@ -6006,13 +6718,11 @@ stdin:
                                line=${line:1}
                        done
                done
-               if (( (pos & 15) != 1 )); then
-                       while (( pos & 15 )); do
-                               print -n '   '
-                               (( (pos++ & 15) == 7 )) && print -n -- '- '
-                       done
-                       print "$dasc|"
-               fi
+               while (( pos & 15 )); do
+                       print -n '   '
+                       (( (pos++ & 15) == 7 )) && print -n -- '- '
+               done
+               (( hv == 2147483647 )) || print "$dasc|"
        }
 expected-stdout:
        00000000  20 21 22 23 24 25 26 27 - 28 29 2A 2B 2C 2D 2E 2F  | !"#$%&'()*+,-./|
@@ -6249,9 +6959,10 @@ expected-stderr-pattern:
        /1#à\80\80: unexpected '\80'/
 expected-exit: e != 0
 ---
-name: integer-base-one-3A
+name: integer-base-one-3As
 description:
        some sample code for hexdumping
+       not NUL safe; input lines must be NL terminated
 stdin:
        {
                print 'Hello, World!\\\nこんにちは!'
@@ -6261,10 +6972,11 @@ stdin:
                while (( i++ < 0x1FF )); do
                        print -n "\x${i#16#1}"
                done
-               print
+               print '\0z'
        } | {
+               # integer-base-one-3As
                typeset -Uui16 -Z11 pos=0
-               typeset -Uui16 -Z5 hv
+               typeset -Uui16 -Z5 hv=2147483647
                typeset -i1 wc=0x0A
                dasc=
                nl=${wc#1#}
@@ -6287,13 +6999,11 @@ stdin:
                                line=${line:1}
                        done
                done
-               if (( (pos & 15) != 1 )); then
-                       while (( pos & 15 )); do
-                               print -n '   '
-                               (( (pos++ & 15) == 7 )) && print -n -- '- '
-                       done
-                       print "$dasc|"
-               fi
+               while (( pos & 15 )); do
+                       print -n '   '
+                       (( (pos++ & 15) == 7 )) && print -n -- '- '
+               done
+               (( hv == 2147483647 )) || print "$dasc|"
        }
 expected-stdout:
        00000000  48 65 6C 6C 6F 2C 20 57 - 6F 72 6C 64 21 5C 0A E3  |Hello, World!\..|
@@ -6314,11 +7024,12 @@ expected-stdout:
        000000F0  CF D0 D1 D2 D3 D4 D5 D6 - D7 D8 D9 DA DB DC DD DE  |................|
        00000100  DF E0 E1 E2 E3 E4 E5 E6 - E7 E8 E9 EA EB EC ED EE  |................|
        00000110  EF F0 F1 F2 F3 F4 F5 F6 - F7 F8 F9 FA FB FC FD FE  |................|
-       00000120  FF 0A                   -                          |..|
+       00000120  FF 7A 0A                -                          |.z.|
 ---
-name: integer-base-one-3W
+name: integer-base-one-3Ws
 description:
        some sample code for hexdumping Unicode
+       not NUL safe; input lines must be NL terminated
 stdin:
        set -U
        {
@@ -6336,7 +7047,9 @@ stdin:
                print \\xc0\\x80        # non-minimalistic
                print \\xe0\\x80\\x80   # non-minimalistic
                print '�￾￿'       # end of range
+               print '\0z'             # embedded NUL
        } | {
+               # integer-base-one-3Ws
                typeset -Uui16 -Z11 pos=0
                typeset -Uui16 -Z7 hv
                typeset -i1 wc=0x0A
@@ -6371,13 +7084,174 @@ stdin:
                                dasc=$dasc$dch
                        done
                done
-               if (( pos & 7 )); then
-                       while (( pos & 7 )); do
-                               print -n '     '
-                               (( (pos++ & 7) == 3 )) && print -n -- '- '
+               while (( pos & 7 )); do
+                       print -n '     '
+                       (( (pos++ & 7) == 3 )) && print -n -- '- '
+               done
+               (( hv == 2147483647 )) || print "$dasc|"
+       }
+expected-stdout:
+       00000000  0048 0065 006C 006C - 006F 002C 0020 0057  |Hello, W|
+       00000008  006F 0072 006C 0064 - 0021 005C 000A 3053  |orld!\.こ|
+       00000010  3093 306B 3061 306F - FF01 000A 0001 0002  |んにちは!...|
+       00000018  0003 0004 0005 0006 - 0007 0008 0009 000A  |........|
+       00000020  000B 000C 000D 000E - 000F 0010 0011 0012  |........|
+       00000028  0013 0014 0015 0016 - 0017 0018 0019 001A  |........|
+       00000030  001B 001C 001D 001E - 001F 0020 0021 0022  |..... !"|
+       00000038  0023 0024 0025 0026 - 0027 0028 0029 002A  |#$%&'()*|
+       00000040  002B 002C 002D 002E - 002F 0030 0031 0032  |+,-./012|
+       00000048  0033 0034 0035 0036 - 0037 0038 0039 003A  |3456789:|
+       00000050  003B 003C 003D 003E - 003F 0040 0041 0042  |;<=>?@AB|
+       00000058  0043 0044 0045 0046 - 0047 0048 0049 004A  |CDEFGHIJ|
+       00000060  004B 004C 004D 004E - 004F 0050 0051 0052  |KLMNOPQR|
+       00000068  0053 0054 0055 0056 - 0057 0058 0059 005A  |STUVWXYZ|
+       00000070  005B 005C 005D 005E - 005F 0060 0061 0062  |[\]^_`ab|
+       00000078  0063 0064 0065 0066 - 0067 0068 0069 006A  |cdefghij|
+       00000080  006B 006C 006D 006E - 006F 0070 0071 0072  |klmnopqr|
+       00000088  0073 0074 0075 0076 - 0077 0078 0079 007A  |stuvwxyz|
+       00000090  007B 007C 007D 007E - 007F 0080 0081 0082  |{|}~....|
+       00000098  0083 0084 0085 0086 - 0087 0088 0089 008A  |........|
+       000000A0  008B 008C 008D 008E - 008F 0090 0091 0092  |........|
+       000000A8  0093 0094 0095 0096 - 0097 0098 0099 009A  |........|
+       000000B0  009B 009C 009D 009E - 009F 00A0 00A1 00A2  |..... ¡¢|
+       000000B8  00A3 00A4 00A5 00A6 - 00A7 00A8 00A9 00AA  |£¤¥¦§¨©ª|
+       000000C0  00AB 00AC 00AD 00AE - 00AF 00B0 00B1 00B2  |«¬­®¯°±²|
+       000000C8  00B3 00B4 00B5 00B6 - 00B7 00B8 00B9 00BA  |³´µ¶·¸¹º|
+       000000D0  00BB 00BC 00BD 00BE - 00BF 00C0 00C1 00C2  |»¼½¾¿ÀÁÂ|
+       000000D8  00C3 00C4 00C5 00C6 - 00C7 00C8 00C9 00CA  |ÃÄÅÆÇÈÉÊ|
+       000000E0  00CB 00CC 00CD 00CE - 00CF 00D0 00D1 00D2  |ËÌÍÎÏÐÑÒ|
+       000000E8  00D3 00D4 00D5 00D6 - 00D7 00D8 00D9 00DA  |ÓÔÕÖ×ØÙÚ|
+       000000F0  00DB 00DC 00DD 00DE - 00DF 00E0 00E1 00E2  |ÛÜÝÞßàáâ|
+       000000F8  00E3 00E4 00E5 00E6 - 00E7 00E8 00E9 00EA  |ãäåæçèéê|
+       00000100  00EB 00EC 00ED 00EE - 00EF 00F0 00F1 00F2  |ëìíîïðñò|
+       00000108  00F3 00F4 00F5 00F6 - 00F7 00F8 00F9 00FA  |óôõö÷øùú|
+       00000110  00FB 00FC 00FD 00FE - 00FF 000A EFFF 000A  |ûüýþÿ.�.|
+       00000118  EFC2 000A EFEF EFBF - EFC0 000A EFC0 EF80  |�.���.��|
+       00000120  000A EFE0 EF80 EF80 - 000A FFFD EFEF EFBF  |.���.���|
+       00000128  EFBE EFEF EFBF EFBF - 000A 007A 000A       |����.z.|
+---
+name: integer-base-one-3Ar
+description:
+       some sample code for hexdumping; NUL and binary safe
+stdin:
+       {
+               print 'Hello, World!\\\nこんにちは!'
+               typeset -Uui16 i=0x100
+               # change that to 0xFF once we can handle embedded
+               # NUL characters in strings / here documents
+               while (( i++ < 0x1FF )); do
+                       print -n "\x${i#16#1}"
+               done
+               print '\0z'
+       } | {
+               # integer-base-one-3Ar
+               typeset -Uui16 -Z11 pos=0
+               typeset -Uui16 -Z5 hv=2147483647
+               dasc=
+               if read -arN -1 line; then
+                       typeset -i1 line
+                       i=0
+                       while (( i < ${#line[*]} )); do
+                               hv=${line[i++]}
+                               if (( (pos & 15) == 0 )); then
+                                       (( pos )) && print "$dasc|"
+                                       print -n "${pos#16#}  "
+                                       dasc=' |'
+                               fi
+                               print -n "${hv#16#} "
+                               if (( (hv < 32) || (hv > 126) )); then
+                                       dasc=$dasc.
+                               else
+                                       dasc=$dasc${line[i-1]#1#}
+                               fi
+                               (( (pos++ & 15) == 7 )) && print -n -- '- '
+                       done
+               fi
+               while (( pos & 15 )); do
+                       print -n '   '
+                       (( (pos++ & 15) == 7 )) && print -n -- '- '
+               done
+               (( hv == 2147483647 )) || print "$dasc|"
+       }
+expected-stdout:
+       00000000  48 65 6C 6C 6F 2C 20 57 - 6F 72 6C 64 21 5C 0A E3  |Hello, World!\..|
+       00000010  81 93 E3 82 93 E3 81 AB - E3 81 A1 E3 81 AF EF BC  |................|
+       00000020  81 0A 01 02 03 04 05 06 - 07 08 09 0A 0B 0C 0D 0E  |................|
+       00000030  0F 10 11 12 13 14 15 16 - 17 18 19 1A 1B 1C 1D 1E  |................|
+       00000040  1F 20 21 22 23 24 25 26 - 27 28 29 2A 2B 2C 2D 2E  |. !"#$%&'()*+,-.|
+       00000050  2F 30 31 32 33 34 35 36 - 37 38 39 3A 3B 3C 3D 3E  |/0123456789:;<=>|
+       00000060  3F 40 41 42 43 44 45 46 - 47 48 49 4A 4B 4C 4D 4E  |?@ABCDEFGHIJKLMN|
+       00000070  4F 50 51 52 53 54 55 56 - 57 58 59 5A 5B 5C 5D 5E  |OPQRSTUVWXYZ[\]^|
+       00000080  5F 60 61 62 63 64 65 66 - 67 68 69 6A 6B 6C 6D 6E  |_`abcdefghijklmn|
+       00000090  6F 70 71 72 73 74 75 76 - 77 78 79 7A 7B 7C 7D 7E  |opqrstuvwxyz{|}~|
+       000000A0  7F 80 81 82 83 84 85 86 - 87 88 89 8A 8B 8C 8D 8E  |................|
+       000000B0  8F 90 91 92 93 94 95 96 - 97 98 99 9A 9B 9C 9D 9E  |................|
+       000000C0  9F A0 A1 A2 A3 A4 A5 A6 - A7 A8 A9 AA AB AC AD AE  |................|
+       000000D0  AF B0 B1 B2 B3 B4 B5 B6 - B7 B8 B9 BA BB BC BD BE  |................|
+       000000E0  BF C0 C1 C2 C3 C4 C5 C6 - C7 C8 C9 CA CB CC CD CE  |................|
+       000000F0  CF D0 D1 D2 D3 D4 D5 D6 - D7 D8 D9 DA DB DC DD DE  |................|
+       00000100  DF E0 E1 E2 E3 E4 E5 E6 - E7 E8 E9 EA EB EC ED EE  |................|
+       00000110  EF F0 F1 F2 F3 F4 F5 F6 - F7 F8 F9 FA FB FC FD FE  |................|
+       00000120  FF 00 7A 0A             -                          |..z.|
+---
+name: integer-base-one-3Wr
+description:
+       some sample code for hexdumping Unicode; NUL and binary safe
+stdin:
+       set -U
+       {
+               print 'Hello, World!\\\nこんにちは!'
+               typeset -Uui16 i=0x100
+               # change that to 0xFF once we can handle embedded
+               # NUL characters in strings / here documents
+               while (( i++ < 0x1FF )); do
+                       print -n "\u${i#16#1}"
+               done
+               print
+               print \\xff             # invalid utf-8
+               print \\xc2             # invalid 2-byte
+               print \\xef\\xbf\\xc0   # invalid 3-byte
+               print \\xc0\\x80        # non-minimalistic
+               print \\xe0\\x80\\x80   # non-minimalistic
+               print '�￾￿'       # end of range
+               print '\0z'             # embedded NUL
+       } | {
+               # integer-base-one-3Wr
+               typeset -Uui16 -Z11 pos=0
+               typeset -Uui16 -Z7 hv=2147483647
+               dasc=
+               if read -arN -1 line; then
+                       typeset -i1 line
+                       i=0
+                       while (( i < ${#line[*]} )); do
+                               hv=${line[i++]}
+                               if (( (hv < 32) || \
+                                   ((hv > 126) && (hv < 160)) )); then
+                                       dch=.
+                               elif (( (hv & 0xFF80) == 0xEF80 )); then
+                                       dch=�
+                               else
+                                       dch=${line[i-1]#1#}
+                               fi
+                               if (( (pos & 7) == 7 )); then
+                                       dasc=$dasc$dch
+                                       dch=
+                               elif (( (pos & 7) == 0 )); then
+                                       (( pos )) && print "$dasc|"
+                                       print -n "${pos#16#}  "
+                                       dasc=' |'
+                               fi
+                               print -n "${hv#16#} "
+                               (( (pos++ & 7) == 3 )) && \
+                                   print -n -- '- '
+                               dasc=$dasc$dch
                        done
-                       print "$dasc|"
                fi
+               while (( pos & 7 )); do
+                       print -n '     '
+                       (( (pos++ & 7) == 3 )) && print -n -- '- '
+               done
+               (( hv == 2147483647 )) || print "$dasc|"
        }
 expected-stdout:
        00000000  0048 0065 006C 006C - 006F 002C 0020 0057  |Hello, W|
@@ -6417,7 +7291,7 @@ expected-stdout:
        00000110  00FB 00FC 00FD 00FE - 00FF 000A EFFF 000A  |ûüýþÿ.�.|
        00000118  EFC2 000A EFEF EFBF - EFC0 000A EFC0 EF80  |�.���.��|
        00000120  000A EFE0 EF80 EF80 - 000A FFFD EFEF EFBF  |.���.���|
-       00000128  EFBE EFEF EFBF EFBF - 000A                 |����.|
+       00000128  EFBE EFEF EFBF EFBF - 000A 0000 007A 000A  |����..z.|
 ---
 name: integer-base-one-4
 description:
@@ -6440,6 +7314,32 @@ expected-stdout:
        5 97
        6 97
 ---
+name: integer-base-one-5A
+description:
+       Check to see that we’re NUL and Unicode safe
+stdin:
+       set +U
+       print 'a\0b\xfdz' >x
+       read -a y <x
+       set -U
+       typeset -Uui16 y
+       print ${y[*]} .
+expected-stdout:
+       16#61 16#0 16#62 16#FD 16#7A .
+---
+name: integer-base-one-5W
+description:
+       Check to see that we’re NUL and Unicode safe
+stdin:
+       set -U
+       print 'a\0b€c' >x
+       read -a y <x
+       set +U
+       typeset -Uui16 y
+       print ${y[*]} .
+expected-stdout:
+       16#61 16#0 16#62 16#20AC 16#63 .
+---
 name: ulimit-1
 description:
        Check if we can use a specific syntax idiom for ulimit
@@ -6555,7 +7455,7 @@ stdin:
 expected-stdout:
        ===
        mir
-expected-stderr-pattern: /.*: cannot (create|overwrite) .*/
+expected-stderr-pattern: /.*: can't (create|overwrite) .*/
 ---
 name: bashiop-3b
 description:
@@ -6720,21 +7620,22 @@ stdin:
 name: fd-cloexec-1
 description:
        Verify that file descriptors > 2 are private for Korn shells
+       AT&T ksh93 does this still, which means we must keep it as well
 file-setup: file 644 "test.sh"
-       print -u3 Fowl
+       echo >&3 Fowl
 stdin:
        exec 3>&1
        "$__progname" test.sh
 expected-exit: e != 0
-expected-stderr:
-       test.sh[1]: print: -u: 3: bad file descriptor
+expected-stderr-pattern:
+       /bad file descriptor/
 ---
 name: fd-cloexec-2
 description:
        Verify that file descriptors > 2 are not private for POSIX shells
        See Debian Bug #154540, Closes: #499139
 file-setup: file 644 "test.sh"
-       print -u3 Fowl
+       echo >&3 Fowl
 stdin:
        test -n "$POSH_VERSION" || set -o sh
        exec 3>&1
@@ -6742,28 +7643,61 @@ stdin:
 expected-stdout:
        Fowl
 ---
-name: comsub-1
+name: comsub-1a
 description:
-       COMSUB are currently parsed by hacking lex.c instead of
-       recursively (see regression-6): matching parenthesēs bug
-       Fails on: pdksh mksh bash2 bash3 zsh
-       Passes on: bash4 ksh93
-expected-fail: yes
+       COMSUB are now parsed recursively, so this works
+       see also regression-6: matching parenthesēs bug
+       Fails on: pdksh bash2 bash3 zsh
+       Passes on: bash4 ksh93 mksh(20110313+)
 stdin:
        echo $(case 1 in (1) echo yes;; (2) echo no;; esac)
        echo $(case 1 in 1) echo yes;; 2) echo no;; esac)
+       TEST=1234; echo ${TEST: $(case 1 in (1) echo 1;; (*) echo 2;; esac)}
+       TEST=5678; echo ${TEST: $(case 1 in 1) echo 1;; *) echo 2;; esac)}
 expected-stdout:
        yes
        yes
+       234
+       678
+---
+name: comsub-1b
+description:
+       COMSUB are now parsed recursively, so this works
+       Fails on GNU bash even, ksh93 passes
+stdin:
+       echo $(($(case 1 in (1) echo 1;; (*) echo 2;; esac)+10))
+       echo $(($(case 1 in 1) echo 1;; *) echo 2;; esac)+20))
+       (( a = $(case 1 in (1) echo 1;; (*) echo 2;; esac) )); echo $a.
+       (( a = $(case 1 in 1) echo 1;; *) echo 2;; esac) )); echo $a.
+expected-stdout:
+       11
+       21
+       1.
+       1.
+---
+name: comsub-1c
+description:
+       COMSUB are now parsed recursively, so this works (ksh93, mksh)
+       First test passes on bash4, second fails there
+category: !smksh
+stdin:
+       a=($(case 1 in (1) echo 1;; (*) echo 2;; esac)); echo ${a[0]}.
+       a=($(case 1 in 1) echo 1;; *) echo 2;; esac)); echo ${a[0]}.
+       a=($(($(case 1 in (1) echo 1;; (*) echo 2;; esac)+10))); echo ${a[0]}.
+       a=($(($(case 1 in 1) echo 1;; *) echo 2;; esac)+20))); echo ${a[0]}.
+expected-stdout:
+       1.
+       1.
+       11.
+       21.
 ---
 name: comsub-2
 description:
        RedHat BZ#496791 – another case of missing recursion
        in parsing COMSUB expressions
-       Fails on: pdksh mksh bash2 bash3¹ bash4¹ zsh
-       Passes on: ksh93
+       Fails on: pdksh bash2 bash3¹ bash4¹ zsh
+       Passes on: ksh93 mksh(20110305+)
        ① bash[34] seem to choke on comment ending with backslash-newline
-expected-fail: yes
 stdin:
        # a comment with " ' \
        x=$(
@@ -6774,6 +7708,707 @@ stdin:
 expected-stdout:
        yes
 ---
+name: comsub-3
+description:
+       Extended test for COMSUB explaining why a recursive parser
+       is a must (a non-recursive parser cannot pass all three of
+       these test cases, especially the ‘#’ is difficult)
+stdin:
+       echo $(typeset -i10 x=16#20; echo $x)
+       echo $(typeset -Uui16 x=16#$(id -u)
+       ) .
+       echo $(c=1; d=1
+       typeset -Uui16 a=36#foo; c=2
+       typeset -Uui16 b=36 #foo; d=2
+       echo $a $b $c $d)
+expected-stdout:
+       32
+       .
+       16#4F68 16#24 2 1
+---
+name: comsub-4
+description:
+       Check the tree dump functions for !MKSH_SMALL functionality
+category: !smksh
+stdin:
+       x() { case $1 in a) a+=b ;;& *) c+=(d e) ;; esac; }
+       typeset -f x
+expected-stdout:
+       x() {
+               case $1 in
+               (a)
+                       a+=b 
+                       ;|
+               (*)
+                       set -A c+ -- d e 
+                       ;;
+               esac 
+       } 
+---
+name: comsub-torture
+description:
+       Check the tree dump functions work correctly
+stdin:
+       if [[ -z $__progname ]]; then echo >&2 call me with __progname; exit 1; fi
+       while IFS= read -r line; do
+               if [[ $line = '#1' ]]; then
+                       lastf=0
+                       continue
+               elif [[ $line = EOFN* ]]; then
+                       fbody=$fbody$'\n'$line
+                       continue
+               elif [[ $line != '#'* ]]; then
+                       fbody=$fbody$'\n\t'$line
+                       continue
+               fi
+               if (( lastf )); then
+                       x="inline_${nextf}() {"$fbody$'\n}\n'
+                       print -nr -- "$x"
+                       print -r -- "${x}typeset -f inline_$nextf" | "$__progname"
+                       x="function comsub_$nextf { x=\$("$fbody$'\n); }\n'
+                       print -nr -- "$x"
+                       print -r -- "${x}typeset -f comsub_$nextf" | "$__progname"
+                       x="function reread_$nextf { x=\$(("$fbody$'\n)|tr u x); }\n'
+                       print -nr -- "$x"
+                       print -r -- "${x}typeset -f reread_$nextf" | "$__progname"
+               fi
+               lastf=1
+               fbody=
+               nextf=${line#?}
+       done <<'EOD'
+       #1
+       #TCOM
+       vara=1  varb='2  3'  cmd  arg1  $arg2  "$arg3  4"
+       #TPAREN_TPIPE_TLIST
+       (echo $foo  |  tr -dc 0-9; echo)
+       #TAND_TOR
+       cmd  &&  echo ja  ||  echo nein
+       #TSELECT
+       select  file  in  *;  do  echo  "<$file>" ;  break ;  done
+       #TFOR_TTIME
+       for  i  in  {1,2,3}  ;  do  time  echo  $i ;  done
+       #TCASE
+       case  $foo  in  1)  echo eins;& 2) echo zwei  ;| *) echo kann net bis drei zählen;;  esac
+       #TIF_TBANG_TDBRACKET_TELIF
+       if  !  [[  1  =  1  ]]  ;  then  echo eins;  elif [[ 1 = 2 ]]; then echo zwei  ;else echo drei; fi
+       #TWHILE
+       i=1; while (( i < 10 )); do echo $i; let ++i; done
+       #TUNTIL
+       i=10; until  (( !--i )) ; do echo $i; done
+       #TCOPROC
+       cat  *  |&  ls
+       #TFUNCT_TBRACE_TASYNC
+       function  korn  {  echo eins; echo zwei ;  }
+       bourne  ()  {  logger *  &  }
+       #IOREAD_IOCAT
+       tr  x  u  0<foo  >>bar
+       #IOWRITE_IOCLOB_IOHERE_noIOSKIP
+       cat  >|bar  <<'EOFN'
+       foo
+       EOFN
+       #IOWRITE_noIOCLOB_IOHERE_IOSKIP
+       cat  1>bar  <<-EOFI
+       foo
+       EOFI
+       #IORDWR_IODUP
+       sh  1<>/dev/console  0<&1  2>&1
+       #COMSUB_EXPRSUB
+       echo $(true) $((1+ 2))
+       #QCHAR_OQUOTE_CQUOTE
+       echo fo\ob\"a\`r\'b\$az
+       echo "fo\ob\"a\`r\'b\$az"
+       echo 'fo\ob\"a\`r'\''b\$az'
+       #OSUBST_CSUBST_OPAT_SPAT_CPAT
+       [[ ${foo#bl\(u\)b} = @(bar|baz) ]]
+       #heredoc_closed
+       x=$(cat <<EOFN
+       note there must be no space between EOFN and )
+       EOFN); echo $x
+       #heredoc_space
+       x=$(cat <<EOFN\ 
+       note the space between EOFN and ) is actually part of the here document marker
+       EOFN ); echo $x
+       #patch_motd
+       x=$(sysctl -n kern.version | sed 1q)
+       [[ -s /etc/motd && "$([[ "$(head -1 /etc/motd)" != $x ]] && \
+           ed -s /etc/motd 2>&1 <<-EOF
+               1,/^\$/d
+               0a
+                       $x
+       
+               .
+               wq
+       EOF)" = @(?) ]] && rm -f /etc/motd
+       if [[ ! -s /etc/motd ]]; then
+               install -c -o root -g wheel -m 664 /dev/null /etc/motd
+               print -- "$x\n" >/etc/motd
+       fi
+       #0
+       EOD
+expected-stdout:
+       inline_TCOM() {
+               vara=1  varb='2  3'  cmd  arg1  $arg2  "$arg3  4"
+       }
+       inline_TCOM() {
+               vara=1 varb="2  3" cmd arg1 $arg2 "$arg3  4" 
+       } 
+       function comsub_TCOM { x=$(
+               vara=1  varb='2  3'  cmd  arg1  $arg2  "$arg3  4"
+       ); }
+       function comsub_TCOM {
+               x=$(vara=1 varb="2  3" cmd arg1 $arg2 "$arg3  4" ) 
+       } 
+       function reread_TCOM { x=$((
+               vara=1  varb='2  3'  cmd  arg1  $arg2  "$arg3  4"
+       )|tr u x); }
+       function reread_TCOM {
+               x=$(( vara=1 varb="2  3" cmd arg1 $arg2 "$arg3  4" ) | tr u x ) 
+       } 
+       inline_TPAREN_TPIPE_TLIST() {
+               (echo $foo  |  tr -dc 0-9; echo)
+       }
+       inline_TPAREN_TPIPE_TLIST() {
+               ( echo $foo | tr -dc 0-9 
+                 echo ) 
+       } 
+       function comsub_TPAREN_TPIPE_TLIST { x=$(
+               (echo $foo  |  tr -dc 0-9; echo)
+       ); }
+       function comsub_TPAREN_TPIPE_TLIST {
+               x=$(( echo $foo | tr -dc 0-9 ; echo ) ) 
+       } 
+       function reread_TPAREN_TPIPE_TLIST { x=$((
+               (echo $foo  |  tr -dc 0-9; echo)
+       )|tr u x); }
+       function reread_TPAREN_TPIPE_TLIST {
+               x=$(( ( echo $foo | tr -dc 0-9 ; echo ) ) | tr u x ) 
+       } 
+       inline_TAND_TOR() {
+               cmd  &&  echo ja  ||  echo nein
+       }
+       inline_TAND_TOR() {
+               cmd && echo ja || echo nein 
+       } 
+       function comsub_TAND_TOR { x=$(
+               cmd  &&  echo ja  ||  echo nein
+       ); }
+       function comsub_TAND_TOR {
+               x=$(cmd && echo ja || echo nein ) 
+       } 
+       function reread_TAND_TOR { x=$((
+               cmd  &&  echo ja  ||  echo nein
+       )|tr u x); }
+       function reread_TAND_TOR {
+               x=$(( cmd && echo ja || echo nein ) | tr u x ) 
+       } 
+       inline_TSELECT() {
+               select  file  in  *;  do  echo  "<$file>" ;  break ;  done
+       }
+       inline_TSELECT() {
+               select file in * 
+               do
+                       echo "<$file>" 
+                       break 
+               done 
+       } 
+       function comsub_TSELECT { x=$(
+               select  file  in  *;  do  echo  "<$file>" ;  break ;  done
+       ); }
+       function comsub_TSELECT {
+               x=$(select file in * ; do echo "<$file>" ; break ; done ) 
+       } 
+       function reread_TSELECT { x=$((
+               select  file  in  *;  do  echo  "<$file>" ;  break ;  done
+       )|tr u x); }
+       function reread_TSELECT {
+               x=$(( select file in * ; do echo "<$file>" ; break ; done ) | tr u x ) 
+       } 
+       inline_TFOR_TTIME() {
+               for  i  in  {1,2,3}  ;  do  time  echo  $i ;  done
+       }
+       inline_TFOR_TTIME() {
+               for i in {1,2,3} 
+               do
+                       time echo $i 
+               done 
+       } 
+       function comsub_TFOR_TTIME { x=$(
+               for  i  in  {1,2,3}  ;  do  time  echo  $i ;  done
+       ); }
+       function comsub_TFOR_TTIME {
+               x=$(for i in {1,2,3} ; do time echo $i ; done ) 
+       } 
+       function reread_TFOR_TTIME { x=$((
+               for  i  in  {1,2,3}  ;  do  time  echo  $i ;  done
+       )|tr u x); }
+       function reread_TFOR_TTIME {
+               x=$(( for i in {1,2,3} ; do time echo $i ; done ) | tr u x ) 
+       } 
+       inline_TCASE() {
+               case  $foo  in  1)  echo eins;& 2) echo zwei  ;| *) echo kann net bis drei zählen;;  esac
+       }
+       inline_TCASE() {
+               case $foo in
+               (1)
+                       echo eins 
+                       ;&
+               (2)
+                       echo zwei 
+                       ;|
+               (*)
+                       echo kann net bis drei zählen 
+                       ;;
+               esac 
+       } 
+       function comsub_TCASE { x=$(
+               case  $foo  in  1)  echo eins;& 2) echo zwei  ;| *) echo kann net bis drei zählen;;  esac
+       ); }
+       function comsub_TCASE {
+               x=$(case $foo in (1) echo eins  ;& (2) echo zwei  ;| (*) echo kann net bis drei zählen  ;; esac ) 
+       } 
+       function reread_TCASE { x=$((
+               case  $foo  in  1)  echo eins;& 2) echo zwei  ;| *) echo kann net bis drei zählen;;  esac
+       )|tr u x); }
+       function reread_TCASE {
+               x=$(( case $foo in (1) echo eins  ;& (2) echo zwei  ;| (*) echo kann net bis drei zählen  ;; esac ) | tr u x ) 
+       } 
+       inline_TIF_TBANG_TDBRACKET_TELIF() {
+               if  !  [[  1  =  1  ]]  ;  then  echo eins;  elif [[ 1 = 2 ]]; then echo zwei  ;else echo drei; fi
+       }
+       inline_TIF_TBANG_TDBRACKET_TELIF() {
+               if ! [[ 1 = 1 ]] 
+               then
+                       echo eins 
+               elif [[ 1 = 2 ]] 
+               then
+                       echo zwei 
+               else
+                       echo drei 
+               fi 
+       } 
+       function comsub_TIF_TBANG_TDBRACKET_TELIF { x=$(
+               if  !  [[  1  =  1  ]]  ;  then  echo eins;  elif [[ 1 = 2 ]]; then echo zwei  ;else echo drei; fi
+       ); }
+       function comsub_TIF_TBANG_TDBRACKET_TELIF {
+               x=$(if ! [[ 1 = 1 ]] ; then echo eins ; elif [[ 1 = 2 ]] ; then echo zwei ; else echo drei ; fi ) 
+       } 
+       function reread_TIF_TBANG_TDBRACKET_TELIF { x=$((
+               if  !  [[  1  =  1  ]]  ;  then  echo eins;  elif [[ 1 = 2 ]]; then echo zwei  ;else echo drei; fi
+       )|tr u x); }
+       function reread_TIF_TBANG_TDBRACKET_TELIF {
+               x=$(( if ! [[ 1 = 1 ]] ; then echo eins ; elif [[ 1 = 2 ]] ; then echo zwei ; else echo drei ; fi ) | tr u x ) 
+       } 
+       inline_TWHILE() {
+               i=1; while (( i < 10 )); do echo $i; let ++i; done
+       }
+       inline_TWHILE() {
+               i=1 
+               while let " i < 10 " 
+               do
+                       echo $i 
+                       let ++i 
+               done 
+       } 
+       function comsub_TWHILE { x=$(
+               i=1; while (( i < 10 )); do echo $i; let ++i; done
+       ); }
+       function comsub_TWHILE {
+               x=$(i=1 ; while let " i < 10 " ; do echo $i ; let ++i ; done ) 
+       } 
+       function reread_TWHILE { x=$((
+               i=1; while (( i < 10 )); do echo $i; let ++i; done
+       )|tr u x); }
+       function reread_TWHILE {
+               x=$(( i=1 ; while let " i < 10 " ; do echo $i ; let ++i ; done ) | tr u x ) 
+       } 
+       inline_TUNTIL() {
+               i=10; until  (( !--i )) ; do echo $i; done
+       }
+       inline_TUNTIL() {
+               i=10 
+               until let " !--i " 
+               do
+                       echo $i 
+               done 
+       } 
+       function comsub_TUNTIL { x=$(
+               i=10; until  (( !--i )) ; do echo $i; done
+       ); }
+       function comsub_TUNTIL {
+               x=$(i=10 ; until let " !--i " ; do echo $i ; done ) 
+       } 
+       function reread_TUNTIL { x=$((
+               i=10; until  (( !--i )) ; do echo $i; done
+       )|tr u x); }
+       function reread_TUNTIL {
+               x=$(( i=10 ; until let " !--i " ; do echo $i ; done ) | tr u x ) 
+       } 
+       inline_TCOPROC() {
+               cat  *  |&  ls
+       }
+       inline_TCOPROC() {
+               cat * |& 
+               ls 
+       } 
+       function comsub_TCOPROC { x=$(
+               cat  *  |&  ls
+       ); }
+       function comsub_TCOPROC {
+               x=$(cat * |&  ls ) 
+       } 
+       function reread_TCOPROC { x=$((
+               cat  *  |&  ls
+       )|tr u x); }
+       function reread_TCOPROC {
+               x=$(( cat * |&  ls ) | tr u x ) 
+       } 
+       inline_TFUNCT_TBRACE_TASYNC() {
+               function  korn  {  echo eins; echo zwei ;  }
+               bourne  ()  {  logger *  &  }
+       }
+       inline_TFUNCT_TBRACE_TASYNC() {
+               function korn {
+                       echo eins 
+                       echo zwei 
+               } 
+               bourne() {
+                       logger * & 
+               } 
+       } 
+       function comsub_TFUNCT_TBRACE_TASYNC { x=$(
+               function  korn  {  echo eins; echo zwei ;  }
+               bourne  ()  {  logger *  &  }
+       ); }
+       function comsub_TFUNCT_TBRACE_TASYNC {
+               x=$(function korn { echo eins ; echo zwei ; } ; bourne() { logger * &  } ) 
+       } 
+       function reread_TFUNCT_TBRACE_TASYNC { x=$((
+               function  korn  {  echo eins; echo zwei ;  }
+               bourne  ()  {  logger *  &  }
+       )|tr u x); }
+       function reread_TFUNCT_TBRACE_TASYNC {
+               x=$(( function korn { echo eins ; echo zwei ; } ; bourne() { logger * &  } ) | tr u x ) 
+       } 
+       inline_IOREAD_IOCAT() {
+               tr  x  u  0<foo  >>bar
+       }
+       inline_IOREAD_IOCAT() {
+               tr x u <foo >>bar 
+       } 
+       function comsub_IOREAD_IOCAT { x=$(
+               tr  x  u  0<foo  >>bar
+       ); }
+       function comsub_IOREAD_IOCAT {
+               x=$(tr x u <foo >>bar ) 
+       } 
+       function reread_IOREAD_IOCAT { x=$((
+               tr  x  u  0<foo  >>bar
+       )|tr u x); }
+       function reread_IOREAD_IOCAT {
+               x=$(( tr x u <foo >>bar ) | tr u x ) 
+       } 
+       inline_IOWRITE_IOCLOB_IOHERE_noIOSKIP() {
+               cat  >|bar  <<'EOFN'
+               foo
+       EOFN
+       }
+       inline_IOWRITE_IOCLOB_IOHERE_noIOSKIP() {
+               cat >|bar <<"EOFN" 
+               foo
+       EOFN
+       
+       } 
+       function comsub_IOWRITE_IOCLOB_IOHERE_noIOSKIP { x=$(
+               cat  >|bar  <<'EOFN'
+               foo
+       EOFN
+       ); }
+       function comsub_IOWRITE_IOCLOB_IOHERE_noIOSKIP {
+               x=$(cat >|bar <<"EOFN" 
+               foo
+       EOFN
+       ) 
+       } 
+       function reread_IOWRITE_IOCLOB_IOHERE_noIOSKIP { x=$((
+               cat  >|bar  <<'EOFN'
+               foo
+       EOFN
+       )|tr u x); }
+       function reread_IOWRITE_IOCLOB_IOHERE_noIOSKIP {
+               x=$(( cat >|bar <<"EOFN" 
+               foo
+       EOFN
+       ) | tr u x ) 
+       } 
+       inline_IOWRITE_noIOCLOB_IOHERE_IOSKIP() {
+               cat  1>bar  <<-EOFI
+               foo
+               EOFI
+       }
+       inline_IOWRITE_noIOCLOB_IOHERE_IOSKIP() {
+               cat >bar <<-EOFI 
+       foo
+       EOFI
+       
+       } 
+       function comsub_IOWRITE_noIOCLOB_IOHERE_IOSKIP { x=$(
+               cat  1>bar  <<-EOFI
+               foo
+               EOFI
+       ); }
+       function comsub_IOWRITE_noIOCLOB_IOHERE_IOSKIP {
+               x=$(cat >bar <<-EOFI 
+       foo
+       EOFI
+       ) 
+       } 
+       function reread_IOWRITE_noIOCLOB_IOHERE_IOSKIP { x=$((
+               cat  1>bar  <<-EOFI
+               foo
+               EOFI
+       )|tr u x); }
+       function reread_IOWRITE_noIOCLOB_IOHERE_IOSKIP {
+               x=$(( cat >bar <<-EOFI 
+       foo
+       EOFI
+       ) | tr u x ) 
+       } 
+       inline_IORDWR_IODUP() {
+               sh  1<>/dev/console  0<&1  2>&1
+       }
+       inline_IORDWR_IODUP() {
+               sh 1<>/dev/console <&1 2>&1 
+       } 
+       function comsub_IORDWR_IODUP { x=$(
+               sh  1<>/dev/console  0<&1  2>&1
+       ); }
+       function comsub_IORDWR_IODUP {
+               x=$(sh 1<>/dev/console <&1 2>&1 ) 
+       } 
+       function reread_IORDWR_IODUP { x=$((
+               sh  1<>/dev/console  0<&1  2>&1
+       )|tr u x); }
+       function reread_IORDWR_IODUP {
+               x=$(( sh 1<>/dev/console <&1 2>&1 ) | tr u x ) 
+       } 
+       inline_COMSUB_EXPRSUB() {
+               echo $(true) $((1+ 2))
+       }
+       inline_COMSUB_EXPRSUB() {
+               echo $(true ) $((1+ 2)) 
+       } 
+       function comsub_COMSUB_EXPRSUB { x=$(
+               echo $(true) $((1+ 2))
+       ); }
+       function comsub_COMSUB_EXPRSUB {
+               x=$(echo $(true ) $((1+ 2)) ) 
+       } 
+       function reread_COMSUB_EXPRSUB { x=$((
+               echo $(true) $((1+ 2))
+       )|tr u x); }
+       function reread_COMSUB_EXPRSUB {
+               x=$(( echo $(true ) $((1+ 2)) ) | tr u x ) 
+       } 
+       inline_QCHAR_OQUOTE_CQUOTE() {
+               echo fo\ob\"a\`r\'b\$az
+               echo "fo\ob\"a\`r\'b\$az"
+               echo 'fo\ob\"a\`r'\''b\$az'
+       }
+       inline_QCHAR_OQUOTE_CQUOTE() {
+               echo fo\ob\"a\`r\'b\$az 
+               echo "fo\ob\"a\`r\'b\$az" 
+               echo "fo\\ob\\\"a\\\`r"\'"b\\\$az" 
+       } 
+       function comsub_QCHAR_OQUOTE_CQUOTE { x=$(
+               echo fo\ob\"a\`r\'b\$az
+               echo "fo\ob\"a\`r\'b\$az"
+               echo 'fo\ob\"a\`r'\''b\$az'
+       ); }
+       function comsub_QCHAR_OQUOTE_CQUOTE {
+               x=$(echo fo\ob\"a\`r\'b\$az ; echo "fo\ob\"a\`r\'b\$az" ; echo "fo\\ob\\\"a\\\`r"\'"b\\\$az" ) 
+       } 
+       function reread_QCHAR_OQUOTE_CQUOTE { x=$((
+               echo fo\ob\"a\`r\'b\$az
+               echo "fo\ob\"a\`r\'b\$az"
+               echo 'fo\ob\"a\`r'\''b\$az'
+       )|tr u x); }
+       function reread_QCHAR_OQUOTE_CQUOTE {
+               x=$(( echo fo\ob\"a\`r\'b\$az ; echo "fo\ob\"a\`r\'b\$az" ; echo "fo\\ob\\\"a\\\`r"\'"b\\\$az" ) | tr u x ) 
+       } 
+       inline_OSUBST_CSUBST_OPAT_SPAT_CPAT() {
+               [[ ${foo#bl\(u\)b} = @(bar|baz) ]]
+       }
+       inline_OSUBST_CSUBST_OPAT_SPAT_CPAT() {
+               [[ ${foo#bl\(u\)b} = @(bar|baz) ]] 
+       } 
+       function comsub_OSUBST_CSUBST_OPAT_SPAT_CPAT { x=$(
+               [[ ${foo#bl\(u\)b} = @(bar|baz) ]]
+       ); }
+       function comsub_OSUBST_CSUBST_OPAT_SPAT_CPAT {
+               x=$([[ ${foo#bl\(u\)b} = @(bar|baz) ]] ) 
+       } 
+       function reread_OSUBST_CSUBST_OPAT_SPAT_CPAT { x=$((
+               [[ ${foo#bl\(u\)b} = @(bar|baz) ]]
+       )|tr u x); }
+       function reread_OSUBST_CSUBST_OPAT_SPAT_CPAT {
+               x=$(( [[ ${foo#bl\(u\)b} = @(bar|baz) ]] ) | tr u x ) 
+       } 
+       inline_heredoc_closed() {
+               x=$(cat <<EOFN
+               note there must be no space between EOFN and )
+       EOFN); echo $x
+       }
+       inline_heredoc_closed() {
+               x=$(cat <<EOFN 
+               note there must be no space between EOFN and )
+       EOFN
+       ) 
+               echo $x 
+       } 
+       function comsub_heredoc_closed { x=$(
+               x=$(cat <<EOFN
+               note there must be no space between EOFN and )
+       EOFN); echo $x
+       ); }
+       function comsub_heredoc_closed {
+               x=$(x=$(cat <<EOFN 
+               note there must be no space between EOFN and )
+       EOFN
+       ) ; echo $x ) 
+       } 
+       function reread_heredoc_closed { x=$((
+               x=$(cat <<EOFN
+               note there must be no space between EOFN and )
+       EOFN); echo $x
+       )|tr u x); }
+       function reread_heredoc_closed {
+               x=$(( x=$(cat <<EOFN 
+               note there must be no space between EOFN and )
+       EOFN
+       ) ; echo $x ) | tr u x ) 
+       } 
+       inline_heredoc_space() {
+               x=$(cat <<EOFN\ 
+               note the space between EOFN and ) is actually part of the here document marker
+       EOFN ); echo $x
+       }
+       inline_heredoc_space() {
+               x=$(cat <<EOFN\  
+               note the space between EOFN and ) is actually part of the here document marker
+       EOFN 
+       ) 
+               echo $x 
+       } 
+       function comsub_heredoc_space { x=$(
+               x=$(cat <<EOFN\ 
+               note the space between EOFN and ) is actually part of the here document marker
+       EOFN ); echo $x
+       ); }
+       function comsub_heredoc_space {
+               x=$(x=$(cat <<EOFN\  
+               note the space between EOFN and ) is actually part of the here document marker
+       EOFN 
+       ) ; echo $x ) 
+       } 
+       function reread_heredoc_space { x=$((
+               x=$(cat <<EOFN\ 
+               note the space between EOFN and ) is actually part of the here document marker
+       EOFN ); echo $x
+       )|tr u x); }
+       function reread_heredoc_space {
+               x=$(( x=$(cat <<EOFN\  
+               note the space between EOFN and ) is actually part of the here document marker
+       EOFN 
+       ) ; echo $x ) | tr u x ) 
+       } 
+       inline_patch_motd() {
+               x=$(sysctl -n kern.version | sed 1q)
+               [[ -s /etc/motd && "$([[ "$(head -1 /etc/motd)" != $x ]] && \
+                   ed -s /etc/motd 2>&1 <<-EOF
+                       1,/^\$/d
+                       0a
+                               $x
+               
+                       .
+                       wq
+               EOF)" = @(?) ]] && rm -f /etc/motd
+               if [[ ! -s /etc/motd ]]; then
+                       install -c -o root -g wheel -m 664 /dev/null /etc/motd
+                       print -- "$x\n" >/etc/motd
+               fi
+       }
+       inline_patch_motd() {
+               x=$(sysctl -n kern.version | sed 1q ) 
+               [[ -s /etc/motd && "$([[ "$(head -1 /etc/motd )" != $x ]] && ed -s /etc/motd 2>&1 <<-EOF 
+       1,/^\$/d
+       0a
+       $x
+       
+       .
+       wq
+       EOF
+       )" = @(?) ]] && rm -f /etc/motd 
+               if [[ ! -s /etc/motd ]] 
+               then
+                       install -c -o root -g wheel -m 664 /dev/null /etc/motd 
+                       print -- "$x\n" >/etc/motd 
+               fi 
+       } 
+       function comsub_patch_motd { x=$(
+               x=$(sysctl -n kern.version | sed 1q)
+               [[ -s /etc/motd && "$([[ "$(head -1 /etc/motd)" != $x ]] && \
+                   ed -s /etc/motd 2>&1 <<-EOF
+                       1,/^\$/d
+                       0a
+                               $x
+               
+                       .
+                       wq
+               EOF)" = @(?) ]] && rm -f /etc/motd
+               if [[ ! -s /etc/motd ]]; then
+                       install -c -o root -g wheel -m 664 /dev/null /etc/motd
+                       print -- "$x\n" >/etc/motd
+               fi
+       ); }
+       function comsub_patch_motd {
+               x=$(x=$(sysctl -n kern.version | sed 1q ) ; [[ -s /etc/motd && "$([[ "$(head -1 /etc/motd )" != $x ]] && ed -s /etc/motd 2>&1 <<-EOF 
+       1,/^\$/d
+       0a
+       $x
+       
+       .
+       wq
+       EOF
+       )" = @(?) ]] && rm -f /etc/motd ; if [[ ! -s /etc/motd ]] ; then install -c -o root -g wheel -m 664 /dev/null /etc/motd ; print -- "$x\n" >/etc/motd ; fi ) 
+       } 
+       function reread_patch_motd { x=$((
+               x=$(sysctl -n kern.version | sed 1q)
+               [[ -s /etc/motd && "$([[ "$(head -1 /etc/motd)" != $x ]] && \
+                   ed -s /etc/motd 2>&1 <<-EOF
+                       1,/^\$/d
+                       0a
+                               $x
+               
+                       .
+                       wq
+               EOF)" = @(?) ]] && rm -f /etc/motd
+               if [[ ! -s /etc/motd ]]; then
+                       install -c -o root -g wheel -m 664 /dev/null /etc/motd
+                       print -- "$x\n" >/etc/motd
+               fi
+       )|tr u x); }
+       function reread_patch_motd {
+               x=$(( x=$(sysctl -n kern.version | sed 1q ) ; [[ -s /etc/motd && "$([[ "$(head -1 /etc/motd )" != $x ]] && ed -s /etc/motd 2>&1 <<-EOF 
+       1,/^\$/d
+       0a
+       $x
+       
+       .
+       wq
+       EOF
+       )" = @(?) ]] && rm -f /etc/motd ; if [[ ! -s /etc/motd ]] ; then install -c -o root -g wheel -m 664 /dev/null /etc/motd ; print -- "$x\n" >/etc/motd ; fi ) | tr u x ) 
+       } 
+---
 name: test-stnze-1
 description:
        Check that the short form [ $x ] works
@@ -6875,6 +8510,7 @@ file-setup: file 755 "falsetto"
 file-setup: file 755 "!false"
        #! /bin/sh
        echo si
+need-ctty: yes
 arguments: !-i!
 stdin:
        export PATH=.:$PATH
@@ -6903,6 +8539,7 @@ file-setup: file 755 "falsetto"
 file-setup: file 755 "!"
        #! /bin/sh
        echo si
+need-ctty: yes
 arguments: !-i!
 stdin:
        export PATH=.:$PATH
@@ -6929,6 +8566,7 @@ file-setup: file 755 "falsetto"
 file-setup: file 755 "!"
        #! /bin/sh
        echo si
+need-ctty: yes
 arguments: !-i!
 env-setup: !ENV=./Env!
 file-setup: file 644 "Env"
@@ -7152,11 +8790,28 @@ expected-stdout:
        2 a .
        3 .
 ---
+name: nameref-4
+description:
+       Ensure we don't run in an infinite loop
+time-limit: 3
+stdin:
+       baz() {
+               typeset -n foo=foo
+               foo[0]=bar
+       }
+       set -A foo bad
+       echo sind $foo .
+       baz
+       echo blah $foo .
+expected-stdout:
+       sind bad .
+       blah bar .
+---
 name: better-parens-1a
 description:
        Check support for ((…)) and $((…)) vs (…) and $(…)
 stdin:
-       if ( (echo fubar) | tr u x); then
+       if ( (echo fubar)|tr u x); then
                echo ja
        else
                echo nein
@@ -7169,7 +8824,15 @@ name: better-parens-1b
 description:
        Check support for ((…)) and $((…)) vs (…) and $(…)
 stdin:
-       echo $( (echo fubar) | tr u x) $?
+       echo $( (echo fubar)|tr u x) $?
+expected-stdout:
+       fxbar 0
+---
+name: better-parens-1c
+description:
+       Check support for ((…)) and $((…)) vs (…) and $(…)
+stdin:
+       x=$( (echo fubar)|tr u x); echo $x $?
 expected-stdout:
        fxbar 0
 ---
@@ -7177,7 +8840,7 @@ name: better-parens-2a
 description:
        Check support for ((…)) and $((…)) vs (…) and $(…)
 stdin:
-       if ((echo fubar) | tr u x); then
+       if ((echo fubar)|tr u x); then
                echo ja
        else
                echo nein
@@ -7190,7 +8853,15 @@ name: better-parens-2b
 description:
        Check support for ((…)) and $((…)) vs (…) and $(…)
 stdin:
-       echo $((echo fubar) | tr u x) $?
+       echo $((echo fubar)|tr u x) $?
+expected-stdout:
+       fxbar 0
+---
+name: better-parens-2c
+description:
+       Check support for ((…)) and $((…)) vs (…) and $(…)
+stdin:
+       x=$((echo fubar)|tr u x); echo $x $?
 expected-stdout:
        fxbar 0
 ---
@@ -7198,7 +8869,7 @@ name: better-parens-3a
 description:
        Check support for ((…)) and $((…)) vs (…) and $(…)
 stdin:
-       if ( (echo fubar) | (tr u x)); then
+       if ( (echo fubar)|(tr u x)); then
                echo ja
        else
                echo nein
@@ -7211,7 +8882,15 @@ name: better-parens-3b
 description:
        Check support for ((…)) and $((…)) vs (…) and $(…)
 stdin:
-       echo $( (echo fubar) | (tr u x)) $?
+       echo $( (echo fubar)|(tr u x)) $?
+expected-stdout:
+       fxbar 0
+---
+name: better-parens-3c
+description:
+       Check support for ((…)) and $((…)) vs (…) and $(…)
+stdin:
+       x=$( (echo fubar)|(tr u x)); echo $x $?
 expected-stdout:
        fxbar 0
 ---
@@ -7219,7 +8898,7 @@ name: better-parens-4a
 description:
        Check support for ((…)) and $((…)) vs (…) and $(…)
 stdin:
-       if ((echo fubar) | (tr u x)); then
+       if ((echo fubar)|(tr u x)); then
                echo ja
        else
                echo nein
@@ -7232,7 +8911,15 @@ name: better-parens-4b
 description:
        Check support for ((…)) and $((…)) vs (…) and $(…)
 stdin:
-       echo $((echo fubar) | (tr u x)) $?
+       echo $((echo fubar)|(tr u x)) $?
+expected-stdout:
+       fxbar 0
+---
+name: better-parens-4c
+description:
+       Check support for ((…)) and $((…)) vs (…) and $(…)
+stdin:
+       x=$((echo fubar)|(tr u x)); echo $x $?
 expected-stdout:
        fxbar 0
 ---
@@ -7441,3 +9128,117 @@ expected-stdout:
        24 ?lnnix/nix =lnnix/nix: No such file or directory !2
        25 ?lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself =lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself: Too many levels of symbolic links !62
 ---
+name: realpath-2
+description:
+       Ensure that exactly two leading slashes are not collapsed
+       POSIX guarantees this exception, e.g. for UNC paths on Cygwin
+category: os:mirbsd
+stdin:
+       ln -s /bin t1
+       ln -s //bin t2
+       ln -s ///bin t3
+       realpath /bin
+       realpath //bin
+       realpath ///bin
+       realpath /usr/bin
+       realpath /usr//bin
+       realpath /usr///bin
+       realpath t1
+       realpath t2
+       realpath t3
+       rm -f t1 t2 t3
+       cd //usr/bin
+       pwd
+       cd ../lib
+       pwd
+       realpath //usr/include/../bin
+expected-stdout:
+       /bin
+       //bin
+       /bin
+       /usr/bin
+       /usr/bin
+       /usr/bin
+       /bin
+       //bin
+       /bin
+       //usr/bin
+       //usr/lib
+       //usr/bin
+---
+name: crash-1
+description:
+       Crashed during March 2011, fixed on vernal equinōx ☺
+category: os:mirbsd,os:openbsd
+stdin:
+       export MALLOC_OPTIONS=FGJPRSX
+       "$__progname" -c 'x=$(tr z r <<<baz); echo $x'
+expected-stdout:
+       bar
+---
+name: debian-117-1
+description:
+       Check test - bug#465250
+stdin:
+       test \( ! -e \) ; echo $?
+expected-stdout:
+       1
+---
+name: debian-117-2
+description:
+       Check test - bug#465250
+stdin:
+       test \(  -e \) ; echo $?
+expected-stdout:
+       0
+---
+name: debian-117-3
+description:
+       Check test - bug#465250
+stdin:
+       test ! -e  ; echo $?
+expected-stdout:
+       1
+---
+name: debian-117-4
+description:
+       Check test - bug#465250
+stdin:
+       test  -e  ; echo $?
+expected-stdout:
+       0
+---
+name: case-zsh
+description:
+       Check that zsh case variants work
+stdin:
+       case 'b' in
+         a) echo a ;;
+         b) echo b ;;
+         c) echo c ;;
+         *) echo x ;;
+       esac
+       echo =
+       case 'b' in
+         a) echo a ;&
+         b) echo b ;&
+         c) echo c ;&
+         *) echo x ;&
+       esac
+       echo =
+       case 'b' in
+         a) echo a ;|
+         b) echo b ;|
+         c) echo c ;|
+         *) echo x ;|
+       esac
+expected-stdout:
+       b
+       =
+       b
+       c
+       x
+       =
+       b
+       x
+---
diff --git a/src/dot.mkshrc b/src/dot.mkshrc
new file mode 100644 (file)
index 0000000..518f031
--- /dev/null
@@ -0,0 +1,375 @@
+# $Id$
+# $MirOS: src/bin/mksh/dot.mkshrc,v 1.65 2011/08/27 18:06:40 tg Exp $
+#-
+# Copyright (c) 2002, 2003, 2004, 2006, 2007, 2008, 2009, 2010, 2011
+#      Thorsten Glaser <tg@mirbsd.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un-
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person's immediate fault when using the work as intended.
+#-
+# ${ENV:-~/.mkshrc}: mksh initialisation file for interactive shells
+
+: ${EDITOR:=/bin/ed} ${TERM:=vt100} ${HOSTNAME:=$(ulimit -c 0;hostname -s 2>&-)}
+[[ $HOSTNAME = @(localhost|*([  ])) ]] && HOSTNAME=$(ulimit -c 0;hostname 2>&-)
+: ${HOSTNAME:=nil}; if (( USER_ID )); then PS1='$'; else PS1='#'; fi
+function precmd {
+       local e=$?
+
+       (( e )) && print -n "$e|"
+}
+PS1=$'\001\r''$(precmd)${USER:=$(ulimit -c 0; id -un 2>/dev/null || echo \?
+       )}@${HOSTNAME%%.*}:$(local d=${PWD:-?} p=~; [[ $p = ?(*/) ]] || \
+       d=${d/#$p/~}; local m=${%d} n p=...; (( m > 0 )) || m=${#d}
+       (( m > (n = (COLUMNS/3 < 7 ? 7 : COLUMNS/3)) )) && d=${d:(-n)} || \
+       p=; print -nr -- "$p$d") '"$PS1 "
+: ${MKSH:=$(whence -p mksh)}; export EDITOR HOSTNAME MKSH TERM USER
+alias ls=ls
+unalias ls
+alias l='ls -F'
+alias la='l -a'
+alias ll='l -l'
+alias lo='l -alo'
+whence -p rot13 >&- || alias rot13='tr \
+    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ \
+    nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM'
+whence -p hd >&- || function hd {
+       hexdump -e '"%08.8_ax  " 8/1 "%02X " " - " 8/1 "%02X "' \
+           -e '"  |" "%_p"' -e '"|\n"' "$@"
+}
+
+# Berkeley C shell compatible dirs, popd, and pushd functions
+# Z shell compatible chpwd() hook, used to update DIRSTACK[0]
+DIRSTACKBASE=$(realpath ~/. 2>&- || print -nr -- "$HOME")
+set -A DIRSTACK
+function chpwd {
+       DIRSTACK[0]=$(realpath . 2>&- || print -r -- "$PWD")
+       [[ $DIRSTACKBASE = ?(*/) ]] || \
+           DIRSTACK[0]=${DIRSTACK[0]/#$DIRSTACKBASE/~}
+       :
+}
+chpwd .
+function cd {
+       builtin cd "$@"
+       chpwd "$@"
+}
+function cd_csh {
+       local d t=${1/#~/$DIRSTACKBASE}
+
+       if ! d=$(builtin cd "$t" 2>&1); then
+               print -u2 "${1}: ${d##*$t - }."
+               return 1
+       fi
+       cd "$t"
+}
+function dirs {
+       local d dwidth
+       local -i isnoglob=0 fl=0 fv=0 fn=0 cpos=0
+
+       [[ $(set +o) == *@(-o noglob)@(| *) ]] && isnoglob=1
+       set -o noglob
+       while getopts ":lvn" d; do
+               case $d {
+               (l)     fl=1 ;;
+               (v)     fv=1 ;;
+               (n)     fn=1 ;;
+               (*)     print -u2 'Usage: dirs [-lvn].'
+                       return 1 ;;
+               }
+       done
+       shift $((OPTIND - 1))
+       if (( $# > 0 )); then
+               print -u2 'Usage: dirs [-lvn].'
+               return 1
+       fi
+       if (( fv )); then
+               fv=0
+               while (( fv < ${#DIRSTACK[*]} )); do
+                       d=${DIRSTACK[fv]}
+                       (( fl )) && d=${d/#~/$DIRSTACKBASE}
+                       print -r -- "$fv        $d"
+                       let fv++
+               done
+       else
+               fv=0
+               while (( fv < ${#DIRSTACK[*]} )); do
+                       d=${DIRSTACK[fv]}
+                       (( fl )) && d=${d/#~/$DIRSTACKBASE}
+                       (( dwidth = (${%d} > 0 ? ${%d} : ${#d}) ))
+                       if (( fn && (cpos += dwidth + 1) >= 79 && \
+                           dwidth < 80 )); then
+                               print
+                               (( cpos = dwidth + 1 ))
+                       fi
+                       print -nr -- "$d "
+                       let fv++
+               done
+               print
+       fi
+       (( isnoglob )) || set +o noglob
+       return 0
+}
+function popd {
+       local d fa
+       local -i isnoglob=0 n=1
+
+       [[ $(set +o) == *@(-o noglob)@(| *) ]] && isnoglob=1
+       set -o noglob
+       while getopts ":0123456789lvn" d; do
+               case $d {
+               (l|v|n) fa="$fa -$d" ;;
+               (+*)    n=2
+                       break ;;
+               (*)     print -u2 'Usage: popd [-lvn] [+<n>].'
+                       return 1 ;;
+               }
+       done
+       shift $((OPTIND - n))
+       n=0
+       if (( $# > 1 )); then
+               print -u2 popd: Too many arguments.
+               return 1
+       elif [[ $1 = ++([0-9]) && $1 != +0 ]]; then
+               if (( (n = ${1#+}) >= ${#DIRSTACK[*]} )); then
+                       print -u2 popd: Directory stack not that deep.
+                       return 1
+               fi
+       elif [[ -n $1 ]]; then
+               print -u2 popd: Bad directory.
+               return 1
+       fi
+       if (( ${#DIRSTACK[*]} < 2 )); then
+               print -u2 popd: Directory stack empty.
+               return 1
+       fi
+       unset DIRSTACK[n]
+       set -A DIRSTACK -- "${DIRSTACK[@]}"
+       cd_csh "${DIRSTACK[0]}" || return 1
+       (( isnoglob )) || set +o noglob
+       dirs $fa
+}
+function pushd {
+       local d fa
+       local -i isnoglob=0 n=1
+
+       [[ $(set +o) == *@(-o noglob)@(| *) ]] && isnoglob=1
+       set -o noglob
+       while getopts ":0123456789lvn" d; do
+               case $d {
+               (l|v|n) fa="$fa -$d" ;;
+               (+*)    n=2
+                       break ;;
+               (*)     print -u2 'Usage: pushd [-lvn] [<dir>|+<n>].'
+                       return 1 ;;
+               }
+       done
+       shift $((OPTIND - n))
+       if (( $# == 0 )); then
+               if (( ${#DIRSTACK[*]} < 2 )); then
+                       print -u2 pushd: No other directory.
+                       return 1
+               fi
+               d=${DIRSTACK[1]}
+               DIRSTACK[1]=${DIRSTACK[0]}
+               cd_csh "$d" || return 1
+       elif (( $# > 1 )); then
+               print -u2 pushd: Too many arguments.
+               return 1
+       elif [[ $1 = ++([0-9]) && $1 != +0 ]]; then
+               if (( (n = ${1#+}) >= ${#DIRSTACK[*]} )); then
+                       print -u2 pushd: Directory stack not that deep.
+                       return 1
+               fi
+               while (( n-- )); do
+                       d=${DIRSTACK[0]}
+                       unset DIRSTACK[0]
+                       set -A DIRSTACK -- "${DIRSTACK[@]}" "$d"
+               done
+               cd_csh "${DIRSTACK[0]}" || return 1
+       else
+               set -A DIRSTACK -- placeholder "${DIRSTACK[@]}"
+               cd_csh "$1" || return 1
+       fi
+       (( isnoglob )) || set +o noglob
+       dirs $fa
+}
+
+# pager (not control character safe)
+function smores {
+       local dummy line llen curlin=0
+
+       cat "$@" | while IFS= read -r line; do
+               llen=${%line}
+               (( llen == -1 )) && llen=${#line}
+               (( llen = llen ? (llen + COLUMNS - 1) / COLUMNS : 1 ))
+               if (( (curlin += llen) >= LINES )); then
+                       print -n -- '\033[7m--more--\033[0m'
+                       read -u1 dummy
+                       [[ $dummy = [Qq]* ]] && return 0
+                       curlin=$llen
+               fi
+               print -r -- "$line"
+       done
+}
+
+# base64 encoder and decoder, RFC compliant, NUL safe
+function Lb64decode {
+       [[ -o utf8-mode ]]; local u=$?
+       set +U
+       local c s="$*" t=
+       [[ -n $s ]] || { s=$(cat;print x); s=${s%x}; }
+       local -i i=0 n=${#s} p=0 v x
+       local -i16 o
+
+       while (( i < n )); do
+               c=${s:(i++):1}
+               case $c {
+               (=)     break ;;
+               ([A-Z]) (( v = 1#$c - 65 )) ;;
+               ([a-z]) (( v = 1#$c - 71 )) ;;
+               ([0-9]) (( v = 1#$c + 4 )) ;;
+               (+)     v=62 ;;
+               (/)     v=63 ;;
+               (*)     continue ;;
+               }
+               (( x = (x << 6) | v ))
+               case $((p++)) {
+               (0)     continue ;;
+               (1)     (( o = (x >> 4) & 255 )) ;;
+               (2)     (( o = (x >> 2) & 255 )) ;;
+               (3)     (( o = x & 255 ))
+                       p=0
+                       ;;
+               }
+               t=$t\\x${o#16#}
+       done
+       print -n $t
+       (( u )) || set -U
+}
+
+set -A Lb64encode_code -- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z \
+    a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9 + /
+function Lb64encode {
+       [[ -o utf8-mode ]]; local u=$?
+       set +U
+       local c s t
+       if (( $# )); then
+               read -raN-1 s <<<"$*"
+               unset s[${#s[*]}-1]
+       else
+               read -raN-1 s
+       fi
+       local -i i=0 n=${#s[*]} j v
+
+       while (( i < n )); do
+               (( v = s[i++] << 16 ))
+               (( j = i < n ? s[i++] : 0 ))
+               (( v |= j << 8 ))
+               (( j = i < n ? s[i++] : 0 ))
+               (( v |= j ))
+               t=$t${Lb64encode_code[v >> 18]}${Lb64encode_code[v >> 12 & 63]}
+               c=${Lb64encode_code[v >> 6 & 63]}
+               if (( i <= n )); then
+                       t=$t$c${Lb64encode_code[v & 63]}
+               elif (( i == n + 1 )); then
+                       t=$t$c=
+               else
+                       t=$t==
+               fi
+               if (( ${#t} == 76 || i >= n )); then
+                       print $t
+                       t=
+               fi
+       done
+       (( u )) || set -U
+}
+
+# mksh NUL counting, never zero
+typeset -Z11 -Uui16 Lnzathash_v
+function Lnzathash_add {
+       [[ -o utf8-mode ]]; local u=$?
+       set +U
+       local s
+       if (( $# )); then
+               read -raN-1 s <<<"$*"
+               unset s[${#s[*]}-1]
+       else
+               read -raN-1 s
+       fi
+       local -i i=0 n=${#s[*]}
+
+       while (( i < n )); do
+               ((# Lnzathash_v = (Lnzathash_v + s[i++] + 1) * 1025 ))
+               ((# Lnzathash_v ^= Lnzathash_v >> 6 ))
+       done
+
+       (( u )) || set -U
+}
+function Lnzaathash_end {
+       ((# Lnzathash_v *= 1025 ))
+       ((# Lnzathash_v ^= Lnzathash_v >> 6 ))
+       ((# Lnzathash_v += Lnzathash_v << 3 ))
+       ((# Lnzathash_v = (Lnzathash_v ^
+           (Lnzathash_v >> 11)) * 32769 ))
+       print ${Lnzathash_v#16#}
+}
+function Lnzaathash {
+       Lnzathash_v=0
+       Lnzathash_add "$@"
+       Lnzaathash_end
+}
+function Lnzathash {
+       Lnzathash_v=0
+       Lnzathash_add "$@"
+       if (( Lnzathash_v )); then
+               Lnzaathash_end
+       else
+               Lnzathash_v=1
+               print ${Lnzathash_v#16#}
+       fi
+}
+
+# strip comments (and leading/trailing whitespace if IFS is set) from
+# any file(s) given as argument, or stdin if none, and spew to stdout
+function Lstripcom {
+       cat "$@" | { set -o noglob; while read _line; do
+               _line=${_line%%#*}
+               [[ -n $_line ]] && print -r -- $_line
+       done; }
+}
+
+# give MidnightBSD's laffer1 a bit of csh feeling
+function setenv {
+       eval export $1'="$2"'
+}
+
+: place customisations below this line
+
+for p in ~/.etc/bin ~/bin; do
+       [[ -d $p/. ]] || continue
+       [[ :$PATH: = *:$p:* ]] || PATH=$p:$PATH
+done
+
+export SHELL=$MKSH MANWIDTH=80 LESSHISTFILE=-
+alias cls='print -n \\033c'
+
+#unset LANGUAGE LC_ADDRESS LC_ALL LC_COLLATE LC_IDENTIFICATION LC_MONETARY \
+#    LC_NAME LC_NUMERIC LC_TELEPHONE LC_TIME
+#p=en_GB.UTF-8
+#set -U
+#export LANG=C LC_CTYPE=$p LC_MEASUREMENT=$p LC_MESSAGES=$p LC_PAPER=$p
+
+unset p
+
+: place customisations above this line
index 905de7e..8242563 100644 (file)
@@ -1,10 +1,10 @@
 /*     $OpenBSD: edit.c,v 1.34 2010/05/20 01:13:07 fgsch Exp $ */
-/*     $OpenBSD: edit.h,v 1.8 2005/03/28 21:28:22 deraadt Exp $        */
-/*     $OpenBSD: emacs.c,v 1.42 2009/06/02 06:47:47 halex Exp $        */
+/*     $OpenBSD: edit.h,v 1.9 2011/05/30 17:14:35 martynas Exp $       */
+/*     $OpenBSD: emacs.c,v 1.44 2011/09/05 04:50:33 marco Exp $        */
 /*     $OpenBSD: vi.c,v 1.26 2009/06/29 22:50:19 martynas Exp $        */
 
 /*-
- * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
  *     Thorsten Glaser <tg@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -25,7 +25,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/edit.c,v 1.196 2010/07/25 11:35:40 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/edit.c,v 1.222 2011/10/07 19:45:08 tg Exp $");
 
 /*
  * in later versions we might use libtermcap for this, but since external
@@ -52,11 +52,14 @@ typedef struct {
 
 static X_chars edchars;
 
-/* x_fc_glob() flags */
+/* x_cf_glob() flags */
 #define XCF_COMMAND    BIT(0)  /* Do command completion */
 #define XCF_FILE       BIT(1)  /* Do file completion */
 #define XCF_FULLPATH   BIT(2)  /* command completion: store full path */
-#define XCF_COMMAND_FILE (XCF_COMMAND|XCF_FILE)
+#define XCF_COMMAND_FILE (XCF_COMMAND | XCF_FILE)
+#define XCF_IS_COMMAND BIT(3)  /* return flag: is command */
+#define XCF_IS_SUBGLOB BIT(4)  /* return flag: is $FOO or ~foo substitution */
+#define XCF_IS_EXTGLOB BIT(5)  /* return flag: is foo* expansion */
 
 static char editmode;
 static int xx_cols;                    /* for Emacs mode */
@@ -65,12 +68,11 @@ static char holdbuf[LINE];          /* place to hold last edit buffer */
 
 static int x_getc(void);
 static void x_putcf(int);
-static bool x_mode(bool);
-static int x_do_comment(char *, int, int *);
+static void x_mode(bool);
+static int x_do_comment(char *, ssize_t, ssize_t *);
 static void x_print_expansions(int, char *const *, bool);
-static int x_cf_glob(int, const char *, int, int, int *, int *, char ***,
-    bool *);
-static int x_longest_prefix(int, char *const *);
+static int x_cf_glob(int *, const char *, int, int, int *, int *, char ***);
+static size_t x_longest_prefix(int, char *const *);
 static int x_basename(const char *, const char *);
 static void x_free_words(int, char **);
 static int x_escape(const char *, size_t, int (*)(const char *, size_t));
@@ -89,17 +91,10 @@ static int x_vi(char *, size_t);
 #endif
 
 static int path_order_cmp(const void *aa, const void *bb);
-static char *add_glob(const char *, int)
-    MKSH_A_NONNULL((nonnull (1)))
-    MKSH_A_BOUNDED(string, 1, 2);
 static void glob_table(const char *, XPtrV *, struct table *);
 static void glob_path(int flags, const char *, XPtrV *, const char *);
-static int x_file_glob(int, const char *, int, char ***)
-    MKSH_A_NONNULL((nonnull (2)))
-    MKSH_A_BOUNDED(string, 2, 3);
-static int x_command_glob(int, const char *, int, char ***)
-    MKSH_A_NONNULL((nonnull (2)))
-    MKSH_A_BOUNDED(string, 2, 3);
+static int x_file_glob(int, char *, char ***);
+static int x_command_glob(int, char *, char ***);
 static int x_locate_word(const char *, int, int, int *, bool *);
 
 static int x_e_getmbc(char *);
@@ -111,11 +106,14 @@ static int x_e_rebuildline(const char *);
 void
 x_init(void)
 {
-       /* set to -2 to force initial binding */
+       /*
+        * Set edchars to -2 to force initial binding, except
+        * we need default values for some deficient systems…
+        */
        edchars.erase = edchars.kill = edchars.intr = edchars.quit =
            edchars.eof = -2;
-       /* default value for deficient systems */
-       edchars.werase = 027;   /* ^W */
+       /* ^W */
+       edchars.werase = 027;
        x_init_emacs();
 }
 
@@ -136,7 +134,8 @@ x_read(char *buf, size_t len)
                i = x_vi(buf, len);
 #endif
        else
-               i = -1;         /* internal error */
+               /* internal error */
+               i = -1;
        editmode = 0;
        x_mode(false);
        return (i);
@@ -148,7 +147,7 @@ static int
 x_getc(void)
 {
        char c;
-       int n;
+       ssize_t n;
 
        while ((n = blocking_read(STDIN_FILENO, &c, 1)) < 0 && errno == EINTR)
                if (trap) {
@@ -179,7 +178,8 @@ x_putcf(int c)
  * Misc common code for vi/emacs *
  *********************************/
 
-/* Handle the commenting/uncommenting of a line.
+/*-
+ * Handle the commenting/uncommenting of a line.
  * Returns:
  *     1 if a carriage return is indicated (comment added)
  *     0 if no return (comment removed)
@@ -188,12 +188,13 @@ x_putcf(int c)
  * moved to the start of the line after (un)commenting.
  */
 static int
-x_do_comment(char *buf, int bsize, int *lenp)
+x_do_comment(char *buf, ssize_t bsize, ssize_t *lenp)
 {
-       int i, j, len = *lenp;
+       ssize_t i, j, len = *lenp;
 
        if (len == 0)
-               return (1); /* somewhat arbitrary - it's what AT&T ksh does */
+               /* somewhat arbitrary - it's what AT&T ksh does */
+               return (1);
 
        /* Already commented? */
        if (buf[0] == '#') {
@@ -238,7 +239,8 @@ x_print_expansions(int nwords, char * const *words, bool is_command)
        int prefix_len;
        XPtrV l = { NULL, NULL, NULL };
 
-       /* Check if all matches are in the same directory (in this
+       /*
+        * Check if all matches are in the same directory (in this
         * case, we want to omit the directory name)
         */
        if (!is_command &&
@@ -272,7 +274,8 @@ x_print_expansions(int nwords, char * const *words, bool is_command)
        pr_list(use_copy ? (char **)XPptrv(l) : words);
 
        if (use_copy)
-               XPfree(l); /* not x_free_words() */
+               /* not x_free_words() */
+               XPfree(l);
 }
 
 /**
@@ -283,19 +286,14 @@ x_print_expansions(int nwords, char * const *words, bool is_command)
  *     - returns number of matching strings
  */
 static int
-x_file_glob(int flags MKSH_A_UNUSED, const char *str, int slen, char ***wordsp)
+x_file_glob(int flags MKSH_A_UNUSED, char *toglob, char ***wordsp)
 {
-       char *toglob, **words;
+       char **words;
        int nwords, i, idx;
        bool escaping;
        XPtrV w;
        struct source *s, *sold;
 
-       if (slen < 0)
-               return (0);
-
-       toglob = add_glob(str, slen);
-
        /* remove all escaping backward slashes */
        escaping = false;
        for (i = 0, idx = 0; toglob[i]; i++) {
@@ -324,7 +322,7 @@ x_file_glob(int flags MKSH_A_UNUSED, const char *str, int slen, char ***wordsp)
        source = s;
        if (yylex(ONEWORD | LQCHAR) != LWORD) {
                source = sold;
-               internal_warningf("fileglob: substitute error");
+               internal_warningf("%s: %s", "fileglob", "bad substitution");
                return (0);
        }
        source = sold;
@@ -338,8 +336,9 @@ x_file_glob(int flags MKSH_A_UNUSED, const char *str, int slen, char ***wordsp)
        if (nwords == 1) {
                struct stat statb;
 
-               /* Check if globbing failed (returned glob pattern),
-                * but be careful (E.g. toglob == "ab*" when the file
+               /*
+                * Check if globbing failed (returned glob pattern),
+                * but be careful (e.g. toglob == "ab*" when the file
                 * "ab*" exists is not an error).
                 * Also, check for empty result - happens if we tried
                 * to glob something which evaluated to an empty
@@ -353,7 +352,6 @@ x_file_glob(int flags MKSH_A_UNUSED, const char *str, int slen, char ***wordsp)
                        nwords = 0;
                }
        }
-       afree(toglob, ATEMP);
 
        if ((*wordsp = nwords ? words : NULL) == NULL && words != NULL)
                x_free_words(nwords, words);
@@ -381,21 +379,15 @@ path_order_cmp(const void *aa, const void *bb)
 }
 
 static int
-x_command_glob(int flags, const char *str, int slen, char ***wordsp)
+x_command_glob(int flags, char *toglob, char ***wordsp)
 {
-       char *toglob, *pat, *fpath;
+       char *pat, *fpath;
        int nwords;
        XPtrV w;
        struct block *l;
 
-       if (slen < 0)
-               return (0);
-
-       toglob = add_glob(str, slen);
-
        /* Convert "foo*" (toglob) to a pattern for future use */
        pat = evalstr(toglob, DOPAT | DOTILDE);
-       afree(toglob, ATEMP);
 
        XPinit(w, 32);
 
@@ -424,7 +416,7 @@ x_command_glob(int flags, const char *str, int slen, char ***wordsp)
                int i, path_order = 0;
 
                info = (struct path_order_info *)
-                   alloc(nwords * sizeof(struct path_order_info), ATEMP);
+                   alloc2(nwords, sizeof(struct path_order_info), ATEMP);
                for (i = 0; i < nwords; i++) {
                        info[i].word = words[i];
                        info[i].base = x_basename(words[i], NULL);
@@ -481,7 +473,8 @@ x_locate_word(const char *buf, int buflen, int pos, int *startp,
        /* The case where pos == buflen happens to take care of itself... */
 
        start = pos;
-       /* Keep going backwards to start of word (has effect of allowing
+       /*
+        * Keep going backwards to start of word (has effect of allowing
         * one blank after the end of a word)
         */
        for (; (start > 0 && IS_WORDC(buf[start - 1])) ||
@@ -502,7 +495,8 @@ x_locate_word(const char *buf, int buflen, int pos, int *startp,
                        p--;
                iscmd = p < 0 || vstrchr(";|&()`", buf[p]);
                if (iscmd) {
-                       /* If command has a /, path, etc. is not searched;
+                       /*
+                        * If command has a /, path, etc. is not searched;
                         * only current directory is searched which is just
                         * like file globbing.
                         */
@@ -519,86 +513,105 @@ x_locate_word(const char *buf, int buflen, int pos, int *startp,
 }
 
 static int
-x_cf_glob(int flags, const char *buf, int buflen, int pos, int *startp,
-    int *endp, char ***wordsp, bool *is_commandp)
+x_cf_glob(int *flagsp, const char *buf, int buflen, int pos, int *startp,
+    int *endp, char ***wordsp)
 {
-       int len, nwords;
+       int len, nwords = 0;
        char **words = NULL;
        bool is_command;
 
        len = x_locate_word(buf, buflen, pos, startp, &is_command);
-       if (!(flags & XCF_COMMAND))
+       if (!((*flagsp) & XCF_COMMAND))
                is_command = false;
-       /* Don't do command globing on zero length strings - it takes too
+       /*
+        * Don't do command globing on zero length strings - it takes too
         * long and isn't very useful. File globs are more likely to be
         * useful, so allow these.
         */
        if (len == 0 && is_command)
                return (0);
 
-       nwords = is_command ?
-           x_command_glob(flags, buf + *startp, len, &words) :
-           x_file_glob(flags, buf + *startp, len, &words);
+       if (len >= 0) {
+               char *toglob, *s;
+               bool saw_dollar = false, saw_glob = false;
+
+               /*
+                * Given a string, copy it and possibly add a '*' to the end.
+                */
+
+               strndupx(toglob, buf + *startp, len + /* the '*' */ 1, ATEMP);
+               toglob[len] = '\0';
+
+               /*
+                * If the pathname contains a wildcard (an unquoted '*',
+                * '?', or '[') or parameter expansion ('$'), or a ~username
+                * with no trailing slash, then it is globbed based on that
+                * value (i.e., without the appended '*').
+                */
+               for (s = toglob; *s; s++) {
+                       if (*s == '\\' && s[1])
+                               s++;
+                       else if (*s == '$') {
+                               /*
+                                * Do not append a space after the value
+                                * if expanding a parameter substitution
+                                * as in: “cat $HOME/.ss↹” (LP: #710539)
+                                */
+                               saw_dollar = true;
+                       } else if (*s == '?' || *s == '*' || *s == '[' ||
+                           /* ?() *() +() @() !() but two already checked */
+                           (s[1] == '(' /*)*/ &&
+                           (*s == '+' || *s == '@' || *s == '!'))) {
+                               /* just expand based on the extglob */
+                               saw_glob = true;
+                       }
+               }
+               if (saw_glob) {
+                       /*
+                        * do not append a glob, we already have a
+                        * glob or extglob; it works even if this is
+                        * a parameter expansion as we have a glob
+                        */
+                       *flagsp |= XCF_IS_EXTGLOB;
+               } else if (saw_dollar ||
+                   (*toglob == '~' && !vstrchr(toglob, '/'))) {
+                       /* do not append a glob, nor later a space */
+                       *flagsp |= XCF_IS_SUBGLOB;
+               } else {
+                       /* append a glob, this is not just a tilde */
+                       toglob[len] = '*';
+                       toglob[len + 1] = '\0';
+               }
+
+               /*
+                * Expand (glob) it now.
+                */
+
+               nwords = is_command ?
+                   x_command_glob(*flagsp, toglob, &words) :
+                   x_file_glob(*flagsp, toglob, &words);
+               afree(toglob, ATEMP);
+       }
        if (nwords == 0) {
                *wordsp = NULL;
                return (0);
        }
-       if (is_commandp)
-               *is_commandp = is_command;
+       if (is_command)
+               *flagsp |= XCF_IS_COMMAND;
        *wordsp = words;
        *endp = *startp + len;
 
        return (nwords);
 }
 
-/* Given a string, copy it and possibly add a '*' to the end.
- * The new string is returned.
- */
-static char *
-add_glob(const char *str, int slen)
-{
-       char *toglob, *s;
-       bool saw_slash = false;
-
-       if (slen < 0)
-               return (NULL);
-
-       /* for clang's static analyser, the nonnull attribute isn't enough */
-       mkssert(str != NULL);
-
-       strndupx(toglob, str, slen + 1, ATEMP); /* + 1 for "*" */
-       toglob[slen] = '\0';
-
-       /*
-        * If the pathname contains a wildcard (an unquoted '*',
-        * '?', or '[') or parameter expansion ('$'), or a ~username
-        * with no trailing slash, then it is globbed based on that
-        * value (i.e., without the appended '*').
-        */
-       for (s = toglob; *s; s++) {
-               if (*s == '\\' && s[1])
-                       s++;
-               else if (*s == '*' || *s == '[' || *s == '?' || *s == '$' ||
-                   (s[1] == '(' /*)*/ && /* *s in '*','?' already checked */
-                   (*s == '+' || *s == '@' || *s == '!')))
-                       break;
-               else if (*s == '/')
-                       saw_slash = true;
-       }
-       if (!*s && (*toglob != '~' || saw_slash)) {
-               toglob[slen] = '*';
-               toglob[slen + 1] = '\0';
-       }
-       return (toglob);
-}
-
 /*
  * Find longest common prefix
  */
-static int
+static size_t
 x_longest_prefix(int nwords, char * const * words)
 {
-       int i, j, prefix_len;
+       int i;
+       size_t j, prefix_len;
        char *p;
 
        if (nwords <= 0)
@@ -622,7 +635,8 @@ x_free_words(int nwords, char **words)
        afree(words, ATEMP);
 }
 
-/* Return the offset of the basename of string s (which ends at se - need not
+/*-
+ * Return the offset of the basename of string s (which ends at se - need not
  * be null terminated). Trailing slashes are ignored. If s is just a slash,
  * then the offset is 0 (actually, length - 1).
  *     s               Return
@@ -678,13 +692,14 @@ glob_table(const char *pat, XPtrV *wp, struct table *tp)
 static void
 glob_path(int flags, const char *pat, XPtrV *wp, const char *lpath)
 {
-       const char *sp, *p;
+       const char *sp = lpath, *p;
        char *xp, **words;
-       int staterr, pathlen, patlen, oldsize, newsize, i, j;
+       size_t pathlen, patlen, oldsize, newsize, i, j;
        XString xs;
 
-       patlen = strlen(pat) + 1;
-       sp = lpath;
+       patlen = strlen(pat);
+       checkoktoadd(patlen, 129 + X_EXTRA);
+       ++patlen;
        Xinit(xs, xp, patlen + 128, ATEMP);
        while (sp) {
                xp = Xstring(xs, xp);
@@ -692,7 +707,8 @@ glob_path(int flags, const char *pat, XPtrV *wp, const char *lpath)
                        p = sp + strlen(sp);
                pathlen = p - sp;
                if (pathlen) {
-                       /* Copy sp into xp, stuffing any MAGIC characters
+                       /*
+                        * Copy sp into xp, stuffing any MAGIC characters
                         * on the way
                         */
                        const char *s = sp;
@@ -711,15 +727,14 @@ glob_path(int flags, const char *pat, XPtrV *wp, const char *lpath)
                memcpy(xp, pat, patlen);
 
                oldsize = XPsize(*wp);
-               glob_str(Xstring(xs, xp), wp, 1); /* mark dirs */
+               /* mark dirs */
+               glob_str(Xstring(xs, xp), wp, 1);
                newsize = XPsize(*wp);
 
                /* Check that each match is executable... */
                words = (char **)XPptrv(*wp);
                for (i = j = oldsize; i < newsize; i++) {
-                       staterr = 0;
-                       if ((search_access(words[i], X_OK, &staterr) >= 0) ||
-                           (staterr == EISDIR)) {
+                       if (ksh_access(words[i], X_OK) == 0) {
                                words[j] = words[i];
                                if (!(flags & XCF_FULLPATH))
                                        memmove(words[j], words[j] + pathlen,
@@ -807,7 +822,8 @@ struct x_defbindings {
 #define X_NTABS                3                       /* normal, meta1, meta2 */
 #define X_TABSZ                256                     /* size of keydef tables etc */
 
-/* Arguments for do_complete()
+/*-
+ * Arguments for do_complete()
  * 0 = enumerate       M-=     complete as much as possible and then list
  * 1 = complete                M-Esc
  * 2 = list            M-?
@@ -837,7 +853,7 @@ static int x_adj_done;
 static int x_col;
 static int x_displen;
 static int x_arg;              /* general purpose arg */
-static int x_arg_defaulted;    /* x_arg not explicitly set; defaulted to 1 */
+static bool x_arg_defaulted;   /* x_arg not explicitly set; defaulted to 1 */
 
 static int xlp_valid;
 
@@ -855,7 +871,7 @@ static char *killstack[KILLSIZE];
 static int killsp, killtp;
 static int x_curprefix;
 #ifndef MKSH_SMALL
-static char *macroptr = NULL;  /* bind key macro active? */
+static char *macroptr;         /* bind key macro active? */
 #endif
 #if !MKSH_S_NOVI
 static int cur_col;            /* current column on line */
@@ -891,7 +907,7 @@ static int x_match(char *, char *);
 static void x_redraw(int);
 static void x_push(int);
 static char *x_mapin(const char *, Area *)
-    MKSH_A_NONNULL((nonnull (1)));
+    MKSH_A_NONNULL((__nonnull__ (1)));
 static char *x_mapout(int);
 static void x_mapout2(int, char **);
 static void x_print(int, int);
@@ -998,7 +1014,8 @@ static struct x_defbindings const x_defbindings[] = {
        { XFUNC_fold_capitalise,        1,      'C'     },
        { XFUNC_fold_capitalise,        1,      'c'     },
 #endif
-       /* These for ansi arrow keys: arguablely shouldn't be here by
+       /*
+        * These for ANSI arrow keys: arguablely shouldn't be here by
         * default, but its simpler/faster/smaller than using termcap
         * entries.
         */
@@ -1120,7 +1137,7 @@ x_emacs(char *buf, size_t len)
                x_nextcmd = -1;
        }
        editmode = 1;
-       while (1) {
+       while (/* CONSTCOND */ 1) {
                x_flush();
                if ((c = x_e_getc()) < 0)
                        return (0);
@@ -1142,7 +1159,7 @@ x_emacs(char *buf, size_t len)
                if (!(x_ftab[f].xf_flags & XF_PREFIX) &&
                    x_last_command != XFUNC_set_arg) {
                        x_arg = 1;
-                       x_arg_defaulted = 1;
+                       x_arg_defaulted = true;
                }
                i = c | (x_curprefix << 8);
                x_curprefix = 0;
@@ -1154,7 +1171,8 @@ x_emacs(char *buf, size_t len)
                case KEOL:
                        i = xep - xbuf;
                        return (i);
-               case KINTR:     /* special case for interrupt */
+               case KINTR:
+                       /* special case for interrupt */
                        trapsig(SIGINT);
                        x_mode(false);
                        unwind(LSHELL);
@@ -1167,7 +1185,7 @@ x_emacs(char *buf, size_t len)
 static int
 x_insert(int c)
 {
-       static int left = 0, pos, save_arg;
+       static int left, pos, save_arg;
        static char str[4];
 
        /*
@@ -1265,7 +1283,8 @@ x_ins(const char *s)
        x_lastcp();
        x_adj_ok = (xcp >= xlp);
        x_zots(cp);
-       if (adj == x_adj_done) {        /* has x_adjust() been called? */
+       /* has x_adjust() been called? */
+       if (adj == x_adj_done) {
                /* no */
                cp = xlp;
                while (cp > xcp)
@@ -1354,13 +1373,15 @@ x_delete(int nc, int push)
                x_push(nb);
 
        xep -= nb;
-       memmove(xcp, xcp + nb, xep - xcp + 1);  /* Copies the NUL */
-       x_adj_ok = 0;                   /* don't redraw */
+       /* Copies the NUL */
+       memmove(xcp, xcp + nb, xep - xcp + 1);
+       /* don't redraw */
+       x_adj_ok = 0;
        xlp_valid = false;
        x_zots(xcp);
        /*
         * if we are already filling the line,
-        * there is no need to ' ','\b'.
+        * there is no need to ' ', '\b'.
         * But if we must, make sure we do the minimum.
         */
        if ((i = xx_cols - 2 - x_col) > 0 || xep - xlp == 0) {
@@ -1472,10 +1493,12 @@ x_goto(char *cp)
                /* we are heading off screen */
                xcp = cp;
                x_adjust();
-       } else if (cp < xcp) {          /* move back */
+       } else if (cp < xcp) {
+               /* move back */
                while (cp < xcp)
                        x_bs3(&xcp);
-       } else if (cp > xcp) {          /* move forward */
+       } else if (cp > xcp) {
+               /* move forward */
                while (cp > xcp)
                        x_zotc3(&xcp);
        }
@@ -1515,9 +1538,11 @@ x_size2(char *cp, char **dcp)
        if (dcp)
                *dcp = cp + 1;
        if (c == '\t')
-               return (4);     /* Kludge, tabs are always four spaces. */
+               /* Kludge, tabs are always four spaces. */
+               return (4);
        if (c < ' ' || c == 0x7f)
-               return (2);     /* control unsigned char */
+               /* control unsigned char */
+               return (2);
        return (1);
 }
 
@@ -1700,7 +1725,8 @@ x_next_com(int c MKSH_A_UNUSED)
        return (KSTD);
 }
 
-/* Goto a particular history number obtained from argument.
+/*
+ * Goto a particular history number obtained from argument.
  * If no argument is given history 1 is probably not what you
  * want so we'll simply go to the oldest one.
  */
@@ -1770,7 +1796,7 @@ x_search_hist(int c)
        unsigned char f;
 
        *p = '\0';
-       while (1) {
+       while (/* CONSTCOND */ 1) {
                if (offset < 0) {
                        x_e_puts("\nI-search: ");
                        x_e_puts(pat);
@@ -1833,7 +1859,8 @@ x_search_hist(int c)
                        if (offset >= 0)
                                x_load_hist(histptr + 1);
                        break;
-               } else { /* other command */
+               } else {
+                       /* other command */
                        x_e_ungetc(c);
                        break;
                }
@@ -1965,7 +1992,8 @@ x_cls(int c MKSH_A_UNUSED)
        return (x_e_rebuildline(MKSH_CLS_STRING));
 }
 
-/* Redraw (part of) the line. If limit is < 0, the everything is redrawn
+/*
+ * Redraw (part of) the line. If limit is < 0, the everything is redrawn
  * on a NEW line, otherwise limit is the screen column up to which needs
  * redrawing.
  */
@@ -2002,7 +2030,8 @@ x_redraw(int limit)
                limit = xx_cols;
        if (limit >= 0) {
                if (xep > xlp)
-                       i = 0;                  /* we fill the line */
+                       /* we fill the line */
+                       i = 0;
                else {
                        char *cpl = xbp;
 
@@ -2019,7 +2048,8 @@ x_redraw(int limit)
                        j++;
                }
                i = ' ';
-               if (xep > xlp) {                /* more off screen */
+               if (xep > xlp) {
+                       /* more off screen */
                        if (xbp > xbuf)
                                i = '*';
                        else
@@ -2043,7 +2073,8 @@ x_transpose(int c MKSH_A_UNUSED)
 {
        unsigned int tmpa, tmpb;
 
-       /* What transpose is meant to do seems to be up for debate. This
+       /*-
+        * What transpose is meant to do seems to be up for debate. This
         * is a general summary of the options; the text is abcd with the
         * upper case character or underscore indicating the cursor position:
         *      Who                     Before  After   Before  After
@@ -2064,8 +2095,9 @@ x_transpose(int c MKSH_A_UNUSED)
                        x_e_putc2(7);
                        return (KSTD);
                }
-               /* Gosling/Unipress emacs style: Swap two characters before the
-                * cursor, do not change cursor position
+               /*
+                * Gosling/Unipress emacs style: Swap two characters before
+                * the cursor, do not change cursor position
                 */
                x_bs3(&xcp);
                if (utf_mbtowc(&tmpa, xcp) == (size_t)-1) {
@@ -2082,7 +2114,8 @@ x_transpose(int c MKSH_A_UNUSED)
                utf_wctomb(xcp, tmpb);
                x_zotc3(&xcp);
        } else {
-               /* GNU emacs style: Swap the characters before and under the
+               /*
+                * GNU emacs style: Swap the characters before and under the
                 * cursor, move cursor position along one.
                 */
                if (utf_mbtowc(&tmpa, xcp) == (size_t)-1) {
@@ -2177,7 +2210,7 @@ x_yank(int c MKSH_A_UNUSED)
 static int
 x_meta_yank(int c MKSH_A_UNUSED)
 {
-       int len;
+       size_t len;
 
        if ((x_last_command != XFUNC_yank && x_last_command != XFUNC_meta_yank) ||
            killstack[killtp] == 0) {
@@ -2230,7 +2263,7 @@ x_vt_hack(int c)
        switch ((c = x_e_getc())) {
        case '~':
                x_arg = 1;
-               x_arg_defaulted = 1;
+               x_arg_defaulted = true;
                return (x_mv_begin(0));
        case ';':
                /* "interesting" sequence detected */
@@ -2245,7 +2278,7 @@ x_vt_hack(int c)
 
        /*-
         * At this point, we have read the following octets so far:
-        * - ESC+[ or ESC+O or Ctrl-X (Præfix 2)
+        * - ESC+[ or ESC+O or Ctrl-X (Prefix 2)
         * - 1 (vt_hack)
         * - ;
         * - 5 (Ctrl key combiner) or 3 (Alt key combiner)
@@ -2279,7 +2312,8 @@ x_mapin(const char *cp, Area *ap)
                /* XXX -- should handle \^ escape? */
                if (*cp == '^') {
                        cp++;
-                       if (*cp >= '?') /* includes '?'; ASCII */
+                       if (*cp >= '?')
+                               /* includes '?'; ASCII */
                                *op++ = CTRL(*cp);
                        else {
                                *op++ = '^';
@@ -2343,9 +2377,11 @@ x_print(int prefix, int key)
 int
 x_bind(const char *a1, const char *a2,
 #ifndef MKSH_SMALL
-    bool macro,                        /* bind -m */
+    /* bind -m */
+    bool macro,
 #endif
-    bool list)                 /* bind -l */
+    /* bind -l */
+    bool list)
 {
        unsigned char f;
        int prefix, key;
@@ -2356,7 +2392,7 @@ x_bind(const char *a1, const char *a2,
 #endif
 
        if (x_tab == NULL) {
-               bi_errorf("cannot bind, not a tty");
+               bi_errorf("can't bind, not a tty");
                return (1);
        }
        /* List function names */
@@ -2398,16 +2434,16 @@ x_bind(const char *a1, const char *a2,
            && ((*m1 != '~') || *(m1 + 1))
 #endif
            ) {
-               char msg[256] = "key sequence '";
+               char msg[256];
                const char *c = a1;
-               m1 = msg + strlen(msg);
+               m1 = msg;
                while (*c && m1 < (msg + sizeof(msg) - 3))
                        x_mapout2(*c++, &m1);
-               bi_errorf("%s' too long", msg);
+               bi_errorf("%s: %s", "too long key sequence", msg);
                return (1);
        }
 #ifndef MKSH_SMALL
-       hastilde = *m1;
+       hastilde = tobool(*m1);
 #endif
        afree(m2, ATEMP);
 
@@ -2428,7 +2464,7 @@ x_bind(const char *a1, const char *a2,
                            strcmp(x_ftab[f].xf_name, a2) == 0)
                                break;
                if (f == NELEM(x_ftab) || x_ftab[f].xf_flags & XF_NOBIND) {
-                       bi_errorf("%s: no such function", a2);
+                       bi_errorf("%s: %s %s", a2, "no such", Tfunction);
                        return (1);
                }
        }
@@ -2466,7 +2502,7 @@ x_init_emacs(void)
        ainit(AEDIT);
        x_nextcmd = -1;
 
-       x_tab = alloc(X_NTABS * sizeof(*x_tab), AEDIT);
+       x_tab = alloc2(X_NTABS, sizeof(*x_tab), AEDIT);
        for (j = 0; j < X_TABSZ; j++)
                x_tab[0][j] = XFUNC_insert;
        for (i = 1; i < X_NTABS; i++)
@@ -2477,7 +2513,7 @@ x_init_emacs(void)
                    = x_defbindings[i].xdb_func;
 
 #ifndef MKSH_SMALL
-       x_atab = alloc(X_NTABS * sizeof(*x_atab), AEDIT);
+       x_atab = alloc2(X_NTABS, sizeof(*x_atab), AEDIT);
        for (i = 1; i < X_NTABS; i++)
                for (j = 0; j < X_TABSZ; j++)
                        x_atab[i][j] = NULL;
@@ -2603,10 +2639,10 @@ x_expand(int c MKSH_A_UNUSED)
 {
        char **words;
        int start, end, nwords, i;
-       bool is_command;
 
-       nwords = x_cf_glob(XCF_FILE, xbuf, xep - xbuf, xcp - xbuf,
-           &start, &end, &words, &is_command);
+       i = XCF_FILE;
+       nwords = x_cf_glob(&i, xbuf, xep - xbuf, xcp - xbuf,
+           &start, &end, &words);
 
        if (nwords == 0) {
                x_e_putc2(7);
@@ -2614,7 +2650,9 @@ x_expand(int c MKSH_A_UNUSED)
        }
        x_goto(xbuf + start);
        x_delete(end - start, false);
-       for (i = 0; i < nwords;) {
+
+       i = 0;
+       while (i < nwords) {
                if (x_escape(words[i], strlen(words[i]), x_do_ins) < 0 ||
                    (++i < nwords && x_ins(" ") < 0)) {
                        x_e_putc2(7);
@@ -2626,24 +2664,27 @@ x_expand(int c MKSH_A_UNUSED)
        return (KSTD);
 }
 
-/* type == 0 for list, 1 for complete and 2 for complete-list */
 static void
-do_complete(int flags, /* XCF_{COMMAND,FILE,COMMAND_FILE} */
+do_complete(
+    /* XCF_{COMMAND,FILE,COMMAND_FILE} */
+    int flags,
+    /* 0 for list, 1 for complete and 2 for complete-list */
     Comp_type type)
 {
        char **words;
        int start, end, nlen, olen, nwords;
-       bool is_command, completed = false;
+       bool completed = false;
 
-       nwords = x_cf_glob(flags, xbuf, xep - xbuf, xcp - xbuf,
-           &start, &end, &words, &is_command);
+       nwords = x_cf_glob(&flags, xbuf, xep - xbuf, xcp - xbuf,
+           &start, &end, &words);
        /* no match */
        if (nwords == 0) {
                x_e_putc2(7);
                return;
        }
        if (type == CT_LIST) {
-               x_print_expansions(nwords, words, is_command);
+               x_print_expansions(nwords, words,
+                   tobool(flags & XCF_IS_COMMAND));
                x_redraw(0);
                x_free_words(nwords, words);
                return;
@@ -2658,13 +2699,18 @@ do_complete(int flags,  /* XCF_{COMMAND,FILE,COMMAND_FILE} */
                x_adjust();
                completed = true;
        }
-       /* add space if single non-dir match */
-       if (nwords == 1 && words[0][nlen - 1] != '/') {
+       /*
+        * append a space if this is a single non-directory match
+        * and not a parameter or homedir substitution
+        */
+       if (nwords == 1 && words[0][nlen - 1] != '/' &&
+           !(flags & XCF_IS_SUBGLOB)) {
                x_ins(" ");
                completed = true;
        }
        if (type == CT_COMPLIST && !completed) {
-               x_print_expansions(nwords, words, is_command);
+               x_print_expansions(nwords, words,
+                   tobool(flags & XCF_IS_COMMAND));
                completed = true;
        }
        if (completed)
@@ -2673,7 +2719,8 @@ do_complete(int flags,    /* XCF_{COMMAND,FILE,COMMAND_FILE} */
        x_free_words(nwords, words);
 }
 
-/* NAME:
+/*-
+ * NAME:
  *     x_adjust - redraw the line adjusting starting point etc.
  *
  * DESCRIPTION:
@@ -2689,7 +2736,8 @@ do_complete(int flags,    /* XCF_{COMMAND,FILE,COMMAND_FILE} */
 static void
 x_adjust(void)
 {
-       x_adj_done++;                   /* flag the fact that we were called. */
+       /* flag the fact that we were called. */
+       x_adj_done++;
        /*
         * we had a problem if the prompt length > xx_cols / 2
         */
@@ -2817,7 +2865,8 @@ x_e_puts(const char *s)
                x_e_putc3(&s);
 }
 
-/* NAME:
+/*-
+ * NAME:
  *     x_set_arg - set an arg value for next function
  *
  * DESCRIPTION:
@@ -2829,19 +2878,28 @@ x_e_puts(const char *s)
 static int
 x_set_arg(int c)
 {
-       int n = 0, first = 1;
+       unsigned int n = 0;
+       bool first = true;
 
-       c &= 255;       /* strip command prefix */
-       for (; c >= 0 && ksh_isdigit(c); c = x_e_getc(), first = 0)
+       /* strip command prefix */
+       c &= 255;
+       while (c >= 0 && ksh_isdigit(c)) {
                n = n * 10 + (c - '0');
+               if (n > LINE)
+                       /* upper bound for repeat */
+                       goto x_set_arg_too_big;
+               c = x_e_getc();
+               first = false;
+       }
        if (c < 0 || first) {
+ x_set_arg_too_big:
                x_e_putc2(7);
                x_arg = 1;
-               x_arg_defaulted = 1;
+               x_arg_defaulted = true;
        } else {
                x_e_ungetc(c);
                x_arg = n;
-               x_arg_defaulted = 0;
+               x_arg_defaulted = false;
        }
        return (KSTD);
 }
@@ -2851,7 +2909,7 @@ static int
 x_comment(int c MKSH_A_UNUSED)
 {
        int oldsize = x_size_str(xbuf);
-       int len = xep - xbuf;
+       ssize_t len = xep - xbuf;
        int ret = x_do_comment(xbuf, xend - xbuf, &len);
 
        if (ret < 0)
@@ -2873,7 +2931,8 @@ x_version(int c MKSH_A_UNUSED)
 {
        char *o_xbuf = xbuf, *o_xend = xend;
        char *o_xbp = xbp, *o_xep = xep, *o_xcp = xcp;
-       int vlen, lim = x_lastcp() - xbp;
+       int lim = x_lastcp() - xbp;
+       size_t vlen;
        char *v;
 
        strdupx(v, KSH_VERSION, ATEMP);
@@ -2889,7 +2948,7 @@ x_version(int c MKSH_A_UNUSED)
        xbp = o_xbp;
        xep = o_xep;
        xcp = o_xcp;
-       x_redraw(vlen);
+       x_redraw((int)vlen);
 
        if (c < 0)
                return (KSTD);
@@ -2927,7 +2986,8 @@ x_edit_line(int c MKSH_A_UNUSED)
 }
 #endif
 
-/* NAME:
+/*-
+ * NAME:
  *     x_prev_histword - recover word from prev command
  *
  * DESCRIPTION:
@@ -2948,11 +3008,17 @@ x_prev_histword(int c MKSH_A_UNUSED)
 {
        char *rcp, *cp;
        char **xhp;
-       int m;
-
-       if (xmp && modified > 1)
-               x_kill_region(0);
-       m = modified ? modified : 1;
+       int m = 1;
+       /* -1 = defaulted; 0+ = argument */
+       static int last_arg = -1;
+
+       if (x_last_command == XFUNC_prev_histword) {
+               if (xmp && modified > 1)
+                       x_kill_region(0);
+               if (modified)
+                       m = modified;
+       } else
+               last_arg = x_arg_defaulted ? -1 : x_arg;
        xhp = histptr - (m - 1);
        if ((xhp < history) || !(cp = *xhp)) {
                x_e_putc2(7);
@@ -2960,7 +3026,9 @@ x_prev_histword(int c MKSH_A_UNUSED)
                return (KSTD);
        }
        x_set_mark(0);
-       if (x_arg_defaulted) {
+       if ((x_arg = last_arg) == -1) {
+               /* x_arg_defaulted */
+
                rcp = &cp[strlen(cp) - 1];
                /*
                 * ignore white-space after the last word
@@ -2973,6 +3041,7 @@ x_prev_histword(int c MKSH_A_UNUSED)
                        rcp++;
                x_ins(rcp);
        } else {
+               /* not x_arg_defaulted */
                char ch;
 
                rcp = cp;
@@ -2981,7 +3050,7 @@ x_prev_histword(int c MKSH_A_UNUSED)
                 */
                while (*rcp && is_cfs(*rcp))
                        rcp++;
-               while (x_arg-- > 1) {
+               while (x_arg-- > 0) {
                        while (*rcp && !is_cfs(*rcp))
                                rcp++;
                        while (*rcp && is_cfs(*rcp))
@@ -3021,7 +3090,8 @@ x_fold_capitalise(int c MKSH_A_UNUSED)
        return (x_fold_case('C'));
 }
 
-/* NAME:
+/*-
+ * NAME:
  *     x_fold_case - convert word to UPPER/lower/Capital case
  *
  * DESCRIPTION:
@@ -3051,9 +3121,11 @@ x_fold_case(int c)
                 * a different action than for the rest.
                 */
                if (cp != xep) {
-                       if (c == 'L')           /* lowercase */
+                       if (c == 'L')
+                               /* lowercase */
                                *cp = ksh_tolower(*cp);
-                       else                    /* uppercase, capitalise */
+                       else
+                               /* uppercase, capitalise */
                                *cp = ksh_toupper(*cp);
                        cp++;
                }
@@ -3061,9 +3133,11 @@ x_fold_case(int c)
                 * now for the rest of the word
                 */
                while (cp != xep && !is_mfs(*cp)) {
-                       if (c == 'U')           /* uppercase */
+                       if (c == 'U')
+                               /* uppercase */
                                *cp = ksh_toupper(*cp);
-                       else                    /* lowercase, capitalise */
+                       else
+                               /* lowercase, capitalise */
                                *cp = ksh_tolower(*cp);
                        cp++;
                }
@@ -3074,7 +3148,8 @@ x_fold_case(int c)
 }
 #endif
 
-/* NAME:
+/*-
+ * NAME:
  *     x_lastcp - last visible char
  *
  * SYNOPSIS:
@@ -3114,44 +3189,26 @@ x_lastcp(void)
        return (xlp);
 }
 
-static bool
+static void
 x_mode(bool onoff)
 {
        static bool x_cur_mode;
-       bool prev;
 
        if (x_cur_mode == onoff)
-               return (x_cur_mode);
-       prev = x_cur_mode;
+               return;
        x_cur_mode = onoff;
 
        if (onoff) {
-               struct termios cb;
+               x_mkraw(tty_fd, NULL, false);
 
-               cb = tty_state;
-
-               edchars.erase = cb.c_cc[VERASE];
-               edchars.kill = cb.c_cc[VKILL];
-               edchars.intr = cb.c_cc[VINTR];
-               edchars.quit = cb.c_cc[VQUIT];
-               edchars.eof = cb.c_cc[VEOF];
+               edchars.erase = tty_state.c_cc[VERASE];
+               edchars.kill = tty_state.c_cc[VKILL];
+               edchars.intr = tty_state.c_cc[VINTR];
+               edchars.quit = tty_state.c_cc[VQUIT];
+               edchars.eof = tty_state.c_cc[VEOF];
 #ifdef VWERASE
-               edchars.werase = cb.c_cc[VWERASE];
-#endif
-               cb.c_iflag &= ~(INLCR | ICRNL);
-               cb.c_lflag &= ~(ISIG | ICANON | ECHO);
-#if defined(VLNEXT) && defined(_POSIX_VDISABLE)
-               /* osf/1 processes lnext when ~icanon */
-               cb.c_cc[VLNEXT] = _POSIX_VDISABLE;
+               edchars.werase = tty_state.c_cc[VWERASE];
 #endif
-               /* sunos 4.1.x & osf/1 processes discard(flush) when ~icanon */
-#if defined(VDISCARD) && defined(_POSIX_VDISABLE)
-               cb.c_cc[VDISCARD] = _POSIX_VDISABLE;
-#endif
-               cb.c_cc[VTIME] = 0;
-               cb.c_cc[VMIN] = 1;
-
-               tcsetattr(tty_fd, TCSADRAIN, &cb);
 
 #ifdef _POSIX_VDISABLE
                /* Convert unset values to internal 'unset' value */
@@ -3183,8 +3240,6 @@ x_mode(bool onoff)
                        bind_if_not_bound(0, edchars.quit, XFUNC_noop);
        } else
                tcsetattr(tty_fd, TCSADRAIN, &tty_state);
-
-       return (prev);
 }
 
 #if !MKSH_S_NOVI
@@ -3194,10 +3249,10 @@ x_mode(bool onoff)
 
 struct edstate {
        char *cbuf;
-       int winleft;
-       int cbufsize;
-       int linelen;
-       int cursor;
+       ssize_t winleft;
+       ssize_t cbufsize;
+       ssize_t linelen;
+       ssize_t cursor;
 };
 
 static int vi_hook(int);
@@ -3210,7 +3265,7 @@ static void yank_range(int, int);
 static int bracktype(int);
 static void save_cbuf(void);
 static void restore_cbuf(void);
-static int putbuf(const char *, int, int);
+static int putbuf(const char *, ssize_t, int);
 static void del_range(int, int);
 static int findch(int, int, int, int);
 static int forwword(int);
@@ -3221,7 +3276,7 @@ static int Backword(int);
 static int Endword(int);
 static int grabhist(int, int);
 static int grabsearch(int, int, int, char *);
-static void redraw_line(int);
+static void redraw_line(bool);
 static void refresh(int);
 static int outofwin(void);
 static void rewindow(void);
@@ -3237,58 +3292,58 @@ static void vi_error(void);
 static void vi_macro_reset(void);
 static int x_vi_putbuf(const char *, size_t);
 
-#define C_     0x1             /* a valid command that isn't a M_, E_, U_ */
-#define M_     0x2             /* movement command (h, l, etc.) */
-#define E_     0x4             /* extended command (c, d, y) */
-#define X_     0x8             /* long command (@, f, F, t, T, etc.) */
-#define U_     0x10            /* an UN-undoable command (that isn't a M_) */
-#define B_     0x20            /* bad command (^@) */
-#define Z_     0x40            /* repeat count defaults to 0 (not 1) */
-#define S_     0x80            /* search (/, ?) */
-
-#define is_bad(c)      (classify[(c)&0x7f]&B_)
-#define is_cmd(c)      (classify[(c)&0x7f]&(M_|E_|C_|U_))
-#define is_move(c)     (classify[(c)&0x7f]&M_)
-#define is_extend(c)   (classify[(c)&0x7f]&E_)
-#define is_long(c)     (classify[(c)&0x7f]&X_)
-#define is_undoable(c) (!(classify[(c)&0x7f]&U_))
-#define is_srch(c)     (classify[(c)&0x7f]&S_)
-#define is_zerocount(c)        (classify[(c)&0x7f]&Z_)
+#define vC     0x01            /* a valid command that isn't a vM, vE, vU */
+#define vM     0x02            /* movement command (h, l, etc.) */
+#define vE     0x04            /* extended command (c, d, y) */
+#define vX     0x08            /* long command (@, f, F, t, T, etc.) */
+#define vU     0x10            /* an UN-undoable command (that isn't a vM) */
+#define vB     0x20            /* bad command (^@) */
+#define vZ     0x40            /* repeat count defaults to 0 (not 1) */
+#define vS     0x80            /* search (/, ?) */
+
+#define is_bad(c)      (classify[(c)&0x7f]&vB)
+#define is_cmd(c)      (classify[(c)&0x7f]&(vM|vE|vC|vU))
+#define is_move(c)     (classify[(c)&0x7f]&vM)
+#define is_extend(c)   (classify[(c)&0x7f]&vE)
+#define is_long(c)     (classify[(c)&0x7f]&vX)
+#define is_undoable(c) (!(classify[(c)&0x7f]&vU))
+#define is_srch(c)     (classify[(c)&0x7f]&vS)
+#define is_zerocount(c)        (classify[(c)&0x7f]&vZ)
 
 static const unsigned char classify[128] = {
 /*      0      1       2       3       4       5       6       7       */
 /* 0   ^@      ^A      ^B      ^C      ^D      ^E      ^F      ^G      */
-       B_,     0,      0,      0,      0,      C_|U_,  C_|Z_,  0,
+       vB,     0,      0,      0,      0,      vC|vU,  vC|vZ,  0,
 /* 1   ^H      ^I      ^J      ^K      ^L      ^M      ^N      ^O      */
-       M_,     C_|Z_,  0,      0,      C_|U_,  0,      C_,     0,
+       vM,     vC|vZ,  0,      0,      vC|vU,  0,      vC,     0,
 /* 2   ^P      ^Q      ^R      ^S      ^T      ^U      ^V      ^W      */
-       C_,     0,      C_|U_,  0,      0,      0,      C_,     0,
+       vC,     0,      vC|vU,  0,      0,      0,      vC,     0,
 /* 3   ^X      ^Y      ^Z      ^[      ^\      ^]      ^^      ^_      */
-       C_,     0,      0,      C_|Z_,  0,      0,      0,      0,
+       vC,     0,      0,      vC|vZ,  0,      0,      0,      0,
 /* 4   <space> !       "       #       $       %       &       '       */
-       M_,     0,      0,      C_,     M_,     M_,     0,      0,
+       vM,     0,      0,      vC,     vM,     vM,     0,      0,
 /* 5   (       )       *       +       ,       -       .       /       */
-       0,      0,      C_,     C_,     M_,     C_,     0,      C_|S_,
+       0,      0,      vC,     vC,     vM,     vC,     0,      vC|vS,
 /* 6   0       1       2       3       4       5       6       7       */
-       M_,     0,      0,      0,      0,      0,      0,      0,
+       vM,     0,      0,      0,      0,      0,      0,      0,
 /* 7   8       9       :       ;       <       =       >       ?       */
-       0,      0,      0,      M_,     0,      C_,     0,      C_|S_,
+       0,      0,      0,      vM,     0,      vC,     0,      vC|vS,
 /* 8   @       A       B       C       D       E       F       G       */
-       C_|X_,  C_,     M_,     C_,     C_,     M_,     M_|X_,  C_|U_|Z_,
+       vC|vX,  vC,     vM,     vC,     vC,     vM,     vM|vX,  vC|vU|vZ,
 /* 9   H       I       J       K       L       M       N       O       */
-       0,      C_,     0,      0,      0,      0,      C_|U_,  0,
+       0,      vC,     0,      0,      0,      0,      vC|vU,  0,
 /* A   P       Q       R       S       T       U       V       W       */
-       C_,     0,      C_,     C_,     M_|X_,  C_,     0,      M_,
+       vC,     0,      vC,     vC,     vM|vX,  vC,     0,      vM,
 /* B   X       Y       Z       [       \       ]       ^       _       */
-       C_,     C_|U_,  0,      0,      C_|Z_,  0,      M_,     C_|Z_,
+       vC,     vC|vU,  0,      0,      vC|vZ,  0,      vM,     vC|vZ,
 /* C   `       a       b       c       d       e       f       g       */
-       0,      C_,     M_,     E_,     E_,     M_,     M_|X_,  C_|Z_,
+       0,      vC,     vM,     vE,     vE,     vM,     vM|vX,  vC|vZ,
 /* D   h       i       j       k       l       m       n       o       */
-       M_,     C_,     C_|U_,  C_|U_,  M_,     0,      C_|U_,  0,
+       vM,     vC,     vC|vU,  vC|vU,  vM,     0,      vC|vU,  0,
 /* E   p       q       r       s       t       u       v       w       */
-       C_,     0,      X_,     C_,     M_|X_,  C_|U_,  C_|U_|Z_, M_,
+       vC,     0,      vX,     vC,     vM|vX,  vC|vU,  vC|vU|vZ, vM,
 /* F   x       y       z       {       |       }       ~       ^?      */
-       C_,     E_|U_,  0,      0,      M_|Z_,  0,      C_,     0
+       vC,     vE|vU,  0,      0,      vM|vZ,  0,      vC,     0
 };
 
 #define MAXVICMD       3
@@ -3340,7 +3395,8 @@ static int ohnum;                 /* history line copied (after mod) */
 static int hlast;                      /* 1 past last position in history */
 static int state;
 
-/* Information for keeping track of macros that are being expanded.
+/*
+ * Information for keeping track of macros that are being expanded.
  * The format of buf is the alias contents followed by a NUL byte followed
  * by the name (letter) of the alias. The end of the buffer is marked by
  * a double NUL. The name of the alias is stored so recursive macros can
@@ -3349,14 +3405,14 @@ static int state;
 struct macro_state {
        unsigned char *p;       /* current position in buf */
        unsigned char *buf;     /* pointer to macro(s) being expanded */
-       int len;                /* how much data in buffer */
+       size_t len;             /* how much data in buffer */
 };
 static struct macro_state macro;
 
-enum expand_mode {
-       NONE, EXPAND, COMPLETE, PRINT
-};
-static enum expand_mode expanded = NONE;       /* last input was expanded */
+/* last input was expanded */
+static enum expand_mode {
+       NONE = 0, EXPAND, COMPLETE, PRINT
+} expanded;
 
 static int
 x_vi(char *buf, size_t len)
@@ -3397,8 +3453,10 @@ x_vi(char *buf, size_t len)
                wbuf[0] = aresize(wbuf[0], wbuf_len, APERM);
                wbuf[1] = aresize(wbuf[1], wbuf_len, APERM);
        }
-       (void)memset(wbuf[0], ' ', wbuf_len);
-       (void)memset(wbuf[1], ' ', wbuf_len);
+       if (wbuf_len) {
+               memset(wbuf[0], ' ', wbuf_len);
+               memset(wbuf[1], ' ', wbuf_len);
+       }
        winwidth = x_cols - pwidth - 3;
        win = 0;
        morec = ' ';
@@ -3407,7 +3465,7 @@ x_vi(char *buf, size_t len)
 
        editmode = 2;
        x_flush();
-       while (1) {
+       while (/* CONSTCOND */ 1) {
                if (macro.p) {
                        c = *macro.p++;
                        /* end of current macro? */
@@ -3804,7 +3862,8 @@ vi_insert(int ch)
                expanded = NONE;
                return (0);
        }
-       /* If any chars are entered before escape, trash the saved insert
+       /*
+        * If any chars are entered before escape, trash the saved insert
         * buffer (if user inserts & deletes char, ibuf gets trashed and
         * we don't want to use it)
         */
@@ -3905,14 +3964,14 @@ vi_cmd(int argcnt, const char *cmd)
 
                case Ctrl('l'):
                case Ctrl('r'):
-                       redraw_line(1);
+                       redraw_line(true);
                        break;
 
                case '@':
                        {
                                static char alias[] = "_\0";
                                struct tbl *ap;
-                               int olen, nlen;
+                               size_t olen, nlen;
                                char *p, *nbuf;
 
                                /* lookup letter in alias list... */
@@ -3929,6 +3988,10 @@ vi_cmd(int argcnt, const char *cmd)
                                nlen = strlen(ap->val.s) + 1;
                                olen = !macro.p ? 2 :
                                    macro.len - (macro.p - macro.buf);
+                               /*
+                                * at this point, it's fairly reasonable that
+                                * nlen + olen + 2 doesn't overflow
+                                */
                                nbuf = alloc(nlen + 1 + olen, APERM);
                                memcpy(nbuf, ap->val.s, nlen);
                                nbuf[nlen++] = cmd[1];
@@ -4323,29 +4386,37 @@ vi_cmd(int argcnt, const char *cmd)
                                return (ret);
                        }
 
-               case '=':                       /* AT&T ksh */
-               case Ctrl('e'):                 /* Nonstandard vi/ksh */
+               /* AT&T ksh */
+               case '=':
+               /* Nonstandard vi/ksh */
+               case Ctrl('e'):
                        print_expansions(es, 1);
                        break;
 
 
-               case Ctrl('i'):                 /* Nonstandard vi/ksh */
+               /* Nonstandard vi/ksh */
+               case Ctrl('i'):
                        if (!Flag(FVITABCOMPLETE))
                                return (-1);
                        complete_word(1, argcnt);
                        break;
 
-               case Ctrl('['):                 /* some annoying AT&T kshs */
+               /* some annoying AT&T kshs */
+               case Ctrl('['):
                        if (!Flag(FVIESCCOMPLETE))
                                return (-1);
-               case '\\':                      /* AT&T ksh */
-               case Ctrl('f'):                 /* Nonstandard vi/ksh */
+               /* AT&T ksh */
+               case '\\':
+               /* Nonstandard vi/ksh */
+               case Ctrl('f'):
                        complete_word(1, argcnt);
                        break;
 
 
-               case '*':                       /* AT&T ksh */
-               case Ctrl('x'):                 /* Nonstandard vi/ksh */
+               /* AT&T ksh */
+               case '*':
+               /* Nonstandard vi/ksh */
+               case Ctrl('x'):
                        expand_word(1);
                        break;
                }
@@ -4613,7 +4684,7 @@ x_vi_putbuf(const char *s, size_t len)
 }
 
 static int
-putbuf(const char *buf, int len, int repl)
+putbuf(const char *buf, ssize_t len, int repl)
 {
        if (len == 0)
                return (0);
@@ -4811,7 +4882,7 @@ grabhist(int save, int n)
        }
        (void)histnum(n);
        if ((hptr = *histpos()) == NULL) {
-               internal_warningf("grabhist: bad history array");
+               internal_warningf("%s: %s", "grabhist", "bad history array");
                return (-1);
        }
        if (save)
@@ -4839,7 +4910,7 @@ grabsearch(int save, int start, int fwd, char *pat)
                start--;
        anchored = *pat == '^' ? (++pat, 1) : 0;
        if ((hist = findhist(start, fwd, pat, anchored)) < 0) {
-               /* if (start != 0 && fwd && match(holdbuf, pat) >= 0) { */
+               /* if (start != 0 && fwd && match(holdbuf, pat) >= 0) {} */
                /* XXX should strcmp be strncmp? */
                if (start != 0 && fwd && strcmp(holdbuf, pat) >= 0) {
                        restore_cbuf();
@@ -4859,9 +4930,10 @@ grabsearch(int save, int start, int fwd, char *pat)
 }
 
 static void
-redraw_line(int newl)
+redraw_line(bool newl)
 {
-       (void)memset(wbuf[win], ' ', wbuf_len);
+       if (wbuf_len)
+               memset(wbuf[win], ' ', wbuf_len);
        if (newl) {
                x_putc('\r');
                x_putc('\n');
@@ -4996,7 +5068,8 @@ display(char *wb1, char *wb2, int leftside)
                col++;
        }
        if (es->winleft > 0 && moreright)
-               /* POSIX says to use * for this but that is a globbing
+               /*
+                * POSIX says to use * for this but that is a globbing
                 * character and may confuse people; + is more innocuous
                 */
                mc = '+';
@@ -5045,11 +5118,8 @@ static int
 expand_word(int cmd)
 {
        static struct edstate *buf;
-       int rval = 0;
-       int nwords;
-       int start, end;
+       int rval = 0, nwords, start, end, i;
        char **words;
-       int i;
 
        /* Undo previous expansion */
        if (cmd == 0 && expanded == EXPAND && buf) {
@@ -5063,9 +5133,9 @@ expand_word(int cmd)
                buf = 0;
        }
 
-       nwords = x_cf_glob(XCF_COMMAND_FILE|XCF_FULLPATH,
-           es->cbuf, es->linelen, es->cursor,
-           &start, &end, &words, NULL);
+       i = XCF_COMMAND_FILE | XCF_FULLPATH;
+       nwords = x_cf_glob(&i, es->cbuf, es->linelen, es->cursor,
+           &start, &end, &words);
        if (nwords == 0) {
                vi_error();
                return (-1);
@@ -5075,7 +5145,8 @@ expand_word(int cmd)
        expanded = EXPAND;
        del_range(start, end);
        es->cursor = start;
-       for (i = 0; i < nwords; ) {
+       i = 0;
+       while (i < nwords) {
                if (x_escape(words[i], strlen(words[i]), x_vi_putbuf) != 0) {
                        rval = -1;
                        break;
@@ -5100,10 +5171,11 @@ static int
 complete_word(int cmd, int count)
 {
        static struct edstate *buf;
-       int rval, nwords, start, end, match_len;
+       int rval, nwords, start, end, flags;
+       size_t match_len;
        char **words;
        char *match;
-       bool is_command, is_unique;
+       bool is_unique;
 
        /* Undo previous completion */
        if (cmd == 0 && expanded == COMPLETE && buf) {
@@ -5122,12 +5194,15 @@ complete_word(int cmd, int count)
                buf = 0;
        }
 
-       /* XCF_FULLPATH for count 'cause the menu printed by print_expansions()
-        * was done this way.
+       /*
+        * XCF_FULLPATH for count 'cause the menu printed by
+        * print_expansions() was done this way.
         */
-       nwords = x_cf_glob(XCF_COMMAND_FILE | (count ? XCF_FULLPATH : 0),
-           es->cbuf, es->linelen, es->cursor,
-           &start, &end, &words, &is_command);
+       flags = XCF_COMMAND_FILE;
+       if (count)
+               flags |= XCF_FULLPATH;
+       nwords = x_cf_glob(&flags, es->cbuf, es->linelen, es->cursor,
+           &start, &end, &words);
        if (nwords == 0) {
                vi_error();
                return (-1);
@@ -5138,15 +5213,16 @@ complete_word(int cmd, int count)
                count--;
                if (count >= nwords) {
                        vi_error();
-                       x_print_expansions(nwords, words, is_command);
+                       x_print_expansions(nwords, words,
+                           tobool(flags & XCF_IS_COMMAND));
                        x_free_words(nwords, words);
-                       redraw_line(0);
+                       redraw_line(false);
                        return (-1);
                }
                /*
                 * Expand the count'th word to its basename
                 */
-               if (is_command) {
+               if (flags & XCF_IS_COMMAND) {
                        match = words[count] +
                            x_basename(words[count], NULL);
                        /* If more than one possible match, use full path */
@@ -5165,7 +5241,8 @@ complete_word(int cmd, int count)
        } else {
                match = words[0];
                match_len = x_longest_prefix(nwords, words);
-               expanded = COMPLETE;    /* next call will list completions */
+               /* next call will list completions */
+               expanded = COMPLETE;
                is_unique = nwords == 1;
        }
 
@@ -5173,18 +5250,25 @@ complete_word(int cmd, int count)
        del_range(start, end);
        es->cursor = start;
 
-       /* escape all shell-sensitive characters and put the result into
-        * command buffer */
+       /*
+        * escape all shell-sensitive characters and put the result into
+        * command buffer
+        */
        rval = x_escape(match, match_len, x_vi_putbuf);
 
        if (rval == 0 && is_unique) {
-               /* If exact match, don't undo. Allows directory completions
+               /*
+                * If exact match, don't undo. Allows directory completions
                 * to be used (ie, complete the next portion of the path).
                 */
                expanded = NONE;
 
-               /* If not a directory, add a space to the end... */
-               if (match_len > 0 && match[match_len - 1] != '/')
+               /*
+                * append a space if this is a non-directory match
+                * and not a parameter or homedir substitution
+                */
+               if (match_len > 0 && match[match_len - 1] != '/' &&
+                   !(flags & XCF_IS_SUBGLOB))
                        rval = putbuf(" ", 1, 0);
        }
        x_free_words(nwords, words);
@@ -5192,7 +5276,8 @@ complete_word(int cmd, int count)
        modified = 1;
        hnum = hlast;
        insert = INSERT;
-       lastac = 0;      /* prevent this from being redone... */
+       /* prevent this from being redone... */
+       lastac = 0;
        refresh(0);
 
        return (rval);
@@ -5201,20 +5286,19 @@ complete_word(int cmd, int count)
 static int
 print_expansions(struct edstate *est, int cmd MKSH_A_UNUSED)
 {
-       int start, end, nwords;
+       int start, end, nwords, i;
        char **words;
-       bool is_command;
 
-       nwords = x_cf_glob(XCF_COMMAND_FILE | XCF_FULLPATH,
-           est->cbuf, est->linelen, est->cursor,
-           &start, &end, &words, &is_command);
+       i = XCF_COMMAND_FILE | XCF_FULLPATH;
+       nwords = x_cf_glob(&i, est->cbuf, est->linelen, est->cursor,
+           &start, &end, &words);
        if (nwords == 0) {
                vi_error();
                return (-1);
        }
-       x_print_expansions(nwords, words, is_command);
+       x_print_expansions(nwords, words, tobool(i & XCF_IS_COMMAND));
        x_free_words(nwords, words);
-       redraw_line(0);
+       redraw_line(false);
        return (0);
 }
 
@@ -5247,3 +5331,34 @@ vi_macro_reset(void)
        }
 }
 #endif /* !MKSH_S_NOVI */
+
+void
+x_mkraw(int fd, struct termios *ocb, bool forread)
+{
+       struct termios cb;
+
+       if (ocb)
+               tcgetattr(fd, ocb);
+       else
+               ocb = &tty_state;
+
+       cb = *ocb;
+       if (forread) {
+               cb.c_lflag &= ~(ICANON) | ECHO;
+       } else {
+               cb.c_iflag &= ~(INLCR | ICRNL);
+               cb.c_lflag &= ~(ISIG | ICANON | ECHO);
+       }
+#if defined(VLNEXT) && defined(_POSIX_VDISABLE)
+       /* OSF/1 processes lnext when ~icanon */
+       cb.c_cc[VLNEXT] = _POSIX_VDISABLE;
+#endif
+       /* SunOS 4.1.x & OSF/1 processes discard(flush) when ~icanon */
+#if defined(VDISCARD) && defined(_POSIX_VDISABLE)
+       cb.c_cc[VDISCARD] = _POSIX_VDISABLE;
+#endif
+       cb.c_cc[VTIME] = 0;
+       cb.c_cc[VMIN] = 1;
+
+       tcsetattr(fd, TCSADRAIN, &cb);
+}
index c22e346..49e5e6e 100644 (file)
@@ -1,7 +1,7 @@
-/*     $OpenBSD: eval.c,v 1.35 2010/03/24 08:27:26 fgsch Exp $ */
+/*     $OpenBSD: eval.c,v 1.37 2011/10/11 14:32:43 otto Exp $  */
 
 /*-
- * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
  *     Thorsten Glaser <tg@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -22,7 +22,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/eval.c,v 1.90 2010/07/17 22:09:33 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/eval.c,v 1.109 2011/10/11 19:06:07 tg Exp $");
 
 /*
  * string expansion
@@ -67,11 +67,11 @@ static char *tilde(char *);
 static char *homedir(char *);
 #endif
 static void alt_expand(XPtrV *, char *, char *, char *, int);
-static size_t utflen(const char *);
+static int utflen(const char *);
 static void utfincptr(const char *, mksh_ari_t *);
 
 /* UTFMODE functions */
-static size_t
+static int
 utflen(const char *s)
 {
        size_t n;
@@ -84,7 +84,10 @@ utflen(const char *s)
                }
        } else
                n = strlen(s);
-       return (n);
+
+       if (n > 2147483647)
+               n = 2147483647;
+       return ((int)n);
 }
 
 static void
@@ -108,7 +111,7 @@ substitute(const char *cp, int f)
        s->start = s->str = cp;
        source = s;
        if (yylex(ONEWORD) != LWORD)
-               internal_errorf("substitute");
+               internal_errorf("bad substitution");
        source = sold;
        afree(s, ATEMP);
        return (evalstr(yylval.cp, f));
@@ -129,7 +132,8 @@ eval(const char **ap, int f)
                return (vap.rw);
        }
        XPinit(w, 32);
-       XPput(w, NULL);         /* space for shell name */
+       /* space for shell name */
+       XPput(w, NULL);
        while (*ap != NULL)
                expand(*ap++, &w, f);
        XPput(w, NULL);
@@ -205,11 +209,13 @@ expand(const char *cp,    /* input word */
        const char *sp;         /* source */
        int fdo, word;          /* second pass flags; have word */
        int doblank;            /* field splitting of parameter/command subst */
-       Expand x = {            /* expansion variables */
+       Expand x = {
+               /* expansion variables */
                NULL, { NULL }, NULL, 0
        };
        SubType st_head, *st;
-       int newlines = 0; /* For trailing newlines in COMSUB */
+       /* For trailing newlines in COMSUB */
+       int newlines = 0;
        int saw_eq, tilde_ok;
        int make_magic;
        size_t len;
@@ -226,14 +232,16 @@ expand(const char *cp,    /* input word */
        if (Flag(FMARKDIRS))
                f |= DOMARKDIRS;
        if (Flag(FBRACEEXPAND) && (f & DOGLOB))
-               f |= DOBRACE_;
+               f |= DOBRACE;
 
-       Xinit(ds, dp, 128, ATEMP);      /* init dest. string */
+       /* init destination string */
+       Xinit(ds, dp, 128, ATEMP);
        type = XBASE;
        sp = cp;
        fdo = 0;
        saw_eq = 0;
-       tilde_ok = (f & (DOTILDE|DOASNTILDE)) ? 1 : 0; /* must be 1/0 */
+       /* must be 1/0 */
+       tilde_ok = (f & (DOTILDE|DOASNTILDE)) ? 1 : 0;
        doblank = 0;
        make_magic = 0;
        word = (f&DOBLANK) ? IFS_WS : IFS_WORD;
@@ -241,11 +249,12 @@ expand(const char *cp,    /* input word */
        memset(&st_head, 0, sizeof(st_head));
        st = &st_head;
 
-       while (1) {
+       while (/* CONSTCOND */ 1) {
                Xcheck(ds, dp);
 
                switch (type) {
-               case XBASE:     /* original prefixed string */
+               case XBASE:
+                       /* original prefixed string */
                        c = *sp++;
                        switch (c) {
                        case EOS:
@@ -255,7 +264,8 @@ expand(const char *cp,      /* input word */
                                c = *sp++;
                                break;
                        case QCHAR:
-                               quote |= 2; /* temporary quote */
+                               /* temporary quote */
+                               quote |= 2;
                                c = *sp++;
                                break;
                        case OQUOTE:
@@ -299,7 +309,8 @@ expand(const char *cp,      /* input word */
                                        char *p;
 
                                        v.flag = DEFINED|ISSET|INTEGER;
-                                       v.type = 10; /* not default */
+                                       /* not default */
+                                       v.type = 10;
                                        v.name[0] = '\0';
                                        v_evaluate(&v, substitute(sp, 0),
                                            KSH_UNWIND_ERROR, true);
@@ -310,23 +321,27 @@ expand(const char *cp,    /* input word */
                                        }
                                }
                                continue;
-                       case OSUBST: {  /* ${{#}var{:}[=+-?#%]word} */
-                       /* format is:
+                       case OSUBST: {
+                               /* ${{#}var{:}[=+-?#%]word} */
+                       /*-
+                        * format is:
                         *      OSUBST [{x] plain-variable-part \0
                         *          compiled-word-part CSUBST [}x]
                         * This is where all syntax checking gets done...
                         */
-                               const char *varname = ++sp; /* skip the { or x (}) */
+                               /* skip the { or x (}) */
+                               const char *varname = ++sp;
                                int stype;
                                int slen = 0;
 
-                               sp = cstrchr(sp, '\0') + 1; /* skip variable */
+                               /* skip variable */
+                               sp = cstrchr(sp, '\0') + 1;
                                type = varsub(&x, varname, sp, &stype, &slen);
                                if (type < 0) {
                                        char *beg, *end, *str;
-
  unwind_substsyn:
-                                       sp = varname - 2; /* restore sp */
+                                       /* restore sp */
+                                       sp = varname - 2;
                                        end = (beg = wdcopy(sp, ATEMP)) +
                                            (wdscan(sp, CSUBST) - sp);
                                        /* ({) the } or x is already skipped */
@@ -334,12 +349,13 @@ expand(const char *cp,    /* input word */
                                                *end = EOS;
                                        str = snptreef(NULL, 64, "%S", beg);
                                        afree(beg, ATEMP);
-                                       errorf("%s: bad substitution", str);
+                                       errorf("%s: %s", str, "bad substitution");
                                }
                                if (f & DOBLANK)
                                        doblank++;
                                tilde_ok = 0;
-                               if (type == XBASE) {    /* expand? */
+                               if (type == XBASE) {
+                                       /* expand? */
                                        if (!st->next) {
                                                SubType *newst;
 
@@ -357,7 +373,11 @@ expand(const char *cp,     /* input word */
                                        /* skip qualifier(s) */
                                        if (stype)
                                                sp += slen;
-                                       switch (stype & 0x7f) {
+                                       switch (stype & 0x17F) {
+                                       case 0x100 | '#':
+                                               x.str = shf_smprintf("%08X",
+                                                   (unsigned int)hash(str_val(st->var)));
+                                               break;
                                        case '0': {
                                                char *beg, *mid, *end, *stg;
                                                mksh_ari_t from = 0, num = -1, flen, finc = 0;
@@ -374,16 +394,18 @@ expand(const char *cp,    /* input word */
                                                } else {
                                                        end = mid +
                                                            (wdscan(mid, ADELIM) - mid);
-                                                       if (end >= stg)
+                                                       if (end >= stg ||
+                                                           /* more than max delimiters */
+                                                           end[-1] != /*{*/ '}')
                                                                goto unwind_substsyn;
                                                        end[-2] = EOS;
                                                        sp += end - beg - 1;
                                                }
-                                               evaluate(substitute(stg = wdstrip(beg, false, false), 0),
+                                               evaluate(substitute(stg = wdstrip(beg, 0), 0),
                                                    &from, KSH_UNWIND_ERROR, true);
                                                afree(stg, ATEMP);
                                                if (end) {
-                                                       evaluate(substitute(stg = wdstrip(mid, false, false), 0),
+                                                       evaluate(substitute(stg = wdstrip(mid, 0), 0),
                                                            &num, KSH_UNWIND_ERROR, true);
                                                        afree(stg, ATEMP);
                                                }
@@ -422,10 +444,11 @@ expand(const char *cp,    /* input word */
                                                else
                                                        d[-2] = EOS;
                                                sp += (d ? d : p) - s - 1;
-                                               tpat0 = wdstrip(s, true, true);
+                                               tpat0 = wdstrip(s,
+                                                   WDS_KEEPQ | WDS_MAGIC);
                                                pat = substitute(tpat0, 0);
                                                if (d) {
-                                                       d = wdstrip(p, true, false);
+                                                       d = wdstrip(p, WDS_KEEPQ);
                                                        rrep = substitute(d, 0);
                                                        afree(d, ATEMP);
                                                } else
@@ -445,12 +468,44 @@ expand(const char *cp,    /* input word */
                                                *d = '\0';
                                                afree(tpat0, ATEMP);
 
-                                               /* reject empty pattern */
-                                               if (!*pat || gmatchx("", pat, false))
+                                               /* check for special cases */
+                                               d = str_val(st->var);
+                                               switch (*pat) {
+                                               case '#':
+                                                       /* anchor at begin */
+                                                       tpat0 = pat + 1;
+                                                       tpat1 = rrep;
+                                                       tpat2 = d;
+                                                       break;
+                                               case '%':
+                                                       /* anchor at end */
+                                                       tpat0 = pat + 1;
+                                                       tpat1 = d;
+                                                       tpat2 = rrep;
+                                                       break;
+                                               case '\0':
+                                                       /* empty pattern */
                                                        goto no_repl;
+                                               default:
+                                                       tpat0 = pat;
+                                                       /* silence gcc */
+                                                       tpat1 = tpat2 = NULL;
+                                               }
+                                               if (gmatchx(null, tpat0, false)) {
+                                                       /*
+                                                        * pattern matches
+                                                        * the empty string
+                                                        */
+                                                       if (tpat0 == pat)
+                                                               goto no_repl;
+                                                       /* but is anchored */
+                                                       s = shf_smprintf("%s%s",
+                                                           tpat1, tpat2);
+                                                       goto do_repl;
+                                               }
 
                                                /* prepare string on which to work */
-                                               strdupx(s, str_val(st->var), ATEMP);
+                                               strdupx(s, d, ATEMP);
                                                sbeg = s;
 
                                                /* first see if we have any match at all */
@@ -469,7 +524,8 @@ expand(const char *cp,      /* input word */
                                                        tpat2 = tpat1 + 2;
                                                }
  again_repl:
-                                               /* this would not be necessary if gmatchx would return
+                                               /*
+                                                * this would not be necessary if gmatchx would return
                                                 * the start and end values of a match found, like re*
                                                 */
                                                if (!gmatchx(sbeg, tpat1, false))
@@ -489,8 +545,9 @@ expand(const char *cp,      /* input word */
                                                        while (p >= sbeg) {
                                                                bool gotmatch;
 
-                                                               c = *p; *p = '\0';
-                                                               gotmatch = gmatchx(sbeg, tpat0, false);
+                                                               c = *p;
+                                                               *p = '\0';
+                                                               gotmatch = tobool(gmatchx(sbeg, tpat0, false));
                                                                *p = c;
                                                                if (gotmatch)
                                                                        break;
@@ -506,6 +563,7 @@ expand(const char *cp,      /* input word */
                                                        goto again_repl;
  end_repl:
                                                afree(tpat1, ATEMP);
+ do_repl:
                                                x.str = s;
  no_repl:
                                                afree(pat, ATEMP);
@@ -515,19 +573,23 @@ expand(const char *cp,    /* input word */
                                        }
                                        case '#':
                                        case '%':
-                                               /* ! DOBLANK,DOBRACE_,DOTILDE */
+                                               /* ! DOBLANK,DOBRACE,DOTILDE */
                                                f = DOPAT | (f&DONTRUNCOMMAND) |
-                                                   DOTEMP_;
+                                                   DOTEMP;
                                                st->quotew = quote = 0;
-                                               /* Prepend open pattern (so |
+                                               /*
+                                                * Prepend open pattern (so |
                                                 * in a trim will work as
                                                 * expected)
                                                 */
-                                               *dp++ = MAGIC;
-                                               *dp++ = (char)('@' | 0x80);
+                                               if (!Flag(FSH)) {
+                                                       *dp++ = MAGIC;
+                                                       *dp++ = '@' | 0x80;
+                                               }
                                                break;
                                        case '=':
-                                               /* Enabling tilde expansion
+                                               /*
+                                                * Enabling tilde expansion
                                                 * after :s here is
                                                 * non-standard ksh, but is
                                                 * consistent with rules for
@@ -542,16 +604,17 @@ expand(const char *cp,    /* input word */
                                                 */
                                                if (!(x.var->flag & INTEGER))
                                                        f |= DOASNTILDE|DOTILDE;
-                                               f |= DOTEMP_;
-                                               /* These will be done after the
+                                               f |= DOTEMP;
+                                               /*
+                                                * These will be done after the
                                                 * value has been assigned.
                                                 */
-                                               f &= ~(DOBLANK|DOGLOB|DOBRACE_);
+                                               f &= ~(DOBLANK|DOGLOB|DOBRACE);
                                                tilde_ok = 1;
                                                break;
                                        case '?':
                                                f &= ~DOBLANK;
-                                               f |= DOTEMP_;
+                                               f |= DOTEMP;
                                                /* FALLTHROUGH */
                                        default:
                                                /* Enable tilde expansion */
@@ -563,22 +626,30 @@ expand(const char *cp,    /* input word */
                                        sp += wdscan(sp, CSUBST) - sp;
                                continue;
                        }
-                       case CSUBST: /* only get here if expanding word */
+                       case CSUBST:
+                               /* only get here if expanding word */
  do_CSUBST:
-                               sp++; /* ({) skip the } or x */
-                               tilde_ok = 0;   /* in case of ${unset:-} */
+                               /* ({) skip the } or x */
+                               sp++;
+                               /* in case of ${unset:-} */
+                               tilde_ok = 0;
                                *dp = '\0';
                                quote = st->quotep;
                                f = st->f;
                                if (f&DOBLANK)
                                        doblank--;
-                               switch (st->stype&0x7f) {
+                               switch (st->stype & 0x17F) {
                                case '#':
                                case '%':
-                                       /* Append end-pattern */
-                                       *dp++ = MAGIC; *dp++ = ')'; *dp = '\0';
+                                       if (!Flag(FSH)) {
+                                               /* Append end-pattern */
+                                               *dp++ = MAGIC;
+                                               *dp++ = ')';
+                                       }
+                                       *dp = '\0';
                                        dp = Xrestpos(ds, dp, st->base);
-                                       /* Must use st->var since calling
+                                       /*
+                                        * Must use st->var since calling
                                         * global would break things
                                         * like x[i+=1].
                                         */
@@ -593,20 +664,24 @@ expand(const char *cp,    /* input word */
                                        st = st->prev;
                                        continue;
                                case '=':
-                                       /* Restore our position and substitute
+                                       /*
+                                        * Restore our position and substitute
                                         * the value of st->var (may not be
                                         * the assigned value in the presence
                                         * of integer/right-adj/etc attributes).
                                         */
                                        dp = Xrestpos(ds, dp, st->base);
-                                       /* Must use st->var since calling
+                                       /*
+                                        * Must use st->var since calling
                                         * global would cause with things
                                         * like x[i+=1] to be evaluated twice.
                                         */
-                                       /* Note: not exported by FEXPORT
+                                       /*
+                                        * Note: not exported by FEXPORT
                                         * in AT&T ksh.
                                         */
-                                       /* XXX POSIX says readonly is only
+                                       /*
+                                        * XXX POSIX says readonly is only
                                         * fatal for special builtins (setstr
                                         * does readonly check).
                                         */
@@ -630,6 +705,7 @@ expand(const char *cp,      /* input word */
                                }
                                case '0':
                                case '/':
+                               case 0x100 | '#':
                                        dp = Xrestpos(ds, dp, st->base);
                                        type = XSUB;
                                        if (f&DOBLANK)
@@ -641,18 +717,21 @@ expand(const char *cp,    /* input word */
                                type = XBASE;
                                continue;
 
-                       case OPAT: /* open pattern: *(foo|bar) */
+                       case OPAT:
+                               /* open pattern: *(foo|bar) */
                                /* Next char is the type of pattern */
                                make_magic = 1;
-                               c = *sp++ + 0x80;
+                               c = *sp++ | 0x80;
                                break;
 
-                       case SPAT: /* pattern separator (|) */
+                       case SPAT:
+                               /* pattern separator (|) */
                                make_magic = 1;
                                c = '|';
                                break;
 
-                       case CPAT: /* close pattern */
+                       case CPAT:
+                               /* close pattern */
                                make_magic = 1;
                                c = /*(*/ ')';
                                break;
@@ -660,14 +739,16 @@ expand(const char *cp,    /* input word */
                        break;
 
                case XNULLSUB:
-                       /* Special case for "$@" (and "${foo[@]}") - no
+                       /*
+                        * Special case for "$@" (and "${foo[@]}") - no
                         * word is generated if $# is 0 (unless there is
                         * other stuff inside the quotes).
                         */
                        type = XBASE;
                        if (f&DOBLANK) {
                                doblank--;
-                               /* not really correct: x=; "$x$@" should
+                               /*
+                                * not really correct: x=; "$x$@" should
                                 * generate a null argument and
                                 * set A; "${@:+}" shouldn't.
                                 */
@@ -691,7 +772,8 @@ expand(const char *cp,      /* input word */
                        quote = 1;
                case XARG:
                        if ((c = *x.str++) == '\0') {
-                               /* force null words to be created so
+                               /*
+                                * force null words to be created so
                                 * set -- '' 2 ''; foo "$@" will do
                                 * the right thing
                                 */
@@ -718,7 +800,8 @@ expand(const char *cp,      /* input word */
                        break;
 
                case XCOM:
-                       if (newlines) {         /* Spit out saved NLs */
+                       if (newlines) {
+                               /* Spit out saved NLs */
                                c = '\n';
                                --newlines;
                        } else {
@@ -748,7 +831,8 @@ expand(const char *cp,      /* input word */
                /* check for end of word or IFS separation */
                if (c == 0 || (!quote && (f & DOBLANK) && doblank &&
                    !make_magic && ctype(c, C_IFS))) {
-                       /* How words are broken up:
+                       /*-
+                        * How words are broken up:
                         *                      |       value of c
                         *      word            |       ws      nws     0
                         *      -----------------------------------
@@ -765,14 +849,14 @@ expand(const char *cp,    /* input word */
 
                                *dp++ = '\0';
                                p = Xclose(ds, dp);
-                               if (fdo & DOBRACE_)
+                               if (fdo & DOBRACE)
                                        /* also does globbing */
                                        alt_expand(wp, p, p,
                                            p + Xlength(ds, (dp - 1)),
                                            fdo | (f & DOMARKDIRS));
                                else if (fdo & DOGLOB)
                                        glob(p, wp, f & DOMARKDIRS);
-                               else if ((f & DOPAT) || !(fdo & DOMAGIC_))
+                               else if ((f & DOPAT) || !(fdo & DOMAGIC))
                                        XPput(*wp, p);
                                else
                                        XPput(*wp, debunk(p, p, strlen(p) + 1));
@@ -807,12 +891,13 @@ expand(const char *cp,    /* input word */
                                case NOT:
                                case '-':
                                case ']':
-                                       /* For character classes - doesn't hurt
+                                       /*
+                                        * For character classes - doesn't hurt
                                         * to have magic !,-,]s outside of
                                         * [...] expressions.
                                         */
                                        if (f & (DOPAT | DOGLOB)) {
-                                               fdo |= DOMAGIC_;
+                                               fdo |= DOMAGIC;
                                                if (c == '[')
                                                        fdo |= f & DOGLOB;
                                                *dp++ = MAGIC;
@@ -821,35 +906,37 @@ expand(const char *cp,    /* input word */
                                case '*':
                                case '?':
                                        if (f & (DOPAT | DOGLOB)) {
-                                               fdo |= DOMAGIC_ | (f & DOGLOB);
+                                               fdo |= DOMAGIC | (f & DOGLOB);
                                                *dp++ = MAGIC;
                                        }
                                        break;
                                case OBRACE:
                                case ',':
                                case CBRACE:
-                                       if ((f & DOBRACE_) && (c == OBRACE ||
-                                           (fdo & DOBRACE_))) {
-                                               fdo |= DOBRACE_|DOMAGIC_;
+                                       if ((f & DOBRACE) && (c == OBRACE ||
+                                           (fdo & DOBRACE))) {
+                                               fdo |= DOBRACE|DOMAGIC;
                                                *dp++ = MAGIC;
                                        }
                                        break;
                                case '=':
                                        /* Note first unquoted = for ~ */
-                                       if (!(f & DOTEMP_) && !saw_eq &&
+                                       if (!(f & DOTEMP) && !saw_eq &&
                                            (Flag(FBRACEEXPAND) ||
                                            (f & DOASNTILDE))) {
                                                saw_eq = 1;
                                                tilde_ok = 1;
                                        }
                                        break;
-                               case ':': /* : */
+                               case ':':
+                                       /* : */
                                        /* Note unquoted : for ~ */
-                                       if (!(f & DOTEMP_) && (f & DOASNTILDE))
+                                       if (!(f & DOTEMP) && (f & DOASNTILDE))
                                                tilde_ok = 1;
                                        break;
                                case '~':
-                                       /* tilde_ok is reset whenever
+                                       /*
+                                        * tilde_ok is reset whenever
                                         * any of ' " $( $(( ${ } are seen.
                                         * Note that tilde_ok must be preserved
                                         * through the sequence ${A=a=}~
@@ -875,17 +962,19 @@ expand(const char *cp,    /* input word */
                                        break;
                                }
                        else
-                               quote &= ~2; /* undo temporary */
+                               /* undo temporary */
+                               quote &= ~2;
 
                        if (make_magic) {
                                make_magic = 0;
-                               fdo |= DOMAGIC_ | (f & DOGLOB);
+                               fdo |= DOMAGIC | (f & DOGLOB);
                                *dp++ = MAGIC;
                        } else if (ISMAGIC(c)) {
-                               fdo |= DOMAGIC_;
+                               fdo |= DOMAGIC;
                                *dp++ = MAGIC;
                        }
-                       *dp++ = c; /* save output char */
+                       /* save output char */
+                       *dp++ = c;
                        word = IFS_WORD;
                }
        }
@@ -907,7 +996,8 @@ varsub(Expand *xp, const char *sp, const char *word,
        struct tbl *vp;
        bool zero_ok = false;
 
-       if ((stype = sp[0]) == '\0')    /* Bad variable name */
+       if ((stype = sp[0]) == '\0')
+               /* Bad variable name */
                return (-1);
 
        xp->var = NULL;
@@ -974,8 +1064,9 @@ varsub(Expand *xp, const char *sp, const char *word,
                        }
                }
                if (Flag(FNOUNSET) && c == 0 && !zero_ok)
-                       errorf("%s: parameter not set", sp);
-               *stypep = 0; /* unqualified variable/string substitution */
+                       errorf("%s: %s", sp, "parameter not set");
+               /* unqualified variable/string substitution */
+               *stypep = 0;
                xp->str = shf_smprintf("%d", c);
                return (XSUB);
        }
@@ -1000,14 +1091,24 @@ varsub(Expand *xp, const char *sp, const char *word,
        } else if (ctype(c, C_SUBOP1)) {
                slen += 2;
                stype |= c;
-       } else if (ctype(c, C_SUBOP2)) { /* Note: ksh88 allows :%, :%%, etc */
+       } else if (ctype(c, C_SUBOP2)) {
+               /* Note: ksh88 allows :%, :%%, etc */
                slen += 2;
                stype = c;
                if (word[slen + 0] == CHAR && c == word[slen + 1]) {
                        stype |= 0x80;
                        slen += 2;
                }
-       } else if (stype)       /* : is not ok */
+       } else if (c == '@') {
+               /* @x where x is command char */
+               slen += 2;
+               stype |= 0x100;
+               if (word[slen] == CHAR) {
+                       stype |= word[slen + 1];
+                       slen += 2;
+               }
+       } else if (stype)
+               /* : is not ok */
                return (-1);
        if (!stype && *word != CSUBST)
                return (-1);
@@ -1016,12 +1117,13 @@ varsub(Expand *xp, const char *sp, const char *word,
 
        c = sp[0];
        if (c == '*' || c == '@') {
-               switch (stype & 0x7f) {
+               switch (stype & 0x17F) {
                case '=':       /* can't assign to a vector */
                case '%':       /* can't trim a vector (yet) */
                case '#':
                case '0':
                case '/':
+               case 0x100 | '#':
                        return (-1);
                }
                if (e->loc->argc == 0) {
@@ -1034,19 +1136,21 @@ varsub(Expand *xp, const char *sp, const char *word,
                        xp->split = c == '@'; /* $@ */
                        state = XARG;
                }
-               zero_ok = true; /* POSIX 2009? */
+               /* POSIX 2009? */
+               zero_ok = true;
        } else {
                if ((p = cstrchr(sp, '[')) && (p[1] == '*' || p[1] == '@') &&
                    p[2] == ']') {
                        XPtrV wv;
 
-                       switch (stype & 0x7f) {
+                       switch (stype & 0x17F) {
                        case '=':       /* can't assign to a vector */
                        case '%':       /* can't trim a vector (yet) */
                        case '#':
                        case '?':
                        case '0':
                        case '/':
+                       case 0x100 | '#':
                                return (-1);
                        }
                        XPinit(wv, 32);
@@ -1073,13 +1177,13 @@ varsub(Expand *xp, const char *sp, const char *word,
                        }
                } else {
                        /* Can't assign things like $! or $1 */
-                       if ((stype & 0x7f) == '=' &&
+                       if ((stype & 0x17F) == '=' &&
                            ctype(*sp, C_VAR1 | C_DIGIT))
                                return (-1);
                        if (*sp == '!' && sp[1]) {
                                ++sp;
                                xp->var = global(sp);
-                               if (cstrchr(sp, '[')) {
+                               if (vstrchr(sp, '[')) {
                                        if (xp->var->flag & ISSET)
                                                xp->str = shf_smprintf("%lu",
                                                    arrayindex(xp->var));
@@ -1088,7 +1192,8 @@ varsub(Expand *xp, const char *sp, const char *word,
                                } else if (xp->var->flag & ISSET)
                                        xp->str = xp->var->name;
                                else
-                                       xp->str = "0";  /* ksh93 compat */
+                                       /* ksh93 compat */
+                                       xp->str = "0";
                        } else {
                                xp->var = global(sp);
                                xp->str = str_val(xp->var);
@@ -1097,15 +1202,17 @@ varsub(Expand *xp, const char *sp, const char *word,
                }
        }
 
-       c = stype&0x7f;
+       c = stype & 0x7F;
        /* test the compiler's code generator */
-       if (ctype(c, C_SUBOP2) || stype == (0x80 | '0') || c == '/' ||
+       if (((stype < 0x100) && (ctype(c, C_SUBOP2) || c == '/' ||
            (((stype&0x80) ? *xp->str=='\0' : xp->str==null) ? /* undef? */
-           c == '=' || c == '-' || c == '?' : c == '+'))
-               state = XBASE;  /* expand word instead of variable value */
+           c == '=' || c == '-' || c == '?' : c == '+'))) ||
+           stype == (0x80 | '0') || stype == (0x100 | '#'))
+               /* expand word instead of variable value */
+               state = XBASE;
        if (Flag(FNOUNSET) && xp->str == null && !zero_ok &&
            (ctype(c, C_SUBOP2) || (state != XBASE && c != '+')))
-               errorf("%s: parameter not set", sp);
+               errorf("%s: %s", sp, "parameter not set");
        return (state);
 }
 
@@ -1118,30 +1225,33 @@ comsub(Expand *xp, const char *cp)
        Source *s, *sold;
        struct op *t;
        struct shf *shf;
+       uint8_t old_utfmode = UTFMODE;
 
        s = pushs(SSTRING, ATEMP);
        s->start = s->str = cp;
        sold = source;
-       t = compile(s);
+       t = compile(s, true);
        afree(s, ATEMP);
        source = sold;
 
        if (t == NULL)
                return (XBASE);
 
-       if (t != NULL && t->type == TCOM && /* $(<file) */
+       if (t != NULL && t->type == TCOM &&
            *t->args == NULL && *t->vars == NULL && t->ioact != NULL) {
+               /* $(<file) */
                struct ioword *io = *t->ioact;
                char *name;
 
-               if ((io->flag&IOTYPE) != IOREAD)
-                       errorf("funny $() command: %s",
+               if ((io->flag & IOTYPE) != IOREAD)
+                       errorf("%s: %s", "funny $() command",
                            snptreef(NULL, 32, "%R", io));
                shf = shf_open(name = evalstr(io->name, DOTILDE), O_RDONLY, 0,
                        SHF_MAPHI|SHF_CLEXEC);
                if (shf == NULL)
-                       errorf("%s: cannot open $() input", name);
-               xp->split = 0;  /* no waitlast() */
+                       errorf("%s: %s %s", name, "can't open", "$() input");
+               /* no waitlast() */
+               xp->split = 0;
        } else {
                int ofd1, pv[2];
                openpipe(pv);
@@ -1154,9 +1264,11 @@ comsub(Expand *xp, const char *cp)
                execute(t, XFORK|XXCOM|XPIPEO, NULL);
                restfd(1, ofd1);
                startlast();
-               xp->split = 1;  /* waitlast() */
+               /* waitlast() */
+               xp->split = 1;
        }
 
+       UTFMODE = old_utfmode;
        xp->u.shf = shf;
        return (XCOM);
 }
@@ -1172,7 +1284,8 @@ trimsub(char *str, char *pat, int how)
        char *p, c;
 
        switch (how & 0xFF) {
-       case '#':               /* shortest at beginning */
+       case '#':
+               /* shortest match at beginning */
                for (p = str; p <= end; p += utf_ptradj(p)) {
                        c = *p; *p = '\0';
                        if (gmatchx(str, pat, false)) {
@@ -1182,7 +1295,8 @@ trimsub(char *str, char *pat, int how)
                        *p = c;
                }
                break;
-       case '#'|0x80:          /* longest match at beginning */
+       case '#'|0x80:
+               /* longest match at beginning */
                for (p = end; p >= str; p--) {
                        c = *p; *p = '\0';
                        if (gmatchx(str, pat, false)) {
@@ -1192,7 +1306,8 @@ trimsub(char *str, char *pat, int how)
                        *p = c;
                }
                break;
-       case '%':               /* shortest match at end */
+       case '%':
+               /* shortest match at end */
                p = end;
                while (p >= str) {
                        if (gmatchx(p, pat, false))
@@ -1207,7 +1322,8 @@ trimsub(char *str, char *pat, int how)
                                --p;
                }
                break;
-       case '%'|0x80:          /* longest match at end */
+       case '%'|0x80:
+               /* longest match at end */
                for (p = str; p <= end; p++)
                        if (gmatchx(p, pat, false)) {
  trimsub_match:
@@ -1217,7 +1333,8 @@ trimsub(char *str, char *pat, int how)
                break;
        }
 
-       return (str);           /* no match, return string */
+       /* no match, return string */
+       return (str);
 }
 
 /*
@@ -1243,7 +1360,8 @@ glob(char *cp, XPtrV *wp, int markdirs)
 #define GF_GLOBBED     BIT(1)          /* some globbing has been done */
 #define GF_MARKDIR     BIT(2)          /* add trailing / to directories */
 
-/* Apply file globbing to cp and store the matching files in wp. Returns
+/*
+ * Apply file globbing to cp and store the matching files in wp. Returns
  * the number of matches found.
  */
 int
@@ -1275,8 +1393,10 @@ globit(XString *xs,      /* dest string */
        /* This to allow long expansions to be interrupted */
        intrcheck();
 
-       if (sp == NULL) {       /* end of source path */
-               /* We only need to check if the file exists if a pattern
+       if (sp == NULL) {
+               /* end of source path */
+               /*
+                * We only need to check if the file exists if a pattern
                 * is followed by a non-pattern (eg, foo*x/bar; no check
                 * is needed for foo* since the match must exist) or if
                 * any patterns were expanded and the markdirs option is set.
@@ -1292,7 +1412,8 @@ globit(XString *xs,       /* dest string */
 
                        if (lstat(Xstring(*xs, xp), &lstatb) < 0)
                                return;
-                       /* special case for systems which strip trailing
+                       /*
+                        * special case for systems which strip trailing
                         * slashes from regular files (eg, /etc/passwd/).
                         * SunOS 4.1.3 does this...
                         */
@@ -1301,7 +1422,8 @@ globit(XString *xs,       /* dest string */
                            (!S_ISLNK(lstatb.st_mode) ||
                            stat_check() < 0 || !S_ISDIR(statb.st_mode)))
                                return;
-                       /* Possibly tack on a trailing / if there isn't already
+                       /*
+                        * Possibly tack on a trailing / if there isn't already
                         * one and if the file is a directory or a symlink to a
                         * directory
                         */
@@ -1328,7 +1450,8 @@ globit(XString *xs,       /* dest string */
        np = strchr(sp, '/');
        if (np != NULL) {
                se = np;
-               odirsep = *np;  /* don't assume '/', can be multiple kinds */
+               /* don't assume '/', can be multiple kinds */
+               odirsep = *np;
                *np++ = '\0';
        } else {
                odirsep = '\0'; /* keep gcc quiet */
@@ -1336,7 +1459,8 @@ globit(XString *xs,       /* dest string */
        }
 
 
-       /* Check if sp needs globbing - done to avoid pattern checks for strings
+       /*
+        * Check if sp needs globbing - done to avoid pattern checks for strings
         * containing MAGIC characters, open [s without the matching close ],
         * etc. (otherwise opendir() will be called which may fail because the
         * directory isn't readable - if no globbing is needed, only execute
@@ -1352,8 +1476,7 @@ globit(XString *xs,       /* dest string */
                DIR *dirp;
                struct dirent *d;
                char *name;
-               int len;
-               int prefix_len;
+               size_t len, prefix_len;
 
                /* xp = *xpp;   copy_non_glob() may have re-alloc'd xs */
                *xp = '\0';
@@ -1365,7 +1488,8 @@ globit(XString *xs,       /* dest string */
                        name = d->d_name;
                        if (name[0] == '.' &&
                            (name[1] == 0 || (name[1] == '.' && name[2] == 0)))
-                               continue; /* always ignore . and .. */
+                               /* always ignore . and .. */
+                               continue;
                        if ((*name == '.' && *sp != '.') ||
                            !gmatchx(name, sp, true))
                                continue;
@@ -1416,7 +1540,8 @@ debunk(char *dp, const char *sp, size_t dlen)
        return (dp);
 }
 
-/* Check if p is an unquoted name, possibly followed by a / or :. If so
+/*
+ * Check if p is an unquoted name, possibly followed by a / or :. If so
  * puts the expanded version in *dcp,dp and returns a pointer in p just
  * past the name, otherwise returns 0.
  */
@@ -1534,7 +1659,8 @@ alt_expand(XPtrV *wp, char *start, char *exp_start, char *end, int fdo)
        }
        /* no valid expansions... */
        if (!p || count != 0) {
-               /* Note that given a{{b,c} we do not expand anything (this is
+               /*
+                * Note that given a{{b,c} we do not expand anything (this is
                 * what AT&T ksh does. This may be changed to do the {b,c}
                 * expansion. }
                 */
@@ -1562,6 +1688,10 @@ alt_expand(XPtrV *wp, char *start, char *exp_start, char *end, int fdo)
                                char *news;
                                int l1, l2, l3;
 
+                               /*
+                                * addition safe since these operate on
+                                * one string (separate substrings)
+                                */
                                l1 = brace_start - start;
                                l2 = (p - 1) - field_start;
                                l3 = end - brace_end;
index 391321a..c8dd4c4 100644 (file)
@@ -1,7 +1,7 @@
 /*     $OpenBSD: exec.c,v 1.49 2009/01/29 23:27:26 jaredy Exp $        */
 
 /*-
- * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
  *     Thorsten Glaser <tg@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/exec.c,v 1.75 2010/07/17 22:09:34 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/exec.c,v 1.96 2011/09/07 15:24:14 tg Exp $");
 
 #ifndef MKSH_DEFAULT_EXECSHELL
 #define MKSH_DEFAULT_EXECSHELL "/bin/sh"
 #endif
 
-static int comexec(struct op *, struct tbl *volatile, const char **,
+static int comexec(struct op *, struct tbl * volatile, const char **,
     int volatile, volatile int *);
 static void scriptexec(struct op *, const char **) MKSH_A_NORETURN;
 static int call_builtin(struct tbl *, const char **);
 static int iosetup(struct ioword *, struct tbl *);
-static int herein(const char *, int);
+static int herein(const char *, int, char **);
 static const char *do_selectargs(const char **, bool);
 static Test_op dbteste_isa(Test_env *, Test_meta);
 static const char *dbteste_getopnd(Test_env *, Test_op, bool);
 static void dbteste_error(Test_env *, int, const char *);
+static int search_access(const char *, int);
 
 /*
  * execute command tree
  */
 int
-execute(struct op *volatile t,
-    volatile int flags,                /* if XEXEC don't fork */
+execute(struct op * volatile t,
+    /* if XEXEC don't fork */
+    volatile int flags,
     volatile int * volatile xerrok)
 {
        int i;
        volatile int rv = 0, dummy = 0;
        int pv[2];
-       const char ** volatile ap;
+       const char ** volatile ap = NULL;
        char ** volatile up;
-       const char *s, *cp;
+       const char *s, *ccp;
        struct ioword **iowp;
        struct tbl *tp = NULL;
+       char *cp;
 
        if (t == NULL)
                return (0);
@@ -71,15 +74,60 @@ execute(struct op *volatile t,
        if (trap)
                runtraps(0);
 
+       /* we want to run an executable, do some variance checks */
        if (t->type == TCOM) {
-               /* Clear subst_exstat before argument expansion. Used by
+               /* check if this is 'var=<<EOF' */
+               if (
+                   /* we have zero arguments, i.e. no programme to run */
+                   t->args[0] == NULL &&
+                   /* we have exactly one variable assignment */
+                   t->vars[0] != NULL && t->vars[1] == NULL &&
+                   /* we have exactly one I/O redirection */
+                   t->ioact != NULL && t->ioact[0] != NULL &&
+                   t->ioact[1] == NULL &&
+                   /* of type "here document" (or "here string") */
+                   (t->ioact[0]->flag & IOTYPE) == IOHERE &&
+                   /* the variable assignment begins with a valid varname */
+                   (ccp = skip_wdvarname(t->vars[0], true)) != t->vars[0] &&
+                   /* and has no right-hand side (i.e. "varname=") */
+                   ccp[0] == CHAR && ccp[1] == '=' && ccp[2] == EOS &&
+                   /* plus we can have a here document content */
+                   herein(t->ioact[0]->heredoc, t->ioact[0]->flag & IOEVAL,
+                   &cp) == 0 && cp && *cp) {
+                       char *sp = cp, *dp;
+                       size_t n = ccp - t->vars[0] + 2, z;
+
+                       /* drop redirection (will be garbage collected) */
+                       t->ioact = NULL;
+
+                       /* set variable to its expanded value */
+                       z = strlen(cp) + 1;
+                       if (notoktomul(z, 2) || notoktoadd(z * 2, n))
+                               internal_errorf(Toomem, (unsigned long)-1);
+                       dp = alloc(z * 2 + n, ATEMP);
+                       memcpy(dp, t->vars[0], n);
+                       t->vars[0] = dp;
+                       dp += n;
+                       while (*sp) {
+                               *dp++ = QCHAR;
+                               *dp++ = *sp++;
+                       }
+                       *dp = EOS;
+                       /* free the expanded value */
+                       afree(cp, APERM);
+               }
+
+               /*
+                * Clear subst_exstat before argument expansion. Used by
                 * null commands (see comexec() and c_eval()) and by c_set().
                 */
                subst_exstat = 0;
 
-               current_lineno = t->lineno;     /* for $LINENO */
+               /* for $LINENO */
+               current_lineno = t->lineno;
 
-               /* POSIX says expand command words first, then redirections,
+               /*
+                * POSIX says expand command words first, then redirections,
                 * and assignments last..
                 */
                up = eval(t->args, t->u.evalflags | DOBLANK | DOGLOB | DOTILDE);
@@ -88,8 +136,8 @@ execute(struct op *volatile t,
                        timex_hook(t, &up);
                ap = (const char **)up;
                if (Flag(FXTRACE) && ap[0]) {
-                       shf_fprintf(shl_out, "%s",
-                               substitute(str_val(global("PS4")), 0));
+                       shf_puts(substitute(str_val(global("PS4")), 0),
+                           shl_out);
                        for (i = 0; ap[i]; i++)
                                shf_fprintf(shl_out, "%s%c", ap[i],
                                    ap[i + 1] ? ' ' : '\n');
@@ -101,17 +149,21 @@ execute(struct op *volatile t,
        flags &= ~XTIME;
 
        if (t->ioact != NULL || t->type == TPIPE || t->type == TCOPROC) {
-               e->savefd = alloc(NUFILE * sizeof(short), ATEMP);
+               e->savefd = alloc2(NUFILE, sizeof(short), ATEMP);
                /* initialise to not redirected */
                memset(e->savefd, 0, NUFILE * sizeof(short));
        }
 
+       /* mark for replacement later (unless TPIPE) */
+       vp_pipest->flag |= INT_L;
+
        /* do redirection, to be restored in quitenv() */
        if (t->ioact != NULL)
                for (iowp = t->ioact; *iowp != NULL; iowp++) {
                        if (iosetup(*iowp, tp) < 0) {
                                exstat = rv = 1;
-                               /* Redirection failures for special commands
+                               /*
+                                * Redirection failures for special commands
                                 * cause (non-interactive) shell to exit.
                                 */
                                if (tp && tp->type == CSHELL &&
@@ -138,7 +190,8 @@ execute(struct op *volatile t,
                e->savefd[1] = savefd(1);
                while (t->type == TPIPE) {
                        openpipe(pv);
-                       ksh_dup2(pv[1], 1, false); /* stdout of curr */
+                       /* stdout of curr */
+                       ksh_dup2(pv[1], 1, false);
                        /**
                         * Let exchild() close pv[0] in child
                         * (if this isn't done, commands like
@@ -147,15 +200,18 @@ execute(struct op *volatile t,
                         */
                        exchild(t->left, flags | XPIPEO | XCCLOSE,
                            NULL, pv[0]);
-                       ksh_dup2(pv[0], 0, false); /* stdin of next */
+                       /* stdin of next */
+                       ksh_dup2(pv[0], 0, false);
                        closepipe(pv);
                        flags |= XPIPEI;
                        t = t->right;
                }
-               restfd(1, e->savefd[1]); /* stdout of last */
-               e->savefd[1] = 0; /* no need to re-restore this */
+               /* stdout of last */
+               restfd(1, e->savefd[1]);
+               /* no need to re-restore this */
+               e->savefd[1] = 0;
                /* Let exchild() close 0 in parent, after fork, before wait */
-               i = exchild(t, flags | XPCLOSE, xerrok, 0);
+               i = exchild(t, flags | XPCLOSE | XPIPEST, xerrok, 0);
                if (!(flags&XBGND) && !(flags&XXCOM))
                        rv = i;
                break;
@@ -169,9 +225,11 @@ execute(struct op *volatile t,
                break;
 
        case TCOPROC: {
+#ifndef MKSH_NOPROSPECTOFWORK
                sigset_t omask;
 
-               /* Block sigchild as we are using things changed in the
+               /*
+                * Block sigchild as we are using things changed in the
                 * signal handler
                 */
                sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
@@ -183,6 +241,7 @@ execute(struct op *volatile t,
                        unwind(i);
                        /* NOTREACHED */
                }
+#endif
                /* Already have a (live) co-process? */
                if (coproc.job && coproc.write >= 0)
                        errorf("coprocess already exists");
@@ -208,15 +267,20 @@ execute(struct op *volatile t,
                        openpipe(pv);
                        coproc.read = pv[0];
                        ksh_dup2(pv[1], 1, false);
-                       coproc.readw = pv[1];    /* closed before first read */
+                       /* closed before first read */
+                       coproc.readw = pv[1];
                        coproc.njobs = 0;
                        /* create new coprocess id */
                        ++coproc.id;
                }
+#ifndef MKSH_NOPROSPECTOFWORK
                sigprocmask(SIG_SETMASK, &omask, NULL);
-               e->type = E_EXEC; /* no more need for error handler */
+               /* no more need for error handler */
+               e->type = E_EXEC;
+#endif
 
-               /* exchild() closes coproc.* in child after fork,
+               /*
+                * exchild() closes coproc.* in child after fork,
                 * will also increment coproc.njobs when the
                 * job is actually created.
                 */
@@ -227,7 +291,8 @@ execute(struct op *volatile t,
        }
 
        case TASYNC:
-               /* XXX non-optimal, I think - "(foo &)", forks for (),
+               /*
+                * XXX non-optimal, I think - "(foo &)", forks for (),
                 * forks again for async... parent should optimise
                 * this to "foo &"...
                 */
@@ -272,7 +337,7 @@ execute(struct op *volatile t,
                    (const char **)eval((const char **)t->vars,
                    DOBLANK | DOGLOB | DOTILDE);
                e->type = E_LOOP;
-               while (1) {
+               while (/* CONSTCOND */ 1) {
                        i = sigsetjmp(e->jbuf, 0);
                        if (!i)
                                break;
@@ -285,20 +350,22 @@ execute(struct op *volatile t,
                                goto Break;
                        }
                }
-               rv = 0; /* in case of a continue */
+               /* in case of a continue */
+               rv = 0;
                if (t->type == TFOR) {
                        while (*ap != NULL) {
                                setstr(global(t->str), *ap++, KSH_UNWIND_ERROR);
                                rv = execute(t->left, flags & XERROK, xerrok);
                        }
-               } else { /* TSELECT */
+               } else {
+                       /* TSELECT */
                        for (;;) {
-                               if (!(cp = do_selectargs(ap, is_first))) {
+                               if (!(ccp = do_selectargs(ap, is_first))) {
                                        rv = 1;
                                        break;
                                }
                                is_first = false;
-                               setstr(global(t->str), cp, KSH_UNWIND_ERROR);
+                               setstr(global(t->str), ccp, KSH_UNWIND_ERROR);
                                execute(t->left, flags & XERROK, xerrok);
                        }
                }
@@ -308,7 +375,7 @@ execute(struct op *volatile t,
        case TWHILE:
        case TUNTIL:
                e->type = E_LOOP;
-               while (1) {
+               while (/* CONSTCOND */ 1) {
                        i = sigsetjmp(e->jbuf, 0);
                        if (!i)
                                break;
@@ -321,7 +388,8 @@ execute(struct op *volatile t,
                                goto Break;
                        }
                }
-               rv = 0; /* in case of a continue */
+               /* in case of a continue */
+               rv = 0;
                while ((execute(t->left, XERROK, NULL) == 0) ==
                    (t->type == TWHILE))
                        rv = execute(t->right, flags & XERROK, xerrok);
@@ -330,22 +398,38 @@ execute(struct op *volatile t,
        case TIF:
        case TELIF:
                if (t->right == NULL)
-                       break;  /* should be error */
+                       /* should be error */
+                       break;
                rv = execute(t->left, XERROK, NULL) == 0 ?
                    execute(t->right->left, flags & XERROK, xerrok) :
                    execute(t->right->right, flags & XERROK, xerrok);
                break;
 
        case TCASE:
-               cp = evalstr(t->str, DOTILDE);
-               for (t = t->left; t != NULL && t->type == TPAT; t = t->right)
-                   for (ap = (const char **)t->vars; *ap; ap++)
-                       if ((s = evalstr(*ap, DOTILDE|DOPAT)) &&
-                           gmatchx(cp, s, false))
-                               goto Found;
-               break;
- Found:
-               rv = execute(t->left, flags & XERROK, xerrok);
+               i = 0;
+               ccp = evalstr(t->str, DOTILDE);
+               for (t = t->left; t != NULL && t->type == TPAT; t = t->right) {
+                       for (ap = (const char **)t->vars; *ap; ap++) {
+                               if (i || ((s = evalstr(*ap, DOTILDE|DOPAT)) &&
+                                   gmatchx(ccp, s, false))) {
+                                       rv = execute(t->left, flags & XERROK,
+                                           xerrok);
+                                       i = 0;
+                                       switch (t->u.charflag) {
+                                       case '&':
+                                               i = 1;
+                                               /* FALLTHROUGH */
+                                       case '|':
+                                               goto TCASE_next;
+                                       }
+                                       goto TCASE_out;
+                               }
+                       }
+                       i = 0;
+ TCASE_next:
+                       /* empty */;
+               }
+ TCASE_out:
                break;
 
        case TBRACE:
@@ -357,13 +441,15 @@ execute(struct op *volatile t,
                break;
 
        case TTIME:
-               /* Clear XEXEC so nested execute() call doesn't exit
+               /*
+                * Clear XEXEC so nested execute() call doesn't exit
                 * (allows "ls -l | time grep foo").
                 */
                rv = timex(t, flags & ~XEXEC, xerrok);
                break;
 
-       case TEXEC:             /* an eval'd TCOM */
+       case TEXEC:
+               /* an eval'd TCOM */
                s = t->args[0];
                up = makenv();
                restoresigs();
@@ -382,13 +468,21 @@ execute(struct op *volatile t,
        }
  Break:
        exstat = rv;
+       if (vp_pipest->flag & INT_L) {
+               unset(vp_pipest, 1);
+               vp_pipest->flag = DEFINED | ISSET | INTEGER | RDONLY |
+                   ARRAY | INT_U;
+               vp_pipest->val.i = rv;
+       }
 
-       quitenv(NULL);          /* restores IO */
+       /* restores IO */
+       quitenv(NULL);
        if ((flags&XEXEC))
-               unwind(LEXIT);  /* exit child */
+               /* exit child */
+               unwind(LEXIT);
        if (rv != 0 && !(flags & XERROK) &&
            (xerrok == NULL || !*xerrok)) {
-               trapsig(SIGERR_);
+               trapsig(ksh_SIGERR);
                if (Flag(FERREXIT))
                        unwind(LERROR);
        }
@@ -400,21 +494,23 @@ execute(struct op *volatile t,
  */
 
 static int
-comexec(struct op *t, struct tbl *volatile tp, const char **ap,
+comexec(struct op *t, struct tbl * volatile tp, const char **ap,
     volatile int flags, volatile int *xerrok)
 {
        int i;
        volatile int rv = 0;
        const char *cp;
        const char **lastp;
-       static struct op texec; /* Must be static (XXX but why?) */
+       /* Must be static (XXX but why?) */
+       static struct op texec;
        int type_flags;
        int keepasn_ok;
        int fcflags = FC_BI|FC_FUNC|FC_PATH;
        bool bourne_function_call = false;
        struct block *l_expand, *l_assign;
 
-       /* snag the last argument for $_ XXX not the same as AT&T ksh,
+       /*
+        * snag the last argument for $_ XXX not the same as AT&T ksh,
         * which only seems to set $_ after a newline (but not in
         * functions/dot scripts, but in interactive and script) -
         * perhaps save last arg here and set it in shell()?.
@@ -427,7 +523,8 @@ comexec(struct op *t, struct tbl *volatile tp, const char **ap,
                    KSH_RETURN_ERROR);
        }
 
-       /* Deal with the shell builtins builtin, exec and command since
+       /**
+        * Deal with the shell builtins builtin, exec and command since
         * they can be followed by other commands. This must be done before
         * we know if we should create a local block which must be done
         * before we can do a path search (in case the assignments change
@@ -441,15 +538,16 @@ comexec(struct op *t, struct tbl *volatile tp, const char **ap,
         */
        keepasn_ok = 1;
        while (tp && tp->type == CSHELL) {
-               fcflags = FC_BI|FC_FUNC|FC_PATH;/* undo effects of command */
+               /* undo effects of command */
+               fcflags = FC_BI|FC_FUNC|FC_PATH;
                if (tp->val.f == c_builtin) {
-                       if ((cp = *++ap) == NULL) {
+                       if ((cp = *++ap) == NULL ||
+                           (!strcmp(cp, "--") && (cp = *++ap) == NULL)) {
                                tp = NULL;
                                break;
                        }
-                       tp = findcom(cp, FC_BI);
-                       if (tp == NULL)
-                               errorf("builtin: %s: not a builtin", cp);
+                       if ((tp = findcom(cp, FC_BI)) == NULL)
+                               errorf("%s: %s: %s", Tbuiltin, cp, "not a builtin");
                        continue;
                } else if (tp->val.f == c_exec) {
                        if (ap[1] == NULL)
@@ -459,27 +557,30 @@ comexec(struct op *t, struct tbl *volatile tp, const char **ap,
                } else if (tp->val.f == c_command) {
                        int optc, saw_p = 0;
 
-                       /* Ugly dealing with options in two places (here and
-                        * in c_command(), but such is life)
+                       /*
+                        * Ugly dealing with options in two places (here
+                        * and in c_command(), but such is life)
                         */
                        ksh_getopt_reset(&builtin_opt, 0);
                        while ((optc = ksh_getopt(ap, &builtin_opt, ":p")) == 'p')
                                saw_p = 1;
                        if (optc != EOF)
-                               break;  /* command -vV or something */
+                               /* command -vV or something */
+                               break;
                        /* don't look for functions */
                        fcflags = FC_BI|FC_PATH;
                        if (saw_p) {
                                if (Flag(FRESTRICTED)) {
-                                       warningf(true,
-                                           "command -prestricted");
+                                       warningf(true, "%s: %s",
+                                           "command -p", "restricted");
                                        rv = 1;
                                        goto Leave;
                                }
                                fcflags |= FC_DEFPATH;
                        }
                        ap += builtin_opt.optind;
-                       /* POSIX says special builtins lose their status
+                       /*
+                        * POSIX says special builtins lose their status
                         * if accessed using command.
                         */
                        keepasn_ok = 0;
@@ -488,6 +589,25 @@ comexec(struct op *t, struct tbl *volatile tp, const char **ap,
                                subst_exstat = 0;
                                break;
                        }
+#ifndef MKSH_NO_EXTERNAL_CAT
+               } else if (tp->val.f == c_cat) {
+                       /*
+                        * if we have any flags, do not use the builtin
+                        * in theory, we could allow -u, but that would
+                        * mean to use ksh_getopt here and possibly ad-
+                        * ded complexity and more code and isn't worth
+                        * additional hassle (and the builtin must call
+                        * ksh_getopt already but can't come back here)
+                        */
+                       if (ap[1] && ap[1][0] == '-' && ap[1][1] != '\0' &&
+                           /* argument, begins with -, is not - or -- */
+                           (ap[1][1] != '-' || ap[1][2] != '\0'))
+                               /* don't look for builtins or functions */
+                               fcflags = FC_PATH;
+                       else
+                               /* go on, use the builtin */
+                               break;
+#endif
                } else
                        break;
                tp = findcom(ap[0], fcflags & (FC_BI|FC_FUNC));
@@ -518,8 +638,8 @@ comexec(struct op *t, struct tbl *volatile tp, const char **ap,
 
                if (Flag(FXTRACE)) {
                        if (i == 0)
-                               shf_fprintf(shl_out, "%s",
-                                       substitute(str_val(global("PS4")), 0));
+                               shf_puts(substitute(str_val(global("PS4")), 0),
+                                   shl_out);
                        shf_fprintf(shl_out, "%s%c", cp,
                            t->vars[i + 1] ? ' ' : '\n');
                        if (!t->vars[i + 1])
@@ -535,7 +655,7 @@ comexec(struct op *t, struct tbl *volatile tp, const char **ap,
                goto Leave;
        } else if (!tp) {
                if (Flag(FRESTRICTED) && vstrchr(cp, '/')) {
-                       warningf(true, "%s: restricted", cp);
+                       warningf(true, "%s: %s", cp, "restricted");
                        rv = 1;
                        goto Leave;
                }
@@ -543,54 +663,49 @@ comexec(struct op *t, struct tbl *volatile tp, const char **ap,
        }
 
        switch (tp->type) {
-       case CSHELL:                    /* shell built-in */
+
+       /* shell built-in */
+       case CSHELL:
                rv = call_builtin(tp, (const char **)ap);
                break;
 
-       case CFUNC: {                   /* function call */
+       /* function call */
+       case CFUNC: {
                volatile unsigned char old_xflag;
-               volatile Tflag old_inuse;
-               const char *volatile old_kshname;
+               volatile uint32_t old_inuse;
+               const char * volatile old_kshname;
 
                if (!(tp->flag & ISSET)) {
                        struct tbl *ftp;
 
                        if (!tp->u.fpath) {
-                               if (tp->u2.errno_) {
-                                       warningf(true,
-                                           "%s: can't find function "
-                                           "definition file - %s",
-                                           cp, strerror(tp->u2.errno_));
-                                       rv = 126;
-                               } else {
-                                       warningf(true,
-                                           "%s: can't find function "
-                                           "definition file", cp);
-                                       rv = 127;
-                               }
+                               rv = (tp->u2.errnov == ENOENT) ? 127 : 126;
+                               warningf(true, "%s: %s %s: %s", cp,
+                                   "can't find", "function definition file",
+                                   strerror(tp->u2.errnov));
                                break;
                        }
                        if (include(tp->u.fpath, 0, NULL, 0) < 0) {
                                rv = errno;
-                               warningf(true,
-                                   "%s: can't open function definition file %s - %s",
-                                   cp, tp->u.fpath, strerror(rv));
+                               warningf(true, "%s: %s %s %s: %s", cp,
+                                   "can't open", "function definition file",
+                                   tp->u.fpath, strerror(rv));
                                rv = 127;
                                break;
                        }
                        if (!(ftp = findfunc(cp, hash(cp), false)) ||
                            !(ftp->flag & ISSET)) {
-                               warningf(true,
-                                   "%s: function not defined by %s",
-                                   cp, tp->u.fpath);
+                               warningf(true, "%s: %s %s", cp,
+                                   "function not defined by", tp->u.fpath);
                                rv = 127;
                                break;
                        }
                        tp = ftp;
                }
 
-               /* ksh functions set $0 to function name, POSIX functions leave
-                * $0 unchanged.
+               /*
+                * ksh functions set $0 to function name, POSIX
+                * functions leave $0 unchanged.
                 */
                old_kshname = kshname;
                if (tp->flag & FKSH)
@@ -601,7 +716,8 @@ comexec(struct op *t, struct tbl *volatile tp, const char **ap,
                for (i = 0; *ap++ != NULL; i++)
                        ;
                e->loc->argc = i - 1;
-               /* ksh-style functions handle getopts sanely,
+               /*
+                * ksh-style functions handle getopts sanely,
                 * Bourne/POSIX functions are insane...
                 */
                if (tp->flag & FKSH) {
@@ -611,7 +727,7 @@ comexec(struct op *t, struct tbl *volatile tp, const char **ap,
                }
 
                old_xflag = Flag(FXTRACE);
-               Flag(FXTRACE) = tp->flag & TRACE ? 1 : 0;
+               Flag(FXTRACE) |= tp->flag & TRACE ? 1 : 0;
 
                old_inuse = tp->flag & FINUSE;
                tp->flag |= FINUSE;
@@ -626,9 +742,10 @@ comexec(struct op *t, struct tbl *volatile tp, const char **ap,
                kshname = old_kshname;
                Flag(FXTRACE) = old_xflag;
                tp->flag = (tp->flag & ~FINUSE) | old_inuse;
-               /* Were we deleted while executing? If so, free the execution
-                * tree. todo: Unfortunately, the table entry is never re-used
-                * until the lookup table is expanded.
+               /*
+                * Were we deleted while executing? If so, free the
+                * execution tree. TODO: Unfortunately, the table entry
+                * is never re-used until the lookup table is expanded.
                 */
                if ((tp->flag & (FDELETE|FINUSE)) == FDELETE) {
                        if (tp->flag & ALLOC) {
@@ -651,26 +768,23 @@ comexec(struct op *t, struct tbl *volatile tp, const char **ap,
                        /* NOTREACHED */
                default:
                        quitenv(NULL);
-                       internal_errorf("CFUNC %d", i);
+                       internal_errorf("%s %d", "CFUNC", i);
                }
                break;
        }
 
-       case CEXEC:             /* executable command */
-       case CTALIAS:           /* tracked alias */
+       /* executable command */
+       case CEXEC:
+       /* tracked alias */
+       case CTALIAS:
                if (!(tp->flag&ISSET)) {
-                       /* errno_ will be set if the named command was found
-                        * but could not be executed (permissions, no execute
-                        * bit, directory, etc). Print out a (hopefully)
-                        * useful error message and set the exit status to 126.
-                        */
-                       if (tp->u2.errno_) {
-                               warningf(true, "%s: cannot execute - %s", cp,
-                                   strerror(tp->u2.errno_));
-                               rv = 126;       /* POSIX */
-                       } else {
-                               warningf(true, "%s: not found", cp);
+                       if (tp->u2.errnov == ENOENT) {
                                rv = 127;
+                               warningf(true, "%s: %s", cp, "not found");
+                       } else {
+                               rv = 126;
+                               warningf(true, "%s: %s: %s", cp, "can't execute",
+                                   strerror(tp->u2.errnov));
                        }
                        break;
                }
@@ -694,7 +808,8 @@ comexec(struct op *t, struct tbl *volatile tp, const char **ap,
 
                /* to fork we set up a TEXEC node and call execute */
                texec.type = TEXEC;
-               texec.left = t; /* for tprint */
+               /* for tprint */
+               texec.left = t;
                texec.str = tp->val.s;
                texec.args = ap;
                rv = exchild(&texec, flags, xerrok, -1);
@@ -714,14 +829,15 @@ scriptexec(struct op *tp, const char **ap)
        const char *sh;
 #ifndef MKSH_SMALL
        unsigned char *cp;
-       char buf[64];           /* 64 == MAXINTERP in MirBSD <sys/param.h> */
+       /* 64 == MAXINTERP in MirBSD <sys/param.h> */
+       char buf[64];
        int fd;
 #endif
        union mksh_ccphack args, cap;
 
        sh = str_val(global("EXECSHELL"));
        if (sh && *sh)
-               sh = search(sh, path, X_OK, NULL);
+               sh = search_path(sh, path, X_OK, NULL);
        if (!sh || !*sh)
                sh = MKSH_DEFAULT_EXECSHELL;
 
@@ -734,8 +850,15 @@ scriptexec(struct op *tp, const char **ap)
                        /* read error -> no good */
                        buf[0] = '\0';
                close(fd);
-               /* scan for newline (or CR) or NUL _before_ end of buffer */
+
+               /* skip UTF-8 Byte Order Mark, if present */
                cp = (unsigned char *)buf;
+               if ((cp[0] == 0xEF) && (cp[1] == 0xBB) && (cp[2] == 0xBF))
+                       cp += 3;
+               /* save begin of shebang for later */
+               fd = (char *)cp - buf;          /* either 0 or (if BOM) 3 */
+
+               /* scan for newline (or CR) or NUL _before_ end of buffer */
                while ((char *)cp < (buf + sizeof(buf)))
                        if (*cp == '\0' || *cp == '\n' || *cp == '\r') {
                                *cp = '\0';
@@ -745,13 +868,13 @@ scriptexec(struct op *tp, const char **ap)
                /* if the shebang line is longer than MAXINTERP, bail out */
                if ((char *)cp >= (buf + sizeof(buf)))
                        goto noshebang;
-               /* skip UTF-8 Byte Order Mark, if present */
-               cp = (unsigned char *)buf;
-               if ((cp[0] == 0xEF) && (cp[1] == 0xBB) && (cp[2] == 0xBF))
-                       cp += 3;
+
+               /* restore begin of shebang position (buf+0 or buf+3) */
+               cp = (unsigned char *)(buf + fd);
                /* bail out if read error (above) or no shebang */
                if ((cp[0] != '#') || (cp[1] != '!'))
                        goto noshebang;
+
                cp += 2;
                /* skip whitespace before shell name */
                while (*cp == ' ' || *cp == '\t')
@@ -806,7 +929,7 @@ shcomexec(const char **wp)
 
        tp = ktsearch(&builtins, *wp, hash(*wp));
        if (tp == NULL)
-               internal_errorf("shcomexec: %s", *wp);
+               internal_errorf("%s: %s", "shcomexec", *wp);
        return (call_builtin(tp, wp));
 }
 
@@ -842,20 +965,31 @@ findfunc(const char *name, uint32_t h, bool create)
 int
 define(const char *name, struct op *t)
 {
+       uint32_t nhash;
        struct tbl *tp;
        bool was_set = false;
 
-       while (1) {
-               tp = findfunc(name, hash(name), true);
+       nhash = hash(name);
+
+       if (t != NULL && !tobool(t->u.ksh_func)) {
+               /* drop same-name aliases for POSIX functions */
+               if ((tp = ktsearch(&aliases, name, nhash)))
+                       ktdelete(tp);
+       }
+
+       while (/* CONSTCOND */ 1) {
+               tp = findfunc(name, nhash, true);
 
                if (tp->flag & ISSET)
                        was_set = true;
-               /* If this function is currently being executed, we zap this
-                * table entry so findfunc() won't see it
+               /*
+                * If this function is currently being executed, we zap
+                * this table entry so findfunc() won't see it
                 */
                if (tp->flag & FINUSE) {
                        tp->name[0] = '\0';
-                       tp->flag &= ~DEFINED; /* ensure it won't be found */
+                       /* ensure it won't be found */
+                       tp->flag &= ~DEFINED;
                        tp->flag |= FDELETE;
                } else
                        break;
@@ -866,7 +1000,8 @@ define(const char *name, struct op *t)
                tfree(tp->val.t, tp->areap);
        }
 
-       if (t == NULL) {                /* undefine */
+       if (t == NULL) {
+               /* undefine */
                ktdelete(tp);
                return (was_set ? 0 : 1);
        }
@@ -882,19 +1017,22 @@ define(const char *name, struct op *t)
 /*
  * add builtin
  */
-void
+const char *
 builtin(const char *name, int (*func) (const char **))
 {
        struct tbl *tp;
-       Tflag flag;
+       uint32_t flag;
 
        /* see if any flags should be set for this builtin */
        for (flag = 0; ; name++) {
-               if (*name == '=')       /* command does variable assignment */
+               if (*name == '=')
+                       /* command does variable assignment */
                        flag |= KEEPASN;
-               else if (*name == '*')  /* POSIX special builtin */
+               else if (*name == '*')
+                       /* POSIX special builtin */
                        flag |= SPEC_BI;
-               else if (*name == '+')  /* POSIX regular builtin */
+               else if (*name == '+')
+                       /* POSIX regular builtin */
                        flag |= REG_BI;
                else
                        break;
@@ -904,6 +1042,8 @@ builtin(const char *name, int (*func) (const char **))
        tp->flag = DEFINED | flag;
        tp->type = CSHELL;
        tp->val.f = func;
+
+       return (name);
 }
 
 /*
@@ -916,8 +1056,10 @@ findcom(const char *name, int flags)
        static struct tbl temp;
        uint32_t h = hash(name);
        struct tbl *tp = NULL, *tbi;
-       unsigned char insert = Flag(FTRACKALL); /* insert if not found */
-       char *fpath;                    /* for function autoloading */
+       /* insert if not found */
+       unsigned char insert = Flag(FTRACKALL);
+       /* for function autoloading */
+       char *fpath;
        union mksh_cchack npath;
 
        if (vstrchr(name, '/')) {
@@ -927,7 +1069,8 @@ findcom(const char *name, int flags)
                goto Search;
        }
        tbi = (flags & FC_BI) ? ktsearch(&builtins, name, h) : NULL;
-       /* POSIX says special builtins first, then functions, then
+       /*
+        * POSIX says special builtins first, then functions, then
         * POSIX regular builtins, then search path...
         */
        if ((flags & FC_SPECBI) && tbi && (tbi->flag & SPEC_BI))
@@ -937,10 +1080,10 @@ findcom(const char *name, int flags)
                if (tp && !(tp->flag & ISSET)) {
                        if ((fpath = str_val(global("FPATH"))) == null) {
                                tp->u.fpath = NULL;
-                               tp->u2.errno_ = 0;
+                               tp->u2.errnov = ENOENT;
                        } else
-                               tp->u.fpath = search(name, fpath, R_OK,
-                                   &tp->u2.errno_);
+                               tp->u.fpath = search_path(name, fpath, R_OK,
+                                   &tp->u2.errnov);
                }
        }
        if (!tp && (flags & FC_REGBI) && tbi && (tbi->flag & REG_BI))
@@ -949,7 +1092,8 @@ findcom(const char *name, int flags)
                tp = tbi;
        if (!tp && (flags & FC_PATH) && !(flags & FC_DEFPATH)) {
                tp = ktsearch(&taliases, name, h);
-               if (tp && (tp->flag & ISSET) && access(tp->val.s, X_OK) != 0) {
+               if (tp && (tp->flag & ISSET) &&
+                   ksh_access(tp->val.s, X_OK) != 0) {
                        if (tp->flag & ALLOC) {
                                tp->flag &= ~ALLOC;
                                afree(tp->val.s, APERM);
@@ -969,10 +1113,12 @@ findcom(const char *name, int flags)
                                tp = &temp;
                                tp->type = CEXEC;
                        }
-                       tp->flag = DEFINED;     /* make ~ISSET */
+                       /* make ~ISSET */
+                       tp->flag = DEFINED;
                }
-               npath.ro = search(name, flags & FC_DEFPATH ? def_path : path,
-                   X_OK, &tp->u2.errno_);
+               npath.ro = search_path(name,
+                   (flags & FC_DEFPATH) ? def_path : path,
+                   X_OK, &tp->u2.errnov);
                if (npath.ro) {
                        strdupx(tp->val.s, npath.ro, APERM);
                        if (npath.ro != name)
@@ -980,16 +1126,18 @@ findcom(const char *name, int flags)
                        tp->flag |= ISSET|ALLOC;
                } else if ((flags & FC_FUNC) &&
                    (fpath = str_val(global("FPATH"))) != null &&
-                   (npath.ro = search(name, fpath, R_OK,
-                   &tp->u2.errno_)) != NULL) {
-                       /* An undocumented feature of AT&T ksh is that it
-                        * searches FPATH if a command is not found, even
-                        * if the command hasn't been set up as an autoloaded
-                        * function (ie, no typeset -uf).
+                   (npath.ro = search_path(name, fpath, R_OK,
+                   &tp->u2.errnov)) != NULL) {
+                       /*
+                        * An undocumented feature of AT&T ksh is that
+                        * it searches FPATH if a command is not found,
+                        * even if the command hasn't been set up as an
+                        * autoloaded function (ie, no typeset -uf).
                         */
                        tp = &temp;
                        tp->type = CFUNC;
-                       tp->flag = DEFINED; /* make ~ISSET */
+                       /* make ~ISSET */
+                       tp->flag = DEFINED;
                        tp->u.fpath = npath.ro;
                }
        }
@@ -998,9 +1146,10 @@ findcom(const char *name, int flags)
 
 /*
  * flush executable commands with relative paths
+ * (just relative or all?)
  */
 void
-flushcom(int all)      /* just relative or all */
+flushcom(bool all)
 {
        struct tbl *tp;
        struct tstate ts;
@@ -1015,49 +1164,50 @@ flushcom(int all)       /* just relative or all */
                }
 }
 
-/* Check if path is something we want to find. Returns -1 for failure. */
-int
-search_access(const char *lpath, int mode,
-    int *errnop)       /* set if candidate found, but not suitable */
+/* check if path is something we want to find */
+static int
+search_access(const char *fn, int mode)
 {
-       int ret, err = 0;
-       struct stat statb;
-
-       if (stat(lpath, &statb) < 0)
-               return (-1);
-       ret = access(lpath, mode);
-       if (ret < 0)
-               err = errno; /* File exists, but we can't access it */
-       else if (mode == X_OK && (!S_ISREG(statb.st_mode) ||
-           !(statb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)))) {
-               /* This 'cause access() says root can execute everything */
-               ret = -1;
-               err = S_ISDIR(statb.st_mode) ? EISDIR : EACCES;
-       }
-       if (err && errnop && !*errnop)
-               *errnop = err;
-       return (ret);
+       struct stat sb;
+
+       if (stat(fn, &sb) < 0)
+               /* file does not exist */
+               return (ENOENT);
+       /* LINTED use of access */
+       if (access(fn, mode) < 0)
+               /* file exists, but we can't access it */
+               return (errno);
+       if (mode == X_OK && (!S_ISREG(sb.st_mode) ||
+           !(sb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH))))
+               /* access(2) may say root can execute everything */
+               return (S_ISDIR(sb.st_mode) ? EISDIR : EACCES);
+       return (0);
 }
 
 /*
  * search for command with PATH
  */
 const char *
-search(const char *name, const char *lpath,
-    int mode,          /* R_OK or X_OK */
-    int *errnop)       /* set if candidate found, but not suitable */
+search_path(const char *name, const char *lpath,
+    /* R_OK or X_OK */
+    int mode,
+    /* set if candidate found, but not suitable */
+    int *errnop)
 {
        const char *sp, *p;
        char *xp;
        XString xs;
-       int namelen;
+       size_t namelen;
+       int ec = 0, ev;
 
-       if (errnop)
-               *errnop = 0;
        if (vstrchr(name, '/')) {
-               if (search_access(name, mode, errnop) == 0)
+               if ((ec = search_access(name, mode)) == 0) {
+ search_path_ok:
+                       if (errnop)
+                               *errnop = 0;
                        return (name);
-               return (NULL);
+               }
+               goto search_path_err;
        }
 
        namelen = strlen(name) + 1;
@@ -1077,12 +1227,20 @@ search(const char *name, const char *lpath,
                sp = p;
                XcheckN(xs, xp, namelen);
                memcpy(xp, name, namelen);
-               if (search_access(Xstring(xs, xp), mode, errnop) == 0)
-                       return (Xclose(xs, xp + namelen));
+               if ((ev = search_access(Xstring(xs, xp), mode)) == 0) {
+                       name = Xclose(xs, xp + namelen);
+                       goto search_path_ok;
+               }
+               /* accumulate non-ENOENT errors only */
+               if (ev != ENOENT && ec == 0)
+                       ec = ev;
                if (*sp++ == '\0')
                        sp = NULL;
        }
        Xfree(xs, xp);
+ search_path_err:
+       if (errnop)
+               *errnop = ec ? ec : ENOENT;
        return (NULL);
 }
 
@@ -1094,11 +1252,11 @@ call_builtin(struct tbl *tp, const char **wp)
        builtin_argv0 = wp[0];
        builtin_flag = tp->flag;
        shf_reopen(1, SHF_WR, shl_stdout);
-       shl_stdout_ok = 1;
+       shl_stdout_ok = true;
        ksh_getopt_reset(&builtin_opt, GF_ERROR);
        rv = (*tp->val.f)(wp);
        shf_flush(shl_stdout);
-       shl_stdout_ok = 0;
+       shl_stdout_ok = false;
        builtin_flag = 0;
        builtin_argv0 = NULL;
        return (rv);
@@ -1141,7 +1299,8 @@ iosetup(struct ioword *iop, struct tbl *tp)
 
        case IOWRITE:
                flags = O_WRONLY | O_CREAT | O_TRUNC;
-               /* The stat() is here to allow redirections to
+               /*
+                * The stat() is here to allow redirections to
                 * things like /dev/null without error.
                 */
                if (Flag(FNOCLOBBER) && !(iop->flag & IOCLOB) &&
@@ -1156,7 +1315,7 @@ iosetup(struct ioword *iop, struct tbl *tp)
        case IOHERE:
                do_open = 0;
                /* herein() returns -2 if error has been printed */
-               u = herein(iop->heredoc, iop->flag & IOEVAL);
+               u = herein(iop->heredoc, iop->flag & IOEVAL, NULL);
                /* cp may have wrong name */
                break;
 
@@ -1165,7 +1324,8 @@ iosetup(struct ioword *iop, struct tbl *tp)
 
                do_open = 0;
                if (*cp == '-' && !cp[1]) {
-                       u = 1009;        /* prevent error return below */
+                       /* prevent error return below */
+                       u = 1009;
                        do_close = 1;
                } else if ((u = check_fd(cp,
                    X_OK | ((iop->flag & IORDUP) ? R_OK : W_OK),
@@ -1175,14 +1335,15 @@ iosetup(struct ioword *iop, struct tbl *tp)
                        return (-1);
                }
                if (u == iop->unit)
-                       return (0);             /* "dup from" == "dup to" */
+                       /* "dup from" == "dup to" */
+                       return (0);
                break;
        }
        }
 
        if (do_open) {
                if (Flag(FRESTRICTED) && (flags & O_CREAT)) {
-                       warningf(true, "%s: restricted", cp);
+                       warningf(true, "%s: %s", cp, "restricted");
                        return (-1);
                }
                u = open(cp, flags, 0666);
@@ -1191,7 +1352,7 @@ iosetup(struct ioword *iop, struct tbl *tp)
                /* herein() may already have printed message */
                if (u == -1) {
                        u = errno;
-                       warningf(true, "cannot %s %s: %s",
+                       warningf(true, "can't %s %s: %s",
                            iotype == IODUP ? "dup" :
                            (iotype == IOREAD || iotype == IOHERE) ?
                            "open" : "create", cp, strerror(u));
@@ -1204,7 +1365,8 @@ iosetup(struct ioword *iop, struct tbl *tp)
                if (u == iop->unit)
                        e->savefd[iop->unit] = -1;
                else
-                       /* c_exec() assumes e->savefd[fd] set for any
+                       /*
+                        * c_exec() assumes e->savefd[fd] set for any
                         * redirections. Ask savefd() not to close iop->unit;
                         * this allows error messages to be seen if iop->unit
                         * is 2; also means we can't lose the fd (eg, both
@@ -1220,8 +1382,8 @@ iosetup(struct ioword *iop, struct tbl *tp)
                        int ev;
 
                        ev = errno;
-                       warningf(true,
-                           "could not finish (dup) redirection %s: %s",
+                       warningf(true, "%s %s %s",
+                           "can't finish (dup) redirection",
                            snptreef(NULL, 32, "%R", &iotmp),
                            strerror(ev));
                        if (iotype != IODUP)
@@ -1230,84 +1392,111 @@ iosetup(struct ioword *iop, struct tbl *tp)
                }
                if (iotype != IODUP)
                        close(u);
-               /* Touching any co-process fd in an empty exec
+               /*
+                * Touching any co-process fd in an empty exec
                 * causes the shell to close its copies
                 */
                else if (tp && tp->type == CSHELL && tp->val.f == c_exec) {
-                       if (iop->flag & IORDUP) /* possible exec <&p */
+                       if (iop->flag & IORDUP)
+                               /* possible exec <&p */
                                coproc_read_close(u);
-                       else                    /* possible exec >&p */
+                       else
+                               /* possible exec >&p */
                                coproc_write_close(u);
                }
        }
-       if (u == 2) /* Clear any write errors */
+       if (u == 2)
+               /* Clear any write errors */
                shf_reopen(2, SHF_WR, shl_out);
        return (0);
 }
 
 /*
- * open here document temp file.
- * if unquoted here, expand here temp file into second temp file.
+ * Process here documents by providing the content, either as
+ * result (globally allocated) string or in a temp file; if
+ * unquoted, the string is expanded first.
  */
 static int
-herein(const char *content, int sub)
+hereinval(const char *content, int sub, char **resbuf, struct shf *shf)
+{
+       const char *ccp;
+       struct source *s, *osource;
+
+       osource = source;
+       newenv(E_ERRH);
+       if (sigsetjmp(e->jbuf, 0)) {
+               source = osource;
+               quitenv(shf);
+               /* special to iosetup(): don't print error */
+               return (-2);
+       }
+       if (sub) {
+               /* do substitutions on the content of heredoc */
+               s = pushs(SSTRING, ATEMP);
+               s->start = s->str = content;
+               source = s;
+               if (yylex(ONEWORD|HEREDOC) != LWORD)
+                       internal_errorf("%s: %s", "herein", "yylex");
+               source = osource;
+               ccp = evalstr(yylval.cp, 0);
+       } else
+               ccp = content;
+
+       if (resbuf == NULL)
+               shf_puts(ccp, shf);
+       else
+               strdupx(*resbuf, ccp, APERM);
+
+       quitenv(NULL);
+       return (0);
+}
+
+static int
+herein(const char *content, int sub, char **resbuf)
 {
-       volatile int fd = -1;
-       struct source *s, *volatile osource;
-       struct shf *volatile shf;
+       int fd = -1;
+       struct shf *shf;
        struct temp *h;
        int i;
 
        /* ksh -c 'cat << EOF' can cause this... */
        if (content == NULL) {
-               warningf(true, "here document missing");
-               return (-2); /* special to iosetup(): don't print error */
+               warningf(true, "%s missing", "here document");
+               /* special to iosetup(): don't print error */
+               return (-2);
        }
 
-       /* Create temp file to hold content (done before newenv so temp
-        * doesn't get removed too soon).
+       /* skip all the fd setup if we just want the value */
+       if (resbuf != NULL)
+               return (hereinval(content, sub, resbuf, NULL));
+
+       /*
+        * Create temp file to hold content (done before newenv
+        * so temp doesn't get removed too soon).
         */
        h = maketemp(ATEMP, TT_HEREDOC_EXP, &e->temps);
        if (!(shf = h->shf) || (fd = open(h->name, O_RDONLY, 0)) < 0) {
-               fd = errno;
+               i = errno;
                warningf(true, "can't %s temporary file %s: %s",
-                   !shf ? "create" : "open",
-                   h->name, strerror(fd));
+                   !shf ? "create" : "open", h->name, strerror(i));
                if (shf)
                        shf_close(shf);
-               return (-2 /* special to iosetup(): don't print error */);
+               /* special to iosetup(): don't print error */
+               return (-2);
        }
 
-       osource = source;
-       newenv(E_ERRH);
-       i = sigsetjmp(e->jbuf, 0);
-       if (i) {
-               source = osource;
-               quitenv(shf);
+       if (hereinval(content, sub, NULL, shf) == -2) {
                close(fd);
-               return (-2); /* special to iosetup(): don't print error */
+               /* special to iosetup(): don't print error */
+               return (-2);
        }
-       if (sub) {
-               /* Do substitutions on the content of heredoc */
-               s = pushs(SSTRING, ATEMP);
-               s->start = s->str = content;
-               source = s;
-               if (yylex(ONEWORD|HEREDOC) != LWORD)
-                       internal_errorf("herein: yylex");
-               source = osource;
-               shf_puts(evalstr(yylval.cp, 0), shf);
-       } else
-               shf_puts(content, shf);
-
-       quitenv(NULL);
 
        if (shf_close(shf) == EOF) {
                i = errno;
                close(fd);
-               fd = errno;
-               warningf(true, "error writing %s: %s, %s", h->name,
-                   strerror(i), strerror(fd));
-               return (-2);    /* special to iosetup(): don't print error */
+               warningf(true, "%s: %s: %s", "write", h->name, strerror(i));
+               /* special to iosetup(): don't print error */
+               return (-2);
        }
 
        return (fd);
@@ -1328,8 +1517,9 @@ do_selectargs(const char **ap, bool print_menu)
 
        for (argct = 0; ap[argct]; argct++)
                ;
-       while (1) {
-               /* Menu is printed if
+       while (/* CONSTCOND */ 1) {
+               /*-
+                * Menu is printed if
                 *      - this is the first time around the select loop
                 *      - the user enters a blank line
                 *      - the REPLY parameter is empty
@@ -1353,11 +1543,11 @@ struct select_menu_info {
        int num_width;
 };
 
-static char *select_fmt_entry(char *, int, int, const void *);
+static char *select_fmt_entry(char *, size_t, int, const void *);
 
 /* format a single select menu item */
 static char *
-select_fmt_entry(char *buf, int buflen, int i, const void *arg)
+select_fmt_entry(char *buf, size_t buflen, int i, const void *arg)
 {
        const struct select_menu_info *smi =
            (const struct select_menu_info *)arg;
@@ -1375,7 +1565,8 @@ pr_menu(const char * const *ap)
 {
        struct select_menu_info smi;
        const char * const *pp;
-       int acols = 0, aocts = 0, i, n;
+       size_t acols = 0, aocts = 0, i;
+       int n;
 
        /*
         * width/column calculations were done once and saved, but this
@@ -1412,19 +1603,20 @@ pr_menu(const char * const *ap)
 }
 
 /* XXX: horrible kludge to fit within the framework */
-static char *plain_fmt_entry(char *, int, int, const void *);
+static char *plain_fmt_entry(char *, size_t, int, const void *);
 
 static char *
-plain_fmt_entry(char *buf, int buflen, int i, const void *arg)
+plain_fmt_entry(char *buf, size_t buflen, int i, const void *arg)
 {
-       shf_snprintf(buf, buflen, "%s", ((const char * const *)arg)[i]);
+       strlcpy(buf, ((const char * const *)arg)[i], buflen);
        return (buf);
 }
 
 int
 pr_list(char * const *ap)
 {
-       int acols = 0, aocts = 0, i, n;
+       size_t acols = 0, aocts = 0, i;
+       int n;
        char * const *pp;
 
        for (n = 0, pp = ap; *pp; n++, pp++) {
@@ -1468,8 +1660,10 @@ dbteste_isa(Test_env *te, Test_meta meta)
 
        if (meta == TM_UNOP || meta == TM_BINOP) {
                if (uqword) {
-                       char buf[8];    /* longer than the longest operator */
+                       /* longer than the longest operator */
+                       char buf[8];
                        char *q = buf;
+
                        for (p = *te->pos.wp;
                            *p == CHAR && q < &buf[sizeof(buf) - 1]; p += 2)
                                *q++ = p[1];
index 6c5710c..3c8252d 100644 (file)
@@ -1,7 +1,7 @@
 /*     $OpenBSD: expr.c,v 1.21 2009/06/01 19:00:57 deraadt Exp $       */
 
 /*-
- * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
  *     Thorsten Glaser <tg@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -22,7 +22,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/expr.c,v 1.44 2010/08/14 21:35:13 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/expr.c,v 1.49 2011/09/07 15:24:14 tg Exp $");
 
 /* The order of these enums is constrained by the order of opinfo[] */
 enum token {
@@ -141,12 +141,6 @@ struct expr_state {
        (mksh_ari_t)((x)->val.u op (y)->val.u) :        \
        (mksh_ari_t)((x)->val.i op (y)->val.i)          \
 )
-#define chvui(x, op)   do {                    \
-       if (es->natural)                        \
-               (x)->val.u = op (x)->val.u;     \
-       else                                    \
-               (x)->val.i = op (x)->val.i;     \
-} while (/* CONSTCOND */ 0)
 #define stvui(x, n)    do {                    \
        if (es->natural)                        \
                (x)->val.u = (n);               \
@@ -269,26 +263,28 @@ evalerr(Expr_state *es, enum error_type type, const char *str)
                default:
                        s = opinfo[(int)es->tok].name;
                }
-               warningf(true, "%s: unexpected '%s'", es->expression, s);
+               warningf(true, "%s: %s '%s'", es->expression,
+                   "unexpected", s);
                break;
 
        case ET_BADLIT:
-               warningf(true, "%s: bad number '%s'", es->expression, str);
+               warningf(true, "%s: %s '%s'", es->expression,
+                   "bad number", str);
                break;
 
        case ET_RECURSIVE:
-               warningf(true, "%s: expression recurses on parameter '%s'",
-                   es->expression, str);
+               warningf(true, "%s: %s '%s'", es->expression,
+                   "expression recurses on parameter", str);
                break;
 
        case ET_LVALUE:
-               warningf(true, "%s: %s requires lvalue",
-                   es->expression, str);
+               warningf(true, "%s: %s %s",
+                   es->expression, str, "requires lvalue");
                break;
 
        case ET_RDONLY:
-               warningf(true, "%s: %s applied to read only variable",
-                   es->expression, str);
+               warningf(true, "%s: %s %s",
+                   es->expression, str, "applied to read only variable");
                break;
 
        default: /* keep gcc happy */
@@ -313,11 +309,11 @@ evalexpr(Expr_state *es, int prec)
                        exprtoken(es);
                        vl = intvar(es, evalexpr(es, P_PRIMARY));
                        if (op == O_BNOT)
-                               chvui(vl, ~);
+                               vl->val.i = ~vl->val.i;
                        else if (op == O_LNOT)
-                               chvui(vl, !);
+                               vl->val.i = !vl->val.i;
                        else if (op == O_MINUS)
-                               chvui(vl, -);
+                               vl->val.i = -vl->val.i;
                        /* op == O_PLUS is a no-op */
                } else if (op == OPEN_PAREN) {
                        exprtoken(es);
@@ -503,7 +499,7 @@ exprtoken(Expr_state *es)
                for (; ksh_isalnux(c); c = *cp)
                        cp++;
                if (c == '[') {
-                       int len;
+                       size_t len;
 
                        len = array_ref_len(cp);
                        if (len == 0)
@@ -680,12 +676,12 @@ utf_widthadj(const char *src, const char **dst)
        return (width);
 }
 
-int
+size_t
 utf_mbswidth(const char *s)
 {
-       size_t len;
+       size_t len, width = 0;
        unsigned int wc;
-       int width = 0, cw;
+       int cw;
 
        if (!UTFMODE)
                return (strlen(s));
@@ -808,7 +804,7 @@ utf_wctomb(char *dst, unsigned int wc)
  * disclaims all warranties with regard to this software.
  */
 
-__RCSID("$miros: src/lib/libc/i18n/wcwidth.c,v 1.8 2008/09/20 12:01:18 tg Exp $");
+__RCSID("$miros: src/lib/libc/i18n/wcwidth.c,v 1.10 2010/12/11 16:05:03 tg Exp $");
 
 int
 utf_wcwidth(unsigned int c)
@@ -817,54 +813,77 @@ utf_wcwidth(unsigned int c)
                unsigned short first;
                unsigned short last;
        } comb[] = {
-               { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 },
-               { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 },
-               { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 },
-               { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 },
-               { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED },
+               /* Unicode 6.0.0 BMP */
+               { 0x0300, 0x036F }, { 0x0483, 0x0489 }, { 0x0591, 0x05BD },
+               { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, { 0x05C4, 0x05C5 },
+               { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 }, { 0x0610, 0x061A },
+               { 0x064B, 0x065F }, { 0x0670, 0x0670 }, { 0x06D6, 0x06DD },
+               { 0x06DF, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED },
                { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A },
-               { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 },
+               { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0816, 0x0819 },
+               { 0x081B, 0x0823 }, { 0x0825, 0x0827 }, { 0x0829, 0x082D },
+               { 0x0859, 0x085B }, { 0x0900, 0x0902 }, { 0x093A, 0x093A },
                { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D },
-               { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 },
+               { 0x0951, 0x0957 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 },
                { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD },
                { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C },
                { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D },
-               { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC },
-               { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD },
-               { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C },
-               { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D },
-               { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 },
+               { 0x0A51, 0x0A51 }, { 0x0A70, 0x0A71 }, { 0x0A75, 0x0A75 },
+               { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC }, { 0x0AC1, 0x0AC5 },
+               { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD }, { 0x0AE2, 0x0AE3 },
+               { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C }, { 0x0B3F, 0x0B3F },
+               { 0x0B41, 0x0B44 }, { 0x0B4D, 0x0B4D }, { 0x0B56, 0x0B56 },
+               { 0x0B62, 0x0B63 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 },
                { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 },
-               { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC },
-               { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD },
-               { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D },
-               { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 },
-               { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E },
-               { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC },
-               { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 },
-               { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E },
-               { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 },
-               { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 },
-               { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 },
-               { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F },
-               { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 },
-               { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD },
-               { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD },
-               { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 },
-               { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B },
-               { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 },
+               { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0C62, 0x0C63 },
+               { 0x0CBC, 0x0CBC }, { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 },
+               { 0x0CCC, 0x0CCD }, { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D44 },
+               { 0x0D4D, 0x0D4D }, { 0x0D62, 0x0D63 }, { 0x0DCA, 0x0DCA },
+               { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, { 0x0E31, 0x0E31 },
+               { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, { 0x0EB1, 0x0EB1 },
+               { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, { 0x0EC8, 0x0ECD },
+               { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, { 0x0F37, 0x0F37 },
+               { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, { 0x0F80, 0x0F84 },
+               { 0x0F86, 0x0F87 }, { 0x0F8D, 0x0F97 }, { 0x0F99, 0x0FBC },
+               { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, { 0x1032, 0x1037 },
+               { 0x1039, 0x103A }, { 0x103D, 0x103E }, { 0x1058, 0x1059 },
+               { 0x105E, 0x1060 }, { 0x1071, 0x1074 }, { 0x1082, 0x1082 },
+               { 0x1085, 0x1086 }, { 0x108D, 0x108D }, { 0x109D, 0x109D },
+               { 0x1160, 0x11FF }, { 0x135D, 0x135F }, { 0x1712, 0x1714 },
+               { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, { 0x1772, 0x1773 },
+               { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, { 0x17C6, 0x17C6 },
+               { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, { 0x180B, 0x180D },
+               { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, { 0x1927, 0x1928 },
+               { 0x1932, 0x1932 }, { 0x1939, 0x193B }, { 0x1A17, 0x1A18 },
+               { 0x1A56, 0x1A56 }, { 0x1A58, 0x1A5E }, { 0x1A60, 0x1A60 },
+               { 0x1A62, 0x1A62 }, { 0x1A65, 0x1A6C }, { 0x1A73, 0x1A7C },
+               { 0x1A7F, 0x1A7F }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 },
                { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 },
-               { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF },
-               { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 },
-               { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F },
-               { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B },
-               { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F },
-               { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }
+               { 0x1B6B, 0x1B73 }, { 0x1B80, 0x1B81 }, { 0x1BA2, 0x1BA5 },
+               { 0x1BA8, 0x1BA9 }, { 0x1BE6, 0x1BE6 }, { 0x1BE8, 0x1BE9 },
+               { 0x1BED, 0x1BED }, { 0x1BEF, 0x1BF1 }, { 0x1C2C, 0x1C33 },
+               { 0x1C36, 0x1C37 }, { 0x1CD0, 0x1CD2 }, { 0x1CD4, 0x1CE0 },
+               { 0x1CE2, 0x1CE8 }, { 0x1CED, 0x1CED }, { 0x1DC0, 0x1DE6 },
+               { 0x1DFC, 0x1DFF }, { 0x200B, 0x200F }, { 0x202A, 0x202E },
+               { 0x2060, 0x2064 }, { 0x206A, 0x206F }, { 0x20D0, 0x20F0 },
+               { 0x2CEF, 0x2CF1 }, { 0x2D7F, 0x2D7F }, { 0x2DE0, 0x2DFF },
+               { 0x302A, 0x302F }, { 0x3099, 0x309A }, { 0xA66F, 0xA672 },
+               { 0xA67C, 0xA67D }, { 0xA6F0, 0xA6F1 }, { 0xA802, 0xA802 },
+               { 0xA806, 0xA806 }, { 0xA80B, 0xA80B }, { 0xA825, 0xA826 },
+               { 0xA8C4, 0xA8C4 }, { 0xA8E0, 0xA8F1 }, { 0xA926, 0xA92D },
+               { 0xA947, 0xA951 }, { 0xA980, 0xA982 }, { 0xA9B3, 0xA9B3 },
+               { 0xA9B6, 0xA9B9 }, { 0xA9BC, 0xA9BC }, { 0xAA29, 0xAA2E },
+               { 0xAA31, 0xAA32 }, { 0xAA35, 0xAA36 }, { 0xAA43, 0xAA43 },
+               { 0xAA4C, 0xAA4C }, { 0xAAB0, 0xAAB0 }, { 0xAAB2, 0xAAB4 },
+               { 0xAAB7, 0xAAB8 }, { 0xAABE, 0xAABF }, { 0xAAC1, 0xAAC1 },
+               { 0xABE5, 0xABE5 }, { 0xABE8, 0xABE8 }, { 0xABED, 0xABED },
+               { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, { 0xFE20, 0xFE26 },
+               { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }
        };
        size_t min = 0, mid, max = NELEM(comb) - 1;
 
        /* test for 8-bit control characters */
-       if (c < 32 || (c >= 0x7f && c < 0xa0))
+       if (c < 32 || (c >= 0x7F && c < 0xA0))
                return (c ? -1 : 0);
 
        /* binary search in table of non-spacing characters */
@@ -880,16 +899,36 @@ utf_wcwidth(unsigned int c)
                }
 
        /* if we arrive here, c is not a combining or C0/C1 control char */
+
        return ((c >= 0x1100 && (
-           c <= 0x115f || /* Hangul Jamo init. consonants */
-           c == 0x2329 || c == 0x232a ||
-           (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f) || /* CJK ... Yi */
-           (c >= 0xac00 && c <= 0xd7a3) || /* Hangul Syllables */
-           (c >= 0xf900 && c <= 0xfaff) || /* CJK Compatibility Ideographs */
-           (c >= 0xfe10 && c <= 0xfe19) || /* Vertical forms */
-           (c >= 0xfe30 && c <= 0xfe6f) || /* CJK Compatibility Forms */
-           (c >= 0xff00 && c <= 0xff60) || /* Fullwidth Forms */
-           (c >= 0xffe0 && c <= 0xffe6))) ? 2 : 1);
+           c <= 0x115F || /* Hangul Jamo init. consonants */
+           c == 0x2329 || c == 0x232A ||
+           (c >= 0x2E80 && c <= 0xA4CF && c != 0x303F) || /* CJK ... Yi */
+           (c >= 0xAC00 && c <= 0xD7A3) || /* Hangul Syllables */
+           (c >= 0xF900 && c <= 0xFAFF) || /* CJK Compatibility Ideographs */
+           (c >= 0xFE10 && c <= 0xFE19) || /* Vertical forms */
+           (c >= 0xFE30 && c <= 0xFE6F) || /* CJK Compatibility Forms */
+           (c >= 0xFF00 && c <= 0xFF60) || /* Fullwidth Forms */
+           (c >= 0xFFE0 && c <= 0xFFE6))) ? 2 : 1);
 }
 /* --- end of wcwidth.c excerpt --- */
 #endif
+
+/*
+ * Wrapper around access(2) because it says root can execute everything
+ * on some operating systems. Does not set errno, no user needs it. Use
+ * this iff mode can have the X_OK bit set, access otherwise.
+ */
+int
+ksh_access(const char *fn, int mode)
+{
+       int rv;
+       struct stat sb;
+
+       if ((rv = access(fn, mode)) == 0 && kshuid == 0 && (mode & X_OK) &&
+           (rv = stat(fn, &sb)) == 0 && !S_ISDIR(sb.st_mode) &&
+           (sb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) == 0)
+               rv = -1;
+
+       return (rv);
+}
index 9d9c03a..23f45de 100644 (file)
@@ -4,7 +4,8 @@
 /*     $OpenBSD: c_ulimit.c,v 1.17 2008/03/21 12:51:19 millert Exp $   */
 
 /*-
- * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
+ *              2010, 2011
  *     Thorsten Glaser <tg@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/funcs.c,v 1.157 2010/08/24 14:42:01 tg Exp $");
+#if HAVE_SELECT
+#if HAVE_SYS_BSDTYPES_H
+#include <sys/bsdtypes.h>
+#endif
+#if HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+#if HAVE_BSTRING_H
+#include <bstring.h>
+#endif
+#endif
+
+__RCSID("$MirOS: src/bin/mksh/funcs.c,v 1.197 2011/09/07 15:24:15 tg Exp $");
 
 #if HAVE_KILLPG
 /*
@@ -40,51 +53,69 @@ __RCSID("$MirOS: src/bin/mksh/funcs.c,v 1.157 2010/08/24 14:42:01 tg Exp $");
 
 /* XXX conditions correct? */
 #if !defined(RLIM_INFINITY) && !defined(MKSH_NO_LIMITS)
-#define MKSH_NO_LIMITS
+#define MKSH_NO_LIMITS 1
 #endif
 
 #ifdef MKSH_NO_LIMITS
-#define c_ulimit c_label
+#define c_ulimit       c_true
+#endif
+
+#if defined(ANDROID)
+static int c_android_lsmod(const char **);
 #endif
 
-extern uint8_t set_refflag;
+static int
+c_true(const char **wp MKSH_A_UNUSED)
+{
+       return (0);
+}
 
-/* A leading = means assignments before command are kept;
+static int
+c_false(const char **wp MKSH_A_UNUSED)
+{
+       return (1);
+}
+
+/*
+ * A leading = means assignments before command are kept;
  * a leading * means a POSIX special builtin;
  * a leading + means a POSIX regular builtin
  * (* and + should not be combined).
  */
 const struct builtin mkshbuiltins[] = {
        {"*=.", c_dot},
-       {"*=:", c_label},
+       {"*=:", c_true},
        {"[", c_test},
        {"*=break", c_brkcont},
-       {"=builtin", c_builtin},
+       {Tgbuiltin, c_builtin},
        {"*=continue", c_brkcont},
        {"*=eval", c_eval},
        {"*=exec", c_exec},
        {"*=exit", c_exitreturn},
-       {"+false", c_label},
+       {"+false", c_false},
        {"*=return", c_exitreturn},
-       {"*=set", c_set},
+       {Tsgset, c_set},
        {"*=shift", c_shift},
        {"=times", c_times},
        {"*=trap", c_trap},
        {"+=wait", c_wait},
        {"+read", c_read},
        {"test", c_test},
-       {"+true", c_label},
+       {"+true", c_true},
        {"ulimit", c_ulimit},
        {"+umask", c_umask},
        {"*=unset", c_unset},
-       {"+alias", c_alias},    /* no =: AT&T manual wrong */
+       /* no =: AT&T manual wrong */
+       {Tpalias, c_alias},
        {"+cd", c_cd},
-       {"chdir", c_cd},        /* dash compatibility hack */
+       /* dash compatibility hack */
+       {"chdir", c_cd},
        {"+command", c_command},
        {"echo", c_print},
        {"*=export", c_typeset},
        {"+fc", c_fc},
        {"+getopts", c_getopts},
+       {"=global", c_typeset},
        {"+jobs", c_jobs},
        {"+kill", c_kill},
        {"let", c_let},
@@ -94,19 +125,30 @@ const struct builtin mkshbuiltins[] = {
 #endif
        {"pwd", c_pwd},
        {"*=readonly", c_typeset},
-       {T__typeset, c_typeset},
-       {"+unalias", c_unalias},
+       {T_typeset, c_typeset},
+       {Tpunalias, c_unalias},
        {"whence", c_whence},
 #ifndef MKSH_UNEMPLOYED
        {"+bg", c_fgbg},
        {"+fg", c_fgbg},
 #endif
        {"bind", c_bind},
+       {"cat", c_cat},
 #if HAVE_MKNOD
        {"mknod", c_mknod},
 #endif
        {"realpath", c_realpath},
        {"rename", c_rename},
+#if HAVE_SELECT
+       {"sleep", c_sleep},
+#endif
+#ifdef __MirBSD__
+       /* alias to "true" for historical reasons */
+       {"domainname", c_true},
+#endif
+#if defined(ANDROID)
+       {"lsmod", c_android_lsmod},
+#endif
        {NULL, (int (*)(const char **))NULL}
 };
 
@@ -163,7 +205,6 @@ static const struct t_op b_ops[] = {
        {"",    TO_NONOP }
 };
 
-static int test_eaccess(const char *, int);
 static int test_oexpr(Test_env *, bool);
 static int test_aexpr(Test_env *, bool);
 static int test_nexpr(Test_env *, bool);
@@ -171,331 +212,16 @@ static int test_primary(Test_env *, bool);
 static Test_op ptest_isa(Test_env *, Test_meta);
 static const char *ptest_getopnd(Test_env *, Test_op, bool);
 static void ptest_error(Test_env *, int, const char *);
-static char *kill_fmt_entry(char *, int, int, const void *);
+static char *kill_fmt_entry(char *, size_t, int, const void *);
 static void p_time(struct shf *, bool, long, int, int,
     const char *, const char *)
-    MKSH_A_NONNULL((nonnull (6, 7)));
-static char *do_realpath(const char *);
-
-static char *
-do_realpath(const char *upath)
-{
-       char *xp, *ip, *tp, *ipath, *ldest = NULL;
-       XString xs;
-       ptrdiff_t pos;
-       size_t len;
-       int symlinks = 32;      /* max. recursion depth */
-       int llen;
-       struct stat sb;
-#ifdef NO_PATH_MAX
-       size_t ldestlen = 0;
-#define pathlen sb.st_size
-#define pathcnd (ldestlen < (pathlen + 1))
-#else
-#define pathlen PATH_MAX
-#define pathcnd (!ldest)
-#endif
-
-       if (upath[0] == '/') {
-               /* upath is an absolute pathname */
-               strdupx(ipath, upath, ATEMP);
-       } else {
-               /* upath is a relative pathname, prepend cwd */
-               if ((tp = ksh_get_wd(NULL)) == NULL || tp[0] != '/')
-                       return (NULL);
-               ipath = shf_smprintf("%s/%s", tp, upath);
-               afree(tp, ATEMP);
-       }
-
-       Xinit(xs, xp, strlen(ip = ipath) + 1, ATEMP);
-
-       while (*ip) {
-               /* skip slashes in input */
-               while (*ip == '/')
-                       ++ip;
-               if (!*ip)
-                       break;
-
-               /* get next pathname component from input */
-               tp = ip;
-               while (*ip && *ip != '/')
-                       ++ip;
-               len = ip - tp;
-
-               /* check input for "." and ".." */
-               if (tp[0] == '.') {
-                       if (len == 1)
-                               /* just continue with the next one */
-                               continue;
-                       else if (len == 2 && tp[1] == '.') {
-                               /* strip off last pathname component */
-                               while (xp > Xstring(xs, xp))
-                                       if (*--xp == '/')
-                                               break;
-                               /* then continue with the next one */
-                               continue;
-                       }
-               }
-
-               /* store output position away, then append slash to output */
-               pos = Xsavepos(xs, xp);
-               /* 1 for the '/' and len + 1 for tp and the NUL from below */
-               XcheckN(xs, xp, 1 + len + 1);
-               Xput(xs, xp, '/');
-
-               /* append next pathname component to output */
-               memcpy(xp, tp, len);
-               xp += len;
-               *xp = '\0';
-
-               /* lstat the current output, see if it's a symlink */
-               if (lstat(Xstring(xs, xp), &sb)) {
-                       /* lstat failed */
-                       if (errno == ENOENT) {
-                               /* because the pathname does not exist */
-                               while (*ip == '/')
-                                       /* skip any trailing slashes */
-                                       ++ip;
-                               /* no more components left? */
-                               if (!*ip)
-                                       /* we can still return successfully */
-                                       break;
-                               /* more components left? fall through */
-                       }
-                       /* not ENOENT or not at the end of ipath */
-                       goto notfound;
-               }
-
-               /* check if we encountered a symlink? */
-               if (S_ISLNK(sb.st_mode)) {
-                       /* reached maximum recursion depth? */
-                       if (!symlinks--) {
-                               /* yep, prevent infinite loops */
-                               errno = ELOOP;
-                               goto notfound;
-                       }
-
-                       /* get symlink(7) target */
-                       if (pathcnd)
-                               ldest = aresize(ldest, pathlen + 1, ATEMP);
-                       llen = readlink(Xstring(xs, xp), ldest, pathlen);
-                       if (llen < 0)
-                               /* oops... */
-                               goto notfound;
-                       ldest[llen] = '\0';
-
-                       /*
-                        * restart if symlink target is an absolute path,
-                        * otherwise continue with currently resolved prefix
-                        */
-                       xp = (ldest[0] == '/') ? Xstring(xs, xp) :
-                           Xrestpos(xs, xp, pos);
-                       tp = shf_smprintf("%s%s%s", ldest, *ip ? "/" : "", ip);
-                       afree(ipath, ATEMP);
-                       ip = ipath = tp;
-               }
-               /* otherwise (no symlink) merely go on */
-       }
-
-       /*
-        * either found the target and successfully resolved it,
-        * or found its parent directory and may create it
-        */
-       if (Xlength(xs, xp) == 0)
-               /*
-                * if the resolved pathname is "", make it "/",
-                * otherwise do not add a trailing slash
-                */
-               Xput(xs, xp, '/');
-       Xput(xs, xp, '\0');
-
-       /*
-        * if source path had a trailing slash, check if target path
-        * is not a non-directory existing file
-        */
-       if (ip > ipath && ip[-1] == '/') {
-               if (stat(Xstring(xs, xp), &sb)) {
-                       if (errno != ENOENT)
-                               goto notfound;
-               } else if (!S_ISDIR(sb.st_mode)) {
-                       errno = ENOTDIR;
-                       goto notfound;
-               }
-               /* target now either does not exist or is a directory */
-       }
-
-       /* return target path */
-       if (ldest != NULL)
-               afree(ldest, ATEMP);
-       afree(ipath, ATEMP);
-       return (Xclose(xs, xp));
-
- notfound:
-       llen = errno;   /* save; free(3) might trash it */
-       if (ldest != NULL)
-               afree(ldest, ATEMP);
-       afree(ipath, ATEMP);
-       Xfree(xs, xp);
-       errno = llen;
-       return (NULL);
-
-#undef pathlen
-#undef pathcnd
-}
-
-int
-c_cd(const char **wp)
-{
-       int optc, rv, phys_path;
-       bool physical = Flag(FPHYSICAL) ? true : false;
-       int cdnode;                     /* was a node from cdpath added in? */
-       bool printpath = false;         /* print where we cd'd? */
-       struct tbl *pwd_s, *oldpwd_s;
-       XString xs;
-       char *dir, *allocd = NULL, *tryp, *pwd, *cdpath;
-
-       while ((optc = ksh_getopt(wp, &builtin_opt, "LP")) != -1)
-               switch (optc) {
-               case 'L':
-                       physical = false;
-                       break;
-               case 'P':
-                       physical = true;
-                       break;
-               case '?':
-                       return (1);
-               }
-       wp += builtin_opt.optind;
-
-       if (Flag(FRESTRICTED)) {
-               bi_errorf("restricted shell - can't cd");
-               return (1);
-       }
-
-       pwd_s = global("PWD");
-       oldpwd_s = global("OLDPWD");
-
-       if (!wp[0]) {
-               /* No arguments - go home */
-               if ((dir = str_val(global("HOME"))) == null) {
-                       bi_errorf("no home directory (HOME not set)");
-                       return (1);
-               }
-       } else if (!wp[1]) {
-               /* One argument: - or dir */
-               strdupx(allocd, wp[0], ATEMP);
-               if (ksh_isdash((dir = allocd))) {
-                       afree(allocd, ATEMP);
-                       allocd = NULL;
-                       dir = str_val(oldpwd_s);
-                       if (dir == null) {
-                               bi_errorf("no OLDPWD");
-                               return (1);
-                       }
-                       printpath = true;
-               }
-       } else if (!wp[2]) {
-               /* Two arguments - substitute arg1 in PWD for arg2 */
-               int ilen, olen, nlen, elen;
-               char *cp;
-
-               if (!current_wd[0]) {
-                       bi_errorf("don't know current directory");
-                       return (1);
-               }
-               /* substitute arg1 for arg2 in current path.
-                * if the first substitution fails because the cd fails
-                * we could try to find another substitution. For now
-                * we don't
-                */
-               if ((cp = strstr(current_wd, wp[0])) == NULL) {
-                       bi_errorf("bad substitution");
-                       return (1);
-               }
-               ilen = cp - current_wd;
-               olen = strlen(wp[0]);
-               nlen = strlen(wp[1]);
-               elen = strlen(current_wd + ilen + olen) + 1;
-               dir = allocd = alloc(ilen + nlen + elen, ATEMP);
-               memcpy(dir, current_wd, ilen);
-               memcpy(dir + ilen, wp[1], nlen);
-               memcpy(dir + ilen + nlen, current_wd + ilen + olen, elen);
-               printpath = true;
-       } else {
-               bi_errorf("too many arguments");
-               return (1);
-       }
-
-#ifdef NO_PATH_MAX
-       /* only a first guess; make_path will enlarge xs if necessary */
-       XinitN(xs, 1024, ATEMP);
-#else
-       XinitN(xs, PATH_MAX, ATEMP);
-#endif
-
-       cdpath = str_val(global("CDPATH"));
-       do {
-               cdnode = make_path(current_wd, dir, &cdpath, &xs, &phys_path);
-               if (physical)
-                       rv = chdir(tryp = Xstring(xs, xp) + phys_path);
-               else {
-                       simplify_path(Xstring(xs, xp));
-                       rv = chdir(tryp = Xstring(xs, xp));
-               }
-       } while (rv < 0 && cdpath != NULL);
-
-       if (rv < 0) {
-               if (cdnode)
-                       bi_errorf("%s: bad directory", dir);
-               else
-                       bi_errorf("%s - %s", tryp, strerror(errno));
-               afree(allocd, ATEMP);
-               return (1);
-       }
-
-       /* allocd (above) => dir, which is no longer used */
-       afree(allocd, ATEMP);
-       allocd = NULL;
-
-       /* Clear out tracked aliases with relative paths */
-       flushcom(0);
-
-       /* Set OLDPWD (note: unsetting OLDPWD does not disable this
-        * setting in AT&T ksh)
-        */
-       if (current_wd[0])
-               /* Ignore failure (happens if readonly or integer) */
-               setstr(oldpwd_s, current_wd, KSH_RETURN_ERROR);
-
-       if (Xstring(xs, xp)[0] != '/') {
-               pwd = NULL;
-       } else if (!physical || !(pwd = allocd = do_realpath(Xstring(xs, xp))))
-               pwd = Xstring(xs, xp);
-
-       /* Set PWD */
-       if (pwd) {
-               char *ptmp = pwd;
-
-               set_current_wd(ptmp);
-               /* Ignore failure (happens if readonly or integer) */
-               setstr(pwd_s, ptmp, KSH_RETURN_ERROR);
-       } else {
-               set_current_wd(null);
-               pwd = Xstring(xs, xp);
-               /* XXX unset $PWD? */
-       }
-       if (printpath || cdnode)
-               shprintf("%s\n", pwd);
-
-       afree(allocd, ATEMP);
-       return (0);
-}
+    MKSH_A_NONNULL((__nonnull__ (6, 7)));
 
 int
 c_pwd(const char **wp)
 {
        int optc;
-       bool physical = Flag(FPHYSICAL) ? true : false;
+       bool physical = tobool(Flag(FPHYSICAL));
        char *p, *allocd = NULL;
 
        while ((optc = ksh_getopt(wp, &builtin_opt, "LP")) != -1)
@@ -517,10 +243,12 @@ c_pwd(const char **wp)
        }
        p = current_wd[0] ? (physical ? allocd = do_realpath(current_wd) :
            current_wd) : NULL;
+       /* LINTED use of access */
        if (p && access(p, R_OK) < 0)
                p = NULL;
-       if (!p && !(p = allocd = ksh_get_wd(NULL))) {
-               bi_errorf("can't get current directory - %s", strerror(errno));
+       if (!p && !(p = allocd = ksh_get_wd())) {
+               bi_errorf("%s: %s", "can't determine current directory",
+                   strerror(errno));
                return (1);
        }
        shprintf("%s\n", p);
@@ -549,7 +277,7 @@ c_print(const char **wp)
        if (wp[0][0] == 'e') {
                /* echo builtin */
                wp++;
-               if (Flag(FPOSIX) || Flag(FSH)) {
+               if (Flag(FPOSIX) || Flag(FSH) || Flag(FAS_BUILTIN)) {
                        /* Debian Policy 10.4 compliant "echo" builtin */
                        if (*wp && !strcmp(*wp, "-n")) {
                                /* we recognise "-n" only as the first arg */
@@ -599,7 +327,8 @@ c_print(const char **wp)
 
                while ((optc = ksh_getopt(wp, &builtin_opt, opts)) != -1)
                        switch (optc) {
-                       case 'R': /* fake BSD echo command */
+                       case 'R':
+                               /* fake BSD echo command */
                                flags |= PO_PMINUSMINUS;
                                flags &= ~PO_EXPAND;
                                opts = "ne";
@@ -612,7 +341,7 @@ c_print(const char **wp)
                                break;
                        case 'p':
                                if ((fd = coproc_getfd(W_OK, &emsg)) < 0) {
-                                       bi_errorf("-p: %s", emsg);
+                                       bi_errorf("%s: %s", "-p", emsg);
                                        return (1);
                                }
                                break;
@@ -626,7 +355,7 @@ c_print(const char **wp)
                                if (!*(s = builtin_opt.optarg))
                                        fd = 0;
                                else if ((fd = check_fd(s, W_OK, &emsg)) < 0) {
-                                       bi_errorf("-u: %s: %s", s, emsg);
+                                       bi_errorf("%s: %s: %s", "-u", s, emsg);
                                        return (1);
                                }
                                break;
@@ -672,8 +401,7 @@ c_print(const char **wp)
                                        /* generic function returned Unicode */
                                        char ts[4];
 
-                                       c = utf_wctomb(ts, c - 0x100);
-                                       ts[c] = 0;
+                                       ts[utf_wctomb(ts, c - 0x100)] = 0;
                                        for (c = 0; ts[c]; ++c)
                                                Xput(xs, xp, ts[c]);
                                        continue;
@@ -695,7 +423,8 @@ c_print(const char **wp)
                int len = Xlength(xs, xp);
                int opipe = 0;
 
-               /* Ensure we aren't killed by a SIGPIPE while writing to
+               /*
+                * Ensure we aren't killed by a SIGPIPE while writing to
                 * a coprocess. AT&T ksh doesn't seem to do this (seems
                 * to just check that the co-process is alive which is
                 * not enough).
@@ -770,7 +499,8 @@ c_whence(const char **wp)
                /* Note that -p on its own is deal with in comexec() */
                if (pflag)
                        fcflags |= FC_DEFPATH;
-               /* Convert command options to whence options - note that
+               /*
+                * Convert command options to whence options - note that
                 * command -pV uses a different path search than whence -v
                 * or whence -pv. This should be considered a feature.
                 */
@@ -795,22 +525,32 @@ c_whence(const char **wp)
                if (vflag || (tp->type != CALIAS && tp->type != CEXEC &&
                    tp->type != CTALIAS))
                        shf_puts(id, shl_stdout);
+               if (vflag)
+                       switch (tp->type) {
+                       case CKEYWD:
+                       case CALIAS:
+                       case CFUNC:
+                       case CSHELL:
+                               shf_puts(" is a", shl_stdout);
+                               break;
+                       }
+
                switch (tp->type) {
                case CKEYWD:
                        if (vflag)
-                               shf_puts(" is a reserved word", shl_stdout);
+                               shf_puts(" reserved word", shl_stdout);
                        break;
                case CALIAS:
                        if (vflag)
-                               shprintf(" is an %salias for ",
-                                   (tp->flag & EXPORT) ? "exported " : null);
+                               shprintf("n %s%s for ",
+                                   (tp->flag & EXPORT) ? "exported " : null,
+                                   Talias);
                        if (!iam_whence && !vflag)
-                               shprintf("alias %s=", id);
+                               shprintf("%s %s=", Talias, id);
                        print_value_quoted(tp->val.s);
                        break;
                case CFUNC:
                        if (vflag) {
-                               shf_puts(" is a", shl_stdout);
                                if (tp->flag & EXPORT)
                                        shf_puts("n exported", shl_stdout);
                                if (tp->flag & TRACE)
@@ -821,13 +561,14 @@ c_whence(const char **wp)
                                                shprintf(" (autoload from %s)",
                                                    tp->u.fpath);
                                }
-                               shf_puts(" function", shl_stdout);
+                               shf_puts(T_function, shl_stdout);
                        }
                        break;
                case CSHELL:
                        if (vflag)
-                               shprintf(" is a%s shell builtin",
-                                   (tp->flag & SPEC_BI) ? " special" : null);
+                               shprintf("%s %s %s",
+                                   (tp->flag & SPEC_BI) ? " special" : null,
+                                   "shell", Tbuiltin);
                        break;
                case CTALIAS:
                case CEXEC:
@@ -835,14 +576,15 @@ c_whence(const char **wp)
                                if (vflag) {
                                        shf_puts(" is ", shl_stdout);
                                        if (tp->type == CTALIAS)
-                                               shprintf("a tracked %salias for ",
+                                               shprintf("a tracked %s%s for ",
                                                    (tp->flag & EXPORT) ?
-                                                   "exported " : null);
+                                                   "exported " : null,
+                                                   Talias);
                                }
                                shf_puts(tp->val.s, shl_stdout);
                        } else {
                                if (vflag)
-                                       shf_puts(" not found", shl_stdout);
+                                       shprintf(" %s\n", "not found");
                                rv = 1;
                        }
                        break;
@@ -860,37 +602,46 @@ c_whence(const char **wp)
 int
 c_command(const char **wp)
 {
-       /* Let c_whence do the work. Note that c_command() must be
+       /*
+        * Let c_whence do the work. Note that c_command() must be
         * a distinct function from c_whence() (tested in comexec()).
         */
        return (c_whence(wp));
 }
 
-/* typeset, export, and readonly */
+/* typeset, global, export, and readonly */
 int
 c_typeset(const char **wp)
 {
        struct block *l;
        struct tbl *vp, **p;
-       Tflag fset = 0, fclr = 0, flag;
+       uint32_t fset = 0, fclr = 0, flag;
        int thing = 0, field, base, optc;
        const char *opts;
        const char *fieldstr, *basestr;
        bool localv = false, func = false, pflag = false, istset = true;
 
        switch (**wp) {
-       case 'e':               /* export */
+
+       /* export */
+       case 'e':
                fset |= EXPORT;
                istset = false;
                break;
-       case 'r':               /* readonly */
+
+       /* readonly */
+       case 'r':
                fset |= RDONLY;
                istset = false;
                break;
-       case 's':               /* set */
+
+       /* set */
+       case 's':
                /* called with 'typeset -' */
                break;
-       case 't':               /* typeset */
+
+       /* typeset */
+       case 't':
                localv = true;
                break;
        }
@@ -900,7 +651,8 @@ c_typeset(const char **wp)
 
        fieldstr = basestr = NULL;
        builtin_opt.flags |= GF_PLUSOPT;
-       /* AT&T ksh seems to have 0-9 as options which are multiplied
+       /*
+        * AT&T ksh seems to have 0-9 as options which are multiplied
         * to get a number that is used with -L, -R, -Z or -i (eg, -1R2
         * sets right justify in a field of 12). This allows options
         * to be grouped in an order (eg, -Lu12), but disallows -i8 -L3 and
@@ -920,7 +672,8 @@ c_typeset(const char **wp)
                        fieldstr = builtin_opt.optarg;
                        break;
                case 'U':
-                       /* AT&T ksh uses u, but this conflicts with
+                       /*
+                        * AT&T ksh uses u, but this conflicts with
                         * upper/lower case. If this option is changed,
                         * need to change the -U below as well
                         */
@@ -948,10 +701,11 @@ c_typeset(const char **wp)
                        flag = LCASEV;
                        break;
                case 'n':
-                       set_refflag = (builtin_opt.info & GI_PLUS) ? 2 : 1;
+                       set_refflag = (builtin_opt.info & GI_PLUS) ?
+                           SRF_DISABLE : SRF_ENABLE;
                        break;
+               /* export, readonly: POSIX -p flag */
                case 'p':
-                       /* export, readonly: POSIX -p flag */
                        /* typeset: show values as well */
                        pflag = true;
                        if (istset)
@@ -964,7 +718,8 @@ c_typeset(const char **wp)
                        flag = TRACE;
                        break;
                case 'u':
-                       flag = UCASEV_AL;       /* upper case / autoload */
+                       /* upper case / autoload */
+                       flag = UCASEV_AL;
                        break;
                case 'x':
                        flag = EXPORT;
@@ -998,29 +753,35 @@ c_typeset(const char **wp)
                builtin_opt.optind++;
        }
 
-       if (func && (((fset|fclr) & ~(TRACE|UCASEV_AL|EXPORT)) || set_refflag)) {
+       if (func && (((fset|fclr) & ~(TRACE|UCASEV_AL|EXPORT)) ||
+           set_refflag != SRF_NOP)) {
                bi_errorf("only -t, -u and -x options may be used with -f");
-               set_refflag = 0;
+               set_refflag = SRF_NOP;
                return (1);
        }
        if (wp[builtin_opt.optind]) {
-               /* Take care of exclusions.
+               /*
+                * Take care of exclusions.
                 * At this point, flags in fset are cleared in fclr and vice
                 * versa. This property should be preserved.
                 */
-               if (fset & LCASEV)      /* LCASEV has priority over UCASEV_AL */
+               if (fset & LCASEV)
+                       /* LCASEV has priority over UCASEV_AL */
                        fset &= ~UCASEV_AL;
-               if (fset & LJUST)       /* LJUST has priority over RJUST */
+               if (fset & LJUST)
+                       /* LJUST has priority over RJUST */
                        fset &= ~RJUST;
-               if ((fset & (ZEROFIL|LJUST)) == ZEROFIL) { /* -Z implies -ZR */
+               if ((fset & (ZEROFIL|LJUST)) == ZEROFIL) {
+                       /* -Z implies -ZR */
                        fset |= RJUST;
                        fclr &= ~RJUST;
                }
-               /* Setting these attributes clears the others, unless they
+               /*
+                * Setting these attributes clears the others, unless they
                 * are also set in this command
                 */
                if ((fset & (LJUST | RJUST | ZEROFIL | UCASEV_AL | LCASEV |
-                   INTEGER | INT_U | INT_L)) || set_refflag)
+                   INTEGER | INT_U | INT_L)) || set_refflag != SRF_NOP)
                        fclr |= ~fset & (LJUST | RJUST | ZEROFIL | UCASEV_AL |
                            LCASEV | INTEGER | INT_U | INT_L);
        }
@@ -1035,7 +796,7 @@ c_typeset(const char **wp)
                for (i = builtin_opt.optind; wp[i]; i++) {
                        if (func) {
                                f = findfunc(wp[i], hash(wp[i]),
-                                   (fset&UCASEV_AL) ? true : false);
+                                   tobool(fset & UCASEV_AL));
                                if (!f) {
                                        /* AT&T ksh does ++rv: bogus */
                                        rv = 1;
@@ -1044,34 +805,38 @@ c_typeset(const char **wp)
                                if (fset | fclr) {
                                        f->flag |= fset;
                                        f->flag &= ~fclr;
-                               } else
-                                       fptreef(shl_stdout, 0,
-                                           f->flag & FKSH ?
-                                           "function %s %T\n" :
-                                           "%s() %T\n", wp[i], f->val.t);
+                               } else {
+                                       fpFUNCTf(shl_stdout, 0,
+                                           tobool(f->flag & FKSH),
+                                           wp[i], f->val.t);
+                                       shf_putc('\n', shl_stdout);
+                               }
                        } else if (!typeset(wp[i], fset, fclr, field, base)) {
-                               bi_errorf("%s: not identifier", wp[i]);
-                               set_refflag = 0;
+                               bi_errorf("%s: %s", wp[i], "not identifier");
+                               set_refflag = SRF_NOP;
                                return (1);
                        }
                }
-               set_refflag = 0;
+               set_refflag = SRF_NOP;
                return (rv);
        }
 
        /* list variables and attributes */
-       flag = fset | fclr; /* no difference at this point.. */
+
+       /* no difference at this point.. */
+       flag = fset | fclr;
        if (func) {
                for (l = e->loc; l; l = l->next) {
                        for (p = ktsort(&l->funs); (vp = *p++); ) {
                                if (flag && (vp->flag & flag) == 0)
                                        continue;
                                if (thing == '-')
-                                       fptreef(shl_stdout, 0, vp->flag & FKSH ?
-                                           "function %s %T\n" : "%s() %T\n",
+                                       fpFUNCTf(shl_stdout, 0,
+                                           tobool(vp->flag & FKSH),
                                            vp->name, vp->val.t);
                                else
-                                       shprintf("%s\n", vp->name);
+                                       shf_puts(vp->name, shl_stdout);
+                               shf_putc('\n', shl_stdout);
                        }
                }
        } else {
@@ -1103,15 +868,18 @@ c_typeset(const char **wp)
                                if (flag && (vp->flag & flag) == 0)
                                        continue;
                                for (; vp; vp = vp->u.array) {
-                                       /* Ignore array elements that aren't
+                                       /*
+                                        * Ignore array elements that aren't
                                         * set unless there are no set elements,
-                                        * in which case the first is reported on */
+                                        * in which case the first is reported on
+                                        */
                                        if ((vp->flag&ARRAY) && any_set &&
                                            !(vp->flag & ISSET))
                                                continue;
                                        /* no arguments */
                                        if (thing == 0 && flag == 0) {
-                                               /* AT&T ksh prints things
+                                               /*
+                                                * AT&T ksh prints things
                                                 * like export, integer,
                                                 * leftadj, zerofill, etc.,
                                                 * but POSIX says must
@@ -1119,34 +887,36 @@ c_typeset(const char **wp)
                                                 */
                                                shf_puts("typeset ", shl_stdout);
                                                if (((vp->flag&(ARRAY|ASSOC))==ASSOC))
-                                                       shf_puts("-n ", shl_stdout);
+                                                       shprintf("%s ", "-n");
                                                if ((vp->flag&INTEGER))
-                                                       shf_puts("-i ", shl_stdout);
+                                                       shprintf("%s ", "-i");
                                                if ((vp->flag&EXPORT))
-                                                       shf_puts("-x ", shl_stdout);
+                                                       shprintf("%s ", "-x");
                                                if ((vp->flag&RDONLY))
-                                                       shf_puts("-r ", shl_stdout);
+                                                       shprintf("%s ", "-r");
                                                if ((vp->flag&TRACE))
-                                                       shf_puts("-t ", shl_stdout);
+                                                       shprintf("%s ", "-t");
                                                if ((vp->flag&LJUST))
                                                        shprintf("-L%d ", vp->u2.field);
                                                if ((vp->flag&RJUST))
                                                        shprintf("-R%d ", vp->u2.field);
                                                if ((vp->flag&ZEROFIL))
-                                                       shf_puts("-Z ", shl_stdout);
+                                                       shprintf("%s ", "-Z");
                                                if ((vp->flag&LCASEV))
-                                                       shf_puts("-l ", shl_stdout);
+                                                       shprintf("%s ", "-l");
                                                if ((vp->flag&UCASEV_AL))
-                                                       shf_puts("-u ", shl_stdout);
+                                                       shprintf("%s ", "-u");
                                                if ((vp->flag&INT_U))
-                                                       shf_puts("-U ", shl_stdout);
+                                                       shprintf("%s ", "-U");
                                                shf_puts(vp->name, shl_stdout);
                                                if (pflag) {
                                                        char *s = str_val(vp);
 
                                                        shf_putc('=', shl_stdout);
-                                                       /* AT&T ksh can't have
-                                                        * justified integers.. */
+                                                       /*
+                                                        * AT&T ksh can't have
+                                                        * justified integers...
+                                                        */
                                                        if ((vp->flag &
                                                            (INTEGER|LJUST|RJUST)) ==
                                                            INTEGER)
@@ -1175,8 +945,10 @@ c_typeset(const char **wp)
                                                        char *s = str_val(vp);
 
                                                        shf_putc('=', shl_stdout);
-                                                       /* AT&T ksh can't have
-                                                        * justified integers.. */
+                                                       /*
+                                                        * AT&T ksh can't have
+                                                        * justified integers...
+                                                        */
                                                        if ((vp->flag &
                                                            (INTEGER|LJUST|RJUST)) ==
                                                            INTEGER)
@@ -1186,7 +958,8 @@ c_typeset(const char **wp)
                                                }
                                                shf_putc('\n', shl_stdout);
                                        }
-                                       /* Only report first 'element' of an array with
+                                       /*
+                                        * Only report first 'element' of an array with
                                         * no set elements.
                                         */
                                        if (!any_set)
@@ -1204,7 +977,7 @@ c_alias(const char **wp)
        struct table *t = &aliases;
        int rv = 0, prefix = 0;
        bool rflag = false, tflag, Uflag = false, pflag = false;
-       Tflag xflag = 0;
+       uint32_t xflag = 0;
        int optc;
 
        builtin_opt.flags |= GF_PLUSOPT;
@@ -1258,12 +1031,12 @@ c_alias(const char **wp)
        /* "hash -r" means reset all the tracked aliases.. */
        if (rflag) {
                static const char *args[] = {
-                       "unalias", "-ta", NULL
+                       Tunalias, "-ta", NULL
                };
 
                if (!tflag || *wp) {
-                       shf_puts("alias: -r flag can only be used with -t"
-                           " and without arguments\n", shl_stdout);
+                       shprintf("%s: -r flag can only be used with -t"
+                           " and without arguments\n", Talias);
                        return (1);
                }
                ksh_getopt_reset(&builtin_opt, GF_ERROR);
@@ -1276,7 +1049,7 @@ c_alias(const char **wp)
                for (p = ktsort(t); (ap = *p++) != NULL; )
                        if ((ap->flag & (ISSET|xflag)) == (ISSET|xflag)) {
                                if (pflag)
-                                       shf_puts("alias ", shl_stdout);
+                                       shprintf("%s ", Talias);
                                shf_puts(ap->name, shl_stdout);
                                if (prefix != '+') {
                                        shf_putc('=', shl_stdout);
@@ -1301,7 +1074,7 @@ c_alias(const char **wp)
                        ap = ktsearch(t, alias, h);
                        if (ap != NULL && (ap->flag&ISSET)) {
                                if (pflag)
-                                       shf_puts("alias ", shl_stdout);
+                                       shprintf("%s ", Talias);
                                shf_puts(ap->name, shl_stdout);
                                if (prefix != '+') {
                                        shf_putc('=', shl_stdout);
@@ -1309,7 +1082,8 @@ c_alias(const char **wp)
                                }
                                shf_putc('\n', shl_stdout);
                        } else {
-                               shprintf("%s alias not found\n", alias);
+                               shprintf("%s %s %s\n", alias, Talias,
+                                   "not found");
                                rv = 1;
                        }
                        continue;
@@ -1323,7 +1097,9 @@ c_alias(const char **wp)
                                afree(ap->val.s, APERM);
                        }
                        /* ignore values for -t (AT&T ksh does this) */
-                       newval = tflag ? search(alias, path, X_OK, NULL) : val;
+                       newval = tflag ?
+                           search_path(alias, path, X_OK, NULL) :
+                           val;
                        if (newval) {
                                strdupx(ap->val.s, newval, APERM);
                                ap->flag |= ALLOC|ISSET;
@@ -1356,7 +1132,8 @@ c_unalias(const char **wp)
                        break;
                case 'd':
 #ifdef MKSH_NOPWNAM
-                       t = NULL;       /* fix "unalias -dt" */
+                       /* fix "unalias -dt" */
+                       t = NULL;
 #else
                        t = &homedirs;
 #endif
@@ -1376,7 +1153,8 @@ c_unalias(const char **wp)
        for (; *wp != NULL; wp++) {
                ap = ktsearch(t, *wp, hash(*wp));
                if (ap == NULL) {
-                       rv = 1; /* POSIX */
+                       /* POSIX */
+                       rv = 1;
                        continue;
                }
                if (ap->flag&ALLOC) {
@@ -1407,12 +1185,14 @@ c_let(const char **wp)
        int rv = 1;
        mksh_ari_t val;
 
-       if (wp[1] == NULL) /* AT&T ksh does this */
+       if (wp[1] == NULL)
+               /* AT&T ksh does this */
                bi_errorf("no arguments");
        else
                for (wp++; *wp; wp++)
                        if (!evaluate(*wp, &val, KSH_RETURN_ERROR, true)) {
-                               rv = 2; /* distinguish error from zero result */
+                               /* distinguish error from zero result */
+                               rv = 2;
                                break;
                        } else
                                rv = val == 0;
@@ -1435,7 +1215,8 @@ c_jobs(const char **wp)
                case 'n':
                        nflag = 1;
                        break;
-               case 'z':       /* debugging: print zombies */
+               case 'z':
+                       /* debugging: print zombies */
                        nflag = -1;
                        break;
                case '?':
@@ -1478,7 +1259,7 @@ c_fgbg(const char **wp)
 
 /* format a single kill item */
 static char *
-kill_fmt_entry(char *buf, int buflen, int i, const void *arg)
+kill_fmt_entry(char *buf, size_t buflen, int i, const void *arg)
 {
        const struct kill_info *ki = (const struct kill_info *)arg;
 
@@ -1549,7 +1330,8 @@ c_kill(const char **wp)
                                        shprintf("%d\n", n);
                        }
                } else {
-                       int w, j, mess_cols, mess_octs;
+                       ssize_t w, mess_cols, mess_octs;
+                       int j;
                        struct kill_info ki;
 
                        for (j = NSIG, ki.num_width = 1; j >= 10; j /= 10)
@@ -1582,8 +1364,8 @@ c_kill(const char **wp)
                        if (j_kill(p, sig))
                                rv = 1;
                } else if (!getn(p, &n)) {
-                       bi_errorf("%s: arguments must be jobs or process IDs",
-                           p);
+                       bi_errorf("%s: %s", p,
+                           "arguments must be jobs or process IDs");
                        rv = 1;
                } else {
                        if (mksh_kill(n, sig) < 0) {
@@ -1618,22 +1400,22 @@ c_getopts(const char **wp)
 
        opts = *wp++;
        if (!opts) {
-               bi_errorf("missing options argument");
+               bi_errorf("missing %s argument", "options");
                return (1);
        }
 
        var = *wp++;
        if (!var) {
-               bi_errorf("missing name argument");
+               bi_errorf("missing %s argument", "name");
                return (1);
        }
        if (!*var || *skip_varname(var, true)) {
-               bi_errorf("%s: is not an identifier", var);
+               bi_errorf("%s: %s", var, "is not an identifier");
                return (1);
        }
 
        if (e->loc->next == NULL) {
-               internal_warningf("c_getopts: no argv");
+               internal_warningf("%s: %s", "c_getopts", "no argv");
                return (1);
        }
        /* Which arguments are we parsing... */
@@ -1660,7 +1442,8 @@ c_getopts(const char **wp)
                buf[1] = optc;
                buf[2] = '\0';
        } else {
-               /* POSIX says var is set to ? at end-of-options, AT&T ksh
+               /*
+                * POSIX says var is set to ? at end-of-options, AT&T ksh
                 * sets it to null - we go with POSIX...
                 */
                buf[0] = optc < 0 ? '?' : optc;
@@ -1671,7 +1454,8 @@ c_getopts(const char **wp)
        user_opt.uoptind = user_opt.optind;
 
        voptarg = global("OPTARG");
-       voptarg->flag &= ~RDONLY;       /* AT&T ksh clears ro and int */
+       /* AT&T ksh clears ro and int */
+       voptarg->flag &= ~RDONLY;
        /* Paranoia: ensure no bizarre results. */
        if (voptarg->flag & INTEGER)
            typeset("OPTARG", 0, INTEGER, 0, 0);
@@ -1686,7 +1470,7 @@ c_getopts(const char **wp)
        vq = global(var);
        /* Error message already printed (integer, readonly) */
        if (!setstr(vq, buf, KSH_RETURN_ERROR))
-               rv = 1;
+               rv = 2;
        if (Flag(FEXPORT))
                typeset(var, EXPORT, 0, 0, 0);
 
@@ -1725,7 +1509,8 @@ c_bind(const char **wp)
                }
        wp += builtin_opt.optind;
 
-       if (*wp == NULL)        /* list all */
+       if (*wp == NULL)
+               /* list all */
                rv = x_bind(NULL, NULL,
 #ifndef MKSH_SMALL
                    false,
@@ -1751,13 +1536,6 @@ c_bind(const char **wp)
        return (rv);
 }
 
-/* :, false and true (and ulimit if MKSH_NO_LIMITS) */
-int
-c_label(const char **wp)
-{
-       return (wp[0][0] == 'f' ? 1 : 0);
-}
-
 int
 c_shift(const char **wp)
 {
@@ -1776,7 +1554,7 @@ c_shift(const char **wp)
        } else
                n = 1;
        if (n < 0) {
-               bi_errorf("%s: bad number", arg);
+               bi_errorf("%s: %s", arg, "bad number");
                return (1);
        }
        if (l->argc < n) {
@@ -1843,7 +1621,8 @@ c_umask(const char **wp)
                        char op;
 
                        old_umask = umask((mode_t)0);
-                       umask(old_umask); /* in case of error */
+                       /* in case of error */
+                       umask(old_umask);
                        old_umask = ~old_umask;
                        new_umask = old_umask;
                        positions = 0;
@@ -1864,7 +1643,8 @@ c_umask(const char **wp)
                                                break;
                                        }
                                if (!positions)
-                                       positions = 0111; /* default is a */
+                                       /* default is a */
+                                       positions = 0111;
                                if (!vstrchr("=+-", op = *cp))
                                        break;
                                cp++;
@@ -1933,16 +1713,16 @@ c_dot(const char **wp)
                bi_errorf("missing argument");
                return (1);
        }
-       if ((file = search(cp, path, R_OK, &errcode)) == NULL) {
-               bi_errorf("%s: %s", cp,
-                   errcode ? strerror(errcode) : "not found");
+       if ((file = search_path(cp, path, R_OK, &errcode)) == NULL) {
+               bi_errorf("%s: %s", cp, strerror(errcode));
                return (1);
        }
 
        /* Set positional parameters? */
        if (wp[builtin_opt.optind + 1]) {
                argv = wp + builtin_opt.optind;
-               argv[0] = e->loc->argv[0]; /* preserve $0 */
+               /* preserve $0 */
+               argv[0] = e->loc->argv[0];
                for (argc = 0; argv[argc + 1]; argc++)
                        ;
        } else {
@@ -1973,7 +1753,8 @@ c_wait(const char **wp)
                for (; *wp; wp++)
                        rv = waitfor(*wp, &sig);
                if (rv < 0)
-                       rv = sig ? sig : 127; /* magic exit code: bad job-id */
+                       /* magic exit code: bad job-id */
+                       rv = sig ? sig : 127;
        }
        return (rv);
 }
@@ -1981,175 +1762,399 @@ c_wait(const char **wp)
 int
 c_read(const char **wp)
 {
-       int c = 0, ecode = 0, fd = 0, optc;
-       bool expande = true, historyr = false, expanding;
-       const char *cp, *emsg;
-       struct shf *shf;
-       XString cs, xs = { NULL, NULL, 0, NULL};
-       struct tbl *vp;
-       char *ccp, *xp = NULL, *wpalloc = NULL;
+#define is_ifsws(c) (ctype((c), C_IFS) && ctype((c), C_IFSWS))
        static char REPLY[] = "REPLY";
+       int c, fd = 0, rv = 0, lastparm = 0;
+       bool savehist = false, intoarray = false, aschars = false;
+       bool rawmode = false, expanding = false;
+       enum { LINES, BYTES, UPTO, READALL } readmode = LINES;
+       char delim = '\n';
+       size_t bytesleft = 128, bytesread;
+       struct tbl *vp /* FU gcc */ = NULL, *vq;
+       char *cp, *allocd = NULL, *xp;
+       const char *ccp;
+       XString xs;
+       ptrdiff_t xsave = 0;
+       struct termios tios;
+       bool restore_tios = false;
+#if HAVE_SELECT
+       bool hastimeout = false;
+       struct timeval tv, tvlim;
+#define c_read_opts "Aad:N:n:prst:u,"
+#else
+#define c_read_opts "Aad:N:n:prsu,"
+#endif
 
-       while ((optc = ksh_getopt(wp, &builtin_opt, "prsu,")) != -1)
-               switch (optc) {
-               case 'p':
-                       if ((fd = coproc_getfd(R_OK, &emsg)) < 0) {
-                               bi_errorf("-p: %s", emsg);
-                               return (1);
-                       }
-                       break;
-               case 'r':
-                       expande = false;
-                       break;
-               case 's':
-                       historyr = true;
-                       break;
-               case 'u':
-                       if (!*(cp = builtin_opt.optarg))
-                               fd = 0;
-                       else if ((fd = check_fd(cp, R_OK, &emsg)) < 0) {
-                               bi_errorf("-u: %s: %s", cp, emsg);
-                               return (1);
-                       }
-                       break;
-               case '?':
-                       return (1);
+       while ((c = ksh_getopt(wp, &builtin_opt, c_read_opts)) != -1)
+       switch (c) {
+       case 'a':
+               aschars = true;
+               /* FALLTHROUGH */
+       case 'A':
+               intoarray = true;
+               break;
+       case 'd':
+               delim = builtin_opt.optarg[0];
+               break;
+       case 'N':
+       case 'n':
+               readmode = c == 'N' ? BYTES : UPTO;
+               if (!bi_getn(builtin_opt.optarg, &c))
+                       return (2);
+               if (c == -1) {
+                       readmode = READALL;
+                       bytesleft = 1024;
+               } else
+                       bytesleft = (unsigned int)c;
+               break;
+       case 'p':
+               if ((fd = coproc_getfd(R_OK, &ccp)) < 0) {
+                       bi_errorf("%s: %s", "-p", ccp);
+                       return (2);
                }
+               break;
+       case 'r':
+               rawmode = true;
+               break;
+       case 's':
+               savehist = true;
+               break;
+#if HAVE_SELECT
+       case 't':
+               if (parse_usec(builtin_opt.optarg, &tv)) {
+                       bi_errorf("%s: %s '%s'", Tsynerr, strerror(errno),
+                           builtin_opt.optarg);
+                       return (2);
+               }
+               hastimeout = true;
+               break;
+#endif
+       case 'u':
+               if (!builtin_opt.optarg[0])
+                       fd = 0;
+               else if ((fd = check_fd(builtin_opt.optarg, R_OK, &ccp)) < 0) {
+                       bi_errorf("%s: %s: %s", "-u", builtin_opt.optarg, ccp);
+                       return (2);
+               }
+               break;
+       case '?':
+               return (2);
+       }
        wp += builtin_opt.optind;
-
        if (*wp == NULL)
                *--wp = REPLY;
 
-       /* Since we can't necessarily seek backwards on non-regular files,
-        * don't buffer them so we can't read too much.
-        */
-       shf = shf_reopen(fd, SHF_RD | SHF_INTERRUPT | can_seek(fd), shl_spare);
+       if (intoarray && wp[1] != NULL) {
+               bi_errorf("too many arguments");
+               return (2);
+       }
 
-       if ((cp = cstrchr(*wp, '?')) != NULL) {
-               strdupx(wpalloc, *wp, ATEMP);
-               wpalloc[cp - *wp] = '\0';
-               *wp = wpalloc;
+       if ((ccp = cstrchr(*wp, '?')) != NULL) {
+               strdupx(allocd, *wp, ATEMP);
+               allocd[ccp - *wp] = '\0';
+               *wp = allocd;
                if (isatty(fd)) {
-                       /* AT&T ksh says it prints prompt on fd if it's open
+                       /*
+                        * AT&T ksh says it prints prompt on fd if it's open
                         * for writing and is a tty, but it doesn't do it
                         * (it also doesn't check the interactive flag,
-                        * as is indicated in the Kornshell book).
+                        * as is indicated in the Korn Shell book).
                         */
-                       shellf("%s", cp+1);
+                       shf_puts(ccp + 1, shl_out);
+                       shf_flush(shl_out);
                }
        }
 
-       /* If we are reading from the co-process for the first time,
-        * make sure the other side of the pipe is closed first. This allows
-        * the detection of eof.
-        *
-        * This is not compatible with AT&T ksh... the fd is kept so another
-        * coproc can be started with same output, however, this means eof
-        * can't be detected... This is why it is closed here.
-        * If this call is removed, remove the eof check below, too.
-        * coproc_readw_close(fd);
-        */
+       Xinit(xs, xp, bytesleft, ATEMP);
 
-       if (historyr)
-               Xinit(xs, xp, 128, ATEMP);
-       expanding = false;
-       Xinit(cs, ccp, 128, ATEMP);
-       for (; *wp != NULL; wp++) {
-               for (ccp = Xstring(cs, ccp); ; ) {
-                       if (c == '\n' || c == EOF)
-                               break;
-                       while (1) {
-                               c = shf_getc(shf);
-                               if (c == '\0')
-                                       continue;
-                               if (c == EOF && shf_error(shf) &&
-                                   shf_errno(shf) == EINTR) {
-                                       /* Was the offending signal one that
-                                        * would normally kill a process?
-                                        * If so, pretend the read was killed.
-                                        */
-                                       ecode = fatal_trap_check();
+       if (readmode == LINES)
+               bytesleft = 1;
+       else if (isatty(fd)) {
+               x_mkraw(fd, &tios, true);
+               restore_tios = true;
+       }
 
-                                       /* non fatal (eg, CHLD), carry on */
-                                       if (!ecode) {
-                                               shf_clearerr(shf);
-                                               continue;
-                                       }
+#if HAVE_SELECT
+       if (hastimeout) {
+               gettimeofday(&tvlim, NULL);
+               timeradd(&tvlim, &tv, &tvlim);
+       }
+#endif
+
+ c_read_readloop:
+#if HAVE_SELECT
+       if (hastimeout) {
+               fd_set fdset;
+
+               FD_ZERO(&fdset);
+               FD_SET(fd, &fdset);
+               gettimeofday(&tv, NULL);
+               timersub(&tvlim, &tv, &tv);
+               if (tv.tv_sec < 0) {
+                       /* timeout expired globally */
+                       rv = 1;
+                       goto c_read_out;
+               }
+
+               switch (select(fd + 1, &fdset, NULL, NULL, &tv)) {
+               case 1:
+                       break;
+               case 0:
+                       /* timeout expired for this call */
+                       rv = 1;
+                       goto c_read_out;
+               default:
+                       bi_errorf("%s: %s", Tselect, strerror(errno));
+                       rv = 2;
+                       goto c_read_out;
+               }
+       }
+#endif
+
+       bytesread = blocking_read(fd, xp, bytesleft);
+       if (bytesread == (size_t)-1) {
+               /* interrupted */
+               if (errno == EINTR && fatal_trap_check()) {
+                       /*
+                        * Was the offending signal one that would
+                        * normally kill a process? If so, pretend
+                        * the read was killed.
+                        */
+                       rv = 2;
+                       goto c_read_out;
+               }
+               /* just ignore the signal */
+               goto c_read_readloop;
+       }
+
+       switch (readmode) {
+       case READALL:
+               if (bytesread == 0) {
+                       /* end of file reached */
+                       rv = 1;
+                       goto c_read_readdone;
+               }
+               xp += bytesread;
+               XcheckN(xs, xp, bytesleft);
+               break;
+
+       case UPTO:
+               if (bytesread == 0)
+                       /* end of file reached */
+                       rv = 1;
+               xp += bytesread;
+               goto c_read_readdone;
+
+       case BYTES:
+               if (bytesread == 0) {
+                       /* end of file reached */
+                       rv = 1;
+                       xp = Xstring(xs, xp);
+                       goto c_read_readdone;
+               }
+               xp += bytesread;
+               if ((bytesleft -= bytesread) == 0)
+                       goto c_read_readdone;
+               break;
+       case LINES:
+               if (bytesread == 0) {
+                       /* end of file reached */
+                       rv = 1;
+                       goto c_read_readdone;
+               }
+               if ((c = *xp) == '\0' && !aschars && delim != '\0') {
+                       /* skip any read NULs unless delimiter */
+                       break;
+               }
+               if (expanding) {
+                       expanding = false;
+                       if (c == delim) {
+                               if (Flag(FTALKING_I) && isatty(fd)) {
+                                       /*
+                                        * set prompt in case this is
+                                        * called from .profile or $ENV
+                                        */
+                                       set_prompt(PS2, NULL);
+                                       pprompt(prompt, 0);
                                }
+                               /* drop the backslash */
+                               --xp;
+                               /* and the delimiter */
                                break;
                        }
-                       if (historyr) {
-                               Xcheck(xs, xp);
-                               Xput(xs, xp, c);
-                       }
-                       Xcheck(cs, ccp);
-                       if (expanding) {
-                               expanding = false;
-                               if (c == '\n') {
-                                       c = 0;
-                                       if (Flag(FTALKING_I) && isatty(fd)) {
-                                               /* set prompt in case this is
-                                                * called from .profile or $ENV
-                                                */
-                                               set_prompt(PS2, NULL);
-                                               pprompt(prompt, 0);
-                                       }
-                               } else if (c != EOF)
-                                       Xput(cs, ccp, c);
-                               continue;
-                       }
-                       if (expande && c == '\\') {
-                               expanding = true;
-                               continue;
-                       }
-                       if (c == '\n' || c == EOF)
-                               break;
-                       if (ctype(c, C_IFS)) {
-                               if (Xlength(cs, ccp) == 0 && ctype(c, C_IFSWS))
-                                       continue;
-                               if (wp[1])
-                                       break;
-                       }
-                       Xput(cs, ccp, c);
-               }
-               /* strip trailing IFS white space from last variable */
-               if (!wp[1])
-                       while (Xlength(cs, ccp) && ctype(ccp[-1], C_IFS) &&
-                           ctype(ccp[-1], C_IFSWS))
-                               ccp--;
-               Xput(cs, ccp, '\0');
+               } else if (c == delim) {
+                       goto c_read_readdone;
+               } else if (!rawmode && c == '\\') {
+                       expanding = true;
+               }
+               Xcheck(xs, xp);
+               ++xp;
+               break;
+       }
+       goto c_read_readloop;
+
+ c_read_readdone:
+       bytesread = Xlength(xs, xp);
+       Xput(xs, xp, '\0');
+
+       /*-
+        * state: we finished reading the input and NUL terminated it
+        * Xstring(xs, xp) -> xp-1 = input string without trailing delim
+        * rv = 1 if EOF, 0 otherwise (errors handled already)
+        */
+
+       if (rv == 1) {
+               /* clean up coprocess if needed, on EOF */
+               coproc_read_close(fd);
+               if (readmode == READALL)
+                       /* EOF is no error here */
+                       rv = 0;
+       }
+
+       if (savehist)
+               histsave(&source->line, Xstring(xs, xp), true, false);
+
+       ccp = cp = Xclose(xs, xp);
+       expanding = false;
+       XinitN(xs, 128, ATEMP);
+       if (intoarray) {
                vp = global(*wp);
-               /* Must be done before setting export. */
                if (vp->flag & RDONLY) {
-                       shf_flush(shf);
-                       bi_errorf("%s is read only", *wp);
-                       afree(wpalloc, ATEMP);
-                       return (1);
+ c_read_splitro:
+                       bi_errorf("%s: %s", *wp, "is read only");
+ c_read_spliterr:
+                       rv = 2;
+                       afree(cp, ATEMP);
+                       goto c_read_out;
+               }
+               /* exporting an array is currently pointless */
+               unset(vp, 1);
+               /* counter for array index */
+               c = 0;
+       }
+       if (!aschars) {
+               /* skip initial IFS whitespace */
+               while (bytesread && is_ifsws(*ccp)) {
+                       ++ccp;
+                       --bytesread;
+               }
+               /* trim trailing IFS whitespace */
+               while (bytesread && is_ifsws(ccp[bytesread - 1])) {
+                       --bytesread;
+               }
+       }
+ c_read_splitloop:
+       xp = Xstring(xs, xp);
+       /* generate next word */
+       if (!bytesread) {
+               /* no more input */
+               if (intoarray)
+                       goto c_read_splitdone;
+               /* zero out next parameters */
+               goto c_read_gotword;
+       }
+       if (aschars) {
+               Xput(xs, xp, '1');
+               Xput(xs, xp, '#');
+               bytesleft = utf_ptradj(ccp);
+               while (bytesleft && bytesread) {
+                       *xp++ = *ccp++;
+                       --bytesleft;
+                       --bytesread;
+               }
+               if (xp[-1] == '\0') {
+                       xp[-1] = '0';
+                       xp[-3] = '2';
+               }
+               goto c_read_gotword;
+       }
+
+       if (!intoarray && wp[1] == NULL)
+               lastparm = 1;
+
+ c_read_splitlast:
+       /* copy until IFS character */
+       while (bytesread) {
+               char ch;
+
+               ch = *ccp;
+               if (expanding) {
+                       expanding = false;
+                       goto c_read_splitcopy;
+               } else if (ctype(ch, C_IFS)) {
+                       break;
+               } else if (!rawmode && ch == '\\') {
+                       expanding = true;
+               } else {
+ c_read_splitcopy:
+                       Xcheck(xs, xp);
+                       Xput(xs, xp, ch);
                }
+               ++ccp;
+               --bytesread;
+       }
+       xsave = Xsavepos(xs, xp);
+       /* copy word delimiter: IFSWS+IFS,IFSWS */
+       while (bytesread) {
+               char ch;
+
+               ch = *ccp;
+               if (!ctype(ch, C_IFS))
+                       break;
+               Xcheck(xs, xp);
+               Xput(xs, xp, ch);
+               ++ccp;
+               --bytesread;
+               if (!ctype(ch, C_IFSWS))
+                       break;
+       }
+       while (bytesread && is_ifsws(*ccp)) {
+               Xcheck(xs, xp);
+               Xput(xs, xp, *ccp);
+               ++ccp;
+               --bytesread;
+       }
+       /* if no more parameters, rinse and repeat */
+       if (lastparm && bytesread) {
+               ++lastparm;
+               goto c_read_splitlast;
+       }
+       /* get rid of the delimiter unless we pack the rest */
+       if (lastparm < 2)
+               xp = Xrestpos(xs, xp, xsave);
+ c_read_gotword:
+       Xput(xs, xp, '\0');
+       if (intoarray) {
+               vq = arraysearch(vp, c++);
+       } else {
+               vq = global(*wp);
+               /* must be checked before exporting */
+               if (vq->flag & RDONLY)
+                       goto c_read_splitro;
                if (Flag(FEXPORT))
                        typeset(*wp, EXPORT, 0, 0, 0);
-               if (!setstr(vp, Xstring(cs, ccp), KSH_RETURN_ERROR)) {
-                       shf_flush(shf);
-                       afree(wpalloc, ATEMP);
-                       return (1);
-               }
        }
-
-       shf_flush(shf);
-       if (historyr) {
-               Xput(xs, xp, '\0');
-               histsave(&source->line, Xstring(xs, xp), true, false);
-               Xfree(xs, xp);
+       if (!setstr(vq, Xstring(xs, xp), KSH_RETURN_ERROR))
+               goto c_read_spliterr;
+       if (aschars) {
+               setint_v(vq, vq, false);
+               /* protect from UTFMODE changes */
+               vq->type = 0;
        }
-       /* if this is the co-process fd, close the file descriptor
-        * (can get eof if and only if all processes are have died, ie,
-        * coproc.njobs is 0 and the pipe is closed).
-        */
-       if (c == EOF && !ecode)
-               coproc_read_close(fd);
+       if (intoarray || *++wp != NULL)
+               goto c_read_splitloop;
+
+ c_read_splitdone:
+       /* free up */
+       afree(cp, ATEMP);
 
-       afree(wpalloc, ATEMP);
-       return (ecode ? ecode : c == EOF);
+ c_read_out:
+       afree(allocd, ATEMP);
+       Xfree(xs, xp);
+       if (restore_tios)
+               tcsetattr(fd, TCSADRAIN, &tios);
+       return (rv);
+#undef is_ifsws
 }
 
 int
@@ -2231,20 +2236,21 @@ c_trap(const char **wp)
         * command 'exit' isn't confused with the pseudo-signal
         * 'EXIT'.
         */
-       s = (gettrap(*wp, false) == NULL) ? *wp++ : NULL; /* get command */
+       /* get command */
+       s = (gettrap(*wp, false) == NULL) ? *wp++ : NULL;
        if (s != NULL && s[0] == '-' && s[1] == '\0')
                s = NULL;
 
        /* set/clear traps */
-       while (*wp != NULL) {
-               p = gettrap(*wp++, true);
-               if (p == NULL) {
-                       bi_errorf("bad signal %s", wp[-1]);
-                       return (1);
-               }
-               settrap(p, s);
-       }
-       return (0);
+       i = 0;
+       while (*wp != NULL)
+               if ((p = gettrap(*wp++, true)) == NULL) {
+                       warningf(true, "%s: %s '%s'", builtin_argv0,
+                           "bad signal", wp[-1]);
+                       ++i;
+               } else
+                       settrap(p, s);
+       return (i);
 }
 
 int
@@ -2260,14 +2266,17 @@ c_exitreturn(const char **wp)
        if (arg) {
                if (!getn(arg, &n)) {
                        exstat = 1;
-                       warningf(true, "%s: bad number", arg);
+                       warningf(true, "%s: %s", arg, "bad number");
                } else
                        exstat = n;
-       }
-       if (wp[0][0] == 'r') { /* return */
+       } else if (trap_exstat != -1)
+               exstat = trap_exstat;
+       if (wp[0][0] == 'r') {
+               /* return */
                struct env *ep;
 
-               /* need to tell if this is exit or return so trap exit will
+               /*
+                * need to tell if this is exit or return so trap exit will
                 * work right (POSIX)
                 */
                for (ep = e; ep; ep = ep->oenv)
@@ -2282,7 +2291,8 @@ c_exitreturn(const char **wp)
                how = LSHELL;
        }
 
-       quitenv(NULL);  /* get rid of any i/o redirections */
+       /* get rid of any i/o redirections */
+       quitenv(NULL);
        unwind(how);
        /* NOTREACHED */
 }
@@ -2305,7 +2315,7 @@ c_brkcont(const char **wp)
        quit = n;
        if (quit <= 0) {
                /* AT&T ksh does this for non-interactive shells only - weird */
-               bi_errorf("%s: bad value", arg);
+               bi_errorf("%s: %s", arg, "bad value");
                return (1);
        }
 
@@ -2319,15 +2329,17 @@ c_brkcont(const char **wp)
                }
 
        if (quit) {
-               /* AT&T ksh doesn't print a message - just does what it
+               /*
+                * AT&T ksh doesn't print a message - just does what it
                 * can. We print a message 'cause it helps in debugging
                 * scripts, but don't generate an error (ie, keep going).
                 */
                if (n == quit) {
-                       warningf(true, "%s: cannot %s", wp[0], wp[0]);
+                       warningf(true, "%s: %s %s", wp[0], "can't", wp[0]);
                        return (0);
                }
-               /* POSIX says if n is too big, the last enclosing loop
+               /*
+                * POSIX says if n is too big, the last enclosing loop
                 * shall be used. Doesn't say to print an error but we
                 * do anyway 'cause the user messed up.
                 */
@@ -2350,7 +2362,7 @@ c_set(const char **wp)
        const char **owp;
 
        if (wp[1] == NULL) {
-               static const char *args[] = { "set", "-", NULL };
+               static const char *args[] = { Tset, "-", NULL };
                return (c_typeset(args));
        }
 
@@ -2361,11 +2373,12 @@ c_set(const char **wp)
        if (setargs) {
                wp += argi - 1;
                owp = wp;
-               wp[0] = l->argv[0]; /* save $0 */
+               /* save $0 */
+               wp[0] = l->argv[0];
                while (*++wp != NULL)
                        strdupx(*wp, *wp, &l->area);
                l->argc = wp - owp - 1;
-               l->argv = alloc((l->argc + 2) * sizeof(char *), &l->area);
+               l->argv = alloc2(l->argc + 2, sizeof(char *), &l->area);
                for (wp = l->argv; (*wp++ = *owp++) != NULL; )
                        ;
        }
@@ -2385,7 +2398,7 @@ int
 c_unset(const char **wp)
 {
        const char *id;
-       int optc;
+       int optc, rv = 0;
        bool unset_var = true;
 
        while ((optc = ksh_getopt(wp, &builtin_opt, "fv")) != -1)
@@ -2397,11 +2410,13 @@ c_unset(const char **wp)
                        unset_var = true;
                        break;
                case '?':
-                       return (1);
+                       /*XXX not reached due to GF_ERROR */
+                       return (2);
                }
        wp += builtin_opt.optind;
        for (; (id = *wp) != NULL; wp++)
-               if (unset_var) {        /* unset variable */
+               if (unset_var) {
+                       /* unset variable */
                        struct tbl *vp;
                        char *cp = NULL;
                        size_t n;
@@ -2419,13 +2434,15 @@ c_unset(const char **wp)
                        afree(cp, ATEMP);
 
                        if ((vp->flag&RDONLY)) {
-                               bi_errorf("%s is read only", vp->name);
-                               return (1);
-                       }
-                       unset(vp, optc);
-               } else                  /* unset function */
+                               warningf(true, "%s: %s", vp->name,
+                                   "is read only");
+                               rv = 1;
+                       } else
+                               unset(vp, optc);
+               } else
+                       /* unset function */
                        define(id, NULL);
-       return (0);
+       return (rv);
 }
 
 static void
@@ -2497,7 +2514,8 @@ timex(struct op *t, int f, volatile int *xerrok)
        } else
                tf = TF_NOARGS;
 
-       if (tf & TF_NOARGS) { /* ksh93 - report shell times (shell+kids) */
+       if (tf & TF_NOARGS) {
+               /* ksh93 - report shell times (shell+kids) */
                tf |= TF_NOREAL;
                timeradd(&ru0.ru_utime, &cru0.ru_utime, &usrtime);
                timeradd(&ru0.ru_stime, &cru0.ru_stime, &systime);
@@ -2542,17 +2560,19 @@ timex_hook(struct op *t, char **volatile *app)
        Getopt opt;
 
        ksh_getopt_reset(&opt, 0);
-       opt.optind = 0; /* start at the start */
+       /* start at the start */
+       opt.optind = 0;
        while ((optc = ksh_getopt((const char **)wp, &opt, ":p")) != -1)
                switch (optc) {
                case 'p':
                        t->str[0] |= TF_POSIX;
                        break;
                case '?':
-                       errorf("time: -%s unknown option", opt.optarg);
+                       errorf("time: -%s %s", opt.optarg,
+                           "unknown option");
                case ':':
-                       errorf("time: -%s requires an argument",
-                           opt.optarg);
+                       errorf("time: -%s %s", opt.optarg,
+                           "requires an argument");
                }
        /* Copy command words down over options. */
        if (opt.optind != 0) {
@@ -2609,7 +2629,7 @@ c_mknod(const char **wp)
                                return (1);
                        }
                        mode = getmode(set, (mode_t)(DEFFILEMODE));
-                       free(set);
+                       free_ossetmode(set);
                        break;
                default:
                        goto c_mknod_usage;
@@ -2640,28 +2660,28 @@ c_mknod(const char **wp)
 
                majnum = strtoul(argv[2], &c, 0);
                if ((c == argv[2]) || (*c != '\0')) {
-                       bi_errorf("non-numeric device major '%s'", argv[2]);
+                       bi_errorf("non-numeric %s %s '%s'", "device", "major", argv[2]);
                        goto c_mknod_err;
                }
                minnum = strtoul(argv[3], &c, 0);
                if ((c == argv[3]) || (*c != '\0')) {
-                       bi_errorf("non-numeric device minor '%s'", argv[3]);
+                       bi_errorf("non-numeric %s %s '%s'", "device", "minor", argv[3]);
                        goto c_mknod_err;
                }
                dv = makedev(majnum, minnum);
                if ((unsigned long)(major(dv)) != majnum) {
-                       bi_errorf("device major too large: %lu", majnum);
+                       bi_errorf("%s %s too large: %lu", "device", "major", majnum);
                        goto c_mknod_err;
                }
                if ((unsigned long)(minor(dv)) != minnum) {
-                       bi_errorf("device minor too large: %lu", minnum);
+                       bi_errorf("%s %s too large: %lu", "device", "minor", minnum);
                        goto c_mknod_err;
                }
                if (mknod(argv[0], mode, dv))
                        goto c_mknod_failed;
        } else if (mkfifo(argv[0], mode)) {
  c_mknod_failed:
-               bi_errorf("%s: %s", *wp, strerror(errno));
+               bi_errorf("%s: %s", argv[0], strerror(errno));
  c_mknod_err:
                rv = 1;
        }
@@ -2670,20 +2690,14 @@ c_mknod(const char **wp)
                umask(oldmode);
        return (rv);
  c_mknod_usage:
-       bi_errorf("usage: mknod [-m mode] name b|c major minor");
-       bi_errorf("usage: mknod [-m mode] name p");
+       bi_errorf("%s: %s", "usage", "mknod [-m mode] name b|c major minor");
+       bi_errorf("%s: %s", "usage", "mknod [-m mode] name p");
        return (1);
 }
 #endif
 
-/* dummy function, special case in comexec() */
-int
-c_builtin(const char **wp MKSH_A_UNUSED)
-{
-       return (0);
-}
-
-/* test(1) accepts the following grammar:
+/*-
+   test(1) accepts the following grammar:
        oexpr   ::= aexpr | aexpr "-o" oexpr ;
        aexpr   ::= nexpr | nexpr "-a" aexpr ;
        nexpr   ::= primary | "!" nexpr ;
@@ -2704,7 +2718,8 @@ c_builtin(const char **wp MKSH_A_UNUSED)
        operand ::= <any thing>
 */
 
-#define T_ERR_EXIT     2       /* POSIX says > 1 for errors */
+/* POSIX says > 1 for errors */
+#define T_ERR_EXIT     2
 
 int
 c_test(const char **wp)
@@ -2737,11 +2752,23 @@ c_test(const char **wp)
         * our parser does the right thing for the omitted steps.
         */
        if (argc <= 5) {
-               const char **owp = wp;
+               const char **owp = wp, **owpend = te.wp_end;
                int invert = 0;
                Test_op op;
                const char *opnd1, *opnd2;
 
+               if (argc >= 2 && ((*te.isa)(&te, TM_OPAREN))) {
+                       te.pos.wp = te.wp_end - 1;
+                       if ((*te.isa)(&te, TM_CPAREN)) {
+                               argc -= 2;
+                               te.wp_end--;
+                               te.pos.wp = owp + 2;
+                       } else {
+                               te.pos.wp = owp + 1;
+                               te.wp_end = owpend;
+                       }
+               }
+
                while (--argc >= 0) {
                        if ((*te.isa)(&te, TM_END))
                                return (!0);
@@ -2762,8 +2789,6 @@ c_test(const char **wp)
                        }
                        if (argc == 1) {
                                opnd1 = (*te.getopnd)(&te, TO_NONOP, 1);
-                               if (strcmp(opnd1, "-t") == 0)
-                                   break;
                                res = (*te.eval)(&te, TO_STNZE, opnd1,
                                    NULL, 1);
                                if (invert & 1)
@@ -2776,6 +2801,7 @@ c_test(const char **wp)
                                break;
                }
                te.pos.wp = owp + 1;
+               te.wp_end = owpend;
        }
 
        return (test_parse(&te));
@@ -2813,99 +2839,184 @@ test_eval(Test_env *te, Test_op op, const char *opnd1, const char *opnd2,
        if (!do_eval)
                return (0);
 
-       switch ((int)op) {
+       switch (op) {
+
        /*
         * Unary Operators
         */
-       case TO_STNZE: /* -n */
+
+       /* -n */
+       case TO_STNZE:
                return (*opnd1 != '\0');
-       case TO_STZER: /* -z */
+
+       /* -z */
+       case TO_STZER:
                return (*opnd1 == '\0');
-       case TO_OPTION: /* -o */
+
+       /* -o */
+       case TO_OPTION:
                if ((i = *opnd1) == '!' || i == '?')
                        opnd1++;
                if ((k = option(opnd1)) == (size_t)-1)
                        return (0);
                return (i == '?' ? 1 : i == '!' ? !Flag(k) : Flag(k));
-       case TO_FILRD: /* -r */
-               return (test_eaccess(opnd1, R_OK) == 0);
-       case TO_FILWR: /* -w */
-               return (test_eaccess(opnd1, W_OK) == 0);
-       case TO_FILEX: /* -x */
-               return (test_eaccess(opnd1, X_OK) == 0);
-       case TO_FILAXST: /* -a */
-       case TO_FILEXST: /* -e */
+
+       /* -r */
+       case TO_FILRD:
+               /* LINTED use of access */
+               return (access(opnd1, R_OK) == 0);
+
+       /* -w */
+       case TO_FILWR:
+               /* LINTED use of access */
+               return (access(opnd1, W_OK) == 0);
+
+       /* -x */
+       case TO_FILEX:
+               return (ksh_access(opnd1, X_OK) == 0);
+
+       /* -a */
+       case TO_FILAXST:
+       /* -e */
+       case TO_FILEXST:
                return (stat(opnd1, &b1) == 0);
-       case TO_FILREG: /* -r */
+
+       /* -r */
+       case TO_FILREG:
                return (stat(opnd1, &b1) == 0 && S_ISREG(b1.st_mode));
-       case TO_FILID: /* -d */
+
+       /* -d */
+       case TO_FILID:
                return (stat(opnd1, &b1) == 0 && S_ISDIR(b1.st_mode));
-       case TO_FILCDEV: /* -c */
+
+       /* -c */
+       case TO_FILCDEV:
                return (stat(opnd1, &b1) == 0 && S_ISCHR(b1.st_mode));
-       case TO_FILBDEV: /* -b */
+
+       /* -b */
+       case TO_FILBDEV:
                return (stat(opnd1, &b1) == 0 && S_ISBLK(b1.st_mode));
-       case TO_FILFIFO: /* -p */
+
+       /* -p */
+       case TO_FILFIFO:
                return (stat(opnd1, &b1) == 0 && S_ISFIFO(b1.st_mode));
-       case TO_FILSYM: /* -h -L */
+
+       /* -h or -L */
+       case TO_FILSYM:
                return (lstat(opnd1, &b1) == 0 && S_ISLNK(b1.st_mode));
-       case TO_FILSOCK: /* -S */
+
+       /* -S */
+       case TO_FILSOCK:
                return (stat(opnd1, &b1) == 0 && S_ISSOCK(b1.st_mode));
-       case TO_FILCDF:/* -H HP context dependent files (directories) */
+
+       /* -H => HP context dependent files (directories) */
+       case TO_FILCDF:
+#ifdef S_ISCDF
+       {
+               char *nv;
+
+               /*
+                * Append a + to filename and check to see if result is
+                * a setuid directory. CDF stuff in general is hookey,
+                * since it breaks for, e.g., the following sequence:
+                * echo hi >foo+; mkdir foo; echo bye >foo/default;
+                * chmod u+s foo (foo+ refers to the file with hi in it,
+                * there is no way to get at the file with bye in it;
+                * please correct me if I'm wrong about this).
+                */
+
+               nv = shf_smprintf("%s+", opnd1);
+               i = (stat(nv, &b1) == 0 && S_ISCDF(b1.st_mode));
+               afree(nv, ATEMP);
+               return (i);
+       }
+#else
                return (0);
-       case TO_FILSETU: /* -u */
+#endif
+
+       /* -u */
+       case TO_FILSETU:
                return (stat(opnd1, &b1) == 0 &&
                    (b1.st_mode & S_ISUID) == S_ISUID);
-       case TO_FILSETG: /* -g */
+
+       /* -g */
+       case TO_FILSETG:
                return (stat(opnd1, &b1) == 0 &&
                    (b1.st_mode & S_ISGID) == S_ISGID);
-       case TO_FILSTCK: /* -k */
+
+       /* -k */
+       case TO_FILSTCK:
 #ifdef S_ISVTX
                return (stat(opnd1, &b1) == 0 &&
                    (b1.st_mode & S_ISVTX) == S_ISVTX);
 #else
                return (0);
 #endif
-       case TO_FILGZ: /* -s */
+
+       /* -s */
+       case TO_FILGZ:
                return (stat(opnd1, &b1) == 0 && b1.st_size > 0L);
-       case TO_FILTT: /* -t */
+
+       /* -t */
+       case TO_FILTT:
                if (opnd1 && !bi_getn(opnd1, &i)) {
                        te->flags |= TEF_ERROR;
                        i = 0;
                } else
                        i = isatty(opnd1 ? i : 0);
                return (i);
-       case TO_FILUID: /* -O */
+
+       /* -O */
+       case TO_FILUID:
                return (stat(opnd1, &b1) == 0 && b1.st_uid == ksheuid);
-       case TO_FILGID: /* -G */
+
+       /* -G */
+       case TO_FILGID:
                return (stat(opnd1, &b1) == 0 && b1.st_gid == getegid());
+
        /*
         * Binary Operators
         */
-       case TO_STEQL: /* = */
+
+       /* = */
+       case TO_STEQL:
                if (te->flags & TEF_DBRACKET)
                        return (gmatchx(opnd1, opnd2, false));
                return (strcmp(opnd1, opnd2) == 0);
-       case TO_STNEQ: /* != */
+
+       /* != */
+       case TO_STNEQ:
                if (te->flags & TEF_DBRACKET)
                        return (!gmatchx(opnd1, opnd2, false));
                return (strcmp(opnd1, opnd2) != 0);
-       case TO_STLT: /* < */
+
+       /* < */
+       case TO_STLT:
                return (strcmp(opnd1, opnd2) < 0);
-       case TO_STGT: /* > */
+
+       /* > */
+       case TO_STGT:
                return (strcmp(opnd1, opnd2) > 0);
-       case TO_INTEQ: /* -eq */
-       case TO_INTNE: /* -ne */
-       case TO_INTGE: /* -ge */
-       case TO_INTGT: /* -gt */
-       case TO_INTLE: /* -le */
-       case TO_INTLT: /* -lt */
+
+       /* -eq */
+       case TO_INTEQ:
+       /* -ne */
+       case TO_INTNE:
+       /* -ge */
+       case TO_INTGE:
+       /* -gt */
+       case TO_INTGT:
+       /* -le */
+       case TO_INTLE:
+       /* -lt */
+       case TO_INTLT:
                if (!evaluate(opnd1, &v1, KSH_RETURN_ERROR, false) ||
                    !evaluate(opnd2, &v2, KSH_RETURN_ERROR, false)) {
                        /* error already printed.. */
                        te->flags |= TEF_ERROR;
                        return (1);
                }
-               switch ((int)op) {
+               switch (op) {
                case TO_INTEQ:
                        return (v1 == v2);
                case TO_INTNE:
@@ -2918,49 +3029,47 @@ test_eval(Test_env *te, Test_op op, const char *opnd1, const char *opnd2,
                        return (v1 <= v2);
                case TO_INTLT:
                        return (v1 < v2);
+               default:
+                       /* NOTREACHED */
+                       break;
                }
-       case TO_FILNT: /* -nt */
-               /* ksh88/ksh93 succeed if file2 can't be stated
+               /* NOTREACHED */
+
+       /* -nt */
+       case TO_FILNT:
+               /*
+                * ksh88/ksh93 succeed if file2 can't be stated
                 * (subtly different from 'does not exist').
                 */
                return (stat(opnd1, &b1) == 0 &&
                    (((s = stat(opnd2, &b2)) == 0 &&
                    b1.st_mtime > b2.st_mtime) || s < 0));
-       case TO_FILOT: /* -ot */
-               /* ksh88/ksh93 succeed if file1 can't be stated
+
+       /* -ot */
+       case TO_FILOT:
+               /*
+                * ksh88/ksh93 succeed if file1 can't be stated
                 * (subtly different from 'does not exist').
                 */
                return (stat(opnd2, &b2) == 0 &&
                    (((s = stat(opnd1, &b1)) == 0 &&
                    b1.st_mtime < b2.st_mtime) || s < 0));
-       case TO_FILEQ: /* -ef */
+
+       /* -ef */
+       case TO_FILEQ:
                return (stat (opnd1, &b1) == 0 && stat (opnd2, &b2) == 0 &&
                    b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino);
+
+       /* all other cases */
+       case TO_NONOP:
+       case TO_NONNULL:
+               /* throw the error */
+               break;
        }
        (*te->error)(te, 0, "internal error: unknown op");
        return (1);
 }
 
-/* On most/all unixen, access() says everything is executable for root... */
-static int
-test_eaccess(const char *pathl, int mode)
-{
-       int rv;
-
-       if ((rv = access(pathl, mode)) == 0 && ksheuid == 0 && (mode & X_OK)) {
-               struct stat statb;
-
-               if (stat(pathl, &statb) < 0)
-                       rv = -1;
-               else if (S_ISDIR(statb.st_mode))
-                       rv = 0;
-               else
-                       rv = (statb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) ?
-                           0 : -1;
-       }
-       return (rv);
-}
-
 int
 test_parse(Test_env *te)
 {
@@ -3020,7 +3129,7 @@ test_primary(Test_env *te, bool do_eval)
                if (te->flags & TEF_ERROR)
                        return (0);
                if (!(*te->isa)(te, TM_CPAREN)) {
-                       (*te->error)(te, 0, "missing closing paren");
+                       (*te->error)(te, 0, "missing )");
                        return (0);
                }
                return (rv);
@@ -3280,7 +3389,8 @@ c_ulimit(const char **wp)
                        all = true;
                        break;
                case '?':
-                       bi_errorf("usage: ulimit [-acdfHLlmnpSsTtvw] [value]");
+                       bi_errorf("%s: %s", "usage",
+                           "ulimit [-acdfHLlmnpSsTtvw] [value]");
                        return (1);
                default:
                        what = optc;
@@ -3336,7 +3446,7 @@ set_ulimit(const struct limits *l, const char *v, int how)
        }
 
        if (getrlimit(l->resource, &limit) < 0) {
-               /* some cannot be read, e.g. Linux RLIMIT_LOCKS */
+               /* some can't be read, e.g. Linux RLIMIT_LOCKS */
                limit.rlim_cur = RLIM_INFINITY;
                limit.rlim_max = RLIM_INFINITY;
        }
@@ -3379,15 +3489,20 @@ c_rename(const char **wp)
 {
        int rv = 1;
 
-       if (wp == NULL          /* argv */ ||
-           wp[0] == NULL       /* name of builtin */ ||
-           wp[1] == NULL       /* first argument */ ||
-           wp[2] == NULL       /* second argument */ ||
-           wp[3] != NULL       /* no further args please */)
-               bi_errorf(T_synerr);
-       else if ((rv = rename(wp[1], wp[2])) != 0) {
+       /* skip argv[0] */
+       ++wp;
+       if (wp[0] && !strcmp(wp[0], "--"))
+               /* skip "--" (options separator) */
+               ++wp;
+
+       /* check for exactly two arguments */
+       if (wp[0] == NULL       /* first argument */ ||
+           wp[1] == NULL       /* second argument */ ||
+           wp[2] != NULL       /* no further args please */)
+               bi_errorf(Tsynerr);
+       else if ((rv = rename(wp[0], wp[1])) != 0) {
                rv = errno;
-               bi_errorf("failed: %s", strerror(rv));
+               bi_errorf("%s: %s", "failed", strerror(rv));
        }
 
        return (rv);
@@ -3399,31 +3514,161 @@ c_realpath(const char **wp)
        int rv = 1;
        char *buf;
 
-       if (wp != NULL && wp[0] != NULL && wp[1] != NULL) {
-               if (strcmp(wp[1], "--")) {
-                       if (wp[2] == NULL) {
-                               wp += 1;
-                               rv = 0;
-                       }
-               } else {
-                       if (wp[2] != NULL && wp[3] == NULL) {
-                               wp += 2;
-                               rv = 0;
-                       }
-               }
-       }
+       /* skip argv[0] */
+       ++wp;
+       if (wp[0] && !strcmp(wp[0], "--"))
+               /* skip "--" (options separator) */
+               ++wp;
 
-       if (rv)
-               bi_errorf(T_synerr);
-       else if ((buf = do_realpath(*wp)) == NULL) {
+       /* check for exactly one argument */
+       if (wp[0] == NULL || wp[1] != NULL)
+               bi_errorf(Tsynerr);
+       else if ((buf = do_realpath(wp[0])) == NULL) {
                rv = errno;
-               bi_errorf("%s: %s", *wp, strerror(rv));
+               bi_errorf("%s: %s", wp[0], strerror(rv));
                if ((unsigned int)rv > 255)
                        rv = 255;
        } else {
                shprintf("%s\n", buf);
                afree(buf, ATEMP);
+               rv = 0;
        }
 
        return (rv);
 }
+
+int
+c_cat(const char **wp)
+{
+       int fd = STDIN_FILENO, rv;
+       ssize_t n, w;
+       const char *fn = "<stdin>";
+       char *buf, *cp;
+#define MKSH_CAT_BUFSIZ 4096
+
+       if ((buf = malloc_osfunc(MKSH_CAT_BUFSIZ)) == NULL) {
+               bi_errorf(Toomem, (unsigned long)MKSH_CAT_BUFSIZ);
+               return (1);
+       }
+
+       /* parse options: POSIX demands we support "-u" as no-op */
+       while ((rv = ksh_getopt(wp, &builtin_opt, "u")) != -1) {
+               switch (rv) {
+               case 'u':
+                       /* we already operate unbuffered */
+                       break;
+               default:
+                       bi_errorf(Tsynerr);
+                       return (1);
+               }
+       }
+       wp += builtin_opt.optind;
+       rv = 0;
+
+       do {
+               if (*wp) {
+                       fn = *wp++;
+                       if (fn[0] == '-' && fn[1] == '\0')
+                               fd = STDIN_FILENO;
+                       else if ((fd = open(fn, O_RDONLY)) < 0) {
+                               rv = errno;
+                               bi_errorf("%s: %s", fn, strerror(rv));
+                               rv = 1;
+                               continue;
+                       }
+               }
+               while (/* CONSTCOND */ 1) {
+                       n = blocking_read(fd, (cp = buf), MKSH_CAT_BUFSIZ);
+                       if (n == -1) {
+                               if (errno == EINTR) {
+                                       /* give the user a chance to ^C out */
+                                       intrcheck();
+                                       /* interrupted, try again */
+                                       continue;
+                               }
+                               /* an error occured during reading */
+                               rv = errno;
+                               bi_errorf("%s: %s", fn, strerror(rv));
+                               rv = 1;
+                               break;
+                       } else if (n == 0)
+                               /* end of file reached */
+                               break;
+                       while (n) {
+                               w = write(STDOUT_FILENO, cp, n);
+                               if (w == -1) {
+                                       if (errno == EINTR)
+                                               /* interrupted, try again */
+                                               continue;
+                                       /* an error occured during writing */
+                                       rv = errno;
+                                       bi_errorf("%s: %s", "<stdout>",
+                                           strerror(rv));
+                                       rv = 1;
+                                       if (fd != STDIN_FILENO)
+                                               close(fd);
+                                       goto out;
+                               }
+                               n -= w;
+                               cp += w;
+                       }
+               }
+               if (fd != STDIN_FILENO)
+                       close(fd);
+       } while (*wp);
+
+ out:
+       free_osfunc(buf);
+       return (rv);
+}
+
+#if HAVE_SELECT
+int
+c_sleep(const char **wp)
+{
+       struct timeval tv;
+       int rv = 1;
+
+       /* skip argv[0] */
+       ++wp;
+       if (wp[0] && !strcmp(wp[0], "--"))
+               /* skip "--" (options separator) */
+               ++wp;
+
+       if (!wp[0] || wp[1])
+               bi_errorf(Tsynerr);
+       else if (parse_usec(wp[0], &tv))
+               bi_errorf("%s: %s '%s'", Tsynerr, strerror(errno), wp[0]);
+       else {
+#ifndef MKSH_NOPROSPECTOFWORK
+               sigset_t omask;
+
+               /* block SIGCHLD from interrupting us, though */
+               sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+#endif
+               if (select(0, NULL, NULL, NULL, &tv) == 0 || errno == EINTR)
+                       /*
+                        * strictly speaking only for SIGALRM, but the
+                        * execution may be interrupted by other signals
+                        */
+                       rv = 0;
+               else
+                       bi_errorf("%s: %s", Tselect, strerror(errno));
+#ifndef MKSH_NOPROSPECTOFWORK
+               sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
+       }
+       return (rv);
+}
+#endif
+
+#if defined(ANDROID)
+static int
+c_android_lsmod(const char **wp MKSH_A_UNUSED)
+{
+       const char *cwp[3] = { "cat", "/proc/modules", NULL };
+
+       builtin_argv0 = cwp[0];
+       return (c_cat(cwp));
+}
+#endif
index 2ac4c38..4a4a275 100644 (file)
@@ -2,7 +2,7 @@
 /*     $OpenBSD: trap.c,v 1.23 2010/05/19 17:36:08 jasper Exp $        */
 
 /*-
- * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
  *     Thorsten Glaser <tg@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
  */
 
 #include "sh.h"
-#if HAVE_PERSISTENT_HISTORY
+#if HAVE_SYS_FILE_H
 #include <sys/file.h>
 #endif
 
-__RCSID("$MirOS: src/bin/mksh/histrap.c,v 1.98 2010/07/24 17:08:29 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/histrap.c,v 1.111 2011/09/07 15:24:16 tg Exp $");
 
 /*-
  * MirOS: This is the default mapping type, and need not be specified.
@@ -60,11 +60,15 @@ static int hstarted;                /* set after hist_init() called */
 static Source *hist_source;
 
 #if HAVE_PERSISTENT_HISTORY
-static char *hname;            /* current name of history file */
+/* current history file: name, fd, size */
+static char *hname;
 static int histfd;
-static int hsize;
+static size_t hsize;
 #endif
 
+static const char Tnot_in_history[] = "not in history";
+#define Thistory (Tnot_in_history + 7)
+
 int
 c_fc(const char **wp)
 {
@@ -79,39 +83,50 @@ c_fc(const char **wp)
        char **hfirst, **hlast, **hp;
 
        if (!Flag(FTALKING_I)) {
-               bi_errorf("history functions not available");
+               bi_errorf("history %ss not available", Tfunction);
                return (1);
        }
 
        while ((optc = ksh_getopt(wp, &builtin_opt,
            "e:glnrs0,1,2,3,4,5,6,7,8,9,")) != -1)
                switch (optc) {
+
                case 'e':
                        p = builtin_opt.optarg;
                        if (ksh_isdash(p))
                                sflag = true;
                        else {
                                size_t len = strlen(p);
+
+                               /* almost certainly not overflowing */
                                editor = alloc(len + 4, ATEMP);
                                memcpy(editor, p, len);
                                memcpy(editor + len, " $_", 4);
                        }
                        break;
-               case 'g': /* non-AT&T ksh */
+
+               /* non-AT&T ksh */
+               case 'g':
                        gflag = true;
                        break;
+
                case 'l':
                        lflag = true;
                        break;
+
                case 'n':
                        nflag = true;
                        break;
+
                case 'r':
                        rflag = true;
                        break;
-               case 's':       /* POSIX version of -e - */
+
+               /* POSIX version of -e - */
+               case 's':
                        sflag = true;
                        break;
+
                /* kludge city - accept -num as -- -num (kind of) */
                case '0': case '1': case '2': case '3': case '4':
                case '5': case '6': case '7': case '8': case '9':
@@ -126,6 +141,7 @@ c_fc(const char **wp)
                                return (1);
                        }
                        break;
+
                case '?':
                        return (1);
                }
@@ -183,11 +199,12 @@ c_fc(const char **wp)
                /* can't fail if hfirst didn't fail */
                hlast = hist_get_newest(false);
        } else {
-               /* POSIX says not an error if first/last out of bounds
-                * when range is specified; AT&T ksh and pdksh allow out of
-                * bounds for -l as well.
+               /*
+                * POSIX says not an error if first/last out of bounds
+                * when range is specified; AT&T ksh and pdksh allow out
+                * of bounds for -l as well.
                 */
-               hfirst = hist_get(first, (lflag || last) ? true : false, lflag);
+               hfirst = hist_get(first, tobool(lflag || last), lflag);
                if (!hfirst)
                        return (1);
                hlast = last ? hist_get(last, true, lflag) :
@@ -199,7 +216,8 @@ c_fc(const char **wp)
                char **temp;
 
                temp = hfirst; hfirst = hlast; hlast = temp;
-               rflag = !rflag; /* POSIX */
+               /* POSIX */
+               rflag = !rflag;
        }
 
        /* List history */
@@ -230,15 +248,16 @@ c_fc(const char **wp)
 
        tf = maketemp(ATEMP, TT_HIST_EDIT, &e->temps);
        if (!(shf = tf->shf)) {
-               bi_errorf("cannot create temp file %s - %s",
-                   tf->name, strerror(errno));
+               bi_errorf("can't %s temporary file %s: %s",
+                   "create", tf->name, strerror(errno));
                return (1);
        }
        for (hp = rflag ? hlast : hfirst;
            hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1)
                shf_fprintf(shf, "%s\n", *hp);
        if (shf_close(shf) == EOF) {
-               bi_errorf("error writing temporary file - %s", strerror(errno));
+               bi_errorf("can't %s temporary file %s: %s",
+                   "write", tf->name, strerror(errno));
                return (1);
        }
 
@@ -263,11 +282,19 @@ c_fc(const char **wp)
                int n;
 
                if (!(shf = shf_open(tf->name, O_RDONLY, 0, 0))) {
-                       bi_errorf("cannot open temp file %s", tf->name);
+                       bi_errorf("can't %s temporary file %s: %s",
+                           "open", tf->name, strerror(errno));
                        return (1);
                }
 
-               n = stat(tf->name, &statb) < 0 ? 128 : statb.st_size + 1;
+               if (stat(tf->name, &statb) < 0)
+                       n = 128;
+               else if (statb.st_size > (1024 * 1048576)) {
+                       bi_errorf("%s %s too large: %lu", Thistory,
+                           "file", (unsigned long)statb.st_size);
+                       goto errout;
+               } else
+                       n = statb.st_size + 1;
                Xinit(xs, xp, n, hist_source->areap);
                while ((n = shf_read(xp, Xnleft(xs, xp), shf)) > 0) {
                        xp += n;
@@ -275,8 +302,9 @@ c_fc(const char **wp)
                                XcheckN(xs, xp, Xlength(xs, xp));
                }
                if (n < 0) {
-                       bi_errorf("error reading temp file %s - %s",
-                           tf->name, strerror(shf_errno(shf)));
+                       bi_errorf("can't %s temporary file %s: %s",
+                           "read", tf->name, strerror(shf_errno(shf)));
+ errout:
                        shf_close(shf);
                        return (1);
                }
@@ -299,18 +327,22 @@ hist_execute(char *cmd)
 
        for (p = cmd; p; p = q) {
                if ((q = strchr(p, '\n'))) {
-                       *q++ = '\0'; /* kill the newline */
-                       if (!*q) /* ignore trailing newline */
+                       /* kill the newline */
+                       *q++ = '\0';
+                       if (!*q)
+                               /* ignore trailing newline */
                                q = NULL;
                }
                histsave(&hist_source->line, p, true, true);
 
-               shellf("%s\n", p); /* POSIX doesn't say this is done... */
-               if (q)          /* restore \n (trailing \n not restored) */
+               /* POSIX doesn't say this is done... */
+               shellf("%s\n", p);
+               if (q)
+                       /* restore \n (trailing \n not restored) */
                        q[-1] = '\n';
        }
 
-       /*
+       /*-
         * Commands are executed here instead of pushing them onto the
         * input 'cause POSIX says the redirection and variable assignments
         * in
@@ -333,9 +365,9 @@ hist_replace(char **hp, const char *pat, const char *rep, bool globr)
                strdupx(line, *hp, ATEMP);
        else {
                char *s, *s1;
-               int pat_len = strlen(pat);
-               int rep_len = strlen(rep);
-               int len;
+               size_t pat_len = strlen(pat);
+               size_t rep_len = strlen(rep);
+               size_t len;
                XString xs;
                char *xp;
                bool any_subst = false;
@@ -346,13 +378,15 @@ hist_replace(char **hp, const char *pat, const char *rep, bool globr)
                        any_subst = true;
                        len = s1 - s;
                        XcheckN(xs, xp, len + rep_len);
-                       memcpy(xp, s, len);             /* first part */
+                       /*; first part */
+                       memcpy(xp, s, len);
                        xp += len;
-                       memcpy(xp, rep, rep_len);       /* replacement */
+                       /* replacement */
+                       memcpy(xp, rep, rep_len);
                        xp += rep_len;
                }
                if (!any_subst) {
-                       bi_errorf("substitution failed");
+                       bi_errorf("bad substitution");
                        return (1);
                }
                len = strlen(s) + 1;
@@ -380,18 +414,18 @@ hist_get(const char *str, bool approx, bool allow_cur)
                        if (approx)
                                hp = hist_get_oldest();
                        else {
-                               bi_errorf("%s: not in history", str);
+                               bi_errorf("%s: %s", str, Tnot_in_history);
                                hp = NULL;
                        }
                } else if ((ptrdiff_t)hp > (ptrdiff_t)histptr) {
                        if (approx)
                                hp = hist_get_newest(allow_cur);
                        else {
-                               bi_errorf("%s: not in history", str);
+                               bi_errorf("%s: %s", str, Tnot_in_history);
                                hp = NULL;
                        }
                } else if (!allow_cur && hp == histptr) {
-                       bi_errorf("%s: invalid range", str);
+                       bi_errorf("%s: %s", str, "invalid range");
                        hp = NULL;
                }
        } else {
@@ -399,7 +433,7 @@ hist_get(const char *str, bool approx, bool allow_cur)
 
                /* the -1 is to avoid the current fc command */
                if ((n = findhist(histptr - history - 1, 0, str, anchored)) < 0)
-                       bi_errorf("%s: not in history", str);
+                       bi_errorf("%s: %s", str, Tnot_in_history);
                else
                        hp = &history[n];
        }
@@ -428,9 +462,9 @@ hist_get_oldest(void)
        return (history);
 }
 
-/******************************/
-/* Back up over last histsave */
-/******************************/
+/*
+ * Back up over last histsave
+ */
 static void
 histbackup(void)
 {
@@ -475,10 +509,10 @@ histnum(int n)
 int
 findhist(int start, int fwd, const char *str, int anchored)
 {
-       char    **hp;
-       int     maxhist = histptr - history;
-       int     incr = fwd ? 1 : -1;
-       int     len = strlen(str);
+       char **hp;
+       int maxhist = histptr - history;
+       int incr = fwd ? 1 : -1;
+       size_t len = strlen(str);
 
        if (start < 0 || start >= maxhist)
                start = maxhist;
@@ -492,26 +526,6 @@ findhist(int start, int fwd, const char *str, int anchored)
        return (-1);
 }
 
-int
-findhistrel(const char *str)
-{
-       int     maxhist = histptr - history;
-       int     start = maxhist - 1;
-       int     rec;
-
-       getn(str, &rec);
-       if (rec == 0)
-               return (-1);
-       if (rec > 0) {
-               if (rec > maxhist)
-                       return (-1);
-               return (rec - 1);
-       }
-       if (rec > maxhist)
-               return (-1);
-       return (start + rec + 1);
-}
-
 /*
  *     set history
  *     this means reallocating the dataspace
@@ -528,7 +542,7 @@ sethistsize(int n)
                        cursize = n;
                }
 
-               history = aresize(history, n * sizeof(char *), APERM);
+               history = aresize2(history, n, sizeof(char *), APERM);
 
                histsize = n;
                histptr = history + cursize;
@@ -579,7 +593,7 @@ init_histvec(void)
 {
        if (history == (char **)NULL) {
                histsize = HISTORYSIZE;
-               history = alloc(histsize * sizeof(char *), APERM);
+               history = alloc2(histsize, sizeof(char *), APERM);
                histptr = history - 1;
        }
 }
@@ -645,7 +659,8 @@ histsave(int *lnp, const char *cmd, bool dowrite MKSH_A_UNUSED, bool ignoredups)
 
        hp = histptr;
 
-       if (++hp >= history + histsize) { /* remove oldest command */
+       if (++hp >= history + histsize) {
+               /* remove oldest command */
                afree(*history, APERM);
                for (hp = history; hp < history + histsize - 1; hp++)
                        hp[0] = hp[1];
@@ -665,7 +680,7 @@ histsave(int *lnp, const char *cmd, bool dowrite MKSH_A_UNUSED, bool ignoredups)
  *     if your system ain't got it - then you'll have to undef HISTORYFILE
  */
 
-/*
+/*-
  *     Open a history file
  *     Format is:
  *     Bytes 1, 2:
@@ -685,6 +700,7 @@ hist_init(Source *s)
 #if HAVE_PERSISTENT_HISTORY
        unsigned char *base;
        int lines, fd, rv = 0;
+       off_t hfsize;
 #endif
 
        if (Flag(FTALKING) == 0)
@@ -710,7 +726,10 @@ hist_init(Source *s)
 
        (void)flock(histfd, LOCK_EX);
 
-       hsize = lseek(histfd, (off_t)0, SEEK_END);
+       hfsize = lseek(histfd, (off_t)0, SEEK_END);
+       hsize = 1024 * 1048576;
+       if (hfsize < (off_t)hsize)
+               hsize = (size_t)hfsize;
 
        if (hsize == 0) {
                /* add magic */
@@ -746,8 +765,9 @@ hist_init(Source *s)
                                hist_finish();
                                if (rv) {
  hiniterr:
-                                       bi_errorf("cannot unlink HISTFILE %s"
-                                           " - %s", hname, strerror(errno));
+                                       bi_errorf("can't %s %s: %s",
+                                           "unlink HISTFILE", hname,
+                                           strerror(errno));
                                        hsize = 0;
                                        return;
                                }
@@ -758,7 +778,10 @@ hist_init(Source *s)
                munmap((caddr_t)base, hsize);
        }
        (void)flock(histfd, LOCK_UN);
-       hsize = lseek(histfd, (off_t)0, SEEK_END);
+       hfsize = lseek(histfd, (off_t)0, SEEK_END);
+       hsize = 1024 * 1048576;
+       if (hfsize < (off_t)hsize)
+               hsize = hfsize;
 #endif
 }
 
@@ -954,36 +977,34 @@ histinsert(Source *s, int lno, const char *line)
 static void
 writehistfile(int lno, char *cmd)
 {
-       int     sizenow;
-       unsigned char   *base;
-       unsigned char   *news;
-       int     bytes;
-       unsigned char   hdr[5];
+       off_t sizenow;
+       ssize_t bytes;
+       unsigned char *base, *news, hdr[5];
 
        (void)flock(histfd, LOCK_EX);
        sizenow = lseek(histfd, (off_t)0, SEEK_END);
-       if (sizenow != hsize) {
+       if ((sizenow <= (1024 * 1048576)) && ((size_t)sizenow != hsize)) {
                /*
                 *      Things have changed
                 */
-               if (sizenow > hsize) {
+               if ((size_t)sizenow > hsize) {
                        /* someone has added some lines */
-                       bytes = sizenow - hsize;
-                       base = (void *)mmap(NULL, sizenow, PROT_READ,
+                       bytes = (size_t)sizenow - hsize;
+                       base = (void *)mmap(NULL, (size_t)sizenow, PROT_READ,
                            MAP_FILE | MAP_PRIVATE, histfd, (off_t)0);
                        if (base == (unsigned char *)MAP_FAILED)
                                goto bad;
                        news = base + hsize;
                        if (*news != COMMAND) {
-                               munmap((caddr_t)base, sizenow);
+                               munmap((caddr_t)base, (size_t)sizenow);
                                goto bad;
                        }
                        hist_source->line--;
                        histload(hist_source, news, bytes);
                        hist_source->line++;
                        lno = hist_source->line;
-                       munmap((caddr_t)base, sizenow);
-                       hsize = sizenow;
+                       munmap((caddr_t)base, (size_t)sizenow);
+                       hsize = (size_t)sizenow;
                } else {
                        /* it has shrunk */
                        /* but to what? */
@@ -1004,7 +1025,10 @@ writehistfile(int lno, char *cmd)
                if ((write(histfd, hdr, 5) != 5) ||
                    (write(histfd, cmd, bytes) != bytes))
                        goto bad;
-               hsize = lseek(histfd, (off_t)0, SEEK_END);
+               sizenow = lseek(histfd, (off_t)0, SEEK_END);
+               hsize = 1024 * 1048576;
+               if (sizenow < (off_t)hsize)
+                       hsize = (size_t)sizenow;
        }
        (void)flock(histfd, LOCK_UN);
        return;
@@ -1048,10 +1072,12 @@ inittraps(void)
        int i;
        const char *cs;
 
+       trap_exstat = -1;
+
        /* Populate sigtraps based on sys_signame and sys_siglist. */
        for (i = 0; i <= NSIG; i++) {
                sigtraps[i].signal = i;
-               if (i == SIGERR_) {
+               if (i == ksh_SIGERR) {
                        sigtraps[i].name = "ERR";
                        sigtraps[i].mess = "Error handler";
                } else {
@@ -1085,10 +1111,12 @@ inittraps(void)
 #endif
                        if ((sigtraps[i].mess == NULL) ||
                            (sigtraps[i].mess[0] == '\0'))
-                               sigtraps[i].mess = shf_smprintf("Signal %d", i);
+                               sigtraps[i].mess = shf_smprintf("%s %d",
+                                   "Signal", i);
                }
        }
-       sigtraps[SIGEXIT_].name = "EXIT";       /* our name for signal 0 */
+       /* our name for signal 0 */
+       sigtraps[ksh_SIGEXIT].name = "EXIT";
 
        (void)sigemptyset(&Sigact_ign.sa_mask);
        Sigact_ign.sa_flags = 0; /* interruptible */
@@ -1096,7 +1124,8 @@ inittraps(void)
 
        sigtraps[SIGINT].flags |= TF_DFL_INTR | TF_TTY_INTR;
        sigtraps[SIGQUIT].flags |= TF_DFL_INTR | TF_TTY_INTR;
-       sigtraps[SIGTERM].flags |= TF_DFL_INTR;/* not fatal for interactive */
+       /* SIGTERM is not fatal for interactive */
+       sigtraps[SIGTERM].flags |= TF_DFL_INTR;
        sigtraps[SIGHUP].flags |= TF_FATAL;
        sigtraps[SIGCHLD].flags |= TF_SHELL_USES;
 
@@ -1165,7 +1194,7 @@ void
 trapsig(int i)
 {
        Trap *p = &sigtraps[i];
-       int errno_ = errno;
+       int errno_sv = errno;
 
        trap = p->set = 1;
        if (p->flags & TF_DFL_INTR)
@@ -1176,7 +1205,7 @@ trapsig(int i)
        }
        if (p->shtrap)
                (*p->shtrap)(i);
-       errno = errno_;
+       errno = errno_sv;
 }
 
 /*
@@ -1252,22 +1281,29 @@ runtraps(int flag)
                intrsig = 0;
        if (flag & TF_FATAL)
                fatal_trap = 0;
+       ++trap_nested;
        for (p = sigtraps, i = NSIG+1; --i >= 0; p++)
                if (p->set && (!flag ||
                    ((p->flags & flag) && p->trap == NULL)))
-                       runtrap(p);
+                       runtrap(p, false);
+       if (!--trap_nested)
+               runtrap(NULL, true);
 }
 
 void
-runtrap(Trap *p)
+runtrap(Trap *p, bool is_last)
 {
-       int     i = p->signal;
-       char    *trapstr = p->trap;
-       int     oexstat;
-       int     old_changed = 0;
-
+       int old_changed = 0, i;
+       char *trapstr;
+
+       if (p == NULL)
+               /* just clean up, see runtraps() above */
+               goto donetrap;
+       i = p->signal;
+       trapstr = p->trap;
        p->set = 0;
-       if (trapstr == NULL) { /* SIG_DFL */
+       if (trapstr == NULL) {
+               /* SIG_DFL */
                if (p->flags & TF_FATAL) {
                        /* eg, SIGHUP */
                        exstat = 128 + i;
@@ -1278,23 +1314,25 @@ runtrap(Trap *p)
                        exstat = 128 + i;
                        unwind(LINTR);
                }
-               return;
+               goto donetrap;
        }
-       if (trapstr[0] == '\0') /* SIG_IGN */
-               return;
-       if (i == SIGEXIT_ || i == SIGERR_) {    /* avoid recursion on these */
+       if (trapstr[0] == '\0')
+               /* SIG_IGN */
+               goto donetrap;
+       if (i == ksh_SIGEXIT || i == ksh_SIGERR) {
+               /* avoid recursion on these */
                old_changed = p->flags & TF_CHANGED;
                p->flags &= ~TF_CHANGED;
                p->trap = NULL;
        }
-       oexstat = exstat;
+       if (trap_exstat == -1)
+               trap_exstat = exstat;
        /*
         * Note: trapstr is fully parsed before anything is executed, thus
         * no problem with afree(p->trap) in settrap() while still in use.
         */
        command(trapstr, current_lineno);
-       exstat = oexstat;
-       if (i == SIGEXIT_ || i == SIGERR_) {
+       if (i == ksh_SIGEXIT || i == ksh_SIGERR) {
                if (p->flags & TF_CHANGED)
                        /* don't clear TF_CHANGED */
                        afree(trapstr, APERM);
@@ -1302,6 +1340,13 @@ runtrap(Trap *p)
                        p->trap = trapstr;
                p->flags |= old_changed;
        }
+
+ donetrap:
+       /* we're the last trap of a sequence executed */
+       if (is_last && trap_exstat != -1) {
+               exstat = trap_exstat;
+               trap_exstat = -1;
+       }
 }
 
 /* clear pending traps and reset user's trap handlers; used after fork(2) */
@@ -1341,7 +1386,8 @@ settrap(Trap *p, const char *s)
 
        if (p->trap)
                afree(p->trap, APERM);
-       strdupx(p->trap, s, APERM); /* handles s == 0 */
+       /* handles s == NULL */
+       strdupx(p->trap, s, APERM);
        p->flags |= TF_CHANGED;
        f = !s ? SIG_DFL : s[0] ? trapsig : SIG_IGN;
 
@@ -1385,7 +1431,8 @@ block_pipe(void)
                        restore_dfl = 1;
        } else if (p->cursig == SIG_DFL) {
                setsig(p, SIG_IGN, SS_RESTORE_CURR);
-               restore_dfl = 1; /* restore to SIG_DFL */
+               /* restore to SIG_DFL */
+               restore_dfl = 1;
        }
        return (restore_dfl);
 }
@@ -1407,7 +1454,7 @@ setsig(Trap *p, sig_t f, int flags)
 {
        struct sigaction sigact;
 
-       if (p->signal == SIGEXIT_ || p->signal == SIGERR_)
+       if (p->signal == ksh_SIGEXIT || p->signal == ksh_SIGERR)
                return (1);
 
        /*
@@ -1448,7 +1495,8 @@ setsig(Trap *p, sig_t f, int flags)
        if (p->cursig != f) {
                p->cursig = f;
                (void)sigemptyset(&sigact.sa_mask);
-               sigact.sa_flags = 0 /* interruptible */;
+               /* interruptible */
+               sigact.sa_flags = 0;
                sigact.sa_handler = f;
                sigaction(p->signal, &sigact, NULL);
        }
@@ -1468,7 +1516,8 @@ setexecsig(Trap *p, int restore)
        /* restore original value for exec'd kids */
        p->flags &= ~(TF_EXEC_IGN|TF_EXEC_DFL);
        switch (restore & SS_RESTORE_MASK) {
-       case SS_RESTORE_CURR: /* leave things as they currently are */
+       case SS_RESTORE_CURR:
+               /* leave things as they currently are */
                break;
        case SS_RESTORE_ORIG:
                p->flags |= p->flags & TF_ORIG_IGN ? TF_EXEC_IGN : TF_EXEC_DFL;
index 47326a1..0b5df4e 100644 (file)
@@ -1,7 +1,7 @@
 /*     $OpenBSD: jobs.c,v 1.38 2009/12/12 04:28:44 deraadt Exp $       */
 
 /*-
- * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2011
  *     Thorsten Glaser <tg@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -22,7 +22,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/jobs.c,v 1.69 2010/07/04 17:33:54 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/jobs.c,v 1.81 2011/08/27 18:06:46 tg Exp $");
 
 #if HAVE_KILLPG
 #define mksh_killpg            killpg
@@ -63,7 +63,7 @@ struct proc {
 #define JF_W_ASYNCNOTIFY 0x004 /* set if waiting and async notification ok */
 #define JF_XXCOM       0x008   /* set for $(command) jobs */
 #define JF_FG          0x010   /* running in foreground (also has tty pgrp) */
-#define JF_SAVEDTTY    0x020   /* j->ttystate is valid */
+#define JF_SAVEDTTY    0x020   /* j->ttystat is valid */
 #define JF_CHANGED     0x040   /* process has changed state */
 #define JF_KNOWN       0x080   /* $! referenced */
 #define JF_ZOMBIE      0x100   /* known, unwaited process */
@@ -87,7 +87,7 @@ struct job {
        int32_t age;            /* number of jobs started */
        Coproc_id coproc_id;    /* 0 or id of coprocess output pipe */
 #ifndef MKSH_UNEMPLOYED
-       struct termios ttystate;/* saved tty state for stopped jobs */
+       struct termios ttystat/* saved tty state for stopped jobs */
        pid_t saved_ttypgrp;    /* saved tty process group for stopped jobs */
 #endif
 };
@@ -97,6 +97,7 @@ struct job {
 #define JW_INTERRUPT   0x01    /* ^C will stop the wait */
 #define JW_ASYNCNOTIFY 0x02    /* asynchronous notification during wait ok */
 #define JW_STOPPEDWAIT 0x04    /* wait even if job stopped */
+#define JW_PIPEST      0x08    /* want PIPESTATUS */
 
 /* Error codes for j_lookup() */
 #define JL_OK          0
@@ -124,8 +125,10 @@ static int32_t njobs;              /* # of jobs started */
 #define CHILD_MAX      25
 #endif
 
+#ifndef MKSH_NOPROSPECTOFWORK
 /* held_sigchld is set if sigchld occurs before a job is completely started */
 static volatile sig_atomic_t held_sigchld;
+#endif
 
 #ifndef MKSH_UNEMPLOYED
 static struct shf      *shl_j;
@@ -157,6 +160,7 @@ j_init(void)
        Flag(FMONITOR) = 0;
 #endif
 
+#ifndef MKSH_NOPROSPECTOFWORK
        (void)sigemptyset(&sm_default);
        sigprocmask(SIG_SETMASK, &sm_default, NULL);
 
@@ -165,6 +169,10 @@ j_init(void)
 
        setsig(&sigtraps[SIGCHLD], j_sigchld,
            SS_RESTORE_ORIG|SS_FORCE|SS_SHTRAP);
+#else
+       /* Make sure SIGCHLD isn't ignored - can do odd things under SYSV */
+       setsig(&sigtraps[SIGCHLD], SIG_DFL, SS_RESTORE_ORIG|SS_FORCE);
+#endif
 
 #ifndef MKSH_UNEMPLOYED
        if (!mflagset && Flag(FTALKING))
@@ -200,13 +208,26 @@ j_init(void)
                tty_init(true, true);
 }
 
+static int
+proc_errorlevel(Proc *p)
+{
+       switch (p->state) {
+       case PEXITED:
+               return (WEXITSTATUS(p->status));
+       case PSIGNALLED:
+               return (128 + WTERMSIG(p->status));
+       default:
+               return (0);
+       }
+}
+
 /* job cleanup before shell exit */
 void
 j_exit(void)
 {
        /* kill stopped, and possibly running, jobs */
-       Job     *j;
-       int     killed = 0;
+       Job *j;
+       bool killed = false;
 
        for (j = job_list; j != NULL; j = j->next) {
                if (j->ppid == procpid &&
@@ -214,7 +235,7 @@ j_exit(void)
                    (j->state == PRUNNING &&
                    ((j->flags & JF_FG) ||
                    (Flag(FLOGIN) && !Flag(FNOHUP) && procpid == kshpid))))) {
-                       killed = 1;
+                       killed = true;
                        if (j->pgrp == 0)
                                kill_job(j, SIGHUP);
                        else
@@ -272,12 +293,12 @@ j_change(void)
                        setsig(&sigtraps[SIGTTIN], SIG_DFL,
                            SS_RESTORE_ORIG|SS_FORCE);
                        /* wait to be given tty (POSIX.1, B.2, job control) */
-                       while (1) {
+                       while (/* CONSTCOND */ 1) {
                                pid_t ttypgrp;
 
                                if ((ttypgrp = tcgetpgrp(tty_fd)) < 0) {
-                                       warningf(false,
-                                           "j_init: tcgetpgrp() failed: %s",
+                                       warningf(false, "%s: %s %s: %s",
+                                           "j_init", "tcgetpgrp", "failed",
                                            strerror(errno));
                                        ttypgrp_ok = false;
                                        break;
@@ -292,14 +313,13 @@ j_change(void)
                            SS_RESTORE_DFL|SS_FORCE);
                if (ttypgrp_ok && kshpgrp != kshpid) {
                        if (setpgid(0, kshpid) < 0) {
-                               warningf(false,
-                                   "j_init: setpgid() failed: %s",
-                                   strerror(errno));
+                               warningf(false, "%s: %s %s: %s", "j_init",
+                                   "setpgid", "failed", strerror(errno));
                                ttypgrp_ok = false;
                        } else {
                                if (tcsetpgrp(tty_fd, kshpid) < 0) {
-                                       warningf(false,
-                                           "j_init: tcsetpgrp() failed: %s",
+                                       warningf(false, "%s: %s %s: %s",
+                                           "j_init", "tcsetpgrp", "failed",
                                            strerror(errno));
                                        ttypgrp_ok = false;
                                } else
@@ -308,7 +328,8 @@ j_change(void)
                        }
                }
                if (use_tty && !ttypgrp_ok)
-                       warningf(false, "warning: won't have full job control");
+                       warningf(false, "%s: %s", "warning",
+                           "won't have full job control");
                if (tty_fd >= 0)
                        tcgetattr(tty_fd, &tty_state);
        } else {
@@ -332,21 +353,46 @@ j_change(void)
 }
 #endif
 
+#if HAVE_NICE
+/* run nice(3) and ignore the result */
+static void
+ksh_nice(int ness)
+{
+#if defined(__USE_FORTIFY_LEVEL) && (__USE_FORTIFY_LEVEL > 0)
+       int e;
+
+       errno = 0;
+       /* this is gonna annoy users; complain to your distro, people! */
+       if (nice(ness) == -1 && (e = errno) != 0)
+               warningf(false, "%s: %s", "bgnice", strerror(e));
+#else
+       (void)nice(ness);
+#endif
+}
+#endif
+
 /* execute tree in child subprocess */
 int
 exchild(struct op *t, int flags,
     volatile int *xerrok,
-    /* used if XPCLOSE or XCCLOSE */ int close_fd)
+    /* used if XPCLOSE or XCCLOSE */
+    int close_fd)
 {
-       static Proc *last_proc;         /* for pipelines */
+       /* for pipelines */
+       static Proc *last_proc;
 
-       int rv = 0, forksleep;
+       int rv = 0, forksleep, jwflags = JW_NONE;
+#ifndef MKSH_NOPROSPECTOFWORK
        sigset_t omask;
-       struct {
-               Proc *p;
-               Job *j;
-               pid_t cldpid;
-       } pi;
+#endif
+       Proc *p;
+       Job *j;
+       pid_t cldpid;
+
+       if (flags & XPIPEST) {
+               flags &= ~XPIPEST;
+               jwflags |= JW_PIPEST;
+       }
 
        if (flags & XEXEC)
                /*
@@ -355,103 +401,111 @@ exchild(struct op *t, int flags,
                 */
                return (execute(t, flags & (XEXEC | XERROK), xerrok));
 
+#ifndef MKSH_NOPROSPECTOFWORK
        /* no SIGCHLDs while messing with job and process lists */
        sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+#endif
 
-       pi.p = new_proc();
-       pi.p->next = NULL;
-       pi.p->state = PRUNNING;
-       pi.p->status = 0;
-       pi.p->pid = 0;
+       p = new_proc();
+       p->next = NULL;
+       p->state = PRUNNING;
+       p->status = 0;
+       p->pid = 0;
 
        /* link process into jobs list */
        if (flags & XPIPEI) {
                /* continuing with a pipe */
                if (!last_job)
-                       internal_errorf(
-                           "exchild: XPIPEI and no last_job - pid %d",
+                       internal_errorf("%s %d",
+                           "exchild: XPIPEI and no last_job - pid",
                            (int)procpid);
-               pi.j = last_job;
+               j = last_job;
                if (last_proc)
-                       last_proc->next = pi.p;
-               last_proc = pi.p;
+                       last_proc->next = p;
+               last_proc = p;
        } else {
-               pi.j = new_job(); /* fills in pi.j->job */
+               /* fills in j->job */
+               j = new_job();
                /*
                 * we don't consider XXCOMs foreground since they don't get
                 * tty process group and we don't save or restore tty modes.
                 */
-               pi.j->flags = (flags & XXCOM) ? JF_XXCOM :
+               j->flags = (flags & XXCOM) ? JF_XXCOM :
                    ((flags & XBGND) ? 0 : (JF_FG|JF_USETTYMODE));
-               timerclear(&pi.j->usrtime);
-               timerclear(&pi.j->systime);
-               pi.j->state = PRUNNING;
-               pi.j->pgrp = 0;
-               pi.j->ppid = procpid;
-               pi.j->age = ++njobs;
-               pi.j->proc_list = pi.p;
-               pi.j->coproc_id = 0;
-               last_job = pi.j;
-               last_proc = pi.p;
-               put_job(pi.j, PJ_PAST_STOPPED);
+               timerclear(&j->usrtime);
+               timerclear(&j->systime);
+               j->state = PRUNNING;
+               j->pgrp = 0;
+               j->ppid = procpid;
+               j->age = ++njobs;
+               j->proc_list = p;
+               j->coproc_id = 0;
+               last_job = j;
+               last_proc = p;
+               put_job(j, PJ_PAST_STOPPED);
        }
 
-       snptreef(pi.p->command, sizeof(pi.p->command), "%T", t);
+       vistree(p->command, sizeof(p->command), t);
 
        /* create child process */
        forksleep = 1;
-       while ((pi.cldpid = fork()) < 0 && errno == EAGAIN && forksleep < 32) {
-               if (intrsig)     /* allow user to ^C out... */
+       while ((cldpid = fork()) < 0 && errno == EAGAIN && forksleep < 32) {
+               if (intrsig)
+                       /* allow user to ^C out... */
                        break;
                sleep(forksleep);
                forksleep <<= 1;
        }
-       if (pi.cldpid < 0) {
-               kill_job(pi.j, SIGKILL);
-               remove_job(pi.j, "fork failed");
+       /* ensure $RANDOM changes between parent and child */
+       rndset((long)cldpid);
+       /* fork failed? */
+       if (cldpid < 0) {
+               kill_job(j, SIGKILL);
+               remove_job(j, "fork failed");
+#ifndef MKSH_NOPROSPECTOFWORK
                sigprocmask(SIG_SETMASK, &omask, NULL);
-               errorf("cannot fork - try again");
+#endif
+               errorf("can't fork - try again");
        }
-       pi.p->pid = pi.cldpid ? pi.cldpid : (procpid = getpid());
-
-       /*
-        * ensure next child gets a (slightly) different $RANDOM sequence
-        * from its parent process and other child processes
-        */
-       change_random(&pi, sizeof(pi));
+       p->pid = cldpid ? cldpid : (procpid = getpid());
 
 #ifndef MKSH_UNEMPLOYED
        /* job control set up */
        if (Flag(FMONITOR) && !(flags&XXCOM)) {
-               int     dotty = 0;
-               if (pi.j->pgrp == 0) {  /* First process */
-                       pi.j->pgrp = pi.p->pid;
-                       dotty = 1;
+               bool dotty = false;
+               if (j->pgrp == 0) {
+                       /* First process */
+                       j->pgrp = p->pid;
+                       dotty = true;
                }
 
-               /* set pgrp in both parent and child to deal with race
+               /*
+                * set pgrp in both parent and child to deal with race
                 * condition
                 */
-               setpgid(pi.p->pid, pi.j->pgrp);
+               setpgid(p->pid, j->pgrp);
                if (ttypgrp_ok && dotty && !(flags & XBGND))
-                       tcsetpgrp(tty_fd, pi.j->pgrp);
+                       tcsetpgrp(tty_fd, j->pgrp);
        }
 #endif
 
        /* used to close pipe input fd */
-       if (close_fd >= 0 && (((flags & XPCLOSE) && pi.cldpid) ||
-           ((flags & XCCLOSE) && !pi.cldpid)))
+       if (close_fd >= 0 && (((flags & XPCLOSE) && cldpid) ||
+           ((flags & XCCLOSE) && !cldpid)))
                close(close_fd);
-       if (!pi.cldpid) {
+       if (!cldpid) {
                /* child */
 
                /* Do this before restoring signal */
                if (flags & XCOPROC)
                        coproc_cleanup(false);
+#ifndef MKSH_NOPROSPECTOFWORK
                sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
                cleanup_parents_env();
 #ifndef MKSH_UNEMPLOYED
-               /* If FMONITOR or FTALKING is set, these signals are ignored,
+               /*
+                * If FMONITOR or FTALKING is set, these signals are ignored,
                 * if neither FMONITOR nor FTALKING are set, the signals have
                 * their inherited values.
                 */
@@ -463,7 +517,7 @@ exchild(struct op *t, int flags,
 #endif
 #if HAVE_NICE
                if (Flag(FBGNICE) && (flags & XBGND))
-                       (void)nice(4);
+                       ksh_nice(4);
 #endif
                if ((flags & XBGND)
 #ifndef MKSH_UNEMPLOYED
@@ -480,7 +534,8 @@ exchild(struct op *t, int flags,
                                close(forksleep);
                        }
                }
-               remove_job(pi.j, "child");      /* in case of $(jobs) command */
+               /* in case of $(jobs) command */
+               remove_job(j, "child");
                nzombie = 0;
 #ifndef MKSH_UNEMPLOYED
                ttypgrp_ok = false;
@@ -494,8 +549,9 @@ exchild(struct op *t, int flags,
 #ifndef MKSH_SMALL
                if (t->type == TPIPE)
                        unwind(LLEAVE);
-               internal_warningf("exchild: execute() returned");
-               fptreef(shl_out, 2, "exchild: tried to execute {\n%T\n}\n", t);
+               internal_warningf("%s: %s", "exchild", "execute() returned");
+               fptreef(shl_out, 8, "%s: tried to execute {\n\t%T\n}\n",
+                   "exchild", t);
                shf_flush(shl_out);
 #endif
                unwind(LLEAVE);
@@ -503,31 +559,33 @@ exchild(struct op *t, int flags,
        }
 
        /* shell (parent) stuff */
-       if (!(flags & XPIPEO)) {        /* last process in a job */
-               j_startjob(pi.j);
+       if (!(flags & XPIPEO)) {
+               /* last process in a job */
+               j_startjob(j);
                if (flags & XCOPROC) {
-                       pi.j->coproc_id = coproc.id;
+                       j->coproc_id = coproc.id;
                        /* n jobs using co-process output */
                        coproc.njobs++;
                        /* j using co-process input */
-                       coproc.job = (void *)pi.j;
+                       coproc.job = (void *)j;
                }
                if (flags & XBGND) {
-                       j_set_async(pi.j);
+                       j_set_async(j);
                        if (Flag(FTALKING)) {
-                               shf_fprintf(shl_out, "[%d]", pi.j->job);
-                               for (pi.p = pi.j->proc_list; pi.p;
-                                   pi.p = pi.p->next)
+                               shf_fprintf(shl_out, "[%d]", j->job);
+                               for (p = j->proc_list; p; p = p->next)
                                        shf_fprintf(shl_out, " %d",
-                                           (int)pi.p->pid);
+                                           (int)p->pid);
                                shf_putchar('\n', shl_out);
                                shf_flush(shl_out);
                        }
                } else
-                       rv = j_waitj(pi.j, JW_NONE, "jw:last proc");
+                       rv = j_waitj(j, jwflags, "jw:last proc");
        }
 
+#ifndef MKSH_NOPROSPECTOFWORK
        sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
 
        return (rv);
 }
@@ -536,41 +594,53 @@ exchild(struct op *t, int flags,
 void
 startlast(void)
 {
+#ifndef MKSH_NOPROSPECTOFWORK
        sigset_t omask;
 
        sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+#endif
 
-       if (last_job) { /* no need to report error - waitlast() will do it */
+       /* no need to report error - waitlast() will do it */
+       if (last_job) {
                /* ensure it isn't removed by check_job() */
                last_job->flags |= JF_WAITING;
                j_startjob(last_job);
        }
+#ifndef MKSH_NOPROSPECTOFWORK
        sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
 }
 
 /* wait for last job: only used for $(command) jobs */
 int
 waitlast(void)
 {
-       int     rv;
-       Job     *j;
+       int rv;
+       Job *j;
+#ifndef MKSH_NOPROSPECTOFWORK
        sigset_t omask;
 
        sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+#endif
 
        j = last_job;
        if (!j || !(j->flags & JF_STARTED)) {
                if (!j)
-                       warningf(true, "waitlast: no last job");
+                       warningf(true, "%s: %s", "waitlast", "no last job");
                else
-                       internal_warningf("waitlast: not started");
+                       internal_warningf("%s: %s", "waitlast", "not started");
+#ifndef MKSH_NOPROSPECTOFWORK
                sigprocmask(SIG_SETMASK, &omask, NULL);
-               return (125); /* not so arbitrary, non-zero value */
+#endif
+               /* not so arbitrary, non-zero value */
+               return (125);
        }
 
-       rv = j_waitj(j, JW_NONE, "jw:waitlast");
+       rv = j_waitj(j, JW_NONE, "waitlast");
 
+#ifndef MKSH_NOPROSPECTOFWORK
        sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
 
        return (rv);
 }
@@ -579,13 +649,13 @@ waitlast(void)
 int
 waitfor(const char *cp, int *sigp)
 {
-       int     rv;
-       Job     *j;
-       int     ecode;
-       int     flags = JW_INTERRUPT|JW_ASYNCNOTIFY;
+       int rv, ecode, flags = JW_INTERRUPT|JW_ASYNCNOTIFY;
+       Job *j;
+#ifndef MKSH_NOPROSPECTOFWORK
        sigset_t omask;
 
        sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+#endif
 
        *sigp = 0;
 
@@ -599,18 +669,24 @@ waitfor(const char *cp, int *sigp)
                        if (j->ppid == procpid && j->state == PRUNNING)
                                break;
                if (!j) {
+#ifndef MKSH_NOPROSPECTOFWORK
                        sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
                        return (-1);
                }
        } else if ((j = j_lookup(cp, &ecode))) {
                /* don't report normal job completion */
                flags &= ~JW_ASYNCNOTIFY;
                if (j->ppid != procpid) {
+#ifndef MKSH_NOPROSPECTOFWORK
                        sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
                        return (-1);
                }
        } else {
+#ifndef MKSH_NOPROSPECTOFWORK
                sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
                if (ecode != JL_NOSUCH)
                        bi_errorf("%s: %s", cp, lookup_msgs[ecode]);
                return (-1);
@@ -619,9 +695,12 @@ waitfor(const char *cp, int *sigp)
        /* AT&T ksh will wait for stopped jobs - we don't */
        rv = j_waitj(j, flags, "jw:waitfor");
 
+#ifndef MKSH_NOPROSPECTOFWORK
        sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
 
-       if (rv < 0) /* we were interrupted */
+       if (rv < 0)
+               /* we were interrupted */
                *sigp = 128 + -rv;
 
        return (rv);
@@ -631,20 +710,24 @@ waitfor(const char *cp, int *sigp)
 int
 j_kill(const char *cp, int sig)
 {
-       Job     *j;
-       int     rv = 0;
-       int     ecode;
+       Job *j;
+       int rv = 0, ecode;
+#ifndef MKSH_NOPROSPECTOFWORK
        sigset_t omask;
 
        sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+#endif
 
        if ((j = j_lookup(cp, &ecode)) == NULL) {
+#ifndef MKSH_NOPROSPECTOFWORK
                sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
                bi_errorf("%s: %s", cp, lookup_msgs[ecode]);
                return (1);
        }
 
-       if (j->pgrp == 0) {     /* started when !Flag(FMONITOR) */
+       if (j->pgrp == 0) {
+               /* started when !Flag(FMONITOR) */
                if (kill_job(j, sig) < 0) {
                        bi_errorf("%s: %s", cp, strerror(errno));
                        rv = 1;
@@ -660,7 +743,9 @@ j_kill(const char *cp, int sig)
                }
        }
 
+#ifndef MKSH_NOPROSPECTOFWORK
        sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
 
        return (rv);
 }
@@ -670,11 +755,10 @@ j_kill(const char *cp, int sig)
 int
 j_resume(const char *cp, int bg)
 {
-       Job     *j;
-       Proc    *p;
-       int     ecode;
-       int     running;
-       int     rv = 0;
+       Job *j;
+       Proc *p;
+       int ecode, rv = 0;
+       bool running;
        sigset_t omask;
 
        sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
@@ -694,12 +778,12 @@ j_resume(const char *cp, int bg)
        if (bg)
                shprintf("[%d] ", j->job);
 
-       running = 0;
+       running = false;
        for (p = j->proc_list; p != NULL; p = p->next) {
                if (p->state == PSTOPPED) {
                        p->state = PRUNNING;
                        p->status = 0;
-                       running = 1;
+                       running = true;
                }
                shf_puts(p->command, shl_stdout);
                if (p->next)
@@ -717,7 +801,7 @@ j_resume(const char *cp, int bg)
                /* attach tty to job */
                if (j->state == PRUNNING) {
                        if (ttypgrp_ok && (j->flags & JF_SAVEDTTY))
-                               tcsetattr(tty_fd, TCSADRAIN, &j->ttystate);
+                               tcsetattr(tty_fd, TCSADRAIN, &j->ttystat);
                        /* See comment in j_waitj regarding saved_ttypgrp. */
                        if (ttypgrp_ok &&
                            tcsetpgrp(tty_fd, (j->flags & JF_SAVEDTTYPGRP) ?
@@ -725,12 +809,11 @@ j_resume(const char *cp, int bg)
                                rv = errno;
                                if (j->flags & JF_SAVEDTTY)
                                        tcsetattr(tty_fd, TCSADRAIN, &tty_state);
-                               sigprocmask(SIG_SETMASK, &omask,
-                                   NULL);
-                               bi_errorf("1st tcsetpgrp(%d, %d) failed: %s",
-                                   tty_fd,
-                                   (int)((j->flags & JF_SAVEDTTYPGRP) ?
-                                   j->saved_ttypgrp : j->pgrp),
+                               sigprocmask(SIG_SETMASK, &omask, NULL);
+                               bi_errorf("%s %s(%d, %ld) %s: %s",
+                                   "1st", "tcsetpgrp", tty_fd,
+                                   (long)((j->flags & JF_SAVEDTTYPGRP) ?
+                                   j->saved_ttypgrp : j->pgrp), "failed",
                                    strerror(rv));
                                return (1);
                        }
@@ -749,12 +832,12 @@ j_resume(const char *cp, int bg)
                        if (ttypgrp_ok && (j->flags & JF_SAVEDTTY))
                                tcsetattr(tty_fd, TCSADRAIN, &tty_state);
                        if (ttypgrp_ok && tcsetpgrp(tty_fd, kshpgrp) < 0)
-                               warningf(true,
-                                   "fg: 2nd tcsetpgrp(%d, %ld) failed: %s",
-                                   tty_fd, (long)kshpgrp, strerror(errno));
+                               warningf(true, "%s %s(%d, %ld) %s: %s",
+                                   "fg: 2nd", "tcsetpgrp", tty_fd,
+                                   (long)kshpgrp, "failed", strerror(errno));
                }
                sigprocmask(SIG_SETMASK, &omask, NULL);
-               bi_errorf("cannot continue job %s: %s",
+               bi_errorf("%s %s %s", "can't continue job",
                    cp, strerror(err));
                return (1);
        }
@@ -773,8 +856,8 @@ j_resume(const char *cp, int bg)
 int
 j_stopped_running(void)
 {
-       Job     *j;
-       int     which = 0;
+       Job *j;
+       int which = 0;
 
        for (j = job_list; j != NULL; j = j->next) {
 #ifndef MKSH_UNEMPLOYED
@@ -796,35 +879,23 @@ j_stopped_running(void)
        return (0);
 }
 
-int
-j_njobs(void)
-{
-       Job *j;
-       int nj = 0;
-       sigset_t omask;
-
-       sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
-       for (j = job_list; j; j = j->next)
-               nj++;
-
-       sigprocmask(SIG_SETMASK, &omask, NULL);
-       return (nj);
-}
-
 
 /* list jobs for jobs built-in */
 int
 j_jobs(const char *cp, int slp,
-    int nflag)         /* 0: short, 1: long, 2: pgrp */
+    /* 0: short, 1: long, 2: pgrp */
+    int nflag)
 {
-       Job     *j, *tmp;
-       int     how;
-       int     zflag = 0;
+       Job *j, *tmp;
+       int how, zflag = 0;
+#ifndef MKSH_NOPROSPECTOFWORK
        sigset_t omask;
 
        sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+#endif
 
-       if (nflag < 0) { /* kludge: print zombies */
+       if (nflag < 0) {
+               /* kludge: print zombies */
                nflag = 0;
                zflag = 1;
        }
@@ -832,7 +903,9 @@ j_jobs(const char *cp, int slp,
                int     ecode;
 
                if ((j = j_lookup(cp, &ecode)) == NULL) {
+#ifndef MKSH_NOPROSPECTOFWORK
                        sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
                        bi_errorf("%s: %s", cp, lookup_msgs[ecode]);
                        return (1);
                }
@@ -855,7 +928,9 @@ j_jobs(const char *cp, int slp,
                if (j->flags & JF_REMOVE)
                        remove_job(j, "jobs");
        }
+#ifndef MKSH_NOPROSPECTOFWORK
        sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
        return (0);
 }
 
@@ -863,16 +938,19 @@ j_jobs(const char *cp, int slp,
 void
 j_notify(void)
 {
-       Job     *j, *tmp;
+       Job *j, *tmp;
+#ifndef MKSH_NOPROSPECTOFWORK
        sigset_t omask;
 
        sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+#endif
        for (j = job_list; j; j = j->next) {
 #ifndef MKSH_UNEMPLOYED
                if (Flag(FMONITOR) && (j->flags & JF_CHANGED))
                        j_print(j, JP_MEDIUM, shl_out);
 #endif
-               /* Remove job after doing reports so there aren't
+               /*
+                * Remove job after doing reports so there aren't
                 * multiple +/- jobs.
                 */
                if (j->state == PEXITED || j->state == PSIGNALLED)
@@ -884,21 +962,27 @@ j_notify(void)
                        remove_job(j, "notify");
        }
        shf_flush(shl_out);
+#ifndef MKSH_NOPROSPECTOFWORK
        sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
 }
 
 /* Return pid of last process in last asynchronous job */
 pid_t
 j_async(void)
 {
+#ifndef MKSH_NOPROSPECTOFWORK
        sigset_t omask;
 
        sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+#endif
 
        if (async_job)
                async_job->flags |= JF_KNOWN;
 
+#ifndef MKSH_NOPROSPECTOFWORK
        sigprocmask(SIG_SETMASK, &omask, NULL);
+#endif
 
        return (async_pid);
 }
@@ -916,7 +1000,7 @@ j_set_async(Job *j)
        if (async_job && (async_job->flags & (JF_KNOWN|JF_ZOMBIE)) == JF_ZOMBIE)
                remove_job(async_job, "async");
        if (!(j->flags & JF_STARTED)) {
-               internal_warningf("j_async: job not started");
+               internal_warningf("%s: %s", "j_async", "job not started");
                return;
        }
        async_job = j;
@@ -930,8 +1014,8 @@ j_set_async(Job *j)
                if (!oldest) {
                        /* XXX debugging */
                        if (!(async_job->flags & JF_ZOMBIE) || nzombie != 1) {
-                               internal_warningf("j_async: bad nzombie (%d)",
-                                   nzombie);
+                               internal_warningf("%s: bad nzombie (%d)",
+                                   "j_async", nzombie);
                                nzombie = 0;
                        }
                        break;
@@ -955,11 +1039,13 @@ j_startjob(Job *j)
                ;
        j->last_proc = p;
 
+#ifndef MKSH_NOPROSPECTOFWORK
        if (held_sigchld) {
                held_sigchld = 0;
                /* Don't call j_sigchld() as it may remove job... */
                kill(procpid, SIGCHLD);
        }
+#endif
 }
 
 /*
@@ -969,10 +1055,11 @@ j_startjob(Job *j)
  */
 static int
 j_waitj(Job *j,
-    int flags,                 /* see JW_* */
+    /* see JW_* */
+    int flags,
     const char *where)
 {
-       int     rv;
+       int rv;
 
        /*
         * No auto-notify on the job we are waiting on.
@@ -988,12 +1075,17 @@ j_waitj(Job *j,
 
        while (j->state == PRUNNING ||
            ((flags & JW_STOPPEDWAIT) && j->state == PSTOPPED)) {
+#ifndef MKSH_NOPROSPECTOFWORK
                sigsuspend(&sm_default);
+#else
+               j_sigchld(SIGCHLD);
+#endif
                if (fatal_trap) {
                        int oldf = j->flags & (JF_WAITING|JF_W_ASYNCNOTIFY);
                        j->flags &= ~(JF_WAITING|JF_W_ASYNCNOTIFY);
                        runtraps(TF_FATAL);
-                       j->flags |= oldf; /* not reached... */
+                       /* not reached... */
+                       j->flags |= oldf;
                }
                if ((flags & JW_INTERRUPT) && (rv = trap_pending())) {
                        j->flags &= ~(JF_WAITING|JF_W_ASYNCNOTIFY);
@@ -1021,12 +1113,12 @@ j_waitj(Job *j,
                            (j->saved_ttypgrp = tcgetpgrp(tty_fd)) >= 0)
                                j->flags |= JF_SAVEDTTYPGRP;
                        if (tcsetpgrp(tty_fd, kshpgrp) < 0)
-                               warningf(true,
-                                   "j_waitj: tcsetpgrp(%d, %ld) failed: %s",
-                                   tty_fd, (long)kshpgrp, strerror(errno));
+                               warningf(true, "%s %s(%d, %ld) %s: %s",
+                                   "j_waitj:", "tcsetpgrp", tty_fd,
+                                   (long)kshpgrp, "failed", strerror(errno));
                        if (j->state == PSTOPPED) {
                                j->flags |= JF_SAVEDTTY;
-                               tcgetattr(tty_fd, &j->ttystate);
+                               tcgetattr(tty_fd, &j->ttystat);
                        }
                }
 #endif
@@ -1084,6 +1176,38 @@ j_waitj(Job *j,
        j_systime = j->systime;
        rv = j->status;
 
+       if ((flags & JW_PIPEST) && (j->proc_list != NULL)) {
+               uint32_t num = 0;
+               Proc *p = j->proc_list;
+               struct tbl *vp;
+
+               unset(vp_pipest, 1);
+               vp = vp_pipest;
+               vp->flag = DEFINED | ISSET | INTEGER | RDONLY | ARRAY | INT_U;
+               goto got_array;
+
+               while (p != NULL) {
+                       {
+                               struct tbl *vq;
+
+                               /* strlen(vp_pipest->name) == 10 */
+                               vq = alloc(offsetof(struct tbl, name[0]) + 11,
+                                   vp_pipest->areap);
+                               memset(vq, 0, offsetof(struct tbl, name[0]));
+                               memcpy(vq->name, vp_pipest->name, 11);
+                               vp->u.array = vq;
+                               vp = vq;
+                       }
+                       vp->areap = vp_pipest->areap;
+                       vp->ua.index = ++num;
+                       vp->flag = DEFINED | ISSET | INTEGER | RDONLY |
+                           ARRAY | INT_U | AINDEX;
+ got_array:
+                       vp->val.i = proc_errorlevel(p);
+                       p = p->next;
+               }
+       }
+
        if (!(flags & JW_ASYNCNOTIFY)
 #ifndef MKSH_UNEMPLOYED
            && (!Flag(FMONITOR) || j->state != PSTOPPED)
@@ -1119,6 +1243,7 @@ j_sigchld(int sig MKSH_A_UNUSED)
        int status;
        struct rusage ru0, ru1;
 
+#ifndef MKSH_NOPROSPECTOFWORK
        /*
         * Don't wait for any processes if a job is partially started.
         * This is so we don't do away with the process group leader
@@ -1130,10 +1255,15 @@ j_sigchld(int sig MKSH_A_UNUSED)
                        held_sigchld = 1;
                        return;
                }
+#endif
 
        getrusage(RUSAGE_CHILDREN, &ru0);
        do {
+#ifndef MKSH_NOPROSPECTOFWORK
                pid = waitpid(-1, &status, (WNOHANG|WUNTRACED));
+#else
+               pid = wait(&status);
+#endif
 
                /*
                 * return if this would block (0) or no children
@@ -1175,8 +1305,14 @@ j_sigchld(int sig MKSH_A_UNUSED)
                else
                        p->state = PEXITED;
 
-               check_job(j);   /* check to see if entire job is done */
-       } while (1);
+               /* check to see if entire job is done */
+               check_job(j);
+       }
+#ifndef MKSH_NOPROSPECTOFWORK
+           while (/* CONSTCOND */ 1);
+#else
+           while (/* CONSTCOND */ 0);
+#endif
 }
 
 /*
@@ -1203,23 +1339,13 @@ check_job(Job *j)
        jstate = PRUNNING;
        for (p=j->proc_list; p != NULL; p = p->next) {
                if (p->state == PRUNNING)
-                       return; /* some processes still running */
+                       /* some processes still running */
+                       return;
                if (p->state > jstate)
                        jstate = p->state;
        }
        j->state = jstate;
-
-       switch (j->last_proc->state) {
-       case PEXITED:
-               j->status = WEXITSTATUS(j->last_proc->status);
-               break;
-       case PSIGNALLED:
-               j->status = 128 + WTERMSIG(j->last_proc->status);
-               break;
-       default:
-               j->status = 0;
-               break;
-       }
+       j->status = proc_errorlevel(j->last_proc);
 
        /*
         * Note when co-process dies: can't be done in j_wait() nor
@@ -1371,7 +1497,7 @@ j_print(Job *j, int how, struct shf *shf)
                        if (p == j->proc_list)
                                shf_fprintf(shf, "[%d] %c ", j->job, jobchar);
                        else
-                               shf_fprintf(shf, "%s", filler);
+                               shf_puts(filler, shf);
                }
 
                if (how == JP_LONG)
@@ -1416,9 +1542,10 @@ j_print(Job *j, int how, struct shf *shf)
 static Job *
 j_lookup(const char *cp, int *ecodep)
 {
-       Job             *j, *last_match;
-       Proc            *p;
-       int             len, job = 0;
+       Job *j, *last_match;
+       Proc *p;
+       size_t len;
+       int job = 0;
 
        if (ksh_isdigit(*cp)) {
                getn(cp, &job);
@@ -1463,7 +1590,8 @@ j_lookup(const char *cp, int *ecodep)
                                return (j);
                break;
 
-       case '?':               /* %?string */
+       /* %?string */
+       case '?':
                last_match = NULL;
                for (j = job_list; j != NULL; j = j->next)
                        for (p = j->proc_list; p != NULL; p = p->next)
@@ -1479,7 +1607,8 @@ j_lookup(const char *cp, int *ecodep)
                        return (last_match);
                break;
 
-       default:                /* %string */
+       /* %string */
+       default:
                len = strlen(cp);
                last_match = NULL;
                for (j = job_list; j != NULL; j = j->next)
@@ -1568,7 +1697,7 @@ remove_job(Job *j, const char *where)
        for (; curr != NULL && curr != j; prev = &curr->next, curr = *prev)
                ;
        if (curr != j) {
-               internal_warningf("remove_job: job not found (%s)", where);
+               internal_warningf("remove_job: job %s (%s)", "not found", where);
                return;
        }
        *prev = curr->next;
index 79627d1..daaee57 100644 (file)
@@ -1,32 +1,32 @@
 /*-
- * Copyright © 2009
+ * Copyright (c) 2009, 2010, 2011
  *     Thorsten Glaser <tg@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
  * are retained or reproduced in an accompanying document, permission
- * is granted to deal in this work without restriction, including un
+ * is granted to deal in this work without restriction, including un-
  * limited rights to use, publicly perform, distribute, sell, modify,
  * merge, give away, or sublicence.
  *
- * This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
  * the utmost extent permitted by applicable law, neither express nor
  * implied; without malicious intent or gross negligence. In no event
  * may a licensor, author or contributor be held liable for indirect,
  * direct, other damage, loss, or other issues arising in any way out
  * of dealing in the work, even if advised of the possibility of such
  * damage or existence of a defect, except proven that it results out
- * of said persons immediate fault when using the work as intended.
+ * of said person's immediate fault when using the work as intended.
  */
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/lalloc.c,v 1.11 2009/08/08 13:08:51 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/lalloc.c,v 1.19 2011/09/07 15:24:16 tg Exp $");
 
 /* build with CPPFLAGS+= -DUSE_REALLOC_MALLOC=0 on ancient systems */
 #if defined(USE_REALLOC_MALLOC) && (USE_REALLOC_MALLOC == 0)
-#define remalloc(p,n)  ((p) == NULL ? malloc(n) : realloc((p), (n)))
+#define remalloc(p,n)  ((p) == NULL ? malloc_osi(n) : realloc_osi((p), (n)))
 #else
-#define remalloc(p,n)  realloc((p), (n))
+#define remalloc(p,n)  realloc_osi((p), (n))
 #endif
 
 #define ALLOC_ISUNALIGNED(p) (((ptrdiff_t)(p)) % ALLOC_SIZE)
@@ -61,12 +61,27 @@ findptr(ALLOC_ITEM **lpp, char *ptr, Area *ap)
 #ifndef MKSH_SMALL
  fail:
 #endif
-                       internal_errorf("rogue pointer %p", ptr);
+#ifdef DEBUG
+                       internal_warningf("rogue pointer %zX in ap %zX",
+                           (size_t)ptr, (size_t)ap);
+                       /* try to get a coredump */
+                       abort();
+#else
+                       internal_errorf("rogue pointer %zX", (size_t)ptr);
+#endif
                }
        return (ap);
 }
 
 void *
+aresize2(void *ptr, size_t fac1, size_t fac2, Area *ap)
+{
+       if (notoktomul(fac1, fac2))
+               internal_errorf(Tintovfl, fac1, '*', fac2);
+       return (aresize(ptr, fac1 * fac2, ap));
+}
+
+void *
 aresize(void *ptr, size_t numb, Area *ap)
 {
        ALLOC_ITEM *lp = NULL;
@@ -79,14 +94,13 @@ aresize(void *ptr, size_t numb, Area *ap)
                pp->next = lp->next;
        }
 
-       if ((numb >= SIZE_MAX - ALLOC_SIZE) ||
+       if (notoktoadd(numb, ALLOC_SIZE) ||
            (lp = remalloc(lp, numb + ALLOC_SIZE)) == NULL
 #ifndef MKSH_SMALL
            || ALLOC_ISUNALIGNED(lp)
 #endif
            )
-               internal_errorf("cannot allocate %lu data bytes",
-                   (unsigned long)numb);
+               internal_errorf(Toomem, (unsigned long)numb);
        /* this only works because Area is an ALLOC_ITEM */
        lp->next = ap->next;
        ap->next = lp;
@@ -104,7 +118,7 @@ afree(void *ptr, Area *ap)
                /* unhook */
                pp->next = lp->next;
                /* now free ALLOC_ITEM */
-               free(lp);
+               free_osimalloc(lp);
        }
 }
 
@@ -118,6 +132,6 @@ afreeall(Area *ap)
                /* make next ALLOC_ITEM head of list */
                ap->next = lp->next;
                /* free old head */
-               free(lp);
+               free_osimalloc(lp);
        }
 }
index d0219e7..8a1959a 100644 (file)
--- a/src/lex.c
+++ b/src/lex.c
@@ -1,7 +1,7 @@
-/*     $OpenBSD: lex.c,v 1.44 2008/07/03 17:52:08 otto Exp $   */
+/*     $OpenBSD: lex.c,v 1.45 2011/03/09 09:30:39 okan Exp $   */
 
 /*-
- * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
  *     Thorsten Glaser <tg@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -22,7 +22,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/lex.c,v 1.118 2010/07/25 11:35:41 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/lex.c,v 1.156 2011/09/07 15:24:16 tg Exp $");
 
 /*
  * states while lexing word
@@ -35,78 +35,53 @@ __RCSID("$MirOS: src/bin/mksh/lex.c,v 1.118 2010/07/25 11:35:41 tg Exp $");
 #define SEQUOTE                5       /* inside $'' */
 #define SBRACE         6       /* inside ${} */
 #define SQBRACE                7       /* inside "${}" */
-#define SCSPAREN       8       /* inside $() */
-#define SBQUOTE                9       /* inside `` */
-#define SASPAREN       10      /* inside $(( )) */
-#define SHEREDELIM     11      /* parsing <<,<<- delimiter */
-#define SHEREDQUOTE    12      /* parsing " in <<,<<- delimiter */
-#define SPATTERN       13      /* parsing *(...|...) pattern (*+?@!) */
-#define STBRACE                14      /* parsing ${...[#%]...} */
-#define SLETARRAY      15      /* inside =( ), just copy */
-#define SADELIM                16      /* like SBASE, looking for delimiter */
-#define SHERESTRING    17      /* parsing <<< string */
-
-/* Structure to keep track of the lexing state and the various pieces of info
- * needed for each particular state. */
-typedef struct lex_state Lex_state;
-struct lex_state {
-       int ls_state;
-       union {
-               /* $(...) */
-               struct scsparen_info {
-                       int nparen;     /* count open parenthesis */
-                       int csstate;    /* XXX remove */
-#define ls_scsparen ls_info.u_scsparen
-               } u_scsparen;
-
-               /* $((...)) */
-               struct sasparen_info {
-                       int nparen;     /* count open parenthesis */
-                       int start;      /* marks start of $(( in output str */
-#define ls_sasparen ls_info.u_sasparen
-               } u_sasparen;
-
-               /* ((...)) */
-               struct sletparen_info {
-                       int nparen;     /* count open parenthesis */
-#define ls_sletparen ls_info.u_sletparen
-               } u_sletparen;
-
-               /* `...` */
-               struct sbquote_info {
-                       int indquotes;  /* true if in double quotes: "`...`" */
-#define ls_sbquote ls_info.u_sbquote
-               } u_sbquote;
-
-#ifndef MKSH_SMALL
-               /* =(...) */
-               struct sletarray_info {
-                       int nparen;     /* count open parentheses */
-#define ls_sletarray ls_info.u_sletarray
-               } u_sletarray;
-#endif
+#define SBQUOTE                8       /* inside `` */
+#define SASPAREN       9       /* inside $(( )) */
+#define SHEREDELIM     10      /* parsing <<,<<- delimiter */
+#define SHEREDQUOTE    11      /* parsing " in <<,<<- delimiter */
+#define SPATTERN       12      /* parsing *(...|...) pattern (*+?@!) */
+#define SADELIM                13      /* like SBASE, looking for delimiter */
+#define SHERESTRING    14      /* parsing <<< string */
+#define STBRACEKORN    15      /* parsing ${...[#%]...} !FSH */
+#define STBRACEBOURNE  16      /* parsing ${...[#%]...} FSH */
+#define SINVALID       255     /* invalid state */
+
+struct sretrace_info {
+       struct sretrace_info *next;
+       XString xs;
+       char *xp;
+};
 
-               /* ADELIM */
-               struct sadelim_info {
-                       unsigned char nparen;   /* count open parentheses */
-#define SADELIM_BASH   0
-#define SADELIM_MAKE   1
-                       unsigned char style;
+/*
+ * Structure to keep track of the lexing state and the various pieces of info
+ * needed for each particular state.
+ */
+typedef struct lex_state {
+       union {
+               /* point to the next state block */
+               struct lex_state *base;
+               /* marks start of state output in output string */
+               int start;
+               /* SBQUOTE: true if in double quotes: "`...`" */
+               /* SEQUOTE: got NUL, ignore rest of string */
+               bool abool;
+               /* SADELIM information */
+               struct {
+                       /* character to search for */
                        unsigned char delimiter;
+                       /* max. number of delimiters */
                        unsigned char num;
-                       unsigned char flags;    /* ofs. into sadelim_flags[] */
-#define ls_sadelim ls_info.u_sadelim
-               } u_sadelim;
-
-               /* $'...' */
-               struct sequote_info {
-                       bool got_NUL;   /* ignore rest of string */
-#define ls_sequote ls_info.u_sequote
-               } u_sequote;
-
-               Lex_state *base;        /* used to point to next state block */
-       } ls_info;
-};
+               } adelim;
+       } u;
+       /* count open parentheses */
+       short nparen;
+       /* type of this state */
+       uint8_t type;
+} Lex_state;
+#define ls_base                u.base
+#define ls_start       u.start
+#define ls_bool                u.abool
+#define ls_adelim      u.adelim
 
 typedef struct {
        Lex_state *base;
@@ -114,72 +89,106 @@ typedef struct {
 } State_info;
 
 static void readhere(struct ioword *);
-static int getsc__(void);
+static void ungetsc(int);
+static void ungetsc_(int);
+static int getsc_uu(void);
 static void getsc_line(Source *);
 static int getsc_bn(void);
 static int s_get(void);
 static void s_put(int);
 static char *get_brace_var(XString *, char *);
-static int arraysub(char **);
-static const char *ungetsc(int);
+static bool arraysub(char **);
 static void gethere(bool);
 static Lex_state *push_state_(State_info *, Lex_state *);
 static Lex_state *pop_state_(State_info *, Lex_state *);
 
 static int dopprompt(const char *, int, bool);
+void yyskiputf8bom(void);
 
 static int backslash_skip;
 static int ignore_backslash_newline;
+static struct sretrace_info *retrace_info;
+short subshell_nesting_level = 0;
 
 /* optimised getsc_bn() */
-#define _getsc()       (*source->str != '\0' && *source->str != '\\' \
-                        && !backslash_skip && !(source->flags & SF_FIRST) \
-                        ? *source->str++ : getsc_bn())
-/* optimised getsc__() */
-#define        _getsc_()       ((*source->str != '\0') && !(source->flags & SF_FIRST) \
-                        ? *source->str++ : getsc__())
+#define o_getsc()      (*source->str != '\0' && *source->str != '\\' && \
+                           !backslash_skip ? *source->str++ : getsc_bn())
+/* optimised getsc_uu() */
+#define        o_getsc_u()     ((*source->str != '\0') ? *source->str++ : getsc_uu())
+
+/* retrace helper */
+#define o_getsc_r(carg)        {                               \
+       int cev = (carg);                               \
+       struct sretrace_info *rp = retrace_info;        \
+                                                       \
+       while (rp) {                                    \
+               Xcheck(rp->xs, rp->xp);                 \
+               *rp->xp++ = cev;                        \
+               rp = rp->next;                          \
+       }                                               \
+                                                       \
+       return (cev);                                   \
+}
 
 #ifdef MKSH_SMALL
 static int getsc(void);
-static int getsc_(void);
 
 static int
 getsc(void)
 {
-       return (_getsc());
+       o_getsc_r(o_getsc());
 }
+#else
+static int getsc_r(int);
 
 static int
-getsc_(void)
+getsc_r(int c)
 {
-       return (_getsc_());
+       o_getsc_r(c);
 }
-#else
-/* !MKSH_SMALL: use them inline */
-#define getsc()                _getsc()
-#define getsc_()       _getsc_()
+
+#define getsc()                getsc_r(o_getsc())
 #endif
 
-#define STATE_BSIZE    32
+#define STATE_BSIZE    8
 
 #define PUSH_STATE(s)  do {                                    \
        if (++statep == state_info.end)                         \
                statep = push_state_(&state_info, statep);      \
-       state = statep->ls_state = (s);                         \
-} while (0)
+       state = statep->type = (s);                             \
+} while (/* CONSTCOND */ 0)
 
 #define POP_STATE()    do {                                    \
        if (--statep == state_info.base)                        \
                statep = pop_state_(&state_info, statep);       \
-       state = statep->ls_state;                               \
-} while (0)
+       state = statep->type;                                   \
+} while (/* CONSTCOND */ 0)
+
+#define PUSH_SRETRACE()        do {                                    \
+       struct sretrace_info *ri;                               \
+                                                               \
+       statep->ls_start = Xsavepos(ws, wp);                    \
+       ri = alloc(sizeof(struct sretrace_info), ATEMP);        \
+       Xinit(ri->xs, ri->xp, 64, ATEMP);                       \
+       ri->next = retrace_info;                                \
+       retrace_info = ri;                                      \
+} while (/* CONSTCOND */ 0)
+
+#define POP_SRETRACE() do {                                    \
+       wp = Xrestpos(ws, wp, statep->ls_start);                \
+       *retrace_info->xp = '\0';                               \
+       sp = Xstring(retrace_info->xs, retrace_info->xp);       \
+       dp = (void *)retrace_info;                              \
+       retrace_info = retrace_info->next;                      \
+       afree(dp, ATEMP);                                       \
+} while (/* CONSTCOND */ 0)
 
 /**
  * Lexical analyser
  *
  * tokens are not regular expressions, they are LL(1).
  * for example, "${var:-${PWD}}", and "$(size $(whence ksh))".
- * hence the state stack.
+ * hence the state stack. Note "$(...)" are now parsed recursively.
  */
 
 int
@@ -188,13 +197,14 @@ yylex(int cf)
        Lex_state states[STATE_BSIZE], *statep, *s2, *base;
        State_info state_info;
        int c, c2, state;
+       size_t cz;
        XString ws;             /* expandable output word */
        char *wp;               /* output word pointer */
        char *sp, *dp;
 
  Again:
-       states[0].ls_state = -1;
-       states[0].ls_info.base = NULL;
+       states[0].type = SINVALID;
+       states[0].ls_base = NULL;
        statep = &states[1];
        state_info.base = states;
        state_info.end = &state_info.base[STATE_BSIZE];
@@ -204,19 +214,15 @@ yylex(int cf)
        backslash_skip = 0;
        ignore_backslash_newline = 0;
 
-       if (cf&ONEWORD)
+       if (cf & ONEWORD)
                state = SWORD;
-       else if (cf&LETEXPR) {
+       else if (cf & LETEXPR) {
                /* enclose arguments in (double) quotes */
                *wp++ = OQUOTE;
                state = SLETPAREN;
-               statep->ls_sletparen.nparen = 0;
-#ifndef MKSH_SMALL
-       } else if (cf&LETARRAY) {
-               state = SLETARRAY;
-               statep->ls_sletarray.nparen = 0;
-#endif
-       } else {                /* normal lexing */
+               statep->nparen = 0;
+       } else {
+               /* normal lexing */
                state = (cf & HEREDELIM) ? SHEREDELIM : SBASE;
                while ((c = getsc()) == ' ' || c == '\t')
                        ;
@@ -228,13 +234,14 @@ yylex(int cf)
                }
                ungetsc(c);
        }
-       if (source->flags & SF_ALIAS) { /* trailing ' ' in alias definition */
+       if (source->flags & SF_ALIAS) {
+               /* trailing ' ' in alias definition */
                source->flags &= ~SF_ALIAS;
                cf |= ALIAS;
        }
 
-       /* Initial state: one of SBASE SHEREDELIM SWORD SASPAREN */
-       statep->ls_state = state;
+       /* Initial state: one of SWORD SLETPAREN SHEREDELIM SBASE */
+       statep->type = state;
 
        /* check for here string */
        if (state == SHEREDELIM) {
@@ -259,14 +266,14 @@ yylex(int cf)
                switch (state) {
                case SADELIM:
                        if (c == '(')
-                               statep->ls_sadelim.nparen++;
+                               statep->nparen++;
                        else if (c == ')')
-                               statep->ls_sadelim.nparen--;
-                       else if (statep->ls_sadelim.nparen == 0 &&
-                           (c == /*{*/ '}' || c == statep->ls_sadelim.delimiter)) {
+                               statep->nparen--;
+                       else if (statep->nparen == 0 &&
+                           (c == /*{*/ '}' || c == statep->ls_adelim.delimiter)) {
                                *wp++ = ADELIM;
                                *wp++ = c;
-                               if (c == /*{*/ '}' || --statep->ls_sadelim.num == 0)
+                               if (c == /*{*/ '}' || --statep->ls_adelim.num == 0)
                                        POP_STATE();
                                if (c == /*{*/ '}')
                                        POP_STATE();
@@ -275,7 +282,8 @@ yylex(int cf)
                        /* FALLTHROUGH */
                case SBASE:
                        if (c == '[' && (cf & (VARASN|ARRAYVAR))) {
-                               *wp = EOS;      /* temporary */
+                               /* temporary */
+                               *wp = EOS;
                                if (is_wdvarname(Xstring(ws, wp), false)) {
                                        char *p, *tmp;
 
@@ -378,17 +386,20 @@ yylex(int cf)
                                if (c == '(') /*)*/ {
                                        c = getsc();
                                        if (c == '(') /*)*/ {
-                                               PUSH_STATE(SASPAREN);
-                                               statep->ls_sasparen.nparen = 2;
-                                               statep->ls_sasparen.start =
-                                                   Xsavepos(ws, wp);
                                                *wp++ = EXPRSUB;
+                                               PUSH_STATE(SASPAREN);
+                                               statep->nparen = 2;
+                                               PUSH_SRETRACE();
+                                               *retrace_info->xp++ = '(';
                                        } else {
                                                ungetsc(c);
-                                               PUSH_STATE(SCSPAREN);
-                                               statep->ls_scsparen.nparen = 1;
-                                               statep->ls_scsparen.csstate = 0;
+ subst_command:
+                                               sp = yyrecursive();
+                                               cz = strlen(sp) + 1;
+                                               XcheckN(ws, wp, cz);
                                                *wp++ = COMSUB;
+                                               memcpy(wp, sp, cz);
+                                               wp += cz;
                                        }
                                } else if (c == '{') /*}*/ {
                                        *wp++ = OSUBST;
@@ -407,14 +418,14 @@ yylex(int cf)
                                                        *wp++ = ':';
                                                        PUSH_STATE(SBRACE);
                                                        PUSH_STATE(SADELIM);
-                                                       statep->ls_sadelim.style = SADELIM_BASH;
-                                                       statep->ls_sadelim.delimiter = ':';
-                                                       statep->ls_sadelim.num = 1;
-                                                       statep->ls_sadelim.nparen = 0;
+                                                       statep->ls_adelim.delimiter = ':';
+                                                       statep->ls_adelim.num = 1;
+                                                       statep->nparen = 0;
                                                        break;
                                                } else if (ksh_isdigit(c) ||
                                                    c == '('/*)*/ || c == ' ' ||
-                                                   c == '$' /* XXX what else? */) {
+                                                   /*XXX what else? */
+                                                   c == '$') {
                                                        /* substring subst. */
                                                        if (c != ' ') {
                                                                *wp++ = CHAR;
@@ -423,10 +434,9 @@ yylex(int cf)
                                                        ungetsc(c);
                                                        PUSH_STATE(SBRACE);
                                                        PUSH_STATE(SADELIM);
-                                                       statep->ls_sadelim.style = SADELIM_BASH;
-                                                       statep->ls_sadelim.delimiter = ':';
-                                                       statep->ls_sadelim.num = 2;
-                                                       statep->ls_sadelim.nparen = 0;
+                                                       statep->ls_adelim.delimiter = ':';
+                                                       statep->ls_adelim.num = 2;
+                                                       statep->nparen = 0;
                                                        break;
                                                }
                                        } else if (c == '/') {
@@ -439,18 +449,21 @@ yylex(int cf)
                                                        ungetsc(c);
                                                PUSH_STATE(SBRACE);
                                                PUSH_STATE(SADELIM);
-                                               statep->ls_sadelim.style = SADELIM_BASH;
-                                               statep->ls_sadelim.delimiter = '/';
-                                               statep->ls_sadelim.num = 1;
-                                               statep->ls_sadelim.nparen = 0;
+                                               statep->ls_adelim.delimiter = '/';
+                                               statep->ls_adelim.num = 1;
+                                               statep->nparen = 0;
                                                break;
                                        }
-                                       /* If this is a trim operation,
+                                       /*
+                                        * If this is a trim operation,
                                         * treat (,|,) specially in STBRACE.
                                         */
                                        if (ctype(c, C_SUBOP2)) {
                                                ungetsc(c);
-                                               PUSH_STATE(STBRACE);
+                                               if (Flag(FSH))
+                                                       PUSH_STATE(STBRACEBOURNE);
+                                               else
+                                                       PUSH_STATE(STBRACEKORN);
                                        } else {
                                                ungetsc(c);
                                                if (state == SDQUOTE)
@@ -483,11 +496,15 @@ yylex(int cf)
                                        *wp++ = OQUOTE;
                                        ignore_backslash_newline++;
                                        PUSH_STATE(SEQUOTE);
-                                       statep->ls_sequote.got_NUL = false;
+                                       statep->ls_bool = false;
                                        break;
+                               } else if (c == '"' && (state == SBASE)) {
+                                       /* XXX which other states are valid? */
+                                       goto DEQUOTE;
                                } else {
                                        *wp++ = CHAR;
                                        *wp++ = '$';
+ DEQUOTE:
                                        ungetsc(c);
                                }
                                break;
@@ -495,7 +512,8 @@ yylex(int cf)
  subst_gravis:
                                PUSH_STATE(SBQUOTE);
                                *wp++ = COMSUB;
-                               /* Need to know if we are inside double quotes
+                               /*
+                                * Need to know if we are inside double quotes
                                 * since sh/AT&T-ksh translate the \" to " in
                                 * "`...\"...`".
                                 * This is not done in POSIX mode (section
@@ -515,19 +533,19 @@ yylex(int cf)
                                 * literal meaning, except when followed by
                                 * $ ` \.").
                                 */
-                               statep->ls_sbquote.indquotes = 0;
+                               statep->ls_bool = false;
                                s2 = statep;
                                base = state_info.base;
-                               while (1) {
+                               while (/* CONSTCOND */ 1) {
                                        for (; s2 != base; s2--) {
-                                               if (s2->ls_state == SDQUOTE) {
-                                                       statep->ls_sbquote.indquotes = 1;
+                                               if (s2->type == SDQUOTE) {
+                                                       statep->ls_bool = true;
                                                        break;
                                                }
                                        }
                                        if (s2 != base)
                                                break;
-                                       if (!(s2 = s2->ls_info.base))
+                                       if (!(s2 = s2->ls_base))
                                                break;
                                        base = s2-- - STATE_BSIZE;
                                }
@@ -555,23 +573,23 @@ yylex(int cf)
                                if ((c2 = unbksl(true, s_get, s_put)) == -1)
                                        c2 = s_get();
                                if (c2 == 0)
-                                       statep->ls_sequote.got_NUL = true;
-                               if (!statep->ls_sequote.got_NUL) {
+                                       statep->ls_bool = true;
+                               if (!statep->ls_bool) {
                                        char ts[4];
 
                                        if ((unsigned int)c2 < 0x100) {
                                                *wp++ = QCHAR;
                                                *wp++ = c2;
                                        } else {
-                                               c = utf_wctomb(ts, c2 - 0x100);
-                                               ts[c] = 0;
-                                               for (c = 0; ts[c]; ++c) {
+                                               cz = utf_wctomb(ts, c2 - 0x100);
+                                               ts[cz] = 0;
+                                               for (cz = 0; ts[cz]; ++cz) {
                                                        *wp++ = QCHAR;
-                                                       *wp++ = ts[c];
+                                                       *wp++ = ts[cz];
                                                }
                                        }
                                }
-                       } else if (!statep->ls_sequote.got_NUL) {
+                       } else if (!statep->ls_bool) {
                                *wp++ = QCHAR;
                                *wp++ = c;
                        }
@@ -596,97 +614,47 @@ yylex(int cf)
                                goto Subst;
                        break;
 
-               case SCSPAREN:  /* $( ... ) */
-                       /* todo: deal with $(...) quoting properly
-                        * kludge to partly fake quoting inside $(...): doesn't
-                        * really work because nested $(...) or ${...} inside
-                        * double quotes aren't dealt with.
-                        */
-                       switch (statep->ls_scsparen.csstate) {
-                       case 0: /* normal */
-                               switch (c) {
-                               case '(':
-                                       statep->ls_scsparen.nparen++;
-                                       break;
-                               case ')':
-                                       statep->ls_scsparen.nparen--;
-                                       break;
-                               case '\\':
-                                       statep->ls_scsparen.csstate = 1;
-                                       break;
-                               case '"':
-                                       statep->ls_scsparen.csstate = 2;
-                                       break;
-                               case '\'':
-                                       statep->ls_scsparen.csstate = 4;
-                                       ignore_backslash_newline++;
-                                       break;
-                               }
-                               break;
-
-                       case 1: /* backslash in normal mode */
-                       case 3: /* backslash in double quotes */
-                               --statep->ls_scsparen.csstate;
-                               break;
-
-                       case 2: /* double quotes */
-                               if (c == '"')
-                                       statep->ls_scsparen.csstate = 0;
-                               else if (c == '\\')
-                                       statep->ls_scsparen.csstate = 3;
-                               break;
-
-                       case 4: /* single quotes */
-                               if (c == '\'') {
-                                       statep->ls_scsparen.csstate = 0;
-                                       ignore_backslash_newline--;
-                               }
-                               break;
-                       }
-                       if (statep->ls_scsparen.nparen == 0) {
-                               POP_STATE();
-                               *wp++ = 0;      /* end of COMSUB */
-                       } else
-                               *wp++ = c;
-                       break;
-
-               case SASPAREN:  /* $(( ... )) */
-                       /* XXX should nest using existing state machine
-                        * (embed "...", $(...), etc.) */
+               /* $(( ... )) */
+               case SASPAREN:
                        if (c == '(')
-                               statep->ls_sasparen.nparen++;
+                               statep->nparen++;
                        else if (c == ')') {
-                               statep->ls_sasparen.nparen--;
-                               if (statep->ls_sasparen.nparen == 1) {
-                                       /*(*/
-                                       if ((c2 = getsc()) == ')') {
-                                               POP_STATE();
-                                               /* end of EXPRSUB */
-                                               *wp++ = 0;
+                               statep->nparen--;
+                               if (statep->nparen == 1) {
+                                       /* end of EXPRSUB */
+                                       POP_SRETRACE();
+                                       POP_STATE();
+
+                                       if ((c2 = getsc()) == /*(*/ ')') {
+                                               cz = strlen(sp) - 2;
+                                               XcheckN(ws, wp, cz);
+                                               memcpy(wp, sp + 1, cz);
+                                               wp += cz;
+                                               afree(sp, ATEMP);
+                                               *wp++ = '\0';
                                                break;
                                        } else {
-                                               char *s;
+                                               Source *s;
 
                                                ungetsc(c2);
-                                               /* mismatched parenthesis -
+                                               /*
+                                                * mismatched parenthesis -
                                                 * assume we were really
                                                 * parsing a $(...) expression
                                                 */
-                                               s = Xrestpos(ws, wp,
-                                                   statep->ls_sasparen.start);
-                                               memmove(s + 1, s, wp - s);
-                                               *s++ = COMSUB;
-                                               *s = '('; /*)*/
-                                               wp++;
-                                               statep->ls_scsparen.nparen = 1;
-                                               statep->ls_scsparen.csstate = 0;
-                                               state = statep->ls_state =
-                                                   SCSPAREN;
+                                               --wp;
+                                               s = pushs(SREREAD,
+                                                   source->areap);
+                                               s->start = s->str =
+                                                   s->u.freeme = sp;
+                                               s->next = source;
+                                               source = s;
+                                               goto subst_command;
                                        }
                                }
                        }
-                       *wp++ = c;
-                       break;
+                       /* reuse existing state machine */
+                       goto Sbase2;
 
                case SQBRACE:
                        if (c == '\\') {
@@ -724,18 +692,21 @@ yylex(int cf)
                        *wp++ = /*{*/ '}';
                        break;
 
-               case STBRACE:
-                       /* Same as SBASE, except (,|,) treated specially */
-                       if (c == /*{*/ '}') {
-                               POP_STATE();
-                               *wp++ = CSUBST;
-                               *wp++ = /*{*/ '}';
-                       } else if (c == '|') {
+               /* Same as SBASE, except (,|,) treated specially */
+               case STBRACEKORN:
+                       if (c == '|')
                                *wp++ = SPAT;
-                       else if (c == '(') {
+                       else if (c == '(') {
                                *wp++ = OPAT;
-                               *wp++ = ' ';    /* simile for @ */
+                               /* simile for @ */
+                               *wp++ = ' ';
                                PUSH_STATE(SPATTERN);
+                       } else /* FALLTHROUGH */
+               case STBRACEBOURNE:
+                         if (c == /*{*/ '}') {
+                               POP_STATE();
+                               *wp++ = CSUBST;
+                               *wp++ = /*{*/ '}';
                        } else
                                goto Sbase1;
                        break;
@@ -746,36 +717,37 @@ yylex(int cf)
                                POP_STATE();
                        } else if (c == '\\') {
                                switch (c = getsc()) {
+                               case 0:
+                                       /* trailing \ is lost */
+                                       break;
                                case '\\':
                                case '$': case '`':
                                        *wp++ = c;
                                        break;
                                case '"':
-                                       if (statep->ls_sbquote.indquotes) {
+                                       if (statep->ls_bool) {
                                                *wp++ = c;
                                                break;
                                        }
                                        /* FALLTHROUGH */
                                default:
-                                       if (c) {
-                                               /* trailing \ is lost */
-                                               *wp++ = '\\';
-                                               *wp++ = c;
-                                       }
+                                       *wp++ = '\\';
+                                       *wp++ = c;
                                        break;
                                }
                        } else
                                *wp++ = c;
                        break;
 
-               case SWORD:     /* ONEWORD */
+               /* ONEWORD */
+               case SWORD:
                        goto Subst;
 
-               case SLETPAREN: /* LETEXPR: (( ... )) */
-                       /*(*/
-                       if (c == ')') {
-                               if (statep->ls_sletparen.nparen > 0)
-                                       --statep->ls_sletparen.nparen;
+               /* LETEXPR: (( ... )) */
+               case SLETPAREN:
+                       if (c == /*(*/ ')') {
+                               if (statep->nparen > 0)
+                                       --statep->nparen;
                                else if ((c2 = getsc()) == /*(*/ ')') {
                                        c = 0;
                                        *wp++ = CQUOTE;
@@ -784,13 +756,14 @@ yylex(int cf)
                                        Source *s;
 
                                        ungetsc(c2);
-                                       /* mismatched parenthesis -
+                                       /*
+                                        * mismatched parenthesis -
                                         * assume we were really
-                                        * parsing a $(...) expression
+                                        * parsing a (...) expression
                                         */
                                        *wp = EOS;
                                        sp = Xstring(ws, wp);
-                                       dp = wdstrip(sp, true, false);
+                                       dp = wdstrip(sp, WDS_KEEPQ);
                                        s = pushs(SREREAD, source->areap);
                                        s->start = s->str = s->u.freeme = dp;
                                        s->next = source;
@@ -798,28 +771,16 @@ yylex(int cf)
                                        return ('('/*)*/);
                                }
                        } else if (c == '(')
-                               /* parenthesis inside quotes and backslashes
-                                * are lost, but AT&T ksh doesn't count them
-                                * either
+                               /*
+                                * parentheses inside quotes and
+                                * backslashes are lost, but AT&T ksh
+                                * doesn't count them either
                                 */
-                               ++statep->ls_sletparen.nparen;
+                               ++statep->nparen;
                        goto Sbase2;
 
-#ifndef MKSH_SMALL
-               case SLETARRAY: /* LETARRAY: =( ... ) */
-                       if (c == '('/*)*/)
-                               ++statep->ls_sletarray.nparen;
-                       else if (c == /*(*/')')
-                               if (statep->ls_sletarray.nparen-- == 0) {
-                                       c = 0;
-                                       goto Done;
-                               }
-                       *wp++ = CHAR;
-                       *wp++ = c;
-                       break;
-#endif
-
-               case SHERESTRING:       /* <<< delimiter */
+               /* <<< delimiter */
+               case SHERESTRING:
                        if (c == '\\') {
                                c = getsc();
                                if (c) {
@@ -827,14 +788,13 @@ yylex(int cf)
                                        *wp++ = QCHAR;
                                        *wp++ = c;
                                }
-                               /* invoke quoting mode */
-                               Xstring(ws, wp)[0] = QCHAR;
                        } else if (c == '$') {
                                if ((c2 = getsc()) == '\'') {
                                        PUSH_STATE(SEQUOTE);
-                                       statep->ls_sequote.got_NUL = false;
+                                       statep->ls_bool = false;
                                        goto sherestring_quoted;
-                               }
+                               } else if (c2 == '"')
+                                       goto sherestring_dquoted;
                                ungetsc(c2);
                                goto sherestring_regular;
                        } else if (c == '\'') {
@@ -842,10 +802,9 @@ yylex(int cf)
  sherestring_quoted:
                                *wp++ = OQUOTE;
                                ignore_backslash_newline++;
-                               /* invoke quoting mode */
-                               Xstring(ws, wp)[0] = QCHAR;
                        } else if (c == '"') {
-                               state = statep->ls_state = SHEREDQUOTE;
+ sherestring_dquoted:
+                               state = statep->type = SHEREDQUOTE;
                                *wp++ = OQUOTE;
                                /* just don't IFS split; no quoting mode */
                        } else {
@@ -855,13 +814,16 @@ yylex(int cf)
                        }
                        break;
 
-               case SHEREDELIM:        /* <<,<<- delimiter */
-                       /* XXX chuck this state (and the next) - use
+               /* <<,<<- delimiter */
+               case SHEREDELIM:
+                       /*
+                        * XXX chuck this state (and the next) - use
                         * the existing states ($ and \`...` should be
                         * stripped of their specialness after the
                         * fact).
                         */
-                       /* here delimiters need a special case since
+                       /*
+                        * here delimiters need a special case since
                         * $ and `...` are not to be treated specially
                         */
                        if (c == '\\') {
@@ -874,9 +836,10 @@ yylex(int cf)
                        } else if (c == '$') {
                                if ((c2 = getsc()) == '\'') {
                                        PUSH_STATE(SEQUOTE);
-                                       statep->ls_sequote.got_NUL = false;
+                                       statep->ls_bool = false;
                                        goto sheredelim_quoted;
-                               }
+                               } else if (c2 == '"')
+                                       goto sheredelim_dquoted;
                                ungetsc(c2);
                                goto sheredelim_regular;
                        } else if (c == '\'') {
@@ -885,7 +848,8 @@ yylex(int cf)
                                *wp++ = OQUOTE;
                                ignore_backslash_newline++;
                        } else if (c == '"') {
-                               state = statep->ls_state = SHEREDQUOTE;
+ sheredelim_dquoted:
+                               state = statep->type = SHEREDQUOTE;
                                *wp++ = OQUOTE;
                        } else {
  sheredelim_regular:
@@ -894,25 +858,27 @@ yylex(int cf)
                        }
                        break;
 
-               case SHEREDQUOTE:       /* " in <<,<<- delimiter */
+               /* " in <<,<<- delimiter */
+               case SHEREDQUOTE:
                        if (c == '"') {
                                *wp++ = CQUOTE;
-                               state = statep->ls_state =
+                               state = statep->type =
                                    /* dp[1] == '<' means here string */
                                    Xstring(ws, wp)[1] == '<' ?
                                    SHERESTRING : SHEREDELIM;
                        } else {
                                if (c == '\\') {
                                        switch (c = getsc()) {
-                                       case '\\': case '"':
-                                       case '$': case '`':
+                                       case 0:
+                                               /* trailing \ is lost */
+                                       case '\\':
+                                       case '"':
+                                       case '$':
+                                       case '`':
                                                break;
                                        default:
-                                               if (c) {
-                                                       /* trailing \ lost */
-                                                       *wp++ = CHAR;
-                                                       *wp++ = '\\';
-                                               }
+                                               *wp++ = CHAR;
+                                               *wp++ = '\\';
                                                break;
                                        }
                                }
@@ -921,15 +887,17 @@ yylex(int cf)
                        }
                        break;
 
-               case SPATTERN:  /* in *(...|...) pattern (*+?@!) */
-                       if ( /*(*/ c == ')') {
+               /* in *(...|...) pattern (*+?@!) */
+               case SPATTERN:
+                       if (c == /*(*/ ')') {
                                *wp++ = CPAT;
                                POP_STATE();
                        } else if (c == '|') {
                                *wp++ = SPAT;
                        } else if (c == '(') {
                                *wp++ = OPAT;
-                               *wp++ = ' ';    /* simile for @ */
+                               /* simile for @ */
+                               *wp++ = ' ';
                                PUSH_STATE(SPATTERN);
                        } else
                                goto Sbase1;
@@ -942,11 +910,6 @@ yylex(int cf)
                /* XXX figure out what is missing */
                yyerror("no closing quote\n");
 
-#ifndef MKSH_SMALL
-       if (state == SLETARRAY && statep->ls_sletarray.nparen != -1)
-               yyerror("%s: ')' missing\n", T_synerr);
-#endif
-
        /* This done to avoid tests for SHEREDELIM wherever SBASE tested */
        if (state == SHEREDELIM || state == SHERESTRING)
                state = SBASE;
@@ -984,10 +947,14 @@ yylex(int cf)
                        iop->flag |= c == c2 ?
                            (c == '>' ? IOCAT : IOHERE) : IORDWR;
                        if (iop->flag == IOHERE) {
-                               if ((c2 = getsc()) == '-')
+                               if ((c2 = getsc()) == '-') {
                                        iop->flag |= IOSKIP;
-                               else
-                                       ungetsc(c2);
+                                       c2 = getsc();
+                               } else if (c2 == '<')
+                                       iop->flag |= IOHERESTR;
+                               ungetsc(c2);
+                               if (c2 == '\n')
+                                       iop->flag |= IONDELIM;
                        }
                } else if (c2 == '&')
                        iop->flag |= IODUP | (c == '<' ? IORDUP : 0);
@@ -1002,15 +969,17 @@ yylex(int cf)
                iop->name = NULL;
                iop->delim = NULL;
                iop->heredoc = NULL;
-               Xfree(ws, wp);  /* free word */
+               /* free word */
+               Xfree(ws, wp);
                yylval.iop = iop;
                return (REDIR);
  no_iop:
-               ;
+               afree(iop, ATEMP);
        }
 
        if (wp == dp && state == SBASE) {
-               Xfree(ws, wp);  /* free word */
+               /* free word */
+               Xfree(ws, wp);
                /* no word, process LEX1 character */
                if ((c == '|') || (c == '&') || (c == ';') || (c == '('/*)*/)) {
                        if ((c2 = getsc()) == c)
@@ -1020,8 +989,20 @@ yylex(int cf)
                                    /* c == '(' ) */ MDPAREN;
                        else if (c == '|' && c2 == '&')
                                c = COPROC;
+                       else if (c == ';' && c2 == '|')
+                               c = BRKEV;
+                       else if (c == ';' && c2 == '&')
+                               c = BRKFT;
                        else
                                ungetsc(c2);
+#ifndef MKSH_SMALL
+                       if (c == BREAK) {
+                               if ((c2 = getsc()) == '&')
+                                       c = BRKEV;
+                               else
+                                       ungetsc(c2);
+                       }
+#endif
                } else if (c == '\n') {
                        gethere(false);
                        if (cf & CONTIN)
@@ -1032,14 +1013,11 @@ yylex(int cf)
                return (c);
        }
 
-       *wp++ = EOS;            /* terminate word */
+       /* terminate word */
+       *wp++ = EOS;
        yylval.cp = Xclose(ws, wp);
        if (state == SWORD || state == SLETPAREN
-           /* XXX ONEWORD? */
-#ifndef MKSH_SMALL
-           || state == SLETARRAY
-#endif
-           )
+           /* XXX ONEWORD? */)
                return (LWORD);
 
        /* unget terminator */
@@ -1067,15 +1045,16 @@ yylex(int cf)
        /* Make sure the ident array stays '\0' padded */
        memset(dp, 0, (ident+IDENT) - dp + 1);
        if (c != EOS)
-               *ident = '\0';  /* word is not unquoted */
+               /* word is not unquoted */
+               *ident = '\0';
 
-       if (*ident != '\0' && (cf&(KEYWORD|ALIAS))) {
+       if (*ident != '\0' && (cf & (KEYWORD | ALIAS))) {
                struct tbl *p;
                uint32_t h = hash(ident);
 
-               /* { */
                if ((cf & KEYWORD) && (p = ktsearch(&keywords, ident, h)) &&
-                   (!(cf & ESACONLY) || p->val.i == ESAC || p->val.i == '}')) {
+                   (!(cf & ESACONLY) || p->val.i == ESAC ||
+                   p->val.i == /*{*/ '}')) {
                        afree(yylval.cp, ATEMP);
                        return (p->val.i);
                }
@@ -1101,13 +1080,7 @@ yylex(int cf)
                                 */
                                ++cp;
                        /* prefer functions over aliases */
-                       if (*cp == '(' /*)*/)
-                               /*
-                                * delete alias upon encountering function
-                                * definition
-                                */
-                               ktdelete(p);
-                       else {
+                       if (cp[0] != '(' || cp[1] != ')') {
                                Source *s = source;
 
                                while (s && (s->flags & SF_HASALIAS))
@@ -1142,7 +1115,7 @@ gethere(bool iseof)
        struct ioword **p;
 
        for (p = heres; p < herep; p++)
-               if (iseof && (*p)->delim[1] != '<')
+               if (iseof && !((*p)->flag & IOHERESTR))
                        /* only here strings at EOF */
                        return;
                else
@@ -1158,62 +1131,90 @@ static void
 readhere(struct ioword *iop)
 {
        int c;
-       char *volatile eof;
-       char *eofp;
-       int skiptabs;
+       const char *eof, *eofp;
        XString xs;
        char *xp;
        int xpos;
 
-       if (iop->delim[1] == '<') {
+       if (iop->flag & IOHERESTR) {
                /* process the here string */
-               xp = iop->heredoc = evalstr(iop->delim, DOBLANK);
-               c = strlen(xp) - 1;
-               memmove(xp, xp + 1, c);
-               xp[c] = '\n';
+               iop->heredoc = xp = evalstr(iop->delim, DOBLANK);
+               xpos = strlen(xp) - 1;
+               memmove(xp, xp + 1, xpos);
+               xp[xpos] = '\n';
                return;
        }
 
-       eof = evalstr(iop->delim, 0);
+       eof = iop->flag & IONDELIM ? "<<" : evalstr(iop->delim, 0);
 
        if (!(iop->flag & IOEVAL))
                ignore_backslash_newline++;
 
        Xinit(xs, xp, 256, ATEMP);
 
-       for (;;) {
-               eofp = eof;
-               skiptabs = iop->flag & IOSKIP;
-               xpos = Xsavepos(xs, xp);
-               while ((c = getsc()) != 0) {
-                       if (skiptabs) {
-                               if (c == '\t')
-                                       continue;
-                               skiptabs = 0;
-                       }
-                       if (c != *eofp)
+ heredoc_read_line:
+       /* beginning of line */
+       eofp = eof;
+       xpos = Xsavepos(xs, xp);
+       if (iop->flag & IOSKIP) {
+               /* skip over leading tabs */
+               while ((c = getsc()) == '\t')
+                       /* nothing */;
+               goto heredoc_parse_char;
+       }
+ heredoc_read_char:
+       c = getsc();
+ heredoc_parse_char:
+       /* compare with here document marker */
+       if (!*eofp) {
+               /* end of here document marker, what to do? */
+               switch (c) {
+               case /*(*/ ')':
+                       if (!subshell_nesting_level)
+                               /*-
+                                * not allowed outside $(...) or (...)
+                                * => mismatch
+                                */
                                break;
-                       Xcheck(xs, xp);
-                       Xput(xs, xp, c);
-                       eofp++;
-               }
-               /* Allow EOF here so commands with out trailing newlines
-                * will work (eg, ksh -c '...', $(...), etc).
-                */
-               if (*eofp == '\0' && (c == 0 || c == '\n')) {
-                       xp = Xrestpos(xs, xp, xpos);
-                       break;
-               }
-               ungetsc(c);
-               while ((c = getsc()) != '\n') {
-                       if (c == 0)
-                               yyerror("here document '%s' unclosed\n", eof);
-                       Xcheck(xs, xp);
-                       Xput(xs, xp, c);
+                       /* allow $(...) or (...) to close here */
+                       ungetsc(/*(*/ ')');
+                       /* FALLTHROUGH */
+               case 0:
+                       /*
+                        * Allow EOF here to commands without trailing
+                        * newlines (mksh -c '...') will work as well.
+                        */
+               case '\n':
+                       /* Newline terminates here document marker */
+                       goto heredoc_found_terminator;
                }
+       } else if (c == *eofp++)
+               /* store; then read and compare next character */
+               goto heredoc_store_and_loop;
+       /* nope, mismatch; read until end of line */
+       while (c != '\n') {
+               if (!c)
+                       /* oops, reached EOF */
+                       yyerror("%s '%s' unclosed\n", "here document", eof);
+               /* store character */
                Xcheck(xs, xp);
                Xput(xs, xp, c);
+               /* read next character */
+               c = getsc();
        }
+       /* we read a newline as last character */
+ heredoc_store_and_loop:
+       /* store character */
+       Xcheck(xs, xp);
+       Xput(xs, xp, c);
+       if (c == '\n')
+               goto heredoc_read_line;
+       goto heredoc_read_char;
+
+ heredoc_found_terminator:
+       /* jump back to saved beginning of line */
+       xp = Xrestpos(xs, xp, xpos);
+       /* terminate, close and store */
        Xput(xs, xp, '\0');
        iop->heredoc = Xclose(xs, xp);
 
@@ -1229,7 +1230,8 @@ yyerror(const char *fmt, ...)
        /* pop aliases and re-reads */
        while (source->type == SALIAS || source->type == SREREAD)
                source = source->next;
-       source->str = null;     /* zap pending input */
+       /* zap pending input */
+       source->str = null;
 
        error_prefix(true);
        va_start(va, fmt);
@@ -1258,14 +1260,14 @@ pushs(int type, Area *areap)
 }
 
 static int
-getsc__(void)
+getsc_uu(void)
 {
        Source *s = source;
        int c;
 
- getsc_again:
        while ((c = *s->str++) == 0) {
-               s->str = NULL;          /* return 0 for EOF by default */
+               /* return 0 for EOF by default */
+               s->str = NULL;
                switch (s->type) {
                case SEOF:
                        s->str = null;
@@ -1305,25 +1307,28 @@ getsc__(void)
                                s = source;
                        } else if (*s->u.tblp->val.s &&
                            (c = strnul(s->u.tblp->val.s)[-1], ksh_isspace(c))) {
-                               source = s = s->next;   /* pop source stack */
-                               /* Note that this alias ended with a space,
-                                * enabling alias expansion on the following
-                                * word.
+                               /* pop source stack */
+                               source = s = s->next;
+                               /*
+                                * Note that this alias ended with a
+                                * space, enabling alias expansion on
+                                * the following word.
                                 */
                                s->flags |= SF_ALIAS;
                        } else {
-                               /* At this point, we need to keep the current
+                               /*
+                                * At this point, we need to keep the current
                                 * alias in the source list so recursive
-                                * aliases can be detected and we also need
-                                * to return the next character. Do this
-                                * by temporarily popping the alias to get
-                                * the next character and then put it back
-                                * in the source list with the SF_ALIASEND
-                                * flag set.
+                                * aliases can be detected and we also need to
+                                * return the next character. Do this by
+                                * temporarily popping the alias to get the
+                                * next character and then put it back in the
+                                * source list with the SF_ALIASEND flag set.
                                 */
-                               source = s->next;       /* pop source stack */
+                               /* pop source stack */
+                               source = s->next;
                                source->flags |= s->flags & SF_ALIAS;
-                               c = getsc__();
+                               c = getsc_uu();
                                if (c) {
                                        s->flags |= SF_ALIASEND;
                                        s->ugbuf[0] = c; s->ugbuf[1] = '\0';
@@ -1332,7 +1337,7 @@ getsc__(void)
                                        source = s;
                                } else {
                                        s = source;
-                                       /* avoid reading eof twice */
+                                       /* avoid reading EOF twice */
                                        s->str = NULL;
                                        break;
                                }
@@ -1340,7 +1345,8 @@ getsc__(void)
                        continue;
 
                case SREREAD:
-                       if (s->start != s->ugbuf)       /* yuck */
+                       if (s->start != s->ugbuf)
+                               /* yuck */
                                afree(s->u.freeme, ATEMP);
                        source = s = s->next;
                        continue;
@@ -1355,17 +1361,6 @@ getsc__(void)
                        shf_flush(shl_out);
                }
        }
-       /* check for UTF-8 byte order mark */
-       if (s->flags & SF_FIRST) {
-               s->flags &= ~SF_FIRST;
-               if (((unsigned char)c == 0xEF) &&
-                   (((const unsigned char *)(s->str))[0] == 0xBB) &&
-                   (((const unsigned char *)(s->str))[1] == 0xBF)) {
-                       s->str += 2;
-                       UTFMODE = 1;
-                       goto getsc_again;
-               }
-       }
        return (c);
 }
 
@@ -1395,7 +1390,8 @@ getsc_line(Source *s)
                int nread;
 
                nread = x_read(xp, LINE);
-               if (nread < 0)  /* read error */
+               if (nread < 0)
+                       /* read error */
                        nread = 0;
                xp[nread] = '\0';
                xp += nread;
@@ -1405,7 +1401,7 @@ getsc_line(Source *s)
                else
                        s->line++;
 
-               while (1) {
+               while (/* CONSTCOND */ 1) {
                        char *p = shf_getse(xp, Xnleft(s->xs, xp), s->u.shf);
 
                        if (!p && shf_error(s->u.shf) &&
@@ -1418,20 +1414,24 @@ getsc_line(Source *s)
                        if (!p || (xp = p, xp[-1] == '\n'))
                                break;
                        /* double buffer size */
-                       xp++;   /* move past NUL so doubling works... */
+                       /* move past NUL so doubling works... */
+                       xp++;
                        XcheckN(s->xs, xp, Xlength(s->xs, xp));
-                       xp--;   /* ...and move back again */
+                       /* ...and move back again */
+                       xp--;
                }
-               /* flush any unwanted input so other programs/builtins
+               /*
+                * flush any unwanted input so other programs/builtins
                 * can read it. Not very optimal, but less error prone
                 * than flushing else where, dealing with redirections,
                 * etc.
-                * todo: reduce size of shf buffer (~128?) if SSTDIN
+                * TODO: reduce size of shf buffer (~128?) if SSTDIN
                 */
                if (s->type == SSTDIN)
                        shf_flush(s->u.shf);
        }
-       /* XXX: temporary kludge to restore source after a
+       /*
+        * XXX: temporary kludge to restore source after a
         * trap may have been executed.
         */
        source = s;
@@ -1445,7 +1445,7 @@ getsc_line(Source *s)
                int linelen;
 
                linelen = Xlength(s->xs, xp);
-               XcheckN(s->xs, xp, fc_e_n + /* NUL */ 1);
+               XcheckN(s->xs, xp, Zfc_e_dash + /* NUL */ 1);
                /* reload after potential realloc */
                cp = Xstring(s->xs, xp);
                /* change initial '!' into space */
@@ -1453,10 +1453,10 @@ getsc_line(Source *s)
                /* NUL terminate the current string */
                *xp = '\0';
                /* move the actual string forward */
-               memmove(cp + fc_e_n, cp, linelen + /* NUL */ 1);
-               xp += fc_e_n;
+               memmove(cp + Zfc_e_dash, cp, linelen + /* NUL */ 1);
+               xp += Zfc_e_dash;
                /* prepend it with "fc -e -" */
-               memcpy(cp, fc_e_, fc_e_n);
+               memcpy(cp, Tfc_e_dash, Zfc_e_dash);
        }
 #endif
        s->start = s->str = cp;
@@ -1489,8 +1489,10 @@ set_prompt(int to, Source *s)
        cur_prompt = to;
 
        switch (to) {
-       case PS1:       /* command */
-               /* Substitute ! and !! here, before substitutions are done
+       /* command */
+       case PS1:
+               /*
+                * Substitute ! and !! here, before substitutions are done
                 * so ! in expanded variables are not expanded.
                 * NOTE: this is not what AT&T ksh does (it does it after
                 * substitutions, POSIX doesn't say which is to be done.
@@ -1514,7 +1516,8 @@ set_prompt(int to, Source *s)
                        newenv(E_ERRH);
                        if (sigsetjmp(e->jbuf, 0)) {
                                prompt = safe_prompt;
-                               /* Don't print an error - assume it has already
+                               /*
+                                * Don't print an error - assume it has already
                                 * been printed. Reason is we may have forked
                                 * to run a command and the child may be
                                 * unwinding its stack through this code as it
@@ -1527,7 +1530,8 @@ set_prompt(int to, Source *s)
                        quitenv(NULL);
                }
                break;
-       case PS2:       /* command continuation */
+       /* command continuation */
+       case PS2:
                prompt = str_val(global("PS2"));
                break;
        }
@@ -1539,11 +1543,12 @@ dopprompt(const char *cp, int ntruncate, bool doprint)
        int columns = 0, lines = 0, indelimit = 0;
        char delimiter = 0;
 
-       /* Undocumented AT&T ksh feature:
-        * If the second char in the prompt string is \r then the first char
-        * is taken to be a non-printing delimiter and any chars between two
-        * instances of the delimiter are not considered to be part of the
-        * prompt length
+       /*
+        * Undocumented AT&T ksh feature:
+        * If the second char in the prompt string is \r then the first
+        * char is taken to be a non-printing delimiter and any chars
+        * between two instances of the delimiter are not considered to
+        * be part of the prompt length
         */
        if (*cp && cp[1] == '\r') {
                delimiter = *cp;
@@ -1594,20 +1599,20 @@ promptlen(const char *cp)
        return (dopprompt(cp, 0, false));
 }
 
-/* Read the variable part of a ${...} expression (ie, up to but not including
- * the :[-+?=#%] or close-brace.
+/*
+ * Read the variable part of a ${...} expression (i.e. up to but not
+ * including the :[-+?=#%] or close-brace).
  */
 static char *
 get_brace_var(XString *wsp, char *wp)
 {
+       char c;
        enum parse_state {
                PS_INITIAL, PS_SAW_HASH, PS_IDENT,
                PS_NUMBER, PS_VAR1
-       } state;
-       char c;
+       } state = PS_INITIAL;
 
-       state = PS_INITIAL;
-       while (1) {
+       while (/* CONSTCOND */ 1) {
                c = getsc();
                /* State machine to figure out where the variable part ends. */
                switch (state) {
@@ -1622,7 +1627,19 @@ get_brace_var(XString *wsp, char *wp)
                                state = PS_IDENT;
                        else if (ksh_isdigit(c))
                                state = PS_NUMBER;
-                       else if (ctype(c, C_VAR1))
+                       else if (c == '#') {
+                               if (state == PS_SAW_HASH) {
+                                       char c2;
+
+                                       c2 = getsc();
+                                       ungetsc(c2);
+                                       if (c2 != '}') {
+                                               ungetsc(c);
+                                               goto out;
+                                       }
+                               }
+                               state = PS_VAR1;
+                       } else if (ctype(c, C_VAR1))
                                state = PS_VAR1;
                        else
                                goto out;
@@ -1640,7 +1657,8 @@ get_brace_var(XString *wsp, char *wp)
                                                *wp++ = *p++;
                                        }
                                        afree(tmp, ATEMP);
-                                       c = getsc();    /* the ] */
+                                       /* the ] */
+                                       c = getsc();
                                }
                                goto out;
                        }
@@ -1656,7 +1674,8 @@ get_brace_var(XString *wsp, char *wp)
                *wp++ = c;
        }
  out:
-       *wp++ = '\0';   /* end of variable part */
+       /* end of variable part */
+       *wp++ = '\0';
        ungetsc(c);
        return (wp);
 }
@@ -1666,13 +1685,13 @@ get_brace_var(XString *wsp, char *wp)
  * if eof or newline was found.
  * (Returned string double null terminated)
  */
-static int
+static bool
 arraysub(char **strp)
 {
        XString ws;
-       char    *wp;
-       char    c;
-       int     depth = 1;      /* we are just past the initial [ */
+       char *wp, c;
+       /* we are just past the initial [ */
+       int depth = 1;
 
        Xinit(ws, wp, 32, ATEMP);
 
@@ -1689,18 +1708,30 @@ arraysub(char **strp)
        *wp++ = '\0';
        *strp = Xclose(ws, wp);
 
-       return (depth == 0 ? 1 : 0);
+       return (tobool(depth == 0));
 }
 
 /* Unget a char: handles case when we are already at the start of the buffer */
-static const char *
+static void
 ungetsc(int c)
 {
+       struct sretrace_info *rp = retrace_info;
+
        if (backslash_skip)
                backslash_skip--;
-       /* Don't unget eof... */
+       /* Don't unget EOF... */
        if (source->str == null && c == '\0')
-               return (source->str);
+               return;
+       while (rp) {
+               if (Xlength(rp->xs, rp->xp))
+                       rp->xp--;
+               rp = rp->next;
+       }
+       ungetsc_(c);
+}
+static void
+ungetsc_(int c)
+{
        if (source->str > source->start)
                source->str--;
        else {
@@ -1712,7 +1743,6 @@ ungetsc(int c)
                s->next = source;
                source = s;
        }
-       return (source->str);
 }
 
 
@@ -1723,34 +1753,57 @@ getsc_bn(void)
        int c, c2;
 
        if (ignore_backslash_newline)
-               return (getsc_());
+               return (o_getsc_u());
 
        if (backslash_skip == 1) {
                backslash_skip = 2;
-               return (getsc_());
+               return (o_getsc_u());
        }
 
        backslash_skip = 0;
 
-       while (1) {
-               c = getsc_();
+       while (/* CONSTCOND */ 1) {
+               c = o_getsc_u();
                if (c == '\\') {
-                       if ((c2 = getsc_()) == '\n')
+                       if ((c2 = o_getsc_u()) == '\n')
                                /* ignore the \newline; get the next char... */
                                continue;
-                       ungetsc(c2);
+                       ungetsc_(c2);
                        backslash_skip = 1;
                }
                return (c);
        }
 }
 
+void
+yyskiputf8bom(void)
+{
+       int c;
+
+       if ((unsigned char)(c = o_getsc_u()) != 0xEF) {
+               ungetsc_(c);
+               return;
+       }
+       if ((unsigned char)(c = o_getsc_u()) != 0xBB) {
+               ungetsc_(c);
+               ungetsc_(0xEF);
+               return;
+       }
+       if ((unsigned char)(c = o_getsc_u()) != 0xBF) {
+               ungetsc_(c);
+               ungetsc_(0xBB);
+               ungetsc_(0xEF);
+               return;
+       }
+       UTFMODE |= 8;
+}
+
 static Lex_state *
 push_state_(State_info *si, Lex_state *old_end)
 {
-       Lex_state *news = alloc(STATE_BSIZE * sizeof(Lex_state), ATEMP);
+       Lex_state *news = alloc2(STATE_BSIZE, sizeof(Lex_state), ATEMP);
 
-       news[0].ls_info.base = old_end;
+       news[0].ls_base = old_end;
        si->base = &news[0];
        si->end = &news[STATE_BSIZE];
        return (&news[1]);
@@ -1761,8 +1814,8 @@ pop_state_(State_info *si, Lex_state *old_end)
 {
        Lex_state *old_base = si->base;
 
-       si->base = old_end->ls_info.base - STATE_BSIZE;
-       si->end = old_end->ls_info.base;
+       si->base = old_end->ls_base - STATE_BSIZE;
+       si->end = old_end->ls_base;
 
        afree(old_base, ATEMP);
 
index f962dd4..b78965e 100644 (file)
@@ -1,10 +1,10 @@
-/*     $OpenBSD: main.c,v 1.46 2010/05/19 17:36:08 jasper Exp $        */
+/*     $OpenBSD: main.c,v 1.47 2011/09/07 11:33:25 otto Exp $  */
 /*     $OpenBSD: tty.c,v 1.9 2006/03/14 22:08:01 deraadt Exp $ */
 /*     $OpenBSD: io.c,v 1.22 2006/03/17 16:30:13 millert Exp $ */
 /*     $OpenBSD: table.c,v 1.13 2009/01/17 22:06:44 millert Exp $      */
 
 /*-
- * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
  *     Thorsten Glaser <tg@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -23,7 +23,7 @@
  * of said person's immediate fault when using the work as intended.
  */
 
-#define        EXTERN
+#define EXTERN
 #include "sh.h"
 
 #if HAVE_LANGINFO_CODESET
 #include <locale.h>
 #endif
 
-__RCSID("$MirOS: src/bin/mksh/main.c,v 1.167 2010/07/04 17:45:15 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/main.c,v 1.200 2011/10/07 19:51:28 tg Exp $");
 
 extern char **environ;
 
-#if !HAVE_SETRESUGID
-extern uid_t kshuid;
-extern gid_t kshgid, kshegid;
-#endif
-
 #ifndef MKSHRC_PATH
 #define MKSHRC_PATH    "~/.mkshrc"
 #endif
@@ -50,10 +45,10 @@ extern gid_t kshgid, kshegid;
 #define MKSH_DEFAULT_TMPDIR    "/tmp"
 #endif
 
+void chvt_reinit(void);
 static void reclaim(void);
 static void remove_temps(struct temp *);
-void chvt_reinit(void);
-Source *mksh_init(int, const char *[]);
+static mksh_uari_t rndsetup(void);
 #ifdef SIGWINCH
 static void x_sigwinch(int);
 #endif
@@ -64,16 +59,19 @@ static const char initsubs[] =
     "${PS2=> } ${PS3=#? } ${PS4=+ } ${SECONDS=0} ${TMOUT=0}";
 
 static const char *initcoms[] = {
-       T_typeset, "-r", initvsn, NULL,
-       T_typeset, "-x", "HOME", "PATH", "RANDOM", "SHELL", NULL,
-       T_typeset, "-i10", "COLUMNS", "LINES", "OPTIND", "PGRP", "PPID",
-           "RANDOM", "SECONDS", "TMOUT", "USER_ID", NULL,
-       "alias",
+       Ttypeset, "-r", initvsn, NULL,
+       Ttypeset, "-x", "HOME", "PATH", "RANDOM", "SHELL", NULL,
+       Ttypeset, "-i10", "SECONDS", "TMOUT", NULL,
+       Talias,
        "integer=typeset -i",
-       T_local_typeset,
-       "hash=alias -t",        /* not "alias -t --": hash -r needs to work */
+       Tlocal_typeset,
+       /* not "alias -t --": hash -r needs to work */
+       "hash=alias -t",
        "type=whence -v",
-#ifndef MKSH_UNEMPLOYED
+#if !defined(ANDROID) && !defined(MKSH_UNEMPLOYED)
+       /* not in Android for political reasons */
+       /* not in ARGE mksh due to no job control */
+       "stop=kill -STOP",
        "suspend=kill -STOP $$",
 #endif
        "autoload=typeset -fu",
@@ -81,20 +79,68 @@ static const char *initcoms[] = {
        "history=fc -l",
        "nameref=typeset -n",
        "nohup=nohup ",
-       r_fc_e_,
+       Tr_fc_e_dash,
        "source=PATH=$PATH:. command .",
        "login=exec login",
        NULL,
         /* this is what AT&T ksh seems to track, with the addition of emacs */
-       "alias", "-tU",
+       Talias, "-tU",
        "cat", "cc", "chmod", "cp", "date", "ed", "emacs", "grep", "ls",
        "make", "mv", "pr", "rm", "sed", "sh", "vi", "who", NULL,
        NULL
 };
 
+static const char *restr_com[] = {
+       Ttypeset, "-r", "PATH", "ENV", "SHELL", NULL
+};
+
 static int initio_done;
 
-struct env *e = &kshstate_v.env_;
+/* top-level parsing and execution environment */
+static struct env env;
+struct env *e = &env;
+
+static mksh_uari_t
+rndsetup(void)
+{
+       register uint32_t h;
+       struct {
+               ALLOC_ITEM alloc_INT;
+               void *dataptr, *stkptr, *mallocptr;
+               sigjmp_buf jbuf;
+               struct timeval tv;
+               struct timezone tz;
+       } *bufptr;
+       char *cp;
+
+       cp = alloc(sizeof(*bufptr) - ALLOC_SIZE, APERM);
+#ifdef DEBUG
+       /* clear the allocated space, for valgrind */
+       memset(cp, 0, sizeof(*bufptr) - ALLOC_SIZE);
+#endif
+       /* undo what alloc() did to the malloc result address */
+       bufptr = (void *)(cp - ALLOC_SIZE);
+       /* PIE or something similar provides us with deltas here */
+       bufptr->dataptr = &rndsetupstate;
+       /* ASLR in at least Windows, Linux, some BSDs */
+       bufptr->stkptr = &bufptr;
+       /* randomised malloc in BSD (and possibly others) */
+       bufptr->mallocptr = bufptr;
+       /* glibc pointer guard */
+       sigsetjmp(bufptr->jbuf, 1);
+       /* introduce variation */
+       gettimeofday(&bufptr->tv, &bufptr->tz);
+
+       NZATInit(h);
+       /* variation through pid, ppid, and the works */
+       NZATUpdateMem(h, &rndsetupstate, sizeof(rndsetupstate));
+       /* some variation, some possibly entropy, depending on OE */
+       NZATUpdateMem(h, bufptr, sizeof(*bufptr));
+       NZAATFinish(h);
+
+       afree(cp, APERM);
+       return ((mksh_uari_t)h);
+}
 
 void
 chvt_reinit(void)
@@ -105,19 +151,23 @@ chvt_reinit(void)
        kshppid = getppid();
 }
 
-Source *
-mksh_init(int argc, const char *argv[])
+static const char *empty_argv[] = {
+       "mksh", NULL
+};
+
+int
+main(int argc, const char *argv[])
 {
        int argi, i;
-       Source *s;
+       Source *s = NULL;
        struct block *l;
        unsigned char restricted, errexit, utf_flag;
-       const char **wp;
+       char *cp;
+       const char *ccp, **wp;
        struct tbl *vp;
        struct stat s_stdin;
 #if !defined(_PATH_DEFPATH) && defined(_CS_PATH)
-       size_t k;
-       char *cp;
+       ssize_t k;
 #endif
 
        /* do things like getpgrp() et al. */
@@ -125,28 +175,59 @@ mksh_init(int argc, const char *argv[])
 
        /* make sure argv[] is sane */
        if (!*argv) {
-               static const char *empty_argv[] = {
-                       "mksh", NULL
-               };
-
                argv = empty_argv;
                argc = 1;
        }
-       kshname = *argv;
+       kshname = argv[0];
 
-       ainit(&aperm);          /* initialise permanent Area */
+       /* initialise permanent Area */
+       ainit(&aperm);
 
        /* set up base environment */
-       kshstate_v.env_.type = E_NONE;
-       ainit(&kshstate_v.env_.area);
-       newblock();             /* set up global l->vars and l->funs */
+       env.type = E_NONE;
+       ainit(&env.area);
+       /* set up global l->vars and l->funs */
+       newblock();
 
        /* Do this first so output routines (eg, errorf, shellf) can work */
        initio();
 
-       argi = parse_args(argv, OF_FIRSTTIME, NULL);
-       if (argi < 0)
-               return (NULL);
+       /* determine the basename (without '-' or path) of the executable */
+       ccp = kshname;
+       goto begin_parse_kshname;
+       while ((i = ccp[argi++])) {
+               if (i == '/') {
+                       ccp += argi;
+ begin_parse_kshname:
+                       argi = 0;
+                       if (*ccp == '-')
+                               ++ccp;
+               }
+       }
+       if (!*ccp)
+               ccp = empty_argv[0];
+
+       /* define built-in commands and see if we were called as one */
+       ktinit(APERM, &builtins,
+           /* currently 50 builtins -> 80% of 64 (2^6) */
+           6);
+       for (i = 0; mkshbuiltins[i].name != NULL; i++)
+               if (!strcmp(ccp, builtin(mkshbuiltins[i].name,
+                   mkshbuiltins[i].func)))
+                       Flag(FAS_BUILTIN) = 1;
+
+       if (!Flag(FAS_BUILTIN)) {
+               /* check for -T option early */
+               argi = parse_args(argv, OF_FIRSTTIME, NULL);
+               if (argi < 0)
+                       return (1);
+
+#ifdef MKSH_BINSHREDUCED
+               /* set FSH if we're called as -sh or /bin/sh or so */
+               if (!strcmp(ccp, "sh"))
+                       change_flag(FSH, OF_FIRSTTIME, 1);
+#endif
+       }
 
        initvar();
 
@@ -157,28 +238,22 @@ mksh_init(int argc, const char *argv[])
        coproc_init();
 
        /* set up variable and command dictionaries */
-       ktinit(&taliases, APERM, 0);
-       ktinit(&aliases, APERM, 0);
+       ktinit(APERM, &taliases, 0);
+       ktinit(APERM, &aliases, 0);
 #ifndef MKSH_NOPWNAM
-       ktinit(&homedirs, APERM, 0);
+       ktinit(APERM, &homedirs, 0);
 #endif
 
        /* define shell keywords */
        initkeywords();
 
-       /* define built-in commands */
-       ktinit(&builtins, APERM,
-           /* must be 80% of 2^n (currently 44 builtins) */ 64);
-       for (i = 0; mkshbuiltins[i].name != NULL; i++)
-               builtin(mkshbuiltins[i].name, mkshbuiltins[i].func);
-
        init_histvec();
 
 #ifdef _PATH_DEFPATH
        def_path = _PATH_DEFPATH;
 #else
 #ifdef _CS_PATH
-       if ((k = confstr(_CS_PATH, NULL, 0)) != (size_t)-1 && k > 0 &&
+       if ((k = confstr(_CS_PATH, NULL, 0)) > 0 &&
            confstr(_CS_PATH, cp = alloc(k + 1, APERM), k + 1) == k + 1)
                def_path = cp;
        else
@@ -194,91 +269,74 @@ mksh_init(int argc, const char *argv[])
                def_path = "/bin:/usr/bin:/sbin:/usr/sbin";
 #endif
 
-       /* Set PATH to def_path (will set the path global variable).
+       /*
+        * Set PATH to def_path (will set the path global variable).
         * (import of environment below will probably change this setting).
         */
        vp = global("PATH");
        /* setstr can't fail here */
        setstr(vp, def_path, KSH_RETURN_ERROR);
 
-       /* Turn on nohup by default for now - will change to off
+       /*
+        * Turn on nohup by default for now - will change to off
         * by default once people are aware of its existence
         * (AT&T ksh does not have a nohup option - it always sends
         * the hup).
         */
        Flag(FNOHUP) = 1;
 
-       /* Turn on brace expansion by default. AT&T kshs that have
+       /*
+        * Turn on brace expansion by default. AT&T kshs that have
         * alternation always have it on.
         */
        Flag(FBRACEEXPAND) = 1;
 
-       /* Set edit mode to emacs by default, may be overridden
+       /*
+        * Set edit mode to emacs by default, may be overridden
         * by the environment or the user. Also, we want tab completion
-        * on in vi by default. */
+        * on in vi by default.
+        */
        change_flag(FEMACS, OF_SPECIAL, 1);
 #if !MKSH_S_NOVI
        Flag(FVITABCOMPLETE) = 1;
 #endif
 
-#ifdef MKSH_BINSHREDUCED
-       /* set FSH if we're called as -sh or /bin/sh or so */
-       {
-               const char *cc;
-
-               cc = kshname;
-               i = 0; argi = 0;
-               while (cc[i] != '\0')
-                       /* the following line matches '-' and '/' ;-) */
-                       if ((cc[i++] | 2) == '/')
-                               argi = i;
-               if (((cc[argi] | 0x20) == 's') && ((cc[argi + 1] | 0x20) == 'h'))
-                       change_flag(FSH, OF_FIRSTTIME, 1);
-       }
-#endif
-
        /* import environment */
        if (environ != NULL)
                for (wp = (const char **)environ; *wp != NULL; wp++)
                        typeset(*wp, IMPORT | EXPORT, 0, 0, 0);
 
-       typeset(initifs, 0, 0, 0, 0);   /* for security */
+       /* for security */
+       typeset(initifs, 0, 0, 0, 0);
 
        /* assign default shell variable values */
        substitute(initsubs, 0);
 
        /* Figure out the current working directory and set $PWD */
-       {
-               struct stat s_pwd, s_dot;
-               struct tbl *pwd_v = global("PWD");
-               char *pwd = str_val(pwd_v);
-               char *pwdx = pwd;
-
-               /* Try to use existing $PWD if it is valid */
-               if (pwd[0] != '/' ||
-                   stat(pwd, &s_pwd) < 0 || stat(".", &s_dot) < 0 ||
-                   s_pwd.st_dev != s_dot.st_dev ||
-                   s_pwd.st_ino != s_dot.st_ino)
-                       pwdx = NULL;
-               set_current_wd(pwdx);
-               if (current_wd[0])
-                       simplify_path(current_wd);
-               /* Only set pwd if we know where we are or if it had a
-                * bogus value
-                */
-               if (current_wd[0] || pwd != null)
-                       /* setstr can't fail here */
-                       setstr(pwd_v, current_wd, KSH_RETURN_ERROR);
-       }
+       vp = global("PWD");
+       cp = str_val(vp);
+       /* Try to use existing $PWD if it is valid */
+       set_current_wd((cp[0] == '/' && test_eval(NULL, TO_FILEQ, cp, ".",
+           true)) ? cp : NULL);
+       if (current_wd[0])
+               simplify_path(current_wd);
+       /* Only set pwd if we know where we are or if it had a bogus value */
+       if (current_wd[0] || *cp)
+               /* setstr can't fail here */
+               setstr(vp, current_wd, KSH_RETURN_ERROR);
 
        for (wp = initcoms; *wp != NULL; wp++) {
                shcomexec(wp);
                while (*wp != NULL)
                        wp++;
        }
-       setint(global("COLUMNS"), 0);
-       setint(global("LINES"), 0);
-       setint(global("OPTIND"), 1);
+       setint_n(global("COLUMNS"), 0);
+       setint_n(global("LINES"), 0);
+       setint_n(global("OPTIND"), 1);
+
+       kshuid = getuid();
+       kshgid = getgid();
+       kshegid = getegid();
 
        safe_prompt = ksheuid ? "$ " : "# ";
        vp = global("PS1");
@@ -287,22 +345,24 @@ mksh_init(int argc, const char *argv[])
            (!ksheuid && !strchr(str_val(vp), '#')))
                /* setstr can't fail here */
                setstr(vp, safe_prompt, KSH_RETURN_ERROR);
-       setint((vp = global("PGRP")), (mksh_uari_t)kshpgrp);
+       setint_n((vp = global("PGRP")), (mksh_uari_t)kshpgrp);
        vp->flag |= INT_U;
-       setint((vp = global("PPID")), (mksh_uari_t)kshppid);
+       setint_n((vp = global("PPID")), (mksh_uari_t)kshppid);
        vp->flag |= INT_U;
-       setint((vp = global("RANDOM")), (mksh_uari_t)evilhash(kshname));
+       setint_n((vp = global("USER_ID")), (mksh_uari_t)ksheuid);
        vp->flag |= INT_U;
-       setint((vp = global("USER_ID")), (mksh_uari_t)ksheuid);
+       setint_n((vp = global("KSHUID")), (mksh_uari_t)kshuid);
        vp->flag |= INT_U;
+       setint_n((vp = global("KSHEGID")), (mksh_uari_t)kshegid);
+       vp->flag |= INT_U;
+       setint_n((vp = global("KSHGID")), (mksh_uari_t)kshgid);
+       vp->flag |= INT_U;
+       setint_n((vp = global("RANDOM")), rndsetup());
+       vp->flag |= INT_U;
+       setint_n((vp_pipest = global("PIPESTATUS")), 0);
 
        /* Set this before parsing arguments */
-#if HAVE_SETRESUGID
-       Flag(FPRIVILEGED) = getuid() != ksheuid || getgid() != getegid();
-#else
-       Flag(FPRIVILEGED) = (kshuid = getuid()) != ksheuid ||
-           (kshgid = getgid()) != (kshegid = getegid());
-#endif
+       Flag(FPRIVILEGED) = kshuid != ksheuid || kshgid != kshegid;
 
        /* this to note if monitor is set on command line (see below) */
 #ifndef MKSH_UNEMPLOYED
@@ -311,18 +371,61 @@ mksh_init(int argc, const char *argv[])
        /* this to note if utf-8 mode is set on command line (see below) */
        UTFMODE = 2;
 
-       argi = parse_args(argv, OF_CMDLINE, NULL);
-       if (argi < 0)
-               return (NULL);
+       if (!Flag(FAS_BUILTIN)) {
+               argi = parse_args(argv, OF_CMDLINE, NULL);
+               if (argi < 0)
+                       return (1);
+       }
+
+#ifdef DEBUG
+       /* test wraparound of arithmetic types */
+       {
+               volatile long xl;
+               volatile unsigned long xul;
+               volatile int xi;
+               volatile unsigned int xui;
+               volatile mksh_ari_t xa;
+               volatile mksh_uari_t xua, xua2;
+               volatile uint8_t xc;
+
+               xa = 2147483647;
+               xua = 2147483647;
+               ++xa;
+               ++xua;
+               xua2 = xa;
+               xl = xa;
+               xul = xua;
+               xa = 0;
+               xua = 0;
+               --xa;
+               --xua;
+               xi = xa;
+               xui = xua;
+               xa = -1;
+               xua = xa;
+               ++xa;
+               ++xua;
+               xc = 0;
+               --xc;
+               if ((xua2 != 2147483648UL) ||
+                   (xl != -2147483648L) || (xul != 2147483648UL) ||
+                   (xi != -1) || (xui != 4294967295U) ||
+                   (xa != 0) || (xua != 0) || (xc != 255))
+                       errorf("integer wraparound test failed");
+       }
+#endif
 
        /* process this later only, default to off (hysterical raisins) */
        utf_flag = UTFMODE;
        UTFMODE = 0;
 
-       if (Flag(FCOMMAND)) {
+       if (Flag(FAS_BUILTIN)) {
+               /* auto-detect from environment variables, always */
+               utf_flag = 3;
+       } else if (Flag(FCOMMAND)) {
                s = pushs(SSTRING, ATEMP);
                if (!(s->start = s->str = argv[argi++]))
-                       errorf("-c requires an argument");
+                       errorf("%s %s", "-c", "requires an argument");
 #ifdef MKSH_MIDNIGHTBSD01ASH_COMPAT
                /* compatibility to MidnightBSD 0.1 /bin/sh (kludge) */
                if (Flag(FSH) && argv[argi] && !strcmp(argv[argi], "--"))
@@ -336,7 +439,7 @@ mksh_init(int argc, const char *argv[])
                s->u.shf = shf_open(s->file, O_RDONLY, 0,
                    SHF_MAPHI | SHF_CLEXEC);
                if (s->u.shf == NULL) {
-                       shl_stdout_ok = 0;
+                       shl_stdout_ok = false;
                        warningf(true, "%s: %s", s->file, strerror(errno));
                        /* mandated by SUSv4 */
                        exstat = 127;
@@ -365,37 +468,17 @@ mksh_init(int argc, const char *argv[])
 
        /* initialise job control */
        j_init();
-       /* set: 0/1; unset: 2->0 */
-       UTFMODE = utf_flag & 1;
        /* Do this after j_init(), as tty_fd is not initialised until then */
        if (Flag(FTALKING)) {
                if (utf_flag == 2) {
 #ifndef MKSH_ASSUME_UTF8
-#define isuc(x)        (((x) != NULL) && \
-                   (stristr((x), "UTF-8") || stristr((x), "utf8")))
-               /* Check if we're in a UTF-8 locale */
-                       const char *ccp;
-
-#if HAVE_SETLOCALE_CTYPE
-                       ccp = setlocale(LC_CTYPE, "");
-#if HAVE_LANGINFO_CODESET
-                       if (!isuc(ccp))
-                               ccp = nl_langinfo(CODESET);
-#endif
-#else
-                       /* these were imported from environ earlier */
-                       ccp = str_val(global("LC_ALL"));
-                       if (ccp == null)
-                               ccp = str_val(global("LC_CTYPE"));
-                       if (ccp == null)
-                               ccp = str_val(global("LANG"));
-#endif
-                       UTFMODE = isuc(ccp);
-#undef isuc
+                       /* auto-detect from locale or environment */
+                       utf_flag = 4;
 #elif MKSH_ASSUME_UTF8
-                       UTFMODE = 1;
+                       utf_flag = 1;
 #else
-                       UTFMODE = 0;
+                       /* always disable UTF-8 (for interactive) */
+                       utf_flag = 0;
 #endif
                }
                x_init();
@@ -408,10 +491,62 @@ mksh_init(int argc, const char *argv[])
 #endif
 
        l = e->loc;
-       l->argv = &argv[argi - 1];
-       l->argc = argc - argi;
-       l->argv[0] = kshname;
-       getopts_reset(1);
+       if (Flag(FAS_BUILTIN)) {
+               l->argc = argc;
+               l->argv = argv;
+               l->argv[0] = ccp;
+       } else {
+               l->argc = argc - argi;
+               l->argv = &argv[argi - 1];
+               l->argv[0] = kshname;
+               getopts_reset(1);
+       }
+
+       /* divine the initial state of the utf8-mode Flag */
+#define isuc(x)        (((x) != NULL) && \
+           (stristr((x), "UTF-8") || stristr((x), "utf8")))
+       ccp = null;
+       switch (utf_flag) {
+
+       /* auto-detect from locale or environment */
+       case 4:
+#if HAVE_SETLOCALE_CTYPE
+               ccp = setlocale(LC_CTYPE, "");
+#if HAVE_LANGINFO_CODESET
+               if (!isuc(ccp))
+                       ccp = nl_langinfo(CODESET);
+#endif
+               if (!isuc(ccp))
+                       ccp = null;
+               /* FALLTHROUGH */
+#endif
+
+       /* auto-detect from environment */
+       case 3:
+               /* these were imported from environ earlier */
+               if (ccp == null)
+                       ccp = str_val(global("LC_ALL"));
+               if (ccp == null)
+                       ccp = str_val(global("LC_CTYPE"));
+               if (ccp == null)
+                       ccp = str_val(global("LANG"));
+               UTFMODE = isuc(ccp);
+               break;
+
+       /* not set on command line, not FTALKING */
+       case 2:
+       /* unknown values */
+       default:
+               utf_flag = 0;
+               /* FALLTHROUGH */
+
+       /* known values */
+       case 1:
+       case 0:
+               UTFMODE = utf_flag;
+               break;
+       }
+#undef isuc
 
        /* Disable during .profile/ENV reading */
        restricted = Flag(FRESTRICTED);
@@ -419,20 +554,21 @@ mksh_init(int argc, const char *argv[])
        errexit = Flag(FERREXIT);
        Flag(FERREXIT) = 0;
 
-       /* Do this before profile/$ENV so that if it causes problems in them,
+       /*
+        * Do this before profile/$ENV so that if it causes problems in them,
         * user will know why things broke.
         */
        if (!current_wd[0] && Flag(FTALKING))
-               warningf(false, "Cannot determine current working directory");
+               warningf(false, "can't determine current directory");
 
        if (Flag(FLOGIN)) {
-               include(KSH_SYSTEM_PROFILE, 0, NULL, 1);
+               include(MKSH_SYSTEM_PROFILE, 0, NULL, 1);
                if (!Flag(FPRIVILEGED))
                        include(substitute("$HOME/.profile", 0), 0,
                            NULL, 1);
        }
        if (Flag(FPRIVILEGED))
-               include("/etc/suid_profile", 0, NULL, 1);
+               include(MKSH_SUID_PROFILE, 0, NULL, 1);
        else if (Flag(FTALKING)) {
                char *env_file;
 
@@ -444,40 +580,27 @@ mksh_init(int argc, const char *argv[])
        }
 
        if (restricted) {
-               static const char *restr_com[] = {
-                       T_typeset, "-r", "PATH",
-                       "ENV", "SHELL",
-                       NULL
-               };
                shcomexec(restr_com);
                /* After typeset command... */
                Flag(FRESTRICTED) = 1;
        }
        Flag(FERREXIT) = errexit;
 
-       if (Flag(FTALKING)) {
+       if (Flag(FTALKING))
                hist_init(s);
-               alarm_init();
-       } else
-               Flag(FTRACKALL) = 1;    /* set after ENV */
-
-       return (s);
-}
+       else
+               /* set after ENV */
+               Flag(FTRACKALL) = 1;
 
-int
-main(int argc, const char *argv[])
-{
-       Source *s;
+       alarm_init();
 
-       kshstate_v.lcg_state_ = 5381;
+       if (Flag(FAS_BUILTIN))
+               return (shcomexec(l->argv));
 
-       if ((s = mksh_init(argc, argv))) {
-               /* put more entropy into the LCG */
-               change_random(s, sizeof(*s));
-               /* doesn’t return */
-               shell(s, true);
-       }
-       return (1);
+       /* doesn't return */
+       shell(s, true);
+       /* NOTREACHED */
+       return (0);
 }
 
 int
@@ -511,9 +634,11 @@ include(const char *name, int argc, const char **argv, int intr_ok)
                switch (i) {
                case LRETURN:
                case LERROR:
-                       return (exstat & 0xff); /* see below */
+                       /* see below */
+                       return (exstat & 0xFF);
                case LINTR:
-                       /* intr_ok is set if we are including .profile or $ENV.
+                       /*
+                        * intr_ok is set if we are including .profile or $ENV.
                         * If user ^Cs out, we don't want to kill the shell...
                         */
                        if (intr_ok && (exstat - 128) != SIGTERM)
@@ -525,7 +650,7 @@ include(const char *name, int argc, const char **argv, int intr_ok)
                        unwind(i);
                        /* NOTREACHED */
                default:
-                       internal_errorf("include: %d", i);
+                       internal_errorf("%s %d", "include", i);
                        /* NOTREACHED */
                }
        }
@@ -542,7 +667,8 @@ include(const char *name, int argc, const char **argv, int intr_ok)
                e->loc->argv = old_argv;
                e->loc->argc = old_argc;
        }
-       return (i & 0xff);      /* & 0xff to ensure value not -1 */
+       /* & 0xff to ensure value not -1 */
+       return (i & 0xFF);
 }
 
 /* spawn a command into a shell optionally keeping track of the line number */
@@ -567,30 +693,32 @@ shell(Source * volatile s, volatile int toplevel)
        volatile int wastty = s->flags & SF_TTY;
        volatile int attempts = 13;
        volatile int interactive = Flag(FTALKING) && toplevel;
+       volatile bool sfirst = true;
        Source *volatile old_source = source;
        int i;
 
-       s->flags |= SF_FIRST;   /* enable UTF-8 BOM check */
-
        newenv(E_PARSE);
        if (interactive)
                really_exit = 0;
        i = sigsetjmp(e->jbuf, 0);
        if (i) {
                switch (i) {
-               case LINTR: /* we get here if SIGINT not caught or ignored */
+               case LINTR:
+                       /* we get here if SIGINT not caught or ignored */
                case LERROR:
                case LSHELL:
                        if (interactive) {
                                if (i == LINTR)
                                        shellf("\n");
-                               /* Reset any eof that was read as part of a
+                               /*
+                                * Reset any eof that was read as part of a
                                 * multiline command.
                                 */
                                if (Flag(FIGNOREEOF) && s->type == SEOF &&
                                    wastty)
                                        s->type = SSTDIN;
-                               /* Used by exit command to get back to
+                               /*
+                                * Used by exit command to get back to
                                 * top level shell. Kind of strange since
                                 * interactive is set if we are reading from
                                 * a tty, but to have stopped jobs, one only
@@ -606,16 +734,17 @@ shell(Source * volatile s, volatile int toplevel)
                case LRETURN:
                        source = old_source;
                        quitenv(NULL);
-                       unwind(i);      /* keep on going */
+                       /* keep on going */
+                       unwind(i);
                        /* NOTREACHED */
                default:
                        source = old_source;
                        quitenv(NULL);
-                       internal_errorf("shell: %d", i);
+                       internal_errorf("%s %d", "shell", i);
                        /* NOTREACHED */
                }
        }
-       while (1) {
+       while (/* CONSTCOND */ 1) {
                if (trap)
                        runtraps(0);
 
@@ -629,17 +758,19 @@ shell(Source * volatile s, volatile int toplevel)
                        j_notify();
                        set_prompt(PS1, s);
                }
-               t = compile(s);
+               t = compile(s, sfirst);
+               sfirst = false;
                if (t != NULL && t->type == TEOF) {
                        if (wastty && Flag(FIGNOREEOF) && --attempts > 0) {
-                               shellf("Use 'exit' to leave ksh\n");
+                               shellf("Use 'exit' to leave mksh\n");
                                s->type = SSTDIN;
                        } else if (wastty && !really_exit &&
                            j_stopped_running()) {
                                really_exit = 1;
                                s->type = SSTDIN;
                        } else {
-                               /* this for POSIX which says EXIT traps
+                               /*
+                                * this for POSIX which says EXIT traps
                                 * shall be taken in the environment
                                 * immediately after the last command
                                 * executed.
@@ -668,14 +799,18 @@ unwind(int i)
 {
        /* ordering for EXIT vs ERR is a bit odd (this is what AT&T ksh does) */
        if (i == LEXIT || (Flag(FERREXIT) && (i == LERROR || i == LINTR) &&
-           sigtraps[SIGEXIT_].trap)) {
-               runtrap(&sigtraps[SIGEXIT_]);
+           sigtraps[ksh_SIGEXIT].trap)) {
+               ++trap_nested;
+               runtrap(&sigtraps[ksh_SIGEXIT], trap_nested == 1);
+               --trap_nested;
                i = LLEAVE;
        } else if (Flag(FERREXIT) && (i == LERROR || i == LINTR)) {
-               runtrap(&sigtraps[SIGERR_]);
+               ++trap_nested;
+               runtrap(&sigtraps[ksh_SIGERR], trap_nested == 1);
+               --trap_nested;
                i = LLEAVE;
        }
-       while (1) {
+       while (/* CONSTCOND */ 1) {
                switch (e->type) {
                case E_PARSE:
                case E_FUNC:
@@ -705,7 +840,8 @@ newenv(int type)
         * so first get the actually used memory, then assign it
         */
        cp = alloc(sizeof(struct env) - ALLOC_SIZE, ATEMP);
-       ep = (void *)(cp - ALLOC_SIZE); /* undo what alloc() did */
+       /* undo what alloc() did to the malloc result address */
+       ep = (void *)(cp - ALLOC_SIZE);
        /* initialise public members of struct env (not the ALLOC_ITEM) */
        ainit(&ep->area);
        ep->oenv = e;
@@ -732,14 +868,17 @@ quitenv(struct shf *shf)
                        /* if ep->savefd[fd] < 0, means fd was closed */
                        if (ep->savefd[fd])
                                restfd(fd, ep->savefd[fd]);
-               if (ep->savefd[2])      /* Clear any write errors */
+               if (ep->savefd[2])
+                       /* Clear any write errors */
                        shf_reopen(2, SHF_WR, shl_out);
        }
-       /* Bottom of the stack.
+       /*
+        * Bottom of the stack.
         * Either main shell is exiting or cleanup_parents_env() was called.
         */
        if (ep->oenv == NULL) {
-               if (ep->type == E_NONE) {       /* Main shell exiting? */
+               if (ep->type == E_NONE) {
+                       /* Main shell exiting? */
 #if HAVE_PERSISTENT_HISTORY
                        if (Flag(FTALKING))
                                hist_finish();
@@ -748,7 +887,8 @@ quitenv(struct shf *shf)
                        if (ep->flags & EF_FAKE_SIGDIE) {
                                int sig = exstat - 128;
 
-                               /* ham up our death a bit (AT&T ksh
+                               /*
+                                * ham up our death a bit (AT&T ksh
                                 * only seems to do this for SIGTERM)
                                 * Don't do it for SIGQUIT, since we'd
                                 * dump a core..
@@ -832,7 +972,8 @@ remove_temps(struct temp *tp)
                        unlink(tp->name);
 }
 
-/* Initialise tty_fd. Used for saving/reseting tty modes upon
+/*
+ * Initialise tty_fd. Used for saving/reseting tty modes upon
  * foreground job completion and for setting up tty process group.
  */
 void
@@ -845,19 +986,21 @@ tty_init(bool init_ttystate, bool need_tty)
                close(tty_fd);
                tty_fd = -1;
        }
-       tty_devtty = 1;
+       tty_devtty = true;
 
 #ifdef _UWIN
-       /* XXX imake style */
-       if (isatty(3))
+       /*XXX imake style */
+       if (isatty(3)) {
+               /* fd 3 on UWIN _is_ /dev/tty (or our controlling tty) */
                tfd = 3;
-       else
+               do_close = false;
+       } else
 #endif
-       if ((tfd = open("/dev/tty", O_RDWR, 0)) < 0) {
-               tty_devtty = 0;
+         if ((tfd = open("/dev/tty", O_RDWR, 0)) < 0) {
+               tty_devtty = false;
                if (need_tty)
-                       warningf(false,
-                           "No controlling tty (open /dev/tty: %s)",
+                       warningf(false, "%s: %s %s: %s",
+                           "No controlling tty", "open", "/dev/tty",
                            strerror(errno));
        }
        if (tfd < 0) {
@@ -868,20 +1011,18 @@ tty_init(bool init_ttystate, bool need_tty)
                        tfd = 2;
                else {
                        if (need_tty)
-                               warningf(false,
-                                   "Can't find tty file descriptor");
+                               warningf(false, "can't find tty fd");
                        return;
                }
        }
        if ((tty_fd = fcntl(tfd, F_DUPFD, FDBASE)) < 0) {
                if (need_tty)
-                       warningf(false, "j_ttyinit: dup of tty fd failed: %s",
-                           strerror(errno));
+                       warningf(false, "%s: %s %s: %s", "j_ttyinit",
+                           "dup of tty fd", "failed", strerror(errno));
        } else if (fcntl(tty_fd, F_SETFD, FD_CLOEXEC) < 0) {
                if (need_tty)
-                       warningf(false,
-                           "j_ttyinit: can't set close-on-exec flag: %s",
-                           strerror(errno));
+                       warningf(false, "%s: %s: %s", "j_ttyinit",
+                           "can't set close-on-exec flag", strerror(errno));
                close(tty_fd);
                tty_fd = -1;
        } else if (init_ttystate)
@@ -900,21 +1041,62 @@ tty_close(void)
 }
 
 /* A shell error occurred (eg, syntax error, etc.) */
+
+#define VWARNINGF_ERRORPREFIX  1
+#define VWARNINGF_FILELINE     2
+#define VWARNINGF_BUILTIN      4
+#define VWARNINGF_INTERNAL     8
+
+static void vwarningf(unsigned int, const char *, va_list)
+    MKSH_A_FORMAT(__printf__, 2, 0);
+
+static void
+vwarningf(unsigned int flags, const char *fmt, va_list ap)
+{
+       if (*fmt != 1) {
+               if (flags & VWARNINGF_INTERNAL)
+                       shf_fprintf(shl_out, "internal error: ");
+               if (flags & VWARNINGF_ERRORPREFIX)
+                       error_prefix(tobool(flags & VWARNINGF_FILELINE));
+               if ((flags & VWARNINGF_BUILTIN) &&
+                   /* not set when main() calls parse_args() */
+                   builtin_argv0 && builtin_argv0 != kshname)
+                       shf_fprintf(shl_out, "%s: ", builtin_argv0);
+               shf_vfprintf(shl_out, fmt, ap);
+               shf_putchar('\n', shl_out);
+       }
+       shf_flush(shl_out);
+}
+
+void
+errorfx(int rc, const char *fmt, ...)
+{
+       va_list va;
+
+       exstat = rc;
+
+       /* debugging: note that stdout not valid */
+       shl_stdout_ok = false;
+
+       va_start(va, fmt);
+       vwarningf(VWARNINGF_ERRORPREFIX | VWARNINGF_FILELINE, fmt, va);
+       va_end(va);
+       unwind(LERROR);
+}
+
 void
 errorf(const char *fmt, ...)
 {
        va_list va;
 
-       shl_stdout_ok = 0;      /* debugging: note that stdout not valid */
        exstat = 1;
-       if (*fmt != 1) {
-               error_prefix(true);
-               va_start(va, fmt);
-               shf_vfprintf(shl_out, fmt, va);
-               va_end(va);
-               shf_putchar('\n', shl_out);
-       }
-       shf_flush(shl_out);
+
+       /* debugging: note that stdout not valid */
+       shl_stdout_ok = false;
+
+       va_start(va, fmt);
+       vwarningf(VWARNINGF_ERRORPREFIX | VWARNINGF_FILELINE, fmt, va);
+       va_end(va);
        unwind(LERROR);
 }
 
@@ -924,15 +1106,14 @@ warningf(bool fileline, const char *fmt, ...)
 {
        va_list va;
 
-       error_prefix(fileline);
        va_start(va, fmt);
-       shf_vfprintf(shl_out, fmt, va);
+       vwarningf(VWARNINGF_ERRORPREFIX | (fileline ? VWARNINGF_FILELINE : 0),
+           fmt, va);
        va_end(va);
-       shf_putchar('\n', shl_out);
-       shf_flush(shl_out);
 }
 
-/* Used by built-in utilities to prefix shell and utility name to message
+/*
+ * Used by built-in utilities to prefix shell and utility name to message
  * (also unwinds environments for special builtins).
  */
 void
@@ -940,20 +1121,18 @@ bi_errorf(const char *fmt, ...)
 {
        va_list va;
 
-       shl_stdout_ok = 0;      /* debugging: note that stdout not valid */
+       /* debugging: note that stdout not valid */
+       shl_stdout_ok = false;
+
        exstat = 1;
-       if (*fmt != 1) {
-               error_prefix(true);
-               /* not set when main() calls parse_args() */
-               if (builtin_argv0)
-                       shf_fprintf(shl_out, "%s: ", builtin_argv0);
-               va_start(va, fmt);
-               shf_vfprintf(shl_out, fmt, va);
-               va_end(va);
-               shf_putchar('\n', shl_out);
-       }
-       shf_flush(shl_out);
-       /* POSIX special builtins and ksh special builtins cause
+
+       va_start(va, fmt);
+       vwarningf(VWARNINGF_ERRORPREFIX | VWARNINGF_FILELINE |
+           VWARNINGF_BUILTIN, fmt, va);
+       va_end(va);
+
+       /*
+        * POSIX special builtins and ksh special builtins cause
         * non-interactive shells to exit.
         * XXX odd use of KEEPASN; also may not want LERROR here
         */
@@ -965,21 +1144,12 @@ bi_errorf(const char *fmt, ...)
 
 /* Called when something that shouldn't happen does */
 void
-internal_verrorf(const char *fmt, va_list ap)
-{
-       shf_fprintf(shl_out, "internal error: ");
-       shf_vfprintf(shl_out, fmt, ap);
-       shf_putchar('\n', shl_out);
-       shf_flush(shl_out);
-}
-
-void
 internal_errorf(const char *fmt, ...)
 {
        va_list va;
 
        va_start(va, fmt);
-       internal_verrorf(fmt, va);
+       vwarningf(VWARNINGF_INTERNAL, fmt, va);
        va_end(va);
        unwind(LERROR);
 }
@@ -990,7 +1160,7 @@ internal_warningf(const char *fmt, ...)
        va_list va;
 
        va_start(va, fmt);
-       internal_verrorf(fmt, va);
+       vwarningf(VWARNINGF_INTERNAL, fmt, va);
        va_end(va);
 }
 
@@ -1015,7 +1185,8 @@ shellf(const char *fmt, ...)
 {
        va_list va;
 
-       if (!initio_done) /* shl_out may not be set up yet... */
+       if (!initio_done)
+               /* shl_out may not be set up yet... */
                return;
        va_start(va, fmt);
        shf_vfprintf(shl_out, fmt, va);
@@ -1051,9 +1222,11 @@ struct shf shf_iob[3];
 void
 initio(void)
 {
-       shf_fdopen(1, SHF_WR, shl_stdout);      /* force buffer allocation */
+       /* force buffer allocation */
+       shf_fdopen(1, SHF_WR, shl_stdout);
        shf_fdopen(2, SHF_WR, shl_out);
-       shf_fdopen(2, SHF_WR, shl_spare);       /* force buffer allocation */
+       /* force buffer allocation */
+       shf_fdopen(2, SHF_WR, shl_spare);
        initio_done = 1;
 }
 
@@ -1067,7 +1240,7 @@ ksh_dup2(int ofd, int nfd, bool errok)
                errorf("too many files open in shell");
 
 #ifdef __ultrix
-       /* XXX imake style */
+       /*XXX imake style */
        if (rv >= 0)
                fcntl(nfd, F_SETFD, 0);
 #endif
@@ -1076,8 +1249,8 @@ ksh_dup2(int ofd, int nfd, bool errok)
 }
 
 /*
- * move fd from user space (0<=fd<10) to shell space (fd>=10),
- * set close-on-exec flag.
+ * Move fd from user space (0 <= fd < 10) to shell space (fd >= 10),
+ * set close-on-exec flag. See FDBASE in sh.h, maybe 24 not 10 here.
  */
 short
 savefd(int fd)
@@ -1098,10 +1271,12 @@ restfd(int fd, int ofd)
 {
        if (fd == 2)
                shf_flush(&shf_iob[fd]);
-       if (ofd < 0)            /* original fd closed */
+       if (ofd < 0)
+               /* original fd closed */
                close(fd);
        else if (fd != ofd) {
-               ksh_dup2(ofd, fd, true); /* XXX: what to do if this fails? */
+               /*XXX: what to do if this dup fails? */
+               ksh_dup2(ofd, fd, true);
                close(ofd);
        }
 }
@@ -1128,7 +1303,8 @@ closepipe(int *pv)
        close(pv[1]);
 }
 
-/* Called by iosetup() (deals with 2>&4, etc.), c_read, c_print to turn
+/*
+ * Called by iosetup() (deals with 2>&4, etc.), c_read, c_print to turn
  * a string (the X in 2>&X, read -uX, print -uX) into a file descriptor.
  */
 int
@@ -1151,7 +1327,8 @@ check_fd(const char *name, int mode, const char **emsgp)
                return (-1);
        }
        fl &= O_ACCMODE;
-       /* X_OK is a kludge to disable this check for dups (x<&1):
+       /*
+        * X_OK is a kludge to disable this check for dups (x<&1):
         * historical shells never did this check (XXX don't know what
         * POSIX has to say).
         */
@@ -1187,7 +1364,8 @@ coproc_read_close(int fd)
        }
 }
 
-/* Called by c_read() and by iosetup() to close the other side of the
+/*
+ * Called by c_read() and by iosetup() to close the other side of the
  * read pipe, so reads will actually terminate.
  */
 void
@@ -1199,7 +1377,8 @@ coproc_readw_close(int fd)
        }
 }
 
-/* Called by c_print when a write to a fd fails with EPIPE and by iosetup
+/*
+ * Called by c_print when a write to a fd fails with EPIPE and by iosetup
  * when co-process input is dup'd
  */
 void
@@ -1211,7 +1390,8 @@ coproc_write_close(int fd)
        }
 }
 
-/* Called to check for existence of/value of the co-process file descriptor.
+/*
+ * Called to check for existence of/value of the co-process file descriptor.
  * (Used by check_fd() and by c_read/c_print to deal with -p option).
  */
 int
@@ -1226,7 +1406,8 @@ coproc_getfd(int mode, const char **emsgp)
        return (-1);
 }
 
-/* called to close file descriptors related to the coprocess (if any)
+/*
+ * called to close file descriptors related to the coprocess (if any)
  * Should be called with SIGCHLD blocked.
  */
 void
@@ -1253,7 +1434,7 @@ struct temp *
 maketemp(Area *ap, Temp_type type, struct temp **tlist)
 {
        struct temp *tp;
-       int len;
+       size_t len;
        int fd;
        char *pathname;
        const char *dir;
@@ -1265,6 +1446,7 @@ maketemp(Area *ap, Temp_type type, struct temp **tlist)
        pathname = tempnam(dir, "mksh.");
        len = ((pathname == NULL) ? 0 : strlen(pathname)) + 1;
 #endif
+       /* reasonably sure that this will not overflow */
        tp = alloc(sizeof(struct temp) + len, ap);
        tp->name = (char *)&tp[1];
 #if !HAVE_MKSTEMP
@@ -1272,14 +1454,14 @@ maketemp(Area *ap, Temp_type type, struct temp **tlist)
                tp->name[0] = '\0';
        else {
                memcpy(tp->name, pathname, len);
-               free(pathname);
+               free_ostempnam(pathname);
        }
 #endif
        pathname = tp->name;
        tp->shf = NULL;
        tp->type = type;
 #if HAVE_MKSTEMP
-       shf_snprintf(pathname, len, "%s/mksh.XXXXXXXXXX", dir);
+       shf_snprintf(pathname, len, "%s%s", dir, "/mksh.XXXXXXXXXX");
        if ((fd = mkstemp(pathname)) >= 0)
 #else
        if (tp->name[0] && (fd = open(tp->name, O_CREAT | O_RDWR, 0600)) >= 0)
@@ -1297,41 +1479,48 @@ maketemp(Area *ap, Temp_type type, struct temp **tlist)
  * but with a slightly tweaked implementation written from scratch.
  */
 
-#define        INIT_TBLS       8       /* initial table size (power of 2) */
+#define        INIT_TBLSHIFT   3       /* initial table shift (2^3 = 8) */
 #define PERTURB_SHIFT  5       /* see Python 2.5.4 Objects/dictobject.c */
 
-static void texpand(struct table *, size_t);
+static void tgrow(struct table *);
 static int tnamecmp(const void *, const void *);
-static struct tbl *ktscan(struct table *, const char *, uint32_t,
-    struct tbl ***);
 
 static void
-texpand(struct table *tp, size_t nsize)
+tgrow(struct table *tp)
 {
-       size_t i, j, osize = tp->size, perturb;
+       size_t i, j, osize, mask, perturb;
        struct tbl *tblp, **pp;
        struct tbl **ntblp, **otblp = tp->tbls;
 
-       ntblp = alloc(nsize * sizeof(struct tbl *), tp->areap);
-       for (i = 0; i < nsize; i++)
-               ntblp[i] = NULL;
-       tp->size = nsize;
-       tp->nfree = (nsize * 4) / 5;    /* table can get 80% full */
+       if (tp->tshift > 29)
+               internal_errorf("hash table size limit reached");
+
+       /* calculate old size, new shift and new size */
+       osize = (size_t)1 << (tp->tshift++);
+       i = osize << 1;
+
+       ntblp = alloc2(i, sizeof(struct tbl *), tp->areap);
+       /* multiplication cannot overflow: alloc2 checked that */
+       memset(ntblp, 0, i * sizeof(struct tbl *));
+
+       /* table can get 80% full except when reaching its limit */
+       tp->nfree = (tp->tshift == 30) ? 0x3FFF0000UL : ((i * 4) / 5);
        tp->tbls = ntblp;
        if (otblp == NULL)
                return;
-       nsize--;                        /* from here on nsize := mask */
+
+       mask = i - 1;
        for (i = 0; i < osize; i++)
                if ((tblp = otblp[i]) != NULL) {
                        if ((tblp->flag & DEFINED)) {
                                /* search for free hash table slot */
-                               j = (perturb = tblp->ua.hval) & nsize;
+                               j = (perturb = tblp->ua.hval) & mask;
                                goto find_first_empty_slot;
  find_next_empty_slot:
                                j = (j << 2) + j + perturb + 1;
                                perturb >>= PERTURB_SHIFT;
  find_first_empty_slot:
-                               pp = &ntblp[j & nsize];
+                               pp = &ntblp[j & mask];
                                if (*pp != NULL)
                                        goto find_next_empty_slot;
                                /* found an empty hash table slot */
@@ -1345,23 +1534,23 @@ texpand(struct table *tp, size_t nsize)
 }
 
 void
-ktinit(struct table *tp, Area *ap, size_t tsize)
+ktinit(Area *ap, struct table *tp, uint8_t initshift)
 {
        tp->areap = ap;
        tp->tbls = NULL;
-       tp->size = tp->nfree = 0;
-       if (tsize)
-               texpand(tp, tsize);
+       tp->tshift = ((initshift > INIT_TBLSHIFT) ?
+           initshift : INIT_TBLSHIFT) - 1;
+       tgrow(tp);
 }
 
 /* table, name (key) to search for, hash(name), rv pointer to tbl ptr */
-static struct tbl *
+struct tbl *
 ktscan(struct table *tp, const char *name, uint32_t h, struct tbl ***ppp)
 {
        size_t j, perturb, mask;
        struct tbl **pp, *p;
 
-       mask = tp->size - 1;
+       mask = ((size_t)1 << (tp->tshift)) - 1;
        /* search for hash table slot matching name */
        j = (perturb = h) & mask;
        goto find_first_slot;
@@ -1379,35 +1568,27 @@ ktscan(struct table *tp, const char *name, uint32_t h, struct tbl ***ppp)
        return (p);
 }
 
-/* table, name (key) to search for, hash(n) */
-struct tbl *
-ktsearch(struct table *tp, const char *n, uint32_t h)
-{
-       return (tp->size ? ktscan(tp, n, h, NULL) : NULL);
-}
-
 /* table, name (key) to enter, hash(n) */
 struct tbl *
 ktenter(struct table *tp, const char *n, uint32_t h)
 {
        struct tbl **pp, *p;
-       int len;
+       size_t len;
 
-       if (tp->size == 0)
-               texpand(tp, INIT_TBLS);
  Search:
        if ((p = ktscan(tp, n, h, &pp)))
                return (p);
 
-       if (tp->nfree <= 0) {
+       if (tp->nfree == 0) {
                /* too full */
-               texpand(tp, 2 * tp->size);
+               tgrow(tp);
                goto Search;
        }
 
        /* create new tbl entry */
-       len = strlen(n) + 1;
-       p = alloc(offsetof(struct tbl, name[0]) + len, tp->areap);
+       len = strlen(n);
+       checkoktoadd(len, offsetof(struct tbl, name[0]) + 1);
+       p = alloc(offsetof(struct tbl, name[0]) + ++len, tp->areap);
        p->flag = 0;
        p->type = 0;
        p->areap = tp->areap;
@@ -1425,7 +1606,7 @@ ktenter(struct table *tp, const char *n, uint32_t h)
 void
 ktwalk(struct tstate *ts, struct table *tp)
 {
-       ts->left = tp->size;
+       ts->left = (size_t)1 << (tp->tshift);
        ts->next = tp->tbls;
 }
 
@@ -1455,15 +1636,19 @@ ktsort(struct table *tp)
        size_t i;
        struct tbl **p, **sp, **dp;
 
-       p = alloc((tp->size + 1) * sizeof(struct tbl *), ATEMP);
+       /*
+        * since the table is never entirely full, no need to reserve
+        * additional space for the trailing NULL appended below
+        */
+       i = (size_t)1 << (tp->tshift);
+       p = alloc2(i, sizeof(struct tbl *), ATEMP);
        sp = tp->tbls;          /* source */
        dp = p;                 /* dest */
-       i = (size_t)tp->size;
        while (i--)
                if ((*dp = *sp++) != NULL && (((*dp)->flag & DEFINED) ||
                    ((*dp)->flag & ARRAY)))
                        dp++;
-       qsort(p, (i = dp - p), sizeof(void *), tnamecmp);
+       qsort(p, (i = dp - p), sizeof(struct tbl *), tnamecmp);
        p[i] = NULL;
        return (p);
 }
index 75a4de1..4adb7f2 100644 (file)
@@ -2,7 +2,7 @@
 /*     $OpenBSD: path.c,v 1.12 2005/03/30 17:16:37 deraadt Exp $       */
 
 /*-
- * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
  *     Thorsten Glaser <tg@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
 #include <grp.h>
 #endif
 
-__RCSID("$MirOS: src/bin/mksh/misc.c,v 1.141 2010/07/17 22:09:36 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/misc.c,v 1.172 2011/09/07 15:24:18 tg Exp $");
 
-unsigned char chtypes[UCHAR_MAX + 1];  /* type bits for unsigned char */
-
-#if !HAVE_SETRESUGID
-uid_t kshuid;
-gid_t kshgid, kshegid;
-#endif
+/* type bits for unsigned char */
+unsigned char chtypes[UCHAR_MAX + 1];
 
+static const unsigned char *pat_scan(const unsigned char *,
+    const unsigned char *, bool);
 static int do_gmatch(const unsigned char *, const unsigned char *,
     const unsigned char *, const unsigned char *);
 static const unsigned char *cclass(const unsigned char *, int);
@@ -45,6 +43,20 @@ static const unsigned char *cclass(const unsigned char *, int);
 static void chvt(const char *);
 #endif
 
+/*XXX this should go away */
+static int make_path(const char *, const char *, char **, XString *, int *);
+
+#ifdef SETUID_CAN_FAIL_WITH_EAGAIN
+/* we don't need to check for other codes, EPERM won't happen */
+#define DO_SETUID(func, argvec) do {                                   \
+       if ((func argvec) && errno == EAGAIN)                           \
+               errorf("%s failed with EAGAIN, probably due to a"       \
+                   " too low process limit; aborting", #func);         \
+} while (/* CONSTCOND */ 0)
+#else
+#define DO_SETUID(func, argvec) func argvec
+#endif
+
 /*
  * Fast character classes
  */
@@ -56,7 +68,8 @@ setctypes(const char *s, int t)
        if (t & C_IFS) {
                for (i = 0; i < UCHAR_MAX + 1; i++)
                        chtypes[i] &= ~C_IFS;
-               chtypes[0] |= C_IFS; /* include \0 in C_IFS */
+               /* include \0 in C_IFS */
+               chtypes[0] |= C_IFS;
        }
        while (*s != 0)
                chtypes[(unsigned char)*s++] |= t;
@@ -73,7 +86,8 @@ initctypes(void)
                chtypes[c] |= C_ALPHA;
        chtypes['_'] |= C_ALPHA;
        setctypes("0123456789", C_DIGIT);
-       setctypes(" \t\n|&;<>()", C_LEX1); /* \0 added automatically */
+       /* \0 added automatically */
+       setctypes(" \t\n|&;<>()", C_LEX1);
        setctypes("*@#!$-?", C_VAR1);
        setctypes(" \t\n", C_IFSWS);
        setctypes("=-+?", C_SUBOP1);
@@ -82,12 +96,15 @@ initctypes(void)
 
 /* called from XcheckN() to grow buffer */
 char *
-Xcheck_grow_(XString *xsp, const char *xp, unsigned int more)
+Xcheck_grow_(XString *xsp, const char *xp, size_t more)
 {
        const char *old_beg = xsp->beg;
 
-       xsp->len += more > xsp->len ? more : xsp->len;
-       xsp->beg = aresize(xsp->beg, xsp->len + 8, xsp->areap);
+       if (more < xsp->len)
+               more = xsp->len;
+       /* (xsp->len + X_EXTRA) never overflows */
+       checkoktoadd(more, xsp->len + X_EXTRA);
+       xsp->beg = aresize(xsp->beg, (xsp->len += more) + X_EXTRA, xsp->areap);
        xsp->end = xsp->beg + xsp->len;
        return (xsp->beg + (xp - old_beg));
 }
@@ -124,12 +141,12 @@ struct options_info {
        int opts[NELEM(options)];
 };
 
-static char *options_fmt_entry(char *, int, int, const void *);
+static char *options_fmt_entry(char *, size_t, int, const void *);
 static void printoptions(bool);
 
 /* format a single select menu item */
 static char *
-options_fmt_entry(char *buf, int buflen, int i, const void *arg)
+options_fmt_entry(char *buf, size_t buflen, int i, const void *arg)
 {
        const struct options_info *oi = (const struct options_info *)arg;
 
@@ -142,17 +159,17 @@ options_fmt_entry(char *buf, int buflen, int i, const void *arg)
 static void
 printoptions(bool verbose)
 {
-       int i = 0;
+       size_t i = 0;
 
        if (verbose) {
-               int n = 0, len, octs = 0;
+               ssize_t n = 0, len, octs = 0;
                struct options_info oi;
 
                /* verbose version */
                shf_puts("Current option settings\n", shl_stdout);
 
                oi.opt_width = 0;
-               while (i < (int)NELEM(options)) {
+               while (i < NELEM(options)) {
                        if (options[i].name) {
                                oi.opts[n++] = i;
                                len = strlen(options[i].name);
@@ -167,11 +184,12 @@ printoptions(bool verbose)
                print_columns(shl_stdout, n, options_fmt_entry, &oi,
                    octs + 4, oi.opt_width + 4, true);
        } else {
-               /* short version á la AT&T ksh93 */
-               shf_puts("set", shl_stdout);
+               /* short version like AT&T ksh93 */
+               shf_puts(Tset, shl_stdout);
                while (i < (int)NELEM(options)) {
                        if (Flag(i) && options[i].name)
-                               shprintf(" -o %s", options[i].name);
+                               shprintf("%s %s %s", null, "-o",
+                                   options[i].name);
                        ++i;
                }
                shf_putc('\n', shl_stdout);
@@ -181,8 +199,8 @@ printoptions(bool verbose)
 char *
 getoptions(void)
 {
-       unsigned int i;
-       char m[(int) FNFLAGS + 1];
+       size_t i;
+       char m[(int)FNFLAGS + 1];
        char *cp = m;
 
        for (i = 0; i < NELEM(options); i++)
@@ -199,7 +217,8 @@ change_flag(enum sh_flag f, int what, unsigned int newval)
        unsigned char oldval;
 
        oldval = Flag(f);
-       Flag(f) = newval ? 1 : 0;       /* needed for tristates */
+       /* needed for tristates */
+       Flag(f) = newval ? 1 : 0;
 #ifndef MKSH_UNEMPLOYED
        if (f == FMONITOR) {
                if (what != OF_CMDLINE && newval != oldval)
@@ -218,18 +237,21 @@ change_flag(enum sh_flag f, int what, unsigned int newval)
                Flag(f) = (unsigned char)newval;
        } else if (f == FPRIVILEGED && oldval && !newval) {
                /* Turning off -p? */
-#if HAVE_SETRESUGID
-               gid_t kshegid = getgid();
 
-               setresgid(kshegid, kshegid, kshegid);
+               /*XXX this can probably be optimised */
+               kshegid = kshgid = getgid();
+#if HAVE_SETRESUGID
+               DO_SETUID(setresgid, (kshegid, kshegid, kshegid));
 #if HAVE_SETGROUPS
+               /* setgroups doesn't EAGAIN on Linux */
                setgroups(1, &kshegid);
 #endif
-               setresuid(ksheuid, ksheuid, ksheuid);
+               DO_SETUID(setresuid, (ksheuid, ksheuid, ksheuid));
 #else
+               /* seteuid, setegid, setgid don't EAGAIN on Linux */
                seteuid(ksheuid = kshuid = getuid());
-               setuid(ksheuid);
-               setegid(kshegid = kshgid = getgid());
+               DO_SETUID(setuid, (ksheuid));
+               setegid(kshegid);
                setgid(kshegid);
 #endif
        } else if ((f == FPOSIX || f == FSH) && newval) {
@@ -243,12 +265,14 @@ change_flag(enum sh_flag f, int what, unsigned int newval)
        }
 }
 
-/* Parse command line & set command arguments. Returns the index of
+/*
+ * Parse command line and set command arguments. Returns the index of
  * non-option arguments, -1 if there is an error.
  */
 int
 parse_args(const char **argv,
-    int what,                  /* OF_CMDLINE or OF_SET */
+    /* OF_CMDLINE or OF_SET */
+    int what,
     bool *setargsp)
 {
        static char cmd_opts[NELEM(options) + 5]; /* o:T:\0 */
@@ -257,7 +281,8 @@ parse_args(const char **argv,
        const char *array = NULL;
        Getopt go;
        size_t i;
-       int optc, sortargs = 0, arrayset = 0;
+       int optc, arrayset = 0;
+       bool sortargs = false;
 
        /* First call? Build option strings... */
        if (cmd_opts[0] == '\0') {
@@ -291,7 +316,8 @@ parse_args(const char **argv,
 
        if (what == OF_CMDLINE) {
                const char *p = argv[0], *q;
-               /* Set FLOGIN before parsing options so user can clear
+               /*
+                * Set FLOGIN before parsing options so user can clear
                 * flag using +l.
                 */
                if (*p != '-')
@@ -319,7 +345,8 @@ parse_args(const char **argv,
                        if (what == OF_FIRSTTIME)
                                break;
                        if (go.optarg == NULL) {
-                               /* lone -o: print options
+                               /*
+                                * lone -o: print options
                                 *
                                 * Note that on the command line, -o requires
                                 * an option (ie, can't get here if what is
@@ -329,14 +356,9 @@ parse_args(const char **argv,
                                break;
                        }
                        i = option(go.optarg);
-                       if ((enum sh_flag)i == FARC4RANDOM) {
-                               warningf(true, "Do not use set ±o arc4random,"
-                                   " it will be removed in the next version"
-                                   " of mksh!");
-                               return (0);
-                       }
                        if ((i != (size_t)-1) && set == Flag(i))
-                               /* Don't check the context if the flag
+                               /*
+                                * Don't check the context if the flag
                                 * isn't changing - makes "set -o interactive"
                                 * work if you're already interactive. Needed
                                 * if the output of "set +o" is to be used.
@@ -345,7 +367,7 @@ parse_args(const char **argv,
                        else if ((i != (size_t)-1) && (options[i].flags & what))
                                change_flag((enum sh_flag)i, what, set);
                        else {
-                               bi_errorf("%s: bad option", go.optarg);
+                               bi_errorf("%s: %s", go.optarg, "bad option");
                                return (-1);
                        }
                        break;
@@ -371,7 +393,7 @@ parse_args(const char **argv,
                                break;
                        /* -s: sort positional params (AT&T ksh stupidity) */
                        if (what == OF_SET && optc == 's') {
-                               sortargs = 1;
+                               sortargs = true;
                                break;
                        }
                        for (i = 0; i < NELEM(options); i++)
@@ -398,9 +420,15 @@ parse_args(const char **argv,
                *setargsp = !arrayset && ((go.info & GI_MINUSMINUS) ||
                    argv[go.optind]);
 
-       if (arrayset && (!*array || *skip_varname(array, false))) {
-               bi_errorf("%s: is not an identifier", array);
-               return (-1);
+       if (arrayset) {
+               const char *ccp = NULL;
+
+               if (*array)
+                       ccp = skip_varname(array, false);
+               if (!ccp || !(!ccp[0] || (ccp[0] == '+' && !ccp[1]))) {
+                       bi_errorf("%s: %s", array, "is not an identifier");
+                       return (-1);
+               }
        }
        if (sortargs) {
                for (i = go.optind; argv[i]; i++)
@@ -409,7 +437,7 @@ parse_args(const char **argv,
                    xstrcmp);
        }
        if (arrayset)
-               go.optind += set_array(array, arrayset > 0 ? true : false,
+               go.optind += set_array(array, tobool(arrayset > 0),
                    argv + go.optind);
 
        return (go.optind);
@@ -456,10 +484,83 @@ bi_getn(const char *as, int *ai)
        int rv;
 
        if (!(rv = getn(as, ai)))
-               bi_errorf("%s: bad number", as);
+               bi_errorf("%s: %s", as, "bad number");
        return (rv);
 }
 
+/**
+ * pattern simplifications:
+ * - @(x) -> x (not @(x|y) though)
+ * - ** -> *
+ */
+static void *
+simplify_gmatch_pattern(const unsigned char *sp)
+{
+       uint8_t c;
+       unsigned char *cp, *dp;
+       const unsigned char *ps, *se;
+
+       cp = alloc(strlen((const void *)sp) + 1, ATEMP);
+       goto simplify_gmatch_pat1a;
+
+       /* foo@(b@(a)r)b@(a|a)z -> foobarb@(a|a)z */
+ simplify_gmatch_pat1:
+       sp = cp;
+ simplify_gmatch_pat1a:
+       dp = cp;
+       se = sp + strlen((const void *)sp);
+       while ((c = *sp++)) {
+               if (!ISMAGIC(c)) {
+                       *dp++ = c;
+                       continue;
+               }
+               switch ((c = *sp++)) {
+               case 0x80|'@':
+               /* simile for @ */
+               case 0x80|' ':
+                       /* check whether it has only one clause */
+                       ps = pat_scan(sp, se, true);
+                       if (!ps || ps[-1] != /*(*/ ')')
+                               /* nope */
+                               break;
+                       /* copy inner clause until matching close */
+                       ps -= 2;
+                       while ((const unsigned char *)sp < ps)
+                               *dp++ = *sp++;
+                       /* skip MAGIC and closing parenthesis */
+                       sp += 2;
+                       /* copy the rest of the pattern */
+                       memmove(dp, sp, strlen((const void *)sp) + 1);
+                       /* redo from start */
+                       goto simplify_gmatch_pat1;
+               }
+               *dp++ = MAGIC;
+               *dp++ = c;
+       }
+       *dp = '\0';
+
+       /* collapse adjacent asterisk wildcards */
+       sp = dp = cp;
+       while ((c = *sp++)) {
+               if (!ISMAGIC(c)) {
+                       *dp++ = c;
+                       continue;
+               }
+               switch ((c = *sp++)) {
+               case '*':
+                       while (ISMAGIC(sp[0]) && sp[1] == c)
+                               sp += 2;
+                       break;
+               }
+               *dp++ = MAGIC;
+               *dp++ = c;
+       }
+       *dp = '\0';
+
+       /* return the result, allocated from ATEMP */
+       return (cp);
+}
+
 /* -------- gmatch.c -------- */
 
 /*
@@ -469,18 +570,20 @@ bi_getn(const char *as, int *ai)
  * Match a pattern as in sh(1).
  * pattern character are prefixed with MAGIC by expand.
  */
-
 int
 gmatchx(const char *s, const char *p, bool isfile)
 {
        const char *se, *pe;
+       char *pnew;
+       int rv;
 
        if (s == NULL || p == NULL)
                return (0);
 
        se = s + strlen(s);
        pe = p + strlen(p);
-       /* isfile is false iff no syntax check has been done on
+       /*
+        * isfile is false iff no syntax check has been done on
         * the pattern. If check fails, just to a strcmp().
         */
        if (!isfile && !has_globbing(p, pe)) {
@@ -490,11 +593,22 @@ gmatchx(const char *s, const char *p, bool isfile)
                debunk(t, p, len);
                return (!strcmp(t, s));
        }
-       return (do_gmatch((const unsigned char *) s, (const unsigned char *) se,
-           (const unsigned char *) p, (const unsigned char *) pe));
+
+       /*
+        * since the do_gmatch() engine sucks so much, we must do some
+        * pattern simplifications
+        */
+       pnew = simplify_gmatch_pattern((const unsigned char *)p);
+       pe = pnew + strlen(pnew);
+
+       rv = do_gmatch((const unsigned char *)s, (const unsigned char *)se,
+           (const unsigned char *)pnew, (const unsigned char *)pe);
+       afree(pnew, ATEMP);
+       return (rv);
 }
 
-/* Returns if p is a syntacticly correct globbing pattern, false
+/**
+ * Returns if p is a syntacticly correct globbing pattern, false
  * if it contains no pattern characters or if there is a syntax error.
  * Syntax errors are:
  *     - [ with no closing ]
@@ -502,14 +616,14 @@ gmatchx(const char *s, const char *p, bool isfile)
  *     - [...] and *(...) not nested (eg, [a$(b|]c), *(a[b|c]d))
  */
 /*XXX
-- if no magic,
      if dest given, copy to dst
      return ?
-- if magic && (no globbing || syntax error)
      debunk to dst
      return ?
-- return ?
-*/
+ * - if no magic,
*     if dest given, copy to dst
*     return ?
+ * - if magic && (no globbing || syntax error)
*     debunk to dst
*     return ?
+ * - return ?
+ */
 int
 has_globbing(const char *xp, const char *xpe)
 {
@@ -517,42 +631,46 @@ has_globbing(const char *xp, const char *xpe)
        const unsigned char *pe = (const unsigned char *) xpe;
        int c;
        int nest = 0, bnest = 0;
-       int saw_glob = 0;
-       int in_bracket = 0; /* inside [...] */
+       bool saw_glob = false;
+       /* inside [...] */
+       bool in_bracket = false;
 
        for (; p < pe; p++) {
                if (!ISMAGIC(*p))
                        continue;
                if ((c = *++p) == '*' || c == '?')
-                       saw_glob = 1;
+                       saw_glob = true;
                else if (c == '[') {
                        if (!in_bracket) {
-                               saw_glob = 1;
-                               in_bracket = 1;
+                               saw_glob = true;
+                               in_bracket = true;
                                if (ISMAGIC(p[1]) && p[2] == NOT)
                                        p += 2;
                                if (ISMAGIC(p[1]) && p[2] == ']')
                                        p += 2;
                        }
-                       /* XXX Do we need to check ranges here? POSIX Q */
+                       /*XXX Do we need to check ranges here? POSIX Q */
                } else if (c == ']') {
                        if (in_bracket) {
-                               if (bnest)              /* [a*(b]) */
+                               if (bnest)
+                                       /* [a*(b]) */
                                        return (0);
-                               in_bracket = 0;
+                               in_bracket = false;
                        }
                } else if ((c & 0x80) && vstrchr("*+?@! ", c & 0x7f)) {
-                       saw_glob = 1;
+                       saw_glob = true;
                        if (in_bracket)
                                bnest++;
                        else
                                nest++;
                } else if (c == '|') {
-                       if (in_bracket && !bnest)       /* *(a[foo|bar]) */
+                       if (in_bracket && !bnest)
+                               /* *(a[foo|bar]) */
                                return (0);
                } else if (c == /*(*/ ')') {
                        if (in_bracket) {
-                               if (!bnest--)           /* *(a[b)c] */
+                               if (!bnest--)
+                                       /* *(a[b)c] */
                                        return (0);
                        } else if (nest)
                                nest--;
@@ -614,9 +732,12 @@ do_gmatch(const unsigned char *s, const unsigned char *se,
                 * [*+?@!](pattern|pattern|..)
                 * This is also needed for ${..%..}, etc.
                 */
-               case 0x80|'+': /* matches one or more times */
-               case 0x80|'*': /* matches zero or more times */
-                       if (!(prest = pat_scan(p, pe, 0)))
+
+               /* matches one or more times */
+               case 0x80|'+':
+               /* matches zero or more times */
+               case 0x80|'*':
+                       if (!(prest = pat_scan(p, pe, false)))
                                return (0);
                        s--;
                        /* take care of zero matches */
@@ -624,7 +745,7 @@ do_gmatch(const unsigned char *s, const unsigned char *se,
                            do_gmatch(s, se, prest, pe))
                                return (1);
                        for (psub = p; ; psub = pnext) {
-                               pnext = pat_scan(psub, pe, 1);
+                               pnext = pat_scan(psub, pe, true);
                                for (srest = s; srest <= se; srest++) {
                                        if (do_gmatch(s, srest, psub, pnext - 2) &&
                                            (do_gmatch(srest, se, prest, pe) ||
@@ -637,10 +758,13 @@ do_gmatch(const unsigned char *s, const unsigned char *se,
                        }
                        return (0);
 
-               case 0x80|'?': /* matches zero or once */
-               case 0x80|'@': /* matches one of the patterns */
-               case 0x80|' ': /* simile for @ */
-                       if (!(prest = pat_scan(p, pe, 0)))
+               /* matches zero or once */
+               case 0x80|'?':
+               /* matches one of the patterns */
+               case 0x80|'@':
+               /* simile for @ */
+               case 0x80|' ':
+                       if (!(prest = pat_scan(p, pe, false)))
                                return (0);
                        s--;
                        /* Take care of zero matches */
@@ -648,7 +772,7 @@ do_gmatch(const unsigned char *s, const unsigned char *se,
                            do_gmatch(s, se, prest, pe))
                                return (1);
                        for (psub = p; ; psub = pnext) {
-                               pnext = pat_scan(psub, pe, 1);
+                               pnext = pat_scan(psub, pe, true);
                                srest = prest == pe ? se : s;
                                for (; srest <= se; srest++) {
                                        if (do_gmatch(s, srest, psub, pnext - 2) &&
@@ -660,15 +784,16 @@ do_gmatch(const unsigned char *s, const unsigned char *se,
                        }
                        return (0);
 
-               case 0x80|'!': /* matches none of the patterns */
-                       if (!(prest = pat_scan(p, pe, 0)))
+               /* matches none of the patterns */
+               case 0x80|'!':
+                       if (!(prest = pat_scan(p, pe, false)))
                                return (0);
                        s--;
                        for (srest = s; srest <= se; srest++) {
                                int matched = 0;
 
                                for (psub = p; ; psub = pnext) {
-                                       pnext = pat_scan(psub, pe, 1);
+                                       pnext = pat_scan(psub, pe, true);
                                        if (do_gmatch(s, srest, psub,
                                            pnext - 2)) {
                                                matched = 1;
@@ -705,9 +830,11 @@ cclass(const unsigned char *p, int sub)
                if (ISMAGIC(c)) {
                        c = *p++;
                        if ((c & 0x80) && !ISMAGIC(c)) {
-                               c &= 0x7f;/* extended pattern matching: *+?@! */
+                               /* extended pattern matching: *+?@! */
+                               c &= 0x7F;
                                /* XXX the ( char isn't handled as part of [] */
-                               if (c == ' ') /* simile for @: plain (..) */
+                               if (c == ' ')
+                                       /* simile for @: plain (..) */
                                        c = '(' /*)*/;
                        }
                }
@@ -716,7 +843,8 @@ cclass(const unsigned char *p, int sub)
                        return (sub == '[' ? orig_p : NULL);
                if (ISMAGIC(p[0]) && p[1] == '-' &&
                    (!ISMAGIC(p[2]) || p[3] != ']')) {
-                       p += 2; /* MAGIC- */
+                       /* MAGIC- */
+                       p += 2;
                        d = *p++;
                        if (ISMAGIC(d)) {
                                d = *p++;
@@ -736,8 +864,8 @@ cclass(const unsigned char *p, int sub)
 }
 
 /* Look for next ) or | (if match_sep) in *(foo|bar) pattern */
-const unsigned char *
-pat_scan(const unsigned char *p, const unsigned char *pe, int match_sep)
+static const unsigned char *
+pat_scan(const unsigned char *p, const unsigned char *pe, bool match_sep)
 {
        int nest = 0;
 
@@ -772,7 +900,8 @@ ksh_getopt_reset(Getopt *go, int flags)
 }
 
 
-/* getopt() used for shell built-in commands, the getopts command, and
+/**
+ * getopt() used for shell built-in commands, the getopts command, and
  * command line options.
  * A leading ':' in options means don't print errors, instead return '?'
  * or ':' and set go->optarg to the offending option character.
@@ -813,7 +942,8 @@ ksh_getopt(const char **argv, Getopt *go, const char *optionsp)
                        return (-1);
                }
                if (arg == NULL ||
-                   ((flag != '-' ) && /* neither a - nor a + (if + allowed) */
+                   ((flag != '-' ) &&
+                   /* neither a - nor a + (if + allowed) */
                    (!(go->flags & GF_PLUSOPT) || flag != '+')) ||
                    (c = arg[1]) == '\0') {
                        go->p = 0;
@@ -830,15 +960,17 @@ ksh_getopt(const char **argv, Getopt *go, const char *optionsp)
                        go->buf[0] = c;
                        go->optarg = go->buf;
                } else {
-                       warningf(true, "%s%s-%c: unknown option",
+                       warningf(true, "%s%s-%c: %s",
                            (go->flags & GF_NONAME) ? "" : argv[0],
-                           (go->flags & GF_NONAME) ? "" : ": ", c);
+                           (go->flags & GF_NONAME) ? "" : ": ", c,
+                           "unknown option");
                        if (go->flags & GF_ERROR)
                                bi_errorfz();
                }
                return ('?');
        }
-       /* : means argument must be present, may be part of option argument
+       /**
+        * : means argument must be present, may be part of option argument
         *   or the next argument
         * ; same as : but argument may be missing
         * , means argument is part of option argument, and may be null.
@@ -856,9 +988,10 @@ ksh_getopt(const char **argv, Getopt *go, const char *optionsp)
                                go->optarg = go->buf;
                                return (':');
                        }
-                       warningf(true, "%s%s-'%c' requires argument",
+                       warningf(true, "%s%s-%c: %s",
                            (go->flags & GF_NONAME) ? "" : argv[0],
-                           (go->flags & GF_NONAME) ? "" : ": ", c);
+                           (go->flags & GF_NONAME) ? "" : ": ", c,
+                           "requires an argument");
                        if (go->flags & GF_ERROR)
                                bi_errorfz();
                        return ('?');
@@ -869,7 +1002,8 @@ ksh_getopt(const char **argv, Getopt *go, const char *optionsp)
                go->optarg = argv[go->optind - 1] + go->p;
                go->p = 0;
        } else if (*o == '#') {
-               /* argument is optional and may be attached or unattached
+               /*
+                * argument is optional and may be attached or unattached
                 * but must start with a digit. optarg is set to 0 if the
                 * argument is missing.
                 */
@@ -890,7 +1024,8 @@ ksh_getopt(const char **argv, Getopt *go, const char *optionsp)
        return (c);
 }
 
-/* print variable/alias value using necessary quotes
+/*
+ * print variable/alias value using necessary quotes
  * (POSIX says they should be suitable for re-entry...)
  * No trailing newline is printed.
  */
@@ -898,25 +1033,33 @@ void
 print_value_quoted(const char *s)
 {
        const char *p;
-       int inquote = 0;
+       bool inquote = false;
 
-       /* Test if any quotes are needed */
+       /* first, check whether any quotes are needed */
        for (p = s; *p; p++)
                if (ctype(*p, C_QUOTE))
                        break;
        if (!*p) {
+               /* nope, use the shortcut */
                shf_puts(s, shl_stdout);
                return;
        }
+
+       /* quote via state machine */
        for (p = s; *p; p++) {
                if (*p == '\'') {
-                       if (inquote)
+                       /*
+                        * multiple '''s or any ' at beginning of string
+                        * look nicer this way than when simply substituting
+                        */
+                       if (inquote) {
                                shf_putc('\'', shl_stdout);
+                               inquote = false;
+                       }
                        shf_putc('\\', shl_stdout);
-                       inquote = 0;
                } else if (!inquote) {
                        shf_putc('\'', shl_stdout);
-                       inquote = 1;
+                       inquote = true;
                }
                shf_putc(*p, shl_stdout);
        }
@@ -930,10 +1073,10 @@ print_value_quoted(const char *s)
  */
 void
 print_columns(struct shf *shf, int n,
-    char *(*func)(char *, int, int, const void *),
-    const void *arg, int max_oct, int max_col, bool prefcol)
+    char *(*func)(char *, size_t, int, const void *),
+    const void *arg, size_t max_oct, size_t max_colz, bool prefcol)
 {
-       int i, r, c, rows, cols, nspace;
+       int i, r, c, rows, cols, nspace, max_col;
        char *str;
 
        if (n <= 0) {
@@ -943,6 +1086,15 @@ print_columns(struct shf *shf, int n,
                return;
        }
 
+       if (max_colz > 2147483647) {
+#ifndef MKSH_SMALL
+               internal_warningf("print_columns called with max_col=%zu > INT_MAX",
+                   max_colz);
+#endif
+               return;
+       }
+       max_col = (int)max_colz;
+
        ++max_oct;
        str = alloc(max_oct, ATEMP);
 
@@ -998,8 +1150,9 @@ strip_nuls(char *buf, int nbytes)
 {
        char *dst;
 
-       /* nbytes check because some systems (older FreeBSDs) have a buggy
-        * memchr()
+       /*
+        * nbytes check because some systems (older FreeBSDs) have a
+        * buggy memchr()
         */
        if (nbytes && (dst = memchr(buf, '\0', nbytes))) {
                char *end = buf + nbytes;
@@ -1019,19 +1172,20 @@ strip_nuls(char *buf, int nbytes)
        }
 }
 
-/* Like read(2), but if read fails due to non-blocking flag, resets flag
- * and restarts read.
+/*
+ * Like read(2), but if read fails due to non-blocking flag,
+ * resets flag and restarts read.
  */
-int
-blocking_read(int fd, char *buf, int nbytes)
+ssize_t
+blocking_read(int fd, char *buf, size_t nbytes)
 {
-       int ret;
-       int tried_reset = 0;
+       ssize_t ret;
+       bool tried_reset = false;
 
        while ((ret = read(fd, buf, nbytes)) < 0) {
                if (!tried_reset && errno == EAGAIN) {
                        if (reset_nonblock(fd) > 0) {
-                               tried_reset = 1;
+                               tried_reset = true;
                                continue;
                        }
                        errno = EAGAIN;
@@ -1041,7 +1195,8 @@ blocking_read(int fd, char *buf, int nbytes)
        return (ret);
 }
 
-/* Reset the non-blocking flag on the specified file descriptor.
+/*
+ * Reset the non-blocking flag on the specified file descriptor.
  * Returns -1 if there was an error, 0 if non-blocking wasn't set,
  * 1 if it was.
  */
@@ -1060,34 +1215,225 @@ reset_nonblock(int fd)
        return (1);
 }
 
-
-/* Like getcwd(), except bsize is ignored if buf is 0 (PATH_MAX is used) */
+/* getcwd(3) equivalent, allocates from ATEMP but doesn't resize */
 char *
-ksh_get_wd(size_t *dlen)
+ksh_get_wd(void)
 {
-       char *ret, *b;
-       size_t len = 1;
-
 #ifdef NO_PATH_MAX
-       if ((b = get_current_dir_name())) {
-               len = strlen(b) + 1;
-               strndupx(ret, b, len - 1, ATEMP);
-               free(b);
+       char *rv, *cp;
+
+       if ((cp = get_current_dir_name())) {
+               strdupx(rv, cp, ATEMP);
+               free_gnu_gcdn(cp);
        } else
-               ret = NULL;
+               rv = NULL;
 #else
-       if ((ret = getcwd((b = alloc(PATH_MAX + 1, ATEMP)), PATH_MAX)))
-               ret = aresize(b, len = (strlen(b) + 1), ATEMP);
-       else
-               afree(b, ATEMP);
+       char *rv;
+
+       if (!getcwd((rv = alloc(PATH_MAX + 1, ATEMP)), PATH_MAX)) {
+               afree(rv, ATEMP);
+               rv = NULL;
+       }
 #endif
 
-       if (dlen)
-               *dlen = len;
-       return (ret);
+       return (rv);
 }
 
-/*
+char *
+do_realpath(const char *upath)
+{
+       char *xp, *ip, *tp, *ipath, *ldest = NULL;
+       XString xs;
+       ptrdiff_t pos;
+       size_t len;
+       int llen;
+       struct stat sb;
+#ifdef NO_PATH_MAX
+       size_t ldestlen = 0;
+#define pathlen sb.st_size
+#define pathcnd (ldestlen < (pathlen + 1))
+#else
+#define pathlen PATH_MAX
+#define pathcnd (!ldest)
+#endif
+       /* max. recursion depth */
+       int symlinks = 32;
+
+       if (upath[0] == '/') {
+               /* upath is an absolute pathname */
+               strdupx(ipath, upath, ATEMP);
+       } else {
+               /* upath is a relative pathname, prepend cwd */
+               if ((tp = ksh_get_wd()) == NULL || tp[0] != '/')
+                       return (NULL);
+               ipath = shf_smprintf("%s%s%s", tp, "/", upath);
+               afree(tp, ATEMP);
+       }
+
+       /* ipath and upath are in memory at the same time -> unchecked */
+       Xinit(xs, xp, strlen(ip = ipath) + 1, ATEMP);
+
+       /* now jump into the deep of the loop */
+       goto beginning_of_a_pathname;
+
+       while (*ip) {
+               /* skip slashes in input */
+               while (*ip == '/')
+                       ++ip;
+               if (!*ip)
+                       break;
+
+               /* get next pathname component from input */
+               tp = ip;
+               while (*ip && *ip != '/')
+                       ++ip;
+               len = ip - tp;
+
+               /* check input for "." and ".." */
+               if (tp[0] == '.') {
+                       if (len == 1)
+                               /* just continue with the next one */
+                               continue;
+                       else if (len == 2 && tp[1] == '.') {
+                               /* strip off last pathname component */
+                               while (xp > Xstring(xs, xp))
+                                       if (*--xp == '/')
+                                               break;
+                               /* then continue with the next one */
+                               continue;
+                       }
+               }
+
+               /* store output position away, then append slash to output */
+               pos = Xsavepos(xs, xp);
+               /* 1 for the '/' and len + 1 for tp and the NUL from below */
+               XcheckN(xs, xp, 1 + len + 1);
+               Xput(xs, xp, '/');
+
+               /* append next pathname component to output */
+               memcpy(xp, tp, len);
+               xp += len;
+               *xp = '\0';
+
+               /* lstat the current output, see if it's a symlink */
+               if (lstat(Xstring(xs, xp), &sb)) {
+                       /* lstat failed */
+                       if (errno == ENOENT) {
+                               /* because the pathname does not exist */
+                               while (*ip == '/')
+                                       /* skip any trailing slashes */
+                                       ++ip;
+                               /* no more components left? */
+                               if (!*ip)
+                                       /* we can still return successfully */
+                                       break;
+                               /* more components left? fall through */
+                       }
+                       /* not ENOENT or not at the end of ipath */
+                       goto notfound;
+               }
+
+               /* check if we encountered a symlink? */
+               if (S_ISLNK(sb.st_mode)) {
+                       /* reached maximum recursion depth? */
+                       if (!symlinks--) {
+                               /* yep, prevent infinite loops */
+                               errno = ELOOP;
+                               goto notfound;
+                       }
+
+                       /* get symlink(7) target */
+                       if (pathcnd) {
+#ifdef NO_PATH_MAX
+                               if (notoktoadd(pathlen, 1)) {
+                                       errno = ENAMETOOLONG;
+                                       goto notfound;
+                               }
+#endif
+                               ldest = aresize(ldest, pathlen + 1, ATEMP);
+                       }
+                       llen = readlink(Xstring(xs, xp), ldest, pathlen);
+                       if (llen < 0)
+                               /* oops... */
+                               goto notfound;
+                       ldest[llen] = '\0';
+
+                       /*
+                        * restart if symlink target is an absolute path,
+                        * otherwise continue with currently resolved prefix
+                        */
+                       /* append rest of current input path to link target */
+                       tp = shf_smprintf("%s%s%s", ldest, *ip ? "/" : "", ip);
+                       afree(ipath, ATEMP);
+                       ip = ipath = tp;
+                       if (ldest[0] != '/') {
+                               /* symlink target is a relative path */
+                               xp = Xrestpos(xs, xp, pos);
+                       } else {
+                               /* symlink target is an absolute path */
+                               xp = Xstring(xs, xp);
+ beginning_of_a_pathname:
+                               /* assert: (ip == ipath)[0] == '/' */
+                               /* assert: xp == xs.beg => start of path */
+
+                               /* exactly two leading slashes? (SUSv4 3.266) */
+                               if (ip[1] == '/' && ip[2] != '/') {
+                                       /* keep them, e.g. for UNC pathnames */
+                                       Xput(xs, xp, '/');
+                               }
+                       }
+               }
+               /* otherwise (no symlink) merely go on */
+       }
+
+       /*
+        * either found the target and successfully resolved it,
+        * or found its parent directory and may create it
+        */
+       if (Xlength(xs, xp) == 0)
+               /*
+                * if the resolved pathname is "", make it "/",
+                * otherwise do not add a trailing slash
+                */
+               Xput(xs, xp, '/');
+       Xput(xs, xp, '\0');
+
+       /*
+        * if source path had a trailing slash, check if target path
+        * is not a non-directory existing file
+        */
+       if (ip > ipath && ip[-1] == '/') {
+               if (stat(Xstring(xs, xp), &sb)) {
+                       if (errno != ENOENT)
+                               goto notfound;
+               } else if (!S_ISDIR(sb.st_mode)) {
+                       errno = ENOTDIR;
+                       goto notfound;
+               }
+               /* target now either does not exist or is a directory */
+       }
+
+       /* return target path */
+       if (ldest != NULL)
+               afree(ldest, ATEMP);
+       afree(ipath, ATEMP);
+       return (Xclose(xs, xp));
+
+ notfound:
+       /* save; freeing memory might trash it */
+       llen = errno;
+       if (ldest != NULL)
+               afree(ldest, ATEMP);
+       afree(ipath, ATEMP);
+       Xfree(xs, xp);
+       errno = llen;
+       return (NULL);
+
+#undef pathlen
+#undef pathcnd
+}
+
+/**
  *     Makes a filename into result using the following algorithm.
  *     - make result NULL
  *     - if file starts with '/', append file to result & set cdpathp to NULL
@@ -1102,16 +1448,17 @@ ksh_get_wd(size_t *dlen)
  *     The return value indicates whether a non-null element from cdpathp
  *     was appended to result.
  */
-int
+static int
 make_path(const char *cwd, const char *file,
-    char **cdpathp,            /* & of : separated list */
+    /* pointer to colon-separated list */
+    char **cdpathp,
     XString *xsp,
     int *phys_pathp)
 {
        int rval = 0;
        bool use_cdpath = true;
        char *plist;
-       int len, plen = 0;
+       size_t len, plen = 0;
        char *xp = Xstring(*xsp, xp);
 
        if (!file)
@@ -1172,96 +1519,296 @@ make_path(const char *cwd, const char *file,
        return (rval);
 }
 
-/*
+/*-
  * Simplify pathnames containing "." and ".." entries.
- * ie, simplify_path("/a/b/c/./../d/..") returns "/a/b"
+ *
+ * simplify_path(this)                 = that
+ * /a/b/c/./../d/..                    /a/b
+ * //./C/foo/bar/../baz                        //C/foo/baz
+ * /foo/                               /foo
+ * /foo/../../bar                      /bar
+ * /foo/./blah/..                      /foo
+ * .                                   .
+ * ..                                  ..
+ * ./foo                               foo
+ * foo/../../../bar                    ../../bar
  */
 void
-simplify_path(char *pathl)
+simplify_path(char *p)
 {
-       char *cur, *t;
-       bool isrooted;
-       char *very_start = pathl, *start;
+       char *dp, *ip, *sp, *tp;
+       size_t len;
+       bool needslash;
 
-       if (!*pathl)
+       switch (*p) {
+       case 0:
                return;
+       case '/':
+               /* exactly two leading slashes? (SUSv4 3.266) */
+               if (p[1] == '/' && p[2] != '/')
+                       /* keep them, e.g. for UNC pathnames */
+                       ++p;
+               needslash = true;
+               break;
+       default:
+               needslash = false;
+       }
+       dp = ip = sp = p;
 
-       if ((isrooted = pathl[0] == '/'))
-               very_start++;
-
-       /* Before                       After
-        * /foo/                        /foo
-        * /foo/../../bar               /bar
-        * /foo/./blah/..               /foo
-        * .                            .
-        * ..                           ..
-        * ./foo                        foo
-        * foo/../../../bar             ../../bar
-        */
-
-       for (cur = t = start = very_start; ; ) {
-               /* treat multiple '/'s as one '/' */
-               while (*t == '/')
-                       t++;
-
-               if (*t == '\0') {
-                       if (cur == pathl)
-                               /* convert empty path to dot */
-                               *cur++ = '.';
-                       *cur = '\0';
+       while (*ip) {
+               /* skip slashes in input */
+               while (*ip == '/')
+                       ++ip;
+               if (!*ip)
                        break;
-               }
 
-               if (t[0] == '.') {
-                       if (!t[1] || t[1] == '/') {
-                               t += 1;
+               /* get next pathname component from input */
+               tp = ip;
+               while (*ip && *ip != '/')
+                       ++ip;
+               len = ip - tp;
+
+               /* check input for "." and ".." */
+               if (tp[0] == '.') {
+                       if (len == 1)
+                               /* just continue with the next one */
                                continue;
-                       } else if (t[1] == '.' && (!t[2] || t[2] == '/')) {
-                               if (!isrooted && cur == start) {
-                                       if (cur != very_start)
-                                               *cur++ = '/';
-                                       *cur++ = '.';
-                                       *cur++ = '.';
-                                       start = cur;
-                               } else if (cur != start)
-                                       while (--cur > start && *cur != '/')
-                                               ;
-                               t += 2;
+                       else if (len == 2 && tp[1] == '.') {
+                               /* parent level, but how? */
+                               if (*p == '/')
+                                       /* absolute path, only one way */
+                                       goto strip_last_component;
+                               else if (dp > sp) {
+                                       /* relative path, with subpaths */
+                                       needslash = false;
+ strip_last_component:
+                                       /* strip off last pathname component */
+                                       while (dp > sp)
+                                               if (*--dp == '/')
+                                                       break;
+                               } else {
+                                       /* relative path, at its beginning */
+                                       if (needslash)
+                                               /* or already dotdot-slash'd */
+                                               *dp++ = '/';
+                                       /* keep dotdot-slash if not absolute */
+                                       *dp++ = '.';
+                                       *dp++ = '.';
+                                       needslash = true;
+                                       sp = dp;
+                               }
+                               /* then continue with the next one */
                                continue;
                        }
                }
 
-               if (cur != very_start)
-                       *cur++ = '/';
+               if (needslash)
+                       *dp++ = '/';
 
-               /* find/copy next component of pathname */
-               while (*t && *t != '/')
-                       *cur++ = *t++;
+               /* append next pathname component to output */
+               memmove(dp, tp, len);
+               dp += len;
+
+               /* append slash if we continue */
+               needslash = true;
+               /* try next component */
        }
+       if (dp == p)
+               /* empty path -> dot */
+               *dp++ = needslash ? '/' : '.';
+       *dp = '\0';
 }
 
-
 void
-set_current_wd(char *pathl)
+set_current_wd(const char *nwd)
 {
-       size_t len = 1;
-       char *p = pathl;
+       char *allocd = NULL;
 
-       if (p == NULL) {
-               if ((p = ksh_get_wd(&len)) == NULL)
-                       p = null;
-       } else
-               len = strlen(p) + 1;
+       if (nwd == NULL) {
+               allocd = ksh_get_wd();
+               nwd = allocd ? allocd : null;
+       }
+
+       afree(current_wd, APERM);
+       strdupx(current_wd, nwd, APERM);
+
+       afree(allocd, ATEMP);
+}
+
+int
+c_cd(const char **wp)
+{
+       int optc, rv, phys_path;
+       bool physical = tobool(Flag(FPHYSICAL));
+       /* was a node from cdpath added in? */
+       int cdnode;
+       /* show where we went?, error for $PWD */
+       bool printpath = false, eflag = false;
+       struct tbl *pwd_s, *oldpwd_s;
+       XString xs;
+       char *dir, *allocd = NULL, *tryp, *pwd, *cdpath;
+
+       while ((optc = ksh_getopt(wp, &builtin_opt, "eLP")) != -1)
+               switch (optc) {
+               case 'e':
+                       eflag = true;
+                       break;
+               case 'L':
+                       physical = false;
+                       break;
+               case 'P':
+                       physical = true;
+                       break;
+               case '?':
+                       return (2);
+               }
+       wp += builtin_opt.optind;
+
+       if (Flag(FRESTRICTED)) {
+               bi_errorf("restricted shell - can't cd");
+               return (2);
+       }
+
+       pwd_s = global("PWD");
+       oldpwd_s = global("OLDPWD");
+
+       if (!wp[0]) {
+               /* No arguments - go home */
+               if ((dir = str_val(global("HOME"))) == null) {
+                       bi_errorf("no home directory (HOME not set)");
+                       return (2);
+               }
+       } else if (!wp[1]) {
+               /* One argument: - or dir */
+               strdupx(allocd, wp[0], ATEMP);
+               if (ksh_isdash((dir = allocd))) {
+                       afree(allocd, ATEMP);
+                       allocd = NULL;
+                       dir = str_val(oldpwd_s);
+                       if (dir == null) {
+                               bi_errorf("no OLDPWD");
+                               return (2);
+                       }
+                       printpath = true;
+               }
+       } else if (!wp[2]) {
+               /* Two arguments - substitute arg1 in PWD for arg2 */
+               size_t ilen, olen, nlen, elen;
+               char *cp;
+
+               if (!current_wd[0]) {
+                       bi_errorf("can't determine current directory");
+                       return (2);
+               }
+               /*
+                * substitute arg1 for arg2 in current path.
+                * if the first substitution fails because the cd fails
+                * we could try to find another substitution. For now
+                * we don't
+                */
+               if ((cp = strstr(current_wd, wp[0])) == NULL) {
+                       bi_errorf("bad substitution");
+                       return (2);
+               }
+               /*-
+                * ilen = part of current_wd before wp[0]
+                * elen = part of current_wd after wp[0]
+                * because current_wd and wp[1] need to be in memory at the
+                * same time beforehand the addition can stay unchecked
+                */
+               ilen = cp - current_wd;
+               olen = strlen(wp[0]);
+               nlen = strlen(wp[1]);
+               elen = strlen(current_wd + ilen + olen) + 1;
+               dir = allocd = alloc(ilen + nlen + elen, ATEMP);
+               memcpy(dir, current_wd, ilen);
+               memcpy(dir + ilen, wp[1], nlen);
+               memcpy(dir + ilen + nlen, current_wd + ilen + olen, elen);
+               printpath = true;
+       } else {
+               bi_errorf("too many arguments");
+               return (2);
+       }
+
+#ifdef NO_PATH_MAX
+       /* only a first guess; make_path will enlarge xs if necessary */
+       XinitN(xs, 1024, ATEMP);
+#else
+       XinitN(xs, PATH_MAX, ATEMP);
+#endif
+
+       cdpath = str_val(global("CDPATH"));
+       do {
+               cdnode = make_path(current_wd, dir, &cdpath, &xs, &phys_path);
+               if (physical)
+                       rv = chdir(tryp = Xstring(xs, xp) + phys_path);
+               else {
+                       simplify_path(Xstring(xs, xp));
+                       rv = chdir(tryp = Xstring(xs, xp));
+               }
+       } while (rv < 0 && cdpath != NULL);
+
+       if (rv < 0) {
+               if (cdnode)
+                       bi_errorf("%s: %s", dir, "bad directory");
+               else
+                       bi_errorf("%s: %s", tryp, strerror(errno));
+               afree(allocd, ATEMP);
+               Xfree(xs, xp);
+               return (2);
+       }
+
+       rv = 0;
+
+       /* allocd (above) => dir, which is no longer used */
+       afree(allocd, ATEMP);
+       allocd = NULL;
+
+       /* Clear out tracked aliases with relative paths */
+       flushcom(false);
+
+       /*
+        * Set OLDPWD (note: unsetting OLDPWD does not disable this
+        * setting in AT&T ksh)
+        */
+       if (current_wd[0])
+               /* Ignore failure (happens if readonly or integer) */
+               setstr(oldpwd_s, current_wd, KSH_RETURN_ERROR);
+
+       if (Xstring(xs, xp)[0] != '/') {
+               pwd = NULL;
+       } else if (!physical) {
+               goto norealpath_PWD;
+       } else if ((pwd = allocd = do_realpath(Xstring(xs, xp))) == NULL) {
+               if (eflag)
+                       rv = 1;
+ norealpath_PWD:
+               pwd = Xstring(xs, xp);
+       }
+
+       /* Set PWD */
+       if (pwd) {
+               char *ptmp = pwd;
 
-       if (len > current_wd_size) {
-               afree(current_wd, APERM);
-               current_wd = alloc(current_wd_size = len, APERM);
+               set_current_wd(ptmp);
+               /* Ignore failure (happens if readonly or integer) */
+               setstr(pwd_s, ptmp, KSH_RETURN_ERROR);
+       } else {
+               set_current_wd(null);
+               pwd = Xstring(xs, xp);
+               /* XXX unset $PWD? */
+               if (eflag)
+                       rv = 1;
        }
-       memcpy(current_wd, p, len);
-       if (p != pathl && p != null)
-               afree(p, ATEMP);
+       if (printpath || cdnode)
+               shprintf("%s\n", pwd);
+
+       afree(allocd, ATEMP);
+       Xfree(xs, xp);
+       return (rv);
 }
 
+
 #ifdef TIOCSCTTY
 extern void chvt_reinit(void);
 
@@ -1272,9 +1819,6 @@ chvt(const char *fn)
        struct stat sb;
        int fd;
 
-       /* for entropy */
-       kshstate_f.h = evilhash(fn);
-
        if (*fn == '-') {
                memcpy(dv, "-/dev/null", sizeof("-/dev/null"));
                fn = dv + 1;
@@ -1285,56 +1829,75 @@ chvt(const char *fn)
                        if (stat(dv, &sb)) {
                                strlcpy(dv + 8, fn, sizeof(dv) - 8);
                                if (stat(dv, &sb))
-                                       errorf("chvt: can't find tty %s", fn);
+                                       errorf("%s: %s %s", "chvt",
+                                           "can't find tty", fn);
                        }
                        fn = dv;
                }
                if (!(sb.st_mode & S_IFCHR))
-                       errorf("chvt: not a char device: %s", fn);
+                       errorf("%s %s %s", "chvt: not a char", "device", fn);
                if ((sb.st_uid != 0) && chown(fn, 0, 0))
-                       warningf(false, "chvt: cannot chown root %s", fn);
+                       warningf(false, "%s: %s %s", "chvt", "can't chown root", fn);
                if (((sb.st_mode & 07777) != 0600) && chmod(fn, (mode_t)0600))
-                       warningf(false, "chvt: cannot chmod 0600 %s", fn);
+                       warningf(false, "%s: %s %s", "chvt", "can't chmod 0600", fn);
 #if HAVE_REVOKE
                if (revoke(fn))
 #endif
-                       warningf(false, "chvt: cannot revoke %s, new shell is"
-                           " potentially insecure", fn);
+                       warningf(false, "%s: %s %s", "chvt",
+                           "new shell is potentially insecure, can't revoke",
+                           fn);
        }
        if ((fd = open(fn, O_RDWR)) == -1) {
                sleep(1);
                if ((fd = open(fn, O_RDWR)) == -1)
-                       errorf("chvt: cannot open %s", fn);
+                       errorf("%s: %s %s", "chvt", "can't open", fn);
        }
        switch (fork()) {
        case -1:
-               errorf("chvt: %s failed", "fork");
+               errorf("%s: %s %s", "chvt", "fork", "failed");
        case 0:
                break;
        default:
                exit(0);
        }
        if (setsid() == -1)
-               errorf("chvt: %s failed", "setsid");
+               errorf("%s: %s %s", "chvt", "setsid", "failed");
        if (fn != dv + 1) {
                if (ioctl(fd, TIOCSCTTY, NULL) == -1)
-                       errorf("chvt: %s failed", "TIOCSCTTY");
+                       errorf("%s: %s %s", "chvt", "TIOCSCTTY", "failed");
                if (tcflush(fd, TCIOFLUSH))
-                       errorf("chvt: %s failed", "TCIOFLUSH");
+                       errorf("%s: %s %s", "chvt", "TCIOFLUSH", "failed");
        }
        ksh_dup2(fd, 0, false);
        ksh_dup2(fd, 1, false);
        ksh_dup2(fd, 2, false);
        if (fd > 2)
                close(fd);
+       {
+               register uint32_t h;
+
+               NZATInit(h);
+               NZATUpdateMem(h, &rndsetupstate, sizeof(rndsetupstate));
+               NZAATFinish(h);
+               rndset((long)h);
+       }
        chvt_reinit();
 }
 #endif
 
 #ifdef DEBUG
-char longsizes_are_okay[sizeof(long) == sizeof(unsigned long) ? 1 : -1];
-char arisize_is_okay[sizeof(mksh_ari_t) == 4 ? 1 : -1];
-char uarisize_is_okay[sizeof(mksh_uari_t) == 4 ? 1 : -1];
+#define assert_eq(name, a, b) char name[a == b ? 1 : -1]
+#define assert_ge(name, a, b) char name[a >= b ? 1 : -1]
+assert_ge(intsize_is_okay, sizeof(int), 4);
+assert_eq(intsizes_are_okay, sizeof(int), sizeof(unsigned int));
+assert_ge(longsize_is_okay, sizeof(long), sizeof(int));
+assert_eq(arisize_is_okay, sizeof(mksh_ari_t), 4);
+assert_eq(uarisize_is_okay, sizeof(mksh_uari_t), 4);
+assert_eq(sizesizes_are_okay, sizeof(size_t), sizeof(ssize_t));
+assert_eq(ptrsizes_are_okay, sizeof(ptrdiff_t), sizeof(void *));
+assert_eq(ptrsize_is_sizet, sizeof(ptrdiff_t), sizeof(size_t));
+/* formatting routines assume this */
+assert_ge(ptr_fits_in_long, sizeof(long), sizeof(size_t));
 
 char *
 strchr(char *p, int ch)
@@ -1367,7 +1930,6 @@ strstr(char *b, const char *l)
 }
 #endif
 
-#ifndef MKSH_ASSUME_UTF8
 #if !HAVE_STRCASESTR
 const char *
 stristr(const char *b, const char *l)
@@ -1387,7 +1949,6 @@ stristr(const char *b, const char *l)
        return (b - 1);
 }
 #endif
-#endif
 
 #ifdef MKSH_SMALL
 char *
@@ -1527,15 +2088,15 @@ unbksl(bool cstyle, int (*fg)(void), void (*fp)(int))
                break;
        case 'U':
                i = 8;
-               if (0)
+               if (/* CONSTCOND */ 0)
                /* FALLTHROUGH */
        case 'u':
                i = 4;
-               if (0)
+               if (/* CONSTCOND */ 0)
                /* FALLTHROUGH */
        case 'x':
                i = cstyle ? -1 : 2;
-               /*
+               /**
                 * x:   look for a hexadecimal number with up to
                 *      two (C style: arbitrary) digits; convert
                 *      to raw octet (C style: Unicode if >0xFF)
diff --git a/src/mksh.1 b/src/mksh.1
new file mode 100644 (file)
index 0000000..2c70ef0
--- /dev/null
@@ -0,0 +1,6280 @@
+.\" $MirOS: src/bin/mksh/mksh.1,v 1.275 2011/10/07 19:51:29 tg Exp $
+.\" $OpenBSD: ksh.1,v 1.141 2011/09/03 22:59:08 jmc Exp $
+.\"-
+.\" Copyright © 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
+.\"            2010, 2011
+.\"    Thorsten Glaser <tg@mirbsd.org>
+.\"
+.\" Provided that these terms and disclaimer and all copyright notices
+.\" are retained or reproduced in an accompanying document, permission
+.\" is granted to deal in this work without restriction, including un‐
+.\" limited rights to use, publicly perform, distribute, sell, modify,
+.\" merge, give away, or sublicence.
+.\"
+.\" This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+.\" the utmost extent permitted by applicable law, neither express nor
+.\" implied; without malicious intent or gross negligence. In no event
+.\" may a licensor, author or contributor be held liable for indirect,
+.\" direct, other damage, loss, or other issues arising in any way out
+.\" of dealing in the work, even if advised of the possibility of such
+.\" damage or existence of a defect, except proven that it results out
+.\" of said person’s immediate fault when using the work as intended.
+.\"-
+.\" Try to make GNU groff and AT&T nroff more compatible
+.\" * ` generates ‘ in gnroff, so use \`
+.\" * ' generates ’ in gnroff, \' generates ´, so use \*(aq
+.\" * - generates ‐ in gnroff, \- generates −, so .tr it to -
+.\"   thus use - for hyphens and \- for minus signs and option dashes
+.\" * ~ is size-reduced and placed atop in groff, so use \*(TI
+.\" * ^ is size-reduced and placed atop in groff, so use \*(ha
+.\" * \(en does not work in nroff, so use \*(en
+.\" The section after the "doc" macropackage has been loaded contains
+.\" additional code to convene between the UCB mdoc macropackage (and
+.\" its variant as BSD mdoc in groff) and the GNU mdoc macropackage.
+.\"
+.ie \n(.g \{\
+.      if \ 1\*[.T]\ 1ascii\ 1 .tr \-\N'45'
+.      if \ 1\*[.T]\ 1latin1\ 1 .tr \-\N'45'
+.      if \ 1\*[.T]\ 1utf8\ 1 .tr \-\N'45'
+.      ds <= \[<=]
+.      ds >= \[>=]
+.      ds Rq \[rq]
+.      ds Lq \[lq]
+.      ds sL \(aq
+.      ds sR \(aq
+.      if \ 1\*[.T]\ 1utf8\ 1 .ds sL `
+.      if \ 1\*[.T]\ 1ps\ 1 .ds sL `
+.      if \ 1\*[.T]\ 1utf8\ 1 .ds sR '
+.      if \ 1\*[.T]\ 1ps\ 1 .ds sR '
+.      ds aq \(aq
+.      ds TI \(ti
+.      ds ha \(ha
+.      ds en \(en
+.\}
+.el \{\
+.      ds aq '
+.      ds TI ~
+.      ds ha ^
+.      ds en \(em
+.\}
+.\"
+.\" Implement .Dd with the Mdocdate RCS keyword
+.\"
+.rn Dd xD
+.de Dd
+.ie \a\\$1\a$Mdocdate:\a \{\
+.      xD \\$2 \\$3, \\$4
+.\}
+.el .xD \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8
+..
+.\"
+.\" .Dd must come before definition of .Mx, because when called
+.\" with -mandoc, it might implement .Mx itself, but we want to
+.\" use our own definition. And .Dd must come *first*, always.
+.\"
+.Dd $Mdocdate: October 7 2011 $
+.\"
+.\" Check which macro package we use
+.\"
+.ie \n(.g \{\
+.      ie d volume-ds-1 .ds tT gnu
+.      el .ds tT bsd
+.\}
+.el .ds tT ucb
+.\"
+.\" Implement .Mx (MirBSD)
+.\"
+.ie "\*(tT"gnu" \{\
+.      eo
+.      de Mx
+.      nr curr-font \n[.f]
+.      nr curr-size \n[.ps]
+.      ds str-Mx \f[\n[curr-font]]\s[\n[curr-size]u]
+.      ds str-Mx1 \*[Tn-font-size]\%MirOS\*[str-Mx]
+.      if !\n[arg-limit] \
+.      if \n[.$] \{\
+.      ds macro-name Mx
+.      parse-args \$@
+.      \}
+.      if (\n[arg-limit] > \n[arg-ptr]) \{\
+.      nr arg-ptr +1
+.      ie (\n[type\n[arg-ptr]] == 2) \
+.      as str-Mx1 \~\*[arg\n[arg-ptr]]
+.      el \
+.      nr arg-ptr -1
+.      \}
+.      ds arg\n[arg-ptr] "\*[str-Mx1]
+.      nr type\n[arg-ptr] 2
+.      ds space\n[arg-ptr] "\*[space]
+.      nr num-args (\n[arg-limit] - \n[arg-ptr])
+.      nr arg-limit \n[arg-ptr]
+.      if \n[num-args] \
+.      parse-space-vector
+.      print-recursive
+..
+.      ec
+.      ds sP \s0
+.      ds tN \*[Tn-font-size]
+.\}
+.el \{\
+.      de Mx
+.      nr cF \\n(.f
+.      nr cZ \\n(.s
+.      ds aa \&\f\\n(cF\s\\n(cZ
+.      if \\n(aC==0 \{\
+.              ie \\n(.$==0 \&MirOS\\*(aa
+.              el .aV \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9
+.      \}
+.      if \\n(aC>\\n(aP \{\
+.              nr aP \\n(aP+1
+.              ie \\n(C\\n(aP==2 \{\
+.                      as b1 \&MirOS\ #\&\\*(A\\n(aP\\*(aa
+.                      ie \\n(aC>\\n(aP \{\
+.                              nr aP \\n(aP+1
+.                              nR
+.                      \}
+.                      el .aZ
+.              \}
+.              el \{\
+.                      as b1 \&MirOS\\*(aa
+.                      nR
+.              \}
+.      \}
+..
+.\}
+.\"-
+.Dt MKSH 1
+.Os MirBSD
+.Sh NAME
+.Nm mksh ,
+.Nm sh
+.Nd MirBSD Korn shell
+.Sh SYNOPSIS
+.Nm
+.Bk -words
+.Op Fl +abCefhiklmnprUuvXx
+.Op Fl T Ar /dev/ttyCn | \-
+.Op Fl +o Ar option
+.Oo
+.Fl c Ar string \*(Ba
+.Fl s \*(Ba
+.Ar file
+.Op Ar argument ...
+.Oc
+.Ek
+.Nm builtin-name
+.Op Ar argument ...
+.Sh DESCRIPTION
+.Nm
+is a command interpreter intended for both interactive and shell
+script use.
+Its command language is a superset of the
+.Xr sh C
+shell language and largely compatible to the original Korn shell.
+.Pp
+Most builtins can be called directly, for example if a link points from its
+name to the shell; not all make sense, have been tested or work at all though.
+.Pp
+The options are as follows:
+.Bl -tag -width XcXstring
+.It Fl c Ar string
+.Nm
+will execute the command(s) contained in
+.Ar string .
+.It Fl i
+Interactive shell.
+A shell is
+.Dq interactive
+if this
+option is used or if both standard input and standard error are attached
+to a
+.Xr tty 4 .
+An interactive shell has job control enabled, ignores the
+.Dv SIGINT ,
+.Dv SIGQUIT ,
+and
+.Dv SIGTERM
+signals, and prints prompts before reading input (see the
+.Ev PS1
+and
+.Ev PS2
+parameters).
+It also processes the
+.Ev ENV
+parameter or the
+.Pa mkshrc
+file (see below).
+For non-interactive shells, the
+.Ic trackall
+option is on by default (see the
+.Ic set
+command below).
+.It Fl l
+Login shell.
+If the basename the shell is called with (i.e. argv[0])
+starts with
+.Ql \-
+or if this option is used,
+the shell is assumed to be a login shell; see
+.Sx Startup files
+below.
+.It Fl p
+Privileged shell.
+A shell is
+.Dq privileged
+if this option is used
+or if the real user ID or group ID does not match the
+effective user ID or group ID (see
+.Xr getuid 2
+and
+.Xr getgid 2 ) .
+Clearing the privileged option causes the shell to set
+its effective user ID (group ID) to its real user ID (group ID).
+For further implications, see
+.Sx Startup files .
+.It Fl r
+Restricted shell.
+A shell is
+.Dq restricted
+if this
+option is used.
+The following restrictions come into effect after the shell processes any
+profile and
+.Ev ENV
+files:
+.Pp
+.Bl -bullet -compact
+.It
+The
+.Ic cd
+.Po and Ic chdir Pc
+command is disabled.
+.It
+The
+.Ev SHELL ,
+.Ev ENV ,
+and
+.Ev PATH
+parameters cannot be changed.
+.It
+Command names can't be specified with absolute or relative paths.
+.It
+The
+.Fl p
+option of the built-in command
+.Ic command
+can't be used.
+.It
+Redirections that create files can't be used (i.e.\&
+.Ql \*(Gt ,
+.Ql \*(Gt\*(Ba ,
+.Ql \*(Gt\*(Gt ,
+.Ql \*(Lt\*(Gt ) .
+.El
+.It Fl s
+The shell reads commands from standard input; all non-option arguments
+are positional parameters.
+.It Fl T Ar tty
+Spawn
+.Nm
+on the
+.Xr tty 4
+device given.
+Superuser only.
+If
+.Ar tty
+is a dash, detach from controlling terminal (daemonise) instead.
+.El
+.Pp
+In addition to the above, the options described in the
+.Ic set
+built-in command can also be used on the command line:
+both
+.Op Fl +abCefhkmnuvXx
+and
+.Op Fl +o Ar option
+can be used for single letter or long options, respectively.
+.Pp
+If neither the
+.Fl c
+nor the
+.Fl s
+option is specified, the first non-option argument specifies the name
+of a file the shell reads commands from.
+If there are no non-option
+arguments, the shell reads commands from the standard input.
+The name of the shell (i.e. the contents of $0)
+is determined as follows: if the
+.Fl c
+option is used and there is a non-option argument, it is used as the name;
+if commands are being read from a file, the file is used as the name;
+otherwise, the basename the shell was called with (i.e. argv[0]) is used.
+.Pp
+The exit status of the shell is 127 if the command file specified on the
+command line could not be opened, or non-zero if a fatal syntax error
+occurred during the execution of a script.
+In the absence of fatal errors,
+the exit status is that of the last command executed, or zero, if no
+command is executed.
+.Ss Startup files
+For the actual location of these files, see
+.Sx FILES .
+A login shell processes the system profile first.
+A privileged shell then processes the suid profile.
+A non-privileged login shell processes the user profile next.
+A non-privileged interactive shell checks the value of the
+.Ev ENV
+parameter after subjecting it to parameter, command, arithmetic and tilde
+.Pq Sq \*(TI
+substitution; if unset or empty, the user mkshrc profile is processed;
+otherwise, if a file whose name is the substitution result exists,
+it is processed; non-existence is silently ignored.
+.Ss Command syntax
+The shell begins parsing its input by removing any backslash-newline
+combinations, then breaking it into
+.Em words .
+Words (which are sequences of characters) are delimited by unquoted whitespace
+characters (space, tab, and newline) or meta-characters
+.Po
+.Ql \*(Lt ,
+.Ql \*(Gt ,
+.Ql \*(Ba ,
+.Ql \&; ,
+.Ql \&( ,
+.Ql \&) ,
+and
+.Ql &
+.Pc .
+Aside from delimiting words, spaces and tabs are ignored, while newlines
+usually delimit commands.
+The meta-characters are used in building the following
+.Em tokens :
+.Ql \*(Lt ,
+.Ql \*(Lt& ,
+.Ql \*(Lt\*(Lt ,
+.Ql \*(Lt\*(Lt\*(Lt ,
+.Ql \*(Gt ,
+.Ql \*(Gt& ,
+.Ql \*(Gt\*(Gt ,
+.Ql &\*(Gt ,
+etc. are used to specify redirections (see
+.Sx Input/output redirection
+below);
+.Ql \*(Ba
+is used to create pipelines;
+.Ql \*(Ba&
+is used to create co-processes (see
+.Sx Co-processes
+below);
+.Ql \&;
+is used to separate commands;
+.Ql &
+is used to create asynchronous pipelines;
+.Ql &&
+and
+.Ql \*(Ba\*(Ba
+are used to specify conditional execution;
+.Ql ;; ,
+.Ql ;&\&
+and
+.Ql ;\*(Ba\&
+are used in
+.Ic case
+statements;
+.Ql \&(( .. ))
+is used in arithmetic expressions;
+and lastly,
+.Ql \&( .. )\&
+is used to create subshells.
+.Pp
+Whitespace and meta-characters can be quoted individually using a backslash
+.Pq Sq \e ,
+or in groups using double
+.Pq Sq \&"
+or single
+.Pq Sq \*(aq
+quotes.
+Note that the following characters are also treated specially by the
+shell and must be quoted if they are to represent themselves:
+.Ql \e ,
+.Ql \&" ,
+.Ql \*(aq ,
+.Ql # ,
+.Ql $ ,
+.Ql \` ,
+.Ql \*(TI ,
+.Ql { ,
+.Ql } ,
+.Ql * ,
+.Ql \&? ,
+and
+.Ql \&[ .
+The first three of these are the above mentioned quoting characters (see
+.Sx Quoting
+below);
+.Ql # ,
+if used at the beginning of a word, introduces a comment \*(en everything after
+the
+.Ql #
+up to the nearest newline is ignored;
+.Ql $
+is used to introduce parameter, command, and arithmetic substitutions (see
+.Sx Substitution
+below);
+.Ql \`
+introduces an old-style command substitution (see
+.Sx Substitution
+below);
+.Ql \*(TI
+begins a directory expansion (see
+.Sx Tilde expansion
+below);
+.Ql {
+and
+.Ql }
+delimit
+.Xr csh 1 Ns -style
+alterations (see
+.Sx Brace expansion
+below);
+and finally,
+.Ql * ,
+.Ql \&? ,
+and
+.Ql \&[
+are used in file name generation (see
+.Sx File name patterns
+below).
+.Pp
+As words and tokens are parsed, the shell builds commands, of which there
+are two basic types:
+.Em simple-commands ,
+typically programmes that are executed, and
+.Em compound-commands ,
+such as
+.Ic for
+and
+.Ic if
+statements, grouping constructs, and function definitions.
+.Pp
+A simple-command consists of some combination of parameter assignments
+(see
+.Sx Parameters
+below),
+input/output redirections (see
+.Sx Input/output redirections
+below),
+and command words; the only restriction is that parameter assignments come
+before any command words.
+The command words, if any, define the command
+that is to be executed and its arguments.
+The command may be a shell built-in command, a function,
+or an external command
+(i.e. a separate executable file that is located using the
+.Ev PATH
+parameter; see
+.Sx Command execution
+below).
+Note that all command constructs have an exit status: for external commands,
+this is related to the status returned by
+.Xr wait 2
+(if the command could not be found, the exit status is 127; if it could not
+be executed, the exit status is 126); the exit status of other command
+constructs (built-in commands, functions, compound-commands, pipelines, lists,
+etc.) are all well-defined and are described where the construct is
+described.
+The exit status of a command consisting only of parameter
+assignments is that of the last command substitution performed during the
+parameter assignment or 0 if there were no command substitutions.
+.Pp
+Commands can be chained together using the
+.Ql \*(Ba
+token to form pipelines, in which the standard output of each command but the
+last is piped (see
+.Xr pipe 2 )
+to the standard input of the following command.
+The exit status of a pipeline is that of its last command.
+All commands of a pipeline are executed in separate subshells;
+this is allowed by POSIX but differs from both variants of
+.At
+.Nm ksh ,
+where all but the last command were executed in subshells; see the
+.Ic read
+builtin's description for implications and workarounds.
+A pipeline may be prefixed by the
+.Ql \&!
+reserved word which causes the exit status of the pipeline to be logically
+complemented: if the original status was 0, the complemented status will be 1;
+if the original status was not 0, the complemented status will be 0.
+.Pp
+.Em Lists
+of commands can be created by separating pipelines by any of the following
+tokens:
+.Ql && ,
+.Ql \*(Ba\*(Ba ,
+.Ql & ,
+.Ql \*(Ba& ,
+and
+.Ql \&; .
+The first two are for conditional execution:
+.Dq Ar cmd1 No && Ar cmd2
+executes
+.Ar cmd2
+only if the exit status of
+.Ar cmd1
+is zero;
+.Ql \*(Ba\*(Ba
+is the opposite \*(en
+.Ar cmd2
+is executed only if the exit status of
+.Ar cmd1
+is non-zero.
+.Ql &&
+and
+.Ql \*(Ba\*(Ba
+have equal precedence which is higher than that of
+.Ql & ,
+.Ql \*(Ba& ,
+and
+.Ql \&; ,
+which also have equal precedence.
+Note that the
+.Ql &&
+and
+.Ql \*(Ba\*(Ba
+operators are
+.Qq left-associative .
+For example, both of these commands will print only
+.Qq bar :
+.Bd -literal -offset indent
+$ false && echo foo \*(Ba\*(Ba echo bar
+$ true \*(Ba\*(Ba echo foo && echo bar
+.Ed
+.Pp
+The
+.Ql &
+token causes the preceding command to be executed asynchronously; that is,
+the shell starts the command but does not wait for it to complete (the shell
+does keep track of the status of asynchronous commands; see
+.Sx Job control
+below).
+When an asynchronous command is started when job control is disabled
+(i.e. in most scripts), the command is started with signals
+.Dv SIGINT
+and
+.Dv SIGQUIT
+ignored and with input redirected from
+.Pa /dev/null
+(however, redirections specified in the asynchronous command have precedence).
+The
+.Ql \*(Ba&
+operator starts a co-process which is a special kind of asynchronous process
+(see
+.Sx Co-processes
+below).
+Note that a command must follow the
+.Ql &&
+and
+.Ql \*(Ba\*(Ba
+operators, while it need not follow
+.Ql & ,
+.Ql \*(Ba& ,
+or
+.Ql \&; .
+The exit status of a list is that of the last command executed, with the
+exception of asynchronous lists, for which the exit status is 0.
+.Pp
+Compound commands are created using the following reserved words.
+These words
+are only recognised if they are unquoted and if they are used as the first
+word of a command (i.e. they can't be preceded by parameter assignments or
+redirections):
+.Bd -literal -offset indent
+case     else     function     then      !       (
+do       esac     if           time      [[      ((
+done     fi       in           until     {
+elif     for      select       while     }
+.Ed
+.Pp
+In the following compound command descriptions, command lists (denoted as
+.Em list )
+that are followed by reserved words must end with a semicolon, a newline, or
+a (syntactically correct) reserved word.
+For example, the following are all valid:
+.Bd -literal -offset indent
+$ { echo foo; echo bar; }
+$ { echo foo; echo bar\*(Ltnewline\*(Gt}
+$ { { echo foo; echo bar; } }
+.Ed
+.Pp
+This is not valid:
+.Pp
+.Dl $ { echo foo; echo bar }
+.Bl -tag -width 4n
+.It Pq Ar list
+Execute
+.Ar list
+in a subshell.
+There is no implicit way to pass environment changes from a
+subshell back to its parent.
+.It { Ar list ; No }
+Compound construct;
+.Ar list
+is executed, but not in a subshell.
+Note that
+.Ql {
+and
+.Ql }
+are reserved words, not meta-characters.
+.It Xo case Ar word No in
+.Oo Op \&(
+.Ar pattern
+.Op \*(Ba Ar pat
+.No ... Ns )
+.Ar list
+.Op ;; \*(Ba ;&\& \*(Ba ;\*(Ba\ \&
+.Oc ... esac
+.Xc
+The
+.Ic case
+statement attempts to match
+.Ar word
+against a specified
+.Ar pattern ;
+the
+.Ar list
+associated with the first successfully matched pattern is executed.
+Patterns used in
+.Ic case
+statements are the same as those used for file name patterns except that the
+restrictions regarding
+.Ql \&.
+and
+.Ql /
+are dropped.
+Note that any unquoted space before and after a pattern is
+stripped; any space within a pattern must be quoted.
+Both the word and the
+patterns are subject to parameter, command, and arithmetic substitution, as
+well as tilde substitution.
+.Pp
+For historical reasons, open and close braces may be used instead of
+.Ic in
+and
+.Ic esac
+e.g.\&
+.Ic case $foo { *) echo bar;; } .
+.Pp
+The list terminators are:
+.Bl -tag -width 4n
+.It Ql ;;
+Terminate after the list.
+.It Ql ;&\&
+Fall through into the next list.
+.It Ql ;\*(Ba\&
+Evaluate the remaining pattern-list tuples.
+.El
+.Pp
+The exit status of a
+.Ic case
+statement is that of the executed
+.Ar list ;
+if no
+.Ar list
+is executed, the exit status is zero.
+.It Xo for Ar name
+.Oo in Ar word No ... Oc ;
+.No do Ar list ; No done
+.Xc
+For each
+.Ar word
+in the specified word list, the parameter
+.Ar name
+is set to the word and
+.Ar list
+is executed.
+If
+.Ic in
+is not used to specify a word list, the positional parameters
+($1, $2, etc.)\&
+are used instead.
+For historical reasons, open and close braces may be used instead of
+.Ic do
+and
+.Ic done
+e.g.\&
+.Ic for i; { echo $i; } .
+The exit status of a
+.Ic for
+statement is the last exit status of
+.Ar list ;
+if
+.Ar list
+is never executed, the exit status is zero.
+.It Xo if Ar list ;
+.No then Ar list ;
+.Oo elif Ar list ;
+.No then Ar list ; Oc
+.No ...
+.Oo else Ar list ; Oc
+.No fi
+.Xc
+If the exit status of the first
+.Ar list
+is zero, the second
+.Ar list
+is executed; otherwise, the
+.Ar list
+following the
+.Ic elif ,
+if any, is executed with similar consequences.
+If all the lists following the
+.Ic if
+and
+.Ic elif Ns s
+fail (i.e. exit with non-zero status), the
+.Ar list
+following the
+.Ic else
+is executed.
+The exit status of an
+.Ic if
+statement is that of non-conditional
+.Ar list
+that is executed; if no non-conditional
+.Ar list
+is executed, the exit status is zero.
+.It Xo select Ar name
+.Oo in Ar word No ... Oc ;
+.No do Ar list ; No done
+.Xc
+The
+.Ic select
+statement provides an automatic method of presenting the user with a menu and
+selecting from it.
+An enumerated list of the specified
+.Ar word Ns (s)
+is printed on standard error, followed by a prompt
+.Po
+.Ev PS3: normally
+.Sq #?\ \&
+.Pc .
+A number corresponding to one of the enumerated words is then read from
+standard input,
+.Ar name
+is set to the selected word (or unset if the selection is not valid),
+.Ev REPLY
+is set to what was read (leading/trailing space is stripped), and
+.Ar list
+is executed.
+If a blank line (i.e. zero or more
+.Ev IFS
+octets) is entered, the menu is reprinted without executing
+.Ar list .
+.Pp
+When
+.Ar list
+completes, the enumerated list is printed if
+.Ev REPLY
+is
+.Dv NULL ,
+the prompt is printed, and so on.
+This process continues until an end-of-file
+is read, an interrupt is received, or a
+.Ic break
+statement is executed inside the loop.
+If
+.Dq in word ...
+is omitted, the positional parameters are used
+(i.e. $1, $2, etc.).
+For historical reasons, open and close braces may be used instead of
+.Ic do
+and
+.Ic done
+e.g.\&
+.Ic select i; { echo $i; } .
+The exit status of a
+.Ic select
+statement is zero if a
+.Ic break
+statement is used to exit the loop, non-zero otherwise.
+.It Xo until Ar list ;
+.No do Ar list ;
+.No done
+.Xc
+This works like
+.Ic while ,
+except that the body is executed only while the exit status of the first
+.Ar list
+is non-zero.
+.It Xo while Ar list ;
+.No do Ar list ;
+.No done
+.Xc
+A
+.Ic while
+is a pre-checked loop.
+Its body is executed as often as the exit status of the first
+.Ar list
+is zero.
+The exit status of a
+.Ic while
+statement is the last exit status of the
+.Ar list
+in the body of the loop; if the body is not executed, the exit status is zero.
+.It Xo function Ar name
+.No { Ar list ; No }
+.Xc
+Defines the function
+.Ar name
+(see
+.Sx Functions
+below).
+Note that redirections specified after a function definition are
+performed whenever the function is executed, not when the function definition
+is executed.
+.It Ar name Ns \&() Ar command
+Mostly the same as
+.Ic function
+(see
+.Sx Functions
+below).
+Whitespace (space or tab) after
+.Ar name
+will be ignored most of the time.
+.It Xo function Ar name Ns \&()
+.No { Ar list ; No }
+.Xc
+The same as
+.Ar name Ns \&()
+.Pq Nm bash Ns ism .
+The
+.Ic function
+keyword is ignored.
+.It Xo Ic time Op Fl p
+.Op Ar pipeline
+.Xc
+The
+.Sx Command execution
+section describes the
+.Ic time
+reserved word.
+.It \&(( Ar expression No ))
+The arithmetic expression
+.Ar expression
+is evaluated; equivalent to
+.Dq let expression
+(see
+.Sx Arithmetic expressions
+and the
+.Ic let
+command, below).
+.It Bq Bq Ar \ \&expression\ \&
+Similar to the
+.Ic test
+and
+.Ic \&[ ... \&]
+commands (described later), with the following exceptions:
+.Bl -bullet
+.It
+Field splitting and file name generation are not performed on arguments.
+.It
+The
+.Fl a
+.Pq AND
+and
+.Fl o
+.Pq OR
+operators are replaced with
+.Ql &&
+and
+.Ql \*(Ba\*(Ba ,
+respectively.
+.It
+Operators (e.g.\&
+.Sq Fl f ,
+.Sq = ,
+.Sq \&! )
+must be unquoted.
+.It
+Parameter, command, and arithmetic substitutions are performed as expressions
+are evaluated and lazy expression evaluation is used for the
+.Ql &&
+and
+.Ql \*(Ba\*(Ba
+operators.
+This means that in the following statement,
+.Ic $(\*(Ltfoo)
+is evaluated if and only if the file
+.Pa foo
+exists and is readable:
+.Bd -literal -offset indent
+$ [[ \-r foo && $(\*(Ltfoo) = b*r ]]
+.Ed
+.It
+The second operand of the
+.Sq !=
+and
+.Sq =
+expressions are patterns (e.g. the comparison
+.Ic \&[[ foobar = f*r ]]
+succeeds).
+This even works indirectly:
+.Bd -literal -offset indent
+$ bar=foobar; baz=\*(aqf*r\*(aq
+$ [[ $bar = $baz ]]; echo $?
+$ [[ $bar = "$baz" ]]; echo $?
+.Ed
+.Pp
+Perhaps surprisingly, the first comparison succeeds,
+whereas the second doesn't.
+.El
+.El
+.Ss Quoting
+Quoting is used to prevent the shell from treating characters or words
+specially.
+There are three methods of quoting.
+First,
+.Ql \e
+quotes the following character, unless it is at the end of a line, in which
+case both the
+.Ql \e
+and the newline are stripped.
+Second, a single quote
+.Pq Sq \*(aq
+quotes everything up to the next single quote (this may span lines).
+Third, a double quote
+.Pq Sq \&"
+quotes all characters, except
+.Ql $ ,
+.Ql \`
+and
+.Ql \e ,
+up to the next unquoted double quote.
+.Ql $
+and
+.Ql \`
+inside double quotes have their usual meaning (i.e. parameter, command, or
+arithmetic substitution) except no field splitting is carried out on the
+results of double-quoted substitutions.
+If a
+.Ql \e
+inside a double-quoted string is followed by
+.Ql \e ,
+.Ql $ ,
+.Ql \` ,
+or
+.Ql \&" ,
+it is replaced by the second character; if it is followed by a newline, both
+the
+.Ql \e
+and the newline are stripped; otherwise, both the
+.Ql \e
+and the character following are unchanged.
+.Pp
+If a single-quoted string is preceded by an unquoted
+.Ql $ ,
+C style backslash expansion (see below) is applied (even single quote
+characters inside can be escaped and do not terminate the string then);
+the expanded result is treated as any other single-quoted string.
+If a double-quoted string is preceded by an unquoted
+.Ql $ ,
+the latter is ignored.
+.Ss Backslash expansion
+In places where backslashes are expanded, certain C and
+.At
+.Nm ksh
+or GNU
+.Nm bash
+style escapes are translated.
+These include
+.Ql \ea ,
+.Ql \eb ,
+.Ql \ef ,
+.Ql \en ,
+.Ql \er ,
+.Ql \et ,
+.Ql \eU######## ,
+.Ql \eu#### ,
+and
+.Ql \ev .
+For
+.Ql \eU########
+and
+.Ql \eu#### ,
+.Dq #
+means a hexadecimal digit, of thich there may be none up to four or eight;
+these escapes translate a Unicode codepoint to UTF-8.
+Furthermore,
+.Ql \eE
+and
+.Ql \ee
+expand to the escape character.
+.Pp
+In the
+.Ic print
+builtin mode,
+.Ql \e" ,
+.Ql \e\*(aq ,
+and
+.Ql \e?
+are explicitly excluded;
+octal sequences must have the none up to three octal digits
+.Dq #
+prefixed with the digit zero
+.Pq Ql \e0### ;
+hexadecimal sequences
+.Ql \ex##
+are limited to none up to two hexadecimal digits
+.Dq # ;
+both octal and hexadecimal sequences convert to raw octets;
+.Ql \e# ,
+where # is none of the above, translates to \e# (backslashes are retained).
+.Pp
+Backslash expansion in the C style mode slightly differs: octal sequences
+.Ql \e###
+must have no digit zero prefixing the one up to three octal digits
+.Dq #
+and yield raw octets; hexadecimal sequences
+.Ql \ex#*
+greedily eat up as many hexadecimal digits
+.Dq #
+as they can and terminate with the first non-hexadecimal digit;
+these translate a Unicode codepoint to UTF-8.
+The sequence
+.Ql \ec# ,
+where
+.Dq #
+is any octet, translates to Ctrl-# (which basically means,
+.Ql \ec?
+becomes DEL, everything else is bitwise ANDed with 0x1F).
+Finally,
+.Ql \e# ,
+where # is none of the above, translates to # (has the backslash trimmed),
+even if it is a newline.
+.Ss Aliases
+There are two types of aliases: normal command aliases and tracked aliases.
+Command aliases are normally used as a short hand for a long or often used
+command.
+The shell expands command aliases (i.e. substitutes the alias name
+for its value) when it reads the first word of a command.
+An expanded alias is re-processed to check for more aliases.
+If a command alias ends in a
+space or tab, the following word is also checked for alias expansion.
+The alias expansion process stops when a word that is not an alias is found,
+when a quoted word is found, or when an alias word that is currently being
+expanded is found.
+.Pp
+The following command aliases are defined automatically by the shell:
+.Bd -literal -offset indent
+autoload=\*(aqtypeset \-fu\*(aq
+functions=\*(aqtypeset \-f\*(aq
+hash=\*(aqalias \-t\*(aq
+history=\*(aqfc \-l\*(aq
+integer=\*(aqtypeset \-i\*(aq
+local=\*(aqtypeset\*(aq
+login=\*(aqexec login\*(aq
+nameref=\*(aqtypeset \-n\*(aq
+nohup=\*(aqnohup \*(aq
+r=\*(aqfc \-e \-\*(aq
+stop=\*(aqkill \-STOP\*(aq
+suspend=\*(aqkill \-STOP $$\*(aq
+type=\*(aqwhence \-v\*(aq
+.Ed
+.Pp
+Tracked aliases allow the shell to remember where it found a particular
+command.
+The first time the shell does a path search for a command that is
+marked as a tracked alias, it saves the full path of the command.
+The next
+time the command is executed, the shell checks the saved path to see that it
+is still valid, and if so, avoids repeating the path search.
+Tracked aliases can be listed and created using
+.Ic alias \-t .
+Note that changing the
+.Ev PATH
+parameter clears the saved paths for all tracked aliases.
+If the
+.Ic trackall
+option is set (i.e.\&
+.Ic set \-o Ic trackall
+or
+.Ic set \-h ) ,
+the shell tracks all commands.
+This option is set automatically for non-interactive shells.
+For interactive shells, only the following commands are
+automatically tracked:
+.Xr cat 1 ,
+.Xr cc 1 ,
+.Xr chmod 1 ,
+.Xr cp 1 ,
+.Xr date 1 ,
+.Xr ed 1 ,
+.Xr emacs 1 ,
+.Xr grep 1 ,
+.Xr ls 1 ,
+.Xr make 1 ,
+.Xr mv 1 ,
+.Xr pr 1 ,
+.Xr rm 1 ,
+.Xr sed 1 ,
+.Xr sh 1 ,
+.Xr vi 1 ,
+and
+.Xr who 1 .
+.Ss Substitution
+The first step the shell takes in executing a simple-command is to perform
+substitutions on the words of the command.
+There are three kinds of
+substitution: parameter, command, and arithmetic.
+Parameter substitutions,
+which are described in detail in the next section, take the form
+.Pf $ Ns Ar name
+or
+.Pf ${ Ns Ar ... Ns } ;
+command substitutions take the form
+.Pf $( Ns Ar command Ns \&)
+or (deprecated)
+.Pf \` Ns Ar command Ns \` ;
+and arithmetic substitutions take the form
+.Pf $(( Ns Ar expression Ns )) .
+.Pp
+If a substitution appears outside of double quotes, the results of the
+substitution are generally subject to word or field splitting according to
+the current value of the
+.Ev IFS
+parameter.
+The
+.Ev IFS
+parameter specifies a list of octets which are used to break a string up
+into several words; any octets from the set space, tab, and newline that
+appear in the
+.Ev IFS
+octets are called
+.Dq IFS whitespace .
+Sequences of one or more
+.Ev IFS
+whitespace octets, in combination with zero or one
+.Pf non- Ev IFS
+whitespace octets, delimit a field.
+As a special case, leading and trailing
+.Ev IFS
+whitespace and trailing
+.Ev IFS
+non-whitespace are stripped (i.e. no leading or trailing empty field
+is created by it); leading
+.Pf non- Ev IFS
+whitespace does create an empty field.
+.Pp
+Example: If
+.Ev IFS
+is set to
+.Dq \*(Ltspace\*(Gt: ,
+and VAR is set to
+.Dq \*(Ltspace\*(GtA\*(Ltspace\*(Gt:\*(Ltspace\*(Gt\*(Ltspace\*(GtB::D ,
+the substitution for $VAR results in four fields:
+.Sq A ,
+.Sq B ,
+.Sq
+(an empty field),
+and
+.Sq D .
+Note that if the
+.Ev IFS
+parameter is set to the
+.Dv NULL
+string, no field splitting is done; if the parameter is unset, the default
+value of space, tab, and newline is used.
+.Pp
+Also, note that the field splitting applies only to the immediate result of
+the substitution.
+Using the previous example, the substitution for $VAR:E
+results in the fields:
+.Sq A ,
+.Sq B ,
+.Sq ,
+and
+.Sq D:E ,
+not
+.Sq A ,
+.Sq B ,
+.Sq ,
+.Sq D ,
+and
+.Sq E .
+This behavior is POSIX compliant, but incompatible with some other shell
+implementations which do field splitting on the word which contained the
+substitution or use
+.Dv IFS
+as a general whitespace delimiter.
+.Pp
+The results of substitution are, unless otherwise specified, also subject to
+brace expansion and file name expansion (see the relevant sections below).
+.Pp
+A command substitution is replaced by the output generated by the specified
+command which is run in a subshell.
+For
+.Pf $( Ns Ar command Ns \&)
+substitutions, normal quoting rules are used when
+.Ar command
+is parsed; however, for the deprecated
+.Pf \` Ns Ar command Ns \`
+form, a
+.Ql \e
+followed by any of
+.Ql $ ,
+.Ql \` ,
+or
+.Ql \e
+is stripped (a
+.Ql \e
+followed by any other character is unchanged).
+As a special case in command substitutions, a command of the form
+.Pf \*(Lt Ar file
+is interpreted to mean substitute the contents of
+.Ar file .
+Note that
+.Ic $(\*(Ltfoo)
+has the same effect as
+.Ic $(cat foo) .
+.Pp
+Note that some shells do not use a recursive parser for command substitutions,
+leading to failure for certain constructs; to be portable, use as workaround
+.Ql x=$(cat) \*(Lt\*(Lt"EOF"
+(or the newline-keeping
+.Ql x=\*(Lt\*(Lt"EOF"
+extension) instead to merely slurp the string.
+.St -p1003.1
+recommends to use case statements of the form
+.Ql "x=$(case $foo in (bar) echo $bar ;; (*) echo $baz ;; esac)"
+instead, which would work but not serve as example for this portability issue.
+.Bd -literal -offset indent
+x=$(case $foo in bar) echo $bar ;; *) echo $baz ;; esac)
+# above fails to parse on old shells; below is the workaround
+x=$(eval $(cat)) \*(Lt\*(Lt"EOF"
+case $foo in bar) echo $bar ;; *) echo $baz ;; esac
+EOF
+.Ed
+.Pp
+Arithmetic substitutions are replaced by the value of the specified expression.
+For example, the command
+.Ic print $((2+3*4))
+displays 14.
+See
+.Sx Arithmetic expressions
+for a description of an expression.
+.Ss Parameters
+Parameters are shell variables; they can be assigned values and their values
+can be accessed using a parameter substitution.
+A parameter name is either one
+of the special single punctuation or digit character parameters described
+below, or a letter followed by zero or more letters or digits
+.Po
+.Ql _
+counts as a letter
+.Pc .
+The latter form can be treated as arrays by appending an array index of the
+form
+.Op Ar expr
+where
+.Ar expr
+is an arithmetic expression.
+Array indices in
+.Nm
+are limited to the range 0 through 4294967295, inclusive.
+That is, they are a 32-bit unsigned integer.
+.Pp
+Parameter substitutions take the form
+.Pf $ Ns Ar name ,
+.Pf ${ Ns Ar name Ns } ,
+or
+.Sm off
+.Pf ${ Ar name Oo Ar expr Oc }
+.Sm on
+where
+.Ar name
+is a parameter name.
+Substitution of all array elements with
+.Pf ${ Ns Ar name Ns \&[*]}
+and
+.Pf ${ Ns Ar name Ns \&[@]}
+works equivalent to $* and $@ for positional parameters.
+If substitution is performed on a parameter
+(or an array parameter element)
+that is not set, a null string is substituted unless the
+.Ic nounset
+option
+.Po
+.Ic set Fl o Ic nounset
+or
+.Ic set Fl u
+.Pc
+is set, in which case an error occurs.
+.Pp
+Parameters can be assigned values in a number of ways.
+First, the shell implicitly sets some parameters like
+.Ql # ,
+.Ql PWD ,
+and
+.Ql $ ;
+this is the only way the special single character parameters are set.
+Second, parameters are imported from the shell's environment at startup.
+Third, parameters can be assigned values on the command line: for example,
+.Ic FOO=bar
+sets the parameter
+.Dq FOO
+to
+.Dq bar ;
+multiple parameter assignments can be given on a single command line and they
+can be followed by a simple-command, in which case the assignments are in
+effect only for the duration of the command (such assignments are also
+exported; see below for the implications of this).
+Note that both the parameter name and the
+.Ql =
+must be unquoted for the shell to recognise a parameter assignment.
+The construct
+.Ic FOO+=baz
+is also recognised; the old and new values are immediately concatenated.
+The fourth way of setting a parameter is with the
+.Ic export ,
+.Ic global ,
+.Ic readonly ,
+and
+.Ic typeset
+commands; see their descriptions in the
+.Sx Command execution
+section.
+Fifth,
+.Ic for
+and
+.Ic select
+loops set parameters as well as the
+.Ic getopts ,
+.Ic read ,
+and
+.Ic set \-A
+commands.
+Lastly, parameters can be assigned values using assignment operators
+inside arithmetic expressions (see
+.Sx Arithmetic expressions
+below) or using the
+.Sm off
+.Pf ${ Ar name No = Ar value No }
+.Sm on
+form of the parameter substitution (see below).
+.Pp
+Parameters with the export attribute (set using the
+.Ic export
+or
+.Ic typeset Fl x
+commands, or by parameter assignments followed by simple commands) are put in
+the environment (see
+.Xr environ 7 )
+of commands run by the shell as
+.Ar name Ns = Ns Ar value
+pairs.
+The order in which parameters appear in the environment of a command is
+unspecified.
+When the shell starts up, it extracts parameters and their values
+from its environment and automatically sets the export attribute for those
+parameters.
+.Pp
+Modifiers can be applied to the
+.Pf ${ Ns Ar name Ns }
+form of parameter substitution:
+.Bl -tag -width Ds
+.Sm off
+.It ${ Ar name No :\- Ar word No }
+.Sm on
+If
+.Ar name
+is set and not
+.Dv NULL ,
+it is substituted; otherwise,
+.Ar word
+is substituted.
+.Sm off
+.It ${ Ar name No :+ Ar word No }
+.Sm on
+If
+.Ar name
+is set and not
+.Dv NULL ,
+.Ar word
+is substituted; otherwise, nothing is substituted.
+.Sm off
+.It ${ Ar name No := Ar word No }
+.Sm on
+If
+.Ar name
+is set and not
+.Dv NULL ,
+it is substituted; otherwise, it is assigned
+.Ar word
+and the resulting value of
+.Ar name
+is substituted.
+.Sm off
+.It ${ Ar name No :? Ar word No }
+.Sm on
+If
+.Ar name
+is set and not
+.Dv NULL ,
+it is substituted; otherwise,
+.Ar word
+is printed on standard error (preceded by
+.Ar name : )
+and an error occurs (normally causing termination of a shell script, function,
+or script sourced using the
+.Sq \&.
+built-in).
+If
+.Ar word
+is omitted, the string
+.Dq parameter null or not set
+is used instead.
+Currently a bug, if
+.Ar word
+is a variable which expands to the null string, the
+error message is also printed.
+.El
+.Pp
+Note that, for all of the above,
+.Ar word
+is actually considered quoted, and special parsing rules apply.
+The parsing rules also differ on whether the expression is double-quoted:
+.Ar word
+then uses double-quoting rules, except for the double quote itself
+.Pq Sq \&"
+and the closing brace, which, if backslash escaped, gets quote removal applied.
+.Pp
+In the above modifiers, the
+.Ql \&:
+can be omitted, in which case the conditions only depend on
+.Ar name
+being set (as opposed to set and not
+.Dv NULL ) .
+If
+.Ar word
+is needed, parameter, command, arithmetic, and tilde substitution are performed
+on it; if
+.Ar word
+is not needed, it is not evaluated.
+.Pp
+The following forms of parameter substitution can also be used (if
+.Ar name
+is an array, its element #0 will be substituted in a scalar context):
+.Pp
+.Bl -tag -width Ds -compact
+.It Pf ${# Ns Ar name Ns \&}
+The number of positional parameters if
+.Ar name
+is
+.Ql * ,
+.Ql @ ,
+or not specified; otherwise the length
+.Pq in characters
+of the string value of parameter
+.Ar name .
+.Pp
+.It Pf ${# Ns Ar name Ns \&[*]}
+.It Pf ${# Ns Ar name Ns \&[@]}
+The number of elements in the array
+.Ar name .
+.Pp
+.It Pf ${% Ns Ar name Ns \&}
+The width
+.Pq in screen columns
+of the string value of parameter
+.Ar name ,
+or \-1 if
+.Pf ${ Ns Ar name Ns }
+contains a control character.
+.Pp
+.It Pf ${! Ns Ar name Ns }
+The name of the variable referred to by
+.Ar name .
+This will be
+.Ar name
+except when
+.Ar name
+is a name reference (bound variable), created by the
+.Ic nameref
+command (which is an alias for
+.Ic typeset Fl n ) .
+.Pp
+.It Pf ${! Ns Ar name Ns \&[*]}
+.It Pf ${! Ns Ar name Ns \&[@]}
+The names of indices (keys) in the array
+.Ar name .
+.Pp
+.Sm off
+.It Xo
+.Pf ${ Ar name
+.Pf # Ar pattern No }
+.Xc
+.It Xo
+.Pf ${ Ar name
+.Pf ## Ar pattern No }
+.Xc
+.Sm on
+If
+.Ar pattern
+matches the beginning of the value of parameter
+.Ar name ,
+the matched text is deleted from the result of substitution.
+A single
+.Ql #
+results in the shortest match, and two
+of them result in the longest match.
+Cannot be applied to a vector
+.Pq ${*} or ${@} or ${array[*]} or ${array[@]} .
+.Pp
+.Sm off
+.It Xo
+.Pf ${ Ar name
+.Pf % Ar pattern No }
+.Xc
+.It Xo
+.Pf ${ Ar name
+.Pf %% Ar pattern No }
+.Xc
+.Sm on
+Like ${..#..} substitution, but it deletes from the end of the value.
+Cannot be applied to a vector.
+.Pp
+.Sm off
+.It Xo
+.Pf ${ Ar name
+.Pf / Ar pattern / Ar string No }
+.Xc
+.It Xo
+.Pf ${ Ar name
+.Pf // Ar pattern / Ar string No }
+.Xc
+.Sm on
+Like ${..#..} substitution, but it replaces the longest match of
+.Ar pattern ,
+anchored anywhere in the value, with
+.Ar string .
+If
+.Ar pattern
+begins with
+.Ql # ,
+it is anchored at the beginning of the value; if it begins with
+.Ql % ,
+it is anchored at the end.
+A single
+.Ql /
+replaces the first occurence of the search
+.Ar pattern ,
+and two of them replace all occurences.
+If
+.Pf / Ar string
+is omitted, the
+.Ar pattern
+is replaced by the empty string, i.e. deleted.
+Cannot be applied to a vector.
+Inefficiently implemented.
+.Pp
+.Sm off
+.It Xo
+.Pf ${ Ar name : Ns Ar pos
+.Pf : Ns Ar len Ns }
+.Xc
+.Sm on
+The first
+.Ar len
+characters of
+.Ar name ,
+starting at position
+.Ar pos ,
+are substituted.
+Both
+.Ar pos
+and
+.Pf : Ns Ar len
+are optional.
+If
+.Ar pos
+is negative, counting starts at the end of the string; if it
+is omitted, it defaults to 0.
+If
+.Ar len
+is omitted or greater than the length of the remaining string,
+all of it is substituted.
+Both
+.Ar pos
+and
+.Ar len
+are evaluated as arithmetic expressions.
+Currently,
+.Ar pos
+must start with a space, opening parenthesis or digit to be recognised.
+Cannot be applied to a vector.
+.Pp
+.It Pf ${ Ns Ar name Ns @#}
+The internal hash of the expansion of
+.Ar name .
+At the moment, this is NZAT (a never-zero 32-bit hash based on
+Bob Jenkins' one-at-a-time hash), but this is not set.
+This is the hash the shell uses internally for its associative arrays.
+.El
+.Pp
+Note that
+.Ar pattern
+may need extended globbing pattern
+.Pq @(...) ,
+single
+.Pq \&\*(aq...\&\*(aq
+or double
+.Pq \&"...\&"
+quote escaping unless
+.Fl o Ic sh
+is set.
+.Pp
+The following special parameters are implicitly set by the shell and cannot be
+set directly using assignments:
+.Bl -tag -width "1 .. 9"
+.It Ev \&!
+Process ID of the last background process started.
+If no background processes have been started, the parameter is not set.
+.It Ev \&#
+The number of positional parameters ($1, $2, etc.).
+.It Ev \&$
+The PID of the shell, or the PID of the original shell if it is a subshell.
+Do
+.Em NOT
+use this mechanism for generating temporary file names; see
+.Xr mktemp 1
+instead.
+.It Ev \-
+The concatenation of the current single letter options (see the
+.Ic set
+command below for a list of options).
+.It Ev \&?
+The exit status of the last non-asynchronous command executed.
+If the last command was killed by a signal,
+.Ic $?\&
+is set to 128 plus the signal number.
+.It Ev 0
+The name of the shell, determined as follows:
+the first argument to
+.Nm
+if it was invoked with the
+.Fl c
+option and arguments were given; otherwise the
+.Ar file
+argument, if it was supplied;
+or else the basename the shell was invoked with (i.e.\&
+.Li argv[0] ) .
+.Ev $0
+is also set to the name of the current script or
+the name of the current function, if it was defined with the
+.Ic function
+keyword (i.e. a Korn shell style function).
+.It Ev 1 No .. Ev 9
+The first nine positional parameters that were supplied to the shell, function,
+or script sourced using the
+.Sq \&.
+built-in.
+Further positional parameters may be accessed using
+.Pf ${ Ar number Ns } .
+.It Ev *
+All positional parameters (except 0), i.e. $1, $2, $3, ...
+.br
+If used
+outside of double quotes, parameters are separate words (which are subjected
+to word splitting); if used within double quotes, parameters are separated
+by the first character of the
+.Ev IFS
+parameter (or the empty string if
+.Ev IFS
+is
+.Dv NULL ) .
+.It Ev @
+Same as
+.Ic $* ,
+unless it is used inside double quotes, in which case a separate word is
+generated for each positional parameter.
+If there are no positional parameters, no word is generated.
+.Ic $@
+can be used to access arguments, verbatim, without losing
+.Dv NULL
+arguments or splitting arguments with spaces.
+.El
+.Pp
+The following parameters are set and/or used by the shell:
+.Bl -tag -width "KSH_VERSION"
+.It Ev _
+.Pq underscore
+When an external command is executed by the shell, this parameter is set in the
+environment of the new process to the path of the executed command.
+In interactive use, this parameter is also set in the parent shell to the last
+word of the previous command.
+.It Ev CDPATH
+Search path for the
+.Ic cd
+built-in command.
+It works the same way as
+.Ev PATH
+for those directories not beginning with
+.Ql /
+in
+.Ic cd
+commands.
+Note that if
+.Ev CDPATH
+is set and does not contain
+.Sq \&.
+or contains an empty path, the current directory is not searched.
+Also, the
+.Ic cd
+built-in command will display the resulting directory when a match is found
+in any search path other than the empty path.
+.It Ev COLUMNS
+Set to the number of columns on the terminal or window.
+Always set, defaults to 80, unless the
+value as reported by
+.Xr stty 1
+is non-zero and sane enough; similar for
+.Ev LINES .
+This parameter is used by the interactive line editing modes, and by the
+.Ic select ,
+.Ic set \-o ,
+and
+.Ic kill \-l
+commands to format information columns.
+.It Ev ENV
+If this parameter is found to be set after any profile files are executed, the
+expanded value is used as a shell startup file.
+It typically contains function and alias definitions.
+.It Ev ERRNO
+Integer value of the shell's
+.Va errno
+variable.
+It indicates the reason the last system call failed.
+Not yet implemented.
+.It Ev EXECSHELL
+If set, this parameter is assumed to contain the shell that is to be used to
+execute commands that
+.Xr execve 2
+fails to execute and which do not start with a
+.Dq #! Ns Ar shell
+sequence.
+.It Ev FCEDIT
+The editor used by the
+.Ic fc
+command (see below).
+.It Ev FPATH
+Like
+.Ev PATH ,
+but used when an undefined function is executed to locate the file defining the
+function.
+It is also searched when a command can't be found using
+.Ev PATH .
+See
+.Sx Functions
+below for more information.
+.It Ev HISTFILE
+The name of the file used to store command history.
+When assigned to, history is loaded from the specified file.
+Also, several invocations of the shell will share history if their
+.Ev HISTFILE
+parameters all point to the same file.
+.Pp
+.Sy Note :
+If
+.Ev HISTFILE
+isn't set, no history file is used.
+This is different from
+.At
+.Nm ksh .
+.It Ev HISTSIZE
+The number of commands normally stored for history.
+The default is 500.
+.It Ev HOME
+The default directory for the
+.Ic cd
+command and the value substituted for an unqualified
+.Ic \*(TI
+(see
+.Sx Tilde expansion
+below).
+.It Ev IFS
+Internal field separator, used during substitution and by the
+.Ic read
+command, to split values into distinct arguments; normally set to space, tab,
+and newline.
+See
+.Sx Substitution
+above for details.
+.Pp
+.Sy Note :
+This parameter is not imported from the environment when the shell is
+started.
+.It Ev KSHEGID
+The effective group id of the shell.
+.It Ev KSHGID
+The real group id of the shell.
+.It Ev KSHUID
+The real user id of the shell.
+.It Ev KSH_VERSION
+The name and version of the shell (read-only).
+See also the version commands in
+.Sx Emacs editing mode
+and
+.Sx Vi editing mode
+sections, below.
+.It Ev LINENO
+The line number of the function or shell script that is currently being
+executed.
+.It Ev LINES
+Set to the number of lines on the terminal or window.
+Always set, defaults to 24.
+.It Ev OLDPWD
+The previous working directory.
+Unset if
+.Ic cd
+has not successfully changed directories since the shell started, or if the
+shell doesn't know where it is.
+.It Ev OPTARG
+When using
+.Ic getopts ,
+it contains the argument for a parsed option, if it requires one.
+.It Ev OPTIND
+The index of the next argument to be processed when using
+.Ic getopts .
+Assigning 1 to this parameter causes
+.Ic getopts
+to process arguments from the beginning the next time it is invoked.
+.It Ev PATH
+A colon separated list of directories that are searched when looking for
+commands and files sourced using the
+.Sq \&.
+command (see below).
+An empty string resulting from a leading or trailing
+colon, or two adjacent colons, is treated as a
+.Sq \&.
+(the current directory).
+.It Ev PGRP
+The process ID of the shell's process group leader.
+.It Ev PIPESTATUS
+An array containing the errorlevel (exit status) codes,
+one by one, of the last pipeline run in the foreground.
+.It Ev PPID
+The process ID of the shell's parent.
+.It Ev PS1
+The primary prompt for interactive shells.
+Parameter, command, and arithmetic
+substitutions are performed, and
+.Ql \&!
+is replaced with the current command number (see the
+.Ic fc
+command below).
+A literal
+.Ql \&!
+can be put in the prompt by placing
+.Ql !!
+in
+.Ev PS1 .
+.Pp
+The default prompt is
+.Sq $\ \&
+for non-root users,
+.Sq #\ \&
+for root.
+If
+.Nm
+is invoked by root and
+.Ev PS1
+does not contain a
+.Sq #
+character, the default value will be used even if
+.Ev PS1
+already exists in the environment.
+.Pp
+The
+.Nm
+distribution comes with a sample
+.Pa dot.mkshrc
+containing a sophisticated example, but you might like the following one
+(note that ${HOSTNAME:=$(hostname)} and the
+root-vs-user distinguishing clause are (in this example) executed at
+.Ev PS1
+assignment time, while the $USER and $PWD are escaped
+and thus will be evaluated each time a prompt is displayed):
+.Bd -literal
+PS1=\*(aq${USER:=$(id \-un)}\*(aq"@${HOSTNAME:=$(hostname)}:\e$PWD $(
+       if (( USER_ID )); then print \e$; else print \e#; fi) "
+.Ed
+.Pp
+Note that since the command-line editors try to figure out how long the prompt
+is (so they know how far it is to the edge of the screen), escape codes in
+the prompt tend to mess things up.
+You can tell the shell not to count certain
+sequences (such as escape codes) by prefixing your prompt with a
+character (such as Ctrl-A) followed by a carriage return and then delimiting
+the escape codes with this character.
+Any occurences of that character in the prompt are not printed.
+By the way, don't blame me for
+this hack; it's derived from the original
+.Xr ksh88 1 ,
+which did print the delimiter character so you were out of luck
+if you did not have any non-printing characters.
+.Pp
+Since Backslashes and other special characters may be
+interpreted by the shell, to set
+.Ev PS1
+either escape the backslash itself,
+or use double quotes.
+The latter is more practical.
+This is a more complex example,
+avoiding to directly enter special characters (for example with
+.Ic \*(haV
+in the emacs editing mode),
+which embeds the current working directory,
+in reverse video
+.Pq colour would work, too ,
+in the prompt string:
+.Bd -literal -offset indent
+x=$(print \e\e001)
+PS1="$x$(print \e\er)$x$(tput smso)$x\e$PWD$x$(tput rmso)$x\*(Gt "
+.Ed
+.Pp
+Due to pressure from David G. Korn,
+.Nm
+now also supports the following form:
+.Bd -literal -offset indent
+PS1=$'\e1\er\e1\ee[7m\e1$PWD\e1\ee[0m\e1\*(Gt '
+.Ed
+.It Ev PS2
+Secondary prompt string, by default
+.Sq \*(Gt\ \& ,
+used when more input is needed to complete a command.
+.It Ev PS3
+Prompt used by the
+.Ic select
+statement when reading a menu selection.
+The default is
+.Sq #?\ \& .
+.It Ev PS4
+Used to prefix commands that are printed during execution tracing (see the
+.Ic set Fl x
+command below).
+Parameter, command, and arithmetic substitutions are performed
+before it is printed.
+The default is
+.Sq +\ \& .
+.It Ev PWD
+The current working directory.
+May be unset or
+.Dv NULL
+if the shell doesn't know where it is.
+.It Ev RANDOM
+Each time
+.Ev RANDOM
+is referenced, it is assigned a number between 0 and 32767 from
+a Linear Congruential PRNG first.
+.It Ev REPLY
+Default parameter for the
+.Ic read
+command if no names are given.
+Also used in
+.Ic select
+loops to store the value that is read from standard input.
+.It Ev SECONDS
+The number of seconds since the shell started or, if the parameter has been
+assigned an integer value, the number of seconds since the assignment plus the
+value that was assigned.
+.It Ev TMOUT
+If set to a positive integer in an interactive shell, it specifies the maximum
+number of seconds the shell will wait for input after printing the primary
+prompt
+.Pq Ev PS1 .
+If the time is exceeded, the shell exits.
+.It Ev TMPDIR
+The directory temporary shell files are created in.
+If this parameter is not
+set, or does not contain the absolute path of a writable directory, temporary
+files are created in
+.Pa /tmp .
+.It Ev USER_ID
+The effective user id of the shell.
+.El
+.Ss Tilde expansion
+Tilde expansion which is done in parallel with parameter substitution, is done
+on words starting with an unquoted
+.Ql \*(TI .
+The characters following the tilde, up to the first
+.Ql / ,
+if any, are assumed to be a login name.
+If the login name is empty,
+.Ql + ,
+or
+.Ql \- ,
+the value of the
+.Ev HOME ,
+.Ev PWD ,
+or
+.Ev OLDPWD
+parameter is substituted, respectively.
+Otherwise, the password file is
+searched for the login name, and the tilde expression is substituted with the
+user's home directory.
+If the login name is not found in the password file or
+if any quoting or parameter substitution occurs in the login name, no
+substitution is performed.
+.Pp
+In parameter assignments
+(such as those preceding a simple-command or those occurring
+in the arguments of
+.Ic alias ,
+.Ic export ,
+.Ic global ,
+.Ic readonly ,
+and
+.Ic typeset ) ,
+tilde expansion is done after any assignment
+(i.e. after the equals sign)
+or after an unquoted colon
+.Pq Sq \&: ;
+login names are also delimited by colons.
+.Pp
+The home directory of previously expanded login names are cached and re-used.
+The
+.Ic alias \-d
+command may be used to list, change, and add to this cache (e.g.\&
+.Ic alias \-d fac=/usr/local/facilities; cd \*(TIfac/bin ) .
+.Ss Brace expansion (alteration)
+Brace expressions take the following form:
+.Bd -unfilled -offset indent
+.Sm off
+.Xo
+.Ar prefix No { Ar str1 No ,...,
+.Ar strN No } Ar suffix
+.Xc
+.Sm on
+.Ed
+.Pp
+The expressions are expanded to
+.Ar N
+words, each of which is the concatenation of
+.Ar prefix ,
+.Ar str Ns i ,
+and
+.Ar suffix
+(e.g.\&
+.Dq a{c,b{X,Y},d}e
+expands to four words:
+.Dq ace ,
+.Dq abXe ,
+.Dq abYe ,
+and
+.Dq ade ) .
+As noted in the example, brace expressions can be nested and the resulting
+words are not sorted.
+Brace expressions must contain an unquoted comma
+.Pq Sq \&,
+for expansion to occur (e.g.\&
+.Ic {}
+and
+.Ic {foo}
+are not expanded).
+Brace expansion is carried out after parameter substitution
+and before file name generation.
+.Ss File name patterns
+A file name pattern is a word containing one or more unquoted
+.Ql \&? ,
+.Ql * ,
+.Ql + ,
+.Ql @ ,
+or
+.Ql \&!
+characters or
+.Dq \&[..]
+sequences.
+Once brace expansion has been performed, the shell replaces file
+name patterns with the sorted names of all the files that match the pattern
+(if no files match, the word is left unchanged).
+The pattern elements have the following meaning:
+.Bl -tag -width Ds
+.It \&?
+Matches any single character.
+.It \&*
+Matches any sequence of octets.
+.It \&[..]
+Matches any of the octets inside the brackets.
+Ranges of octets can be specified by separating two octets by a
+.Ql \-
+(e.g.\&
+.Dq \&[a0\-9]
+matches the letter
+.Sq a
+or any digit).
+In order to represent itself, a
+.Ql \-
+must either be quoted or the first or last octet in the octet list.
+Similarly, a
+.Ql \&]
+must be quoted or the first octet in the list if it is to represent itself
+instead of the end of the list.
+Also, a
+.Ql \&!
+appearing at the start of the list has special meaning (see below), so to
+represent itself it must be quoted or appear later in the list.
+.It \&[!..]
+Like [..],
+except it matches any octet not inside the brackets.
+.Sm off
+.It *( Ar pattern\*(Ba No ...\*(Ba Ar pattern )
+.Sm on
+Matches any string of octets that matches zero or more occurrences of the
+specified patterns.
+Example: The pattern
+.Ic *(foo\*(Babar)
+matches the strings
+.Dq ,
+.Dq foo ,
+.Dq bar ,
+.Dq foobarfoo ,
+etc.
+.Sm off
+.It +( Ar pattern\*(Ba No ...\*(Ba Ar pattern )
+.Sm on
+Matches any string of octets that matches one or more occurrences of the
+specified patterns.
+Example: The pattern
+.Ic +(foo\*(Babar)
+matches the strings
+.Dq foo ,
+.Dq bar ,
+.Dq foobar ,
+etc.
+.Sm off
+.It ?( Ar pattern\*(Ba No ...\*(Ba Ar pattern )
+.Sm on
+Matches the empty string or a string that matches one of the specified
+patterns.
+Example: The pattern
+.Ic ?(foo\*(Babar)
+only matches the strings
+.Dq ,
+.Dq foo ,
+and
+.Dq bar .
+.Sm off
+.It @( Ar pattern\*(Ba No ...\*(Ba Ar pattern )
+.Sm on
+Matches a string that matches one of the specified patterns.
+Example: The pattern
+.Ic @(foo\*(Babar)
+only matches the strings
+.Dq foo
+and
+.Dq bar .
+.Sm off
+.It !( Ar pattern\*(Ba No ...\*(Ba Ar pattern )
+.Sm on
+Matches any string that does not match one of the specified patterns.
+Examples: The pattern
+.Ic !(foo\*(Babar)
+matches all strings except
+.Dq foo
+and
+.Dq bar ;
+the pattern
+.Ic !(*)
+matches no strings; the pattern
+.Ic !(?)*\&
+matches all strings (think about it).
+.El
+.Pp
+Note that
+.Nm mksh
+.Po and Nm pdksh Pc
+never matches
+.Sq \&.
+and
+.Sq .. ,
+but
+.At
+.Nm ksh ,
+Bourne
+.Nm sh ,
+and GNU
+.Nm bash
+do.
+.Pp
+Note that none of the above pattern elements match either a period
+.Pq Sq \&.
+at the start of a file name or a slash
+.Pq Sq / ,
+even if they are explicitly used in a [..] sequence; also, the names
+.Sq \&.
+and
+.Sq ..
+are never matched, even by the pattern
+.Sq .* .
+.Pp
+If the
+.Ic markdirs
+option is set, any directories that result from file name generation are marked
+with a trailing
+.Ql / .
+.Ss Input/output redirection
+When a command is executed, its standard input, standard output, and standard
+error (file descriptors 0, 1, and 2, respectively) are normally inherited from
+the shell.
+Three exceptions to this are commands in pipelines, for which
+standard input and/or standard output are those set up by the pipeline,
+asynchronous commands created when job control is disabled, for which standard
+input is initially set to be from
+.Pa /dev/null ,
+and commands for which any of the following redirections have been specified:
+.Bl -tag -width XXxxmarker
+.It \*(Gt Ar file
+Standard output is redirected to
+.Ar file .
+If
+.Ar file
+does not exist, it is created; if it does exist, is a regular file, and the
+.Ic noclobber
+option is set, an error occurs; otherwise, the file is truncated.
+Note that this means the command
+.Ic cmd \*(Ltfoo \*(Gtfoo
+will open
+.Ar foo
+for reading and then truncate it when it opens it for writing, before
+.Ar cmd
+gets a chance to actually read
+.Ar foo .
+.It \*(Gt\*(Ba Ar file
+Same as
+.Ic \*(Gt ,
+except the file is truncated, even if the
+.Ic noclobber
+option is set.
+.It \*(Gt\*(Gt Ar file
+Same as
+.Ic \*(Gt ,
+except if
+.Ar file
+exists it is appended to instead of being truncated.
+Also, the file is opened
+in append mode, so writes always go to the end of the file (see
+.Xr open 2 ) .
+.It \*(Lt Ar file
+Standard input is redirected from
+.Ar file ,
+which is opened for reading.
+.It \*(Lt\*(Gt Ar file
+Same as
+.Ic \*(Lt ,
+except the file is opened for reading and writing.
+.It \*(Lt\*(Lt Ar marker
+After reading the command line containing this kind of redirection (called a
+.Dq here document ) ,
+the shell copies lines from the command source into a temporary file until a
+line matching
+.Ar marker
+is read.
+When the command is executed, standard input is redirected from the
+temporary file.
+If
+.Ar marker
+contains no quoted characters, the contents of the temporary file are processed
+as if enclosed in double quotes each time the command is executed, so
+parameter, command, and arithmetic substitutions are performed, along with
+backslash
+.Pq Sq \e
+escapes for
+.Ql $ ,
+.Ql \` ,
+.Ql \e ,
+and
+.Ql \enewline ,
+but not for
+.Ql \&" .
+If multiple here documents are used on the same command line, they are saved in
+order.
+.Pp
+If no
+.Ar marker
+is given, the here document ends at the next
+.Ic \*(Lt\*(Lt
+and substitution will be performed.
+If
+.Ar marker
+is only a set of either single
+.Dq \*(aq\*(aq
+or double
+.Sq \&""
+quotes with nothing in between, the here document ends at the next empty line
+and substitution will not be performed.
+.It \*(Lt\*(Lt\- Ar marker
+Same as
+.Ic \*(Lt\*(Lt ,
+except leading tabs are stripped from lines in the here document.
+.It \*(Lt\*(Lt\*(Lt Ar word
+Same as
+.Ic \*(Lt\*(Lt ,
+except that
+.Ar word
+.Em is
+the here document.
+This is called a here string.
+.It \*(Lt& Ar fd
+Standard input is duplicated from file descriptor
+.Ar fd .
+.Ar fd
+can be a number, indicating the number of an existing file descriptor;
+the letter
+.Ql p ,
+indicating the file descriptor associated with the output of the current
+co-process; or the character
+.Ql \- ,
+indicating standard input is to be closed.
+Note that
+.Ar fd
+is limited to a single digit in most shell implementations.
+.It \*(Gt& Ar fd
+Same as
+.Ic \*(Lt& ,
+except the operation is done on standard output.
+.It &\*(Gt Ar file
+Same as
+.Ic \*(Gt Ar file 2\*(Gt&1 .
+This is a GNU
+.Nm bash
+extension supported by
+.Nm
+which also supports the preceding explicit fd number, for example,
+.Ic 3&\*(Gt Ar file
+is the same as
+.Ic 3\*(Gt Ar file 2\*(Gt&3
+in
+.Nm
+but a syntax error in GNU
+.Nm bash .
+.It Xo
+.No &\*(Gt\*(Ba Ar file ,
+.No &\*(Gt\*(Gt Ar file ,
+.No &\*(Gt& Ar fd
+.Xc
+Same as
+.Ic \*(Gt\*(Ba Ar file ,
+.Ic \*(Gt\*(Gt Ar file ,
+or
+.Ic \*(Gt& Ar fd ,
+followed by
+.Ic 2\*(Gt&1 ,
+as above.
+These are
+.Nm
+extensions.
+.El
+.Pp
+In any of the above redirections, the file descriptor that is redirected
+(i.e. standard input or standard output)
+can be explicitly given by preceding the
+redirection with a number (portably, only a single digit).
+Parameter, command, and arithmetic
+substitutions, tilde substitutions, and (if the shell is interactive)
+file name generation are all performed on the
+.Ar file ,
+.Ar marker ,
+and
+.Ar fd
+arguments of redirections.
+Note, however, that the results of any file name
+generation are only used if a single file is matched; if multiple files match,
+the word with the expanded file name generation characters is used.
+Note
+that in restricted shells, redirections which can create files cannot be used.
+.Pp
+For simple-commands, redirections may appear anywhere in the command; for
+compound-commands
+.Po
+.Ic if
+statements, etc.
+.Pc ,
+any redirections must appear at the end.
+Redirections are processed after
+pipelines are created and in the order they are given, so the following
+will print an error with a line number prepended to it:
+.Pp
+.D1 $ cat /foo/bar 2\*(Gt&1 \*(Gt/dev/null \*(Ba pr \-n \-t
+.Pp
+File descriptors created by input/output redirections are private to the
+Korn shell, but passed to sub-processes if
+.Fl o Ic posix
+or
+.Fl o Ic sh
+is set.
+.Ss Arithmetic expressions
+Integer arithmetic expressions can be used with the
+.Ic let
+command, inside $((..)) expressions, inside array references (e.g.\&
+.Ar name Ns Bq Ar expr ) ,
+as numeric arguments to the
+.Ic test
+command, and as the value of an assignment to an integer parameter.
+.Pp
+Expressions are calculated using signed arithmetic and the
+.Vt mksh_ari_t
+type (a 32-bit signed integer), unless they begin with a sole
+.Sq #
+character, in which case they use
+.Vt mksh_uari_t
+.Po a 32-bit unsigned integer Pc .
+.Pp
+Expressions may contain alpha-numeric parameter identifiers, array references,
+and integer constants and may be combined with the following C operators
+(listed and grouped in increasing order of precedence):
+.Pp
+Unary operators:
+.Bd -literal -offset indent
++ \- ! \*(TI ++ \-\-
+.Ed
+.Pp
+Binary operators:
+.Bd -literal -offset indent
+,
+= *= /= %= += \-= \*(Lt\*(Lt= \*(Gt\*(Gt= &= \*(ha= \*(Ba=
+\*(Ba\*(Ba
+&&
+\*(Ba
+\*(ha
+&
+== !=
+\*(Lt \*(Lt= \*(Gt= \*(Gt
+\*(Lt\*(Lt \*(Gt\*(Gt
++ \-
+* / %
+.Ed
+.Pp
+Ternary operators:
+.Bd -literal -offset indent
+?: (precedence is immediately higher than assignment)
+.Ed
+.Pp
+Grouping operators:
+.Bd -literal -offset indent
+( )
+.Ed
+.Pp
+Integer constants and expressions are calculated using the
+.Vt mksh_ari_t
+.Po if signed Pc
+or
+.Vt mksh_uari_t
+.Po if unsigned Pc
+type, and are limited to 32 bits.
+Overflows wrap silently.
+Integer constants may be specified with arbitrary bases using the notation
+.Ar base Ns # Ns Ar number ,
+where
+.Ar base
+is a decimal integer specifying the base, and
+.Ar number
+is a number in the specified base.
+Additionally,
+integers may be prefixed with
+.Sq 0X
+or
+.Sq 0x
+(specifying base 16), similar to
+.At
+.Nm ksh ,
+or
+.Sq 0
+(base 8), as an
+.Nm
+extension, in all forms of arithmetic expressions,
+except as numeric arguments to the
+.Ic test
+command.
+As a special
+.Nm mksh
+extension, numbers to the base of one are treated as either (8-bit
+transparent) ASCII or Unicode codepoints, depending on the shell's
+.Ic utf8\-mode
+flag (current setting).
+The
+.At
+.Nm ksh93
+syntax of
+.Dq \*(aqx\*(aq
+instead of
+.Dq 1#x
+is also supported.
+Note that NUL bytes (integral value of zero) cannot be used.
+In Unicode mode, raw octets are mapped into the range EF80..EFFF as in
+OPTU-8, which is in the PUA and has been assigned by CSUR for this use.
+If more than one octet in ASCII mode, or a sequence of more than one
+octet not forming a valid and minimal CESU-8 sequence is passed, the
+behaviour is undefined (usually, the shell aborts with a parse error,
+but rarely, it succeeds, e.g. on the sequence C2 20).
+That's why you should always use ASCII mode unless you know that the
+input is well-formed UTF-8 in the range of 0000..FFFD.
+.Pp
+The operators are evaluated as follows:
+.Bl -tag -width Ds -offset indent
+.It unary +
+Result is the argument (included for completeness).
+.It unary \-
+Negation.
+.It \&!
+Logical NOT;
+the result is 1 if argument is zero, 0 if not.
+.It \*(TI
+Arithmetic (bit-wise) NOT.
+.It ++
+Increment; must be applied to a parameter (not a literal or other expression).
+The parameter is incremented by 1.
+When used as a prefix operator, the result
+is the incremented value of the parameter; when used as a postfix operator, the
+result is the original value of the parameter.
+.It \-\-
+Similar to
+.Ic ++ ,
+except the parameter is decremented by 1.
+.It \&,
+Separates two arithmetic expressions; the left-hand side is evaluated first,
+then the right.
+The result is the value of the expression on the right-hand side.
+.It =
+Assignment; the variable on the left is set to the value on the right.
+.It Xo
+.No *= /= += \-= \*(Lt\*(Lt=
+.No \*(Gt\*(Gt= &= \*(ha= \*(Ba=
+.Xc
+Assignment operators.
+.Sm off
+.Ao Ar var Ac Xo
+.Aq Ar op
+.No = Aq Ar expr
+.Xc
+.Sm on
+is the same as
+.Sm off
+.Ao Ar var Ac Xo
+.No = Aq Ar var
+.Aq Ar op
+.Aq Ar expr ,
+.Xc
+.Sm on
+with any operator precedence in
+.Aq Ar expr
+preserved.
+For example,
+.Dq var1 *= 5 + 3
+is the same as specifying
+.Dq var1 = var1 * (5 + 3) .
+.It \*(Ba\*(Ba
+Logical OR;
+the result is 1 if either argument is non-zero, 0 if not.
+The right argument is evaluated only if the left argument is zero.
+.It &&
+Logical AND;
+the result is 1 if both arguments are non-zero, 0 if not.
+The right argument is evaluated only if the left argument is non-zero.
+.It \*(Ba
+Arithmetic (bit-wise) OR.
+.It \*(ha
+Arithmetic (bit-wise) XOR
+(exclusive-OR).
+.It &
+Arithmetic (bit-wise) AND.
+.It ==
+Equal; the result is 1 if both arguments are equal, 0 if not.
+.It !=
+Not equal; the result is 0 if both arguments are equal, 1 if not.
+.It \*(Lt
+Less than; the result is 1 if the left argument is less than the right, 0 if
+not.
+.It \*(Lt= \*(Gt= \*(Gt
+Less than or equal, greater than or equal, greater than.
+See
+.Ic \*(Lt .
+.It \*(Lt\*(Lt \*(Gt\*(Gt
+Shift left (right); the result is the left argument with its bits shifted left
+(right) by the amount given in the right argument.
+.It + \- * /
+Addition, subtraction, multiplication, and division.
+.It %
+Remainder; the result is the remainder of the division of the left argument by
+the right.
+The sign of the result is unspecified if either argument is negative.
+.It Xo
+.Sm off
+.Aq Ar arg1 ?
+.Aq Ar arg2 :
+.Aq Ar arg3
+.Sm on
+.Xc
+If
+.Aq Ar arg1
+is non-zero, the result is
+.Aq Ar arg2 ;
+otherwise the result is
+.Aq Ar arg3 .
+.El
+.Ss Co-processes
+A co-process (which is a pipeline created with the
+.Sq \*(Ba&
+operator) is an asynchronous process that the shell can both write to (using
+.Ic print \-p )
+and read from (using
+.Ic read \-p ) .
+The input and output of the co-process can also be manipulated using
+.Ic \*(Gt&p
+and
+.Ic \*(Lt&p
+redirections, respectively.
+Once a co-process has been started, another can't
+be started until the co-process exits, or until the co-process's input has been
+redirected using an
+.Ic exec Ar n Ns Ic \*(Gt&p
+redirection.
+If a co-process's input is redirected in this way, the next
+co-process to be started will share the output with the first co-process,
+unless the output of the initial co-process has been redirected using an
+.Ic exec Ar n Ns Ic \*(Lt&p
+redirection.
+.Pp
+Some notes concerning co-processes:
+.Bl -bullet
+.It
+The only way to close the co-process's input (so the co-process reads an
+end-of-file) is to redirect the input to a numbered file descriptor and then
+close that file descriptor:
+.Ic exec 3\*(Gt&p; exec 3\*(Gt&\-
+.It
+In order for co-processes to share a common output, the shell must keep the
+write portion of the output pipe open.
+This means that end-of-file will not be
+detected until all co-processes sharing the co-process's output have exited
+(when they all exit, the shell closes its copy of the pipe).
+This can be
+avoided by redirecting the output to a numbered file descriptor (as this also
+causes the shell to close its copy).
+Note that this behaviour is slightly
+different from the original Korn shell which closes its copy of the write
+portion of the co-process output when the most recently started co-process
+(instead of when all sharing co-processes) exits.
+.It
+.Ic print \-p
+will ignore
+.Dv SIGPIPE
+signals during writes if the signal is not being trapped or ignored; the same
+is true if the co-process input has been duplicated to another file descriptor
+and
+.Ic print \-u Ns Ar n
+is used.
+.El
+.Ss Functions
+Functions are defined using either Korn shell
+.Ic function Ar function-name
+syntax or the Bourne/POSIX shell
+.Ar function-name Ns \&()
+syntax (see below for the difference between the two forms).
+Functions are like
+.Li .\(hyscripts
+(i.e. scripts sourced using the
+.Sq \&.
+built-in)
+in that they are executed in the current environment.
+However, unlike
+.Li .\(hyscripts ,
+shell arguments (i.e. positional parameters $1, $2, etc.)\&
+are never visible inside them.
+When the shell is determining the location of a command, functions
+are searched after special built-in commands, before regular and
+non-regular built-ins, and before the
+.Ev PATH
+is searched.
+.Pp
+An existing function may be deleted using
+.Ic unset Fl f Ar function-name .
+A list of functions can be obtained using
+.Ic typeset +f
+and the function definitions can be listed using
+.Ic typeset \-f .
+The
+.Ic autoload
+command (which is an alias for
+.Ic typeset \-fu )
+may be used to create undefined functions: when an undefined function is
+executed, the shell searches the path specified in the
+.Ev FPATH
+parameter for a file with the same name as the function which, if found, is
+read and executed.
+If after executing the file the named function is found to
+be defined, the function is executed; otherwise, the normal command search is
+continued (i.e. the shell searches the regular built-in command table and
+.Ev PATH ) .
+Note that if a command is not found using
+.Ev PATH ,
+an attempt is made to autoload a function using
+.Ev FPATH
+(this is an undocumented feature of the original Korn shell).
+.Pp
+Functions can have two attributes,
+.Dq trace
+and
+.Dq export ,
+which can be set with
+.Ic typeset \-ft
+and
+.Ic typeset \-fx ,
+respectively.
+When a traced function is executed, the shell's
+.Ic xtrace
+option is turned on for the function's duration.
+The
+.Dq export
+attribute of functions is currently not used.
+In the original Korn shell,
+exported functions are visible to shell scripts that are executed.
+.Pp
+Since functions are executed in the current shell environment, parameter
+assignments made inside functions are visible after the function completes.
+If this is not the desired effect, the
+.Ic typeset
+command can be used inside a function to create a local parameter.
+Note that
+.At
+.Nm ksh93
+uses static scoping (one global scope, one local scope per function), whereas
+.Nm mksh
+uses dynamic scoping (nested scopes of varying locality).
+Note that special parameters (e.g.\&
+.Ic \&$$ , $! )
+can't be scoped in this way.
+.Pp
+The exit status of a function is that of the last command executed in the
+function.
+A function can be made to finish immediately using the
+.Ic return
+command; this may also be used to explicitly specify the exit status.
+.Pp
+Functions defined with the
+.Ic function
+reserved word are treated differently in the following ways from functions
+defined with the
+.Ic \&()
+notation:
+.Bl -bullet
+.It
+The $0 parameter is set to the name of the function
+(Bourne-style functions leave $0 untouched).
+.It
+Parameter assignments preceding function calls are not kept in the shell
+environment (executing Bourne-style functions will keep assignments).
+.It
+.Ev OPTIND
+is saved/reset and restored on entry and exit from the function so
+.Ic getopts
+can be used properly both inside and outside the function (Bourne-style
+functions leave
+.Ev OPTIND
+untouched, so using
+.Ic getopts
+inside a function interferes with using
+.Ic getopts
+outside the function).
+.It
+Bourne-style function definitions take precedence over alias dereferences
+and remove alias definitions upon encounter, while aliases take precedence
+over Korn-style functions.
+.El
+.Pp
+In the future, the following differences will also be added:
+.Bl -bullet
+.It
+A separate trap/signal environment will be used during the execution of
+functions.
+This will mean that traps set inside a function will not affect the
+shell's traps and signals that are not ignored in the shell (but may be
+trapped) will have their default effect in a function.
+.It
+The EXIT trap, if set in a function, will be executed after the function
+returns.
+.El
+.Ss Command execution
+After evaluation of command-line arguments, redirections, and parameter
+assignments, the type of command is determined: a special built-in, a
+function, a regular built-in, or the name of a file to execute found using the
+.Ev PATH
+parameter.
+The checks are made in the above order.
+Special built-in commands differ from other commands in that the
+.Ev PATH
+parameter is not used to find them, an error during their execution can
+cause a non-interactive shell to exit, and parameter assignments that are
+specified before the command are kept after the command completes.
+Regular built-in commands are different only in that the
+.Ev PATH
+parameter is not used to find them.
+.Pp
+The original
+.Nm ksh
+and POSIX differ somewhat in which commands are considered
+special or regular:
+.Pp
+POSIX special commands
+.Pp
+.Ic \&. , \&: , break , continue ,
+.Ic eval , exec , exit , export ,
+.Ic readonly , return , set , shift ,
+.Ic trap , unset , wait
+.Pp
+Additional
+.Nm
+special commands
+.Pp
+.Ic builtin , global , times , typeset
+.Pp
+Very special commands
+.Pq non-POSIX
+.Pp
+.Ic alias , readonly , set , typeset
+.Pp
+POSIX regular commands
+.Pp
+.Ic alias , bg , cd , command ,
+.Ic false , fc , fg , getopts ,
+.Ic jobs , kill , read , true ,
+.Ic umask , unalias
+.Pp
+Additional
+.Nm
+regular commands
+.Pp
+.Ic \&[ , chdir , bind , cat ,
+.Ic echo , let , mknod , print ,
+.Ic printf , pwd , realpath , rename ,
+.Ic sleep , test , ulimit , whence
+.Pp
+In the future, the additional
+.Nm
+special and regular commands may be treated
+differently from the POSIX special and regular commands.
+.Pp
+Once the type of command has been determined, any command-line parameter
+assignments are performed and exported for the duration of the command.
+.Pp
+The following describes the special and regular built-in commands:
+.Pp
+.Bl -tag -width false -compact
+.It Ic \&. Ar file Op Ar arg ...
+This is called the
+.Dq dot
+command.
+Execute the commands in
+.Ar file
+in the current environment.
+The file is searched for in the directories of
+.Ev PATH .
+If arguments are given, the positional parameters may be used to access them
+while
+.Ar file
+is being executed.
+If no arguments are given, the positional parameters are
+those of the environment the command is used in.
+.Pp
+.It Ic \&: Op Ar ...
+The null command.
+Exit status is set to zero.
+.Pp
+.It Xo Ic alias
+.Oo Fl d \*(Ba t Oo Fl r Oc \*(Ba
+.Cm +\-x Oc
+.Op Fl p
+.Op Cm +
+.Oo Ar name
+.Op Ns = Ns Ar value
+.Ar ... Oc
+.Xc
+Without arguments,
+.Ic alias
+lists all aliases.
+For any name without a value, the existing alias is listed.
+Any name with a value defines an alias (see
+.Sx Aliases
+above).
+.Pp
+When listing aliases, one of two formats is used.
+Normally, aliases are listed as
+.Ar name Ns = Ns Ar value ,
+where
+.Ar value
+is quoted.
+If options were preceded with
+.Ql + ,
+or a lone
+.Ql +
+is given on the command line, only
+.Ar name
+is printed.
+.Pp
+The
+.Fl d
+option causes directory aliases which are used in tilde expansion to be
+listed or set (see
+.Sx Tilde expansion
+above).
+.Pp
+If the
+.Fl p
+option is used, each alias is prefixed with the string
+.Dq alias\ \& .
+.Pp
+The
+.Fl t
+option indicates that tracked aliases are to be listed/set (values specified on
+the command line are ignored for tracked aliases).
+The
+.Fl r
+option indicates that all tracked aliases are to be reset.
+.Pp
+The
+.Fl x
+option sets
+.Pq Ic +x No clears
+the export attribute of an alias, or, if no names are given, lists the aliases
+with the export attribute (exporting an alias has no effect).
+.Pp
+.It Ic bg Op Ar job ...
+Resume the specified stopped job(s) in the background.
+If no jobs are specified,
+.Ic %+
+is assumed.
+See
+.Sx Job control
+below for more information.
+.Pp
+.It Ic bind Op Fl l
+The current bindings are listed.
+If the
+.Fl l
+flag is given,
+.Ic bind
+instead lists the names of the functions to which keys may be bound.
+See
+.Sx Emacs editing mode
+for more information.
+.Pp
+.It Xo Ic bind Op Fl m
+.Ar string Ns = Ns Op Ar substitute
+.Ar ...
+.Xc
+.It Xo Ic bind
+.Ar string Ns = Ns Op Ar editing-command
+.Ar ...
+.Xc
+The specified editing command is bound to the given
+.Ar string ,
+which should consist of a control character
+optionally preceded by one of the two prefix characters
+and optionally succeded by a tilde character.
+Future input of the
+.Ar string
+will cause the editing command to be immediately invoked.
+If the
+.Fl m
+flag is given, the specified input
+.Ar string
+will afterwards be immediately replaced by the given
+.Ar substitute
+string which may contain editing commands but not other macros.
+If a tilde postfix is given, a tilde trailing the one or
+two prefices and the control character is ignored, any
+other trailing character will be processed afterwards.
+.Pp
+Control characters may be written using caret notation
+i.e. \*(haX represents Ctrl-X.
+Note that although only two prefix characters (usually ESC and \*(haX)
+are supported, some multi-character sequences can be supported.
+.Pp
+The following default bindings show how the arrow keys, the home, end and
+delete key on a BSD wsvt25, xterm\-xfree86 or GNU screen terminal are bound
+(of course some escape sequences won't work out quite this nicely):
+.Bd -literal -offset indent
+bind \*(aq\*(haX\*(aq=prefix\-2
+bind \*(aq\*(ha[[\*(aq=prefix\-2
+bind \*(aq\*(haXA\*(aq=up\-history
+bind \*(aq\*(haXB\*(aq=down\-history
+bind \*(aq\*(haXC\*(aq=forward\-char
+bind \*(aq\*(haXD\*(aq=backward\-char
+bind \*(aq\*(haX1\*(TI\*(aq=beginning\-of\-line
+bind \*(aq\*(haX7\*(TI\*(aq=beginning\-of\-line
+bind \*(aq\*(haXH\*(aq=beginning\-of\-line
+bind \*(aq\*(haX4\*(TI\*(aq=end\-of\-line
+bind \*(aq\*(haX8\*(TI\*(aq=end\-of\-line
+bind \*(aq\*(haXF\*(aq=end\-of\-line
+bind \*(aq\*(haX3\*(TI\*(aq=delete\-char\-forward
+.Ed
+.Pp
+.It Ic break Op Ar level
+Exit the
+.Ar level Ns th
+inner-most
+.Ic for ,
+.Ic select ,
+.Ic until ,
+or
+.Ic while
+loop.
+.Ar level
+defaults to 1.
+.Pp
+.It Xo
+.Ic builtin
+.Op Fl \-
+.Ar command Op Ar arg ...
+.Xc
+Execute the built-in command
+.Ar command .
+.Pp
+.It Xo
+.Ic cat
+.Op Fl u
+.Op Ar
+.Xc
+Read files sequentially, in command line order, and write them to
+standard output.
+If a
+.Ar file
+is a single dash
+.Pq Sq -
+or absent, read from standard input.
+Unless compiled with
+.Dv MKSH_NO_EXTERNAL_CAT ,
+if any options are given, an external
+.Xr cat 1
+utility is invoked instead if called from the shell.
+For direct builtin calls, the
+.Tn POSIX
+.Fl u
+option is supported as a no-op.
+.Pp
+.It Xo
+.Ic cd
+.Op Fl L
+.Op Ar dir
+.Xc
+.It Xo
+.Ic cd
+.Fl P Op Fl e
+.Op Ar dir
+.Xc
+.It Xo
+.Ic chdir
+.Op Fl eLP
+.Op Ar dir
+.Xc
+Set the working directory to
+.Ar dir .
+If the parameter
+.Ev CDPATH
+is set, it lists the search path for the directory containing
+.Ar dir .
+A
+.Dv NULL
+path means the current directory.
+If
+.Ar dir
+is found in any component of the
+.Ev CDPATH
+search path other than the
+.Dv NULL
+path, the name of the new working directory will be written to standard output.
+If
+.Ar dir
+is missing, the home directory
+.Ev HOME
+is used.
+If
+.Ar dir
+is
+.Ql \- ,
+the previous working directory is used (see the
+.Ev OLDPWD
+parameter).
+.Pp
+If the
+.Fl L
+option (logical path) is used or if the
+.Ic physical
+option isn't set (see the
+.Ic set
+command below), references to
+.Sq ..
+in
+.Ar dir
+are relative to the path used to get to the directory.
+If the
+.Fl P
+option (physical path) is used or if the
+.Ic physical
+option is set,
+.Sq ..
+is relative to the filesystem directory tree.
+The
+.Ev PWD
+and
+.Ev OLDPWD
+parameters are updated to reflect the current and old working directory,
+respectively.
+If the
+.Fl e
+option is set for physical filesystem traversal, and
+.Ev PWD
+could not be set, the exit code is 1; greater than 1 if an
+error occurred, 0 otherwise.
+.Pp
+.It Xo
+.Ic cd
+.Op Fl eLP
+.Ar old new
+.Xc
+.It Xo
+.Ic chdir
+.Op Fl eLP
+.Ar old new
+.Xc
+The string
+.Ar new
+is substituted for
+.Ar old
+in the current directory, and the shell attempts to change to the new
+directory.
+.Pp
+.It Xo
+.Ic command
+.Op Fl pVv
+.Ar cmd
+.Op Ar arg ...
+.Xc
+If neither the
+.Fl v
+nor
+.Fl V
+option is given,
+.Ar cmd
+is executed exactly as if
+.Ic command
+had not been specified, with two exceptions:
+firstly,
+.Ar cmd
+cannot be a shell function;
+and secondly, special built-in commands lose their specialness
+(i.e. redirection and utility errors do not cause the shell to
+exit, and command assignments are not permanent).
+.Pp
+If the
+.Fl p
+option is given, a default search path is used instead of the current value of
+.Ev PATH ,
+the actual value of which is system dependent.
+.Pp
+If the
+.Fl v
+option is given, instead of executing
+.Ar cmd ,
+information about what would be executed is given (and the same is done for
+.Ar arg ... ) .
+For special and regular built-in commands and functions, their names are simply
+printed; for aliases, a command that defines them is printed; and for commands
+found by searching the
+.Ev PATH
+parameter, the full path of the command is printed.
+If no command is found
+(i.e. the path search fails), nothing is printed and
+.Ic command
+exits with a non-zero status.
+The
+.Fl V
+option is like the
+.Fl v
+option, except it is more verbose.
+.Pp
+.It Ic continue Op Ar level
+Jumps to the beginning of the
+.Ar level Ns th
+inner-most
+.Ic for ,
+.Ic select ,
+.Ic until ,
+or
+.Ic while
+loop.
+.Ar level
+defaults to 1.
+.Pp
+.It Xo
+.Ic echo
+.Op Fl Een
+.Op Ar arg ...
+.Xc
+.Em Warning:
+this utility is not portable; use the Korn shell builtin
+.Ic print
+or the much slower POSIX utility
+.Ic printf
+instead.
+.Pp
+Prints its arguments (separated by spaces) followed by a newline, to the
+standard output.
+The newline is suppressed if any of the arguments contain the
+backslash sequence
+.Ql \ec .
+See the
+.Ic print
+command below for a list of other backslash sequences that are recognised.
+.Pp
+The options are provided for compatibility with
+.Bx
+shell scripts.
+The
+.Fl n
+option suppresses the trailing newline,
+.Fl e
+enables backslash interpretation (a no-op, since this is normally done), and
+.Fl E
+suppresses backslash interpretation.
+.Pp
+If the
+.Ic posix
+or
+.Ic sh
+option is set or this is a direct builtin call, only the first argument
+is treated as an option, and only if it is exactly
+.Dq Fl n .
+Backslash interpretation is disabled.
+.Pp
+.It Ic eval Ar command ...
+The arguments are concatenated (with spaces between them) to form a single
+string which the shell then parses and executes in the current environment.
+.Pp
+.It Xo
+.Ic exec
+.Op Ar command Op Ar arg ...
+.Xc
+The command is executed without forking, replacing the shell process.
+.Pp
+If no command is given except for I/O redirection, the I/O redirection is
+permanent and the shell is
+not replaced.
+Any file descriptors greater than 2 which are opened or
+.Xr dup 2 Ns 'd
+in this way are not made available to other executed commands (i.e. commands
+that are not built-in to the shell).
+Note that the Bourne shell differs here;
+it does pass these file descriptors on.
+.Pp
+.It Ic exit Op Ar status
+The shell exits with the specified exit status.
+If
+.Ar status
+is not specified, the exit status is the current value of the
+.Ic $?\&
+parameter.
+.Pp
+.It Xo
+.Ic export
+.Op Fl p
+.Op Ar parameter Ns Op = Ns Ar value
+.Xc
+Sets the export attribute of the named parameters.
+Exported parameters are passed in the environment to executed commands.
+If values are specified, the named parameters are also assigned.
+.Pp
+If no parameters are specified, the names of all parameters with the export
+attribute are printed one per line, unless the
+.Fl p
+option is used, in which case
+.Ic export
+commands defining all exported parameters, including their values, are printed.
+.Pp
+.It Ic false
+A command that exits with a non-zero status.
+.Pp
+.It Xo
+.Ic fc
+.Oo Fl e Ar editor \*(Ba
+.Fl l Op Fl n Oc
+.Op Fl r
+.Op Ar first Op Ar last
+.Xc
+.Ar first
+and
+.Ar last
+select commands from the history.
+Commands can be selected by history number
+or a string specifying the most recent command starting with that string.
+The
+.Fl l
+option lists the command on standard output, and
+.Fl n
+inhibits the default command numbers.
+The
+.Fl r
+option reverses the order of the list.
+Without
+.Fl l ,
+the selected commands are edited by the editor specified with the
+.Fl e
+option, or if no
+.Fl e
+is specified, the editor specified by the
+.Ev FCEDIT
+parameter (if this parameter is not set,
+.Pa /bin/ed
+is used), and then executed by the shell.
+.Pp
+.It Xo
+.Ic fc
+.Cm \-e \- \*(Ba Fl s
+.Op Fl g
+.Op Ar old Ns = Ns Ar new
+.Op Ar prefix
+.Xc
+Re-execute the selected command (the previous command by default) after
+performing the optional substitution of
+.Ar old
+with
+.Ar new .
+If
+.Fl g
+is specified, all occurrences of
+.Ar old
+are replaced with
+.Ar new .
+The meaning of
+.Cm \-e \-
+and
+.Fl s
+is identical: re-execute the selected command without invoking an editor.
+This command is usually accessed with the predefined
+.Ic alias r=\*(aqfc \-e \-\*(aq
+or by prefixing an interactive mode input line with
+.Sq \&!
+.Pq wbx extension .
+.Pp
+.It Ic fg Op Ar job ...
+Resume the specified job(s) in the foreground.
+If no jobs are specified,
+.Ic %+
+is assumed.
+See
+.Sx Job control
+below for more information.
+.Pp
+.It Xo
+.Ic getopts
+.Ar optstring name
+.Op Ar arg ...
+.Xc
+Used by shell procedures to parse the specified arguments (or positional
+parameters, if no arguments are given) and to check for legal options.
+.Ar optstring
+contains the option letters that
+.Ic getopts
+is to recognise.
+If a letter is followed by a colon, the option is expected to
+have an argument.
+Options that do not take arguments may be grouped in a single argument.
+If an option takes an argument and the option character is not the
+last character of the argument it is found in, the remainder of the argument is
+taken to be the option's argument; otherwise, the next argument is the option's
+argument.
+.Pp
+Each time
+.Ic getopts
+is invoked, it places the next option in the shell parameter
+.Ar name
+and the index of the argument to be processed by the next call to
+.Ic getopts
+in the shell parameter
+.Ev OPTIND .
+If the option was introduced with a
+.Ql + ,
+the option placed in
+.Ar name
+is prefixed with a
+.Ql + .
+When an option requires an argument,
+.Ic getopts
+places it in the shell parameter
+.Ev OPTARG .
+.Pp
+When an illegal option or a missing option argument is encountered, a question
+mark or a colon is placed in
+.Ar name
+(indicating an illegal option or missing argument, respectively) and
+.Ev OPTARG
+is set to the option character that caused the problem.
+Furthermore, if
+.Ar optstring
+does not begin with a colon, a question mark is placed in
+.Ar name ,
+.Ev OPTARG
+is unset, and an error message is printed to standard error.
+.Pp
+When the end of the options is encountered,
+.Ic getopts
+exits with a non-zero exit status.
+Options end at the first (non-option
+argument) argument that does not start with a
+.Ql \- ,
+or when a
+.Ql \-\-
+argument is encountered.
+.Pp
+Option parsing can be reset by setting
+.Ev OPTIND
+to 1 (this is done automatically whenever the shell or a shell procedure is
+invoked).
+.Pp
+Warning: Changing the value of the shell parameter
+.Ev OPTIND
+to a value other than 1, or parsing different sets of arguments without
+resetting
+.Ev OPTIND ,
+may lead to unexpected results.
+.Pp
+.It Xo
+.Ic hash
+.Op Fl r
+.Op Ar name ...
+.Xc
+Without arguments, any hashed executable command pathnames are listed.
+The
+.Fl r
+option causes all hashed commands to be removed from the hash table.
+Each
+.Ar name
+is searched as if it were a command name and added to the hash table if it is
+an executable command.
+.Pp
+.It Xo
+.Ic jobs
+.Op Fl lnp
+.Op Ar job ...
+.Xc
+Display information about the specified job(s); if no jobs are specified, all
+jobs are displayed.
+The
+.Fl n
+option causes information to be displayed only for jobs that have changed
+state since the last notification.
+If the
+.Fl l
+option is used, the process ID of each process in a job is also listed.
+The
+.Fl p
+option causes only the process group of each job to be printed.
+See
+.Sx Job control
+below for the format of
+.Ar job
+and the displayed job.
+.Pp
+.It Xo
+.Ic kill
+.Oo Fl s Ar signame \*(Ba
+.No \- Ns Ar signum \*(Ba
+.No \- Ns Ar signame Oc
+.No { Ar job \*(Ba pid \*(Ba pgrp No }
+.Ar ...
+.Xc
+Send the specified signal to the specified jobs, process IDs, or process
+groups.
+If no signal is specified, the
+.Dv TERM
+signal is sent.
+If a job is specified, the signal is sent to the job's process group.
+See
+.Sx Job control
+below for the format of
+.Ar job .
+.Pp
+.It Xo
+.Ic kill
+.Fl l
+.Op Ar exit-status ...
+.Xc
+Print the signal name corresponding to
+.Ar exit-status .
+If no arguments are specified, a list of all the signals, their numbers, and
+a short description of them are printed.
+.Pp
+.It Ic let Op Ar expression ...
+Each expression is evaluated (see
+.Sx Arithmetic expressions
+above).
+If all expressions are successfully evaluated, the exit status is 0 (1)
+if the last expression evaluated to non-zero (zero).
+If an error occurs during
+the parsing or evaluation of an expression, the exit status is greater than 1.
+Since expressions may need to be quoted,
+.No \&(( Ar expr No ))
+is syntactic sugar for
+.No let \&" Ns Ar expr Ns \&" .
+.Pp
+.It Xo
+.Ic mknod
+.Op Fl m Ar mode
+.Ar name
+.Cm b\*(Bac
+.Ar major minor
+.Xc
+.It Xo
+.Ic mknod
+.Op Fl m Ar mode
+.Ar name
+.Cm p
+.Xc
+Create a device special file.
+The file type may be
+.Cm b
+(block type device),
+.Cm c
+(character type device),
+or
+.Cm p
+(named pipe).
+The file created may be modified according to its
+.Ar mode
+(via the
+.Fl m
+option),
+.Ar major
+(major device number),
+and
+.Ar minor
+(minor device number).
+.Pp
+See
+.Xr mknod 8
+for further information.
+.Pp
+.It Xo
+.Ic print
+.Oo Fl nprsu Ns Oo Ar n Oc \*(Ba
+.Fl R Op Fl en Oc
+.Op Ar argument ...
+.Xc
+.Ic print
+prints its arguments on the standard output, separated by spaces and
+terminated with a newline.
+The
+.Fl n
+option suppresses the newline.
+By default, certain C escapes are translated.
+These include these mentioned in
+.Sx Backslash expansion
+above, as well as
+.Ql \ec ,
+which is equivalent to using the
+.Fl n
+option.
+Backslash expansion may be inhibited with the
+.Fl r
+option.
+The
+.Fl s
+option prints to the history file instead of standard output; the
+.Fl u
+option prints to file descriptor
+.Ar n
+.Po
+.Ar n
+defaults to 1 if omitted
+.Pc ;
+and the
+.Fl p
+option prints to the co-process (see
+.Sx Co-processes
+above).
+.Pp
+The
+.Fl R
+option is used to emulate, to some degree, the
+.Bx
+.Xr echo 1
+command which does not process
+.Ql \e
+sequences unless the
+.Fl e
+option is given.
+As above, the
+.Fl n
+option suppresses the trailing newline.
+.Pp
+.It Ic printf Ar format Op Ar arguments ...
+Formatted output.
+Approximately the same as the utility
+.Ic printf ,
+except that it uses the same
+.Sx Backslash expansion
+and I/O code as the rest of
+.Nm mksh .
+This is not normally part of
+.Nm mksh ;
+however, distributors may have added this as builtin as a speed hack.
+.Pp
+.It Ic pwd Op Fl LP
+Print the present working directory.
+If the
+.Fl L
+option is used or if the
+.Ic physical
+option isn't set (see the
+.Ic set
+command below), the logical path is printed (i.e. the path used to
+.Ic cd
+to the current directory).
+If the
+.Fl P
+option (physical path) is used or if the
+.Ic physical
+option is set, the path determined from the filesystem (by following
+.Sq ..
+directories to the root directory) is printed.
+.Pp
+.It Xo
+.Ic read
+.Op Fl A | Fl a
+.Op Fl d Ar x
+.Oo Fl N Ar z \*(Ba
+.Fl n Ar z Oc
+.Oo Fl p \*(Ba
+.Fl u Ns Op Ar n
+.Oc Op Fl t Ar n
+.Op Fl rs
+.Op Ar p ...
+.Xc
+Reads a line of input, separates the input into fields using the
+.Ev IFS
+parameter (see
+.Sx Substitution
+above), and assigns each field to the specified parameters
+.Ar p .
+If no parameters are specified, the
+.Ev REPLY
+parameter is used to store the result.
+With the
+.Fl A
+and
+.Fl a
+options, only no or one parameter is accepted.
+If there are more parameters than fields, the extra parameters are set to
+the empty string or 0; if there are more fields than parameters, the last
+parameter is assigned the remaining fields (including the word separators).
+.Pp
+The options are as follows:
+.Bl -tag -width XuXnX
+.It Fl A
+Store the result into the parameter
+.Ar p
+(or
+.Ev REPLY )
+as array of words.
+.It Fl a
+Store the result without word splitting into the parameter
+.Ar p
+(or
+.Ev REPLY )
+as array of characters (wide characters if the
+.Ic utf8\-mode
+option is enacted, octets otherwise).
+.It Fl d Ar x
+Use the first byte of
+.Ar x ,
+.Dv NUL
+if empty, instead of the ASCII newline character as input line delimiter.
+.It Fl N Ar z
+Instead of reading till end-of-line, read exactly
+.Ar z
+bytes; less if EOF or a timeout occurs.
+.It Fl n Ar z
+Instead of reading till end-of-line, read up to
+.Ar z
+bytes but return as soon as any bytes are read, e.g.\& from a
+slow terminal device, or if EOF or a timeout occurs.
+.It Fl p
+Read from the currently active co-process, see
+.Sx Co-processes
+above for details on this.
+.It Fl u Ns Op Ar n
+Read from the file descriptor
+.Ar n
+(defaults to 0, i.e.\& standard input).
+The argument must immediately follow the option character.
+.It Fl t Ar n
+Interrupt reading after
+.Ar n
+seconds (specified as positive decimal value with an optional fractional part).
+.It Fl r
+Normally, the ASCII backslash character escapes the special
+meaning of the following character and is stripped from the input;
+.Ic read
+does not stop when encountering a backslash-newline sequence and
+does not store that newline in the result.
+This option enables raw mode, in which backslashes are not processed.
+.It Fl s
+The input line is saved to the history.
+.El
+.Pp
+If the input is a terminal, both the
+.Fl N
+and
+.Fl n
+options set it into raw mode;
+they read an entire file if \-1 is passed as
+.Ar z
+argument.
+.Pp
+The first parameter may have a question mark and a string appended to it, in
+which case the string is used as a prompt (printed to standard error before
+any input is read) if the input is a
+.Xr tty 4
+(e.g.\&
+.Ic read nfoo?\*(aqnumber of foos: \*(aq ) .
+.Pp
+If no input is read or a timeout occurred,
+.Ic read
+exits with a non-zero status.
+.Pp
+Another handy set of tricks:
+If
+.Ic read
+is run in a loop such as
+.Ic while read foo; do ...; done
+then leading whitespace will be removed (IFS) and backslashes processed.
+You might want to use
+.Ic while IFS= read \-r foo; do ...; done
+for pristine I/O.
+Similarily, when using the
+.Fl a
+option, use of the
+.Fl r
+option might be prudent; the same applies for:
+.Bd -literal -offset indent
+find . \-type f \-print0 \*(Ba \e
+    while IFS= read \-d \*(aq\*(aq \-r filename; do
+       print \-r \-\- "found <${filename#./}>"
+done
+.Ed
+.Pp
+The inner loop will be executed in a subshell and variable changes
+cannot be propagated if executed in a pipeline:
+.Bd -literal -offset indent
+bar \*(Ba baz \*(Ba while read foo; do ...; done
+.Ed
+.Pp
+Use co-processes instead:
+.Bd -literal -offset indent
+bar \*(Ba baz \*(Ba&
+while read \-p foo; do ...; done
+exec 3\*(Gt&p; exec 3\*(Gt&\-
+.Ed
+.Pp
+.It Xo
+.Ic readonly
+.Op Fl p
+.Oo Ar parameter
+.Op Ns = Ns Ar value
+.Ar ... Oc
+.Xc
+Sets the read-only attribute of the named parameters.
+If values are given,
+parameters are set to them before setting the attribute.
+Once a parameter is
+made read-only, it cannot be unset and its value cannot be changed.
+.Pp
+If no parameters are specified, the names of all parameters with the read-only
+attribute are printed one per line, unless the
+.Fl p
+option is used, in which case
+.Ic readonly
+commands defining all read-only parameters, including their values, are
+printed.
+.Pp
+.It Xo
+.Ic realpath
+.Op Fl \-
+.Ar name
+.Xc
+Prints the resolved absolute pathname corresponding to
+.Ar name .
+If
+.Ar name
+ends with a slash
+.Pq Sq / ,
+it's also checked for existence and whether it is a directory; otherwise,
+.Ic realpath
+returns 0 if the pathname either exists or can be created immediately,
+i.e. all but the last component exist and are directories.
+.Pp
+.It Xo
+.Ic rename
+.Op Fl \-
+.Ar from to
+.Xc
+Renames the file
+.Ar from
+to
+.Ar to .
+Both must be complete pathnames and on the same device.
+This builtin is intended for emergency situations where
+.Pa /bin/mv
+becomes unusable, and directly calls
+.Xr rename 2 .
+.Pp
+.It Ic return Op Ar status
+Returns from a function or
+.Ic .\&
+script, with exit status
+.Ar status .
+If no
+.Ar status
+is given, the exit status of the last executed command is used.
+If used outside of a function or
+.Ic .\&
+script, it has the same effect as
+.Ic exit .
+Note that
+.Nm
+treats both profile and
+.Ev ENV
+files as
+.Ic .\&
+scripts, while the original Korn shell only treats profiles as
+.Ic .\&
+scripts.
+.Pp
+.It Xo
+.Ic set Op Ic +\-abCefhiklmnprsUuvXx
+.Op Ic +\-o Ar option
+.Op Ic +\-A Ar name
+.Op Fl \-
+.Op Ar arg ...
+.Xc
+The
+.Ic set
+command can be used to set
+.Pq Ic \-
+or clear
+.Pq Ic +
+shell options, set the positional parameters, or set an array parameter.
+Options can be changed using the
+.Cm +\-o Ar option
+syntax, where
+.Ar option
+is the long name of an option, or using the
+.Cm +\- Ns Ar letter
+syntax, where
+.Ar letter
+is the option's single letter name (not all options have a single letter name).
+The following table lists both option letters (if they exist) and long names
+along with a description of what the option does:
+.Bl -tag -width 3n
+.It Fl A Ar name
+Sets the elements of the array parameter
+.Ar name
+to
+.Ar arg ...
+If
+.Fl A
+is used, the array is reset (i.e. emptied) first; if
+.Ic +A
+is used, the first N elements are set (where N is the number of arguments);
+the rest are left untouched.
+.Pp
+An alternative syntax for the command
+.Ic set \-A foo \-\- a b c
+which is compatible to
+.Tn GNU
+.Nm bash
+and also supported by
+.At
+.Nm ksh93
+is:
+.Ic foo=(a b c); foo+=(d e)
+.Pp
+Another
+.At
+.Nm ksh93
+and
+.Tn GNU
+.Nm bash
+extension allows specifying the indices used for
+.Ar arg ...
+.Pq from the above example, Ic a b c
+like this:
+.Ic set \-A foo \-\- [0]=a [1]=b [2]=c
+or
+.Ic foo=([0]=a [1]=b [2]=c)
+which can also be written
+.Ic foo=([0]=a b c)
+because indices are incremented automatically.
+.It Fl a \*(Ba Fl o Ic allexport
+All new parameters are created with the export attribute.
+.It Fl b \*(Ba Fl o Ic notify
+Print job notification messages asynchronously, instead of just before the
+prompt.
+Only used if job control is enabled
+.Pq Fl m .
+.It Fl C \*(Ba Fl o Ic noclobber
+Prevent \*(Gt redirection from overwriting existing files.
+Instead, \*(Gt\*(Ba must be used to force an overwrite.
+.It Fl e \*(Ba Fl o Ic errexit
+Exit (after executing the
+.Dv ERR
+trap) as soon as an error occurs or a command fails (i.e. exits with a
+non-zero status).
+This does not apply to commands whose exit status is
+explicitly tested by a shell construct such as
+.Ic if ,
+.Ic until ,
+.Ic while ,
+.Ic && ,
+.Ic \*(Ba\*(Ba ,
+or
+.Ic !\&
+statements.
+.It Fl f \*(Ba Fl o Ic noglob
+Do not expand file name patterns.
+.It Fl h \*(Ba Fl o Ic trackall
+Create tracked aliases for all executed commands (see
+.Sx Aliases
+above).
+Enabled by default for non-interactive shells.
+.It Fl i \*(Ba Fl o Ic interactive
+The shell is an interactive shell.
+This option can only be used when the shell is invoked.
+See above for a description of what this means.
+.It Fl k \*(Ba Fl o Ic keyword
+Parameter assignments are recognised anywhere in a command.
+.It Fl l \*(Ba Fl o Ic login
+The shell is a login shell.
+This option can only be used when the shell is invoked.
+See above for a description of what this means.
+.It Fl m \*(Ba Fl o Ic monitor
+Enable job control (default for interactive shells).
+.It Fl n \*(Ba Fl o Ic noexec
+Do not execute any commands.
+Useful for checking the syntax of scripts
+(ignored if interactive).
+.It Fl p \*(Ba Fl o Ic privileged
+The shell is a privileged shell.
+It is set automatically if, when the shell starts,
+the real UID or GID does not match
+the effective UID (EUID) or GID (EGID), respectively.
+See above for a description of what this means.
+.It Fl r \*(Ba Fl o Ic restricted
+The shell is a restricted shell.
+This option can only be used when the shell is invoked.
+See above for a description of what this means.
+.It Fl s \*(Ba Fl o Ic stdin
+If used when the shell is invoked, commands are read from standard input.
+Set automatically if the shell is invoked with no arguments.
+.Pp
+When
+.Fl s
+is used with the
+.Ic set
+command it causes the specified arguments to be sorted before assigning them to
+the positional parameters (or to array
+.Ar name ,
+if
+.Fl A
+is used).
+.It Fl U \*(Ba Fl o Ic utf8\-mode
+Enable UTF-8 support in the
+.Sx Emacs editing mode
+and internal string handling functions.
+This flag is disabled by default, but can be enabled by setting it on the
+shell command line; is enabled automatically for interactive shells if
+requested at compile time, your system supports
+.Fn setlocale LC_CTYPE \&""
+and optionally
+.Fn nl_langinfo CODESET ,
+or the
+.Ev LC_ALL ,
+.Ev LC_CTYPE ,
+or
+.Ev LANG
+environment variables,
+and at least one of these returns something that matches
+.Dq UTF\-8
+or
+.Dq utf8
+case-insensitively; for direct builtin calls depending on the
+aforementioned environment variables; or for stdin or scripts,
+if the input begins with a UTF-8 Byte Order Mark.
+.It Fl u \*(Ba Fl o Ic nounset
+Referencing of an unset parameter, other than
+.Dq $@
+or
+.Dq $* ,
+is treated as an error, unless one of the
+.Ql \- ,
+.Ql + ,
+or
+.Ql =
+modifiers is used.
+.It Fl v \*(Ba Fl o Ic verbose
+Write shell input to standard error as it is read.
+.It Fl X \*(Ba Fl o Ic markdirs
+Mark directories with a trailing
+.Ql /
+during file name generation.
+.It Fl x \*(Ba Fl o Ic xtrace
+Print commands and parameter assignments when they are executed, preceded by
+the value of
+.Ev PS4 .
+.It Fl o Ic bgnice
+Background jobs are run with lower priority.
+.It Fl o Ic braceexpand
+Enable brace expansion (a.k.a. alternation).
+This is enabled by default.
+If disabled, tilde expansion after an equals sign is disabled as a side effect.
+.It Fl o Ic emacs
+Enable BRL emacs-like command-line editing (interactive shells only); see
+.Sx Emacs editing mode .
+.It Fl o Ic gmacs
+Enable gmacs-like command-line editing (interactive shells only).
+Currently identical to emacs editing except that transpose\-chars (\*(haT) acts
+slightly differently.
+.It Fl o Ic ignoreeof
+The shell will not (easily) exit when end-of-file is read;
+.Ic exit
+must be used.
+To avoid infinite loops, the shell will exit if
+.Dv EOF
+is read 13 times in a row.
+.It Fl o Ic nohup
+Do not kill running jobs with a
+.Dv SIGHUP
+signal when a login shell exits.
+Currently set by default, but this may
+change in the future to be compatible with
+.At
+.Nm ksh ,
+which
+doesn't have this option, but does send the
+.Dv SIGHUP
+signal.
+.It Fl o Ic nolog
+No effect.
+In the original Korn shell, this prevents function definitions from
+being stored in the history file.
+.It Fl o Ic physical
+Causes the
+.Ic cd
+and
+.Ic pwd
+commands to use
+.Dq physical
+(i.e. the filesystem's)
+.Sq ..
+directories instead of
+.Dq logical
+directories (i.e. the shell handles
+.Sq .. ,
+which allows the user to be oblivious of symbolic links to directories).
+Clear by default.
+Note that setting this option does not affect the current value of the
+.Ev PWD
+parameter; only the
+.Ic cd
+command changes
+.Ev PWD .
+See the
+.Ic cd
+and
+.Ic pwd
+commands above for more details.
+.It Fl o Ic posix
+Enable a somewhat more
+.Px
+ish mode.
+As a side effect, setting this flag turns off
+.Ic braceexpand
+mode, which can be turned back on manually, and
+.Ic sh
+mode.
+.It Fl o Ic sh
+Enable
+.Pa /bin/sh
+.Pq kludge
+mode.
+Automatically enabled if the basename of the shell invocation begins with
+.Dq sh
+and this autodetection feature is compiled in
+.Pq not in MirBSD .
+As a side effect, setting this flag turns off
+.Ic braceexpand
+mode, which can be turned back on manually, and
+.Ic posix
+mode.
+.It Fl o Ic vi
+Enable
+.Xr vi 1 Ns -like
+command-line editing (interactive shells only).
+.It Fl o Ic vi\-esccomplete
+In vi command-line editing, do command and file name completion when escape
+(\*(ha[) is entered in command mode.
+.It Fl o Ic vi\-tabcomplete
+In vi command-line editing, do command and file name completion when tab (\*(haI)
+is entered in insert mode.
+This is the default.
+.It Fl o Ic viraw
+No effect.
+In the original Korn shell, unless
+.Ic viraw
+was set, the vi command-line mode would let the
+.Xr tty 4
+driver do the work until ESC (\*(ha[) was entered.
+.Nm
+is always in viraw mode.
+.El
+.Pp
+These options can also be used upon invocation of the shell.
+The current set of
+options (with single letter names) can be found in the parameter
+.Sq $\- .
+.Ic set Fl o
+with no option name will list all the options and whether each is on or off;
+.Ic set +o
+will print the long names of all options that are currently on.
+.Pp
+Remaining arguments, if any, are positional parameters and are assigned, in
+order, to the positional parameters (i.e. $1, $2, etc.).
+If options end with
+.Ql \-\-
+and there are no remaining arguments, all positional parameters are cleared.
+If no options or arguments are given, the values of all names are printed.
+For unknown historical reasons, a lone
+.Ql \-
+option is treated specially \*(en it clears both the
+.Fl v
+and
+.Fl x
+options.
+.Pp
+.It Ic shift Op Ar number
+The positional parameters
+.Ar number Ns +1 ,
+.Ar number Ns +2 ,
+etc. are renamed to
+.Sq 1 ,
+.Sq 2 ,
+etc.
+.Ar number
+defaults to 1.
+.Pp
+.It Ic sleep Ar seconds
+Suspends execution for a minimum of the
+.Ar seconds
+specified as positive decimal value with an optional fractional part.
+Signal delivery may continue execution earlier.
+.Pp
+.It Ic source Ar file Op Ar arg ...
+Like
+.Ic \&. Po Do dot Dc Pc ,
+except that the current working directory is appended to the
+.Ev PATH
+in GNU
+.Nm bash
+and
+.Nm mksh .
+In
+.Nm ksh93
+and
+.Nm mksh ,
+this is implemented as a shell alias instead of a builtin.
+.Pp
+.It Ic test Ar expression
+.It Ic \&[ Ar expression Ic \&]
+.Ic test
+evaluates the
+.Ar expression
+and returns zero status if true, 1 if false, or greater than 1 if there
+was an error.
+It is normally used as the condition command of
+.Ic if
+and
+.Ic while
+statements.
+Symbolic links are followed for all
+.Ar file
+expressions except
+.Fl h
+and
+.Fl L .
+.Pp
+The following basic expressions are available:
+.Bl -tag -width 17n
+.It Fl a Ar file
+.Ar file
+exists.
+.It Fl b Ar file
+.Ar file
+is a block special device.
+.It Fl c Ar file
+.Ar file
+is a character special device.
+.It Fl d Ar file
+.Ar file
+is a directory.
+.It Fl e Ar file
+.Ar file
+exists.
+.It Fl f Ar file
+.Ar file
+is a regular file.
+.It Fl G Ar file
+.Ar file Ns 's
+group is the shell's effective group ID.
+.It Fl g Ar file
+.Ar file Ns 's
+mode has the setgid bit set.
+.It Fl H Ar file
+.Ar file
+is a context dependent directory (only useful on HP-UX).
+.It Fl h Ar file
+.Ar file
+is a symbolic link.
+.It Fl k Ar file
+.Ar file Ns 's
+mode has the
+.Xr sticky 8
+bit set.
+.It Fl L Ar file
+.Ar file
+is a symbolic link.
+.It Fl O Ar file
+.Ar file Ns 's
+owner is the shell's effective user ID.
+.It Fl o Ar option
+Shell
+.Ar option
+is set (see the
+.Ic set
+command above for a list of options).
+As a non-standard extension, if the option starts with a
+.Ql \&! ,
+the test is negated; the test always fails if
+.Ar option
+doesn't exist (so [ \-o foo \-o \-o !foo ] returns true if and only if option
+.Ar foo
+exists).
+The same can be achieved with [ \-o ?foo ] like in
+.At
+.Nm ksh93 .
+.Ar option
+can also be the short flag led by either
+.Ql \-
+or
+.Ql +
+.Pq no logical negation ,
+for example
+.Ql \-x
+or
+.Ql +x
+instead of
+.Ql xtrace .
+.It Fl p Ar file
+.Ar file
+is a named pipe.
+.It Fl r Ar file
+.Ar file
+exists and is readable.
+.It Fl S Ar file
+.Ar file
+is a
+.Xr unix 4 Ns -domain
+socket.
+.It Fl s Ar file
+.Ar file
+is not empty.
+.It Fl t Ar fd
+File descriptor
+.Ar fd
+is a
+.Xr tty 4
+device.
+.It Fl u Ar file
+.Ar file Ns 's
+mode has the setuid bit set.
+.It Fl w Ar file
+.Ar file
+exists and is writable.
+.It Fl x Ar file
+.Ar file
+exists and is executable.
+.It Ar file1 Fl nt Ar file2
+.Ar file1
+is newer than
+.Ar file2
+or
+.Ar file1
+exists and
+.Ar file2
+does not.
+.It Ar file1 Fl ot Ar file2
+.Ar file1
+is older than
+.Ar file2
+or
+.Ar file2
+exists and
+.Ar file1
+does not.
+.It Ar file1 Fl ef Ar file2
+.Ar file1
+is the same file as
+.Ar file2 .
+.It Ar string
+.Ar string
+has non-zero length.
+.It Fl n Ar string
+.Ar string
+is not empty.
+.It Fl z Ar string
+.Ar string
+is empty.
+.It Ar string No = Ar string
+Strings are equal.
+.It Ar string No == Ar string
+Strings are equal.
+.It Ar string No \*(Gt Ar string
+First string operand is greater than second string operand.
+.It Ar string No \*(Lt Ar string
+First string operand is less than second string operand.
+.It Ar string No != Ar string
+Strings are not equal.
+.It Ar number Fl eq Ar number
+Numbers compare equal.
+.It Ar number Fl ne Ar number
+Numbers compare not equal.
+.It Ar number Fl ge Ar number
+Numbers compare greater than or equal.
+.It Ar number Fl gt Ar number
+Numbers compare greater than.
+.It Ar number Fl le Ar number
+Numbers compare less than or equal.
+.It Ar number Fl \&lt Ar number
+Numbers compare less than.
+.El
+.Pp
+The above basic expressions, in which unary operators have precedence over
+binary operators, may be combined with the following operators (listed in
+increasing order of precedence):
+.Bd -literal -offset indent
+expr \-o expr          Logical OR.
+expr \-a expr          Logical AND.
+! expr                 Logical NOT.
+( expr )               Grouping.
+.Ed
+.Pp
+Note that a number actually may be an arithmetic expression, such as
+a mathematical term or the name of an integer variable:
+.Bd -literal -offset indent
+x=1; [ "x" \-eq 1 ]    evaluates to true
+.Ed
+.Pp
+Note that some special rules are applied (courtesy of POSIX)
+if the number of
+arguments to
+.Ic test
+or
+.Ic \&[ ... \&]
+is less than five: if leading
+.Ql \&!
+arguments can be stripped such that only one argument remains then a string
+length test is performed (again, even if the argument is a unary operator); if
+leading
+.Ql \&!
+arguments can be stripped such that three arguments remain and the second
+argument is a binary operator, then the binary operation is performed (even
+if the first argument is a unary operator, including an unstripped
+.Ql \&! ) .
+.Pp
+.Sy Note :
+A common mistake is to use
+.Dq if \&[ $foo = bar \&]
+which fails if parameter
+.Dq foo
+is
+.Dv NULL
+or unset, if it has embedded spaces (i.e.\&
+.Ev IFS
+octets), or if it is a unary operator like
+.Sq \&!
+or
+.Sq Fl n .
+Use tests like
+.Dq if \&[ x\&"$foo\&" = x"bar" \&]
+instead, or the double-bracket operator
+.Dq if \&[[ $foo = bar \&]]
+or, to avoid pattern matching (see
+.Ic \&[[
+above):
+.Dq if \&[[ $foo = "$bar" \&]]
+.Pp
+.It Xo
+.Ic time
+.Op Fl p
+.Op Ar pipeline
+.Xc
+If a
+.Ar pipeline
+is given, the times used to execute the pipeline are reported.
+If no pipeline
+is given, then the user and system time used by the shell itself, and all the
+commands it has run since it was started, are reported.
+The times reported are the real time (elapsed time from start to finish),
+the user CPU time (time spent running in user mode), and the system CPU time
+(time spent running in kernel mode).
+Times are reported to standard error; the format of the output is:
+.Pp
+.Dl "0m0.00s real     0m0.00s user     0m0.00s system"
+.Pp
+If the
+.Fl p
+option is given the output is slightly longer:
+.Bd -literal -offset indent
+real     0.00
+user     0.00
+sys      0.00
+.Ed
+.Pp
+It is an error to specify the
+.Fl p
+option unless
+.Ar pipeline
+is a simple command.
+.Pp
+Simple redirections of standard error do not affect the output of the
+.Ic time
+command:
+.Pp
+.Dl $ time sleep 1 2\*(Gtafile
+.Dl $ { time sleep 1; } 2\*(Gtafile
+.Pp
+Times for the first command do not go to
+.Dq afile ,
+but those of the second command do.
+.Pp
+.It Ic times
+Print the accumulated user and system times used both by the shell
+and by processes that the shell started which have exited.
+The format of the output is:
+.Bd -literal -offset indent
+0m0.00s 0m0.00s
+0m0.00s 0m0.00s
+.Ed
+.Pp
+.It Ic trap Op Ar handler signal ...
+Sets a trap handler that is to be executed when any of the specified signals are
+received.
+.Ar handler
+is either a
+.Dv NULL
+string, indicating the signals are to be ignored, a minus sign
+.Pq Sq \- ,
+indicating that the default action is to be taken for the signals (see
+.Xr signal 3 ) ,
+or a string containing shell commands to be evaluated and executed at the first
+opportunity (i.e. when the current command completes, or before printing the
+next
+.Ev PS1
+prompt) after receipt of one of the signals.
+.Ar signal
+is the name of a signal (e.g.\&
+.Dv PIPE
+or
+.Dv ALRM )
+or the number of the signal (see the
+.Ic kill \-l
+command above).
+.Pp
+There are two special signals:
+.Dv EXIT
+(also known as 0) which is executed when the shell is about to exit, and
+.Dv ERR ,
+which is executed after an error occurs (an error is something that would cause
+the shell to exit if the
+.Fl e
+or
+.Ic errexit
+option were set \*(en see the
+.Ic set
+command above).
+.Dv EXIT
+handlers are executed in the environment of the last executed command.
+Note
+that for non-interactive shells, the trap handler cannot be changed for signals
+that were ignored when the shell started.
+.Pp
+With no arguments,
+.Ic trap
+lists, as a series of
+.Ic trap
+commands, the current state of the traps that have been set since the shell
+started.
+Note that the output of
+.Ic trap
+cannot be usefully piped to another process (an artifact of the fact that
+traps are cleared when subprocesses are created).
+.Pp
+The original Korn shell's
+.Dv DEBUG
+trap and the handling of
+.Dv ERR
+and
+.Dv EXIT
+traps in functions are not yet implemented.
+.Pp
+.It Ic true
+A command that exits with a zero value.
+.Pp
+.It Xo
+.Ic global
+.Oo Op Ic +\-alpnrtUux
+.Op Fl L Ns Op Ar n
+.Op Fl R Ns Op Ar n
+.Op Fl Z Ns Op Ar n
+.Op Fl i Ns Op Ar n
+.No \*(Ba Fl f Op Fl tux Oc
+.Oo Ar name
+.Op Ns = Ns Ar value
+.Ar ... Oc
+.Xc
+.It Xo
+.Ic typeset
+.Oo Op Ic +\-alpnrtUux
+.Op Fl LRZ Ns Op Ar n
+.Op Fl i Ns Op Ar n
+.No \*(Ba Fl f Op Fl tux Oc
+.Oo Ar name
+.Op Ns = Ns Ar value
+.Ar ... Oc
+.Xc
+Display or set parameter attributes.
+With no
+.Ar name
+arguments, parameter attributes are displayed; if no options are used, the
+current attributes of all parameters are printed as
+.Ic typeset
+commands; if an option is given (or
+.Ql \-
+with no option letter), all parameters and their values with the specified
+attributes are printed; if options are introduced with
+.Ql + ,
+parameter values are not printed.
+.Pp
+If
+.Ar name
+arguments are given, the attributes of the named parameters are set
+.Pq Ic \-
+or cleared
+.Pq Ic + .
+Values for parameters may optionally be specified.
+For
+.Ar name Ns \&[*] ,
+the change affects the entire array, and no value may be specified.
+.Pp
+If
+.Ic typeset
+is used inside a function, any parameters specified are localised.
+This is not done by the otherwise identical
+.Ic global .
+.Pp
+When
+.Fl f
+is used,
+.Ic typeset
+operates on the attributes of functions.
+As with parameters, if no
+.Ar name
+arguments are given,
+functions are listed with their values (i.e. definitions) unless
+options are introduced with
+.Ql + ,
+in which case only the function names are reported.
+.Bl -tag -width Ds
+.It Fl a
+Indexed array attribute.
+.It Fl f
+Function mode.
+Display or set functions and their attributes, instead of parameters.
+.It Fl i Ns Op Ar n
+Integer attribute.
+.Ar n
+specifies the base to use when displaying the integer (if not specified, the
+base given in the first assignment is used).
+Parameters with this attribute may
+be assigned values containing arithmetic expressions.
+.It Fl L Ns Op Ar n
+Left justify attribute.
+.Ar n
+specifies the field width.
+If
+.Ar n
+is not specified, the current width of a parameter (or the width of its first
+assigned value) is used.
+Leading whitespace (and zeros, if used with the
+.Fl Z
+option) is stripped.
+If necessary, values are either truncated or space padded
+to fit the field width.
+.It Fl l
+Lower case attribute.
+All upper case characters in values are converted to lower case.
+(In the original Korn shell, this parameter meant
+.Dq long integer
+when used with the
+.Fl i
+option.)
+.It Fl n
+Create a bound variable (name reference): any access to the variable
+.Ar name
+will access the variable
+.Ar value
+in the current scope (this is different from
+.At
+.Nm ksh93 ! )
+instead.
+Also different from
+.At
+.Nm ksh93
+is that
+.Ar value
+is lazily evaluated at the time
+.Ar name
+is accessed.
+This can be used by functions to access variables whose names are
+passed as parametres, instead of using
+.Ic eval .
+.It Fl p
+Print complete
+.Ic typeset
+commands that can be used to re-create the attributes and values of
+parameters.
+.It Fl R Ns Op Ar n
+Right justify attribute.
+.Ar n
+specifies the field width.
+If
+.Ar n
+is not specified, the current width of a parameter (or the width of its first
+assigned value) is used.
+Trailing whitespace is stripped.
+If necessary, values are either stripped of leading characters or space
+padded to make them fit the field width.
+.It Fl r
+Read-only attribute.
+Parameters with this attribute may not be assigned to or unset.
+Once this attribute is set, it cannot be turned off.
+.It Fl t
+Tag attribute.
+Has no meaning to the shell; provided for application use.
+.Pp
+For functions,
+.Fl t
+is the trace attribute.
+When functions with the trace attribute are executed, the
+.Ic xtrace
+.Pq Fl x
+shell option is temporarily turned on.
+.It Fl U
+Unsigned integer attribute.
+Integers are printed as unsigned values (combine with the
+.Fl i
+option).
+This option is not in the original Korn shell.
+.It Fl u
+Upper case attribute.
+All lower case characters in values are converted to upper case.
+(In the original Korn shell, this parameter meant
+.Dq unsigned integer
+when used with the
+.Fl i
+option which meant upper case letters would never be used for bases greater
+than 10.
+See the
+.Fl U
+option.)
+.Pp
+For functions,
+.Fl u
+is the undefined attribute.
+See
+.Sx Functions
+above for the implications of this.
+.It Fl x
+Export attribute.
+Parameters (or functions) are placed in the environment of
+any executed commands.
+Exported functions are not yet implemented.
+.It Fl Z Ns Op Ar n
+Zero fill attribute.
+If not combined with
+.Fl L ,
+this is the same as
+.Fl R ,
+except zero padding is used instead of space padding.
+For integers, the number instead of the base is padded.
+.El
+.Pp
+If any of the
+.\" long integer ,
+.Fl i ,
+.Fl L ,
+.Fl l ,
+.Fl R ,
+.Fl U ,
+.Fl u ,
+or
+.Fl Z
+options are changed, all others from this set are cleared,
+unless they are also given on the same command line.
+.Pp
+.It Xo
+.Ic ulimit
+.Op Fl aBCcdefHiLlMmnOPpqrSsTtVvw
+.Op Ar value
+.Xc
+Display or set process limits.
+If no options are used, the file size limit
+.Pq Fl f
+is assumed.
+.Ar value ,
+if specified, may be either an arithmetic expression or the word
+.Dq unlimited .
+The limits affect the shell and any processes created by the shell after a
+limit is imposed.
+Note that some systems may not allow limits to be increased
+once they are set.
+Also note that the types of limits available are system
+dependent \*(en some systems have only the
+.Fl f
+limit.
+.Bl -tag -width 5n
+.It Fl a
+Display all limits; unless
+.Fl H
+is used, soft limits are displayed.
+.It Fl B Ar n
+Set the socket buffer size to
+.Ar n
+kibibytes.
+.It Fl C Ar n
+Set the number of cached threads to
+.Ar n .
+.It Fl c Ar n
+Impose a size limit of
+.Ar n
+blocks on the size of core dumps.
+.It Fl d Ar n
+Impose a size limit of
+.Ar n
+kibibytes on the size of the data area.
+.It Fl e Ar n
+Set the maximum niceness to
+.Ar n .
+.It Fl f Ar n
+Impose a size limit of
+.Ar n
+blocks on files written by the shell and its child processes (files of any
+size may be read).
+.It Fl H
+Set the hard limit only (the default is to set both hard and soft limits).
+.It Fl i Ar n
+Set the number of pending signals to
+.Ar n .
+.It Fl L Ar n
+Control flocks; documentation is missing.
+.It Fl l Ar n
+Impose a limit of
+.Ar n
+kibibytes on the amount of locked (wired) physical memory.
+.It Fl M Ar n
+Set the AIO locked memory to
+.Ar n
+kibibytes.
+.It Fl m Ar n
+Impose a limit of
+.Ar n
+kibibytes on the amount of physical memory used.
+.It Fl n Ar n
+Impose a limit of
+.Ar n
+file descriptors that can be open at once.
+.It Fl O Ar n
+Set the number of AIO operations to
+.Ar n .
+.It Fl P Ar n
+Limit the number of threads per process to
+.Ar n .
+.It Fl p Ar n
+Impose a limit of
+.Ar n
+processes that can be run by the user at any one time.
+.It Fl q Ar n
+Limit the size of
+.Tn POSIX
+message queues to
+.Ar n
+bytes.
+.It Fl r Ar n
+Set the maximum real-time priority to
+.Ar n .
+.It Fl S
+Set the soft limit only (the default is to set both hard and soft limits).
+.It Fl s Ar n
+Impose a size limit of
+.Ar n
+kibibytes on the size of the stack area.
+.It Fl T Ar n
+Impose a time limit of
+.Ar n
+real seconds to be used by each process.
+.It Fl t Ar n
+Impose a time limit of
+.Ar n
+CPU seconds spent in user mode to be used by each process.
+.It Fl V Ar n
+Set the number of vnode monitors on Haiku to
+.Ar n .
+.It Fl v Ar n
+Impose a limit of
+.Ar n
+kibibytes on the amount of virtual memory (address space) used.
+.It Fl w Ar n
+Impose a limit of
+.Ar n
+kibibytes on the amount of swap space used.
+.El
+.Pp
+As far as
+.Ic ulimit
+is concerned, a block is 512 bytes.
+.Pp
+.It Xo
+.Ic umask
+.Op Fl S
+.Op Ar mask
+.Xc
+Display or set the file permission creation mask, or umask (see
+.Xr umask 2 ) .
+If the
+.Fl S
+option is used, the mask displayed or set is symbolic; otherwise, it is an
+octal number.
+.Pp
+Symbolic masks are like those used by
+.Xr chmod 1 .
+When used, they describe what permissions may be made available (as opposed to
+octal masks in which a set bit means the corresponding bit is to be cleared).
+For example,
+.Dq ug=rwx,o=
+sets the mask so files will not be readable, writable, or executable by
+.Dq others ,
+and is equivalent (on most systems) to the octal mask
+.Dq 007 .
+.Pp
+.It Xo
+.Ic unalias
+.Op Fl adt
+.Op Ar name ...
+.Xc
+The aliases for the given names are removed.
+If the
+.Fl a
+option is used, all aliases are removed.
+If the
+.Fl t
+or
+.Fl d
+options are used, the indicated operations are carried out on tracked or
+directory aliases, respectively.
+.Pp
+.It Xo
+.Ic unset
+.Op Fl fv
+.Ar parameter ...
+.Xc
+Unset the named parameters
+.Po
+.Fl v ,
+the default
+.Pc
+or functions
+.Pq Fl f .
+With
+.Ar parameter Ns \&[*] ,
+attributes are kept, only values are unset.
+.Pp
+The exit status is non-zero if any of the parameters have the read-only
+attribute set, zero otherwise.
+.Pp
+.It Ic wait Op Ar job ...
+Wait for the specified job(s) to finish.
+The exit status of
+.Ic wait
+is that of the last specified job; if the last job is killed by a signal, the
+exit status is 128 + the number of the signal (see
+.Ic kill \-l Ar exit-status
+above); if the last specified job can't be found (because it never existed, or
+had already finished), the exit status of
+.Ic wait
+is 127.
+See
+.Sx Job control
+below for the format of
+.Ar job .
+.Ic wait
+will return if a signal for which a trap has been set is received, or if a
+.Dv SIGHUP ,
+.Dv SIGINT ,
+or
+.Dv SIGQUIT
+signal is received.
+.Pp
+If no jobs are specified,
+.Ic wait
+waits for all currently running jobs (if any) to finish and exits with a zero
+status.
+If job monitoring is enabled, the completion status of jobs is printed
+(this is not the case when jobs are explicitly specified).
+.Pp
+.It Xo
+.Ic whence
+.Op Fl pv
+.Op Ar name ...
+.Xc
+For each
+.Ar name ,
+the type of command is listed (reserved word, built-in, alias,
+function, tracked alias, or executable).
+If the
+.Fl p
+option is used, a path search is performed even if
+.Ar name
+is a reserved word, alias, etc.
+Without the
+.Fl v
+option,
+.Ic whence
+is similar to
+.Ic command Fl v
+except that
+.Ic whence
+will find reserved words and won't print aliases as alias commands.
+With the
+.Fl v
+option,
+.Ic whence
+is the same as
+.Ic command Fl V .
+Note that for
+.Ic whence ,
+the
+.Fl p
+option does not affect the search path used, as it does for
+.Ic command .
+If the type of one or more of the names could not be determined, the exit
+status is non-zero.
+.El
+.Ss Job control
+Job control refers to the shell's ability to monitor and control jobs which
+are processes or groups of processes created for commands or pipelines.
+At a minimum, the shell keeps track of the status of the background (i.e.\&
+asynchronous) jobs that currently exist; this information can be displayed
+using the
+.Ic jobs
+commands.
+If job control is fully enabled (using
+.Ic set \-m
+or
+.Ic set \-o monitor ) ,
+as it is for interactive shells, the processes of a job are placed in their
+own process group.
+Foreground jobs can be stopped by typing the suspend
+character from the terminal (normally \*(haZ), jobs can be restarted in either the
+foreground or background using the
+.Ic fg
+and
+.Ic bg
+commands, and the state of the terminal is saved or restored when a foreground
+job is stopped or restarted, respectively.
+.Pp
+Note that only commands that create processes (e.g. asynchronous commands,
+subshell commands, and non-built-in, non-function commands) can be stopped;
+commands like
+.Ic read
+cannot be.
+.Pp
+When a job is created, it is assigned a job number.
+For interactive shells, this number is printed inside
+.Dq \&[..] ,
+followed by the process IDs of the processes in the job when an asynchronous
+command is run.
+A job may be referred to in the
+.Ic bg ,
+.Ic fg ,
+.Ic jobs ,
+.Ic kill ,
+and
+.Ic wait
+commands either by the process ID of the last process in the command pipeline
+(as stored in the
+.Ic $!\&
+parameter) or by prefixing the job number with a percent
+sign
+.Pq Sq % .
+Other percent sequences can also be used to refer to jobs:
+.Bl -tag -width "%+ x %% x %XX"
+.It %+ \*(Ba %% \*(Ba %
+The most recently stopped job, or, if there are no stopped jobs, the oldest
+running job.
+.It %\-
+The job that would be the
+.Ic %+
+job if the latter did not exist.
+.It % Ns Ar n
+The job with job number
+.Ar n .
+.It %? Ns Ar string
+The job with its command containing the string
+.Ar string
+(an error occurs if multiple jobs are matched).
+.It % Ns Ar string
+The job with its command starting with the string
+.Ar string
+(an error occurs if multiple jobs are matched).
+.El
+.Pp
+When a job changes state (e.g. a background job finishes or foreground job is
+stopped), the shell prints the following status information:
+.Pp
+.D1 [ Ns Ar number ] Ar flag status command
+.Pp
+where...
+.Bl -tag -width "command"
+.It Ar number
+is the job number of the job;
+.It Ar flag
+is the
+.Ql +
+or
+.Ql \-
+character if the job is the
+.Ic %+
+or
+.Ic %\-
+job, respectively, or space if it is neither;
+.It Ar status
+indicates the current state of the job and can be:
+.Bl -tag -width "RunningXX"
+.It Done Op Ar number
+The job exited.
+.Ar number
+is the exit status of the job which is omitted if the status is zero.
+.It Running
+The job has neither stopped nor exited (note that running does not necessarily
+mean consuming CPU time \*(en
+the process could be blocked waiting for some event).
+.It Stopped Op Ar signal
+The job was stopped by the indicated
+.Ar signal
+(if no signal is given, the job was stopped by
+.Dv SIGTSTP ) .
+.It Ar signal-description Op Dq core dumped
+The job was killed by a signal (e.g. memory fault, hangup); use
+.Ic kill \-l
+for a list of signal descriptions.
+The
+.Dq core dumped
+message indicates the process created a core file.
+.El
+.It Ar command
+is the command that created the process.
+If there are multiple processes in
+the job, each process will have a line showing its
+.Ar command
+and possibly its
+.Ar status ,
+if it is different from the status of the previous process.
+.El
+.Pp
+When an attempt is made to exit the shell while there are jobs in the stopped
+state, the shell warns the user that there are stopped jobs and does not exit.
+If another attempt is immediately made to exit the shell, the stopped jobs are
+sent a
+.Dv SIGHUP
+signal and the shell exits.
+Similarly, if the
+.Ic nohup
+option is not set and there are running jobs when an attempt is made to exit
+a login shell, the shell warns the user and does not exit.
+If another attempt
+is immediately made to exit the shell, the running jobs are sent a
+.Dv SIGHUP
+signal and the shell exits.
+.Ss Interactive input line editing
+The shell supports three modes of reading command lines from a
+.Xr tty 4
+in an interactive session, controlled by the
+.Ic emacs ,
+.Ic gmacs ,
+and
+.Ic vi
+options (at most one of these can be set at once).
+The default is
+.Ic emacs .
+Editing modes can be set explicitly using the
+.Ic set
+built-in.
+If none of these options are enabled,
+the shell simply reads lines using the normal
+.Xr tty 4
+driver.
+If the
+.Ic emacs
+or
+.Ic gmacs
+option is set, the shell allows emacs-like editing of the command; similarly,
+if the
+.Ic vi
+option is set, the shell allows vi-like editing of the command.
+These modes are described in detail in the following sections.
+.Pp
+In these editing modes, if a line is longer than the screen width (see the
+.Ev COLUMNS
+parameter),
+a
+.Ql \*(Gt ,
+.Ql + ,
+or
+.Ql \*(Lt
+character is displayed in the last column indicating that there are more
+characters after, before and after, or before the current position,
+respectively.
+The line is scrolled horizontally as necessary.
+.Pp
+Completed lines are pushed into the history, unless they begin with an
+IFS octet or IFS white space, or are the same as the previous line.
+.Ss Emacs editing mode
+When the
+.Ic emacs
+option is set, interactive input line editing is enabled.
+Warning: This mode is
+slightly different from the emacs mode in the original Korn shell.
+In this mode, various editing commands
+(typically bound to one or more control characters) cause immediate actions
+without waiting for a newline.
+Several editing commands are bound to particular
+control characters when the shell is invoked; these bindings can be changed
+using the
+.Ic bind
+command.
+.Pp
+The following is a list of available editing commands.
+Each description starts with the name of the command,
+suffixed with a colon;
+an
+.Op Ar n
+(if the command can be prefixed with a count); and any keys the command is
+bound to by default, written using caret notation
+e.g. the ASCII ESC character is written as \*(ha[.
+These control sequences are not case sensitive.
+A count prefix for a command is entered using the sequence
+.Pf \*(ha[ Ns Ar n ,
+where
+.Ar n
+is a sequence of 1 or more digits.
+Unless otherwise specified, if a count is
+omitted, it defaults to 1.
+.Pp
+Note that editing command names are used only with the
+.Ic bind
+command.
+Furthermore, many editing commands are useful only on terminals with
+a visible cursor.
+The default bindings were chosen to resemble corresponding
+Emacs key bindings.
+The user's
+.Xr tty 4
+characters (e.g.\&
+.Dv ERASE )
+are bound to
+reasonable substitutes and override the default bindings.
+.Bl -tag -width Ds
+.It abort: \*(haC, \*(haG
+Abort the current command, empty the line buffer and
+set the exit state to interrupted.
+.It auto\-insert: Op Ar n
+Simply causes the character to appear as literal input.
+Most ordinary characters are bound to this.
+.It Xo backward\-char:
+.Op Ar n
+.No \*(haB , \*(haXD , ANSI-CurLeft
+.Xc
+Moves the cursor backward
+.Ar n
+characters.
+.It Xo backward\-word:
+.Op Ar n
+.No \*(ha[b , ANSI-Ctrl-CurLeft , ANSI-Alt-CurLeft
+.Xc
+Moves the cursor backward to the beginning of the word; words consist of
+alphanumerics, underscore
+.Pq Sq _ ,
+and dollar sign
+.Pq Sq $
+characters.
+.It beginning\-of\-history: \*(ha[\*(Lt
+Moves to the beginning of the history.
+.It beginning\-of\-line: \*(haA, ANSI-Home
+Moves the cursor to the beginning of the edited input line.
+.It Xo capitalise\-word:
+.Op Ar n
+.No \*(ha[C , \*(ha[c
+.Xc
+Uppercase the first character in the next
+.Ar n
+words, leaving the cursor past the end of the last word.
+.It clear\-screen: \*(ha[\*(haL
+Prints a compile-time configurable sequence to clear the screen and home
+the cursor, redraws the entire prompt and the currently edited input line.
+The default sequence works for almost all standard terminals.
+.It comment: \*(ha[#
+If the current line does not begin with a comment character, one is added at
+the beginning of the line and the line is entered (as if return had been
+pressed); otherwise, the existing comment characters are removed and the cursor
+is placed at the beginning of the line.
+.It complete: \*(ha[\*(ha[
+Automatically completes as much as is unique of the command name or the file
+name containing the cursor.
+If the entire remaining command or file name is
+unique, a space is printed after its completion, unless it is a directory name
+in which case
+.Ql /
+is appended.
+If there is no command or file name with the current partial word
+as its prefix, a bell character is output (usually causing a beep to be
+sounded).
+.It complete\-command: \*(haX\*(ha[
+Automatically completes as much as is unique of the command name having the
+partial word up to the cursor as its prefix, as in the
+.Ic complete
+command above.
+.It complete\-file: \*(ha[\*(haX
+Automatically completes as much as is unique of the file name having the
+partial word up to the cursor as its prefix, as in the
+.Ic complete
+command described above.
+.It complete\-list: \*(haI, \*(ha[=
+Complete as much as is possible of the current word,
+and list the possible completions for it.
+If only one completion is possible,
+match as in the
+.Ic complete
+command above.
+Note that \*(haI is usually generated by the TAB (tabulator) key.
+.It Xo delete\-char\-backward:
+.Op Ar n
+.No ERASE , \*(ha? , \*(haH
+.Xc
+Deletes
+.Ar n
+characters before the cursor.
+.It Xo delete\-char\-forward:
+.Op Ar n
+.No ANSI-Del
+.Xc
+Deletes
+.Ar n
+characters after the cursor.
+.It Xo delete\-word\-backward:
+.Op Ar n
+.No WERASE , \*(ha[\*(ha? , \*(ha[\*(haH , \*(ha[h
+.Xc
+Deletes
+.Ar n
+words before the cursor.
+.It Xo delete\-word\-forward:
+.Op Ar n
+.No \*(ha[d
+.Xc
+Deletes characters after the cursor up to the end of
+.Ar n
+words.
+.It Xo down\-history:
+.Op Ar n
+.No \*(haN , \*(haXB , ANSI-CurDown
+.Xc
+Scrolls the history buffer forward
+.Ar n
+lines (later).
+Each input line originally starts just after the last entry
+in the history buffer, so
+.Ic down\-history
+is not useful until either
+.Ic search\-history ,
+.Ic search\-history\-up
+or
+.Ic up\-history
+has been performed.
+.It Xo downcase\-word:
+.Op Ar n
+.No \*(ha[L , \*(ha[l
+.Xc
+Lowercases the next
+.Ar n
+words.
+.It Xo edit\-line:
+.Op Ar n
+.No \*(haXe
+.Xc
+Edit line
+.Ar n
+or the current line, if not specified, interactively.
+The actual command executed is
+.Ic fc \-e ${VISUAL:\-${EDITOR:\-vi}} Ar n .
+.It end\-of\-history: \*(ha[\*(Gt
+Moves to the end of the history.
+.It end\-of\-line: \*(haE, ANSI-End
+Moves the cursor to the end of the input line.
+.It eot: \*(ha_
+Acts as an end-of-file; this is useful because edit-mode input disables
+normal terminal input canonicalization.
+.It Xo eot\-or\-delete:
+.Op Ar n
+.No \*(haD
+.Xc
+Acts as
+.Ic eot
+if alone on a line; otherwise acts as
+.Ic delete\-char\-forward .
+.It error: (not bound)
+Error (ring the bell).
+.It exchange\-point\-and\-mark: \*(haX\*(haX
+Places the cursor where the mark is and sets the mark to where the cursor was.
+.It expand\-file: \*(ha[*
+Appends a
+.Ql *
+to the current word and replaces the word with the result of performing file
+globbing on the word.
+If no files match the pattern, the bell is rung.
+.It Xo forward\-char:
+.Op Ar n
+.No \*(haF , \*(haXC , ANSI-CurRight
+.Xc
+Moves the cursor forward
+.Ar n
+characters.
+.It Xo forward\-word:
+.Op Ar n
+.No \*(ha[f , ANSI-Ctrl-CurRight , ANSI-Alt-CurRight
+.Xc
+Moves the cursor forward to the end of the
+.Ar n Ns th
+word.
+.It Xo goto\-history:
+.Op Ar n
+.No \*(ha[g
+.Xc
+Goes to history number
+.Ar n .
+.It kill\-line: KILL
+Deletes the entire input line.
+.It kill\-region: \*(haW
+Deletes the input between the cursor and the mark.
+.It Xo kill\-to\-eol:
+.Op Ar n
+.No \*(haK
+.Xc
+Deletes the input from the cursor to the end of the line if
+.Ar n
+is not specified; otherwise deletes characters between the cursor and column
+.Ar n .
+.It list: \*(ha[?
+Prints a sorted, columnated list of command names or file names (if any) that
+can complete the partial word containing the cursor.
+Directory names have
+.Ql /
+appended to them.
+.It list\-command: \*(haX?
+Prints a sorted, columnated list of command names (if any) that can complete
+the partial word containing the cursor.
+.It list\-file: \*(haX\*(haY
+Prints a sorted, columnated list of file names (if any) that can complete the
+partial word containing the cursor.
+File type indicators are appended as described under
+.Ic list
+above.
+.It newline: \*(haJ , \*(haM
+Causes the current input line to be processed by the shell.
+The current cursor position may be anywhere on the line.
+.It newline\-and\-next: \*(haO
+Causes the current input line to be processed by the shell, and the next line
+from history becomes the current line.
+This is only useful after an
+.Ic up\-history ,
+.Ic search\-history
+or
+.Ic search\-history\-up .
+.It no\-op: QUIT
+This does nothing.
+.It prefix\-1: \*(ha[
+Introduces a 2-character command sequence.
+.It prefix\-2: \*(haX , \*(ha[[ , \*(ha[O
+Introduces a 2-character command sequence.
+.It Xo prev\-hist\-word:
+.Op Ar n
+.No \*(ha[. , \*(ha[_
+.Xc
+The last word, or, if given, the
+.Ar n Ns th
+word (zero-based) of the previous (on repeated execution, second-last,
+third-last, etc.) command is inserted at the cursor.
+Use of this editing command trashes the mark.
+.It quote: \*(ha\*(ha , \*(haV
+The following character is taken literally rather than as an editing command.
+.It redraw: \*(haL
+Reprints the last line of the prompt string and the current input line
+on a new line.
+.It Xo search\-character\-backward:
+.Op Ar n
+.No \*(ha[\*(ha]
+.Xc
+Search backward in the current line for the
+.Ar n Ns th
+occurrence of the next character typed.
+.It Xo search\-character\-forward:
+.Op Ar n
+.No \*(ha]
+.Xc
+Search forward in the current line for the
+.Ar n Ns th
+occurrence of the next character typed.
+.It search\-history: \*(haR
+Enter incremental search mode.
+The internal history list is searched
+backwards for commands matching the input.
+An initial
+.Ql \*(ha
+in the search string anchors the search.
+The escape key will leave search mode.
+Other commands, including sequences of escape as
+.Ic prefix\-1
+followed by a
+.Ic prefix\-1
+or
+.Ic prefix\-2
+key will be executed after leaving search mode.
+The
+.Ic abort Pq \*(haG
+command will restore the input line before search started.
+Successive
+.Ic search\-history
+commands continue searching backward to the next previous occurrence of the
+pattern.
+The history buffer retains only a finite number of lines; the oldest
+are discarded as necessary.
+.It search\-history\-up: ANSI-PgUp
+Search backwards through the history buffer for commands whose beginning match
+the portion of the input line before the cursor.
+When used on an empty line, this has the same effect as
+.Ic up\-history .
+.It search\-history\-down: ANSI-PgDn
+Search forwards through the history buffer for commands whose beginning match
+the portion of the input line before the cursor.
+When used on an empty line, this has the same effect as
+.Ic down\-history .
+This is only useful after an
+.Ic up\-history ,
+.Ic search\-history
+or
+.Ic search\-history\-up .
+.It set\-mark\-command: \*(ha[ Ns Aq space
+Set the mark at the cursor position.
+.It transpose\-chars: \*(haT
+If at the end of line, or if the
+.Ic gmacs
+option is set, this exchanges the two previous characters; otherwise, it
+exchanges the previous and current characters and moves the cursor one
+character to the right.
+.It Xo up\-history:
+.Op Ar n
+.No \*(haP , \*(haXA , ANSI-CurUp
+.Xc
+Scrolls the history buffer backward
+.Ar n
+lines (earlier).
+.It Xo upcase\-word:
+.Op Ar n
+.No \*(ha[U , \*(ha[u
+.Xc
+Uppercase the next
+.Ar n
+words.
+.It version: \*(ha[\*(haV
+Display the version of
+.Nm mksh .
+The current edit buffer is restored as soon as a key is pressed.
+The restoring keypress is processed, unless it is a space.
+.It yank: \*(haY
+Inserts the most recently killed text string at the current cursor position.
+.It yank\-pop: \*(ha[y
+Immediately after a
+.Ic yank ,
+replaces the inserted text string with the next previously killed text string.
+.El
+.Ss Vi editing mode
+.Em Note:
+The vi command-line editing mode is orphaned, yet still functional.
+.Pp
+The vi command-line editor in
+.Nm
+has basically the same commands as the
+.Xr vi 1
+editor with the following exceptions:
+.Bl -bullet
+.It
+You start out in insert mode.
+.It
+There are file name and command completion commands:
+=, \e, *, \*(haX, \*(haE, \*(haF, and, optionally,
+.Aq tab
+and
+.Aq esc .
+.It
+The
+.Ic _
+command is different (in
+.Nm mksh ,
+it is the last argument command; in
+.Xr vi 1
+it goes to the start of the current line).
+.It
+The
+.Ic /
+and
+.Ic G
+commands move in the opposite direction to the
+.Ic j
+command.
+.It
+Commands which don't make sense in a single line editor are not available
+(e.g. screen movement commands and
+.Xr ex 1 Ns -style
+colon
+.Pq Ic \&:
+commands).
+.El
+.Pp
+Like
+.Xr vi 1 ,
+there are two modes:
+.Dq insert
+mode and
+.Dq command
+mode.
+In insert mode, most characters are simply put in the buffer at the
+current cursor position as they are typed; however, some characters are
+treated specially.
+In particular, the following characters are taken from current
+.Xr tty 4
+settings
+(see
+.Xr stty 1 )
+and have their usual meaning (normal values are in parentheses): kill (\*(haU),
+erase (\*(ha?), werase (\*(haW), eof (\*(haD), intr (\*(haC), and quit (\*(ha\e).
+In addition to
+the above, the following characters are also treated specially in insert mode:
+.Bl -tag -width XJXXXXM
+.It \*(haE
+Command and file name enumeration (see below).
+.It \*(haF
+Command and file name completion (see below).
+If used twice in a row, the
+list of possible completions is displayed; if used a third time, the completion
+is undone.
+.It \*(haH
+Erases previous character.
+.It \*(haJ \*(Ba \*(haM
+End of line.
+The current line is read, parsed, and executed by the shell.
+.It \*(haV
+Literal next.
+The next character typed is not treated specially (can be used
+to insert the characters being described here).
+.It \*(haX
+Command and file name expansion (see below).
+.It Aq esc
+Puts the editor in command mode (see below).
+.It Aq tab
+Optional file name and command completion (see
+.Ic \*(haF
+above), enabled with
+.Ic set \-o vi\-tabcomplete .
+.El
+.Pp
+In command mode, each character is interpreted as a command.
+Characters that
+don't correspond to commands, are illegal combinations of commands, or are
+commands that can't be carried out, all cause beeps.
+In the following command descriptions, an
+.Op Ar n
+indicates the command may be prefixed by a number (e.g.\&
+.Ic 10l
+moves right 10 characters); if no number prefix is used,
+.Ar n
+is assumed to be 1 unless otherwise specified.
+The term
+.Dq current position
+refers to the position between the cursor and the character preceding the
+cursor.
+A
+.Dq word
+is a sequence of letters, digits, and underscore characters or a sequence of
+non-letter, non-digit, non-underscore, and non-whitespace characters (e.g.\&
+.Dq ab2*&\*(ha
+contains two words) and a
+.Dq big-word
+is a sequence of non-whitespace characters.
+.Pp
+Special
+.Nm
+vi commands:
+.Pp
+The following commands are not in, or are different from, the normal vi file
+editor:
+.Bl -tag -width 10n
+.It Xo
+.Oo Ar n Oc Ns _
+.Xc
+Insert a space followed by the
+.Ar n Ns th
+big-word from the last command in the history at the current position and enter
+insert mode; if
+.Ar n
+is not specified, the last word is inserted.
+.It #
+Insert the comment character
+.Pq Sq #
+at the start of the current line and return the line to the shell (equivalent
+to
+.Ic I#\*(haJ ) .
+.It Xo
+.Oo Ar n Oc Ns g
+.Xc
+Like
+.Ic G ,
+except if
+.Ar n
+is not specified, it goes to the most recent remembered line.
+.It Xo
+.Oo Ar n Oc Ns v
+.Xc
+Edit line
+.Ar n
+using the
+.Xr vi 1
+editor; if
+.Ar n
+is not specified, the current line is edited.
+The actual command executed is
+.Ic fc \-e ${VISUAL:\-${EDITOR:\-vi}} Ar n .
+.It * and \*(haX
+Command or file name expansion is applied to the current big-word (with an
+appended
+.Ql *
+if the word contains no file globbing characters) \*(en the big-word is replaced
+with the resulting words.
+If the current big-word is the first on the line
+or follows one of the characters
+.Ql \&; ,
+.Ql \*(Ba ,
+.Ql & ,
+.Ql \&( ,
+or
+.Ql \&) ,
+and does not contain a slash
+.Pq Sq / ,
+then command expansion is done; otherwise file name expansion is done.
+Command expansion will match the big-word against all aliases, functions, and
+built-in commands as well as any executable files found by searching the
+directories in the
+.Ev PATH
+parameter.
+File name expansion matches the big-word against the files in the
+current directory.
+After expansion, the cursor is placed just past the last
+word and the editor is in insert mode.
+.It Xo
+.Oo Ar n Oc Ns \e ,
+.Oo Ar n Oc Ns \*(haF ,
+.Oo Ar n Oc Ns Aq tab ,
+.No and
+.Oo Ar n Oc Ns Aq esc
+.Xc
+Command/file name completion.
+Replace the current big-word with the
+longest unique match obtained after performing command and file name expansion.
+.Aq tab
+is only recognised if the
+.Ic vi\-tabcomplete
+option is set, while
+.Aq esc
+is only recognised if the
+.Ic vi\-esccomplete
+option is set (see
+.Ic set \-o ) .
+If
+.Ar n
+is specified, the
+.Ar n Ns th
+possible completion is selected (as reported by the command/file name
+enumeration command).
+.It = and \*(haE
+Command/file name enumeration.
+List all the commands or files that match the current big-word.
+.It \*(haV
+Display the version of
+.Nm mksh .
+The current edit buffer is restored as soon as a key is pressed.
+The restoring keypress is ignored.
+.It @ Ns Ar c
+Macro expansion.
+Execute the commands found in the alias
+.Ar c .
+.El
+.Pp
+Intra-line movement commands:
+.Bl -tag -width Ds
+.It Xo
+.Oo Ar n Oc Ns h and
+.Oo Ar n Oc Ns \*(haH
+.Xc
+Move left
+.Ar n
+characters.
+.It Xo
+.Oo Ar n Oc Ns l and
+.Oo Ar n Oc Ns Aq space
+.Xc
+Move right
+.Ar n
+characters.
+.It 0
+Move to column 0.
+.It \*(ha
+Move to the first non-whitespace character.
+.It Xo
+.Oo Ar n Oc Ns \*(Ba
+.Xc
+Move to column
+.Ar n .
+.It $
+Move to the last character.
+.It Xo
+.Oo Ar n Oc Ns b
+.Xc
+Move back
+.Ar n
+words.
+.It Xo
+.Oo Ar n Oc Ns B
+.Xc
+Move back
+.Ar n
+big-words.
+.It Xo
+.Oo Ar n Oc Ns e
+.Xc
+Move forward to the end of the word,
+.Ar n
+times.
+.It Xo
+.Oo Ar n Oc Ns E
+.Xc
+Move forward to the end of the big-word,
+.Ar n
+times.
+.It Xo
+.Oo Ar n Oc Ns w
+.Xc
+Move forward
+.Ar n
+words.
+.It Xo
+.Oo Ar n Oc Ns W
+.Xc
+Move forward
+.Ar n
+big-words.
+.It %
+Find match.
+The editor looks forward for the nearest parenthesis, bracket, or
+brace and then moves the cursor to the matching parenthesis, bracket, or brace.
+.It Xo
+.Oo Ar n Oc Ns f Ns Ar c
+.Xc
+Move forward to the
+.Ar n Ns th
+occurrence of the character
+.Ar c .
+.It Xo
+.Oo Ar n Oc Ns F Ns Ar c
+.Xc
+Move backward to the
+.Ar n Ns th
+occurrence of the character
+.Ar c .
+.It Xo
+.Oo Ar n Oc Ns t Ns Ar c
+.Xc
+Move forward to just before the
+.Ar n Ns th
+occurrence of the character
+.Ar c .
+.It Xo
+.Oo Ar n Oc Ns T Ns Ar c
+.Xc
+Move backward to just before the
+.Ar n Ns th
+occurrence of the character
+.Ar c .
+.It Xo
+.Oo Ar n Oc Ns \&;
+.Xc
+Repeats the last
+.Ic f , F , t ,
+or
+.Ic T
+command.
+.It Xo
+.Oo Ar n Oc Ns \&,
+.Xc
+Repeats the last
+.Ic f , F , t ,
+or
+.Ic T
+command, but moves in the opposite direction.
+.El
+.Pp
+Inter-line movement commands:
+.Bl -tag -width Ds
+.It Xo
+.Oo Ar n Oc Ns j ,
+.Oo Ar n Oc Ns + ,
+.No and
+.Oo Ar n Oc Ns \*(haN
+.Xc
+Move to the
+.Ar n Ns th
+next line in the history.
+.It Xo
+.Oo Ar n Oc Ns k ,
+.Oo Ar n Oc Ns \- ,
+.No and
+.Oo Ar n Oc Ns \*(haP
+.Xc
+Move to the
+.Ar n Ns th
+previous line in the history.
+.It Xo
+.Oo Ar n Oc Ns G
+.Xc
+Move to line
+.Ar n
+in the history; if
+.Ar n
+is not specified, the number of the first remembered line is used.
+.It Xo
+.Oo Ar n Oc Ns g
+.Xc
+Like
+.Ic G ,
+except if
+.Ar n
+is not specified, it goes to the most recent remembered line.
+.It Xo
+.Oo Ar n Oc Ns / Ns Ar string
+.Xc
+Search backward through the history for the
+.Ar n Ns th
+line containing
+.Ar string ;
+if
+.Ar string
+starts with
+.Ql \*(ha ,
+the remainder of the string must appear at the start of the history line for
+it to match.
+.It Xo
+.Oo Ar n Oc Ns \&? Ns Ar string
+.Xc
+Same as
+.Ic / ,
+except it searches forward through the history.
+.It Xo
+.Oo Ar n Oc Ns n
+.Xc
+Search for the
+.Ar n Ns th
+occurrence of the last search string;
+the direction of the search is the same as the last search.
+.It Xo
+.Oo Ar n Oc Ns N
+.Xc
+Search for the
+.Ar n Ns th
+occurrence of the last search string;
+the direction of the search is the opposite of the last search.
+.El
+.Pp
+Edit commands
+.Bl -tag -width Ds
+.It Xo
+.Oo Ar n Oc Ns a
+.Xc
+Append text
+.Ar n
+times; goes into insert mode just after the current position.
+The append is
+only replicated if command mode is re-entered i.e.\&
+.Aq esc
+is used.
+.It Xo
+.Oo Ar n Oc Ns A
+.Xc
+Same as
+.Ic a ,
+except it appends at the end of the line.
+.It Xo
+.Oo Ar n Oc Ns i
+.Xc
+Insert text
+.Ar n
+times; goes into insert mode at the current position.
+The insertion is only
+replicated if command mode is re-entered i.e.\&
+.Aq esc
+is used.
+.It Xo
+.Oo Ar n Oc Ns I
+.Xc
+Same as
+.Ic i ,
+except the insertion is done just before the first non-blank character.
+.It Xo
+.Oo Ar n Oc Ns s
+.Xc
+Substitute the next
+.Ar n
+characters (i.e. delete the characters and go into insert mode).
+.It S
+Substitute whole line.
+All characters from the first non-blank character to the
+end of the line are deleted and insert mode is entered.
+.It Xo
+.Oo Ar n Oc Ns c Ns Ar move-cmd
+.Xc
+Change from the current position to the position resulting from
+.Ar n move-cmd Ns s
+(i.e. delete the indicated region and go into insert mode); if
+.Ar move-cmd
+is
+.Ic c ,
+the line starting from the first non-blank character is changed.
+.It C
+Change from the current position to the end of the line (i.e. delete to the
+end of the line and go into insert mode).
+.It Xo
+.Oo Ar n Oc Ns x
+.Xc
+Delete the next
+.Ar n
+characters.
+.It Xo
+.Oo Ar n Oc Ns X
+.Xc
+Delete the previous
+.Ar n
+characters.
+.It D
+Delete to the end of the line.
+.It Xo
+.Oo Ar n Oc Ns d Ns Ar move-cmd
+.Xc
+Delete from the current position to the position resulting from
+.Ar n move-cmd Ns s ;
+.Ar move-cmd
+is a movement command (see above) or
+.Ic d ,
+in which case the current line is deleted.
+.It Xo
+.Oo Ar n Oc Ns r Ns Ar c
+.Xc
+Replace the next
+.Ar n
+characters with the character
+.Ar c .
+.It Xo
+.Oo Ar n Oc Ns R
+.Xc
+Replace.
+Enter insert mode but overwrite existing characters instead of
+inserting before existing characters.
+The replacement is repeated
+.Ar n
+times.
+.It Xo
+.Oo Ar n Oc Ns \*(TI
+.Xc
+Change the case of the next
+.Ar n
+characters.
+.It Xo
+.Oo Ar n Oc Ns y Ns Ar move-cmd
+.Xc
+Yank from the current position to the position resulting from
+.Ar n move-cmd Ns s
+into the yank buffer; if
+.Ar move-cmd
+is
+.Ic y ,
+the whole line is yanked.
+.It Y
+Yank from the current position to the end of the line.
+.It Xo
+.Oo Ar n Oc Ns p
+.Xc
+Paste the contents of the yank buffer just after the current position,
+.Ar n
+times.
+.It Xo
+.Oo Ar n Oc Ns P
+.Xc
+Same as
+.Ic p ,
+except the buffer is pasted at the current position.
+.El
+.Pp
+Miscellaneous vi commands
+.Bl -tag -width Ds
+.It \*(haJ and \*(haM
+The current line is read, parsed, and executed by the shell.
+.It \*(haL and \*(haR
+Redraw the current line.
+.It Xo
+.Oo Ar n Oc Ns \&.
+.Xc
+Redo the last edit command
+.Ar n
+times.
+.It u
+Undo the last edit command.
+.It U
+Undo all changes that have been made to the current line.
+.It Ar intr No and Ar quit
+The interrupt and quit terminal characters cause the current line to be
+deleted and a new prompt to be printed.
+.El
+.Sh FILES
+.Bl -tag -width XetcXsuid_profile -compact
+.It Pa \*(TI/.mkshrc
+User mkshrc profile (non-privileged interactive shells); see
+.Sx Startup files.
+The location can be changed at compile time (for embedded systems);
+AOSP Android builds use
+.Pa /system/etc/mkshrc .
+.It Pa \*(TI/.profile
+User profile (non-privileged login shells); see
+.Sx Startup files
+near the top of this manual.
+.It Pa /etc/profile
+System profile (login shells); see
+.Sx Startup files.
+.It Pa /etc/shells
+Shell database.
+.It Pa /etc/suid_profile
+Suid profile (privileged shells); see
+.Sx Startup files.
+.El
+.Pp
+Note: On Android,
+.Pa /system/etc/
+contains the system and suid profile.
+.Sh SEE ALSO
+.Xr awk 1 ,
+.Xr cat 1 ,
+.Xr ed 1 ,
+.Xr getopt 1 ,
+.Xr printf 1 ,
+.Xr sed 1 ,
+.Xr sh 1 ,
+.Xr stty 1 ,
+.Xr dup 2 ,
+.Xr execve 2 ,
+.Xr getgid 2 ,
+.Xr getuid 2 ,
+.Xr mknod 2 ,
+.Xr mkfifo 2 ,
+.Xr open 2 ,
+.Xr pipe 2 ,
+.Xr rename 2 ,
+.Xr wait 2 ,
+.Xr getopt 3 ,
+.Xr nl_langinfo 3 ,
+.Xr setlocale 3 ,
+.Xr signal 3 ,
+.Xr system 3 ,
+.Xr tty 4 ,
+.Xr shells 5 ,
+.Xr environ 7 ,
+.Xr script 7 ,
+.Xr utf\-8 7 ,
+.Xr mknod 8
+.Pp
+.Pa http://docsrv.sco.com:507/en/man/html.C/sh.C.html
+.Rs
+.%A Morris Bolsky
+.%B "The KornShell Command and Programming Language"
+.%D 1989
+.%I "Prentice Hall PTR"
+.%P "xvi\ +\ 356 pages"
+.%O "ISBN 978\-0\-13\-516972\-8 (0\-13\-516972\-0)"
+.Re
+.Rs
+.%A Morris I. Bolsky
+.%A David G. Korn
+.%B "The New KornShell Command and Programming Language (2nd Edition)"
+.%D 1995
+.%I "Prentice Hall PTR"
+.%P "xvi\ +\ 400 pages"
+.%O "ISBN 978\-0\-13\-182700\-4 (0\-13\-182700\-6)"
+.Re
+.Rs
+.%A Stephen G. Kochan
+.%A Patrick H. Wood
+.%B "\\*(tNUNIX\\*(sP Shell Programming"
+.%V "Revised Edition"
+.%D 1990
+.%I "Hayden"
+.%P "xi\ +\ 490 pages"
+.%O "ISBN 978\-0\-672\-48448\-3 (0\-672\-48448\-X)"
+.Re
+.Rs
+.%A "IEEE Inc."
+.%B "\\*(tNIEEE\\*(sP Standard for Information Technology \*(en Portable Operating System Interface (POSIX)"
+.%V "Part 2: Shell and Utilities"
+.%D 1993
+.%I "IEEE Press"
+.%P "xvii\ +\ 1195 pages"
+.%O "ISBN 978\-1\-55937\-255\-8 (1\-55937\-255\-9)"
+.Re
+.Rs
+.%A Bill Rosenblatt
+.%B "Learning the Korn Shell"
+.%D 1993
+.%I "O'Reilly"
+.%P "360 pages"
+.%O "ISBN 978\-1\-56592\-054\-5 (1\-56592\-054\-6)"
+.Re
+.Rs
+.%A Bill Rosenblatt
+.%A Arnold Robbins
+.%B "Learning the Korn Shell, Second Edition"
+.%D 2002
+.%I "O'Reilly"
+.%P "432 pages"
+.%O "ISBN 978\-0\-596\-00195\-7 (0\-596\-00195\-9)"
+.Re
+.Rs
+.%A Barry Rosenberg
+.%B "KornShell Programming Tutorial"
+.%D 1991
+.%I "Addison-Wesley Professional"
+.%P "xxi\ +\ 324 pages"
+.%O "ISBN 978\-0\-201\-56324\-5 (0\-201\-56324\-X)"
+.Re
+.Sh AUTHORS
+.Nm "The MirBSD Korn Shell"
+is developed by
+.An Thorsten Glaser Aq tg@mirbsd.org
+and currently maintained as part of The MirOS Project.
+This shell is based upon the Public Domain Korn SHell.
+The developer of mksh recognises the efforts of the pdksh authors,
+who had dedicated their work into Public Domain, our users, and
+all contributors, such as the Debian and OpenBSD projects.
+.\"
+.\" Charles Forsyth, author of the (Public Domain) Bourne Shell clone,
+.\" which mksh is derived from, agreed to the following:
+.\"
+.\" In countries where the Public Domain status of the work may not be
+.\" valid, its primary author hereby grants a copyright licence to the
+.\" general public to deal in the work without restriction and permis-
+.\" sion to sublicence derivates under the terms of any (OSI approved)
+.\" Open Source licence.
+.\"
+See the documentation, CVS, and web site for details.
+.Sh CAVEATS
+.Nm
+only supports the Unicode BMP (Basic Multilingual Plane).
+It has a different scope model from
+.At
+.Nm ksh ,
+which leads to subtile differences in semantics for identical builtins.
+.Pp
+The parts of a pipeline, like below, are executed in subshells.
+Thus, variable assignments inside them fail.
+Use co-processes instead.
+.Bd -literal -offset indent
+foo \*(Ba bar \*(Ba read baz            # will not change $baz
+foo \*(Ba bar \*(Ba& read \-p baz        # will, however, do so
+.Ed
+.Sh BUGS
+Suspending (using \*(haZ) pipelines like the one below will only suspend
+the currently running part of the pipeline; in this example,
+.Dq fubar
+is immediately printed on suspension (but not later after an
+.Ic fg ) .
+.Bd -literal -offset indent
+$ /bin/sleep 666 && echo fubar
+.Ed
+.Pp
+This document attempts to describe
+.Nm mksh\ R40+CVS
+and up,
+compiled without any options impacting functionality, such as
+.Dv MKSH_SMALL ,
+for an operating environment supporting all of its advanced needs.
+Please report bugs in
+.Nm
+to the
+.Mx
+mailing list at
+.Aq miros\-discuss@mirbsd.org
+or in the
+.Li \&#\&!/bin/mksh
+.Pq or Li \&#ksh
+IRC channel at
+.Pa irc.freenode.net:6667 .
index 11588c9..740518c 100644 (file)
--- a/src/sh.h
+++ b/src/sh.h
@@ -9,28 +9,28 @@
 /*     $OpenBSD: tty.h,v 1.5 2004/12/20 11:34:26 otto Exp $    */
 
 /*-
- * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ * Copyright © 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
  *     Thorsten Glaser <tg@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
  * are retained or reproduced in an accompanying document, permission
- * is granted to deal in this work without restriction, including un-
+ * is granted to deal in this work without restriction, including un
  * limited rights to use, publicly perform, distribute, sell, modify,
  * merge, give away, or sublicence.
  *
- * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
  * the utmost extent permitted by applicable law, neither express nor
  * implied; without malicious intent or gross negligence. In no event
  * may a licensor, author or contributor be held liable for indirect,
  * direct, other damage, loss, or other issues arising in any way out
  * of dealing in the work, even if advised of the possibility of such
  * damage or existence of a defect, except proven that it results out
- * of said person's immediate fault when using the work as intended.
+ * of said persons immediate fault when using the work as intended.
  */
 
 #ifdef __dietlibc__
 /* XXX imake style */
-#define _BSD_SOURCE    /* live, BSD, live! */
+#define _BSD_SOURCE    /* live, BSD, live */
 #endif
 
 #if HAVE_SYS_PARAM_H
@@ -68,9 +68,6 @@
 #include <setjmp.h>
 #include <signal.h>
 #include <stdarg.h>
-#if HAVE_STDBOOL_H
-#include <stdbool.h>
-#endif
 #include <stddef.h>
 #if HAVE_STDINT_H
 #include <stdint.h>
 
 #undef __attribute__
 #if HAVE_ATTRIBUTE_BOUNDED
-#define MKSH_A_BOUNDED(x,y,z)  __attribute__((bounded (x, y, z)))
+#define MKSH_A_BOUNDED(x,y,z)  __attribute__((__bounded__ (x, y, z)))
 #else
 #define MKSH_A_BOUNDED(x,y,z)  /* nothing */
 #endif
 #if HAVE_ATTRIBUTE_FORMAT
-#define MKSH_A_FORMAT(x,y,z)   __attribute__((format (x, y, z)))
+#define MKSH_A_FORMAT(x,y,z)   __attribute__((__format__ (x, y, z)))
 #else
 #define MKSH_A_FORMAT(x,y,z)   /* nothing */
 #endif
 #define MKSH_A_NONNULL(a)      /* nothing */
 #endif
 #if HAVE_ATTRIBUTE_NORETURN
-#define MKSH_A_NORETURN                __attribute__((noreturn))
+#define MKSH_A_NORETURN                __attribute__((__noreturn__))
 #else
 #define MKSH_A_NORETURN                /* nothing */
 #endif
 #if HAVE_ATTRIBUTE_UNUSED
-#define MKSH_A_UNUSED          __attribute__((unused))
+#define MKSH_A_UNUSED          __attribute__((__unused__))
 #else
 #define MKSH_A_UNUSED          /* nothing */
 #endif
 #if HAVE_ATTRIBUTE_USED
-#define MKSH_A_USED            __attribute__((used))
+#define MKSH_A_USED            __attribute__((__used__))
 #else
 #define MKSH_A_USED            /* nothing */
 #endif
 #undef __SCCSID
 #define __IDSTRING_CONCAT(l,p)         __LINTED__ ## l ## _ ## p
 #define __IDSTRING_EXPAND(l,p)         __IDSTRING_CONCAT(l,p)
+#ifdef MKSH_DONT_EMIT_IDSTRING
+#define __IDSTRING(prefix, string)     /* nothing */
+#else
 #define __IDSTRING(prefix, string)                             \
        static const char __IDSTRING_EXPAND(__LINE__,prefix) [] \
            MKSH_A_USED = "@(""#)" #prefix ": " string
+#endif
 #define __COPYRIGHT(x)         __IDSTRING(copyright,x)
 #define __RCSID(x)             __IDSTRING(rcsid,x)
 #define __SCCSID(x)            __IDSTRING(sccsid,x)
 #endif
 
 #ifdef EXTERN
-__RCSID("$MirOS: src/bin/mksh/sh.h,v 1.405 2010/08/24 15:19:54 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/sh.h,v 1.495 2011/10/07 19:51:44 tg Exp $");
 #endif
-#define MKSH_VERSION "R39 2010/08/24"
+#define MKSH_VERSION "R40 2011/10/07"
 
 #ifndef MKSH_INCLUDES_ONLY
 
@@ -163,8 +164,8 @@ __RCSID("$MirOS: src/bin/mksh/sh.h,v 1.405 2010/08/24 15:19:54 tg Exp $");
 #undef RUSAGE_SELF
 #undef RUSAGE_CHILDREN
 #define rusage mksh_rusage
-#define RUSAGE_SELF    0
-#define RUSAGE_CHILDREN        -1
+#define RUSAGE_SELF            0
+#define RUSAGE_CHILDREN                -1
 
 struct rusage {
        struct timeval ru_utime;
@@ -181,13 +182,6 @@ typedef long rlim_t;
 typedef void (*sig_t)(int);
 #endif
 
-#if !HAVE_STDBOOL_H
-/* kludge, but enough for mksh */
-typedef int bool;
-#define false 0
-#define true 1
-#endif
-
 #if !HAVE_CAN_INTTYPES
 #if !HAVE_CAN_UCBINTS
 typedef signed int int32_t;
@@ -264,6 +258,9 @@ typedef u_int8_t uint8_t;
 #ifndef S_ISSOCK
 #define S_ISSOCK(m)    ((m & 0170000) == 0140000)
 #endif
+#if !defined(S_ISCDF) && defined(S_CDF)
+#define S_ISCDF(m)     (S_ISDIR(m) && ((m) & S_CDF))
+#endif
 #ifndef DEFFILEMODE
 #define DEFFILEMODE    (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)
 #endif
@@ -301,11 +298,6 @@ extern int getrusage(int, struct rusage *);
 extern int revoke(const char *);
 #endif
 
-#if !HAVE_SETMODE
-mode_t getmode(const void *, mode_t);
-void *setmode(const char *);
-#endif
-
 #ifdef __ultrix
 /* XXX imake style */
 int strcasecmp(const char *, const char *);
@@ -341,23 +333,35 @@ extern int wcwidth(__WCHAR_TYPE__);
 
 /* some useful #defines */
 #ifdef EXTERN
-# define I__(i) = i
+# define E_INIT(i) = i
 #else
-# define I__(i)
+# define E_INIT(i)
 # define EXTERN extern
 # define EXTERN_DEFINED
 #endif
 
+/* define bit in flag */
+#define BIT(i)         (1 << (i))
 #define NELEM(a)       (sizeof(a) / sizeof((a)[0]))
-#define BIT(i)         (1 << (i))      /* define bit in flag */
-
-/* Table flag type - needs > 16 and < 32 bits */
-typedef int32_t Tflag;
 
 /* arithmetics types */
 typedef int32_t mksh_ari_t;
 typedef uint32_t mksh_uari_t;
 
+/* boolean type (no <stdbool.h> deliberately) */
+typedef unsigned char mksh_bool;
+#undef bool
+/* false MUST equal 0 */
+#undef false
+#undef true
+/* access macros for boolean type */
+#define bool           mksh_bool
+/* values must have identity mapping between mksh_bool and short */
+#define false          0
+#define true           1
+/* make any-type into bool or short */
+#define tobool(cond)   ((cond) ? true : false)
+
 /* these shall be smaller than 100 */
 #ifdef MKSH_CONSERVATIVE_FDS
 #define NUFILE         32      /* Number of user-accessible files */
@@ -367,7 +371,8 @@ typedef uint32_t mksh_uari_t;
 #define FDBASE         24      /* First file usable by Shell */
 #endif
 
-/* Make MAGIC a char that might be printed to make bugs more obvious, but
+/*
+ * Make MAGIC a char that might be printed to make bugs more obvious, but
  * not a char that is used often. Also, can't use the high bit as it causes
  * portability problems (calling strchr(x, 0x80|'x') is error prone).
  */
@@ -378,11 +383,11 @@ typedef uint32_t mksh_uari_t;
 #define LINE           4096    /* input line size */
 
 EXTERN const char *safe_prompt; /* safe prompt if PS1 substitution fails */
-EXTERN const char initvsn[] I__("KSH_VERSION=@(#)MIRBSD KSH " MKSH_VERSION);
+EXTERN const char initvsn[] E_INIT("KSH_VERSION=@(#)MIRBSD KSH " MKSH_VERSION);
 #define KSH_VERSION    (initvsn + /* "KSH_VERSION=@(#)" */ 16)
 
-EXTERN const char digits_uc[] I__("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
-EXTERN const char digits_lc[] I__("0123456789abcdefghijklmnopqrstuvwxyz");
+EXTERN const char digits_uc[] E_INIT("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+EXTERN const char digits_lc[] E_INIT("0123456789abcdefghijklmnopqrstuvwxyz");
 
 /*
  * Evil hack for const correctness due to API brokenness
@@ -471,7 +476,7 @@ char *ucstrstr(char *, const char *);
 #endif
 
 #if HAVE_STRCASESTR
-#define stristr(b,l)   ((const char *)strcasestr((b), (l)))
+#define stristr(b,l)           ((const char *)strcasestr((b), (l)))
 #endif
 
 #ifdef MKSH_SMALL
@@ -490,10 +495,43 @@ char *ucstrstr(char *, const char *);
 #define MKSH_S_NOVI            0
 #endif
 
+#if defined(MKSH_NOPROSPECTOFWORK) && !defined(MKSH_UNEMPLOYED)
+#define MKSH_UNEMPLOYED                1
+#endif
+
 /*
  * simple grouping allocator
  */
 
+
+/* 0. OS API: where to get memory from and how to free it (grouped) */
+
+/* malloc(3)/realloc(3) -> free(3) for use by the memory allocator */
+#define malloc_osi(sz)         malloc(sz)
+#define realloc_osi(p,sz)      realloc((p), (sz))
+#define free_osimalloc(p)      free(p)
+
+/* malloc(3)/realloc(3) -> free(3) for use by mksh code */
+#define malloc_osfunc(sz)      malloc(sz)
+#define realloc_osfunc(p,sz)   realloc((p), (sz))
+#define free_osfunc(p)         free(p)
+
+#if HAVE_MKNOD
+/* setmode(3) -> free(3) */
+#define free_ossetmode(p)      free(p)
+#endif
+
+#if !HAVE_MKSTEMP
+/* tempnam(3) -> free(3) */
+#define free_ostempnam(p)      free(p)
+#endif
+
+#ifdef NO_PATH_MAX
+/* GNU libc: get_current_dir_name(3) -> free(3) */
+#define free_gnu_gcdn(p)       free(p)
+#endif
+
+
 /* 1. internal structure */
 struct lalloc {
        struct lalloc *next;
@@ -520,14 +558,14 @@ enum sh_flag {
        FNFLAGS         /* (place holder: how many flags are there) */
 };
 
-#define Flag(f)        (kshstate_v.shell_flags_[(int)(f)])
+#define Flag(f)        (shell_flags[(int)(f)])
 #define UTFMODE        Flag(FUNICODE)
 
 /*
  * parsing & execution environment
  */
 extern struct env {
-       ALLOC_ITEM __alloc_i;   /* internal, do not touch */
+       ALLOC_ITEM alloc_INT;   /* internal, do not touch */
        Area area;              /* temporary allocation area */
        struct env *oenv;       /* link to previous environment */
        struct block *loc;      /* local variables and functions */
@@ -570,42 +608,32 @@ extern struct env {
 #define LSHELL 8       /* return to interactive shell() */
 #define LAEXPR 9       /* error in arithmetic expression */
 
-/*
- * some kind of global shell state, for change_random() mostly
- */
-
-EXTERN struct mksh_kshstate_v {
-       /* for change_random */
-       struct timeval cr_tv;   /* timestamp */
-       const void *cr_dp;      /* argument address */
-       size_t cr_dsz;          /* argument length */
-       uint32_t lcg_state_;    /* previous LCG state */
-       /* global state */
-       pid_t procpid_;         /* PID of executing process */
-       int exstat_;            /* exit status */
-       int subst_exstat_;      /* exit status of last $(..)/`..` */
-       struct env env_;        /* top-level parsing & execution env. */
-       uint8_t shell_flags_[FNFLAGS];
-} kshstate_v;
-EXTERN struct mksh_kshstate_f {
-       const char *kshname_;   /* $0 */
-       pid_t kshpid_;          /* $$, shell PID */
-       pid_t kshpgrp_;         /* process group of shell */
-       uid_t ksheuid_;         /* effective UID of shell */
-       pid_t kshppid_;         /* PID of parent of shell */
-       uint32_t h;             /* some kind of hash */
-} kshstate_f;
-#define kshname                kshstate_f.kshname_
-#define kshpid         kshstate_f.kshpid_
-#define procpid                kshstate_v.procpid_
-#define kshpgrp                kshstate_f.kshpgrp_
-#define ksheuid                kshstate_f.ksheuid_
-#define kshppid                kshstate_f.kshppid_
-#define exstat         kshstate_v.exstat_
-#define subst_exstat   kshstate_v.subst_exstat_
-
-/* evil hack: return hash(kshstate_f concat (kshstate_f'.h:=hash(arg))) */
-uint32_t evilhash(const char *);
+/* sort of shell global state */
+EXTERN pid_t procpid;          /* PID of executing process */
+EXTERN int exstat;             /* exit status */
+EXTERN int subst_exstat;       /* exit status of last $(..)/`..` */
+EXTERN struct tbl *vp_pipest;  /* global PIPESTATUS array */
+EXTERN short trap_exstat;      /* exit status before running a trap */
+EXTERN uint8_t trap_nested;    /* running nested traps */
+EXTERN uint8_t shell_flags[FNFLAGS];
+EXTERN const char *kshname;    /* $0 */
+EXTERN struct {
+       uid_t kshuid_v;         /* real UID of shell */
+       uid_t ksheuid_v;        /* effective UID of shell */
+       gid_t kshgid_v;         /* real GID of shell */
+       gid_t kshegid_v;        /* effective GID of shell */
+       pid_t kshpgrp_v;        /* process group of shell */
+       pid_t kshppid_v;        /* PID of parent of shell */
+       pid_t kshpid_v;         /* $$, shell PID */
+} rndsetupstate;
+
+#define kshpid         rndsetupstate.kshpid_v
+#define kshpgrp                rndsetupstate.kshpgrp_v
+#define kshuid         rndsetupstate.kshuid_v
+#define ksheuid                rndsetupstate.ksheuid_v
+#define kshgid         rndsetupstate.kshgid_v
+#define kshegid                rndsetupstate.kshegid_v
+#define kshppid                rndsetupstate.kshppid_v
 
 
 /* option processing */
@@ -623,16 +651,34 @@ struct shoption {
 };
 extern const struct shoption options[];
 
-/* null value for variable; comparision pointer for unset */
-EXTERN char null[] I__("");
+/* null value for variable; comparison pointer for unset */
+EXTERN char null[] E_INIT("");
 /* helpers for string pooling */
-#define T_synerr "syntax error"
-EXTERN const char r_fc_e_[] I__("r=fc -e -");
-#define fc_e_          (r_fc_e_ + 2)           /* "fc -e -" */
-#define fc_e_n         7                       /* strlen(fc_e_) */
-EXTERN const char T_local_typeset[] I__("local=typeset");
-#define T__typeset     (T_local_typeset + 5)   /* "=typeset" */
-#define T_typeset      (T_local_typeset + 6)   /* "typeset" */
+EXTERN const char Tintovfl[] E_INIT("integer overflow %zu %c %zu prevented");
+EXTERN const char Toomem[] E_INIT("can't allocate %lu data bytes");
+#if defined(__GNUC__)
+/* trust this to have string pooling; -Wformat bitches otherwise */
+#define Tsynerr                "syntax error"
+#else
+EXTERN const char Tsynerr[] E_INIT("syntax error");
+#endif
+EXTERN const char Tselect[] E_INIT("select");
+EXTERN const char Tr_fc_e_dash[] E_INIT("r=fc -e -");
+#define Tfc_e_dash     (Tr_fc_e_dash + 2)      /* "fc -e -" */
+#define Zfc_e_dash     7                       /* strlen(Tfc_e_dash) */
+EXTERN const char Tlocal_typeset[] E_INIT("local=typeset");
+#define T_typeset      (Tlocal_typeset + 5)    /* "=typeset" */
+#define Ttypeset       (Tlocal_typeset + 6)    /* "typeset" */
+EXTERN const char Tpalias[] E_INIT("+alias");
+#define Talias         (Tpalias + 1)           /* "alias" */
+EXTERN const char Tpunalias[] E_INIT("+unalias");
+#define Tunalias       (Tpunalias + 1)         /* "unalias" */
+EXTERN const char Tsgset[] E_INIT("*=set");
+#define Tset           (Tsgset + 2)            /* "set" */
+EXTERN const char Tgbuiltin[] E_INIT("=builtin");
+#define Tbuiltin       (Tgbuiltin + 1)         /* "builtin" */
+EXTERN const char T_function[] E_INIT(" function");
+#define Tfunction      (T_function + 1)        /* "function" */
 
 enum temp_type {
        TT_HEREDOC_EXP, /* expanded heredoc */
@@ -655,7 +701,7 @@ struct temp {
 #define shl_spare      (&shf_iob[0])   /* for c_read()/c_print() */
 #define shl_stdout     (&shf_iob[1])
 #define shl_out                (&shf_iob[2])
-EXTERN int shl_stdout_ok;
+EXTERN bool shl_stdout_ok;
 
 /*
  * trap handlers
@@ -693,17 +739,17 @@ typedef struct trap {
 #define SS_USER                BIT(4)  /* user is doing the set (ie, trap command) */
 #define SS_SHTRAP      BIT(5)  /* trap for internal use (ALRM, CHLD, WINCH) */
 
-#define SIGEXIT_       0       /* for trap EXIT */
-#define SIGERR_                NSIG    /* for trap ERR */
+#define ksh_SIGEXIT    0       /* for trap EXIT */
+#define ksh_SIGERR     NSIG    /* for trap ERR */
 
 EXTERN volatile sig_atomic_t trap;     /* traps pending? */
 EXTERN volatile sig_atomic_t intrsig;  /* pending trap interrupts command */
-EXTERN volatile sig_atomic_t fatal_trap;/* received a fatal signal */
+EXTERN volatile sig_atomic_t fatal_trap; /* received a fatal signal */
 extern Trap    sigtraps[NSIG+1];
 
 /* got_winch = 1 when we need to re-adjust the window size */
 #ifdef SIGWINCH
-EXTERN volatile sig_atomic_t got_winch I__(1);
+EXTERN volatile sig_atomic_t got_winch E_INIT(1);
 #else
 #define got_winch      true
 #endif
@@ -718,7 +764,7 @@ enum tmout_enum {
        TMOUT_LEAVING           /* have timed out */
 };
 EXTERN unsigned int ksh_tmout;
-EXTERN enum tmout_enum ksh_tmout_state I__(TMOUT_EXECUTING);
+EXTERN enum tmout_enum ksh_tmout_state E_INIT(TMOUT_EXECUTING);
 
 /* For "You have stopped jobs" message */
 EXTERN int really_exit;
@@ -738,13 +784,13 @@ EXTERN int really_exit;
 
 extern unsigned char chtypes[];
 
-#define ctype(c, t)    !!( ((t) == C_SUBOP2) ?                         \
+#define ctype(c, t)    tobool( ((t) == C_SUBOP2) ?                     \
                            (((c) == '#' || (c) == '%') ? 1 : 0) :      \
-                           (chtypes[(unsigned char)(c)]&(t)) )
+                           (chtypes[(unsigned char)(c)] & (t)) )
 #define ksh_isalphx(c) ctype((c), C_ALPHA)
 #define ksh_isalnux(c) ctype((c), C_ALPHA | C_DIGIT)
 
-EXTERN int ifs0 I__(' ');      /* for "$*" */
+EXTERN int ifs0 E_INIT(' ');   /* for "$*" */
 
 /* Argument parsing for built-in commands and getopts command */
 
@@ -758,14 +804,18 @@ EXTERN int ifs0 I__(' '); /* for "$*" */
 #define GI_PLUS                BIT(1)  /* an option started with +... */
 #define GI_MINUSMINUS  BIT(2)  /* arguments were ended with -- */
 
+/* in case some OS defines these */
+#undef optarg
+#undef optind
+
 typedef struct {
-       const char      *optarg;
-       int             optind;
-       int             uoptind;/* what user sees in $OPTIND */
-       int             flags;  /* see GF_* */
-       int             info;   /* see GI_* */
-       unsigned int    p;      /* 0 or index into argv[optind - 1] */
-       char            buf[2]; /* for bad option OPTARG value */
+       const char *optarg;
+       int optind;
+       int uoptind;            /* what user sees in $OPTIND */
+       int flags;              /* see GF_* */
+       int info;               /* see GI_* */
+       unsigned int p;         /* 0 or index into argv[optind - 1] */
+       char buf[2];            /* for bad option OPTARG value */
 } Getopt;
 
 EXTERN Getopt builtin_opt;     /* for shell builtin commands */
@@ -773,7 +823,9 @@ EXTERN Getopt user_opt;             /* parsing state for getopts builtin command */
 
 /* This for co-processes */
 
-typedef int32_t Coproc_id; /* something that won't (realisticly) wrap */
+/* something that won't (realisticly) wrap */
+typedef int32_t Coproc_id;
+
 struct coproc {
        void *job;      /* 0 or job of co-process using input pipe */
        int read;       /* pipe from co-process's stdout */
@@ -784,27 +836,31 @@ struct coproc {
 };
 EXTERN struct coproc coproc;
 
-/* Used in jobs.c and by coprocess stuff in exec.c */
+#ifndef MKSH_NOPROSPECTOFWORK
+/* used in jobs.c and by coprocess stuff in exec.c and select() calls */
 EXTERN sigset_t                sm_default, sm_sigchld;
+#endif
 
 /* name of called builtin function (used by error functions) */
 EXTERN const char *builtin_argv0;
-EXTERN Tflag builtin_flag;     /* flags of called builtin (SPEC_BI, etc.) */
+/* flags of called builtin (SPEC_BI, etc.) */
+EXTERN uint32_t builtin_flag;
 
-/* current working directory, and size of memory allocated for same */
+/* current working directory */
 EXTERN char    *current_wd;
-EXTERN size_t  current_wd_size;
 
-/* Minimum required space to work with on a line - if the prompt leaves less
- * space than this on a line, the prompt is truncated.
+/*
+ * Minimum required space to work with on a line - if the prompt leaves
+ * less space than this on a line, the prompt is truncated.
  */
 #define MIN_EDIT_SPACE 7
-/* Minimum allowed value for x_cols: 2 for prompt, 3 for " < " at end of line
+/*
+ * Minimum allowed value for x_cols: 2 for prompt, 3 for " < " at end of line
  */
 #define MIN_COLS       (2 + MIN_EDIT_SPACE + 3)
 #define MIN_LINS       3
-EXTERN mksh_ari_t x_cols I__(80);      /* tty columns */
-EXTERN mksh_ari_t x_lins I__(-1);      /* tty lines */
+EXTERN mksh_ari_t x_cols E_INIT(80);   /* tty columns */
+EXTERN mksh_ari_t x_lins E_INIT(-1);   /* tty lines */
 
 /* These to avoid bracket matching problems */
 #define OPAREN '('
@@ -814,8 +870,19 @@ EXTERN mksh_ari_t x_lins I__(-1);  /* tty lines */
 #define OBRACE '{'
 #define CBRACE '}'
 
+
 /* Determine the location of the system (common) profile */
-#define KSH_SYSTEM_PROFILE "/etc/profile"
+
+/* This is deliberately not configurable via CPPFLAGS */
+#if defined(ANDROID)
+#define MKSH_ETC_LOCATION      "/system/etc"
+#else
+#define MKSH_ETC_LOCATION      "/etc"
+#endif
+
+#define MKSH_SYSTEM_PROFILE    MKSH_ETC_LOCATION "/profile"
+#define MKSH_SUID_PROFILE      MKSH_ETC_LOCATION "/suid_profile"
+
 
 /* Used by v_evaluate() and setstr() to control action when error occurs */
 #define KSH_UNWIND_ERROR       0       /* unwind the stack (longjmp) */
@@ -825,24 +892,19 @@ EXTERN mksh_ari_t x_lins I__(-1); /* tty lines */
  * Shell file I/O routines
  */
 
-#define SHF_BSIZE      512
+#define SHF_BSIZE              512
 
-#define shf_fileno(shf)        ((shf)->fd)
+#define shf_fileno(shf)                ((shf)->fd)
 #define shf_setfileno(shf,nfd) ((shf)->fd = (nfd))
-#ifdef MKSH_SMALL
-int shf_getc(struct shf *);
-int shf_putc(int, struct shf *);
-#else
-#define shf_getc(shf)          ((shf)->rnleft > 0 ? \
+#define shf_getc_(shf)         ((shf)->rnleft > 0 ? \
                                    (shf)->rnleft--, *(shf)->rp++ : \
                                    shf_getchar(shf))
-#define shf_putc(c, shf)       ((shf)->wnleft == 0 ? \
+#define shf_putc_(c, shf)      ((shf)->wnleft == 0 ? \
                                    shf_putchar((c), (shf)) : \
                                    ((shf)->wnleft--, *(shf)->wp++ = (c)))
-#endif
 #define shf_eof(shf)           ((shf)->flags & SHF_EOF)
 #define shf_error(shf)         ((shf)->flags & SHF_ERROR)
-#define shf_errno(shf)         ((shf)->errno_)
+#define shf_errno(shf)         ((shf)->errnosv)
 #define shf_clearerr(shf)      ((shf)->flags &= ~(SHF_EOF | SHF_ERROR))
 
 /* Flags passed to shf_*open() */
@@ -872,14 +934,14 @@ struct shf {
        unsigned char *rp;      /* read: current position in buffer */
        unsigned char *wp;      /* write: current position in buffer */
        unsigned char *buf;     /* buffer */
+       ssize_t bsize;          /* actual size of buf */
+       ssize_t rbsize;         /* size of buffer (1 if SHF_UNBUF) */
+       ssize_t rnleft;         /* read: how much data left in buffer */
+       ssize_t wbsize;         /* size of buffer (0 if SHF_UNBUF) */
+       ssize_t wnleft;         /* write: how much space left in buffer */
        int flags;              /* see SHF_* */
-       int rbsize;             /* size of buffer (1 if SHF_UNBUF) */
-       int rnleft;             /* read: how much data left in buffer */
-       int wbsize;             /* size of buffer (0 if SHF_UNBUF) */
-       int wnleft;             /* write: how much space left in buffer */
        int fd;                 /* file descriptor */
-       int errno_;             /* saved value of errno after error */
-       int bsize;              /* actual size of buf */
+       int errnosv;            /* saved value of errno after error */
 };
 
 extern struct shf shf_iob[];
@@ -887,34 +949,44 @@ extern struct shf shf_iob[];
 struct table {
        Area *areap;            /* area to allocate entries */
        struct tbl **tbls;      /* hashed table items */
-       short size, nfree;      /* hash size (always 2^^n), free entries */
+       size_t nfree;           /* free table entries */
+       uint8_t tshift;         /* table size (2^tshift) */
 };
 
-struct tbl {                   /* table item */
-       Area *areap;            /* area to allocate from */
+/* table item */
+struct tbl {
+       /* Area to allocate from */
+       Area *areap;
+       /* value */
        union {
-               char *s;                /* string */
-               mksh_ari_t i;           /* integer */
-               mksh_uari_t u;          /* unsigned integer */
-               int (*f)(const char **);/* int function */
-               struct op *t;           /* "function" tree */
-       } val;                  /* value */
+               char *s;                        /* string */
+               mksh_ari_t i;                   /* integer */
+               mksh_uari_t u;                  /* unsigned integer */
+               int (*f)(const char **);        /* built-in command */
+               struct op *t;                   /* "function" tree */
+       } val;
        union {
                struct tbl *array;      /* array values */
                const char *fpath;      /* temporary path to undef function */
        } u;
        union {
-               int field;      /* field with for -L/-R/-Z */
-               int errno_;     /* CEXEC/CTALIAS */
+               int field;              /* field with for -L/-R/-Z */
+               int errnov;             /* CEXEC/CTALIAS */
        } u2;
-       int type;               /* command type (see below), base (if INTEGER),
-                                * or offset from val.s of value (if EXPORT) */
-       Tflag flag;             /* flags */
        union {
                uint32_t hval;          /* hash(name) */
                uint32_t index;         /* index for an array */
        } ua;
-       char name[4];           /* name -- variable length */
+       /*
+        * command type (see below), base (if INTEGER),
+        * offset from val.s of value (if EXPORT)
+        */
+       int type;
+       /* flags (see below) */
+       uint32_t flag;
+
+       /* actually longer: name (variable length) */
+       char name[4];
 };
 
 /* common flag bits */
@@ -936,7 +1008,7 @@ struct tbl {                       /* table item */
 #define LCASEV         BIT(17) /* convert to lower case */
 #define UCASEV_AL      BIT(18) /* convert to upper case / autoload function */
 #define INT_U          BIT(19) /* unsigned integer */
-#define INT_L          BIT(20) /* long integer (no-op) */
+#define INT_L          BIT(20) /* long integer (no-op but used as magic) */
 #define IMPORT         BIT(21) /* flag to typeset(): no arrays, must have = */
 #define LOCAL_COPY     BIT(22) /* with LOCAL - copy attrs from existing var */
 #define EXPRINEVAL     BIT(23) /* contents currently being evaluated */
@@ -950,8 +1022,10 @@ struct tbl {                      /* table item */
 #define FKSH           BIT(11) /* function defined with function x (vs x()) */
 #define SPEC_BI                BIT(12) /* a POSIX special builtin */
 #define REG_BI         BIT(13) /* a POSIX regular builtin */
-/* Attributes that can be set by the user (used to decide if an unset param
- * should be repoted by set/typeset). Does not include ARRAY or LOCAL.
+/*
+ * Attributes that can be set by the user (used to decide if an unset
+ * param should be repoted by set/typeset). Does not include ARRAY or
+ * LOCAL.
  */
 #define USERATTRIB     (EXPORT|INTEGER|RDONLY|LJUST|RJUST|ZEROFIL|\
                            LCASEV|UCASEV_AL|INT_U|INT_L)
@@ -959,6 +1033,12 @@ struct tbl {                      /* table item */
 #define arrayindex(vp) ((unsigned long)((vp)->flag & AINDEX ? \
                            (vp)->ua.index : 0))
 
+EXTERN enum {
+       SRF_NOP,
+       SRF_ENABLE,
+       SRF_DISABLE
+} set_refflag E_INIT(SRF_NOP);
+
 /* command types */
 #define CNONE          0       /* undefined */
 #define CSHELL         1       /* built-in */
@@ -981,13 +1061,13 @@ struct tbl {                     /* table item */
 #define AF_ARGV_ALLOC  0x1     /* argv[] array allocated */
 #define AF_ARGS_ALLOCED        0x2     /* argument strings allocated */
 #define AI_ARGV(a, i)  ((i) == 0 ? (a).argv[0] : (a).argv[(i) - (a).skip])
-#define AI_ARGC(a)     ((a).argc_ - (a).skip)
+#define AI_ARGC(a)     ((a).ai_argc - (a).skip)
 
 /* Argument info. Used for $#, $* for shell, functions, includes, etc. */
 struct arg_info {
        const char **argv;
        int flags;      /* AF_* */
-       int argc_;
+       int ai_argc;
        int skip;       /* first arg is argv[0], second is argv[1 + skip] */
 };
 
@@ -1063,9 +1143,14 @@ struct op {
                                         */
        int lineno;                     /* TCOM/TFUNC: LINENO for this */
        short type;                     /* operation type, see below */
-       union { /* WARNING: newtp(), tcopy() use evalflags = 0 to clear union */
-               short evalflags;        /* TCOM: arg expansion eval() flags */
-               short ksh_func;         /* TFUNC: function x (vs x()) */
+       /* WARNING: newtp(), tcopy() use evalflags = 0 to clear union */
+       union {
+               /* TCOM: arg expansion eval() flags */
+               short evalflags;
+               /* TFUNC: function x (vs x()) */
+               short ksh_func;
+               /* TPAT: termination character */
+               char charflag;
        } u;
 };
 
@@ -1115,27 +1200,29 @@ struct op {
  * IO redirection
  */
 struct ioword {
-       int     unit;   /* unit affected */
-       int     flag;   /* action (below) */
-       char    *name;  /* file name (unused if heredoc) */
-       char    *delim; /* delimiter for <<,<<- */
-       char    *heredoc;/* content of heredoc */
+       int     unit;           /* unit affected */
+       int     flag;           /* action (below) */
+       char    *name;          /* file name (unused if heredoc) */
+       char    *delim;         /* delimiter for <<,<<- */
+       char    *heredoc;       /* content of heredoc */
 };
 
 /* ioword.flag - type of redirection */
-#define IOTYPE 0xF     /* type: bits 0:3 */
-#define IOREAD 0x1     /* < */
-#define IOWRITE        0x2     /* > */
-#define IORDWR 0x3     /* <>: todo */
-#define IOHERE 0x4     /* << (here file) */
-#define IOCAT  0x5     /* >> */
-#define IODUP  0x6     /* <&/>& */
-#define IOEVAL BIT(4)  /* expand in << */
-#define IOSKIP BIT(5)  /* <<-, skip ^\t* */
-#define IOCLOB BIT(6)  /* >|, override -o noclobber */
-#define IORDUP BIT(7)  /* x<&y (as opposed to x>&y) */
-#define IONAMEXP BIT(8)        /* name has been expanded */
-#define IOBASH BIT(9)  /* &> etc. */
+#define IOTYPE         0xF     /* type: bits 0:3 */
+#define IOREAD         0x1     /* < */
+#define IOWRITE                0x2     /* > */
+#define IORDWR         0x3     /* <>: todo */
+#define IOHERE         0x4     /* << (here file) */
+#define IOCAT          0x5     /* >> */
+#define IODUP          0x6     /* <&/>& */
+#define IOEVAL         BIT(4)  /* expand in << */
+#define IOSKIP         BIT(5)  /* <<-, skip ^\t* */
+#define IOCLOB         BIT(6)  /* >|, override -o noclobber */
+#define IORDUP         BIT(7)  /* x<&y (as opposed to x>&y) */
+#define IONAMEXP       BIT(8)  /* name has been expanded */
+#define IOBASH         BIT(9)  /* &> etc. */
+#define IOHERESTR      BIT(10) /* <<< (here string) */
+#define IONDELIM       BIT(11) /* null delimiter (<<) */
 
 /* execute/exchild flags */
 #define XEXEC  BIT(0)          /* execute without forking */
@@ -1150,6 +1237,7 @@ struct ioword {
 #define XERROK BIT(8)          /* non-zero exit ok (for set -e) */
 #define XCOPROC BIT(9)         /* starting a co-process */
 #define XTIME  BIT(10)         /* timing TCOM command */
+#define XPIPEST        BIT(11)         /* want PIPESTATUS */
 
 /*
  * flags to control expansion of words (assumed by t->evalflags to fit
@@ -1161,9 +1249,9 @@ struct ioword {
 #define DOTILDE        BIT(3)          /* normal ~ expansion (first char) */
 #define DONTRUNCOMMAND BIT(4)  /* do not run $(command) things */
 #define DOASNTILDE BIT(5)      /* assignment ~ expansion (after =, :) */
-#define DOBRACE_ BIT(6)                /* used by expand(): do brace expansion */
-#define DOMAGIC_ BIT(7)                /* used by expand(): string contains MAGIC */
-#define DOTEMP_        BIT(8)          /* ditto : in word part of ${..[%#=?]..} */
+#define DOBRACE BIT(6)         /* used by expand(): do brace expansion */
+#define DOMAGIC BIT(7)         /* used by expand(): string contains MAGIC */
+#define DOTEMP BIT(8)          /* dito: in word part of ${..[%#=?]..} */
 #define DOVACHECK BIT(9)       /* var assign check (for typeset, set, etc) */
 #define DOMARKDIRS BIT(10)     /* force markdirs behaviour */
 
@@ -1180,7 +1268,7 @@ struct ioword {
 #define DB_BE  4       /* an inserted -BE */
 #define DB_PAT 5       /* a pattern argument */
 
-#define X_EXTRA              /* this many extra bytes in X string */
+#define X_EXTRA        20      /* this many extra bytes in X string */
 
 typedef struct XString {
        char *end, *beg;        /* end, begin of string */
@@ -1207,7 +1295,7 @@ typedef char *XStringP;
 
 /* check if there are at least n bytes left */
 #define XcheckN(xs, xp, n) do {                                        \
-       int more = ((xp) + (n)) - (xs).end;                     \
+       ssize_t more = ((xp) + (n)) - (xs).end;                 \
        if (more > 0)                                           \
                (xp) = Xcheck_grow_(&(xs), (xp), more);         \
 } while (/* CONSTCOND */ 0)
@@ -1230,7 +1318,7 @@ typedef char *XStringP;
 #define Xsavepos(xs, xp)       ((xp) - (xs).beg)
 #define Xrestpos(xs, xp, n)    ((xs).beg + (n))
 
-char *Xcheck_grow_(XString *, const char *, unsigned int);
+char *Xcheck_grow_(XString *, const char *, size_t);
 
 /*
  * expandable vector of generic pointers
@@ -1242,17 +1330,17 @@ typedef struct XPtrV {
 } XPtrV;
 
 #define XPinit(x, n) do {                                      \
-       void **vp__;                                            \
-       vp__ = alloc((n) * sizeof(void *), ATEMP);              \
-       (x).cur = (x).beg = vp__;                               \
-       (x).end = vp__ + (n);                                   \
+       void **XPinit_vp;                                       \
+       XPinit_vp = alloc2((n), sizeof(void *), ATEMP);         \
+       (x).cur = (x).beg = XPinit_vp;                          \
+       (x).end = XPinit_vp + (n);                              \
 } while (/* CONSTCOND */ 0)
 
 #define XPput(x, p) do {                                       \
        if ((x).cur >= (x).end) {                               \
                size_t n = XPsize(x);                           \
-               (x).beg = aresize((x).beg,                      \
-                   n * 2 * sizeof(void *), ATEMP);             \
+               (x).beg = aresize2((x).beg,                     \
+                   n, 2 * sizeof(void *), ATEMP);              \
                (x).cur = (x).beg + n;                          \
                (x).end = (x).cur + n;                          \
        }                                                       \
@@ -1261,7 +1349,7 @@ typedef struct XPtrV {
 
 #define XPptrv(x)      ((x).beg)
 #define XPsize(x)      ((x).cur - (x).beg)
-#define XPclose(x)     aresize((x).beg, XPsize(x) * sizeof(void *), ATEMP)
+#define XPclose(x)     aresize2((x).beg, XPsize(x), sizeof(void *), ATEMP)
 #define XPfree(x)      afree((x).beg, ATEMP)
 
 #define IDENT  64
@@ -1304,8 +1392,7 @@ struct source {
 #define SF_ALIAS       BIT(1)  /* faking space at end of alias */
 #define SF_ALIASEND    BIT(2)  /* faking space at end of alias */
 #define SF_TTY         BIT(3)  /* type == SSTDIN & it is a tty */
-#define SF_FIRST       BIT(4)  /* initial state (to ignore UTF-8 BOM) */
-#define SF_HASALIAS    BIT(5)  /* u.tblp valid (SALIAS, SEOF) */
+#define SF_HASALIAS    BIT(4)  /* u.tblp valid (SALIAS, SEOF) */
 
 typedef union {
        int i;
@@ -1341,6 +1428,8 @@ typedef union {
 #define BANG           278     /* ! */
 #define DBRACKET       279     /* [[ .. ]] */
 #define COPROC         280     /* |& */
+#define BRKEV          281     /* ;| */
+#define BRKFT          282     /* ;& */
 #define YYERRCODE      300
 
 /* flags to yylex */
@@ -1356,18 +1445,17 @@ typedef union {
 #define HEREDELIM      BIT(9)  /* parsing <<,<<- delimiter */
 #define LQCHAR         BIT(10) /* source string contains QCHAR */
 #define HEREDOC                BIT(11) /* parsing a here document */
-#define LETARRAY       BIT(12) /* copy expression inside =( ) */
 
-#define HERES  10              /* max << in line */
+#define HERES          10      /* max number of << in line */
 
 #undef CTRL
 #define        CTRL(x)         ((x) == '?' ? 0x7F : (x) & 0x1F)        /* ASCII */
 #define        UNCTRL(x)       ((x) ^ 0x40)                            /* ASCII */
 
 EXTERN Source *source;         /* yyparse/yylex source */
-EXTERN YYSTYPE yylval;         /* result from yylex */
-EXTERN struct ioword *heres [HERES], **herep;
-EXTERN char    ident [IDENT+1];
+EXTERN YYSTYPE yylval;         /* result from yylex */
+EXTERN struct ioword *heres[HERES], **herep;
+EXTERN char ident[IDENT+1];
 
 #define HISTORYSIZE    500     /* size of saved history */
 
@@ -1378,12 +1466,75 @@ EXTERN int histsize;    /* history size */
 /* user and system time of last j_waitjed job */
 EXTERN struct timeval j_usrtime, j_systime;
 
+#define notoktomul(fac1, fac2) (((fac1) != 0) && ((fac2) != 0) && \
+                                   ((SIZE_MAX / (fac1)) < (fac2)))
+#define notoktoadd(val, cnst)  ((val) > (SIZE_MAX - (cnst)))
+#define checkoktoadd(val, cnst) do {                                   \
+       if (notoktoadd((val), (cnst)))                                  \
+               internal_errorf(Tintovfl, (size_t)(val),                \
+                   '+', (size_t)(cnst));                               \
+} while (/* CONSTCOND */ 0)
+
+
+/* NZAT/NZAAT hashes based on Bob Jenkins' one-at-a-time hash */
+
+/* From: src/kern/include/nzat.h,v 1.2 2011/07/18 00:35:40 tg Exp $ */
+
+#define NZATInit(h) do {                                       \
+       (h) = 0;                                                \
+} while (/* CONSTCOND */ 0)
+
+#define NZATUpdateByte(h,b) do {                               \
+       (h) += (uint8_t)(b);                                    \
+       ++(h);                                                  \
+       (h) += (h) << 10;                                       \
+       (h) ^= (h) >> 6;                                        \
+} while (/* CONSTCOND */ 0)
+
+#define NZATUpdateMem(h,p,z) do {                              \
+       register const uint8_t *NZATUpdateMem_p;                \
+       register size_t NZATUpdateMem_z = (z);                  \
+                                                               \
+       NZATUpdateMem_p = (const void *)(p);                    \
+       while (NZATUpdateMem_z--)                               \
+               NZATUpdateByte((h), *NZATUpdateMem_p++);        \
+} while (/* CONSTCOND */ 0)
+
+#define NZATUpdateString(h,s) do {                             \
+       register const char *NZATUpdateString_s;                \
+       register uint8_t NZATUpdateString_c;                    \
+                                                               \
+       NZATUpdateString_s = (const void *)(s);                 \
+       while ((NZATUpdateString_c = *NZATUpdateString_s++))    \
+               NZATUpdateByte((h), NZATUpdateString_c);        \
+} while (/* CONSTCOND */ 0)
+
+/* not zero after termination */
+#define NZATFinish(h) do {                                     \
+       if ((h) == 0)                                           \
+               ++(h);                                          \
+       else                                                    \
+               NZAATFinish(h);                                 \
+} while (/* CONSTCOND */ 0)
+
+/* NULs zählen an allen Teilen */
+#define NZAATFinish(h) do {                                    \
+       (h) += (h) << 10;                                       \
+       (h) ^= (h) >> 6;                                        \
+       (h) += (h) << 3;                                        \
+       (h) ^= (h) >> 11;                                       \
+       (h) += (h) << 15;                                       \
+} while (/* CONSTCOND */ 0)
+
+
 /* lalloc.c */
 void ainit(Area *);
 void afreeall(Area *);
 /* these cannot fail and can take NULL (not for ap) */
-#define alloc(n, ap)   aresize(NULL, (n), (ap))
+#define alloc(n, ap)           aresize(NULL, (n), (ap))
+#define alloc2(m, n, ap)       aresize2(NULL, (m), (n), (ap))
 void *aresize(void *, size_t, Area *);
+void *aresize2(void *, size_t, size_t, Area *);
 void afree(void *, Area *);    /* can take NULL */
 /* edit.c */
 #ifndef MKSH_SMALL
@@ -1392,6 +1543,7 @@ int x_bind(const char *, const char *, bool, bool);
 int x_bind(const char *, const char *, bool);
 #endif
 void x_init(void);
+void x_mkraw(int, struct termios *, bool);
 int x_read(char *, size_t);
 /* eval.c */
 char *substitute(const char *, int);
@@ -1406,11 +1558,10 @@ int execute(struct op * volatile, volatile int, volatile int * volatile);
 int shcomexec(const char **);
 struct tbl *findfunc(const char *, uint32_t, bool);
 int define(const char *, struct op *);
-void builtin(const char *, int (*)(const char **));
+const char *builtin(const char *, int (*)(const char **));
 struct tbl *findcom(const char *, int);
-void flushcom(int);
-const char *search(const char *, const char *, int, int *);
-int search_access(const char *, int, int *);
+void flushcom(bool);
+const char *search_path(const char *, const char *, int, int *);
 int pr_menu(const char * const *);
 int pr_list(char * const *);
 /* expr.c */
@@ -1420,15 +1571,15 @@ int v_evaluate(struct tbl *, const char *, volatile int, bool);
 size_t utf_mbtowc(unsigned int *, const char *);
 size_t utf_wctomb(char *, unsigned int);
 int utf_widthadj(const char *, const char **);
-int utf_mbswidth(const char *);
+size_t utf_mbswidth(const char *);
 const char *utf_skipcols(const char *, int);
 size_t utf_ptradj(const char *);
 #ifndef MKSH_mirbsd_wcwidth
 int utf_wcwidth(unsigned int);
 #endif
+int ksh_access(const char *, int);
 /* funcs.c */
 int c_hash(const char **);
-int c_cd(const char **);
 int c_pwd(const char **);
 int c_print(const char **);
 #ifdef MKSH_PRINTF_BUILTIN
@@ -1448,7 +1599,6 @@ int c_kill(const char **);
 void getopts_reset(int);
 int c_getopts(const char **);
 int c_bind(const char **);
-int c_label(const char **);
 int c_shift(const char **);
 int c_umask(const char **);
 int c_dot(const char **);
@@ -1465,13 +1615,16 @@ int c_times(const char **);
 int timex(struct op *, int, volatile int *);
 void timex_hook(struct op *, char ** volatile *);
 int c_exec(const char **);
-int c_builtin(const char **);
+/* dummy function (just need pointer value), special case in comexec() */
+#define c_builtin shcomexec
 int c_test(const char **);
 #if HAVE_MKNOD
 int c_mknod(const char **);
 #endif
 int c_realpath(const char **);
 int c_rename(const char **);
+int c_cat(const char **);
+int c_sleep(const char **);
 /* histrap.c */
 void init_histvec(void);
 void hist_init(Source *);
@@ -1490,7 +1643,6 @@ void sethistfile(const char *);
 char **histpos(void);
 int histnum(int);
 int findhist(int, int, const char *, int);
-int findhistrel(const char *);
 char **hist_get_newest(bool);
 void inittraps(void);
 void alarm_init(void);
@@ -1500,7 +1652,7 @@ void intrcheck(void);
 int fatal_trap_check(void);
 int trap_pending(void);
 void runtraps(int intr);
-void runtrap(Trap *);
+void runtrap(Trap *, bool);
 void cleartraps(void);
 void restoresigs(void);
 void settrap(Trap *, const char *);
@@ -1523,7 +1675,6 @@ int j_kill(const char *, int);
 int j_resume(const char *, int);
 #endif
 int j_jobs(const char *, int, int);
-int j_njobs(void);
 void j_notify(void);
 pid_t j_async(void);
 int j_stopped_running(void);
@@ -1531,7 +1682,7 @@ int j_stopped_running(void);
 int yylex(int);
 void yyerror(const char *, ...)
     MKSH_A_NORETURN
-    MKSH_A_FORMAT(printf, 1, 2);
+    MKSH_A_FORMAT(__printf__, 1, 2);
 Source *pushs(int, Area *);
 void set_prompt(int, Source *);
 void pprompt(const char *, int);
@@ -1547,25 +1698,27 @@ void cleanup_parents_env(void);
 void cleanup_proc_env(void);
 void errorf(const char *, ...)
     MKSH_A_NORETURN
-    MKSH_A_FORMAT(printf, 1, 2);
+    MKSH_A_FORMAT(__printf__, 1, 2);
+void errorfx(int, const char *, ...)
+    MKSH_A_NORETURN
+    MKSH_A_FORMAT(__printf__, 2, 3);
 void warningf(bool, const char *, ...)
-    MKSH_A_FORMAT(printf, 2, 3);
+    MKSH_A_FORMAT(__printf__, 2, 3);
 void bi_errorf(const char *, ...)
-    MKSH_A_FORMAT(printf, 1, 2);
+    MKSH_A_FORMAT(__printf__, 1, 2);
 #define errorfz()      errorf("\1")
+#define errorfxz(rc)   errorfx((rc), "\1")
 #define bi_errorfz()   bi_errorf("\1")
-void internal_verrorf(const char *, va_list)
-    MKSH_A_FORMAT(printf, 1, 0);
 void internal_errorf(const char *, ...)
     MKSH_A_NORETURN
-    MKSH_A_FORMAT(printf, 1, 2);
+    MKSH_A_FORMAT(__printf__, 1, 2);
 void internal_warningf(const char *, ...)
-    MKSH_A_FORMAT(printf, 1, 2);
+    MKSH_A_FORMAT(__printf__, 1, 2);
 void error_prefix(bool);
 void shellf(const char *, ...)
-    MKSH_A_FORMAT(printf, 1, 2);
+    MKSH_A_FORMAT(__printf__, 1, 2);
 void shprintf(const char *, ...)
-    MKSH_A_FORMAT(printf, 1, 2);
+    MKSH_A_FORMAT(__printf__, 1, 2);
 int can_seek(int);
 void initio(void);
 int ksh_dup2(int, int, bool);
@@ -1581,11 +1734,10 @@ void coproc_write_close(int);
 int coproc_getfd(int, const char **);
 void coproc_cleanup(int);
 struct temp *maketemp(Area *, Temp_type, struct temp **);
-#define hash(s) oaathash_full((const uint8_t *)(s))
-uint32_t oaathash_full(register const uint8_t *);
-uint32_t hashmem(const void *, size_t);
-void ktinit(struct table *, Area *, size_t);
-struct tbl *ktsearch(struct table *, const char *, uint32_t);
+void ktinit(Area *, struct table *, uint8_t);
+struct tbl *ktscan(struct table *, const char *, uint32_t, struct tbl ***);
+/* table, name (key) to search for, hash(n) */
+#define ktsearch(tp, s, h) ktscan((tp), (s), (h), NULL)
 struct tbl *ktenter(struct table *, const char *, uint32_t);
 #define ktdelete(p)    do { p->flag = 0; } while (/* CONSTCOND */ 0)
 void ktwalk(struct tstate *, struct table *);
@@ -1602,22 +1754,23 @@ int getn(const char *, int *);
 int bi_getn(const char *, int *);
 int gmatchx(const char *, const char *, bool);
 int has_globbing(const char *, const char *);
-const unsigned char *pat_scan(const unsigned char *, const unsigned char *, int);
 int xstrcmp(const void *, const void *);
 void ksh_getopt_reset(Getopt *, int);
 int ksh_getopt(const char **, Getopt *, const char *);
 void print_value_quoted(const char *);
+char *quote_value(const char *);
 void print_columns(struct shf *, int,
-    char *(*)(char *, int, int, const void *),
-    const void *, int, int, bool);
+    char *(*)(char *, size_t, int, const void *),
+    const void *, size_t, size_t, bool);
 void strip_nuls(char *, int);
-int blocking_read(int, char *, int)
-    MKSH_A_BOUNDED(buffer, 2, 3);
+ssize_t blocking_read(int, char *, size_t)
+    MKSH_A_BOUNDED(__buffer__, 2, 3);
 int reset_nonblock(int);
-char *ksh_get_wd(size_t *);
-int make_path(const char *, const char *, char **, XString *, int *);
+char *ksh_get_wd(void);
+char *do_realpath(const char *);
 void simplify_path(char *);
-void set_current_wd(char *);
+void set_current_wd(const char *);
+int c_cd(const char **);
 #ifdef MKSH_SMALL
 char *strdup_(const char *, Area *);
 char *strndup_(const char *, size_t, Area *);
@@ -1627,38 +1780,56 @@ int unbksl(bool, int (*)(void), void (*)(int));
 struct shf *shf_open(const char *, int, int, int);
 struct shf *shf_fdopen(int, int, struct shf *);
 struct shf *shf_reopen(int, int, struct shf *);
-struct shf *shf_sopen(char *, int, int, struct shf *);
+struct shf *shf_sopen(char *, ssize_t, int, struct shf *);
 int shf_close(struct shf *);
 int shf_fdclose(struct shf *);
 char *shf_sclose(struct shf *);
 int shf_flush(struct shf *);
-int shf_read(char *, int, struct shf *);
-char *shf_getse(char *, int, struct shf *);
+ssize_t shf_read(char *, ssize_t, struct shf *);
+char *shf_getse(char *, ssize_t, struct shf *);
 int shf_getchar(struct shf *s);
 int shf_ungetc(int, struct shf *);
+#ifdef MKSH_SMALL
+int shf_getc(struct shf *);
+int shf_putc(int, struct shf *);
+#else
+#define shf_getc shf_getc_
+#define shf_putc shf_putc_
+#endif
 int shf_putchar(int, struct shf *);
-int shf_puts(const char *, struct shf *);
-int shf_write(const char *, int, struct shf *);
-int shf_fprintf(struct shf *, const char *, ...)
-    MKSH_A_FORMAT(printf, 2, 3);
-int shf_snprintf(char *, int, const char *, ...)
-    MKSH_A_FORMAT(printf, 3, 4)
-    MKSH_A_BOUNDED(string, 1, 2);
+ssize_t shf_puts(const char *, struct shf *);
+ssize_t shf_write(const char *, ssize_t, struct shf *);
+ssize_t shf_fprintf(struct shf *, const char *, ...)
+    MKSH_A_FORMAT(__printf__, 2, 3);
+ssize_t shf_snprintf(char *, ssize_t, const char *, ...)
+    MKSH_A_FORMAT(__printf__, 3, 4)
+    MKSH_A_BOUNDED(__string__, 1, 2);
 char *shf_smprintf(const char *, ...)
-    MKSH_A_FORMAT(printf, 1, 2);
-int shf_vfprintf(struct shf *, const char *, va_list)
-    MKSH_A_FORMAT(printf, 2, 0);
+    MKSH_A_FORMAT(__printf__, 1, 2);
+ssize_t shf_vfprintf(struct shf *, const char *, va_list)
+    MKSH_A_FORMAT(__printf__, 2, 0);
 /* syn.c */
 void initkeywords(void);
-struct op *compile(Source *);
+struct op *compile(Source *, bool);
+bool parse_usec(const char *, struct timeval *);
+char *yyrecursive(void);
 /* tree.c */
-int fptreef(struct shf *, int, const char *, ...);
-char *snptreef(char *, int, const char *, ...);
+void fptreef(struct shf *, int, const char *, ...);
+char *snptreef(char *, ssize_t, const char *, ...);
 struct op *tcopy(struct op *, Area *);
 char *wdcopy(const char *, Area *);
 const char *wdscan(const char *, int);
-char *wdstrip(const char *, bool, bool);
+#define WDS_TPUTS      BIT(0)          /* tputS (dumpwdvar) mode */
+#define WDS_KEEPQ      BIT(1)          /* keep quote characters */
+#define WDS_MAGIC      BIT(2)          /* make MAGIC */
+char *wdstrip(const char *, int);
 void tfree(struct op *, Area *);
+void dumpchar(struct shf *, int);
+void dumptree(struct shf *, struct op *);
+void dumpwdvar(struct shf *, const char *);
+void vistree(char *, size_t, struct op *)
+    MKSH_A_BOUNDED(__string__, 1, 2);
+void fpFUNCTf(struct shf *, int, bool, const char *, struct op *);
 /* var.c */
 void newblock(void);
 void popblock(void);
@@ -1669,22 +1840,26 @@ char *str_val(struct tbl *);
 int setstr(struct tbl *, const char *, int);
 struct tbl *setint_v(struct tbl *, struct tbl *, bool);
 void setint(struct tbl *, mksh_ari_t);
-struct tbl *typeset(const char *, Tflag, Tflag, int, int)
-    MKSH_A_NONNULL((nonnull (1)));
+void setint_n(struct tbl *, mksh_ari_t);
+struct tbl *typeset(const char *, uint32_t, uint32_t, int, int)
+    MKSH_A_NONNULL((__nonnull__ (1)));
 void unset(struct tbl *, int);
 const char *skip_varname(const char *, int);
-const char *skip_wdvarname(const char *, int);
-int is_wdvarname(const char *, int);
+const char *skip_wdvarname(const char *, bool);
+int is_wdvarname(const char *, bool);
 int is_wdvarassign(const char *);
+struct tbl *arraysearch(struct tbl *, uint32_t);
 char **makenv(void);
-void change_random(const void *, size_t);
 void change_winsz(void);
-int array_ref_len(const char *);
+size_t array_ref_len(const char *);
 char *arrayname(const char *);
 mksh_uari_t set_array(const char *, bool, const char **);
+uint32_t hash(const void *);
+void rndset(long);
 
 enum Test_op {
-       TO_NONOP = 0,   /* non-operator */
+       /* non-operator */
+       TO_NONOP = 0,
        /* unary operators */
        TO_STNZE, TO_STZER, TO_OPTION,
        TO_FILAXST,
@@ -1718,15 +1893,15 @@ typedef enum Test_meta Test_meta;
 
 typedef struct test_env {
        union {
-               const char **wp;/* used by ptest_* */
-               XPtrV *av;      /* used by dbtestp_* */
+               const char **wp;        /* used by ptest_* */
+               XPtrV *av;              /* used by dbtestp_* */
        } pos;
-       const char **wp_end;    /* used by ptest_* */
+       const char **wp_end;            /* used by ptest_* */
        Test_op (*isa)(struct test_env *, Test_meta);
        const char *(*getopnd) (struct test_env *, Test_op, bool);
        int (*eval)(struct test_env *, Test_op, const char *, const char *, bool);
        void (*error)(struct test_env *, int, const char *);
-       int flags;              /* TEF_* */
+       int flags;                      /* TEF_* */
 } Test_env;
 
 extern const char *const dbtest_tokens[];
@@ -1735,8 +1910,8 @@ Test_op   test_isop(Test_meta, const char *);
 int test_eval(Test_env *, Test_op, const char *, const char *, bool);
 int test_parse(Test_env *);
 
-EXTERN int tty_fd I__(-1);     /* dup'd tty file descriptor */
-EXTERN int tty_devtty;         /* true if tty_fd is from /dev/tty */
+EXTERN int tty_fd E_INIT(-1);  /* dup'd tty file descriptor */
+EXTERN bool tty_devtty;                /* true if tty_fd is from /dev/tty */
 EXTERN struct termios tty_state;       /* saved tty state */
 
 extern void tty_init(bool, bool);
@@ -1747,6 +1922,6 @@ extern void tty_close(void);
 # undef EXTERN_DEFINED
 # undef EXTERN
 #endif
-#undef I__
+#undef E_INIT
 
 #endif /* !MKSH_INCLUDES_ONLY */
index aa5481e..a850220 100644 (file)
@@ -1,5 +1,5 @@
 #if defined(SHFLAGS_DEFNS)
-__RCSID("$MirOS: src/bin/mksh/sh_flags.h,v 1.7 2010/07/13 13:07:58 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/sh_flags.h,v 1.9 2011/06/12 15:37:10 tg Exp $");
 #define FN(sname,cname,ochar,flags)    /* nothing */
 #elif defined(SHFLAGS_ENUMS)
 #define FN(sname,cname,ochar,flags)    cname,
@@ -21,9 +21,6 @@ __RCSID("$MirOS: src/bin/mksh/sh_flags.h,v 1.7 2010/07/13 13:07:58 tg Exp $");
 /* -a  all new parameters are created with the export attribute */
 F0("allexport", FEXPORT, 'a', OF_ANY)
 
-/* ./. backwards compat: dummy, emits a warning */
-FN("arc4random", FARC4RANDOM, 0, OF_ANY)
-
 #if HAVE_NICE
 /* ./. bgnice */
 FN("bgnice", FBGNICE, 0, OF_ANY)
@@ -135,6 +132,9 @@ FN(NULL, FCOMMAND, 'c', OF_CMDLINE)
  * anonymous flags: used internally by shell only (not visible to user)
  */
 
+/* ./. direct builtin call (divined from argv[0] multi-call binary) */
+FN(NULL, FAS_BUILTIN, 0, OF_INTERNAL)
+
 /* ./. (internal) initial shell was interactive */
 FN(NULL, FTALKING_I, 0, OF_INTERNAL)
 
index 0962752..7592e2b 100644 (file)
--- a/src/shf.c
+++ b/src/shf.c
@@ -1,7 +1,7 @@
 /*     $OpenBSD: shf.c,v 1.15 2006/04/02 00:48:33 deraadt Exp $        */
 
 /*-
- * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2011
  *     Thorsten Glaser <tg@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
  * of dealing in the work, even if advised of the possibility of such
  * damage or existence of a defect, except proven that it results out
  * of said person's immediate fault when using the work as intended.
+ *-
+ * Use %zX instead of %p and floating point isn't supported at all.
  */
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/shf.c,v 1.36 2010/07/19 22:41:04 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/shf.c,v 1.44 2011/09/07 15:24:20 tg Exp $");
 
 /* flags to shf_emptybuf() */
 #define EB_READSW      0x01    /* about to switch to reading */
@@ -37,7 +39,8 @@ __RCSID("$MirOS: src/bin/mksh/shf.c,v 1.36 2010/07/19 22:41:04 tg Exp $");
 static int shf_fillbuf(struct shf *);
 static int shf_emptybuf(struct shf *, int);
 
-/* Open a file. First three args are for open(), last arg is flags for
+/*
+ * Open a file. First three args are for open(), last arg is flags for
  * this package. Returns NULL if file could not be opened, or if a dup
  * fails.
  */
@@ -45,7 +48,9 @@ struct shf *
 shf_open(const char *name, int oflags, int mode, int sflags)
 {
        struct shf *shf;
-       int bsize = sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE;
+       ssize_t bsize =
+           /* at most 512 */
+           sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE;
        int fd;
 
        /* Done before open so if alloca fails, fd won't be lost. */
@@ -79,11 +84,11 @@ shf_open(const char *name, int oflags, int mode, int sflags)
        return (shf_reopen(fd, sflags, shf));
 }
 
-/* Set up the shf structure for a file descriptor. Doesn't fail. */
-struct shf *
-shf_fdopen(int fd, int sflags, struct shf *shf)
+/* helper function for shf_fdopen and shf_reopen */
+static void
+shf_open_hlp(int fd, int *sflagsp, const char *where)
 {
-       int bsize = sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE;
+       int sflags = *sflagsp;
 
        /* use fcntl() to figure out correct read/write flags */
        if (sflags & SHF_GETFL) {
@@ -105,11 +110,22 @@ shf_fdopen(int fd, int sflags, struct shf *shf)
                                break;
                        }
                }
+               *sflagsp = sflags;
        }
 
        if (!(sflags & (SHF_RD | SHF_WR)))
-               internal_errorf("shf_fdopen: missing read/write");
+               internal_errorf("%s: %s", where, "missing read/write");
+}
+
+/* Set up the shf structure for a file descriptor. Doesn't fail. */
+struct shf *
+shf_fdopen(int fd, int sflags, struct shf *shf)
+{
+       ssize_t bsize =
+           /* at most 512 */
+           sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE;
 
+       shf_open_hlp(fd, &sflags, "shf_fdopen");
        if (shf) {
                if (bsize) {
                        shf->buf = alloc(bsize, ATEMP);
@@ -129,7 +145,7 @@ shf_fdopen(int fd, int sflags, struct shf *shf)
        shf->wnleft = 0; /* force call to shf_emptybuf() */
        shf->wbsize = sflags & SHF_UNBUF ? 0 : bsize;
        shf->flags = sflags;
-       shf->errno_ = 0;
+       shf->errnosv = 0;
        shf->bsize = bsize;
        if (sflags & SHF_CLEXEC)
                fcntl(fd, F_SETFD, FD_CLOEXEC);
@@ -140,34 +156,13 @@ shf_fdopen(int fd, int sflags, struct shf *shf)
 struct shf *
 shf_reopen(int fd, int sflags, struct shf *shf)
 {
-       int bsize = sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE;
-
-       /* use fcntl() to figure out correct read/write flags */
-       if (sflags & SHF_GETFL) {
-               int flags = fcntl(fd, F_GETFL, 0);
-
-               if (flags < 0)
-                       /* will get an error on first read/write */
-                       sflags |= SHF_RDWR;
-               else {
-                       switch (flags & O_ACCMODE) {
-                       case O_RDONLY:
-                               sflags |= SHF_RD;
-                               break;
-                       case O_WRONLY:
-                               sflags |= SHF_WR;
-                               break;
-                       case O_RDWR:
-                               sflags |= SHF_RDWR;
-                               break;
-                       }
-               }
-       }
+       ssize_t bsize =
+           /* at most 512 */
+           sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE;
 
-       if (!(sflags & (SHF_RD | SHF_WR)))
-               internal_errorf("shf_reopen: missing read/write");
+       shf_open_hlp(fd, &sflags, "shf_reopen");
        if (!shf || !shf->buf || shf->bsize < bsize)
-               internal_errorf("shf_reopen: bad shf/buf/bsize");
+               internal_errorf("%s: %s", "shf_reopen", "bad shf/buf/bsize");
 
        /* assumes shf->buf and shf->bsize already set up */
        shf->fd = fd;
@@ -177,26 +172,27 @@ shf_reopen(int fd, int sflags, struct shf *shf)
        shf->wnleft = 0; /* force call to shf_emptybuf() */
        shf->wbsize = sflags & SHF_UNBUF ? 0 : bsize;
        shf->flags = (shf->flags & (SHF_ALLOCS | SHF_ALLOCB)) | sflags;
-       shf->errno_ = 0;
+       shf->errnosv = 0;
        if (sflags & SHF_CLEXEC)
                fcntl(fd, F_SETFD, FD_CLOEXEC);
        return (shf);
 }
 
-/* Open a string for reading or writing. If reading, bsize is the number
+/*
+ * Open a string for reading or writing. If reading, bsize is the number
  * of bytes that can be read. If writing, bsize is the maximum number of
- * bytes that can be written. If shf is not null, it is filled in and
- * returned, if it is null, shf is allocated. If writing and buf is null
+ * bytes that can be written. If shf is not NULL, it is filled in and
+ * returned, if it is NULL, shf is allocated. If writing and buf is NULL
  * and SHF_DYNAMIC is set, the buffer is allocated (if bsize > 0, it is
  * used for the initial size). Doesn't fail.
- * When writing, a byte is reserved for a trailing null - see shf_sclose().
+ * When writing, a byte is reserved for a trailing NUL - see shf_sclose().
  */
 struct shf *
-shf_sopen(char *buf, int bsize, int sflags, struct shf *shf)
+shf_sopen(char *buf, ssize_t bsize, int sflags, struct shf *shf)
 {
        /* can't have a read+write string */
        if (!(!(sflags & SHF_RD) ^ !(sflags & SHF_WR)))
-               internal_errorf("shf_sopen: flags 0x%x", sflags);
+               internal_errorf("%s: flags 0x%X", "shf_sopen", sflags);
 
        if (!shf) {
                shf = alloc(sizeof(struct shf), ATEMP);
@@ -216,7 +212,7 @@ shf_sopen(char *buf, int bsize, int sflags, struct shf *shf)
        shf->wnleft = bsize - 1;        /* space for a '\0' */
        shf->wbsize = bsize;
        shf->flags = sflags | SHF_STRING;
-       shf->errno_ = 0;
+       shf->errnosv = 0;
        shf->bsize = bsize;
 
        return (shf);
@@ -260,7 +256,8 @@ shf_fdclose(struct shf *shf)
        return (ret);
 }
 
-/* Close a string - if it was opened for writing, it is null terminated;
+/*
+ * Close a string - if it was opened for writing, it is NUL terminated;
  * returns a pointer to the string and frees shf if it was allocated
  * (does not free string if it was allocated).
  */
@@ -269,7 +266,7 @@ shf_sclose(struct shf *shf)
 {
        unsigned char *s = shf->buf;
 
-       /* null terminate */
+       /* NUL terminate */
        if (shf->flags & SHF_WR) {
                shf->wnleft++;
                shf_putc('\0', shf);
@@ -279,7 +276,8 @@ shf_sclose(struct shf *shf)
        return ((char *)s);
 }
 
-/* Un-read what has been read but not examined, or write what has been
+/*
+ * Un-read what has been read but not examined, or write what has been
  * buffered. Returns 0 for success, EOF for (write) error.
  */
 int
@@ -289,10 +287,10 @@ shf_flush(struct shf *shf)
                return ((shf->flags & SHF_WR) ? EOF : 0);
 
        if (shf->fd < 0)
-               internal_errorf("shf_flush: no fd");
+               internal_errorf("%s: %s", "shf_flush", "no fd");
 
        if (shf->flags & SHF_ERROR) {
-               errno = shf->errno_;
+               errno = shf->errnosv;
                return (EOF);
        }
 
@@ -310,7 +308,8 @@ shf_flush(struct shf *shf)
        return (0);
 }
 
-/* Write out any buffered data. If currently reading, flushes the read
+/*
+ * Write out any buffered data. If currently reading, flushes the read
  * buffer. Returns 0 for success, EOF for (write) error.
  */
 static int
@@ -319,15 +318,16 @@ shf_emptybuf(struct shf *shf, int flags)
        int ret = 0;
 
        if (!(shf->flags & SHF_STRING) && shf->fd < 0)
-               internal_errorf("shf_emptybuf: no fd");
+               internal_errorf("%s: %s", "shf_emptybuf", "no fd");
 
        if (shf->flags & SHF_ERROR) {
-               errno = shf->errno_;
+               errno = shf->errnosv;
                return (EOF);
        }
 
        if (shf->flags & SHF_READING) {
-               if (flags & EB_READSW) /* doesn't happen */
+               if (flags & EB_READSW)
+                       /* doesn't happen */
                        return (0);
                ret = shf_flush(shf);
                shf->flags &= ~SHF_READING;
@@ -335,25 +335,26 @@ shf_emptybuf(struct shf *shf, int flags)
        if (shf->flags & SHF_STRING) {
                unsigned char *nbuf;
 
-               /* Note that we assume SHF_ALLOCS is not set if SHF_ALLOCB
-                * is set... (changing the shf pointer could cause problems)
+               /*
+                * Note that we assume SHF_ALLOCS is not set if
+                * SHF_ALLOCB is set... (changing the shf pointer could
+                * cause problems)
                 */
                if (!(flags & EB_GROW) || !(shf->flags & SHF_DYNAMIC) ||
                    !(shf->flags & SHF_ALLOCB))
                        return (EOF);
                /* allocate more space for buffer */
-               nbuf = aresize(shf->buf, 2 * shf->wbsize, shf->areap);
+               nbuf = aresize2(shf->buf, 2, shf->wbsize, shf->areap);
                shf->rp = nbuf + (shf->rp - shf->buf);
                shf->wp = nbuf + (shf->wp - shf->buf);
                shf->rbsize += shf->wbsize;
                shf->wnleft += shf->wbsize;
-               shf->wbsize *= 2;
+               shf->wbsize <<= 1;
                shf->buf = nbuf;
        } else {
                if (shf->flags & SHF_WRITING) {
-                       int ntowrite = shf->wp - shf->buf;
+                       ssize_t n, ntowrite = shf->wp - shf->buf;
                        unsigned char *buf = shf->buf;
-                       int n;
 
                        while (ntowrite > 0) {
                                n = write(shf->fd, buf, ntowrite);
@@ -362,11 +363,13 @@ shf_emptybuf(struct shf *shf, int flags)
                                            !(shf->flags & SHF_INTERRUPT))
                                                continue;
                                        shf->flags |= SHF_ERROR;
-                                       shf->errno_ = errno;
+                                       shf->errnosv = errno;
                                        shf->wnleft = 0;
                                        if (buf != shf->buf) {
-                                               /* allow a second flush
-                                                * to work */
+                                               /*
+                                                * allow a second flush
+                                                * to work
+                                                */
                                                memmove(shf->buf, buf,
                                                    ntowrite);
                                                shf->wp = shf->buf + ntowrite;
@@ -395,15 +398,17 @@ shf_emptybuf(struct shf *shf, int flags)
 static int
 shf_fillbuf(struct shf *shf)
 {
+       ssize_t n;
+
        if (shf->flags & SHF_STRING)
                return (0);
 
        if (shf->fd < 0)
-               internal_errorf("shf_fillbuf: no fd");
+               internal_errorf("%s: %s", "shf_fillbuf", "no fd");
 
        if (shf->flags & (SHF_EOF | SHF_ERROR)) {
                if (shf->flags & SHF_ERROR)
-                       errno = shf->errno_;
+                       errno = shf->errnosv;
                return (EOF);
        }
 
@@ -413,42 +418,39 @@ shf_fillbuf(struct shf *shf)
        shf->flags |= SHF_READING;
 
        shf->rp = shf->buf;
-       while (1) {
-               shf->rnleft = blocking_read(shf->fd, (char *) shf->buf,
-                   shf->rbsize);
-               if (shf->rnleft < 0 && errno == EINTR &&
-                   !(shf->flags & SHF_INTERRUPT))
+       while (/* CONSTCOND */ 1) {
+               n = blocking_read(shf->fd, (char *)shf->buf, shf->rbsize);
+               if (n < 0 && errno == EINTR && !(shf->flags & SHF_INTERRUPT))
                        continue;
                break;
        }
-       if (shf->rnleft <= 0) {
-               if (shf->rnleft < 0) {
-                       shf->flags |= SHF_ERROR;
-                       shf->errno_ = errno;
-                       shf->rnleft = 0;
-                       shf->rp = shf->buf;
-                       return (EOF);
-               }
-               shf->flags |= SHF_EOF;
+       if (n < 0) {
+               shf->flags |= SHF_ERROR;
+               shf->errnosv = errno;
+               shf->rnleft = 0;
+               shf->rp = shf->buf;
+               return (EOF);
        }
+       if ((shf->rnleft = n) == 0)
+               shf->flags |= SHF_EOF;
        return (0);
 }
 
-/* Read a buffer from shf. Returns the number of bytes read into buf,
- * if no bytes were read, returns 0 if end of file was seen, EOF if
- * a read error occurred.
+/*
+ * Read a buffer from shf. Returns the number of bytes read into buf, if
+ * no bytes were read, returns 0 if end of file was seen, EOF if a read
+ * error occurred.
  */
-int
-shf_read(char *buf, int bsize, struct shf *shf)
+ssize_t
+shf_read(char *buf, ssize_t bsize, struct shf *shf)
 {
-       int orig_bsize = bsize;
-       int ncopy;
+       ssize_t ncopy, orig_bsize = bsize;
 
        if (!(shf->flags & SHF_RD))
-               internal_errorf("shf_read: flags %x", shf->flags);
+               internal_errorf("%s: flags 0x%X", "shf_read", shf->flags);
 
        if (bsize <= 0)
-               internal_errorf("shf_read: bsize %d", bsize);
+               internal_errorf("%s: %s %zd", "shf_write", "bsize", bsize);
 
        while (bsize > 0) {
                if (shf->rnleft == 0 &&
@@ -468,24 +470,27 @@ shf_read(char *buf, int bsize, struct shf *shf)
            orig_bsize - bsize);
 }
 
-/* Read up to a newline or EOF. The newline is put in buf; buf is always
- * null terminated. Returns NULL on read error or if nothing was read before
- * end of file, returns a pointer to the null byte in buf otherwise.
+/*
+ * Read up to a newline or EOF. The newline is put in buf; buf is always
+ * NUL terminated. Returns NULL on read error or if nothing was read
+ * before end of file, returns a pointer to the NUL byte in buf
+ * otherwise.
  */
 char *
-shf_getse(char *buf, int bsize, struct shf *shf)
+shf_getse(char *buf, ssize_t bsize, struct shf *shf)
 {
        unsigned char *end;
-       int ncopy;
+       ssize_t ncopy;
        char *orig_buf = buf;
 
        if (!(shf->flags & SHF_RD))
-               internal_errorf("shf_getse: flags %x", shf->flags);
+               internal_errorf("%s: flags 0x%X", "shf_getse", shf->flags);
 
        if (bsize <= 0)
                return (NULL);
 
-       --bsize;        /* save room for null */
+       /* save room for NUL */
+       --bsize;        
        do {
                if (shf->rnleft == 0) {
                        if (shf_fillbuf(shf) == EOF)
@@ -495,7 +500,7 @@ shf_getse(char *buf, int bsize, struct shf *shf)
                                return (buf == orig_buf ? NULL : buf);
                        }
                }
-               end = (unsigned char *)memchr((char *) shf->rp, '\n',
+               end = (unsigned char *)memchr((char *)shf->rp, '\n',
                    shf->rnleft);
                ncopy = end ? end - shf->rp + 1 : shf->rnleft;
                if (ncopy > bsize)
@@ -515,7 +520,7 @@ int
 shf_getchar(struct shf *shf)
 {
        if (!(shf->flags & SHF_RD))
-               internal_errorf("shf_getchar: flags %x", shf->flags);
+               internal_errorf("%s: flags 0x%X", "shf_getchar", shf->flags);
 
        if (shf->rnleft == 0 && (shf_fillbuf(shf) == EOF || shf->rnleft == 0))
                return (EOF);
@@ -523,14 +528,15 @@ shf_getchar(struct shf *shf)
        return (*shf->rp++);
 }
 
-/* Put a character back in the input stream. Returns the character if
+/*
+ * Put a character back in the input stream. Returns the character if
  * successful, EOF if there is no room.
  */
 int
 shf_ungetc(int c, struct shf *shf)
 {
        if (!(shf->flags & SHF_RD))
-               internal_errorf("shf_ungetc: flags %x", shf->flags);
+               internal_errorf("%s: flags 0x%X", "shf_ungetc", shf->flags);
 
        if ((shf->flags & SHF_ERROR) || c == EOF ||
            (shf->rp == shf->buf && shf->rnleft))
@@ -542,8 +548,9 @@ shf_ungetc(int c, struct shf *shf)
        if (shf->rp == shf->buf)
                shf->rp = shf->buf + shf->rbsize;
        if (shf->flags & SHF_STRING) {
-               /* Can unget what was read, but not something different - we
-                * don't want to modify a string.
+               /*
+                * Can unget what was read, but not something different;
+                * we don't want to modify a string.
                 */
                if (shf->rp[-1] != c)
                        return (EOF);
@@ -558,26 +565,27 @@ shf_ungetc(int c, struct shf *shf)
        return (c);
 }
 
-/* Write a character. Returns the character if successful, EOF if
- * the char could not be written.
+/*
+ * Write a character. Returns the character if successful, EOF if the
+ * char could not be written.
  */
 int
 shf_putchar(int c, struct shf *shf)
 {
        if (!(shf->flags & SHF_WR))
-               internal_errorf("shf_putchar: flags %x", shf->flags);
+               internal_errorf("%s: flags 0x%X", "shf_putchar", shf->flags);
 
        if (c == EOF)
                return (EOF);
 
        if (shf->flags & SHF_UNBUF) {
                unsigned char cc = (unsigned char)c;
-               int n;
+               ssize_t n;
 
                if (shf->fd < 0)
-                       internal_errorf("shf_putchar: no fd");
+                       internal_errorf("%s: %s", "shf_putchar", "no fd");
                if (shf->flags & SHF_ERROR) {
-                       errno = shf->errno_;
+                       errno = shf->errnosv;
                        return (EOF);
                }
                while ((n = write(shf->fd, &cc, 1)) != 1)
@@ -586,7 +594,7 @@ shf_putchar(int c, struct shf *shf)
                                    !(shf->flags & SHF_INTERRUPT))
                                        continue;
                                shf->flags |= SHF_ERROR;
-                               shf->errno_ = errno;
+                               shf->errnosv = errno;
                                return (EOF);
                        }
        } else {
@@ -600,10 +608,11 @@ shf_putchar(int c, struct shf *shf)
        return (c);
 }
 
-/* Write a string. Returns the length of the string if successful, EOF if
- * the string could not be written.
+/*
+ * Write a string. Returns the length of the string if successful, EOF
+ * if the string could not be written.
  */
-int
+ssize_t
 shf_puts(const char *s, struct shf *shf)
 {
        if (!s)
@@ -613,16 +622,16 @@ shf_puts(const char *s, struct shf *shf)
 }
 
 /* Write a buffer. Returns nbytes if successful, EOF if there is an error. */
-int
-shf_write(const char *buf, int nbytes, struct shf *shf)
+ssize_t
+shf_write(const char *buf, ssize_t nbytes, struct shf *shf)
 {
-       int n, ncopy, orig_nbytes = nbytes;
+       ssize_t n, ncopy, orig_nbytes = nbytes;
 
        if (!(shf->flags & SHF_WR))
-               internal_errorf("shf_write: flags %x", shf->flags);
+               internal_errorf("%s: flags 0x%X", "shf_write", shf->flags);
 
        if (nbytes < 0)
-               internal_errorf("shf_write: nbytes %d", nbytes);
+               internal_errorf("%s: %s %zd", "shf_write", "nbytes", nbytes);
 
        /* Don't buffer if buffer is empty and we're writting a large amount. */
        if ((ncopy = shf->wnleft) &&
@@ -659,7 +668,7 @@ shf_write(const char *buf, int nbytes, struct shf *shf)
                                                    !(shf->flags & SHF_INTERRUPT))
                                                        continue;
                                                shf->flags |= SHF_ERROR;
-                                               shf->errno_ = errno;
+                                               shf->errnosv = errno;
                                                shf->wnleft = 0;
                                                /*
                                                 * Note: fwrite(3) returns 0
@@ -684,11 +693,11 @@ shf_write(const char *buf, int nbytes, struct shf *shf)
        return (orig_nbytes);
 }
 
-int
+ssize_t
 shf_fprintf(struct shf *shf, const char *fmt, ...)
 {
        va_list args;
-       int n;
+       ssize_t n;
 
        va_start(args, fmt);
        n = shf_vfprintf(shf, fmt, args);
@@ -697,21 +706,23 @@ shf_fprintf(struct shf *shf, const char *fmt, ...)
        return (n);
 }
 
-int
-shf_snprintf(char *buf, int bsize, const char *fmt, ...)
+ssize_t
+shf_snprintf(char *buf, ssize_t bsize, const char *fmt, ...)
 {
        struct shf shf;
        va_list args;
-       int n;
+       ssize_t n;
 
        if (!buf || bsize <= 0)
-               internal_errorf("shf_snprintf: buf %p, bsize %d", buf, bsize);
+               internal_errorf("shf_snprintf: buf %zX, bsize %zd",
+                   (size_t)buf, bsize);
 
        shf_sopen(buf, bsize, SHF_WR, &shf);
        va_start(args, fmt);
        n = shf_vfprintf(&shf, fmt, args);
        va_end(args);
-       shf_sclose(&shf); /* null terminates */
+       /* NUL terminates */
+       shf_sclose(&shf); 
        return (n);
 }
 
@@ -725,20 +736,11 @@ shf_smprintf(const char *fmt, ...)
        va_start(args, fmt);
        shf_vfprintf(&shf, fmt, args);
        va_end(args);
-       return (shf_sclose(&shf)); /* null terminates */
+       /* NUL terminates */
+       return (shf_sclose(&shf));
 }
 
-#undef FP                      /* if you want floating point stuff */
-
-#ifndef DMAXEXP
-# define DMAXEXP       128     /* should be big enough */
-#endif
-
 #define BUF_SIZE       128
-/* must be > MAX(DMAXEXP, log10(pow(2, DSIGNIF))) + ceil(log10(DMAXEXP)) + 8
- * (I think); since it's hard to express as a constant, just use a large buffer
- */
-#define FPBUF_SIZE     (DMAXEXP+16)
 
 #define        FL_HASH         0x001   /* '#' seen */
 #define FL_PLUS                0x002   /* '+' seen */
@@ -750,19 +752,23 @@ shf_smprintf(const char *fmt, ...)
 #define FL_DOT         0x080   /* '.' seen */
 #define FL_UPPER       0x100   /* format character was uppercase */
 #define FL_NUMBER      0x200   /* a number was formated %[douxefg] */
+#define FL_SIZET       0x400   /* 'z' seen */
+#define FM_SIZES       0x430   /* h/l/z mask */
 
-
-int
+ssize_t
 shf_vfprintf(struct shf *shf, const char *fmt, va_list args)
 {
        const char *s;
        char c, *cp;
-       int tmp = 0, field, precision, len, flags;
+       int tmp = 0, flags;
+       ssize_t field, precision, len;
        unsigned long lnum;
        /* %#o produces the longest output */
        char numbuf[(8 * sizeof(long) + 2) / 3 + 1];
        /* this stuff for dealing with the buffer */
-       int nwritten = 0;
+       ssize_t nwritten = 0;
+
+#define VA(type) va_arg(args, type)
 
        if (!fmt)
                return (0);
@@ -774,13 +780,14 @@ shf_vfprintf(struct shf *shf, const char *fmt, va_list args)
                        continue;
                }
                /*
-                * This will accept flags/fields in any order - not
-                * just the order specified in printf(3), but this is
-                * the way _doprnt() seems to work (on bsd and sysV).
-                * The only restriction is that the format character must
-                * come last :-).
+                * This will accept flags/fields in any order - not just
+                * the order specified in printf(3), but this is the way
+                * _doprnt() seems to work (on BSD and SYSV). The only
+                * restriction is that the format character must come
+                * last :-).
                 */
-               flags = field = precision = 0;
+               flags = 0;
+               field = precision = 0;
                for ( ; (c = *fmt++) ; ) {
                        switch (c) {
                        case '#':
@@ -810,7 +817,7 @@ shf_vfprintf(struct shf *shf, const char *fmt, va_list args)
                                continue;
 
                        case '*':
-                               tmp = va_arg(args, int);
+                               tmp = VA(int);
                                if (flags & FL_DOT)
                                        precision = tmp;
                                else if ((field = tmp) < 0) {
@@ -820,19 +827,27 @@ shf_vfprintf(struct shf *shf, const char *fmt, va_list args)
                                continue;
 
                        case 'l':
+                               flags &= ~FM_SIZES;
                                flags |= FL_LONG;
                                continue;
 
                        case 'h':
+                               flags &= ~FM_SIZES;
                                flags |= FL_SHORT;
                                continue;
+
+                       case 'z':
+                               flags &= ~FM_SIZES;
+                               flags |= FL_SIZET;
+                               continue;
                        }
                        if (ksh_isdigit(c)) {
                                tmp = c - '0';
                                while (c = *fmt++, ksh_isdigit(c))
                                        tmp = tmp * 10 + c - '0';
                                --fmt;
-                               if (tmp < 0)            /* overflow? */
+                               if (tmp < 0)
+                                       /* overflow? */
                                        tmp = 0;
                                if (flags & FL_DOT)
                                        precision = tmp;
@@ -846,7 +861,8 @@ shf_vfprintf(struct shf *shf, const char *fmt, va_list args)
                if (precision < 0)
                        precision = 0;
 
-               if (!c)         /* nasty format */
+               if (!c)
+                       /* nasty format */
                        break;
 
                if (c >= 'A' && c <= 'Z') {
@@ -855,33 +871,34 @@ shf_vfprintf(struct shf *shf, const char *fmt, va_list args)
                }
 
                switch (c) {
-               case 'p': /* pointer */
-                       flags &= ~(FL_LONG | FL_SHORT);
-                       flags |= (sizeof(char *) > sizeof(int)) ?
-                           /* hope it fits.. */ FL_LONG : 0;
-                       /* aaahhh... */
                case 'd':
                case 'i':
+                       if (flags & FL_SIZET)
+                               lnum = (long)VA(ssize_t);
+                       else if (flags & FL_LONG)
+                               lnum = VA(long);
+                       else if (flags & FL_SHORT)
+                               lnum = (long)(short)VA(int);
+                       else
+                               lnum = (long)VA(int);
+                       goto integral;
+
                case 'o':
                case 'u':
                case 'x':
+                       if (flags & FL_SIZET)
+                               lnum = VA(size_t);
+                       else if (flags & FL_LONG)
+                               lnum = VA(unsigned long);
+                       else if (flags & FL_SHORT)
+                               lnum = (unsigned long)(unsigned short)VA(int);
+                       else
+                               lnum = (unsigned long)VA(unsigned int);
+
+ integral:
                        flags |= FL_NUMBER;
                        cp = numbuf + sizeof(numbuf);
-                       /*-
-                        * XXX any better way to do this?
-                        * XXX hopefully the compiler optimises this out
-                        *
-                        * For shorts, we want sign extend for %d but not
-                        * for %[oxu] - on 16 bit machines it doesn't matter.
-                        * Assumes C compiler has converted shorts to ints
-                        * before pushing them. XXX optimise this -tg
-                        */
-                       if (flags & FL_LONG)
-                               lnum = va_arg(args, unsigned long);
-                       else if ((sizeof(int) < sizeof(long)) && (c == 'd'))
-                               lnum = (long)va_arg(args, int);
-                       else
-                               lnum = va_arg(args, unsigned int);
+
                        switch (c) {
                        case 'd':
                        case 'i':
@@ -917,7 +934,6 @@ shf_vfprintf(struct shf *shf, const char *fmt, va_list args)
                                        *--cp = '0';
                                break;
 
-                       case 'p':
                        case 'x': {
                                const char *digits = (flags & FL_UPPER) ?
                                    digits_uc : digits_lc;
@@ -938,19 +954,20 @@ shf_vfprintf(struct shf *shf, const char *fmt, va_list args)
                                        field = precision;
                                        flags |= FL_ZERO;
                                } else
-                                       precision = len; /* no loss */
+                                       /* no loss */
+                                       precision = len;
                        }
                        break;
 
                case 's':
-                       if (!(s = va_arg(args, const char *)))
+                       if ((s = VA(const char *)) == NULL)
                                s = "(null)";
                        len = utf_mbswidth(s);
                        break;
 
                case 'c':
                        flags &= ~FL_DOT;
-                       numbuf[0] = (char)(va_arg(args, int));
+                       numbuf[0] = (char)(VA(int));
                        s = numbuf;
                        len = 1;
                        break;
@@ -1029,14 +1046,12 @@ shf_vfprintf(struct shf *shf, const char *fmt, va_list args)
 int
 shf_getc(struct shf *shf)
 {
-       return ((shf)->rnleft > 0 ? (shf)->rnleft--, *(shf)->rp++ :
-           shf_getchar(shf));
+       return (shf_getc_(shf));
 }
 
 int
 shf_putc(int c, struct shf *shf)
 {
-       return ((shf)->wnleft == 0 ? shf_putchar((c), (shf)) :
-           ((shf)->wnleft--, *(shf)->wp++ = (c)));
+       return (shf_putc_(c, shf));
 }
 #endif
diff --git a/src/strlcpy.c b/src/strlcpy.c
new file mode 100644 (file)
index 0000000..53f9130
--- /dev/null
@@ -0,0 +1,52 @@
+/*-
+ * Copyright (c) 2006, 2008, 2009
+ *     Thorsten Glaser <tg@mirbsd.org>
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "sh.h"
+
+__RCSID("$MirOS: src/bin/mksh/strlcpy.c,v 1.7 2009/06/10 18:12:50 tg Rel $");
+
+/*
+ * Copy src to string dst of size siz. At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz == 0).
+ * Returns strlen(src); if retval >= siz, truncation occurred.
+ */
+size_t
+strlcpy(char *dst, const char *src, size_t siz)
+{
+       const char *s = src;
+
+       if (siz == 0)
+               goto traverse_src;
+
+       /* copy as many chars as will fit */
+       while (--siz && (*dst++ = *s++))
+               ;
+
+       /* not enough room in dst */
+       if (siz == 0) {
+               /* safe to NUL-terminate dst since we copied <= siz-1 chars */
+               *dst = '\0';
+ traverse_src:
+               /* traverse rest of src */
+               while (*s++)
+                       ;
+       }
+
+       /* count does not include NUL */
+       return ((size_t)(s - src - 1));
+}
index 64b2867..b2a3f8b 100644 (file)
--- a/src/syn.c
+++ b/src/syn.c
@@ -1,7 +1,7 @@
 /*     $OpenBSD: syn.c,v 1.28 2008/07/23 16:34:38 jaredy Exp $ */
 
 /*-
- * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2011
  *     Thorsten Glaser <tg@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/syn.c,v 1.49 2010/07/17 22:09:39 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/syn.c,v 1.69 2011/09/07 15:24:21 tg Exp $");
+
+extern short subshell_nesting_level;
+extern void yyskiputf8bom(void);
 
 struct nesting_state {
        int start_token;        /* token than began nesting (eg, FOR) */
@@ -32,7 +35,7 @@ struct nesting_state {
 static void yyparse(void);
 static struct op *pipeline(int);
 static struct op *andor(void);
-static struct op *c_list(int);
+static struct op *c_list(bool);
 static struct ioword *synio(int);
 static struct op *nested(int, int, int);
 static struct op *get_command(int);
@@ -59,14 +62,14 @@ static void dbtestp_error(Test_env *, int, const char *) MKSH_A_NORETURN;
 static struct op *outtree;             /* yyparse output */
 static struct nesting_state nesting;   /* \n changed to ; */
 
-static int reject;             /* token(cf) gets symbol again */
-static int symbol;             /* yylex value */
+static bool reject;                    /* token(cf) gets symbol again */
+static int symbol;                     /* yylex value */
 
-#define REJECT         (reject = 1)
-#define ACCEPT         (reject = 0)
+#define REJECT         (reject = true)
+#define ACCEPT         (reject = false)
 #define token(cf)      ((reject) ? (ACCEPT, symbol) : (symbol = yylex(cf)))
 #define tpeek(cf)      ((reject) ? (symbol) : (REJECT, symbol = yylex(cf)))
-#define musthave(c,cf) do { if (token(cf) != (c)) syntaxerr(NULL); } while (0)
+#define musthave(c,cf) do { if (token(cf) != (c)) syntaxerr(NULL); } while (/* CONSTCOND */ 0)
 
 static void
 yyparse(void)
@@ -122,20 +125,23 @@ andor(void)
 }
 
 static struct op *
-c_list(int multi)
+c_list(bool multi)
 {
        struct op *t = NULL, *p, *tl = NULL;
-       int c, have_sep;
+       int c;
+       bool have_sep;
 
-       while (1) {
+       while (/* CONSTCOND */ 1) {
                p = andor();
-               /* Token has always been read/rejected at this point, so
+               /*
+                * Token has always been read/rejected at this point, so
                 * we don't worry about what flags to pass token()
                 */
                c = token(0);
-               have_sep = 1;
+               have_sep = true;
                if (c == '\n' && (multi || inalias(source))) {
-                       if (!p) /* ignore blank lines */
+                       if (!p)
+                               /* ignore blank lines */
                                continue;
                } else if (!p)
                        break;
@@ -143,7 +149,7 @@ c_list(int multi)
                        p = block(c == '&' ? TASYNC : TCOPROC,
                            p, NOBLOCK, NOWORDS);
                else if (c != ';')
-                       have_sep = 0;
+                       have_sep = false;
                if (!t)
                        t = p;
                else if (!tl)
@@ -161,7 +167,7 @@ static struct ioword *
 synio(int cf)
 {
        struct ioword *iop;
-       static struct ioword *nextiop = NULL;
+       static struct ioword *nextiop;
        bool ishere;
 
        if (nextiop != NULL) {
@@ -174,14 +180,18 @@ synio(int cf)
                return (NULL);
        ACCEPT;
        iop = yylval.iop;
-       ishere = (iop->flag&IOTYPE) == IOHERE;
+       if (iop->flag & IONDELIM)
+               goto gotnulldelim;
+       ishere = (iop->flag & IOTYPE) == IOHERE;
        musthave(LWORD, ishere ? HEREDELIM : 0);
        if (ishere) {
                iop->delim = yylval.cp;
-               if (*ident != 0) /* unquoted */
+               if (*ident != 0)
+                       /* unquoted */
+ gotnulldelim:
                        iop->flag |= IOEVAL;
                if (herep > &heres[HERES - 1])
-                       yyerror("too many <<s\n");
+                       yyerror("too many %ss\n", "<<");
                *herep++ = iop;
        } else
                iop->name = yylval.cp;
@@ -231,7 +241,8 @@ get_command(int cf)
        XPtrV args, vars;
        struct nesting_state old_nesting;
 
-       iops = alloc((NUFILE + 1) * sizeof(struct ioword *), ATEMP);
+       /* NUFILE is small enough to leave this addition unchecked */
+       iops = alloc2((NUFILE + 1), sizeof(struct ioword *), ATEMP);
        XPinit(args, 16);
        XPinit(vars, 16);
 
@@ -242,7 +253,8 @@ get_command(int cf)
                afree(iops, ATEMP);
                XPfree(args);
                XPfree(vars);
-               return (NULL); /* empty line */
+               /* empty line */
+               return (NULL);
 
        case LWORD:
        case REDIR:
@@ -250,21 +262,23 @@ get_command(int cf)
                syniocf &= ~(KEYWORD|ALIAS);
                t = newtp(TCOM);
                t->lineno = source->line;
-               while (1) {
+               while (/* CONSTCOND */ 1) {
                        cf = (t->u.evalflags ? ARRAYVAR : 0) |
                            (XPsize(args) == 0 ? ALIAS|VARASN : CMDWORD);
                        switch (tpeek(cf)) {
                        case REDIR:
                                while ((iop = synio(cf)) != NULL) {
                                        if (iopn >= NUFILE)
-                                               yyerror("too many redirections\n");
+                                               yyerror("too many %ss\n",
+                                                   "redirection");
                                        iops[iopn++] = iop;
                                }
                                break;
 
                        case LWORD:
                                ACCEPT;
-                               /* the iopn == 0 and XPsize(vars) == 0 are
+                               /*
+                                * the iopn == 0 and XPsize(vars) == 0 are
                                 * dubious but AT&T ksh acts this way
                                 */
                                if (iopn == 0 && XPsize(vars) == 0 &&
@@ -279,7 +293,13 @@ get_command(int cf)
                                break;
 
                        case '(':
-                               /* Check for "> foo (echo hi)" which AT&T ksh
+#ifndef MKSH_SMALL
+                               if ((XPsize(args) == 0 || Flag(FKEYWORD)) &&
+                                   XPsize(vars) == 1 && is_wdvarassign(yylval.cp))
+                                       goto is_wdarrassign;
+#endif
+                               /*
+                                * Check for "> foo (echo hi)" which AT&T ksh
                                 * allows (not POSIX, but not disallowed)
                                 */
                                afree(t, ATEMP);
@@ -287,55 +307,50 @@ get_command(int cf)
                                        ACCEPT;
                                        goto Subshell;
                                }
-#ifndef MKSH_SMALL
-                               if ((XPsize(args) == 0 || Flag(FKEYWORD)) &&
-                                   XPsize(vars) == 1 && is_wdvarassign(yylval.cp))
-                                       goto is_wdarrassign;
-#endif
-                               /* Must be a function */
+
+                               /* must be a function */
                                if (iopn != 0 || XPsize(args) != 1 ||
                                    XPsize(vars) != 0)
                                        syntaxerr(NULL);
                                ACCEPT;
-                               /*(*/
-                               musthave(')', 0);
+                               musthave(/*(*/')', 0);
                                t = function_body(XPptrv(args)[0], false);
                                goto Leave;
 #ifndef MKSH_SMALL
  is_wdarrassign:
                        {
                                static const char set_cmd0[] = {
-                                       CHAR, 'e', CHAR, 'v',
-                                       CHAR, 'a', CHAR, 'l', EOS
+                                       CHAR, 's', CHAR, 'e',
+                                       CHAR, 't', EOS
                                };
                                static const char set_cmd1[] = {
-                                       CHAR, 's', CHAR, 'e',
-                                       CHAR, 't', CHAR, ' ',
                                        CHAR, '-', CHAR, 'A', EOS
                                };
                                static const char set_cmd2[] = {
                                        CHAR, '-', CHAR, '-', EOS
                                };
                                char *tcp;
-                               XPfree(vars);
-                               XPinit(vars, 16);
-                               /*
-                                * we know (or rather hope) that yylval.cp
-                                * contains a string "varname="
-                                */
-                               tcp = wdcopy(yylval.cp, ATEMP);
-                               tcp[wdscan(tcp, EOS) - tcp - 3] = EOS;
-                               /* now make an array assignment command */
-                               t = newtp(TCOM);
-                               t->lineno = source->line;
+
                                ACCEPT;
+
+                               /* manipulate the vars string */
+                               tcp = *(--vars.cur);
+                               /* 'varname=' -> 'varname' */
+                               tcp[wdscan(tcp, EOS) - tcp - 3] = EOS;
+
+                               /* construct new args strings */
                                XPput(args, wdcopy(set_cmd0, ATEMP));
                                XPput(args, wdcopy(set_cmd1, ATEMP));
                                XPput(args, tcp);
                                XPput(args, wdcopy(set_cmd2, ATEMP));
-                               musthave(LWORD,LETARRAY);
-                               XPput(args, yylval.cp);
-                               break;
+
+                               /* slurp in words till closing paren */
+                               while (token(CONTIN) == LWORD)
+                                       XPput(args, yylval.cp);
+                               if (symbol != /*(*/ ')')
+                                       syntaxerr(NULL);
+
+                               goto Leave;
                        }
 #endif
 
@@ -348,7 +363,9 @@ get_command(int cf)
 
        case '(':
  Subshell:
+               ++subshell_nesting_level;
                t = nested(TPAREN, '(', ')');
+               --subshell_nesting_level;
                break;
 
        case '{': /*}*/
@@ -362,13 +379,13 @@ get_command(int cf)
                        CHAR, 't', EOS
                };
 
-               /* Leave KEYWORD in syniocf (allow if (( 1 )) then ...) */
+               /* leave KEYWORD in syniocf (allow if (( 1 )) then ...) */
                lno = source->line;
                ACCEPT;
                switch (token(LETEXPR)) {
                case LWORD:
                        break;
-               case '(':       /* ) */
+               case '(': /*)*/
                        goto Subshell;
                default:
                        syntaxerr(NULL);
@@ -381,7 +398,7 @@ get_command(int cf)
        }
 
        case DBRACKET: /* [[ .. ]] */
-               /* Leave KEYWORD in syniocf (allow if [[ -n 1 ]] then ...) */
+               /* leave KEYWORD in syniocf (allow if [[ -n 1 ]] then ...) */
                t = newtp(TDBRACKET);
                ACCEPT;
                {
@@ -403,8 +420,8 @@ get_command(int cf)
                t = newtp((c == FOR) ? TFOR : TSELECT);
                musthave(LWORD, ARRAYVAR);
                if (!is_wdvarname(yylval.cp, true))
-                       yyerror("%s: bad identifier\n",
-                           c == FOR ? "for" : "select");
+                       yyerror("%s: %s\n", c == FOR ? "for" : Tselect,
+                           "bad identifier");
                strdupx(t->str, ident, ATEMP);
                nesting_push(&old_nesting, c);
                t->vars = wordlist();
@@ -452,7 +469,8 @@ get_command(int cf)
                t = pipeline(0);
                if (t) {
                        t->str = alloc(2, ATEMP);
-                       t->str[0] = '\0';       /* TF_* flags */
+                       /* TF_* flags */
+                       t->str[0] = '\0';
                        t->str[1] = '\0';
                }
                t = block(TTIME, t, NOBLOCK, NOWORDS);
@@ -466,7 +484,7 @@ get_command(int cf)
 
        while ((iop = synio(syniocf)) != NULL) {
                if (iopn >= NUFILE)
-                       yyerror("too many redirections\n");
+                       yyerror("too many %ss\n", "redirection");
                iops[iopn++] = iop;
        }
 
@@ -475,7 +493,7 @@ get_command(int cf)
                t->ioact = NULL;
        } else {
                iops[iopn++] = NULL;
-               iops = aresize(iops, iopn * sizeof(struct ioword *), ATEMP);
+               iops = aresize2(iops, iopn, sizeof(struct ioword *), ATEMP);
                t->ioact = iops;
        }
 
@@ -499,7 +517,8 @@ dogroup(void)
        struct op *list;
 
        c = token(CONTIN|KEYWORD|ALIAS);
-       /* A {...} can be used instead of do...done for for/select loops
+       /*
+        * A {...} can be used instead of do...done for for/select loops
         * but not for while/until loops - we don't need to check if it
         * is a while loop because it would have been parsed as part of
         * the conditional command list...
@@ -567,7 +586,8 @@ caselist(void)
        else
                syntaxerr(NULL);
        t = tl = NULL;
-       while ((tpeek(CONTIN|KEYWORD|ESACONLY)) != c) { /* no ALIAS here */
+       /* no ALIAS here */
+       while ((tpeek(CONTIN|KEYWORD|ESACONLY)) != c) {
                struct op *tc = casepart(c);
                if (tl == NULL)
                        t = tl = tc, tl->right = NULL;
@@ -601,53 +621,62 @@ casepart(int endtok)
        t->left = c_list(true);
        /* Note: POSIX requires the ;; */
        if ((tpeek(CONTIN|KEYWORD|ALIAS)) != endtok)
-               musthave(BREAK, CONTIN|KEYWORD|ALIAS);
+               switch (symbol) {
+               default:
+                       syntaxerr(NULL);
+               case BREAK:
+               case BRKEV:
+               case BRKFT:
+                       t->u.charflag =
+                           (symbol == BRKEV) ? '|' :
+                           (symbol == BRKFT) ? '&' : ';';
+                       ACCEPT;
+               }
        return (t);
 }
 
 static struct op *
 function_body(char *name,
-    bool ksh_func)             /* function foo { ... } vs foo() { .. } */
+    /* function foo { ... } vs foo() { .. } */
+    bool ksh_func)
 {
        char *sname, *p;
        struct op *t;
        bool old_func_parse;
 
-       sname = wdstrip(name, false, false);
-       /* Check for valid characters in name. POSIX and AT&T ksh93 say only
-        * allow [a-zA-Z_0-9] but this allows more as old pdkshs have
-        * allowed more (the following were never allowed:
+       sname = wdstrip(name, 0);
+       /*-
+        * Check for valid characters in name. POSIX and AT&T ksh93 say
+        * only allow [a-zA-Z_0-9] but this allows more as old pdkshs
+        * have allowed more; the following were never allowed:
         *      NUL TAB NL SP " $ & ' ( ) ; < = > \ ` |
         * C_QUOTE covers all but adds # * ? [ ]
         */
        for (p = sname; *p; p++)
                if (ctype(*p, C_QUOTE))
-                       yyerror("%s: invalid function name\n", sname);
+                       yyerror("%s: %s\n", sname, "invalid function name");
 
-       /* Note that POSIX allows only compound statements after foo(), sh and
-        * AT&T ksh allow any command, go with the later since it shouldn't
-        * break anything. However, for function foo, AT&T ksh only accepts
-        * an open-brace.
+       /*
+        * Note that POSIX allows only compound statements after foo(),
+        * sh and AT&T ksh allow any command, go with the later since it
+        * shouldn't break anything. However, for function foo, AT&T ksh
+        * only accepts an open-brace.
         */
        if (ksh_func) {
-               if (tpeek(CONTIN|KEYWORD|ALIAS) == '(' /* ) */) {
-                       struct tbl *tp;
-
+               if (tpeek(CONTIN|KEYWORD|ALIAS) == '(' /*)*/) {
                        /* function foo () { */
                        ACCEPT;
                        musthave(')', 0);
                        /* degrade to POSIX function */
                        ksh_func = false;
-                       if ((tp = ktsearch(&aliases, sname, hash(sname))))
-                               ktdelete(tp);
                }
-               musthave('{', CONTIN|KEYWORD|ALIAS); /* } */
+               musthave('{' /*}*/, CONTIN|KEYWORD|ALIAS);
                REJECT;
        }
 
        t = newtp(TFUNCT);
        t->str = sname;
-       t->u.ksh_func = ksh_func;
+       t->u.ksh_func = tobool(ksh_func);
        t->lineno = source->line;
 
        old_func_parse = e->flags & EF_FUNC_PARSE;
@@ -655,12 +684,13 @@ function_body(char *name,
        if ((t->left = get_command(CONTIN)) == NULL) {
                char *tv;
                /*
-                * Probably something like foo() followed by eof or ;.
+                * Probably something like foo() followed by EOF or ';'.
                 * This is accepted by sh and ksh88.
                 * To make "typeset -f foo" work reliably (so its output can
                 * be used as input), we pretend there is a colon here.
                 */
                t->left = newtp(TCOM);
+               /* (2 * sizeof(char *)) is small enough */
                t->left->args = alloc(2 * sizeof(char *), ATEMP);
                t->left->args[0] = tv = alloc(3, ATEMP);
                tv[0] = CHAR;
@@ -686,7 +716,8 @@ wordlist(void)
        XPinit(args, 16);
        /* POSIX does not do alias expansion here... */
        if ((c = token(CONTIN|KEYWORD|ALIAS)) != IN) {
-               if (c != ';') /* non-POSIX, but AT&T ksh accepts a ; here */
+               if (c != ';')
+                       /* non-POSIX, but AT&T ksh accepts a ; here */
                        REJECT;
                return (NULL);
        }
@@ -733,13 +764,13 @@ const struct tokeninfo {
        { "case",       CASE,   true },
        { "esac",       ESAC,   true },
        { "for",        FOR,    true },
-       { "select",     SELECT, true },
+       { Tselect,      SELECT, true },
        { "while",      WHILE,  true },
        { "until",      UNTIL,  true },
        { "do",         DO,     true },
        { "done",       DONE,   true },
        { "in",         IN,     true },
-       { "function",   FUNCTION, true },
+       { Tfunction,    FUNCTION, true },
        { "time",       TIME,   true },
        { "{",          '{',    true },
        { "}",          '}',    true },
@@ -749,6 +780,8 @@ const struct tokeninfo {
        { "&&",         LOGAND, false },
        { "||",         LOGOR,  false },
        { ";;",         BREAK,  false },
+       { ";|",         BRKEV,  false },
+       { ";&",         BRKFT,  false },
        { "((",         MDPAREN, false },
        { "|&",         COPROC, false },
        /* and some special cases... */
@@ -762,8 +795,9 @@ initkeywords(void)
        struct tokeninfo const *tt;
        struct tbl *p;
 
-       ktinit(&keywords, APERM,
-           /* must be 80% of 2^n (currently 20 keywords) */ 32);
+       ktinit(APERM, &keywords,
+           /* currently 28 keywords -> 80% of 64 (2^6) */
+           6);
        for (tt = tokentab; tt->name; tt++) {
                if (tt->reserved) {
                        p = ktenter(&keywords, tt->name, hash(tt->name));
@@ -777,7 +811,8 @@ initkeywords(void)
 static void
 syntaxerr(const char *what)
 {
-       char redir[6];  /* 2<<- is the longest redirection, I think */
+       /* 2<<- is the longest redirection, I think */
+       char redir[6];
        const char *s;
        struct tokeninfo const *tt;
        int c;
@@ -796,7 +831,7 @@ syntaxerr(const char *what)
                        goto Again;
                }
                /* don't quote the EOF */
-               yyerror("%s: unexpected EOF\n", T_synerr);
+               yyerror("%s: %s %s\n", Tsynerr, "unexpected", "EOF");
                /* NOTREACHED */
 
        case LWORD:
@@ -823,7 +858,7 @@ syntaxerr(const char *what)
                        s = redir;
                }
        }
-       yyerror("%s: '%s' %s\n", T_synerr, s, what);
+       yyerror("%s: '%s' %s\n", Tsynerr, s, what);
 }
 
 static void
@@ -857,17 +892,20 @@ newtp(int type)
 }
 
 struct op *
-compile(Source *s)
+compile(Source *s, bool skiputf8bom)
 {
        nesting.start_token = 0;
        nesting.start_line = 0;
        herep = heres;
        source = s;
+       if (skiputf8bom)
+               yyskiputf8bom();
        yyparse();
        return (outtree);
 }
 
-/* This kludge exists to take care of sh/AT&T ksh oddity in which
+/*-
+ * This kludge exists to take care of sh/AT&T ksh oddity in which
  * the arguments of alias/export/readonly/typeset have no field
  * splitting, file globbing, or (normal) tilde expansion done.
  * AT&T ksh seems to do something similar to this since
@@ -882,10 +920,10 @@ assign_command(char *s)
 {
        if (!*s)
                return (0);
-       return ((strcmp(s, "alias") == 0) ||
+       return ((strcmp(s, Talias) == 0) ||
            (strcmp(s, "export") == 0) ||
            (strcmp(s, "readonly") == 0) ||
-           (strcmp(s, T_typeset) == 0));
+           (strcmp(s, Ttypeset) == 0));
 }
 
 /* Check if we are in the middle of reading an alias */
@@ -899,7 +937,8 @@ inalias(struct source *s)
 }
 
 
-/* Order important - indexed by Test_meta values
+/*
+ * Order important - indexed by Test_meta values
  * Note that ||, &&, ( and ) can't appear in as unquoted strings
  * in normal shell input, so these can be interpreted unambiguously
  * in the evaluation pass.
@@ -952,7 +991,8 @@ dbtestp_isa(Test_env *te, Test_meta meta)
                            db_lthan : db_gthan, ATEMP);
                } else if (uqword && (ret = test_isop(meta, ident)))
                        save = yylval.cp;
-       } else /* meta == TM_END */
+       } else
+               /* meta == TM_END */
                ret = (uqword && !strcmp(yylval.cp,
                    db_close)) ? TO_NONNULL : TO_NONOP;
        if (ret != TO_NONOP) {
@@ -1002,3 +1042,96 @@ dbtestp_error(Test_env *te, int offset, const char *msg)
        }
        syntaxerr(msg);
 }
+
+#if HAVE_SELECT
+
+#ifndef EOVERFLOW
+#ifdef ERANGE
+#define EOVERFLOW      ERANGE
+#else
+#define EOVERFLOW      EINVAL
+#endif
+#endif
+
+bool
+parse_usec(const char *s, struct timeval *tv)
+{
+       struct timeval tt;
+       int i;
+
+       tv->tv_sec = 0;
+       /* parse integral part */
+       while (ksh_isdigit(*s)) {
+               tt.tv_sec = tv->tv_sec * 10 + (*s++ - '0');
+               if (tt.tv_sec / 10 != tv->tv_sec) {
+                       errno = EOVERFLOW;
+                       return (true);
+               }
+               tv->tv_sec = tt.tv_sec;
+       }
+
+       tv->tv_usec = 0;
+       if (!*s)
+               /* no decimal fraction */
+               return (false);
+       else if (*s++ != '.') {
+               /* junk after integral part */
+               errno = EINVAL;
+               return (true);
+       }
+
+       /* parse decimal fraction */
+       i = 100000;
+       while (ksh_isdigit(*s)) {
+               tv->tv_usec += i * (*s++ - '0');
+               if (i == 1)
+                       break;
+               i /= 10;
+       }
+       /* check for junk after fractional part */
+       while (ksh_isdigit(*s))
+               ++s;
+       if (*s) {
+               errno = EINVAL;
+               return (true);
+       }
+
+       /* end of input string reached, no errors */
+       return (false);
+}
+#endif
+
+/*
+ * Helper function called from within lex.c:yylex() to parse
+ * a COMSUB recursively using the main shell parser and lexer
+ */
+char *
+yyrecursive(void)
+{
+       struct op *t;
+       char *cp;
+       bool old_reject;
+       int old_symbol;
+       struct ioword **old_herep;
+
+       /* tell the lexer to accept a closing parenthesis as EOD */
+       ++subshell_nesting_level;
+
+       /* push reject state, parse recursively, pop reject state */
+       old_reject = reject;
+       old_symbol = symbol;
+       ACCEPT;
+       old_herep = herep;
+       /* we use TPAREN as a helper container here */
+       t = nested(TPAREN, '(', ')');
+       herep = old_herep;
+       reject = old_reject;
+       symbol = old_symbol;
+
+       /* t->left because nested(TPAREN, ...) hides our goodies there */
+       cp = snptreef(NULL, 0, "%T", t->left);
+       tfree(t, ATEMP);
+
+       --subshell_nesting_level;
+       return (cp);
+}
index aa861db..9ade37b 100644 (file)
@@ -1,7 +1,7 @@
 /*     $OpenBSD: tree.c,v 1.19 2008/08/11 21:50:35 jaredy Exp $        */
 
 /*-
- * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
  *     Thorsten Glaser <tg@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/tree.c,v 1.30 2010/02/25 20:18:19 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/tree.c,v 1.51 2011/09/07 15:24:21 tg Exp $");
 
-#define INDENT 4
+#define INDENT 8
 
-#define tputc(c, shf) shf_putchar(c, shf);
 static void ptree(struct op *, int, struct shf *);
 static void pioact(struct shf *, int, struct ioword *);
-static void tputC(int, struct shf *);
-static void tputS(char *, struct shf *);
+static const char *wdvarput(struct shf *, const char *, int, int);
 static void vfptreef(struct shf *, int, const char *, va_list);
 static struct ioword **iocopy(struct ioword **, Area *);
 static void iofree(struct ioword **, Area *);
 
+/* "foo& ; bar" and "foo |& ; bar" are invalid */
+static bool prevent_semicolon;
+
 /*
  * print a command tree
  */
@@ -44,22 +45,26 @@ ptree(struct op *t, int indent, struct shf *shf)
        const char **w;
        struct ioword **ioact;
        struct op *t1;
+       int i;
 
  Chain:
        if (t == NULL)
                return;
        switch (t->type) {
        case TCOM:
-               if (t->vars)
-                       for (w = (const char **)t->vars; *w != NULL; )
+               if (t->vars) {
+                       w = (const char **)t->vars;
+                       while (*w)
                                fptreef(shf, indent, "%S ", *w++);
-               else
+               else
                        shf_puts("#no-vars# ", shf);
-               if (t->args)
-                       for (w = t->args; *w != NULL; )
+               if (t->args) {
+                       w = t->args;
+                       while (*w)
                                fptreef(shf, indent, "%S ", *w++);
-               else
+               else
                        shf_puts("#no-args# ", shf);
+               prevent_semicolon = false;
                break;
        case TEXEC:
                t = t->left;
@@ -78,30 +83,28 @@ ptree(struct op *t, int indent, struct shf *shf)
        case TOR:
        case TAND:
                fptreef(shf, indent, "%T%s %T",
-                   t->left, (t->type==TOR) ? "||" : "&&", t->right);
+                   t->left, (t->type == TOR) ? "||" : "&&", t->right);
                break;
        case TBANG:
                shf_puts("! ", shf);
+               prevent_semicolon = false;
                t = t->right;
                goto Chain;
-       case TDBRACKET: {
-               int i;
-
+       case TDBRACKET:
+               w = t->args;
                shf_puts("[[", shf);
-               for (i = 0; t->args[i]; i++)
-                       fptreef(shf, indent, " %S", t->args[i]);
+               while (*w)
+                       fptreef(shf, indent, " %S", *w++);
                shf_puts(" ]] ", shf);
                break;
-       }
        case TSELECT:
-               fptreef(shf, indent, "select %s ", t->str);
-               /* FALLTHROUGH */
        case TFOR:
-               if (t->type == TFOR)
-                       fptreef(shf, indent, "for %s ", t->str);
+               fptreef(shf, indent, "%s %s ",
+                   (t->type == TFOR) ? "for" : Tselect, t->str);
                if (t->vars != NULL) {
                        shf_puts("in ", shf);
-                       for (w = (const char **)t->vars; *w; )
+                       w = (const char **)t->vars;
+                       while (*w)
                                fptreef(shf, indent, "%S ", *w++);
                        fptreef(shf, indent, "%;");
                }
@@ -112,34 +115,43 @@ ptree(struct op *t, int indent, struct shf *shf)
                fptreef(shf, indent, "case %S in", t->str);
                for (t1 = t->left; t1 != NULL; t1 = t1->right) {
                        fptreef(shf, indent, "%N(");
-                       for (w = (const char **)t1->vars; *w != NULL; w++)
+                       w = (const char **)t1->vars;
+                       while (*w) {
                                fptreef(shf, indent, "%S%c", *w,
                                    (w[1] != NULL) ? '|' : ')');
-                       fptreef(shf, indent + INDENT, "%;%T%N;;", t1->left);
+                               ++w;
+                       }
+                       fptreef(shf, indent + INDENT, "%N%T%N;%c", t1->left,
+                           t1->u.charflag);
                }
                fptreef(shf, indent, "%Nesac ");
                break;
-       case TIF:
+#ifndef MKSH_NO_DEPRECATED_WARNING
        case TELIF:
-               /* 3 == strlen("if ") */
-               fptreef(shf, indent + 3, "if %T", t->left);
-               for (;;) {
+               internal_errorf("TELIF in tree.c:ptree() unexpected");
+               /* FALLTHROUGH */
+#endif
+       case TIF:
+               i = 2;
+               goto process_TIF;
+               do {
+                       t = t->right;
+                       i = 0;
+                       fptreef(shf, indent, "%;");
+ process_TIF:
+                       /* 5 == strlen("elif ") */
+                       fptreef(shf, indent + 5 - i, "elif %T" + i, t->left);
                        t = t->right;
                        if (t->left != NULL) {
                                fptreef(shf, indent, "%;");
-                               fptreef(shf, indent + INDENT, "then%N%T",
-                                   t->left);
+                               fptreef(shf, indent + INDENT, "%s%N%T",
+                                   "then", t->left);
                        }
-                       if (t->right == NULL || t->right->type != TELIF)
-                               break;
-                       t = t->right;
-                       fptreef(shf, indent, "%;");
-                       /* 5 == strlen("elif ") */
-                       fptreef(shf, indent + 5, "elif %T", t->left);
-               }
+               } while (t->right && t->right->type == TELIF);
                if (t->right != NULL) {
                        fptreef(shf, indent, "%;");
-                       fptreef(shf, indent + INDENT, "else%;%T", t->right);
+                       fptreef(shf, indent + INDENT, "%s%N%T",
+                           "else", t->right);
                }
                fptreef(shf, indent, "%;fi ");
                break;
@@ -147,60 +159,63 @@ ptree(struct op *t, int indent, struct shf *shf)
        case TUNTIL:
                /* 6 == strlen("while"/"until") */
                fptreef(shf, indent + 6, "%s %T",
-                   (t->type==TWHILE) ? "while" : "until",
+                   (t->type == TWHILE) ? "while" : "until",
                    t->left);
-               fptreef(shf, indent, "%;do");
-               fptreef(shf, indent + INDENT, "%;%T", t->right);
+               fptreef(shf, indent, "%;");
+               fptreef(shf, indent + INDENT, "do%N%T", t->right);
                fptreef(shf, indent, "%;done ");
                break;
        case TBRACE:
-               fptreef(shf, indent + INDENT, "{%;%T", t->left);
+               fptreef(shf, indent + INDENT, "{%N%T", t->left);
                fptreef(shf, indent, "%;} ");
                break;
        case TCOPROC:
                fptreef(shf, indent, "%T|& ", t->left);
+               prevent_semicolon = true;
                break;
        case TASYNC:
                fptreef(shf, indent, "%T& ", t->left);
+               prevent_semicolon = true;
                break;
        case TFUNCT:
-               fptreef(shf, indent,
-                   t->u.ksh_func ? "function %s %T" : "%s() %T",
-                   t->str, t->left);
+               fpFUNCTf(shf, indent, tobool(t->u.ksh_func), t->str, t->left);
                break;
        case TTIME:
-               fptreef(shf, indent, "time %T", t->left);
+               fptreef(shf, indent, "%s %T", "time", t->left);
                break;
        default:
                shf_puts("<botch>", shf);
+               prevent_semicolon = false;
                break;
        }
        if ((ioact = t->ioact) != NULL) {
-               int     need_nl = 0;
+               bool need_nl = false;
 
                while (*ioact != NULL)
                        pioact(shf, indent, *ioact++);
                /* Print here documents after everything else... */
-               for (ioact = t->ioact; *ioact != NULL; ) {
+               ioact = t->ioact;
+               while (*ioact != NULL) {
                        struct ioword *iop = *ioact++;
 
-                       /* heredoc is 0 when tracing (set -x) */
-                       if ((iop->flag & IOTYPE) == IOHERE && iop->heredoc &&
-                           /* iop->delim[1] == '<' means here string */
-                           (!iop->delim || iop->delim[1] != '<')) {
-                               tputc('\n', shf);
+                       /* heredoc is NULL when tracing (set -x) */
+                       if ((iop->flag & (IOTYPE | IOHERESTR)) == IOHERE &&
+                           iop->heredoc) {
+                               shf_putc('\n', shf);
                                shf_puts(iop->heredoc, shf);
                                fptreef(shf, indent, "%s",
+                                   iop->flag & IONDELIM ? "<<" :
                                    evalstr(iop->delim, 0));
-                               need_nl = 1;
+                               need_nl = true;
                        }
                }
-               /* Last delimiter must be followed by a newline (this often
-                * leads to an extra blank line, but its not worth worrying
-                * about)
+               /*
+                * Last delimiter must be followed by a newline (this
+                * often leads to an extra blank line, but it's not
+                * worth worrying about)
                 */
                if (need_nl)
-                       tputc('\n', shf);
+                       shf_putc('\n', shf);
        }
 }
 
@@ -220,123 +235,136 @@ pioact(struct shf *shf, int indent, struct ioword *iop)
 
        switch (type) {
        case IOREAD:
-               shf_puts("< ", shf);
+               shf_puts("<", shf);
                break;
        case IOHERE:
                shf_puts(flag & IOSKIP ? "<<-" : "<<", shf);
                break;
        case IOCAT:
-               shf_puts(">> ", shf);
+               shf_puts(">>", shf);
                break;
        case IOWRITE:
-               shf_puts(flag & IOCLOB ? ">| " : "> ", shf);
+               shf_puts(flag & IOCLOB ? ">|" : ">", shf);
                break;
        case IORDWR:
-               shf_puts("<> ", shf);
+               shf_puts("<>", shf);
                break;
        case IODUP:
                shf_puts(flag & IORDUP ? "<&" : ">&", shf);
                break;
        }
-       /* name/delim are 0 when printing syntax errors */
+       /* name/delim are NULL when printing syntax errors */
        if (type == IOHERE) {
                if (iop->delim)
-                       fptreef(shf, indent, "%s%S ",
-                           /* here string */ iop->delim[1] == '<' ? "" : " ",
-                           iop->delim);
+                       fptreef(shf, indent, "%S ", iop->delim);
                else
-                       tputc(' ', shf);
+                       shf_putc(' ', shf);
        } else if (iop->name)
                fptreef(shf, indent, (iop->flag & IONAMEXP) ? "%s " : "%S ",
                    iop->name);
+       prevent_semicolon = false;
 }
 
-
-/*
- * variants of fputc, fputs for ptreef and snptreef
- */
-static void
-tputC(int c, struct shf *shf)
-{
-       if ((c&0x60) == 0) {            /* C0|C1 */
-               tputc((c&0x80) ? '$' : '^', shf);
-               tputc(((c&0x7F)|0x40), shf);
-       } else if ((c&0x7F) == 0x7F) {  /* DEL */
-               tputc((c&0x80) ? '$' : '^', shf);
-               tputc('?', shf);
-       } else
-               tputc(c, shf);
-}
-
-static void
-tputS(char *wp, struct shf *shf)
+/* variant of fputs for ptreef and wdstrip */
+static const char *
+wdvarput(struct shf *shf, const char *wp, int quotelevel, int opmode)
 {
-       int c, quotelevel = 0;
+       int c;
 
-       /* problems:
+       /*-
+        * problems:
         *      `...` -> $(...)
         *      'foo' -> "foo"
+        *      x${foo:-"hi"} -> x${foo:-hi} unless WDS_TPUTS
+        *      x${foo:-'hi'} -> x${foo:-hi} unless WDS_KEEPQ
         * could change encoding to:
         *      OQUOTE ["'] ... CQUOTE ["']
         *      COMSUB [(`] ...\0       (handle $ ` \ and maybe " in `...` case)
         */
-       while (1)
+       while (/* CONSTCOND */ 1)
                switch (*wp++) {
                case EOS:
-                       return;
+                       return (--wp);
                case ADELIM:
                case CHAR:
-                       tputC(*wp++, shf);
+                       c = *wp++;
+                       if ((opmode & WDS_MAGIC) &&
+                           (ISMAGIC(c) || c == '[' || c == NOT ||
+                           c == '-' || c == ']' || c == '*' || c == '?'))
+                               shf_putc(MAGIC, shf);
+                       shf_putc(c, shf);
                        break;
-               case QCHAR:
+               case QCHAR: {
+                       bool doq;
+
                        c = *wp++;
-                       if (!quotelevel || (c == '"' || c == '`' || c == '$'))
-                               tputc('\\', shf);
-                       tputC(c, shf);
+                       doq = (c == '"' || c == '`' || c == '$' || c == '\\');
+                       if (opmode & WDS_TPUTS) {
+                               if (quotelevel == 0)
+                                       doq = true;
+                       } else {
+                               if (!(opmode & WDS_KEEPQ))
+                                       doq = false;
+                       }
+                       if (doq)
+                               shf_putc('\\', shf);
+                       shf_putc(c, shf);
                        break;
+               }
                case COMSUB:
                        shf_puts("$(", shf);
-                       while (*wp != 0)
-                               tputC(*wp++, shf);
-                       tputc(')', shf);
-                       wp++;
+                       while ((c = *wp++) != 0)
+                               shf_putc(c, shf);
+                       shf_putc(')', shf);
                        break;
                case EXPRSUB:
                        shf_puts("$((", shf);
-                       while (*wp != 0)
-                               tputC(*wp++, shf);
+                       while ((c = *wp++) != 0)
+                               shf_putc(c, shf);
                        shf_puts("))", shf);
-                       wp++;
                        break;
                case OQUOTE:
-                       quotelevel++;
-                       tputc('"', shf);
+                       if (opmode & WDS_TPUTS) {
+                               quotelevel++;
+                               shf_putc('"', shf);
+                       }
                        break;
                case CQUOTE:
-                       if (quotelevel)
-                               quotelevel--;
-                       tputc('"', shf);
+                       if (opmode & WDS_TPUTS) {
+                               if (quotelevel)
+                                       quotelevel--;
+                               shf_putc('"', shf);
+                       }
                        break;
                case OSUBST:
-                       tputc('$', shf);
+                       shf_putc('$', shf);
                        if (*wp++ == '{')
-                               tputc('{', shf);
+                               shf_putc('{', shf);
                        while ((c = *wp++) != 0)
-                               tputC(c, shf);
+                               shf_putc(c, shf);
+                       wp = wdvarput(shf, wp, 0, opmode);
                        break;
                case CSUBST:
                        if (*wp++ == '}')
-                               tputc('}', shf);
-                       break;
+                               shf_putc('}', shf);
+                       return (wp);
                case OPAT:
-                       tputc(*wp++, shf);
-                       tputc('(', shf);
+                       if (opmode & WDS_MAGIC) {
+                               shf_putc(MAGIC, shf);
+                               shf_putchar(*wp++ | 0x80, shf);
+                       } else {
+                               shf_putchar(*wp++, shf);
+                               shf_putc('(', shf);
+                       }
                        break;
                case SPAT:
-                       tputc('|', shf);
-                       break;
+                       c = '|';
+                       if (0)
                case CPAT:
-                       tputc(')', shf);
+                               c = /*(*/ ')';
+                       if (opmode & WDS_MAGIC)
+                               shf_putc(MAGIC, shf);
+                       shf_putc(c, shf);
                        break;
                }
 }
@@ -346,21 +374,19 @@ tputS(char *wp, struct shf *shf)
  * variable args with an ANSI compiler
  */
 /* VARARGS */
-int
+void
 fptreef(struct shf *shf, int indent, const char *fmt, ...)
 {
        va_list va;
 
        va_start(va, fmt);
-
        vfptreef(shf, indent, fmt, va);
        va_end(va);
-       return (0);
 }
 
 /* VARARGS */
 char *
-snptreef(char *s, int n, const char *fmt, ...)
+snptreef(char *s, ssize_t n, const char *fmt, ...)
 {
        va_list va;
        struct shf shf;
@@ -371,7 +397,8 @@ snptreef(char *s, int n, const char *fmt, ...)
        vfptreef(&shf, 0, fmt, va);
        va_end(va);
 
-       return (shf_sclose(&shf)); /* null terminates */
+       /* shf_sclose NUL terminates */
+       return (shf_sclose(&shf));
 }
 
 static void
@@ -383,48 +410,63 @@ vfptreef(struct shf *shf, int indent, const char *fmt, va_list va)
                if (c == '%') {
                        switch ((c = *fmt++)) {
                        case 'c':
-                               tputc(va_arg(va, int), shf);
+                               /* character (octet, probably) */
+                               shf_putchar(va_arg(va, int), shf);
                                break;
                        case 's':
+                               /* string */
                                shf_puts(va_arg(va, char *), shf);
                                break;
-                       case 'S':       /* word */
-                               tputS(va_arg(va, char *), shf);
+                       case 'S':
+                               /* word */
+                               wdvarput(shf, va_arg(va, char *), 0, WDS_TPUTS);
                                break;
-                       case 'd':       /* decimal */
+                       case 'd':
+                               /* signed decimal */
                                shf_fprintf(shf, "%d", va_arg(va, int));
                                break;
-                       case 'u':       /* decimal */
+                       case 'u':
+                               /* unsigned decimal */
                                shf_fprintf(shf, "%u", va_arg(va, unsigned int));
                                break;
-                       case 'T':       /* format tree */
+                       case 'T':
+                               /* format tree */
                                ptree(va_arg(va, struct op *), indent, shf);
-                               break;
-                       case ';':       /* newline or ; */
-                       case 'N':       /* newline or space */
+                               goto dont_trash_prevent_semicolon;
+                       case ';':
+                               /* newline or ; */
+                       case 'N':
+                               /* newline or space */
                                if (shf->flags & SHF_STRING) {
-                                       if (c == ';')
-                                               tputc(';', shf);
-                                       tputc(' ', shf);
+                                       if (c == ';' && !prevent_semicolon)
+                                               shf_putc(';', shf);
+                                       shf_putc(' ', shf);
                                } else {
                                        int i;
 
-                                       tputc('\n', shf);
-                                       for (i = indent; i >= 8; i -= 8)
-                                               tputc('\t', shf);
-                                       for (; i > 0; --i)
-                                               tputc(' ', shf);
+                                       shf_putc('\n', shf);
+                                       i = indent;
+                                       while (i >= 8) {
+                                               shf_putc('\t', shf);
+                                               i -= 8;
+                                       }
+                                       while (i--)
+                                               shf_putc(' ', shf);
                                }
                                break;
                        case 'R':
+                               /* I/O redirection */
                                pioact(shf, indent, va_arg(va, struct ioword *));
                                break;
                        default:
-                               tputc(c, shf);
+                               shf_putc(c, shf);
                                break;
                        }
                } else
-                       tputc(c, shf);
+                       shf_putc(c, shf);
+               prevent_semicolon = false;
+ dont_trash_prevent_semicolon:
+               ;
        }
 }
 
@@ -454,11 +496,13 @@ tcopy(struct op *t, Area *ap)
        if (t->vars == NULL)
                r->vars = NULL;
        else {
-               for (tw = (const char **)t->vars; *tw++ != NULL; )
-                       ;
-               rw = r->vars = alloc((tw - (const char **)t->vars + 1) *
+               tw = (const char **)t->vars;
+               while (*tw)
+                       ++tw;
+               rw = r->vars = alloc2(tw - (const char **)t->vars + 1,
                    sizeof(*tw), ap);
-               for (tw = (const char **)t->vars; *tw != NULL; )
+               tw = (const char **)t->vars;
+               while (*tw)
                        *rw++ = wdcopy(*tw++, ap);
                *rw = NULL;
        }
@@ -466,11 +510,13 @@ tcopy(struct op *t, Area *ap)
        if (t->args == NULL)
                r->args = NULL;
        else {
-               for (tw = t->args; *tw++ != NULL; )
-                       ;
-               r->args = (const char **)(rw = alloc((tw - t->args + 1) *
+               tw = t->args;
+               while (*tw)
+                       ++tw;
+               r->args = (const char **)(rw = alloc2(tw - t->args + 1,
                    sizeof(*tw), ap));
-               for (tw = t->args; *tw != NULL; )
+               tw = t->args;
+               while (*tw)
                        *rw++ = wdcopy(*tw++, ap);
                *rw = NULL;
        }
@@ -487,7 +533,9 @@ tcopy(struct op *t, Area *ap)
 char *
 wdcopy(const char *wp, Area *ap)
 {
-       size_t len = wdscan(wp, EOS) - wp;
+       size_t len;
+
+       len = wdscan(wp, EOS) - wp;
        return (memcpy(alloc(len, ap), wp, len));
 }
 
@@ -497,7 +545,7 @@ wdscan(const char *wp, int c)
 {
        int nest = 0;
 
-       while (1)
+       while (/* CONSTCOND */ 1)
                switch (*wp++) {
                case EOS:
                        return (wp);
@@ -546,88 +594,19 @@ wdscan(const char *wp, int c)
                }
 }
 
-/* return a copy of wp without any of the mark up characters and
- * with quote characters (" ' \) stripped.
- * (string is allocated from ATEMP)
+/*
+ * return a copy of wp without any of the mark up characters and with
+ * quote characters (" ' \) stripped. (string is allocated from ATEMP)
  */
 char *
-wdstrip(const char *wp, bool keepq, bool make_magic)
+wdstrip(const char *wp, int opmode)
 {
        struct shf shf;
-       int c;
 
        shf_sopen(NULL, 32, SHF_WR | SHF_DYNAMIC, &shf);
-
-       /* problems:
-        *      `...` -> $(...)
-        *      x${foo:-"hi"} -> x${foo:-hi}
-        *      x${foo:-'hi'} -> x${foo:-hi} unless keepq
-        */
-       while (1)
-               switch (*wp++) {
-               case EOS:
-                       return (shf_sclose(&shf)); /* null terminates */
-               case ADELIM:
-               case CHAR:
-                       c = *wp++;
-                       if (make_magic && (ISMAGIC(c) || c == '[' || c == NOT ||
-                           c == '-' || c == ']' || c == '*' || c == '?'))
-                               shf_putchar(MAGIC, &shf);
-                       shf_putchar(c, &shf);
-                       break;
-               case QCHAR:
-                       c = *wp++;
-                       if (keepq && (c == '"' || c == '`' || c == '$' || c == '\\'))
-                               shf_putchar('\\', &shf);
-                       shf_putchar(c, &shf);
-                       break;
-               case COMSUB:
-                       shf_puts("$(", &shf);
-                       while (*wp != 0)
-                               shf_putchar(*wp++, &shf);
-                       shf_putchar(')', &shf);
-                       break;
-               case EXPRSUB:
-                       shf_puts("$((", &shf);
-                       while (*wp != 0)
-                               shf_putchar(*wp++, &shf);
-                       shf_puts("))", &shf);
-                       break;
-               case OQUOTE:
-                       break;
-               case CQUOTE:
-                       break;
-               case OSUBST:
-                       shf_putchar('$', &shf);
-                       if (*wp++ == '{')
-                           shf_putchar('{', &shf);
-                       while ((c = *wp++) != 0)
-                               shf_putchar(c, &shf);
-                       break;
-               case CSUBST:
-                       if (*wp++ == '}')
-                               shf_putchar('}', &shf);
-                       break;
-               case OPAT:
-                       if (make_magic) {
-                               shf_putchar(MAGIC, &shf);
-                               shf_putchar(*wp++ | 0x80, &shf);
-                       } else {
-                               shf_putchar(*wp++, &shf);
-                               shf_putchar('(', &shf);
-                       }
-                       break;
-               case SPAT:
-                       if (make_magic)
-                               shf_putchar(MAGIC, &shf);
-                       shf_putchar('|', &shf);
-                       break;
-               case CPAT:
-                       if (make_magic)
-                               shf_putchar(MAGIC, &shf);
-                       shf_putchar(')', &shf);
-                       break;
-               }
+       wdvarput(&shf, wp, 0, opmode);
+       /* shf_sclose NUL terminates */
+       return (shf_sclose(&shf));
 }
 
 static struct ioword **
@@ -636,9 +615,10 @@ iocopy(struct ioword **iow, Area *ap)
        struct ioword **ior;
        int i;
 
-       for (ior = iow; *ior++ != NULL; )
-               ;
-       ior = alloc((ior - iow + 1) * sizeof(struct ioword *), ap);
+       ior = iow;
+       while (*ior)
+               ++ior;
+       ior = alloc2(ior - iow + 1, sizeof(struct ioword *), ap);
 
        for (i = 0; iow[i] != NULL; i++) {
                struct ioword *p, *q;
@@ -680,8 +660,9 @@ tfree(struct op *t, Area *ap)
        }
 
        if (t->args != NULL) {
+               /*XXX we assume the caller is right */
                union mksh_ccphack cw;
-               /* XXX we assume the caller is right */
+
                cw.ro = t->args;
                for (w = cw.rw; *w != NULL; w++)
                        afree(*w, ap);
@@ -703,7 +684,8 @@ iofree(struct ioword **iow, Area *ap)
        struct ioword **iop;
        struct ioword *p;
 
-       for (iop = iow; (p = *iop++) != NULL; ) {
+       iop = iow;
+       while ((p = *iop++) != NULL) {
                if (p->name != NULL)
                        afree(p->name, ap);
                if (p->delim != NULL)
@@ -714,3 +696,315 @@ iofree(struct ioword **iow, Area *ap)
        }
        afree(iow, ap);
 }
+
+void
+fpFUNCTf(struct shf *shf, int i, bool isksh, const char *k, struct op *v)
+{
+       if (isksh)
+               fptreef(shf, i, "%s %s %T", Tfunction, k, v);
+       else
+               fptreef(shf, i, "%s() %T", k, v);
+}
+
+
+/* for jobs.c */
+void
+vistree(char *dst, size_t sz, struct op *t)
+{
+       int c;
+       char *cp, *buf;
+
+       buf = alloc(sz, ATEMP);
+       snptreef(buf, sz, "%T", t);
+       cp = buf;
+       while ((c = *cp++)) {
+               if (((c & 0x60) == 0) || ((c & 0x7F) == 0x7F)) {
+                       /* C0 or C1 control character or DEL */
+                       if (!--sz)
+                               break;
+                       *dst++ = (c & 0x80) ? '$' : '^';
+                       c = (c & 0x7F) ^ 0x40;
+               }
+               if (!--sz)
+                       break;
+               *dst++ = c;
+       }
+       *dst = '\0';
+       afree(buf, ATEMP);
+}
+
+#ifdef DEBUG
+void
+dumpchar(struct shf *shf, int c)
+{
+       if (((c & 0x60) == 0) || ((c & 0x7F) == 0x7F)) {
+               /* C0 or C1 control character or DEL */
+               shf_putc((c & 0x80) ? '$' : '^', shf);
+               c = (c & 0x7F) ^ 0x40;
+       }
+       shf_putc(c, shf);
+}
+
+/* see: wdvarput */
+static const char *
+dumpwdvar_(struct shf *shf, const char *wp, int quotelevel)
+{
+       int c;
+
+       while (/* CONSTCOND */ 1) {
+               switch(*wp++) {
+               case EOS:
+                       shf_puts("EOS", shf);
+                       return (--wp);
+               case ADELIM:
+                       shf_puts("ADELIM=", shf);
+                       if (0)
+               case CHAR:
+                               shf_puts("CHAR=", shf);
+                       dumpchar(shf, *wp++);
+                       break;
+               case QCHAR:
+                       shf_puts("QCHAR<", shf);
+                       c = *wp++;
+                       if (quotelevel == 0 ||
+                           (c == '"' || c == '`' || c == '$' || c == '\\'))
+                               shf_putc('\\', shf);
+                       dumpchar(shf, c);
+                       goto closeandout;
+               case COMSUB:
+                       shf_puts("COMSUB<", shf);
+ dumpsub:
+                       while ((c = *wp++) != 0)
+                               dumpchar(shf, c);
+ closeandout:
+                       shf_putc('>', shf);
+                       break;
+               case EXPRSUB:
+                       shf_puts("EXPRSUB<", shf);
+                       goto dumpsub;
+               case OQUOTE:
+                       shf_fprintf(shf, "OQUOTE{%d", ++quotelevel);
+                       break;
+               case CQUOTE:
+                       shf_fprintf(shf, "%d}CQUOTE", quotelevel);
+                       if (quotelevel)
+                               quotelevel--;
+                       else
+                               shf_puts("(err)", shf);
+                       break;
+               case OSUBST:
+                       shf_puts("OSUBST(", shf);
+                       dumpchar(shf, *wp++);
+                       shf_puts(")[", shf);
+                       while ((c = *wp++) != 0)
+                               dumpchar(shf, c);
+                       shf_putc('|', shf);
+                       wp = dumpwdvar_(shf, wp, 0);
+                       break;
+               case CSUBST:
+                       shf_puts("]CSUBST(", shf);
+                       dumpchar(shf, *wp++);
+                       shf_putc(')', shf);
+                       return (wp);
+               case OPAT:
+                       shf_puts("OPAT=", shf);
+                       dumpchar(shf, *wp++);
+                       break;
+               case SPAT:
+                       shf_puts("SPAT", shf);
+                       break;
+               case CPAT:
+                       shf_puts("CPAT", shf);
+                       break;
+               default:
+                       shf_fprintf(shf, "INVAL<%u>", (uint8_t)wp[-1]);
+                       break;
+               }
+               shf_putc(' ', shf);
+       }
+}
+void
+dumpwdvar(struct shf *shf, const char *wp)
+{
+       dumpwdvar_(shf, wp, 0);
+}
+
+void
+dumptree(struct shf *shf, struct op *t)
+{
+       int i;
+       const char **w, *name;
+       struct op *t1;
+       static int nesting = 0;
+
+       for (i = 0; i < nesting; ++i)
+               shf_putc('\t', shf);
+       ++nesting;
+       shf_puts("{tree:" /*}*/, shf);
+       if (t == NULL) {
+               name = "(null)";
+               goto out;
+       }
+       switch (t->type) {
+#define OPEN(x) case x: name = #x; shf_puts(" {" #x ":", shf); /*}*/
+
+       OPEN(TCOM)
+               if (t->vars) {
+                       i = 0;
+                       w = (const char **)t->vars;
+                       while (*w) {
+                               shf_putc('\n', shf);
+                               for (int j = 0; j < nesting; ++j)
+                                       shf_putc('\t', shf);
+                               shf_fprintf(shf, " var%d<", i++);
+                               dumpwdvar(shf, *w++);
+                               shf_putc('>', shf);
+                       }
+               } else
+                       shf_puts(" #no-vars#", shf);
+               if (t->args) {
+                       i = 0;
+                       w = t->args;
+                       while (*w) {
+                               shf_putc('\n', shf);
+                               for (int j = 0; j < nesting; ++j)
+                                       shf_putc('\t', shf);
+                               shf_fprintf(shf, " arg%d<", i++);
+                               dumpwdvar(shf, *w++);
+                               shf_putc('>', shf);
+                       }
+               } else
+                       shf_puts(" #no-args#", shf);
+               break;
+       OPEN(TEXEC)
+ dumpleftandout:
+               t = t->left;
+ dumpandout:
+               shf_putc('\n', shf);
+               dumptree(shf, t);
+               break;
+       OPEN(TPAREN)
+               goto dumpleftandout;
+       OPEN(TPIPE)
+ dumpleftmidrightandout:
+               shf_putc('\n', shf);
+               dumptree(shf, t->left);
+/* middumprightandout: (unused) */
+               shf_fprintf(shf, "/%s:", name);
+ dumprightandout:
+               t = t->right;
+               goto dumpandout;
+       OPEN(TLIST)
+               goto dumpleftmidrightandout;
+       OPEN(TOR)
+               goto dumpleftmidrightandout;
+       OPEN(TAND)
+               goto dumpleftmidrightandout;
+       OPEN(TBANG)
+               goto dumprightandout;
+       OPEN(TDBRACKET)
+               i = 0;
+               w = t->args;
+               while (*w) {
+                       shf_putc('\n', shf);
+                       for (int j = 0; j < nesting; ++j)
+                               shf_putc('\t', shf);
+                       shf_fprintf(shf, " arg%d<", i++);
+                       dumpwdvar(shf, *w++);
+                       shf_putc('>', shf);
+               }
+               break;
+       OPEN(TFOR)
+ dumpfor:
+               shf_fprintf(shf, " str<%s>", t->str);
+               if (t->vars != NULL) {
+                       i = 0;
+                       w = (const char **)t->vars;
+                       while (*w) {
+                               shf_putc('\n', shf);
+                               for (int j = 0; j < nesting; ++j)
+                                       shf_putc('\t', shf);
+                               shf_fprintf(shf, " var%d<", i++);
+                               dumpwdvar(shf, *w++);
+                               shf_putc('>', shf);
+                       }
+               }
+               goto dumpleftandout;
+       OPEN(TSELECT)
+               goto dumpfor;
+       OPEN(TCASE)
+               shf_fprintf(shf, " str<%s>", t->str);
+               i = 0;
+               for (t1 = t->left; t1 != NULL; t1 = t1->right) {
+                       shf_putc('\n', shf);
+                       for (int j = 0; j < nesting; ++j)
+                               shf_putc('\t', shf);
+                       shf_fprintf(shf, " sub%d[(", i);
+                       w = (const char **)t1->vars;
+                       while (*w) {
+                               dumpwdvar(shf, *w);
+                               if (w[1] != NULL)
+                                       shf_putc('|', shf);
+                               ++w;
+                       }
+                       shf_putc(')', shf);
+                       shf_putc('\n', shf);
+                       dumptree(shf, t1->left);
+                       shf_fprintf(shf, " ;%c/%d]", t1->u.charflag, i++);
+               }
+               break;
+       OPEN(TWHILE)
+               goto dumpleftmidrightandout;
+       OPEN(TUNTIL)
+               goto dumpleftmidrightandout;
+       OPEN(TBRACE)
+               goto dumpleftandout;
+       OPEN(TCOPROC)
+               goto dumpleftandout;
+       OPEN(TASYNC)
+               goto dumpleftandout;
+       OPEN(TFUNCT)
+               shf_fprintf(shf, " str<%s> ksh<%s>", t->str,
+                   t->u.ksh_func ? "yes" : "no");
+               goto dumpleftandout;
+       OPEN(TTIME)
+               goto dumpleftandout;
+       OPEN(TIF)
+ dumpif:
+               shf_putc('\n', shf);
+               dumptree(shf, t->left);
+               t = t->right;
+               if (t->left != NULL) {
+                       shf_puts(" /TTHEN:\n", shf);
+                       dumptree(shf, t->left);
+               }
+               if (t->right && t->right->type == TELIF) {
+                       shf_puts(" /TELIF:", shf);
+                       t = t->right;
+                       goto dumpif;
+               }
+               if (t->right != NULL) {
+                       shf_puts(" /TELSE:\n", shf);
+                       dumptree(shf, t->right);
+               }
+               break;
+       OPEN(TEOF)
+ dumpunexpected:
+               shf_puts("unexpected", shf);
+               break;
+       OPEN(TELIF)
+               goto dumpunexpected;
+       OPEN(TPAT)
+               goto dumpunexpected;
+       default:
+               name = "TINVALID";
+               shf_fprintf(shf, "{T<%d>:" /*}*/, t->type);
+               goto dumpunexpected;
+
+#undef OPEN
+       }
+ out:
+       shf_fprintf(shf, /*{*/ " /%s}\n", name);
+       --nesting;
+}
+#endif
index 4e9729e..315294e 100644 (file)
--- a/src/var.c
+++ b/src/var.c
@@ -1,7 +1,7 @@
 /*     $OpenBSD: var.c,v 1.34 2007/10/15 02:16:35 deraadt Exp $        */
 
 /*-
- * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
  *     Thorsten Glaser <tg@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -26,9 +26,9 @@
 #include <sys/sysctl.h>
 #endif
 
-__RCSID("$MirOS: src/bin/mksh/var.c,v 1.110 2010/07/25 11:35:43 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/var.c,v 1.132 2011/09/07 15:24:22 tg Exp $");
 
-/*
+/*-
  * Variables
  *
  * WARNING: unreadable code, needs a rewrite
@@ -37,8 +37,11 @@ __RCSID("$MirOS: src/bin/mksh/var.c,v 1.110 2010/07/25 11:35:43 tg Exp $");
  * otherwise, (val.s + type) contains string value.
  * if (flag&EXPORT), val.s contains "name=value" for E-Z exporting.
  */
+
 static struct tbl vtemp;
 static struct table specials;
+static uint32_t lcg_state = 5381;
+
 static char *formatstr(struct tbl *, const char *);
 static void exportprep(struct tbl *, const char *);
 static int special(const char *);
@@ -47,14 +50,7 @@ static void getspec(struct tbl *);
 static void setspec(struct tbl *);
 static void unsetspec(struct tbl *);
 static int getint(struct tbl *, mksh_ari_t *, bool);
-static mksh_ari_t intval(struct tbl *);
-static struct tbl *arraysearch(struct tbl *, uint32_t);
 static const char *array_index_calc(const char *, bool *, uint32_t *);
-static uint32_t oaathash_update(register uint32_t, register const uint8_t *,
-    register size_t);
-static uint32_t oaathash_finalise(register uint32_t);
-
-uint8_t set_refflag = 0;
 
 /*
  * create a new block for function calls and simple commands
@@ -68,7 +64,8 @@ newblock(void)
 
        l = alloc(sizeof(struct block), ATEMP);
        l->flags = 0;
-       ainit(&l->area); /* todo: could use e->area (l->area => l->areap) */
+       /* TODO: could use e->area (l->area => l->areap) */
+       ainit(&l->area);
        if (!e->loc) {
                l->argc = 0;
                l->argv = empty;
@@ -77,8 +74,8 @@ newblock(void)
                l->argv = e->loc->argv;
        }
        l->exit = l->error = NULL;
-       ktinit(&l->vars, &l->area, 0);
-       ktinit(&l->funs, &l->area, 0);
+       ktinit(&l->area, &l->vars, 0);
+       ktinit(&l->area, &l->funs, 0);
        l->next = e->loc;
        e->loc = l;
 }
@@ -89,12 +86,15 @@ newblock(void)
 void
 popblock(void)
 {
+       ssize_t i;
        struct block *l = e->loc;
        struct tbl *vp, **vpp = l->vars.tbls, *vq;
-       int i;
 
-       e->loc = l->next;       /* pop block */
-       for (i = l->vars.size; --i >= 0; )
+       /* pop block */
+       e->loc = l->next;
+
+       i = 1 << (l->vars.tshift);
+       while (--i >= 0)
                if ((vp = *vpp++) != NULL && (vp->flag&SPECIAL)) {
                        if ((vq = global(vp->name))->flag & ISSET)
                                setspec(vq);
@@ -117,6 +117,7 @@ enum var_specs {
        V_MAX
 };
 
+/* this is biased with -1 relative to VARSPEC_ENUMS */
 static const char * const initvar_names[] = {
 #define VARSPEC_ITEMS
 #include "var_spec.h"
@@ -128,8 +129,9 @@ initvar(void)
        int i = 0;
        struct tbl *tp;
 
-       ktinit(&specials, APERM,
-           /* must be 80% of 2^n (currently 12 specials) */ 16);
+       ktinit(APERM, &specials,
+           /* currently 12 specials -> 80% of 16 (2^4) */
+           4);
        while (i < V_MAX - 1) {
                tp = ktenter(&specials, initvar_names[i],
                    hash(initvar_names[i]));
@@ -138,32 +140,49 @@ initvar(void)
        }
 }
 
-/* Used to calculate an array index for global()/local(). Sets *arrayp to
- * true if this is an array, sets *valp to the array index, returns
+/* common code for several functions below */
+static struct block *
+varsearch(struct block *l, struct tbl **vpp, const char *vn, uint32_t h)
+{
+       register struct tbl *vp;
+
+       if (l) {
+ varsearch_loop:
+               if ((vp = ktsearch(&l->vars, vn, h)) != NULL)
+                       goto varsearch_out;
+               if (l->next != NULL) {
+                       l = l->next;
+                       goto varsearch_loop;
+               }
+       }
+       vp = NULL;
+ varsearch_out:
+       *vpp = vp;
+       return (l);
+}
+
+/*
+ * Used to calculate an array index for global()/local(). Sets *arrayp
+ * to true if this is an array, sets *valp to the array index, returns
  * the basename of the array.
  */
 static const char *
 array_index_calc(const char *n, bool *arrayp, uint32_t *valp)
 {
        const char *p;
-       int len;
+       size_t len;
        char *ap = NULL;
 
        *arrayp = false;
  redo_from_ref:
        p = skip_varname(n, false);
-       if (!set_refflag && (p != n) && ksh_isalphx(n[0])) {
-               struct block *l = e->loc;
+       if (set_refflag == SRF_NOP && (p != n) && ksh_isalphx(n[0])) {
                struct tbl *vp;
                char *vn;
-               uint32_t h;
 
                strndupx(vn, n, p - n, ATEMP);
-               h = hash(vn);
                /* check if this is a reference */
-               do {
-                       vp = ktsearch(&l->vars, vn, h);
-               } while (!vp && (l = l->next));
+               varsearch(e->loc, &vp, vn, hash(vn));
                afree(vn, ATEMP);
                if (vp && (vp->flag & (DEFINED|ASSOC|ARRAY)) ==
                    (DEFINED|ASSOC)) {
@@ -181,7 +200,7 @@ array_index_calc(const char *n, bool *arrayp, uint32_t *valp)
                char *sub, *tmp;
                mksh_ari_t rval;
 
-               /* Calculate the value of the subscript */
+               /* calculate the value of the subscript */
                *arrayp = true;
                strndupx(tmp, p + 1, len - 2, ATEMP);
                sub = substitute(tmp, 0);
@@ -236,7 +255,7 @@ global(const char *n)
                        vp->val.i = kshpid;
                        break;
                case '!':
-                       /* If no job, expand to nothing */
+                       /* if no job, expand to nothing */
                        if ((vp->val.i = j_async()) == 0)
                                vp->flag &= ~(ISSET|INTEGER);
                        break;
@@ -255,17 +274,9 @@ global(const char *n)
                }
                return (vp);
        }
-       for (l = e->loc; ; l = l->next) {
-               vp = ktsearch(&l->vars, n, h);
-               if (vp != NULL) {
-                       if (array)
-                               return (arraysearch(vp, val));
-                       else
-                               return (vp);
-               }
-               if (l->next == NULL)
-                       break;
-       }
+       l = varsearch(e->loc, &vp, n, h);
+       if (vp != NULL)
+               return (array ? arraysearch(vp, val) : vp);
        vp = ktenter(&l->vars, n, h);
        if (array)
                vp = arraysearch(vp, val);
@@ -286,7 +297,7 @@ local(const char *n, bool copy)
        bool array;
        uint32_t h, val;
 
-       /* Check to see if this is an array */
+       /* check to see if this is an array */
        n = array_index_calc(n, &array, &val);
        h = hash(n);
        if (!ksh_isalphx(*n)) {
@@ -298,12 +309,10 @@ local(const char *n, bool copy)
        }
        vp = ktenter(&l->vars, n, h);
        if (copy && !(vp->flag & DEFINED)) {
-               struct block *ll = l;
-               struct tbl *vq = NULL;
+               struct tbl *vq;
 
-               while ((ll = ll->next) && !(vq = ktsearch(&ll->vars, n, h)))
-                       ;
-               if (vq) {
+               varsearch(l->next, &vq, n, h);
+               if (vq != NULL) {
                        vp->flag |= vq->flag &
                            (EXPORT | INTEGER | RDONLY | LJUST | RJUST |
                            ZEROFIL | LCASEV | UCASEV_AL | INT_U | INT_L);
@@ -329,18 +338,23 @@ str_val(struct tbl *vp)
        if ((vp->flag&SPECIAL))
                getspec(vp);
        if (!(vp->flag&ISSET))
-               s = null;               /* special to dollar() */
-       else if (!(vp->flag&INTEGER))   /* string source */
+               /* special to dollar() */
+               s = null;
+       else if (!(vp->flag&INTEGER))
+               /* string source */
                s = vp->val.s + vp->type;
-       else {                          /* integer source */
-               /* worst case number length is when base=2 */
-               /* 1 (minus) + 2 (base, up to 36) + 1 ('#') + number of bits
-                * in the mksh_uari_t + 1 (NUL) */
+       else {
+               /* integer source */
+               mksh_uari_t n;
+               int base;
+               /**
+                * worst case number length is when base == 2:
+                *      1 (minus) + 2 (base, up to 36) + 1 ('#') +
+                *      number of bits in the mksh_uari_t + 1 (NUL)
+                */
                char strbuf[1 + 2 + 1 + 8 * sizeof(mksh_uari_t) + 1];
                const char *digits = (vp->flag & UCASEV_AL) ?
                    digits_uc : digits_lc;
-               mksh_uari_t n;
-               int base;
 
                s = strbuf + sizeof(strbuf);
                if (vp->flag & INT_U)
@@ -349,6 +363,8 @@ str_val(struct tbl *vp)
                        n = (vp->val.i < 0) ? -vp->val.i : vp->val.i;
                base = (vp->type == 0) ? 10 : vp->type;
 
+               if (base == 1 && n == 0)
+                       base = 2;
                if (base == 1) {
                        size_t sz = 1;
 
@@ -375,7 +391,8 @@ str_val(struct tbl *vp)
                        if (!(vp->flag & INT_U) && vp->val.i < 0)
                                *--s = '-';
                }
-               if (vp->flag & (RJUST|LJUST)) /* case already dealt with */
+               if (vp->flag & (RJUST|LJUST))
+                       /* case already dealt with */
                        s = formatstr(vp, s);
                else
                        strdupx(s, s, ATEMP);
@@ -383,20 +400,6 @@ str_val(struct tbl *vp)
        return (s);
 }
 
-/* get variable integer value, with error checking */
-static mksh_ari_t
-intval(struct tbl *vp)
-{
-       mksh_ari_t num;
-       int base;
-
-       base = getint(vp, &num, false);
-       if (base == -1)
-               /* XXX check calls - is error here ok by POSIX? */
-               errorf("%s: bad number", str_val(vp));
-       return (num);
-}
-
 /* set variable to string value */
 int
 setstr(struct tbl *vq, const char *s, int error_ok)
@@ -406,12 +409,13 @@ setstr(struct tbl *vq, const char *s, int error_ok)
 
        error_ok &= ~0x4;
        if ((vq->flag & RDONLY) && !no_ro_check) {
-               warningf(true, "%s: is read only", vq->name);
+               warningf(true, "%s: %s", vq->name, "is read only");
                if (!error_ok)
-                       errorfz();
+                       errorfxz(2);
                return (0);
        }
-       if (!(vq->flag&INTEGER)) { /* string dest */
+       if (!(vq->flag&INTEGER)) {
+               /* string dest */
                if ((vq->flag&ALLOC)) {
                        /* debugging */
                        if (s >= vq->val.s &&
@@ -431,7 +435,8 @@ setstr(struct tbl *vq, const char *s, int error_ok)
                        strdupx(vq->val.s, s, vq->areap);
                        vq->flag |= ALLOC;
                }
-       } else {                /* integer dest */
+       } else {
+               /* integer dest */
                if (!v_evaluate(vq, s, error_ok, true))
                        return (0);
        }
@@ -479,8 +484,6 @@ getint(struct tbl *vp, mksh_ari_t *nump, bool arith)
                return (vp->type);
        }
        s = vp->val.s + vp->type;
-       if (s == NULL)  /* redundant given initial test */
-               s = null;
        base = 10;
        num = 0;
        neg = 0;
@@ -541,7 +544,8 @@ getint(struct tbl *vp, mksh_ari_t *nump, bool arith)
        return (base);
 }
 
-/* convert variable vq to integer variable, setting its value from vp
+/*
+ * convert variable vq to integer variable, setting its value from vp
  * (vq and vp may be the same)
  */
 struct tbl *
@@ -552,17 +556,26 @@ setint_v(struct tbl *vq, struct tbl *vp, bool arith)
 
        if ((base = getint(vp, &num, arith)) == -1)
                return (NULL);
+       setint_n(vq, num);
+       if (vq->type == 0)
+               /* default base */
+               vq->type = base;
+       return (vq);
+}
+
+/* convert variable vq to integer variable, setting its value to num */
+void
+setint_n(struct tbl *vq, mksh_ari_t num)
+{
        if (!(vq->flag & INTEGER) && (vq->flag & ALLOC)) {
                vq->flag &= ~ALLOC;
+               vq->type = 0;
                afree(vq->val.s, vq->areap);
        }
        vq->val.i = num;
-       if (vq->type == 0) /* default base */
-               vq->type = base;
        vq->flag |= ISSET|INTEGER;
        if (vq->flag&SPECIAL)
                setspec(vq);
-       return (vq);
 }
 
 static char *
@@ -572,10 +585,11 @@ formatstr(struct tbl *vp, const char *s)
        char *p, *q;
        size_t psiz;
 
-       olen = utf_mbswidth(s);
+       olen = (int)utf_mbswidth(s);
 
        if (vp->flag & (RJUST|LJUST)) {
-               if (!vp->u2.field)      /* default field width */
+               if (!vp->u2.field)
+                       /* default field width */
                        vp->u2.field = olen;
                nlen = vp->u2.field;
        } else
@@ -649,33 +663,38 @@ exportprep(struct tbl *vp, const char *val)
 {
        char *xp;
        char *op = (vp->flag&ALLOC) ? vp->val.s : NULL;
-       int namelen = strlen(vp->name);
-       int vallen = strlen(val) + 1;
+       size_t namelen, vallen;
+
+       namelen = strlen(vp->name);
+       vallen = strlen(val) + 1;
 
        vp->flag |= ALLOC;
+       /* since name+val are both in memory this can go unchecked */
        xp = alloc(namelen + 1 + vallen, vp->areap);
        memcpy(vp->val.s = xp, vp->name, namelen);
        xp += namelen;
        *xp++ = '=';
-       vp->type = xp - vp->val.s; /* offset to value */
+       /* offset to value */
+       vp->type = xp - vp->val.s;
        memcpy(xp, val, vallen);
        if (op != NULL)
                afree(op, vp->areap);
 }
 
 /*
- * lookup variable (according to (set&LOCAL)),
- * set its attributes (INTEGER, RDONLY, EXPORT, TRACE, LJUST, RJUST, ZEROFIL,
- * LCASEV, UCASEV_AL), and optionally set its value if an assignment.
+ * lookup variable (according to (set&LOCAL)), set its attributes
+ * (INTEGER, RDONLY, EXPORT, TRACE, LJUST, RJUST, ZEROFIL, LCASEV,
+ * UCASEV_AL), and optionally set its value if an assignment.
  */
 struct tbl *
-typeset(const char *var, Tflag set, Tflag clr, int field, int base)
+typeset(const char *var, uint32_t set, uint32_t clr, int field, int base)
 {
        struct tbl *vp;
        struct tbl *vpbase, *t;
        char *tvar;
        const char *val;
-       int len;
+       size_t len;
+       bool vappend = false;
 
        /* check for valid variable name, search for value */
        val = skip_varname(var, false);
@@ -684,55 +703,80 @@ typeset(const char *var, Tflag set, Tflag clr, int field, int base)
        mkssert(var != NULL);
        mkssert(*var != 0);
        if (*val == '[') {
-               if (set_refflag)
-                       errorf("%s: reference variable cannot be an array",
-                           var);
+               if (set_refflag != SRF_NOP)
+                       errorf("%s: %s", var,
+                           "reference variable can't be an array");
                len = array_ref_len(val);
                if (len == 0)
                        return (NULL);
-               /* IMPORT is only used when the shell starts up and is
+               /*
+                * IMPORT is only used when the shell starts up and is
                 * setting up its environment. Allow only simple array
-                * references at this time since parameter/command substitution
-                * is preformed on the [expression] which would be a major
-                * security hole.
+                * references at this time since parameter/command
+                * substitution is preformed on the [expression] which
+                * would be a major security hole.
                 */
                if (set & IMPORT) {
-                       int i;
+                       size_t i;
+
                        for (i = 1; i < len - 1; i++)
                                if (!ksh_isdigit(val[i]))
                                        return (NULL);
                }
                val += len;
        }
-       if (*val == '=')
-               strndupx(tvar, var, val++ - var, ATEMP);
-       else {
-               /* Importing from original environment: must have an = */
+       if (val[0] == '=' || (val[0] == '+' && val[1] == '=')) {
+               strndupx(tvar, var, val - var, ATEMP);
+               if (*val++ == '+') {
+                       ++val;
+                       vappend = true;
+               }
+       } else {
+               /* importing from original environment: must have an = */
                if (set & IMPORT)
                        return (NULL);
                strdupx(tvar, var, ATEMP);
                val = NULL;
-               /* handle foo[*]  foo (whole array) mapping for R39b */
+               /* handle foo[*] => foo (whole array) mapping for R39b */
                len = strlen(tvar);
-               if (len > 3 && tvar[len-3] == '[' && tvar[len-2] == '*' &&
-                   tvar[len-1] == ']')
-                       tvar[len-3] = '\0';
+               if (len > 3 && tvar[len - 3] == '[' && tvar[len - 2] == '*' &&
+                   tvar[len - 1] == ']')
+                       tvar[len - 3] = '\0';
        }
 
-       /* Prevent typeset from creating a local PATH/ENV/SHELL */
+       if (set_refflag == SRF_ENABLE) {
+               const char *qval;
+
+               /* bail out on 'nameref foo+=bar' */
+               if (vappend)
+                       errorfz();
+               /* find value if variable already exists */
+               if ((qval = val) == NULL) {
+                       varsearch(e->loc, &vp, tvar, hash(tvar));
+                       if (vp != NULL)
+                               qval = str_val(vp);
+               }
+               /* silently ignore 'nameref foo=foo' */
+               if (qval != NULL && !strcmp(qval, tvar)) {
+                       afree(tvar, ATEMP);
+                       return (&vtemp);
+               }
+       }
+
+       /* prevent typeset from creating a local PATH/ENV/SHELL */
        if (Flag(FRESTRICTED) && (strcmp(tvar, "PATH") == 0 ||
            strcmp(tvar, "ENV") == 0 || strcmp(tvar, "SHELL") == 0))
-               errorf("%s: restricted", tvar);
+               errorf("%s: %s", tvar, "restricted");
 
-       vp = (set&LOCAL) ? local(tvar, (set & LOCAL_COPY) ? true : false) :
+       vp = (set&LOCAL) ? local(tvar, tobool(set & LOCAL_COPY)) :
            global(tvar);
-       if (set_refflag == 2 && (vp->flag & (ARRAY|ASSOC)) == ASSOC)
+       if (set_refflag == SRF_DISABLE && (vp->flag & (ARRAY|ASSOC)) == ASSOC)
                vp->flag &= ~ASSOC;
-       else if (set_refflag == 1) {
+       else if (set_refflag == SRF_ENABLE) {
                if (vp->flag & ARRAY) {
                        struct tbl *a, *tmp;
 
-                       /* Free up entire array */
+                       /* free up entire array */
                        for (a = vp->u.array; a; ) {
                                tmp = a;
                                a = a->u.array;
@@ -750,21 +794,24 @@ typeset(const char *var, Tflag set, Tflag clr, int field, int base)
 
        vpbase = (vp->flag & ARRAY) ? global(arrayname(var)) : vp;
 
-       /* only allow export flag to be set. AT&T ksh allows any attribute to
-        * be changed which means it can be truncated or modified (-L/-R/-Z/-i)
+       /*
+        * only allow export flag to be set; AT&T ksh allows any
+        * attribute to be changed which means it can be truncated or
+        * modified (-L/-R/-Z/-i)
         */
        if ((vpbase->flag&RDONLY) &&
            (val || clr || (set & ~EXPORT)))
                /* XXX check calls - is error here ok by POSIX? */
-               errorf("%s: is read only", tvar);
+               errorfx(2, "%s: %s", tvar, "is read only");
        afree(tvar, ATEMP);
 
        /* most calls are with set/clr == 0 */
        if (set | clr) {
                bool ok = true;
 
-               /* XXX if x[0] isn't set, there will be problems: need to have
-                * one copy of attributes for arrays...
+               /*
+                * XXX if x[0] isn't set, there will be problems: need
+                * to have one copy of attributes for arrays...
                 */
                for (t = vpbase; t; t = t->u.array) {
                        bool fake_assign;
@@ -791,8 +838,9 @@ typeset(const char *var, Tflag set, Tflag clr, int field, int base)
                                t->flag &= ~ALLOC;
                        }
                        t->flag = (t->flag | set) & ~clr;
-                       /* Don't change base if assignment is to be done,
-                        * in case assignment fails.
+                       /*
+                        * Don't change base if assignment is to be
+                        * done, in case assignment fails.
                         */
                        if ((set & INTEGER) && base > 0 && (!val || t != vp))
                                t->type = base;
@@ -800,9 +848,11 @@ typeset(const char *var, Tflag set, Tflag clr, int field, int base)
                                t->u2.field = field;
                        if (fake_assign) {
                                if (!setstr(t, s, KSH_RETURN_ERROR)) {
-                                       /* Somewhat arbitrary action here:
-                                        * zap contents of variable, but keep
-                                        * the flag settings.
+                                       /*
+                                        * Somewhat arbitrary action
+                                        * here: zap contents of
+                                        * variable, but keep the flag
+                                        * settings.
                                         */
                                        ok = false;
                                        if (t->flag & INTEGER)
@@ -823,15 +873,26 @@ typeset(const char *var, Tflag set, Tflag clr, int field, int base)
        }
 
        if (val != NULL) {
+               char *tval;
+
+               if (vappend) {
+                       tval = shf_smprintf("%s%s", str_val(vp), val);
+                       val = tval;
+               } else
+                       tval = NULL;
+
                if (vp->flag&INTEGER) {
                        /* do not zero base before assignment */
                        setstr(vp, val, KSH_UNWIND_ERROR | 0x4);
-                       /* Done after assignment to override default */
+                       /* done after assignment to override default */
                        if (base > 0)
                                vp->type = base;
                } else
                        /* setstr can't fail (readonly check already done) */
                        setstr(vp, val, KSH_RETURN_ERROR | 0x4);
+
+               if (tval != NULL)
+                       afree(tval, ATEMP);
        }
 
        /* only x[0] is ever exported, so use vpbase */
@@ -855,7 +916,7 @@ unset(struct tbl *vp, int flags)
        if ((vp->flag & ARRAY) && (flags & 1)) {
                struct tbl *a, *tmp;
 
-               /* Free up entire array */
+               /* free up entire array */
                for (a = vp->u.array; a; ) {
                        tmp = a;
                        a = a->u.array;
@@ -869,20 +930,22 @@ unset(struct tbl *vp, int flags)
                vp->flag &= ~(ALLOC|ISSET);
                return;
        }
-       /* If foo[0] is being unset, the remainder of the array is kept... */
+       /* if foo[0] is being unset, the remainder of the array is kept... */
        vp->flag &= SPECIAL | ((flags & 1) ? 0 : ARRAY|DEFINED);
        if (vp->flag & SPECIAL)
-               unsetspec(vp);  /* responsible for 'unspecial'ing var */
+               /* responsible for 'unspecial'ing var */
+               unsetspec(vp);
 }
 
-/* return a pointer to the first char past a legal variable name (returns the
- * argument if there is no legal name, returns a pointer to the terminating
- * NUL if whole string is legal).
+/*
+ * Return a pointer to the first char past a legal variable name
+ * (returns the argument if there is no legal name, returns a pointer to
+ * the terminating NUL if whole string is legal).
  */
 const char *
 skip_varname(const char *s, int aok)
 {
-       int alen;
+       size_t alen;
 
        if (s && ksh_isalphx(*s)) {
                while (*++s && ksh_isalnux(*s))
@@ -896,7 +959,8 @@ skip_varname(const char *s, int aok)
 /* Return a pointer to the first character past any legal variable name */
 const char *
 skip_wdvarname(const char *s,
-    int aok)                           /* skip array de-reference? */
+    /* skip array de-reference? */
+    bool aok)
 {
        if (s[0] == CHAR && ksh_isalphx(s[1])) {
                do {
@@ -908,7 +972,7 @@ skip_wdvarname(const char *s,
                        char c;
                        int depth = 0;
 
-                       while (1) {
+                       while (/* CONSTCOND */ 1) {
                                if (p[0] != CHAR)
                                        break;
                                c = p[1];
@@ -927,7 +991,7 @@ skip_wdvarname(const char *s,
 
 /* Check if coded string s is a variable name */
 int
-is_wdvarname(const char *s, int aok)
+is_wdvarname(const char *s, bool aok)
 {
        const char *p = skip_wdvarname(s, aok);
 
@@ -940,7 +1004,8 @@ is_wdvarassign(const char *s)
 {
        const char *p = skip_wdvarname(s, true);
 
-       return (p != s && p[0] == CHAR && p[1] == '=');
+       return (p != s && p[0] == CHAR &&
+           (p[1] == '=' || (p[1] == '+' && p[2] == CHAR && p[3] == '=')));
 }
 
 /*
@@ -949,14 +1014,16 @@ is_wdvarassign(const char *s)
 char **
 makenv(void)
 {
+       ssize_t i;
        struct block *l;
        XPtrV denv;
        struct tbl *vp, **vpp;
-       int i;
 
        XPinit(denv, 64);
-       for (l = e->loc; l != NULL; l = l->next)
-               for (vpp = l->vars.tbls, i = l->vars.size; --i >= 0; )
+       for (l = e->loc; l != NULL; l = l->next) {
+               vpp = l->vars.tbls;
+               i = 1 << (l->vars.tshift);
+               while (--i >= 0)
                        if ((vp = *vpp++) != NULL &&
                            (vp->flag&(ISSET|EXPORT)) == (ISSET|EXPORT)) {
                                struct block *l2;
@@ -979,90 +1046,11 @@ makenv(void)
                                }
                                XPput(denv, vp->val.s);
                        }
+       }
        XPput(denv, NULL);
        return ((char **)XPclose(denv));
 }
 
-/* Bob Jenkins' one-at-a-time hash */
-static uint32_t
-oaathash_update(register uint32_t h, register const uint8_t *cp,
-    register size_t n)
-{
-       while (n--) {
-               h += *cp++;
-               h += h << 10;
-               h ^= h >> 6;
-       }
-
-       return (h);
-}
-
-static uint32_t
-oaathash_finalise(register uint32_t h)
-{
-       h += h << 3;
-       h ^= h >> 11;
-       h += h << 15;
-
-       return (h);
-}
-
-uint32_t
-oaathash_full(register const uint8_t *bp)
-{
-       register uint32_t h = 0;
-       register uint8_t c;
-
-       while ((c = *bp++)) {
-               h += c;
-               h += h << 10;
-               h ^= h >> 6;
-       }
-
-       return (oaathash_finalise(h));
-}
-
-void
-change_random(const void *vp, size_t n)
-{
-       register uint32_t h = 0x100;
-#if defined(__OpenBSD__)
-       int mib[2];
-       uint8_t k[3];
-       size_t klen;
-#endif
-
-       kshstate_v.cr_dp = vp;
-       kshstate_v.cr_dsz = n;
-       gettimeofday(&kshstate_v.cr_tv, NULL);
-       h = oaathash_update(oaathash_update(h, (void *)&kshstate_v,
-           sizeof(kshstate_v)), vp, n);
-       kshstate_v.lcg_state_ = oaathash_finalise(h);
-
-#if defined(__OpenBSD__)
-       /* OpenBSD, MirBSD: proper kernel entropy comes at zero cost */
-
-       mib[0] = CTL_KERN;
-       mib[1] = KERN_ARND;
-       klen = sizeof(k);
-       sysctl(mib, 2, k, &klen, &kshstate_v.lcg_state_,
-           sizeof(kshstate_v.lcg_state_));
-       /* we ignore failures and take in k anyway */
-       h = oaathash_update(h, k, sizeof(k));
-       kshstate_v.lcg_state_ = oaathash_finalise(h);
-#elif defined(MKSH_A4PB)
-       /* forced by the user to use arc4random_pushb(3) • Cygwin? */
-       {
-               uint32_t prv;
-
-               prv = arc4random_pushb(&kshstate_v.lcg_state_,
-                   sizeof(kshstate_v.lcg_state_));
-               h = oaathash_update(h, &prv, sizeof(prv));
-       }
-       kshstate_v.lcg_state_ = oaathash_finalise(h);
-#endif
-}
-
 /*
  * handle special variables with side effects - PATH, SECONDS.
  */
@@ -1117,8 +1105,7 @@ getspec(struct tbl *vp)
                 * this is the same Linear Congruential PRNG as Borland
                 * C/C++ allegedly uses in its built-in rand() function
                 */
-               i = ((kshstate_v.lcg_state_ =
-                   22695477 * kshstate_v.lcg_state_ + 1) >> 16) & 0x7FFF;
+               i = ((lcg_state = 22695477 * lcg_state + 1) >> 16) & 0x7FFF;
                break;
        case V_HISTSIZE:
                i = histsize;
@@ -1146,7 +1133,7 @@ getspec(struct tbl *vp)
                return;
        }
        vp->flag &= ~SPECIAL;
-       setint(vp, i);
+       setint_n(vp, i);
        vp->flag |= SPECIAL;
 }
 
@@ -1163,7 +1150,8 @@ setspec(struct tbl *vp)
                        afree(path, APERM);
                s = str_val(vp);
                strdupx(path, s, APERM);
-               flushcom(1);    /* clear tracked aliases */
+               /* clear tracked aliases */
+               flushcom(true);
                return;
        case V_IFS:
                setctypes(s = str_val(vp), C_IFS);
@@ -1174,28 +1162,25 @@ setspec(struct tbl *vp)
                        afree(tmpdir, APERM);
                        tmpdir = NULL;
                }
-               /* Use tmpdir iff it is an absolute path, is writable and
-                * searchable and is a directory...
+               /*
+                * Use tmpdir iff it is an absolute path, is writable
+                * and searchable and is a directory...
                 */
                {
                        struct stat statb;
 
                        s = str_val(vp);
+                       /* LINTED use of access */
                        if (s[0] == '/' && access(s, W_OK|X_OK) == 0 &&
                            stat(s, &statb) == 0 && S_ISDIR(statb.st_mode))
                                strdupx(tmpdir, s, APERM);
                }
-               break;
+               return;
 #if HAVE_PERSISTENT_HISTORY
        case V_HISTFILE:
                sethistfile(str_val(vp));
-               break;
+               return;
 #endif
-       case V_TMOUT:
-               /* AT&T ksh seems to do this (only listen if integer) */
-               if (vp->flag & INTEGER)
-                       ksh_tmout = vp->val.i >= 0 ? vp->val.i : 0;
-               break;
 
        /* common sub-cases */
        case V_OPTIND:
@@ -1205,8 +1190,14 @@ setspec(struct tbl *vp)
        case V_RANDOM:
        case V_SECONDS:
        case V_LINENO:
+       case V_TMOUT:
                vp->flag &= ~SPECIAL;
-               i = intval(vp);
+               if (getint(vp, &i, false) == -1) {
+                       s = str_val(vp);
+                       if (st != V_RANDOM)
+                               errorf("%s: %s: %s", vp->name, "bad number", s);
+                       i = hash(s);
+               }
                vp->flag |= SPECIAL;
                break;
        default:
@@ -1236,7 +1227,7 @@ setspec(struct tbl *vp)
                 * mksh R39d+ no longer has the traditional repeatability
                 * of $RANDOM sequences, but always retains state
                 */
-               change_random(&i, sizeof(i));
+               rndset((long)i);
                break;
        case V_SECONDS:
                {
@@ -1250,6 +1241,9 @@ setspec(struct tbl *vp)
                /* The -1 is because line numbering starts at 1. */
                user_lineno = (unsigned int)i - current_lineno - 1;
                break;
+       case V_TMOUT:
+               ksh_tmout = i >= 0 ? i : 0;
+               break;
        }
 }
 
@@ -1261,7 +1255,8 @@ unsetspec(struct tbl *vp)
                if (path)
                        afree(path, APERM);
                strdupx(path, def_path, APERM);
-               flushcom(1);    /* clear tracked aliases */
+               /* clear tracked aliases */
+               flushcom(true);
                break;
        case V_IFS:
                setctypes(" \t\n", C_IFS);
@@ -1277,7 +1272,8 @@ unsetspec(struct tbl *vp)
        case V_LINENO:
        case V_RANDOM:
        case V_SECONDS:
-       case V_TMOUT:           /* AT&T ksh leaves previous value in place */
+       case V_TMOUT:
+               /* AT&T ksh leaves previous value in place */
                unspecial(vp->name);
                break;
 
@@ -1295,14 +1291,14 @@ unsetspec(struct tbl *vp)
  * Search for (and possibly create) a table entry starting with
  * vp, indexed by val.
  */
-static struct tbl *
+struct tbl *
 arraysearch(struct tbl *vp, uint32_t val)
 {
        struct tbl *prev, *curr, *news;
        size_t len;
 
        vp->flag = (vp->flag | (ARRAY|DEFINED)) & ~ASSOC;
-       /* The table entry is always [0] */
+       /* the table entry is always [0] */
        if (val == 0)
                return (vp);
        prev = vp;
@@ -1317,9 +1313,10 @@ arraysearch(struct tbl *vp, uint32_t val)
                news = curr;
        } else
                news = NULL;
-       len = strlen(vp->name) + 1;
        if (!news) {
-               news = alloc(offsetof(struct tbl, name[0]) + len, vp->areap);
+               len = strlen(vp->name);
+               checkoktoadd(len, 1 + offsetof(struct tbl, name[0]));
+               news = alloc(offsetof(struct tbl, name[0]) + ++len, vp->areap);
                memcpy(news->name, vp->name, len);
        }
        news->flag = (vp->flag & ~(ALLOC|DEFINED|ISSET|SPECIAL)) | AINDEX;
@@ -1328,22 +1325,24 @@ arraysearch(struct tbl *vp, uint32_t val)
        news->u2.field = vp->u2.field;
        news->ua.index = val;
 
-       if (curr != news) {             /* not reusing old array entry */
+       if (curr != news) {
+               /* not reusing old array entry */
                prev->u.array = news;
                news->u.array = curr;
        }
        return (news);
 }
 
-/* Return the length of an array reference (eg, [1+2]) - cp is assumed
- * to point to the open bracket. Returns 0 if there is no matching closing
- * bracket.
+/*
+ * Return the length of an array reference (eg, [1+2]) - cp is assumed
+ * to point to the open bracket. Returns 0 if there is no matching
+ * closing bracket.
  */
-int
+size_t
 array_ref_len(const char *cp)
 {
        const char *s = cp;
-       int c;
+       char c;
        int depth = 0;
 
        while ((c = *s++) && (c != ']' || --depth))
@@ -1377,32 +1376,50 @@ mksh_uari_t
 set_array(const char *var, bool reset, const char **vals)
 {
        struct tbl *vp, *vq;
-       mksh_uari_t i;
+       mksh_uari_t i = 0, j = 0;
        const char *ccp;
 #ifndef MKSH_SMALL
-       char *cp;
-       mksh_uari_t j;
+       char *cp = NULL;
+       size_t n;
 #endif
 
        /* to get local array, use "typeset foo; set -A foo" */
-       vp = global(var);
+#ifndef MKSH_SMALL
+       n = strlen(var);
+       if (n > 0 && var[n - 1] == '+') {
+               /* append mode */
+               reset = false;
+               strndupx(cp, var, n - 1, ATEMP);
+       }
+#define CPORVAR        (cp ? cp : var)
+#else
+#define CPORVAR        var
+#endif
+       vp = global(CPORVAR);
 
        /* Note: AT&T ksh allows set -A but not set +A of a read-only var */
        if ((vp->flag&RDONLY))
-               errorf("%s: is read only", var);
+               errorfx(2, "%s: %s", CPORVAR, "is read only");
        /* This code is quite non-optimal */
        if (reset)
                /* trash existing values and attributes */
                unset(vp, 1);
-       /* todo: would be nice for assignment to completely succeed or
+       /*
+        * TODO: would be nice for assignment to completely succeed or
         * completely fail. Only really effects integer arrays:
         * evaluation of some of vals[] may fail...
         */
-       i = 0;
 #ifndef MKSH_SMALL
-       j = 0;
-#else
-#define j i
+       if (cp != NULL) {
+               /* find out where to set when appending */
+               for (vq = vp; vq; vq = vq->u.array) {
+                       if (!(vq->flag & ISSET))
+                               continue;
+                       if (arrayindex(vq) >= j)
+                               j = arrayindex(vq) + 1;
+               }
+               afree(cp, ATEMP);
+       }
 #endif
        while ((ccp = vals[i])) {
 #ifndef MKSH_SMALL
@@ -1432,9 +1449,7 @@ set_array(const char *var, bool reset, const char **vals)
                /* would be nice to deal with errors here... (see above) */
                setstr(vq, ccp, KSH_RETURN_ERROR);
                i++;
-#ifndef MKSH_SMALL
                j++;
-#endif
        }
 
        return (i);
@@ -1448,7 +1463,7 @@ change_winsz(void)
 #ifdef TIOCGWINSZ
                if (tty_fd < 0)
                        /* non-FTALKING, try to get an fd anyway */
-                       tty_init(false, false);
+                       tty_init(true, false);
 #endif
                x_cols = -1;
        }
@@ -1479,12 +1494,42 @@ change_winsz(void)
 }
 
 uint32_t
-evilhash(const char *s)
+hash(const void *s)
 {
-       register uint32_t h = 0x100;
+       register uint32_t h;
+
+       NZATInit(h);
+       NZATUpdateString(h, s);
+       NZATFinish(h);
+       return (h);
+}
+
+void
+rndset(long v)
+{
+       register uint32_t h;
+
+       NZATInit(h);
+       NZATUpdateMem(h, &lcg_state, sizeof(lcg_state));
+       NZATUpdateMem(h, &v, sizeof(v));
+
+#if defined(arc4random_pushb_fast) || defined(MKSH_A4PB)
+       /*
+        * either we have very chap entropy get and push available,
+        * with malloc() pulling in this code already anyway, or the
+        * user requested us to use the old functions
+        */
+       lcg_state = h;
+       NZAATFinish(lcg_state);
+#if defined(arc4random_pushb_fast)
+       arc4random_pushb_fast(&lcg_state, sizeof(lcg_state));
+       lcg_state = arc4random();
+#else
+       lcg_state = arc4random_pushb(&lcg_state, sizeof(lcg_state));
+#endif
+       NZATUpdateMem(h, &lcg_state, sizeof(lcg_state));
+#endif
 
-       h = oaathash_update(h, (void *)&kshstate_f, sizeof(kshstate_f));
-       kshstate_f.h = oaathash_full((const uint8_t *)s);
-       return (oaathash_finalise(oaathash_update(h,
-           (void *)&kshstate_f.h, sizeof(kshstate_f.h))));
+       NZAATFinish(h);
+       lcg_state = h;
 }
index 4035cc9..b3bef4b 100644 (file)
@@ -1,5 +1,5 @@
 #if defined(VARSPEC_DEFNS)
-__RCSID("$MirOS: src/bin/mksh/var_spec.h,v 1.1 2009/09/26 03:40:03 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/var_spec.h,v 1.2 2011/06/05 19:58:21 tg Exp $");
 #define FN(name)                       /* nothing */
 #elif defined(VARSPEC_ENUMS)
 #define FN(name)                       V_##name,
@@ -13,6 +13,8 @@ __RCSID("$MirOS: src/bin/mksh/var_spec.h,v 1.1 2009/09/26 03:40:03 tg Exp $");
 #define F0 FN
 #endif
 
+/* NOTE: F0 are skipped for the ITEMS array, only FN generate names */
+
 /* 0 is always V_NONE */
 F0(NONE)